[{"content":"你这个 DNS 系统的核心定位是什么？ 答：这是一个控制面的 DNS 平台，核心不是单纯的记录管理后台，而是“本地期望态管理 + 多 Provider 聚合 + 私有DNS + 注册中心 + 运维工具 + 防攻击 + 域名优化的平台”。\n提供了：\n智能DNS，geo+view+isp Provider 聚合：反向控制器 证书托管 域名资产管理 域名托底 高级功能：LB，状态，防封等 运维自动化 高级安全防护，解耦+ACL 为什么要自己做一层 DNS 控制面，而不是直接用 Cloudflare 控制台？ 答：因为企业场景通常有多云、多账号、多环境隔离和统一审计需求。 直接用各家 Provider 控制台会带来几个问题：\n入口分散，缺少统一权限和审计 多 Provider 切换成本高 无法做统一的记录模型和调度策略 无法做企业内部自动化，比如批量变更、回滚、任务化切换、漂移检测 他价值就是把这些统一起来。\n你这个 DNS 系统和普通 DNS 管理后台最大的区别是什么？ 答：普通管理后台更多是 CRUD 记录，而这个系统是一个完整的 DNS 控制面。除了 Zone 和 Record 管理，还包括多 Provider 聚合、任务调度、HTTPDNS、证书管理、智能 DNS、防封防污染、私有 DNS、服务注册发现等能力。 也就是说，它不是“改记录的页面”，而是“统一的 DNS 治理平台”。\n为什么要做多 Provider 聚合，而不是绑定单一厂商？ 答：多 Provider 聚合主要解决三类问题：\n降低单厂商依赖风险 支持迁移和容灾切换 统一控制多云 DNS 配置 很多企业线上域名分散在 Cloudflare、Route53、Namecheap、阿里云、DNSPod 等不同平台。Hermes 把这些平台抽象成统一 Provider 层，让业务只关心 Hermes 的标准 Record 模型，而不是各厂商的控制台差异。\n你们怎么保证 Hermes 和云 Provider 的一致性？ 答：本地数据库是 source of truth。Provider 只是实际承载解析的远端系统。所以用户所有变更先写 Hermes，再通过异步 Job 把远端 Provider 收敛到 Hermes 当前状态，而不是反过来以 Provider 控制台为准。\n答：Hermes 本地数据库是 source of truth，Provider 是远端实际状态。 一致性不是靠“某次写成功”，而是靠“持续 reconcile”：\n本地变更触发同步任务 周期性做 drift detect 比较 Hermes 期望态和 Provider 实际态 自动修复或告警 这个思路借鉴了 Kubernetes controller，但不会照搬 reflector/watch。\nHTTPDNS 在这个系统里的价值是什么？ 答：HTTPDNS 主要是为了解决本地运营商 DNS 劫持、污染和解析不可控的问题。\n传统域名解析依赖系统 DNS，而 HTTPDNS 由客户端直接请求 Hermes 的 HTTP 接口获取解析结果，可以做到：\n绕过本地 DNS 污染 配合客户端 SDK 做优选 提供更可控的返回策略 支持业务侧灰度和线路调度 所以 HTTPDNS 更像是“应用层可编程解析服务”。\n你们的 HTTPDNS 和普通 DNS 查询相比，有什么优势和代价？ 答：优势是：\n不依赖本地系统 DNS 更容易做鉴权、限流、日志分析 能结合地域、设备、业务标签做动态返回 更适合 App、移动端和 SDK 集成 代价是：\n客户端要接 SDK 或改解析逻辑 不如标准 DNS 通用 需要额外考虑接口性能和缓存策略 所以 HTTPDNS 适合对可控性要求高的业务场景，不是完全替代标准 DNS。\n智能 DNS 在你们系统里怎么实现？ 答：智能 DNS 的核心是“同一个域名，根据不同请求属性返回不同解析结果”。\n常见维度包括：\n地域 运营商 网络延迟 健康状态 环境标签 业务优先级 实现上通常会结合：\nView 做逻辑隔离 GeoIP 做地域识别 健康检查做故障摘除 权重或优选策略做流量分配 所以它不是简单的 A 记录管理，而是一个调度决策层。\n你们为什么设计了 View？它解决什么问题？ 答：View 主要解决“同一域名在不同请求上下文下返回不同结果”的问题。\n比如：\n内网和外网解析不同 测试环境和生产环境隔离 不同业务线看到不同结果 安全场景下对某些来源直接返回特殊记录 它本质上是 DNS 决策前的流量分流层。\n私有 DNS 这个功能的核心场景是什么？ 答：私有 DNS 更偏企业内部基础设施。主要场景有：\n内网服务发现 办公网、IDC、专线环境域名解析 内外网同域不同解析 混合云/多机房统一命名服务 它和公有 DNS 最大的差异在于，私有 DNS 更强调内网拓扑、服务生命周期、环境隔离和低延迟访问。\n私有 DNS 和公有 DNS 在架构设计上有什么不同？ 答：公有 DNS 更关注全球可达性、抗污染、线路调度和外部解析质量；\n私有 DNS 更关注：\n企业内部命名规范 服务注册发现 与 K8s/Nacos 等系统联动 内网隔离和权限控制 多机房拓扑一致性 所以私有 DNS 往往会和服务发现系统结合得更紧。\n证书管理为什么要放进 DNS 平台？ 答：因为很多证书自动化场景和 DNS 强相关，尤其是 DNS-01 Challenge。\n把证书管理放进 DNS 平台有几个优势：\n域名资产和证书资产统一管理 自动申请、续期和 TXT 验证闭环 支持通配符证书 证书过期和域名过期可统一告警 对企业来说，这样能把域名治理和 HTTPS 治理放在同一控制面里。\n证书自动续期最大的难点是什么？ 答：最大的难点不是调用 ACME 接口，而是“分布式环境下的协调和可靠性”。\n比如：\n多节点不能重复申请同一证书 续期失败要重试和告警 DNS-01 TXT 验证要保证生效 通配符证书和多 SAN 证书管理更复杂 续期后证书分发要可追踪 所以这类场景很适合结合 Job 调度和状态机做。\n你们提到防封、防污染，这块主要解决什么问题？ 答：主要解决域名在复杂网络环境下的可用性问题。常见风险包括：\n运营商 DNS 劫持 污染返回错误 IP 域名被针对性封锁 线路访问质量下降 某些地区或运营商被拦截 Hermes 的设计里，这块不是单点功能，而是组合能力：HTTPDNS、多上游对比、DoH/DoQ、结果验证、污染 IP 过滤、域名轮换、优选线路等一起发挥作用。\n防污染和防封在技术上有什么区别？ 答：两者相关，但重点不同。\n防污染更偏“解析结果被篡改或干扰” 例如错误 IP、假应答、运营商劫持 防封更偏“域名或入口被识别和阻断” 例如某些域名被屏蔽、某些入口 IP 被封 所以防污染侧重解析质量校验和可信上游，防封侧重入口漂移、域名轮换、CDN 隐藏、证书自动化等能力。\n你们怎么做健康检查和自动切换？ 答：核心思路是把“解析配置”和“后端可用性”联动起来。\n一般流程是：\n后台周期性做 HTTP/TCP/自定义健康探测 如果某个目标不可用，更新其健康状态 智能 DNS 在返回记录时跳过不健康节点 必要时通过同步任务把某些记录切换到备用目标 这样 DNS 就不只是返回静态 IP，而是参与流量治理。\n为什么说 DNS 平台也需要任务调度系统？ 答：因为很多 DNS 相关能力本质上都是后台异步任务：\nProvider 同步 Drift 检测 Provider 切换 WHOIS 刷新 证书续期 健康检查 清理和补偿任务 如果没有统一调度系统，这些能力会散落在业务代码里，难以统一重试、审计和可视化。\n你们如何做域名资产管理？ 答：域名资产管理不仅是保存域名列表，还包括：\n域名注册商信息 创建时间、过期时间 WHOIS 状态 NameServer 信息 DNSSEC 状态 是否开启监控 是否需要续费告警 这部分对企业很重要，因为很多故障不是解析配置错，而是域名过期、NS 被改、注册信息异常。\n在性能上最关注的是什么？ 控制面性能 关注后台写入、任务调度、批量同步、查询管理接口响应 数据面性能 关注 DNS 查询 QPS、缓存命中率、延迟和故障隔离 设计里强调内存缓存和查询隔离，是因为面对恶意扫描和高频查询时，不能让数据库被轻易打穿。\n面对恶意子域扫描，你们怎么避免数据库被打穿？ 答：这类问题不能只靠数据库优化，关键是前置缓存和故障隔离。\n设计上会采用：\nL1 内存缓存 未命中异常检测 恶意来源识别 白名单/沙箱策略 只对正常流量回源 核心思想是：未知恶意请求不能无限透传到底层存储。\n如果面试官问，这个项目最有技术含量的点是什么，你会怎么回答？ 答：我会说这个项目最有价值的，不是单一功能，而是把多个基础能力整合成统一 DNS 控制面。\n难点在于：\n用本地模型统一抽象多 Provider 通过异步 Job 保证最终一致性 用 HTTPDNS、智能 DNS 和防污染提升解析可控性 用证书、域名资产、私有 DNS、服务发现把平台能力从“解析管理”扩展到“企业名字服务治理” 它本质上是一个面向企业基础设施的 DNS 平台，不只是后台 CRUD。\n为什么不直接选择 Route53、Cloudflare 这类成熟平台，而要做你这个系统？ 答：因为 Route53、Cloudflare 这类平台本质上是“单厂商 DNS 服务”，而 Hermes 解决的是“企业级统一 DNS 治理”的问题，两者定位不一样。\n如果直接用单一平台，会有几个明显限制：\n厂商绑定强 业务会被某一家 Provider 的能力、计费、区域覆盖和产品策略绑住，迁移成本高。\n多云统一治理差 很多企业现实里不是只有 Route53，往往同时有 Cloudflare、Namecheap、阿里云、DNSPod、私有 DNS。单一平台没法统一管理这些资产。\n企业内网场景支持不完整 Route53 这类更偏公有云 DNS 服务，不会替你统一处理企业里的私有 DNS、内外网同域、服务注册发现、混合云命名体系。\n缺少统一控制面 企业通常需要统一权限、审计、审批、资产管理、证书联动、WHOIS 监控、漂移检测、Provider 切换，这些能力如果全压在单一云厂商上，灵活性不够。\n防封、防污染、HTTPDNS、智能调度策略未必贴合业务\n公有平台提供的是通用能力，但企业业务常常要更强的可编排性，比如按业务标签、内外网、地区、健康状态、风控策略做动态调度。\n所以 Hermes 的价值不是“比 Route53 更会做权威 DNS 解析”，而是：\n把多个 DNS 平台统一抽象成一个控制面 让 Hermes 成为企业自己的 source of truth 支持多 Provider 容灾、迁移和切换 把公有 DNS、私有 DNS、HTTPDNS、证书、资产、安全、服务发现放到同一个治理体系里 一句话总结就是：\nRoute53 是一个优秀的 DNS Provider，而 Hermes 是一个面向企业的 DNS 治理平台。我们不是替代某一家 Provider 的底层解析能力，而是统一管理和编排这些能力。\n如果面试官继续追问，你还可以补一句：\n“如果公司业务规模小、单云、没有内网 DNS 和多 Provider 治理需求，那直接用 Route53 很合理；但一旦进入多云、多环境、多团队协作和基础设施治理阶段，就会需要 Hermes 这种控制面。”\n","permalink":"https://www.161616.top/interview-domain-dns/","summary":"你这个 DNS 系统的核心定位是什么？ 答：这是一个控制面的 DNS 平台，核心不是单纯的记录管理后台，而是“本地期望态管理 + 多 Provider 聚合 + 私有DNS + 注册中心 + 运维工具 + 防攻击 + 域名优化的平台”。\n提供了：\n智能DNS，geo+view+isp Provider 聚合：反向控制器 证书托管 域名资产管理 域名托底 高级功能：LB，状态，防封等 运维自动化 高级安全防护，解耦+ACL 为什么要自己做一层 DNS 控制面，而不是直接用 Cloudflare 控制台？ 答：因为企业场景通常有多云、多账号、多环境隔离和统一审计需求。 直接用各家 Provider 控制台会带来几个问题：\n入口分散，缺少统一权限和审计 多 Provider 切换成本高 无法做统一的记录模型和调度策略 无法做企业内部自动化，比如批量变更、回滚、任务化切换、漂移检测 他价值就是把这些统一起来。\n你这个 DNS 系统和普通 DNS 管理后台最大的区别是什么？ 答：普通管理后台更多是 CRUD 记录，而这个系统是一个完整的 DNS 控制面。除了 Zone 和 Record 管理，还包括多 Provider 聚合、任务调度、HTTPDNS、证书管理、智能 DNS、防封防污染、私有 DNS、服务注册发现等能力。 也就是说，它不是“改记录的页面”，而是“统一的 DNS 治理平台”。\n为什么要做多 Provider 聚合，而不是绑定单一厂商？ 答：多 Provider 聚合主要解决三类问题：","title":"域名系统面试预测"},{"content":"安装 macFUSE + NTFS-3G (免费) jsx 1 2 brew install macfuse brew install gromgit/fuse/ntfs-3g 查看磁盘\njsx 1 diskutil list 安装完成后会在“隐私与安全性”中提示需要启动系统扩展\n启用内核扩展 点击启动系统扩展时候会触发重启\n重启后开机一直按住开机键直到页面提示“正在载入启动选项”\n输入密码后进入，之后在顶部菜单栏选择“工具” → “启东安全性实用工具”，选择磁盘，安全策略\n选择降低安全性\n重启后再回到“隐私与安全性”点允许\n完成之后插上你的NTFS移动硬盘，macOS识别后在终端使用以下命令查找硬盘分区的名称，如diskXsY，X表示磁盘，Y表示分区\ndisk4 整个物理磁盘 disk4s1 第1个分区 jsx 1 diskutil list 找到你的硬盘例如，第几个分区就选择几\njsx 1 /dev/disk4s1 执行挂载\njsx 1 sudo ntfs-3g /dev/disk4s1 /Volumes/NTFS 拷贝数据\n在拷贝数据时候，cp 有时候不能用，例如\njsx 1 2 3 % sudo cp ~/Downloads/Microsoft-Windows-LanguageFeatures-Basic-en-us-Package-amd64.cab /Volumes/NTFS/ cp: /Volumes/NTFS/Microsoft-Windows-LanguageFeatures-Basic-en-us-Package-amd64.cab: fcopyfile failed: Operation not supported on socket cp: /Users/goldstains/Downloads/Microsoft-Windows-LanguageFeatures-Basic-en-us-Package-amd64.cab: could not copy extended attributes to /Volumes/NTFS/Microsoft-Windows-LanguageFeatures-Basic-en-us-Package-amd64.cab: Operation not supported on socket macOS 的 cp 默认会复制：\nxattr ACL Finder metadata 但 NTFS-3G 挂载的 NTFS 分区不支持这些 macOS metadata，所以复制失败。\n直接使用 rsync 就可以\njsx 1 sudo rsync -r --size-only ~/Downloads/WIN /Volumes/NTFS/ 用完的话要 umount 卸载掉，我看硬盘盒一直在唤醒状态，不会动态休眠\njsx 1 sudo umount /Volumes/NTFS 解锁 bitlocker macos 上安装 anylinuxfs\njsx 1 2 brew tap nohajc/anylinuxfs brew install anylinuxfs 如下\njsx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 $ ~ % sudo rsync -r --size-only ~/Downloads/WIN /Volumes/NTFS/ $ ~ % brew tap nohajc/anylinuxfs brew install anylinuxfs ==\u0026gt; Auto-updating Homebrew... Adjust how often this is run with `$HOMEBREW_AUTO_UPDATE_SECS` or disable with `$HOMEBREW_NO_AUTO_UPDATE=1`. Hide these hints with `$HOMEBREW_NO_ENV_HINTS=1` (see `man brew`). ==\u0026gt; Auto-updated Homebrew! Updated 1 tap (homebrew/cask). ==\u0026gt; New Casks fidelity-trader+: Trading platform gram: Code editor focused on stability, without AI, subscsriptions, or telemetry spectra-app: OpenSpec document management desktop app supasidebar: Arc-like sidebar to save links, files and folders from any browser tencent-ugit: Tencent Git GUI Client ulaa: Privacy-centric browser with advanced tracking protection You have 8 outdated formulae installed. ==\u0026gt; Tapping nohajc/anylinuxfs Cloning into \u0026#39;/opt/homebrew/Library/Taps/nohajc/homebrew-anylinuxfs\u0026#39;... remote: Enumerating objects: 422, done. remote: Counting objects: 100% (129/129), done. remote: Compressing objects: 100% (55/55), done. remote: Total 422 (delta 73), reused 102 (delta 47), pack-reused 293 (from 1) Receiving objects: 100% (422/422), 56.39 KiB | 345.00 KiB/s, done. Resolving deltas: 100% (195/195), done. Tapped 1 formula (19 files, 123.8KB). ==\u0026gt; Fetching downloads for: anylinuxfs ==\u0026gt; Tapping slp/krun Cloning into \u0026#39;/opt/homebrew/Library/Taps/slp/homebrew-krun\u0026#39;... remote: Enumerating objects: 533, done. remote: Counting objects: 100% (138/138), done. remote: Compressing objects: 100% (58/58), done. remote: Total 533 (delta 97), reused 87 (delta 80), pack-reused 395 (from 1) Receiving objects: 100% (533/533), 347.54 MiB | 27.72 MiB/s, done. Resolving deltas: 100% (204/204), done. Tapped 7 formulae (34 files, 439.8MB). ✔︎ Bottle Manifest dtc (1.7.2) Downloaded 10.3KB/ 10.3KB ✔︎ Bottle dtc (1.7.2) Downloaded 135.1KB/135.1KB ✔︎ Bottle Manifest sqlite (3.52.0) Downloaded 11.4KB/ 11.4KB ✔︎ Bottle sqlite (3.52.0) Downloaded 2.4MB/ 2.4MB ✔︎ Bottle Manifest z3 (4.15.4) Downloaded 7.6KB/ 7.6KB ✔︎ Bottle z3 (4.15.4) Downloaded 13.1MB/ 13.1MB ✔︎ Bottle Manifest llvm (22.1.1) Downloaded 37.8KB/ 37.8KB ✔︎ Bottle Manifest lld (22.1.1) Downloaded 19.9KB/ 19.9KB ✔︎ Bottle lld (22.1.1) Downloaded 1.9MB/ 1.9MB ✔︎ Bottle libkrunfw (5.2.0) Downloaded 12.3MB/ 12.3MB ✔︎ Bottle Manifest libepoxy (1.5.10) Downloaded 24.5KB/ 24.5KB ✔︎ Bottle libepoxy (1.5.10) Downloaded 393.3KB/393.3KB ✔︎ Bottle Manifest molten-vk (1.4.1) Downloaded 5.3KB/ 5.3KB ✔︎ Bottle virglrenderer (0.10.4e) Downloaded 510.7KB/510.7KB ✔︎ Bottle libkrun (1.17.4) Downloaded 2.0MB/ 2.0MB ✔︎ Bottle Manifest util-linux (2.41.3_1) Downloaded 16.4KB/ 16.4KB ✔︎ Bottle util-linux (2.41.3_1) Downloaded 5.5MB/ 5.5MB ✔︎ Bottle molten-vk (1.4.1) Downloaded 18.0MB/ 18.0MB ✔︎ Bottle anylinuxfs (0.12.2) Downloaded 53.2MB/ 53.2MB ✔︎ Bottle llvm (22.1.1) Downloaded 451.0MB/451.0MB ==\u0026gt; Installing anylinuxfs from nohajc/anylinuxfs ==\u0026gt; Installing dependencies for nohajc/anylinuxfs/anylinuxfs: dtc, sqlite, z3, llvm, lld, slp/krun/libkrunfw, libepoxy, molten-vk, slp/krun/virglrenderer, slp/krun/libkrun and util-linux ==\u0026gt; Installing nohajc/anylinuxfs/anylinuxfs dependency: dtc ==\u0026gt; Pouring dtc--1.7.2.arm64_tahoe.bottle.tar.gz 🍺 /opt/homebrew/Cellar/dtc/1.7.2: 19 files, 640.1KB ==\u0026gt; Installing nohajc/anylinuxfs/anylinuxfs dependency: sqlite ==\u0026gt; Pouring sqlite--3.52.0.arm64_tahoe.bottle.tar.gz 🍺 /opt/homebrew/Cellar/sqlite/3.52.0: 13 files, 5.3MB ==\u0026gt; Installing nohajc/anylinuxfs/anylinuxfs dependency: z3 ==\u0026gt; Pouring z3--4.15.4.arm64_tahoe.bottle.tar.gz 🍺 /opt/homebrew/Cellar/z3/4.15.4: 120 files, 33.6MB ==\u0026gt; Installing nohajc/anylinuxfs/anylinuxfs dependency: llvm ==\u0026gt; Pouring llvm--22.1.1.arm64_tahoe.bottle.tar.gz 🍺 /opt/homebrew/Cellar/llvm/22.1.1: 9,686 files, 1.9GB ==\u0026gt; Installing nohajc/anylinuxfs/anylinuxfs dependency: lld ==\u0026gt; Pouring lld--22.1.1.arm64_tahoe.bottle.tar.gz 🍺 /opt/homebrew/Cellar/lld/22.1.1: 36 files, 6.0MB ==\u0026gt; Installing nohajc/anylinuxfs/anylinuxfs dependency: slp/krun/libkrunfw ==\u0026gt; Pouring libkrunfw-5.2.0.arm64_tahoe.bottle.tar.gz 🍺 /opt/homebrew/Cellar/libkrunfw/5.2.0: 7 files, 22.8MB ==\u0026gt; Installing nohajc/anylinuxfs/anylinuxfs dependency: libepoxy ==\u0026gt; Pouring libepoxy--1.5.10.arm64_tahoe.bottle.tar.gz 🍺 /opt/homebrew/Cellar/libepoxy/1.5.10: 11 files, 2.7MB ==\u0026gt; Installing nohajc/anylinuxfs/anylinuxfs dependency: molten-vk ==\u0026gt; Pouring molten-vk--1.4.1.arm64_tahoe.bottle.tar.gz 🍺 /opt/homebrew/Cellar/molten-vk/1.4.1: 83 files, 82.2MB ==\u0026gt; Installing nohajc/anylinuxfs/anylinuxfs dependency: slp/krun/virglrenderer ==\u0026gt; Pouring virglrenderer-0.10.4e.arm64_tahoe.bottle.tar.gz 🍺 /opt/homebrew/Cellar/virglrenderer/0.10.4e: 10 files, 1.8MB ==\u0026gt; Installing nohajc/anylinuxfs/anylinuxfs dependency: slp/krun/libkrun ==\u0026gt; Pouring libkrun-1.17.4.arm64_tahoe.bottle.tar.gz 🍺 /opt/homebrew/Cellar/libkrun/1.17.4: 13 files, 4.6MB ==\u0026gt; Installing nohajc/anylinuxfs/anylinuxfs dependency: util-linux ==\u0026gt; Pouring util-linux--2.41.3_1.arm64_tahoe.bottle.tar.gz 🍺 /opt/homebrew/Cellar/util-linux/2.41.3_1: 193 files, 18.3MB ==\u0026gt; Installing nohajc/anylinuxfs/anylinuxfs ==\u0026gt; Pouring anylinuxfs-0.12.2.arm64_tahoe.bottle.tar.gz ==\u0026gt; /opt/homebrew/Cellar/anylinuxfs/0.12.2/bin/anylinuxfs upgrade-config /opt/homebrew/etc/anylinuxfs.toml -o /opt/homebrew/etc/anylinuxfs.toml 🍺 /opt/homebrew/Cellar/anylinuxfs/0.12.2: 18 files, 116.8MB ==\u0026gt; Running `brew cleanup anylinuxfs`... Disable this behaviour by setting `HOMEBREW_NO_INSTALL_CLEANUP=1`. Hide these hints with `HOMEBREW_NO_ENV_HINTS=1` (see `man brew`). 查询设备\njsx 1 anylinuxfs list -m 这个命令将列出所有 microsoft 的分区，还有-l 是linux\n例如我这里可以看到\njsx 1 2 3 4 /dev/disk4 (external, physical): #: TYPE NAME SIZE IDENTIFIER 0: GUID_partition_scheme *2.0 TB disk4 1: Microsoft Basic Data 2.0 TB disk4s1 使用 sudo 可以看到加密状态\njsx 1 sudo anylinuxfs list -m 我这里看到的\njsx 1 2 3 4 /dev/disk4 (external, physical): #: TYPE NAME SIZE IDENTIFIER 0: GUID_partition_scheme *2.0 TB disk4 1: BitLocker DESKTOP-xxxxx F: 2... 2.0 TB disk4s1 挂载设备(解密和挂载需要加sudo)\njsx 1 2 % sudo anylinuxfs /dev/disk4s1 Enter passphrase for /dev/disk4s1: 挂载设备时，首次会需要输入两次密码，第一次是 mac 当前用户管理员密码，第二次才是 bitlocker 的磁盘密码。如下所示\njsx 1 2 3 % sudo anylinuxfs /dev/disk4s1 Password: # 注意看英文，这里是本机 Enter passphrase for /dev/disk4s1: # 这里是 bitlocker 磁盘 挂载完成后默认在 /Volumes/DESKTOP-xxx (加密的设备名) 下面，也会弹出 UI 的浏览器\n卸载磁盘 可以用 unmout 也可以在 UI 上弹出\njsx 1 sudo umount /Volumes/DESKTOP-7EP4D9Q_C__7-15-2025/ Reference How to mount BitLocker-encrypted drives on macOS\nApple 矽晶 Mac Bitlocker ","permalink":"https://www.161616.top/macos-unlock-bitlocker/","summary":"安装 macFUSE + NTFS-3G (免费) jsx 1 2 brew install macfuse brew install gromgit/fuse/ntfs-3g 查看磁盘\njsx 1 diskutil list 安装完成后会在“隐私与安全性”中提示需要启动系统扩展\n启用内核扩展 点击启动系统扩展时候会触发重启\n重启后开机一直按住开机键直到页面提示“正在载入启动选项”\n输入密码后进入，之后在顶部菜单栏选择“工具” → “启东安全性实用工具”，选择磁盘，安全策略\n选择降低安全性\n重启后再回到“隐私与安全性”点允许\n完成之后插上你的NTFS移动硬盘，macOS识别后在终端使用以下命令查找硬盘分区的名称，如diskXsY，X表示磁盘，Y表示分区\ndisk4 整个物理磁盘 disk4s1 第1个分区 jsx 1 diskutil list 找到你的硬盘例如，第几个分区就选择几\njsx 1 /dev/disk4s1 执行挂载\njsx 1 sudo ntfs-3g /dev/disk4s1 /Volumes/NTFS 拷贝数据\n在拷贝数据时候，cp 有时候不能用，例如\njsx 1 2 3 % sudo cp ~/Downloads/Microsoft-Windows-LanguageFeatures-Basic-en-us-Package-amd64.cab /Volumes/NTFS/ cp: /Volumes/NTFS/Microsoft-Windows-LanguageFeatures-Basic-en-us-Package-amd64.cab: fcopyfile failed: Operation not supported on socket cp: /Users/goldstains/Downloads/Microsoft-Windows-LanguageFeatures-Basic-en-us-Package-amd64.","title":"macOS上免费解锁bitlocker"},{"content":"问题描述 在使用 haproxy 驱动 Pod 代理时，在个别 k8s 集群中 haproxy 容器 (haproxytech/haproxy-debian:2.7) 在启动时出现 OOM 秒重启，即使配置了 16GB 的内存限制依然被杀掉。而同样的镜像和配置在其他 Rocky Linux 9 集群中运行正常。\n关键症状 内存占用异常：\n内存配置 症状 2GB OOM 8GB OOM 15GB OOM 就是配置多大的内存都会被吞掉。当时日志记录如下\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 Feb 13 11:57:07 unit-z7p4-worker-02 kernel: memory: usage 8388608kB, limit 8388608kB, failcnt 110 Feb 13 11:57:07 unit-z7p4-worker-02 kernel: swap: usage 0kB, limit 0kB, failcnt 0 Feb 13 11:57:07 unit-z7p4-worker-02 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod70d1da33_ec83_422a_8490_81a4cd60785e.slice/cri-containerd-8c13b4b91f76026cc10b09f8d15cf0ba7cf6cfe1d5a3e4833e7e660c3264c991.scope: Feb 13 11:57:07 unit-z7p4-worker-02 kernel: anon 8572551168 file 4096 kernel 17379328 kernel_stack 16384 pagetables 16850944 sec_pagetables 0 percpu 160 sock 0 vmalloc 0 shmem 4096 zswap 0 zswapped 0 file_mapped 4096 file_dirty 0 file_writeback 0 swapcached 0 anon_thp 8562671616 file_thp 0 shmem_thp 0 inactive_anon 4096 active_anon 8572551168 inactive_file 0 active_file 0 unevictable 0 slab_reclaimable 409080 slab_unreclaimable 74336 slab 483416 workingset_refault_anon 0 workingset_refault_file 0 workingset_activate_anon 0 workingset_activate_file 0 workingset_restore_anon 0 workingset_restore_file 0 workingset_nodereclaim 0 pgscan 0 pgsteal 0 pgscan_kswapd 0 pgscan_direct 0 pgscan_khugepaged 0 pgsteal_kswapd 0 pgsteal_direct 0 pgsteal_khugepaged 0 pgfault 6082 pgmajfault 0 pgrefill 0 pgactivate 0 pgdeactivate 0 pglazyfree 0 pglazyfreed 0 zswpin 0 zswpout 0 thp_fault_alloc 4088 thp_collapse_alloc 0 Feb 13 11:57:07 unit-z7p4-worker-02 kernel: Tasks state (memory values in pages): Feb 13 11:57:07 unit-z7p4-worker-02 kernel: [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name Feb 13 11:57:07 unit-z7p4-worker-02 kernel: [3503610] 0 3503610 92289723 2095555 16863232 0 992 haproxy Feb 13 11:57:07 unit-z7p4-worker-02 kernel: oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=cri-containerd-8c13b4b91f76026cc10b09f8d15cf0ba7cf6cfe1d5a3e4833e7e660c3264c991.scope,mems_allowed=0,oom_memcg=/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod70d1da33_ec83_422a_8490_81a4cd60785e.slice/cri-containerd-8c13b4b91f76026cc10b09f8d15cf0ba7cf6cfe1d5a3e4833e7e660c3264c991.scope,task_memcg=/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod70d1da33_ec83_422a_8490_81a4cd60785e.slice/cri-containerd-8c13b4b91f76026cc10b09f8d15cf0ba7cf6cfe1d5a3e4833e7e660c3264c991.scope,task=haproxy,pid=3503610,uid=0 Feb 13 11:57:07 unit-z7p4-worker-02 kernel: Memory cgroup out of memory: Killed process 3503610 (haproxy) total-vm:369158892kB, anon-rss:8371596kB, file-rss:10624kB, shmem-rss:0kB, UID:0 pgtables:16468kB oom_score_adj:992 Feb 13 11:57:07 unit-z7p4-worker-02 kernel: Tasks in /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod70d1da33_ec83_422a_8490_81a4cd60785e.slice/cri-containerd-8c13b4b91f76026cc10b09f8d15cf0ba7cf6cfe1d5a3e4833e7e660c3264c991.scope are going to be killed due to memory.oom.group set Feb 13 11:57:07 unit-z7p4-worker-02 kernel: Memory cgroup out of memory: Killed process 3503610 (haproxy) total-vm:369158892kB, anon-rss:8371596kB, file-rss:10624kB, shmem-rss:0kB, UID:0 pgtables:16468kB oom_score_adj:992 排查过程 之前代码设计是在启动时，controller 获取 k8s pod 信息完成映射，怀疑是因为启动时大量写导致的顺时内存突发，修改代码为异步处理[1] 后问题仍然存在。\n上述问题实施的内容\n增加了 haproxy 启动延迟 增加了内存限制，扩大了内存 排查了系统版本/内核版本/k8s版本/kubelet slice 均没有任何问题，和正常运行在 k8s 集群的其他集群的配置一致。 Pod 配置为\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 spec: containers: - image: hub.rancher8888.com/base/haproxy-debian:2.7 imagePullPolicy: IfNotPresent name: haproxy-proxier-ints ports: - containerPort: 8404 hostPort: 8404 protocol: TCP - containerPort: 5555 hostPort: 5555 protocol: TCP resources: limits: cpu: 200m memory: 1Gi requests: cpu: 200m memory: 1Gi terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: - mountPath: /var/run/secrets/kubernetes.io/serviceaccount name: kube-api-access-gsgfp readOnly: true - args: - | echo \u0026#34;⏳ 等待 haproxy 启动中...\u0026#34;; sleep 30; echo \u0026#34;✅ 启动 pod-proxier\u0026#34;; exec /apps/pod-proxier-gateway \\ --v=5 \\ --enable-v2=true \\ --port-name=debug \\ --port-range-start=13000 \\ --port-range-end=13500 \\ --check-timeout=1800 \\ --check-interval=1800 \\ --resync-time=30 \\ --allowed-namespaces=scanner-test command: - /bin/sh - -c image: cylonchau/proxier:v0.1.2-1.30 imagePullPolicy: IfNotPresent name: pod-proxier ports: - containerPort: 8848 hostPort: 8848 protocol: TCP - containerPort: 3343 hostPort: 3343 protocol: TCP hostNetwork: true 把限制从 1GB 提到 2GB，结果依然是秒OOM\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 Feb 13 23:31:47 unit-z7p4-worker-02 kernel: memory: usage 2097152kB, limit 2097152kB, failcnt 115 Feb 13 23:31:47 unit-z7p4-worker-02 kernel: swap: usage 0kB, limit 0kB, failcnt 0 Feb 13 23:31:47 unit-z7p4-worker-02 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod277b4f89_39d1_453d_ad03_96797120509d.slice/cri-containerd-1bcdf7318e6c0ed6d20e95c5dcda608a541eec5b4265be15b27616340897628e.scope: Feb 13 23:31:47 unit-z7p4-worker-02 kernel: anon 2142683136 file 4096 kernel 4796416 kernel_stack 16384 pagetables 4268032 sec_pagetables 0 percpu 160 sock 0 vmalloc 0 shmem 4096 zswap 0 zswapped 0 file_mapped 4096 file_dirty 0 file_writeback 0 swapcached 0 anon_thp 2132803584 file_thp 0 shmem_thp 0 inactive_anon 4096 active_anon 2142683136 inactive_file 0 active_file 0 unevictable 0 slab_reclaimable 406888 slab_unreclaimable 74336 slab 481224 workingset_refault_anon 0 workingset_refault_file 0 workingset_activate_anon 0 workingset_activate_file 0 workingset_restore_anon 0 workingset_restore_file 0 workingset_nodereclaim 0 pgscan 0 pgsteal 0 pgscan_kswapd 0 pgscan_direct 0 pgscan_khugepaged 0 pgsteal_kswapd 0 pgsteal_direct 0 pgsteal_khugepaged 0 pgfault 3015 pgmajfault 0 pgrefill 0 pgactivate 0 pgdeactivate 0 pglazyfree 0 pglazyfreed 0 zswpin 0 zswpout 0 thp_fault_alloc 1021 thp_collapse_alloc 0 Feb 13 23:31:47 unit-z7p4-worker-02 kernel: Tasks state (memory values in pages): Feb 13 23:31:47 unit-z7p4-worker-02 kernel: [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name Feb 13 23:31:47 unit-z7p4-worker-02 kernel: [ 199483] 0 199483 92289723 525761 4280320 0 968 haproxy Feb 13 23:31:47 unit-z7p4-worker-02 kernel: oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=cri-containerd-1bcdf7318e6c0ed6d20e95c5dcda608a541eec5b4265be15b27616340897628e.scope,mems_allowed=0,oom_memcg=/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod277b4f89_39d1_453d_ad03_96797120509d.slice/cri-containerd-1bcdf7318e6c0ed6d20e95c5dcda608a541eec5b4265be15b27616340897628e.scope,task_memcg=/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod277b4f89_39d1_453d_ad03_96797120509d.slice/cri-containerd-1bcdf7318e6c0ed6d20e95c5dcda608a541eec5b4265be15b27616340897628e.scope,task=haproxy,pid=199483,uid=0 Feb 13 23:31:47 unit-z7p4-worker-02 kernel: Memory cgroup out of memory: Killed process 199483 (haproxy) total-vm:369158892kB, anon-rss:2092292kB, file-rss:10752kB, shmem-rss:0kB, UID:0 pgtables:4180kB oom_score_adj:968 Feb 13 23:31:47 unit-z7p4-worker-02 kernel: Tasks in /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod277b4f89_39d1_453d_ad03_96797120509d.slice/cri-containerd-1bcdf7318e6c0ed6d20e95c5dcda608a541eec5b4265be15b27616340897628e.scope are going to be killed due to memory.oom.group set Feb 13 23:31:47 unit-z7p4-worker-02 kernel: Memory cgroup out of memory: Killed process 199483 (haproxy) total-vm:369158892kB, anon-rss:2092292kB, file-rss:10752kB, shmem-rss:0kB, UID:0 pgtables:4180kB oom_score_adj:968 换一个节点，内存扩容到 15G 仍然会遇到 OOM 问题，如下日志所示\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 Feb 14 00:02:18 unit-z7p4-worker-03 kernel: memory: usage 15728640kB, limit 15728640kB, failcnt 123 Feb 14 00:02:18 unit-z7p4-worker-03 kernel: swap: usage 0kB, limit 0kB, failcnt 0 Feb 14 00:02:18 unit-z7p4-worker-03 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podae2e77b9_d51b_4ab6_b949_70a218e0e398.slice/cri-containerd-734f4fd2b1f5db19eea6ae0db91d4f8013fe9b61cb8ead273c5cf3b0b3d7ce01.scope: Feb 14 00:02:18 unit-z7p4-worker-03 kernel: anon 16074063872 file 4096 kernel 32059392 kernel_stack 16384 pagetables 31531008 sec_pagetables 0 percpu 160 sock 0 vmalloc 0 shmem 4096 zswap 0 zswapped 0 file_mapped 4096 file_dirty 0 file_writeback 0 swapcached 0 anon_thp 16064184320 file_thp 0 shmem_thp 0 inactive_anon 4096 active_anon 16074063872 inactive_file 0 active_file 0 unevictable 0 slab_reclaimable 407984 slab_unreclaimable 74336 slab 482320 workingset_refault_anon 0 workingset_refault_file 0 workingset_activate_anon 0 workingset_activate_file 0 workingset_restore_anon 0 workingset_restore_file 0 workingset_nodereclaim 0 pgscan 0 pgsteal 0 pgscan_kswapd 0 pgscan_direct 0 pgscan_khugepaged 0 pgsteal_kswapd 0 pgsteal_direct 0 pgsteal_khugepaged 0 pgfault 9763 pgmajfault 0 pgrefill 0 pgactivate 0 pgdeactivate 0 pglazyfree 0 pglazyfreed 0 zswpin 0 zswpout 0 thp_fault_alloc 7665 thp_collapse_alloc 0 Feb 14 00:02:18 unit-z7p4-worker-03 kernel: Tasks state (memory values in pages): Feb 14 00:02:18 unit-z7p4-worker-03 kernel: [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name Feb 14 00:02:18 unit-z7p4-worker-03 kernel: [ 238960] 0 238960 92289723 3926999 31547392 0 760 haproxy Feb 14 00:02:18 unit-z7p4-worker-03 kernel: oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=cri-containerd-734f4fd2b1f5db19eea6ae0db91d4f8013fe9b61cb8ead273c5cf3b0b3d7ce01.scope,mems_allowed=0,oom_memcg=/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podae2e77b9_d51b_4ab6_b949_70a218e0e398.slice/cri-containerd-734f4fd2b1f5db19eea6ae0db91d4f8013fe9b61cb8ead273c5cf3b0b3d7ce01.scope,task_memcg=/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podae2e77b9_d51b_4ab6_b949_70a218e0e398.slice/cri-containerd-734f4fd2b1f5db19eea6ae0db91d4f8013fe9b61cb8ead273c5cf3b0b3d7ce01.scope,task=haproxy,pid=238960,uid=0 Feb 14 00:02:18 unit-z7p4-worker-03 kernel: Memory cgroup out of memory: Killed process 238960 (haproxy) total-vm:369158892kB, anon-rss:15697244kB, file-rss:10752kB, shmem-rss:0kB, UID:0 pgtables:30808kB oom_score_adj:760 Feb 14 00:02:18 unit-z7p4-worker-03 kernel: Tasks in /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podae2e77b9_d51b_4ab6_b949_70a218e0e398.slice/cri-containerd-734f4fd2b1f5db19eea6ae0db91d4f8013fe9b61cb8ead273c5cf3b0b3d7ce01.scope are going to be killed due to memory.oom.group set Feb 14 00:02:18 unit-z7p4-worker-03 kernel: Memory cgroup out of memory: Killed process 238960 (haproxy) total-vm:369158892kB, anon-rss:15697244kB, file-rss:10752kB, shmem-rss:0kB, UID:0 pgtables:30808kB oom_score_adj:760 最终根据 AI 提示，说是 “jemalloc 内存分配器问题”，我自己没有找到对应的相同案例\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ kexec pod-proxier-fdgdfq31-p7jfv -- bash Defaulted container \u0026#34;haproxy-proxier-ints\u0026#34; out of: haproxy-proxier-ints, pod-proxier root@pod-proxier-fdgdfq31-p7jfv:/usr/local/etc/haproxy# ldd /usr/local/sbin/haproxy linux-vdso.so.1 (0x00007ffd5d370000) libcrypt.so.1 =\u0026gt; /lib/x86_64-linux-gnu/libcrypt.so.1 (0x00007fb4fdc46000) libssl.so.3 =\u0026gt; /lib/x86_64-linux-gnu/libssl.so.3 (0x00007fb4fdb9c000) libcrypto.so.3 =\u0026gt; /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007fb4fd71a000) liblua5.4.so.0 =\u0026gt; /lib/x86_64-linux-gnu/liblua5.4.so.0 (0x00007fb4fd6d8000) libpcre2-8.so.0 =\u0026gt; /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007fb4fd63e000) libjemalloc.so.2 =\u0026gt; /lib/x86_64-linux-gnu/libjemalloc.so.2 (0x00007fb4fd34f000) libc.so.6 =\u0026gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb4fd16e000) libm.so.6 =\u0026gt; /lib/x86_64-linux-gnu/libm.so.6 (0x00007fb4fd08f000) libdl.so.2 =\u0026gt; /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fb4fd08a000) libstdc++.so.6 =\u0026gt; /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fb4fce70000) libgcc_s.so.1 =\u0026gt; /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fb4fce4e000) /lib64/ld-linux-x86-64.so.2 (0x00007fb4fe20d000) 解决方式 更换了 haproxy:2.7-alpine（alpine 使用musl libc 不使用 jemalloc），官方已经升级到版本3，并且启用了 dataplane api，但是我一直没换 sdk，也有可能是版本太旧了的问题。记录一下后面技术提升后可以复盘问题，这个问题排查了好几个星期\u0026hellip;.\nReference [1] fix: oomkill due to ServiceController batch operation\n","permalink":"https://www.161616.top/haproxy-http-connection-mode/","summary":"问题描述 在使用 haproxy 驱动 Pod 代理时，在个别 k8s 集群中 haproxy 容器 (haproxytech/haproxy-debian:2.7) 在启动时出现 OOM 秒重启，即使配置了 16GB 的内存限制依然被杀掉。而同样的镜像和配置在其他 Rocky Linux 9 集群中运行正常。\n关键症状 内存占用异常：\n内存配置 症状 2GB OOM 8GB OOM 15GB OOM 就是配置多大的内存都会被吞掉。当时日志记录如下\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 Feb 13 11:57:07 unit-z7p4-worker-02 kernel: memory: usage 8388608kB, limit 8388608kB, failcnt 110 Feb 13 11:57:07 unit-z7p4-worker-02 kernel: swap: usage 0kB, limit 0kB, failcnt 0 Feb 13 11:57:07 unit-z7p4-worker-02 kernel: Memory cgroup stats for /kubepods.","title":"记录haproxy在个别k8s集群上异常OOM问题"},{"content":"前言 motorola 和 one plus 应该是目前中国大陆的手机品牌最容易解锁的一款了，其他的都已经被招安内置审查app了。\nG75 版本更新策略，找到有两种\n一个是 5 个系统版本更新，6个安全更新\nThank you for taking the time to reach out to us. I understand how important updates are to everyone. The device is planned to receive 5 OS upgrades and 6 years of bimonthly security updates from the time of release. [1]\n一个是 3 个系统大版本号，6个安全更新\nThanks for reaching out! The Motorola G75 is confirmed to receive 3 major Android updates along with 6 years of security patches. We appreciate your interest and understand how important updates are to keep your device running smoothly. If you have any other questions, feel free to ask. [2]\n国际版与大陆版区别如下： EU/GB installer 没有审查；大陆版 installer 有审查，但是还是可以装。 EU/GB 附带 Google 套件可以离线收到通知；大陆版内置了 google service 开关，尝试很多方法没法离线收到通知。 EU/GB 不带 钱包/漫游等功能，NFC门禁无法使用。 EU/GB 没有状态栏 sim 卡小图标，没有网速显示。 EU/GB 内置 quick share，大陆版没有 。（期待 Google 下放到 quick share to airdrop，目前还不可以使用，只有 pixel 10 和 Samsung 可以）。 目前没有看到 moto g75 有其他 aosp 社区有提供这个手机型号。\nmotorola系列解锁 开启 OEM 解锁 进入「设置 → 关于手机」页面，设备标识符 （英文版是Device identifiers）快速轻敲「版本号」（英文版是 build number）5 下。\n打开「设置 → 系统」页面，点击「高级」选项，进入「开发者选项」页面，从列表中找到「OEM 解锁」选项，并启用它。\n获取设备ID 这里大多数教程都是让你下载 Tiny Fastboot Script，实际上不需要。首先可以重启手机，进入 fastboot 模式（开机震动后按住音量+）如下界面是未解锁。\n手机连接 USB 至电脑，连接后右下角会提示 USB Connected.\nnote 这里使用手机或者使用电脑连接都可以。 获取设备ID note 此步骤需要安装了 adb，或者直接使用其他教程推荐的 Tiny Fastboot Script（二选一） text 1 2 3 fastboot devices fastboot oem get_unlock_data fastboot oem unlock \u0026lt;your code\u0026gt; 获取解锁码 进入网址 “There are 4 steps involved in unlocking your device”，建议直接使用 google 登录，这样邮件就直接发送到你的 Gmail 里了。\n获取设备ID（示例）\ntext 1 2 3 4 5 6 Example Device ID (PC User) $ fastboot oem get_unlock_data (bootloader) 0A40040192024205#4C4D3556313230 (bootloader) 30373731363031303332323239#BD00 (bootloader) 8A672BA4746C2CE02328A2AC0C39F95 (bootloader) 1A3E5#1F53280002000000000000000 (bootloader) 0000000\t拼接设备ID，把上面的输出的“5行”信息（不包含空格，(bootloader)，换行符等信息），拼接成一行，输入到网页下面的输入框内，如图所示。\n点击按钮 “Can my device be unloced”, 如果可以，可以拉到页面最下部会出现获取解锁key的按钮\n这时候会收到邮件，解锁码会在邮件内。然后回到命令行输入\ntext 1 fastboot oem unlock \u0026lt;your code\u0026gt; 这时手机会弹出选择，按音量键选择解锁，按电源键确定\nquote 需要注意的是，解锁后的手机，每次开机界面都需要按两次电源键来交互式确认手机已经解锁的提示。 下载官方rom 在网上找到一个各个国家版本比较全，也比较新的 ROM 下载地址，附录3 [3] ，这里尝试了 EU 和 GB 两个版本均正常使用，区别在于 EU 开机预装的软件比 GB 少很多。\nquote 尽量选择欧洲的版本，那边对隐私立法比较完善，其他非法制地区的版本可能存在后门。 如图所示，这个网址提供的两个下载文件，一个是安装脚本（类似 Tiny Fastboot Script 等封装），另外一个就是安装包了。\n下载后吧 Flash Fastboot Script 和 ROM 全部解压放置到同一个目录下。我自己尝试用 “Flash Fastboot Script” 总是无法执行。让 AI 帮忙生成了刷机脚本。\n刷基带版本\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 @echo off chcp 65001 \u0026gt;nul echo. echo ======================================== echo Moto G75 完整刷机（包含基带） echo ======================================== echo. pause set M=tools\\mfastboot\\34.0.4\\mfastboot.exe REM 检查 mfastboot 是否存在 if not exist %M% ( echo 错误：找不到 %M% pause exit /b 1 ) echo. echo [1/15] 刷入 bootloader... %M% flash bootloader bootloader.img if errorlevel 1 goto error echo. echo [2/15] 刷入 GPT 分区表... %M% flash gpt gpt.bin if errorlevel 1 goto error echo. echo [3/15] 刷入 logo（启动画面）... %M% flash logo logo.bin if errorlevel 1 goto error echo. echo [4/15] 刷入 BTFM（bootloader 固件）... %M% flash BTFM BTFM.bin if errorlevel 1 goto error echo. echo [5/15] 刷入 NON-HLOS（modem 主固件）... %M% flash NON-HLOS NON-HLOS.bin if errorlevel 1 goto error echo. echo [6/15] 刷入 FSG（频段/地区配置）... %M% flash fsg fsg.mbn if errorlevel 1 goto error echo. echo [7/15] 刷入 DSP（数字信号处理）... %M% flash dsp dspso.bin if errorlevel 1 goto error echo. echo [8/15] 刷入 NVM（调制解调器 NVM）... %M% flash nvme_backup slcf_rev_d_default_v1.0.nvm if errorlevel 1 goto error echo. echo [9/15] 刷入 boot 分区... %M% flash boot boot.img if errorlevel 1 goto error echo. echo [10/15] 刷入 dtbo 分区... %M% flash dtbo dtbo.img if errorlevel 1 goto error echo. echo [11/15] 刷入 vendor_boot 分区... %M% flash vendor_boot vendor_boot.img if errorlevel 1 goto error echo. echo [12/15] 刷入 vbmeta 验证分区... %M% flash vbmeta vbmeta.img if errorlevel 1 goto error echo. echo [13/15] 刷入 vbmeta_system 验证分区... %M% flash vbmeta_system vbmeta_system.img if errorlevel 1 goto error echo. echo [14/15] 刷入 super 分区（系统/vendor/product）... for /L %%i in (0,1,18) do ( echo - 刷入 super.img_sparsechunk.%%i %M% flash super super.img_sparsechunk.%%i if errorlevel 1 goto error ) echo. echo [15/15] 重启设备... %M% reboot echo. echo ======================================== echo 刷机完成！设备正在启动... echo ======================================== echo. echo 首次启动可能需要 2-3 分钟，请耐心等待 pause exit /b 0 :error echo. echo ======================================== echo 错误：刷机失败！ echo ======================================== echo 请检查设备连接并重试 pause exit /b 1 不包含基带版本\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 @echo off echo ======================================== echo 完整刷机（包含基带） echo ======================================== pause set M=tools\\mfastboot\\34.0.4\\mfastboot.exe echo 刷入 bootloader... %M% flash bootloader bootloader.img echo 刷入 GPT 分区表... %M% flash gpt gpt.bin echo 刷入 logo（启动画面）... %M% flash logo logo.bin echo 刷入 BTFM（bootloader 固件）... %M% flash BTFM BTFM.bin echo 刷入 NON-HLOS（modem 主固件）... %M% flash NON-HLOS NON-HLOS.bin echo 刷入 FSG（频段/地区配置）... %M% flash fsg fsg.mbn echo 刷入 DSP（数字信号处理）... %M% flash dsp dspso.bin echo 刷入 NVM（调制解调器 NVM）... %M% flash nvme_backup slcf_rev_d_default_v1.0.nvm echo 刷入 boot 分区... %M% flash boot boot.img echo 刷入 dtbo 分区... %M% flash dtbo dtbo.img echo 刷入 vendor_boot 分区... %M% flash vendor_boot vendor_boot.img echo 刷入 vbmeta 分区... %M% flash vbmeta vbmeta.img echo 刷入 vbmeta_system 分区... %M% flash vbmeta_system vbmeta_system.img REM 刷 super 分区 for /L %%i in (0,1,18) do ( echo 刷入 super.img_sparsechunk.%%i %M% flash super super.img_sparsechunk.%%i ) echo 刷入基带和系统完成 %M% reboot pause 这里尝试使用两部大陆版 Moto G75 (XT2437-4) 尝试，一部直接刷 EU 版，没有刷基带，正常启动。另外一部刷 GB 版，没有刷基带无法开机，系统分区错误了。这里重新使用刷基带版本刷后正常启动。\n这里标注一些基带点相关的文件\nbootloader - 引导加载程序 gpt - 分区表 BTFM - Bootloader 固件 NON-HLOS - Modem 主固件（调制解调器） fsg - 频段/地区信息 dsp - DSP 固件 nvme_backup - 调制解调器 NVM 配置 quote 这里不需要在意变不变砖，操作过程中可以随意的插拔手机，启动不了就重新执行脚本即可。 刷机脚本执行过程界面如下\n执行完成后手机会自动重启。\nReference [1] OS \u0026amp; software update Moto G75\n[2] Re:How many OS updates will the Motorola G75 get?\n[3] rom g75\n[4] mirrors.lolinet.com\n","permalink":"https://www.161616.top/moto-g75-unlock-and-flush-rom/","summary":"前言 motorola 和 one plus 应该是目前中国大陆的手机品牌最容易解锁的一款了，其他的都已经被招安内置审查app了。\nG75 版本更新策略，找到有两种\n一个是 5 个系统版本更新，6个安全更新\nThank you for taking the time to reach out to us. I understand how important updates are to everyone. The device is planned to receive 5 OS upgrades and 6 years of bimonthly security updates from the time of release. [1]\n一个是 3 个系统大版本号，6个安全更新\nThanks for reaching out! The Motorola G75 is confirmed to receive 3 major Android updates along with 6 years of security patches.","title":"Moto G75解锁和国际版rom安装步骤"},{"content":"为什么选择 GitHub 作为 YUM 仓库？ 免费托管：GitHub 提供免费的文件存储和 HTTP 服务。 CDN：结合 jsDelivr 等 CDN 服务，可以加速用户的访问速度。 无成本：无需自己搭建服务器，降低维护成本。 使用便利：可以一条命令完成安装，无需再上传安装包了，也可以用来制作下线版本的 yum 仓库。 基本思路 YUM 仓库的核心是文件索引（repodata）和 HTTP 服务。GitHub 提供静态文件托管和 HTTP 访问支持，可以作为 YUM 仓库的存储平台。此外，可以通过 CDN 服务（如 jsDelivr）加速访问（可选）。\n存储：将 RPM 包和仓库索引文件存储在 GitHub 仓库中。 访问：通过 GitHub 的 raw 文件访问路径或 jsDelivr 提供 HTTP 服务。 生成索引：使用 createrepo 工具生成 YUM 仓库的元数据（repodata）。 步骤 1：生成 repodata 安装 createrepo 工具 确保已安装 createrepo 工具，用于生成 YUM 仓库的元数据。\nbash 1 sudo yum install createrepo -y 创建目录结构 为不同发行版（这里为 CentOS 7 和 Rocky 9）创建对应的目录结构：\ntext 1 2 3 4 5 6 ├── 7 │ ├── repodata │ └── rpm ├── 9 │ ├── repodata └── └── rpm 执行以下命令创建目录：\nbash 1 mkdir -p repo/{7,9}/rpm 准备 RPM 包 将需要托管的 RPM 包放入对应的 rpm 目录。例如，将 pfcli-1.0-1.el9.noarch.rpm 放入 repo/9/rpm/。\n生成 repodata 使用 createrepo 命令生成 YUM 仓库索引。以下是命令语法：\ntext 1 2 3 createrepo [OPTION...] \u0026lt;directory_to_index\u0026gt; -o, --outputdir=URL Optional output directory for repodata. -u, --baseurl=URL Optional base URL location for RPM files. 示例命令（以 Rocky 9 为例）：\nbash 1 2 3 createrepo 9/rpm/ \\ -o ./9/ \\ --baseurl=https://github.com/{your_github_username}/ops-tools/raw/main/9/rpm tips \u0026ndash;baseurl 指定 RPM 包的下载路径，必须与 GitHub 仓库的实际路径一致。 如果 \u0026ndash;baseurl 配置错误，可能导致 YUM 客户端下载失败 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ================================================================================================================================================================= Package Architecture Version Repository Size ================================================================================================================================================================= Installing: pfcli noarch 1.0-1.el9 centos9 9.7 k Transaction Summary ================================================================================================================================================================= Install 1 Package Total download size: 9.7 k Installed size: 7.4 k Is this ok [y/N]: y Downloading Packages: [MIRROR] pfcli-1.0-1.el9.noarch.rpm: Status code: 404 for https://github.com/{username}/ops-tools/raw/main/9/pfcli-1.0-1.el9.noarch.rpm (IP: 20.205.243.166) 这里的是在生产索引时候指定，如果你执行的目录不对的话，例如在 repo/7 目录下执行 createrepo\nbash 1 createrepo -o ./9/ 9/rpm/ 这个命令将出现上面的错误，地址缺失 rpm\n上传到 GitHub 最后将整个 repo 目录上传到 GitHub 仓库。上传后，GitHub 会为每个文件生成可通过 HTTP 访问的 URL。\n步骤 2：使用 jsDelivr 加速（可选） jsDelivr 是一个免费的 CDN 服务，可以加速 GitHub 仓库的访问速度，尤其适合全球用户。\n配置 jsDelivr 将 GitHub 的 URL 替换为 jsDelivr 的格式：\nGitHub URL: “https://github.com/{username}/{repo}/raw/main/{path}” jsDelivr URL: “https://cdn.jsdelivr.net/gh/{username}/{repo}@{branch}/{path}” 例如，Rocky 9 的 RPM 路径为：\ntext 1 https://cdn.jsdelivr.net/gh/{username}/ops-tools@main/9/rpm/ 在 YUM 客户端配置中使用此 URL，可显著提升下载速度。\n全球 CDN 节点（中国大陆不可用）。 更高的带宽和可靠性，适合生产环境。 步骤 3：配置 YUM 客户端 在客户端配置 YUM 仓库，指向 GitHub 或 jsDelivr 的 URL。\n创建 .repo 文件 在客户端的 /etc/yum.repos.d/ 目录下创建配置文件。例如，创建 ops-extra-repo.repo\ntext 1 2 3 4 5 6 7 tee /etc/yum.repos.d/centos7-ops-extra-repo.repo \u0026lt;\u0026lt; EOF [centos7] name=Extra ops tools baseurl=https://github.com/{your_github_username}/ops-tools/raw/main/7/rpm/ enabled=1 gpgcheck=0 EOF 如果使用 jsDelivr：\ntext 1 2 3 4 5 6 7 tee /etc/yum.repos.d/centos7-ops-extra-repo.repo \u0026lt;\u0026lt; EOF [centos7] name=Extra ops tools baseurl=https://cdn.jsdelivr.net/gh/{your_github_username}/ops-tools@main/7/rpm/ enabled=1 gpgcheck=0 EOF 为支持多版本系统，可以使用 YUM/DNF 的 $releasever 变量动态选择仓库路径：\ntext 1 2 3 4 5 6 7 sudo tee /etc/yum.repos.d/ops-tools.repo \u0026lt;\u0026lt; EOF [ops-tools] name=OPS Tools Repository baseurl=https://github.com/your-username/ops-tools/raw/main/$releasever/rpm/ enabled=1 gpgcheck=0 EOF $releasever 会根据系统版本自动替换为 7 或 9，与官方镜像（如 阿里云镜像的http://mirrors.aliyun.com/centos/$releasever/extras/x86_64/）的行为一致。\n安装尝试\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 Last metadata expiration check: 0:26:45 ago on Mon 30 Jun 2025 04:37:11 PM CST. Dependencies resolved. ============================================================================================= Package Architecture Version Repository Size ============================================================================================= Installing: pfcli noarch 1.0-1.el9 ops-tools 9.6 k Transaction Summary ============================================================================================= Install 1 Package Total download size: 9.6 k Installed size: 7.4 k Is this ok [y/N]: y Downloading Packages: pfcli-1.0-1.el9.noarch.rpm 9.7 kB/s | 9.6 kB 00:00 --------------------------------------------------------------------------------------------- Total 9.7 kB/s | 9.6 kB 00:00 Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Installing : pfcli-1.0-1.el9.noarch 1/1 Running scriptlet: pfcli-1.0-1.el9.noarch 1/1 Verifying : pfcli-1.0-1.el9.noarch 1/1 Installed: pfcli-1.0-1.el9.noarch Complete! 步骤 4：进一步优化 - 打包仓库配置为 RPM 为了简化客户端配置，可以将 .repo 文件打包为 RPM 包，类似 EPEL 仓库的安装方式。\n创建 RPM 构建目录 text 1 mkdir -p rpmbuild/{RPMS,SOURCES,SPECS,SRPMS} 创建 .repo 文件 将仓库配置文件放入 SOURCES 目录：\nbash 1 2 3 4 5 6 7 tee rpmbuild/SOURCES/ops-extra-repo.repo \u0026lt;\u0026lt; EOF [ops-tools] name=OPS tools Repository baseurl=https://github.com/your-username/my-yum-repo/raw/main/$releasever/rpm/ enabled=1 gpgcheck=0 EOF 使用 jsDelivr 加速（可选） 为提高访问速度，可以使用 jsDelivr 作为 CDN。jsDelivr 提供对 GitHub 仓库文件的加速访问。\n将 baseurl 修改为 jsDelivr 的路径\ntext 1 2 3 4 5 6 7 tee rpmbuild/SOURCES/ops-extra-repo.repo \u0026lt;\u0026lt; EOF [ops-tools] name=OPS tools Repository baseurl=https://cdn.jsdelivr.net/gh/{your_github_username}/ops-tools@main/$releasever/rpm/ enabled=1 gpgcheck=0 EOF 编写 .spec 文件 创建 rpmbuild/SPECS/ops-tools-repo.spec 文件，定义 RPM 包的元数据：\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 tee rpmbuild/SPEC/ops-tools-repo.spec Name: ops-tools-repo Version: 1.0 Release: 1%{?dist} Summary: YUM repository configuration for OPS tools License: GPL URL: https://github.com/hardng/ops-tools Group: System Environment/Base Packager: CylonChau \u0026lt;cylonchau@outlook.com\u0026gt; Source0: ops-extra-repo.repo %description This package provides the YUM repository configuration file for OPS tools, which points to a custom GitHub-hosted repository. %install %{__install} -p -D %{SOURCE0} %{buildroot}/etc/yum.repos.d/ops-extra-repo.repo %files %attr(0744,root,root) /etc/yum.repos.d/ops-extra-repo.repo %clean rm -rf %{buildroot} %changelog * Thu Jun 30 2025 CylonChau \u0026lt;cylonchau@outlook.com\u0026gt; - 1.0-1 - Initial package for github-hosted YUM repo configuration EOF 构建 RPM 包 安装必要的 RPM 构建工具：\ntext 1 rpmbuild -ba rpmbuild/SPECS/ops-tools-repo.spec 生成的文件位于 rpmbuild/RPMS/noarch/ 目录，例如 ops-tools-repo-1.0-1.el9.noarch.rpm。\n放置到 github 仓库中后，可以直接使用 yum install 进行安装\ntext 1 sudo rpm -ivh https://github.com/your-username/ops-tools/raw/main/9/rpm/ops-tools-repo-1.0-1.el9.noarch.rpm 总结 通过以上步骤，你可以使用 GitHub 快速搭建一个 YUM 仓库，支持 CentOS 7, Rocky 9 等系统。进一步优化可以通过打包 .repo 文件为 RPM 包，或使用 jsDelivr 加速访问。这种方法很适合个人项目并易于维护。\n","permalink":"https://www.161616.top/utilize-github-yum-repo/","summary":"为什么选择 GitHub 作为 YUM 仓库？ 免费托管：GitHub 提供免费的文件存储和 HTTP 服务。 CDN：结合 jsDelivr 等 CDN 服务，可以加速用户的访问速度。 无成本：无需自己搭建服务器，降低维护成本。 使用便利：可以一条命令完成安装，无需再上传安装包了，也可以用来制作下线版本的 yum 仓库。 基本思路 YUM 仓库的核心是文件索引（repodata）和 HTTP 服务。GitHub 提供静态文件托管和 HTTP 访问支持，可以作为 YUM 仓库的存储平台。此外，可以通过 CDN 服务（如 jsDelivr）加速访问（可选）。\n存储：将 RPM 包和仓库索引文件存储在 GitHub 仓库中。 访问：通过 GitHub 的 raw 文件访问路径或 jsDelivr 提供 HTTP 服务。 生成索引：使用 createrepo 工具生成 YUM 仓库的元数据（repodata）。 步骤 1：生成 repodata 安装 createrepo 工具 确保已安装 createrepo 工具，用于生成 YUM 仓库的元数据。\nbash 1 sudo yum install createrepo -y 创建目录结构 为不同发行版（这里为 CentOS 7 和 Rocky 9）创建对应的目录结构：","title":"利用github构建个人yum仓库"},{"content":"背景 在一次物理机断电回复后，Kubernetes 集群因断电或其他原因导致 etcd 启动时出现 “panic: lease ID must be 8-byte” 错误。本文记录如何诊断和修复 etcd 数据损坏，并恢复 Kubernetes 集群的过程。\n问题描述 在尝试启动 etcd 服务时，日志显示以下错误：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 {\u0026#34;level\u0026#34;:\u0026#34;warn\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:12.178202Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/config.go:687\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;Running http and grpc server on single port. This is not recommended for production.\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:12.178400Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdmain/etcd.go:73\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;Running: \u0026#34;,\u0026#34;args\u0026#34;:[\u0026#34;etcd\u0026#34;,\u0026#34;--advertise-client-urls=https://10.0.10.188:2379\u0026#34;,\u0026#34;--cert-file=/etc/kubernetes/pki/etcd/server.crt\u0026#34;,\u0026#34;--client-cert-auth=true\u0026#34;,\u0026#34;--data-dir=/var/lib/etcd\u0026#34;,\u0026#34;--experimental-initial-corrupt-check=true\u0026#34;,\u0026#34;--experimental-watch-progress-notify-interval=5s\u0026#34;,\u0026#34;--initial-advertise-peer-urls=https://10.0.10.188:2380\u0026#34;,\u0026#34;--initial-cluster=buss-k8s-master=https://10.0.10.188:2380\u0026#34;,\u0026#34;--key-file=/etc/kubernetes/pki/etcd/server.key\u0026#34;,\u0026#34;--listen-client-urls=https://127.0.0.1:2379,https://10.0.10.188:2379\u0026#34;,\u0026#34;--listen-metrics-urls=http://127.0.0.1:2381\u0026#34;,\u0026#34;--listen-peer-urls=https://10.0.10.188:2380\u0026#34;,\u0026#34;--name=buss-k8s-master\u0026#34;,\u0026#34;--peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt\u0026#34;,\u0026#34;--peer-client-cert-auth=true\u0026#34;,\u0026#34;--peer-key-file=/etc/kubernetes/pki/etcd/peer.key\u0026#34;,\u0026#34;--peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt\u0026#34;,\u0026#34;--snapshot-count=10000\u0026#34;,\u0026#34;--trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt\u0026#34;]} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:12.178527Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdmain/etcd.go:116\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;server has been already initialized\u0026#34;,\u0026#34;data-dir\u0026#34;:\u0026#34;/var/lib/etcd\u0026#34;,\u0026#34;dir-type\u0026#34;:\u0026#34;member\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;warn\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:12.178591Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/config.go:687\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;Running http and grpc server on single port. This is not recommended for production.\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:12.178613Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:128\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;configuring peer listeners\u0026#34;,\u0026#34;listen-peer-urls\u0026#34;:[\u0026#34;https://10.0.10.188:2380\u0026#34;]} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:12.178682Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:496\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;starting with peer TLS\u0026#34;,\u0026#34;tls-info\u0026#34;:\u0026#34;cert = /etc/kubernetes/pki/etcd/peer.crt, key = /etc/kubernetes/pki/etcd/peer.key, client-cert=, client-key=, trusted-ca = /etc/kubernetes/pki/etcd/ca.crt, client-cert-auth = true, crl-file = \u0026#34;,\u0026#34;cipher-suites\u0026#34;:[]} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:12.180487Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:136\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;configuring client listeners\u0026#34;,\u0026#34;listen-client-urls\u0026#34;:[\u0026#34;https://127.0.0.1:2379\u0026#34;,\u0026#34;https://10.0.10.188:2379\u0026#34;]} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:12.180772Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:310\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;starting an etcd server\u0026#34;,\u0026#34;etcd-version\u0026#34;:\u0026#34;3.5.15\u0026#34;,\u0026#34;git-sha\u0026#34;:\u0026#34;9a5533382\u0026#34;,\u0026#34;go-version\u0026#34;:\u0026#34;go1.21.12\u0026#34;,\u0026#34;go-os\u0026#34;:\u0026#34;linux\u0026#34;,\u0026#34;go-arch\u0026#34;:\u0026#34;amd64\u0026#34;,\u0026#34;max-cpu-set\u0026#34;:8,\u0026#34;max-cpu-available\u0026#34;:8,\u0026#34;member-initialized\u0026#34;:true,\u0026#34;name\u0026#34;:\u0026#34;buss-k8s-master\u0026#34;,\u0026#34;data-dir\u0026#34;:\u0026#34;/var/lib/etcd\u0026#34;,\u0026#34;wal-dir\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;wal-dir-dedicated\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;member-dir\u0026#34;:\u0026#34;/var/lib/etcd/member\u0026#34;,\u0026#34;force-new-cluster\u0026#34;:false,\u0026#34;heartbeat-interval\u0026#34;:\u0026#34;100ms\u0026#34;,\u0026#34;election-timeout\u0026#34;:\u0026#34;1s\u0026#34;,\u0026#34;initial-election-tick-advance\u0026#34;:true,\u0026#34;snapshot-count\u0026#34;:10000,\u0026#34;max-wals\u0026#34;:5,\u0026#34;max-snapshots\u0026#34;:5,\u0026#34;snapshot-catchup-entries\u0026#34;:5000,\u0026#34;initial-advertise-peer-urls\u0026#34;:[\u0026#34;https://10.0.10.188:2380\u0026#34;],\u0026#34;listen-peer-urls\u0026#34;:[\u0026#34;https://10.0.10.188:2380\u0026#34;],\u0026#34;advertise-client-urls\u0026#34;:[\u0026#34;https://10.0.10.188:2379\u0026#34;],\u0026#34;listen-client-urls\u0026#34;:[\u0026#34;https://127.0.0.1:2379\u0026#34;,\u0026#34;https://10.0.10.188:2379\u0026#34;],\u0026#34;listen-metrics-urls\u0026#34;:[\u0026#34;http://127.0.0.1:2381\u0026#34;],\u0026#34;cors\u0026#34;:[\u0026#34;*\u0026#34;],\u0026#34;host-whitelist\u0026#34;:[\u0026#34;*\u0026#34;],\u0026#34;initial-cluster\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;initial-cluster-state\u0026#34;:\u0026#34;new\u0026#34;,\u0026#34;initial-cluster-token\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;quota-backend-bytes\u0026#34;:2147483648,\u0026#34;max-request-bytes\u0026#34;:1572864,\u0026#34;max-concurrent-streams\u0026#34;:4294967295,\u0026#34;pre-vote\u0026#34;:true,\u0026#34;initial-corrupt-check\u0026#34;:true,\u0026#34;corrupt-check-time-interval\u0026#34;:\u0026#34;0s\u0026#34;,\u0026#34;compact-check-time-enabled\u0026#34;:false,\u0026#34;compact-check-time-interval\u0026#34;:\u0026#34;1m0s\u0026#34;,\u0026#34;auto-compaction-mode\u0026#34;:\u0026#34;periodic\u0026#34;,\u0026#34;auto-compaction-retention\u0026#34;:\u0026#34;0s\u0026#34;,\u0026#34;auto-compaction-interval\u0026#34;:\u0026#34;0s\u0026#34;,\u0026#34;discovery-url\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;discovery-proxy\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;downgrade-check-interval\u0026#34;:\u0026#34;5s\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:12.196655Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/backend.go:81\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;opened backend db\u0026#34;,\u0026#34;path\u0026#34;:\u0026#34;/var/lib/etcd/member/snap/db\u0026#34;,\u0026#34;took\u0026#34;:\u0026#34;14.157616ms\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:13.588902Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/server.go:511\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;recovered v2 store from snapshot\u0026#34;,\u0026#34;snapshot-index\u0026#34;:67867015,\u0026#34;snapshot-size\u0026#34;:\u0026#34;7.2 kB\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:13.589033Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/server.go:524\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;recovered v3 backend from snapshot\u0026#34;,\u0026#34;backend-size-bytes\u0026#34;:53522432,\u0026#34;backend-size\u0026#34;:\u0026#34;54 MB\u0026#34;,\u0026#34;backend-size-in-use-bytes\u0026#34;:143360,\u0026#34;backend-size-in-use\u0026#34;:\u0026#34;143 kB\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:13.859083Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/raft.go:530\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;restarting local member\u0026#34;,\u0026#34;cluster-id\u0026#34;:\u0026#34;39b983ddef836ae7\u0026#34;,\u0026#34;local-member-id\u0026#34;:\u0026#34;9b72a468becd8ce1\u0026#34;,\u0026#34;commit-index\u0026#34;:67871394} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:13.860760Z\u0026#34;,\u0026#34;logger\u0026#34;:\u0026#34;raft\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/zap_raft.go:77\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;9b72a468becd8ce1 switched to configuration voters=(11201195993008540897)\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:13.860902Z\u0026#34;,\u0026#34;logger\u0026#34;:\u0026#34;raft\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/zap_raft.go:77\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;9b72a468becd8ce1 became follower at term 2\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:13.860942Z\u0026#34;,\u0026#34;logger\u0026#34;:\u0026#34;raft\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/zap_raft.go:77\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;newRaft 9b72a468becd8ce1 [peers: [9b72a468becd8ce1], term: 2, commit: 67871394, applied: 67867015, lastindex: 67871394, lastterm: 2]\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:13.861173Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;api/capability.go:75\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;enabled capabilities for version\u0026#34;,\u0026#34;cluster-version\u0026#34;:\u0026#34;3.5\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:13.861222Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;membership/cluster.go:278\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;recovered/added member from store\u0026#34;,\u0026#34;cluster-id\u0026#34;:\u0026#34;39b983ddef836ae7\u0026#34;,\u0026#34;local-member-id\u0026#34;:\u0026#34;9b72a468becd8ce1\u0026#34;,\u0026#34;recovered-remote-peer-id\u0026#34;:\u0026#34;9b72a468becd8ce1\u0026#34;,\u0026#34;recovered-remote-peer-urls\u0026#34;:[\u0026#34;https://10.0.10.188:2380\u0026#34;]} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:13.861246Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;membership/cluster.go:287\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;set cluster version from store\u0026#34;,\u0026#34;cluster-version\u0026#34;:\u0026#34;3.5\u0026#34;} panic: lease ID must be 8-byte goroutine 1 [running]: go.etcd.io/etcd/server/v3/lease.bytesToLeaseID(...) go.etcd.io/etcd/server/v3/lease/lessor.go:943 go.etcd.io/etcd/server/v3/lease.unsafeGetAllLeases.func1({0x7fec3c955070, 0x11, 0x10?}, {0x7fec3c955081, 0x1e1, 0x1e1}) go.etcd.io/etcd/server/v3/lease/lessor.go:954 +0x179 go.etcd.io/bbolt.(*Bucket).ForEach(0xc0000dc0f8?, 0xc000112e90) go.etcd.io/bbolt@v1.3.10/bucket.go:397 +0x90 go.etcd.io/etcd/server/v3/mvcc/backend.unsafeForEach(0xc0000dc0e0, {0x12723d0?, 0x1a07d00?}, 0x41d601?) go.etcd.io/etcd/server/v3/mvcc/backend/batch_tx.go:236 +0x56 go.etcd.io/etcd/server/v3/mvcc/backend.(*batchTx).UnsafeForEach(...) go.etcd.io/etcd/server/v3/mvcc/backend/batch_tx.go:231 go.etcd.io/etcd/server/v3/lease.unsafeGetAllLeases({0x7feeba39f358, 0xc000363400}) go.etcd.io/etcd/server/v3/lease/lessor.go:950 +0x92 go.etcd.io/etcd/server/v3/lease.(*lessor).initAndRecover(0xc0000003c0) go.etcd.io/etcd/server/v3/lease/lessor.go:801 +0x77 go.etcd.io/etcd/server/v3/lease.newLessor(0xc000158be0, {0x127b3c8?, 0xc000000000}, {0x1265220?, 0xc00068c420}, {0x1272878?, 0xc000112e20?, 0x5f5e100?, 0x0?}) go.etcd.io/etcd/server/v3/lease/lessor.go:235 +0x288 go.etcd.io/etcd/server/v3/lease.NewLessor(...) go.etcd.io/etcd/server/v3/lease/lessor.go:206 go.etcd.io/etcd/server/v3/etcdserver.NewServer({{0x7fff6b55ee37, 0x11}, {0x0, 0x0}, {0x0, 0x0}, {0xc00015bef0, 0x1, 0x1}, {0xc0001c3170, ...}, ...}) go.etcd.io/etcd/server/v3/etcdserver/server.go:601 +0x419a go.etcd.io/etcd/server/v3/embed.StartEtcd(0xc000137500) go.etcd.io/etcd/server/v3/embed/etcd.go:247 +0x10d8 go.etcd.io/etcd/server/v3/etcdmain.startEtcd(0x7fff6b55ee37?) go.etcd.io/etcd/server/v3/etcdmain/etcd.go:228 +0x17 go.etcd.io/etcd/server/v3/etcdmain.startEtcdOrProxyV2({0xc000134000, 0x14, 0x14}) go.etcd.io/etcd/server/v3/etcdmain/etcd.go:123 +0x13c5 go.etcd.io/etcd/server/v3/etcdmain.Main({0xc000134000?, 0x14, 0x14}) go.etcd.io/etcd/server/v3/etcdmain/main.go:40 +0x105 main.main() go.etcd.io/etcd/server/v3/main.go:31 +0x28 仅仅看报错表明 etcd 的租约（lease）数据存储在 bbolt 数据库中出现损坏，具体是租约键的长度不符合 8 字节要求，导致 etcd 服务无法正常启动。\n修复的过程 方法1：snapshot restore 检查快照状态\n使用 etcdctl 或 etcdutl 检查 etcd 快照文件的完整性：\nbash 1 etcdctl snapshot status /var/lib/etcd/member/snap/db 输出结果如下：\nbash 1 dce40287, 64190322, 108, 54 MB Hash: dce40287（快照的哈希值，用于验证完整性） Revision: 64190322（快照的修订版本号） Total Keys: 108（快照中存储的键数量） Total Size: 54 MB（快照文件大小） 注意：etcdctl snapshot status 已被标记为弃用，推荐使用 etcdutl snapshot status。\n因为没有备份就直接吧当前的存储备份后，restore 使用了当前的的db文件 (/var/lib/etcd/member/snap/db)。\n备份数据\n在任何修复操作前，备份当前 etcd 数据目录以防止进一步数据丢失：\nbash 1 cp -r /var/lib/etcd /var/lib/etcd.bak 尝试恢复快照\n使用 etcdutl 尝试从快照恢复数据库：\nbash 1 2 3 $ etcdutl snapshot restore /var/lib/etcd/member/snap/db --data-dir /var/lib/etcd/restore 2025-06-26T16:49:16+08:00 info snapshot/v3_snapshot.go:260 restoring snapshot {\u0026#34;path\u0026#34;: \u0026#34;/var/lib/etcd/member/snap/db\u0026#34;, \u0026#34;wal-dir\u0026#34;: \u0026#34;/var/lib/etcd/restore/member/wal\u0026#34;, \u0026#34;data-dir\u0026#34;: \u0026#34;/var/lib/etcd/restore\u0026#34;, \u0026#34;snap-dir\u0026#34;: \u0026#34;/var/lib/etcd/restore/member/snap\u0026#34;} Error: snapshot missing hash but --skip-hash-check=false 结果报错：\nbash 1 Error: snapshot missing hash but --skip-hash-check=false 增加参数\ntext 1 $ etcdutl snapshot restore /var/lib/etcd/member/snap/db --data-dir /var/lib/etcd/restore --skip-hash-check=false gork 告知 使用更宽松的参数 --force-new-cluster 启用新集群，尝试可以启动\nbash 1 $ etcd --force-new-cluster --data-dir=/var/lib/etcd 回复后的日志\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 {\u0026#34;level\u0026#34;:\u0026#34;warn\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.191181Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/config.go:687\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;Running http and grpc server on single port. This is not recommended for production.\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.191356Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdmain/etcd.go:73\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;Running: \u0026#34;,\u0026#34;args\u0026#34;:[\u0026#34;etcd\u0026#34;,\u0026#34;--force-new-cluster\u0026#34;,\u0026#34;--advertise-client-urls=https://10.0.10.188:2379\u0026#34;,\u0026#34;--cert-file=/etc/kubernetes/pki/etcd/server.crt\u0026#34;,\u0026#34;--client-cert-auth=true\u0026#34;,\u0026#34;--data-dir=/var/lib/etcd\u0026#34;,\u0026#34;--experimental-initial-corrupt-check=true\u0026#34;,\u0026#34;--experimental-watch-progress-notify-interval=5s\u0026#34;,\u0026#34;--initial-advertise-peer-urls=https://10.0.10.188:2380\u0026#34;,\u0026#34;--initial-cluster=buss-k8s-master=https://10.0.10.188:2380\u0026#34;,\u0026#34;--key-file=/etc/kubernetes/pki/etcd/server.key\u0026#34;,\u0026#34;--listen-client-urls=https://127.0.0.1:2379,https://10.0.10.188:2379\u0026#34;,\u0026#34;--listen-metrics-urls=http://127.0.0.1:2381\u0026#34;,\u0026#34;--listen-peer-urls=https://10.0.10.188:2380\u0026#34;,\u0026#34;--name=buss-k8s-master\u0026#34;,\u0026#34;--peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt\u0026#34;,\u0026#34;--peer-client-cert-auth=true\u0026#34;,\u0026#34;--peer-key-file=/etc/kubernetes/pki/etcd/peer.key\u0026#34;,\u0026#34;--peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt\u0026#34;,\u0026#34;--snapshot-count=10000\u0026#34;,\u0026#34;--trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt\u0026#34;]} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.191490Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdmain/etcd.go:116\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;server has been already initialized\u0026#34;,\u0026#34;data-dir\u0026#34;:\u0026#34;/var/lib/etcd\u0026#34;,\u0026#34;dir-type\u0026#34;:\u0026#34;member\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;warn\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.191558Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/config.go:687\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;Running http and grpc server on single port. This is not recommended for production.\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.191586Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:128\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;configuring peer listeners\u0026#34;,\u0026#34;listen-peer-urls\u0026#34;:[\u0026#34;https://10.0.10.188:2380\u0026#34;]} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.191669Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:496\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;starting with peer TLS\u0026#34;,\u0026#34;tls-info\u0026#34;:\u0026#34;cert = /etc/kubernetes/pki/etcd/peer.crt, key = /etc/kubernetes/pki/etcd/peer.key, client-cert=, client-key=, trusted-ca = /etc/kubernetes/pki/etcd/ca.crt, client-cert-auth = true, crl-file = \u0026#34;,\u0026#34;cipher-suites\u0026#34;:[]} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.193868Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:136\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;configuring client listeners\u0026#34;,\u0026#34;listen-client-urls\u0026#34;:[\u0026#34;https://127.0.0.1:2379\u0026#34;,\u0026#34;https://10.0.10.188:2379\u0026#34;]} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.196250Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:310\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;starting an etcd server\u0026#34;,\u0026#34;etcd-version\u0026#34;:\u0026#34;3.5.15\u0026#34;,\u0026#34;git-sha\u0026#34;:\u0026#34;9a5533382\u0026#34;,\u0026#34;go-version\u0026#34;:\u0026#34;go1.21.12\u0026#34;,\u0026#34;go-os\u0026#34;:\u0026#34;linux\u0026#34;,\u0026#34;go-arch\u0026#34;:\u0026#34;amd64\u0026#34;,\u0026#34;max-cpu-set\u0026#34;:8,\u0026#34;max-cpu-available\u0026#34;:8,\u0026#34;member-initialized\u0026#34;:true,\u0026#34;name\u0026#34;:\u0026#34;buss-k8s-master\u0026#34;,\u0026#34;data-dir\u0026#34;:\u0026#34;/var/lib/etcd\u0026#34;,\u0026#34;wal-dir\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;wal-dir-dedicated\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;member-dir\u0026#34;:\u0026#34;/var/lib/etcd/member\u0026#34;,\u0026#34;force-new-cluster\u0026#34;:true,\u0026#34;heartbeat-interval\u0026#34;:\u0026#34;100ms\u0026#34;,\u0026#34;election-timeout\u0026#34;:\u0026#34;1s\u0026#34;,\u0026#34;initial-election-tick-advance\u0026#34;:true,\u0026#34;snapshot-count\u0026#34;:10000,\u0026#34;max-wals\u0026#34;:5,\u0026#34;max-snapshots\u0026#34;:5,\u0026#34;snapshot-catchup-entries\u0026#34;:5000,\u0026#34;initial-advertise-peer-urls\u0026#34;:[\u0026#34;https://10.0.10.188:2380\u0026#34;],\u0026#34;listen-peer-urls\u0026#34;:[\u0026#34;https://10.0.10.188:2380\u0026#34;],\u0026#34;advertise-client-urls\u0026#34;:[\u0026#34;https://10.0.10.188:2379\u0026#34;],\u0026#34;listen-client-urls\u0026#34;:[\u0026#34;https://127.0.0.1:2379\u0026#34;,\u0026#34;https://10.0.10.188:2379\u0026#34;],\u0026#34;listen-metrics-urls\u0026#34;:[\u0026#34;http://127.0.0.1:2381\u0026#34;],\u0026#34;cors\u0026#34;:[\u0026#34;*\u0026#34;],\u0026#34;host-whitelist\u0026#34;:[\u0026#34;*\u0026#34;],\u0026#34;initial-cluster\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;initial-cluster-state\u0026#34;:\u0026#34;new\u0026#34;,\u0026#34;initial-cluster-token\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;quota-backend-bytes\u0026#34;:2147483648,\u0026#34;max-request-bytes\u0026#34;:1572864,\u0026#34;max-concurrent-streams\u0026#34;:4294967295,\u0026#34;pre-vote\u0026#34;:true,\u0026#34;initial-corrupt-check\u0026#34;:true,\u0026#34;corrupt-check-time-interval\u0026#34;:\u0026#34;0s\u0026#34;,\u0026#34;compact-check-time-enabled\u0026#34;:false,\u0026#34;compact-check-time-interval\u0026#34;:\u0026#34;1m0s\u0026#34;,\u0026#34;auto-compaction-mode\u0026#34;:\u0026#34;periodic\u0026#34;,\u0026#34;auto-compaction-retention\u0026#34;:\u0026#34;0s\u0026#34;,\u0026#34;auto-compaction-interval\u0026#34;:\u0026#34;0s\u0026#34;,\u0026#34;discovery-url\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;discovery-proxy\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;downgrade-check-interval\u0026#34;:\u0026#34;5s\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.212345Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/backend.go:81\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;opened backend db\u0026#34;,\u0026#34;path\u0026#34;:\u0026#34;/var/lib/etcd/member/snap/db\u0026#34;,\u0026#34;took\u0026#34;:\u0026#34;15.340047ms\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.213964Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/server.go:532\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;No snapshot found. Recovering WAL from scratch!\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.216418Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/raft.go:603\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;forcing restart member\u0026#34;,\u0026#34;cluster-id\u0026#34;:\u0026#34;39b983ddef836ae7\u0026#34;,\u0026#34;local-member-id\u0026#34;:\u0026#34;9b72a468becd8ce1\u0026#34;,\u0026#34;commit-index\u0026#34;:1} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.217035Z\u0026#34;,\u0026#34;logger\u0026#34;:\u0026#34;raft\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/zap_raft.go:77\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;9b72a468becd8ce1 switched to configuration voters=()\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.217225Z\u0026#34;,\u0026#34;logger\u0026#34;:\u0026#34;raft\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/zap_raft.go:77\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;9b72a468becd8ce1 became follower at term 0\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.217311Z\u0026#34;,\u0026#34;logger\u0026#34;:\u0026#34;raft\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/zap_raft.go:77\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;newRaft 9b72a468becd8ce1 [peers: [], term: 0, commit: 1, applied: 0, lastindex: 1, lastterm: 0]\u0026#34;} [root@buss-k8s-master lib]# crictl logs 6db850d2044ac {\u0026#34;level\u0026#34;:\u0026#34;warn\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.191181Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/config.go:687\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;Running http and grpc server on single port. This is not recommended for production.\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.191356Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdmain/etcd.go:73\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;Running: \u0026#34;,\u0026#34;args\u0026#34;:[\u0026#34;etcd\u0026#34;,\u0026#34;--force-new-cluster\u0026#34;,\u0026#34;--advertise-client-urls=https://10.0.10.188:2379\u0026#34;,\u0026#34;--cert-file=/etc/kubernetes/pki/etcd/server.crt\u0026#34;,\u0026#34;--client-cert-auth=true\u0026#34;,\u0026#34;--data-dir=/var/lib/etcd\u0026#34;,\u0026#34;--experimental-initial-corrupt-check=true\u0026#34;,\u0026#34;--experimental-watch-progress-notify-interval=5s\u0026#34;,\u0026#34;--initial-advertise-peer-urls=https://10.0.10.188:2380\u0026#34;,\u0026#34;--initial-cluster=buss-k8s-master=https://10.0.10.188:2380\u0026#34;,\u0026#34;--key-file=/etc/kubernetes/pki/etcd/server.key\u0026#34;,\u0026#34;--listen-client-urls=https://127.0.0.1:2379,https://10.0.10.188:2379\u0026#34;,\u0026#34;--listen-metrics-urls=http://127.0.0.1:2381\u0026#34;,\u0026#34;--listen-peer-urls=https://10.0.10.188:2380\u0026#34;,\u0026#34;--name=buss-k8s-master\u0026#34;,\u0026#34;--peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt\u0026#34;,\u0026#34;--peer-client-cert-auth=true\u0026#34;,\u0026#34;--peer-key-file=/etc/kubernetes/pki/etcd/peer.key\u0026#34;,\u0026#34;--peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt\u0026#34;,\u0026#34;--snapshot-count=10000\u0026#34;,\u0026#34;--trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt\u0026#34;]} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.191490Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdmain/etcd.go:116\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;server has been already initialized\u0026#34;,\u0026#34;data-dir\u0026#34;:\u0026#34;/var/lib/etcd\u0026#34;,\u0026#34;dir-type\u0026#34;:\u0026#34;member\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;warn\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.191558Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/config.go:687\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;Running http and grpc server on single port. This is not recommended for production.\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.191586Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:128\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;configuring peer listeners\u0026#34;,\u0026#34;listen-peer-urls\u0026#34;:[\u0026#34;https://10.0.10.188:2380\u0026#34;]} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.191669Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:496\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;starting with peer TLS\u0026#34;,\u0026#34;tls-info\u0026#34;:\u0026#34;cert = /etc/kubernetes/pki/etcd/peer.crt, key = /etc/kubernetes/pki/etcd/peer.key, client-cert=, client-key=, trusted-ca = /etc/kubernetes/pki/etcd/ca.crt, client-cert-auth = true, crl-file = \u0026#34;,\u0026#34;cipher-suites\u0026#34;:[]} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.193868Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:136\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;configuring client listeners\u0026#34;,\u0026#34;listen-client-urls\u0026#34;:[\u0026#34;https://127.0.0.1:2379\u0026#34;,\u0026#34;https://10.0.10.188:2379\u0026#34;]} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.196250Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:310\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;starting an etcd server\u0026#34;,\u0026#34;etcd-version\u0026#34;:\u0026#34;3.5.15\u0026#34;,\u0026#34;git-sha\u0026#34;:\u0026#34;9a5533382\u0026#34;,\u0026#34;go-version\u0026#34;:\u0026#34;go1.21.12\u0026#34;,\u0026#34;go-os\u0026#34;:\u0026#34;linux\u0026#34;,\u0026#34;go-arch\u0026#34;:\u0026#34;amd64\u0026#34;,\u0026#34;max-cpu-set\u0026#34;:8,\u0026#34;max-cpu-available\u0026#34;:8,\u0026#34;member-initialized\u0026#34;:true,\u0026#34;name\u0026#34;:\u0026#34;buss-k8s-master\u0026#34;,\u0026#34;data-dir\u0026#34;:\u0026#34;/var/lib/etcd\u0026#34;,\u0026#34;wal-dir\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;wal-dir-dedicated\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;member-dir\u0026#34;:\u0026#34;/var/lib/etcd/member\u0026#34;,\u0026#34;force-new-cluster\u0026#34;:true,\u0026#34;heartbeat-interval\u0026#34;:\u0026#34;100ms\u0026#34;,\u0026#34;election-timeout\u0026#34;:\u0026#34;1s\u0026#34;,\u0026#34;initial-election-tick-advance\u0026#34;:true,\u0026#34;snapshot-count\u0026#34;:10000,\u0026#34;max-wals\u0026#34;:5,\u0026#34;max-snapshots\u0026#34;:5,\u0026#34;snapshot-catchup-entries\u0026#34;:5000,\u0026#34;initial-advertise-peer-urls\u0026#34;:[\u0026#34;https://10.0.10.188:2380\u0026#34;],\u0026#34;listen-peer-urls\u0026#34;:[\u0026#34;https://10.0.10.188:2380\u0026#34;],\u0026#34;advertise-client-urls\u0026#34;:[\u0026#34;https://10.0.10.188:2379\u0026#34;],\u0026#34;listen-client-urls\u0026#34;:[\u0026#34;https://127.0.0.1:2379\u0026#34;,\u0026#34;https://10.0.10.188:2379\u0026#34;],\u0026#34;listen-metrics-urls\u0026#34;:[\u0026#34;http://127.0.0.1:2381\u0026#34;],\u0026#34;cors\u0026#34;:[\u0026#34;*\u0026#34;],\u0026#34;host-whitelist\u0026#34;:[\u0026#34;*\u0026#34;],\u0026#34;initial-cluster\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;initial-cluster-state\u0026#34;:\u0026#34;new\u0026#34;,\u0026#34;initial-cluster-token\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;quota-backend-bytes\u0026#34;:2147483648,\u0026#34;max-request-bytes\u0026#34;:1572864,\u0026#34;max-concurrent-streams\u0026#34;:4294967295,\u0026#34;pre-vote\u0026#34;:true,\u0026#34;initial-corrupt-check\u0026#34;:true,\u0026#34;corrupt-check-time-interval\u0026#34;:\u0026#34;0s\u0026#34;,\u0026#34;compact-check-time-enabled\u0026#34;:false,\u0026#34;compact-check-time-interval\u0026#34;:\u0026#34;1m0s\u0026#34;,\u0026#34;auto-compaction-mode\u0026#34;:\u0026#34;periodic\u0026#34;,\u0026#34;auto-compaction-retention\u0026#34;:\u0026#34;0s\u0026#34;,\u0026#34;auto-compaction-interval\u0026#34;:\u0026#34;0s\u0026#34;,\u0026#34;discovery-url\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;discovery-proxy\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;downgrade-check-interval\u0026#34;:\u0026#34;5s\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.212345Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/backend.go:81\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;opened backend db\u0026#34;,\u0026#34;path\u0026#34;:\u0026#34;/var/lib/etcd/member/snap/db\u0026#34;,\u0026#34;took\u0026#34;:\u0026#34;15.340047ms\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.213964Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/server.go:532\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;No snapshot found. Recovering WAL from scratch!\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.216418Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/raft.go:603\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;forcing restart member\u0026#34;,\u0026#34;cluster-id\u0026#34;:\u0026#34;39b983ddef836ae7\u0026#34;,\u0026#34;local-member-id\u0026#34;:\u0026#34;9b72a468becd8ce1\u0026#34;,\u0026#34;commit-index\u0026#34;:1} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.217035Z\u0026#34;,\u0026#34;logger\u0026#34;:\u0026#34;raft\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/zap_raft.go:77\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;9b72a468becd8ce1 switched to configuration voters=()\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.217225Z\u0026#34;,\u0026#34;logger\u0026#34;:\u0026#34;raft\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/zap_raft.go:77\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;9b72a468becd8ce1 became follower at term 0\u0026#34;} {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:51:58.217311Z\u0026#34;,\u0026#34;logger\u0026#34;:\u0026#34;raft\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;etcdserver/zap_raft.go:77\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;newRaft 9b72a468becd8ce1 [peers: [], term: 0, commit: 1, applied: 0, lastindex: 1, lastterm: 0]\u0026#34;} 方法2：修复 bbolt 数据库 etcd 使用 bbolt（BoltDB 的增强版本）作为存储引擎，租约数据存储在 lease 桶中。可以通过 bbolt 工具直接检查和修复数据库。\n安装 bbolt 工具\ntext 1 go install go.etcd.io/bbolt/cmd/bbolt@latest 检查数据库\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 首先查看 bbolt 的可用命令 bbolt --help # 查看数据库信息 bbolt info /var/lib/etcd/member/snap/db # 列出所有桶 bbolt buckets /var/lib/etcd/member/snap/db # 查看租约桶的内容 bbolt keys /var/lib/etcd/member/snap/db lease # 如果需要删除特定的键，可以使用页面检查 bbolt page /var/lib/etcd/member/snap/db # 尝试修复租约桶（lease bucket） bbolt buckets /var/lib/etcd/member/snap/db 方法3：使用脚本直接修改 lease 的值 本身 bblot db 可以直接使用 go api 来操作，所以也可以使用 go 脚本来直接读取 bbolt 文件然后修复对应的 lease。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;os\u0026#34; bolt \u0026#34;go.etcd.io/bbolt\u0026#34; ) func main() { if len(os.Args) \u0026lt; 2 { fmt.Println(\u0026#34;Usage: go run etcd_lease_fix.go \u0026lt;path-to-etcd-db\u0026gt;\u0026#34;) fmt.Println(\u0026#34;Example: go run etcd_lease_fix.go /var/lib/etcd/member/snap/db\u0026#34;) os.Exit(1) } dbPath := os.Args[1] // 备份原数据库 backupPath := dbPath + \u0026#34;.backup\u0026#34; fmt.Printf(\u0026#34;Creating backup: %s\\n\u0026#34;, backupPath) if err := copyFile(dbPath, backupPath); err != nil { log.Fatalf(\u0026#34;Failed to create backup: %v\u0026#34;, err) } // 打开数据库 db, err := bolt.Open(dbPath, 0600, nil) if err != nil { log.Fatalf(\u0026#34;Failed to open database: %v\u0026#34;, err) } defer db.Close() // 检查并修复租约桶 err = db.Update(func(tx *bolt.Tx) error { // 检查租约桶是否存在 leaseBucket := tx.Bucket([]byte(\u0026#34;lease\u0026#34;)) if leaseBucket == nil { fmt.Println(\u0026#34;Lease bucket not found, nothing to fix\u0026#34;) return nil } fmt.Println(\u0026#34;Found lease bucket, checking for corrupted entries...\u0026#34;) // 收集需要修复的键值对 var keysToFix []struct { oldKey []byte newKey []byte value []byte } err := leaseBucket.ForEach(func(k, v []byte) error { // 检查键长度是否为8字节 if len(k) != 8 { fmt.Printf(\u0026#34;Found corrupted lease key with length %d: %x\\n\u0026#34;, len(k), k) // 尝试修复键 var newKey []byte if len(k) \u0026lt; 8 { // 如果键太短，用零填充到8字节 newKey = make([]byte, 8) copy(newKey, k) fmt.Printf(\u0026#34;Padding short key %x to %x\\n\u0026#34;, k, newKey) } else if len(k) \u0026gt; 8 { // 如果键太长，截取前8字节 newKey = make([]byte, 8) copy(newKey, k[:8]) fmt.Printf(\u0026#34;Truncating long key %x to %x\\n\u0026#34;, k, newKey) } keysToFix = append(keysToFix, struct { oldKey []byte newKey []byte value []byte }{ oldKey: append([]byte(nil), k...), newKey: newKey, value: append([]byte(nil), v...), }) } return nil }) if err != nil { return fmt.Errorf(\u0026#34;failed to iterate lease bucket: %v\u0026#34;, err) } // 修复损坏的键 for _, fix := range keysToFix { fmt.Printf(\u0026#34;Fixing lease key %x -\u0026gt; %x\\n\u0026#34;, fix.oldKey, fix.newKey) // 检查新键是否已存在 if existing := leaseBucket.Get(fix.newKey); existing != nil { fmt.Printf(\u0026#34;Warning: new key %x already exists, skipping repair\\n\u0026#34;, fix.newKey) continue } // 添加修复后的键值对 if err := leaseBucket.Put(fix.newKey, fix.value); err != nil { return fmt.Errorf(\u0026#34;failed to put fixed key %x: %v\u0026#34;, fix.newKey, err) } // 删除原来的损坏键 if err := leaseBucket.Delete(fix.oldKey); err != nil { return fmt.Errorf(\u0026#34;failed to delete old key %x: %v\u0026#34;, fix.oldKey, err) } } if len(keysToFix) \u0026gt; 0 { fmt.Printf(\u0026#34;Fixed %d corrupted lease entries\\n\u0026#34;, len(keysToFix)) } else { fmt.Println(\u0026#34;No corrupted lease entries found\u0026#34;) } return nil }) if err != nil { log.Fatalf(\u0026#34;Failed to fix lease bucket: %v\u0026#34;, err) } fmt.Println(\u0026#34;Database repair completed successfully!\u0026#34;) fmt.Printf(\u0026#34;Original database backed up to: %s\\n\u0026#34;, backupPath) } func copyFile(src, dst string) error { data, err := os.ReadFile(src) if err != nil { return err } return os.WriteFile(dst, data, 0600) } 这里将 lease key 直接修改为了 8 位，尝试启动是不成功的，最终解决方式是通过删除 lease\ntext 1 2 3 4 5 # 先运行脚本分析损坏情况 go run etcd_lease_analyze.go /var/lib/etcd/member/snap/db # 运行修复脚本 $ go run etcd_lease_fix.go /var/lib/etcd/member/snap/db 修复后 etcd 可以启动，但发现数据丢失了，重新启动的集群没有业务 Pod 了。重新部署pod报错\ntext 1 2 3 4 5 6 7 8 a6529859b8b3872d252fe18e02f871bd946ea08c703181ad92ad5e\\\\\\\u0026#34;,\\\\\\\u0026#34;lastState\\\\\\\u0026#34;:{},\\\\\\\u0026#34;name\\\\\\\u0026#34;:\\\\\\\u0026#34;cluster-register\\\\\\\u0026#34;,\\\\\\\u0026#34;ready\\\\\\\u0026#34;:false,\\\\\\\u0026#34;restartCount\\\\\\\u0026#34;:47151,\\\\\\\u0026#34;started\\\\\\\u0026#34;:false,\\\\\\\u0026#34;state\\\\\\\u0026#34;:{\\\\\\\u0026#34;terminated\\\\\\\u0026#34;:{\\\\\\\u0026#34;containerID\\\\\\\u0026#34;:\\\\\\\u0026#34;containerd://5fc6780c61418d5f01543cd25ffd6890adbaa62094c892cabc1c3ddf918bd779\\\\\\\u0026#34;,\\\\\\\u0026#34;exitCode\\\\\\\u0026#34;:1,\\\\\\\u0026#34;finishedAt\\\\\\\u0026#34;:\\\\\\\u0026#34;2025-06-26T00:45:02Z\\\\\\\u0026#34;,\\\\\\\u0026#34;reason\\\\\\\u0026#34;:\\\\\\\u0026#34;Error\\\\\\\u0026#34;,\\\\\\\u0026#34;startedAt\\\\\\\u0026#34;:\\\\\\\u0026#34;2025-06-26T00:45:02Z\\\\\\\u0026#34;}}}],\\\\\\\u0026#34;podIP\\\\\\\u0026#34;:null,\\\\\\\u0026#34;podIPs\\\\\\\u0026#34;:null}}\\\u0026#34; for pod \\\u0026#34;cattle-system\\\u0026#34;/\\\u0026#34;cattle-cluster-agent-54dbc88855-tg9ts\\\u0026#34;: namespaces \\\u0026#34;cattle-system\\\u0026#34; not found\u0026#34; Jun 26 21:37:49 buss-k8s-worker-03 kubelet[4178]: E0626 21:37:49.719262 4178 remote_runtime.go:222] \u0026#34;StopPodSandbox from runtime service failed\u0026#34; err=\u0026#34;rpc error: code = Unknown desc = failed to destroy network for sandbox \\\u0026#34;f7eded679eee4ecd17cd2bc687e27ee626f637c0486dae47d8969b0698bc75d6\\\u0026#34;: plugin type=\\\u0026#34;calico\\\u0026#34; failed (delete): error getting ClusterInformation: Get \\\u0026#34;https://10.96.0.1:443/apis/crd.projectcalico.org/v1/clusterinformations/default\\\u0026#34;: dial tcp 10.96.0.1:443: i/o timeout\u0026#34; podSandboxID=\u0026#34;f7eded679eee4ecd17cd2bc687e27ee626f637c0486dae47d8969b0698bc75d6\u0026#34; Jun 26 21:37:49 buss-k8s-worker-03 kubelet[4178]: E0626 21:37:49.719350 4178 kuberuntime_gc.go:180] \u0026#34;Failed to stop sandbox before removing\u0026#34; err=\u0026#34;rpc error: code = Unknown desc = failed to destroy network for sandbox \\\u0026#34;f7eded679eee4ecd17cd2bc687e27ee626f637c0486dae47d8969b0698bc75d6\\\u0026#34;: plugin type=\\\u0026#34;calico\\\u0026#34; failed (delete): error getting ClusterInformation: Get \\\u0026#34;https://10.96.0.1:443/apis/crd.projectcalico.org/v1/clusterinformations/default\\\u0026#34;: dial tcp 10.96.0.1:443: i/o timeout\u0026#34; sandboxID=\u0026#34;f7eded679eee4ecd17cd2bc687e27ee626f637c0486dae47d8969b0698bc75d6\u0026#34; Jun 26 21:37:54 buss-k8s-worker-03 kubelet[4178]: I0626 21:37:54.339411 4178 scope.go:117] \u0026#34;RemoveContainer\u0026#34; containerID=\u0026#34;4dee6c66c8e8a5c84c9791f37c2ba7a83b7740a25838e498d09e8ea0f032e51e\u0026#34; Jun 26 21:37:54 buss-k8s-worker-03 kubelet[4178]: I0626 21:37:54.340648 4178 scope.go:117] \u0026#34;RemoveContainer\u0026#34; containerID=\u0026#34;4dee6c66c8e8a5c84c9791f37c2ba7a83b7740a25838e498d09e8ea0f032e51e\u0026#34; Jun 26 21:37:54 buss-k8s-worker-03 kubelet[4178]: E0626 21:37:54.343343 4178 remote_runtime.go:385] \u0026#34;RemoveContainer from runtime service failed\u0026#34; err=\u0026#34;rpc error: code = Unknown desc = failed to set removing state for container \\\u0026#34;4dee6c66c8e8a5c84c9791f37c2ba7a83b7740a25838e498d09e8ea0f032e51e\\\u0026#34;: container is already in removing state\u0026#34; containerID=\u0026#34;4dee6c66c8e8a5c84c9791f37c2ba7a83b7740a25838e498d09e8ea0f032e51e\u0026#34; Jun 26 21:37:54 buss-k8s-worker-03 kubelet[4178]: E0626 21:37:54.343482 4178 kuberuntime_container.go:867] failed to remove pod init container \u0026#34;install-cni\u0026#34;: rpc error: code = Unknown desc = failed to set removing state for container \u0026#34;4dee6c66c8e8a5c84c9791f37c2ba7a83b7740a25838e498d09e8ea0f032e51e\u0026#34;: container is already in removing state; Skipping pod \u0026#34;calico-node-hvtrx_kube-system(33e26ece-e14a-4b8c-b8d2-4805c1369315)\u0026#34; Jun 26 21:37:54 buss-k8s-worker-03 kubelet[4178]: E0626 21:37:54.344463 4178 pod_workers.go:1298] \u0026#34;Error syncing pod, skipping\u0026#34; err=\u0026#34;failed to \\\u0026#34;StartContainer\\\u0026#34; for \\\u0026#34;install-cni\\\u0026#34; with CrashLoopBackOff: \\\u0026#34;back-off 20s restarting failed container=install-cni pod=calico-node-hvtrx_kube-system(33e26ece-e14a-4b8c-b8d2-4805c1369315)\\\u0026#34;\u0026#34; pod=\u0026#34;kube-system/calico-node-hvtrx\u0026#34; podUID=\u0026#34;33e26ece-e14a-4b8c-b8d2-4805c1369315\u0026#34; 原因：很典型的 Pod 无法连接 apiserver，检查 kube-proxy 服务，发现因为数据损失导致 kube-proxy 没有了（kubeadm 部署的集群 kube-proxy 是作为 daemonset 方式部署的）\ntext 1 kubeadm init phase addon kube-proxy 也可以使用脚本先读取数据后再启动服务验证（当服务无法启动时）\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;os\u0026#34; bolt \u0026#34;go.etcd.io/bbolt\u0026#34; ) func main() { if len(os.Args) \u0026lt; 2 { log.Fatal(\u0026#34;使用方法: go run main.go \u0026lt;etcd-db-file-path\u0026gt;\u0026#34;) } dbPath := os.Args[1] // 以只读模式打开数据库 db, err := bolt.Open(dbPath, 0600, \u0026amp;bolt.Options{ReadOnly: true}) if err != nil { log.Fatal(\u0026#34;打开数据库失败:\u0026#34;, err) } defer db.Close() fmt.Printf(\u0026#34;正在读取etcd数据库: %s\\n\u0026#34;, dbPath) fmt.Println(\u0026#34;==========================================\u0026#34;) // 查看所有bucket err = db.View(func(tx *bolt.Tx) error { return tx.ForEach(func(name []byte, b *bolt.Bucket) error { fmt.Printf(\u0026#34;\\n[Bucket]: %s\\n\u0026#34;, string(name)) fmt.Println(\u0026#34;------------------------------------------\u0026#34;) return b.ForEach(func(k, v []byte) error { fmt.Printf(\u0026#34;Key: %s\\n\u0026#34;, string(k)) fmt.Printf(\u0026#34;Value: %s\\n\u0026#34;, string(v)) fmt.Println(\u0026#34;---\u0026#34;) return nil }) }) }) if err != nil { log.Fatal(\u0026#34;读取数据失败:\u0026#34;, err) } fmt.Println(\u0026#34;\\n数据读取完成!\u0026#34;) } 总结 在恢复服务时，没有做到数据的备份读取，导致无法确定是否是因为启动时覆盖掉的 etcd 的数据，这样导致在事后无法确定问题的根本原因，后期遇到问题要先分析并保留证据，其次在启动服务。\n","permalink":"https://www.161616.top/server-power-outage-etcd-panic-lease/","summary":"背景 在一次物理机断电回复后，Kubernetes 集群因断电或其他原因导致 etcd 启动时出现 “panic: lease ID must be 8-byte” 错误。本文记录如何诊断和修复 etcd 数据损坏，并恢复 Kubernetes 集群的过程。\n问题描述 在尝试启动 etcd 服务时，日志显示以下错误：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 {\u0026#34;level\u0026#34;:\u0026#34;warn\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2025-06-26T08:46:12.","title":"记录一次服务器断电引起的etcd无法启动问题处理"},{"content":" 本文是CoreDNS 开发系列第4章 CoreDNS 开发系列-1：CoreDNS核心概念和安装配置说明 CoreDNS 开发系列-2：CoreDNS插件详解 CoreDNS 开发系列-3：开发自定义插件 CoreDNS 开发系列-4：高并发域名平台架构设计 在本章节中将创建一个项目，旨在构建一个基于 CoreDNS 的域名系统，该项目目的是可以构建一个完整的 DNS 平台而不是管理平台，可以作为独立的 DNS Provider 运行业务域名解析；也可以作为企业内部 DNS的使用。\n分层架构设计 系统采用四层架构设计：\n视图层：也称为 API 层，负责提供 DNS 的管理后台， API 接口。 业务层：主要作为 CoreDNS Plugins 实现，用于集成 DNS Server。 数据层：视图层与业务层的联动，多数据库支持（SQLite/MySQL/PostgreSQL）、Redis 缓存、数据同步。 基础设施层：提供 command line 工具，并为 DNS Server 提供四层防火墙、监控告警数据。并提供整个公司生态的服务注册、服务发现等功能。 系统功能需求 功能模块 详细描述 优先级 View 支持多视图配置，智能解析 高 Zone 管理 DNS 区域管理与配置 高 域名管理 管理域中的域名解析 高 Transfer 与云 DNS 供应商数据同步 高 白名单 域名解析黑白名单控制，包含全局和单域名 高 证书管理 Let\u0026rsquo;s Encrypt 证书自动申请与续期 高 解析切换 提供多解析功能，实现故障切换 高 域名检测 域名可用性检测，可以配合切换功能完成故障自动切换 高 CDN 集成 CDN 切换与管理功能 中 运维工具 提供命令行工具 中 GeoIP 基于地理位置的智能 DNS 解析 低 外部系统注册 服务自动注册与发现 低 性能需求 性能指标 目标值 备注 QPS 100,000 一个 view 内的集群至少达到的值 项目分模块设计 视图层 模块名称 功能描述 域、域名管理 完成基础 Zone, Domain的管理功能 证书管理 Let\u0026rsquo;s Encrypt 证书自动申请与续期 View管理 管理域与业务层插件进行联动 白名单管理 域名解析黑白名单控制 Geo 管理 基于地理位置的智能 DNS 解析 CDN 集成 集成CDN联动功能 服务注册插件 提供全企业内的服务注册功能（View+业务） 业务层 模块名称 功能描述 实现方式 DNS 服务 DNS 查询处理、插件加载管理 原生 coredns 数据库 Zone, Domain 信息入库，通过库 Custom Plugin Redis 缓存插件 Zone, Domain 的热点缓存功能 Custom Plugin GeoIP 插件 基于地理位置的智能解析 Custom Plugin View View 的管理和配置 Custom Plugin 白名单 域名访问控制 Custom Plugin Transfer 区域传送与同步 Custom Plugin 基础设施层 模块名称 功能描述 实现技术 服务发现 服务注册与发现 Nacos DNS查询流程设计 多级缓存策略\n缓存级别 介质 L1 缓存 (Cache 插件) CoreDNS L2 缓存 (Redis) Redis L3 缓存 (数据库集群) MySQL/PostgreSQL/SQLite 客户端查询 → CoreDNS → L1缓存检查 → L2缓存检查 → 数据库 → 缓存回填 → 返回结果\n部署架构 在这里，一个企业内的 DNS 系统称之为 DNS “拓扑” (Topology)，每一个机房称之为 “拓扑点” (Point in a Topology)，一个拓扑点表示在一个机房内需要部署的软件或者程序的单位。一个拓扑有多个拓扑点组成。\ntip 每个拓扑点包含企业内的私有云和公有云。只要想为这个机房做区域解析，那么这个拓扑点就需要部署下面的组件。 组件 实例数量 部署方式 CoreDNS 1+ 1+/拓扑点 Redis 1 1+/拓扑点 MySQL/PostgreSQL/SQLite 1 只读节点/拓扑点 运维工具设计 这里也提供 CLI 工具为用户提供服务注册，或者自动化脚本，以及证书获取和更新使用。初期暂定兼容 caddy。\ninfo 这里的更新证书指的是：使用证书的第三方客户端通过这个获取证书，或者检查证书是否更新等能力。 免费 SSL 证书签发管理设计 这里提供 “Let\u0026rsquo;s Encrypt” 证书自动申请与续期方案，项目采用成熟的 ACME 客户端工具，不自行实现 Let\u0026rsquo;s Encrypt 协议，确保系统兼容性。\n组件 工具/SDK GitHub 地址 ACME 客户端 lego https://github.com/go-acme/lego 证书申请流程 步骤 操作 验证方式 备注 1. 域名验证 DNS-01 Challenge TXT 记录验证 支持通配符证书 2. 证书申请 ACME v2 协议 Let\u0026rsquo;s Encrypt API 使用 lego 库 3. 证书存储 数据库 4. 证书部署 自动分发 + 运维工具 运维工具 自动续期策略 续期条件 检查频率 提前时间 失败处理 证书过期前30天 不检查或周期性检查 30天 告警通知 + 重试 手动触发续期 按需 立即 实时状态反馈 自动续期任务 每天 自定义，默认3天 告警通知 + 重试 批量续期任务 每周 配置化 失败日志记录 外部系统服务注册设计 域名系统也是为企业名字服务做一个基石，提供外部服务的自动注册与生命周期管理。\n注册方式支持 注册方式 技术实现 适用场景 需求 Agent 注册 / API 注册 SDK / RESTful API 类似 k8s 集群中服务注册 按需 K8S 集群集成 client-go 多集群统一 dns 系统 按需 手动添加 基于 cli/admin 手动创建域名使用 高 DNS聚合 DNS聚合包含的功能如下：\n公有云 DNS 平台接入，可以将 DNS 系统内的域名同步到公有云中，实现切换。 管理系统使用 CoreDNS 作为基础服务底座，用户依然可以使用开源 DNS 平台底座来替换 CoreDNS。 DNS平台 调研 Namesilo Namecheap CloudFlare 阿里云 腾讯云 (DNSPOD) 华为云 百度云 CoreDNS Bind PowerDNS 完成度 平台管理 功能模块 详细描述 错误更正 优先级 完成度 View 支持多视图配置，智能解析 view/geo 高 完成 Zone 管理 DNS 区域管理与配置 高 完成 域名管理 管理域中的域名解析 这里应该为record管理 完成 Transfer 与云 DNS 供应商数据同步 高 白名单 域名解析黑白名单控制，包含全局和单域名 高 证书管理 Let\u0026rsquo;s Encrypt 证书自动申请与续期 高 完成调研，待接入 解析切换 提供多解析功能，实现故障切换 高 完成调研，待接入 域名检测 域名可用性检测，可以配合切换功能完成故障自动切换 高 完成调研，待接入，其他组建已经实现 DNS端 功能模块 详细描述 错误更正 优先级 完成度 View查询 支持多视图配置，智能解析 view/geo 高 完成 Record 完成 多级缓存策略更正 之前设计为：多级缓存策略\ntip 缓存级别 介质 L1 缓存 (Cache 插件) CoreDNS L2 缓存 (Redis) Redis L3 缓存 (数据库集群) MySQL/PostgreSQL/SQLite 客户端查询 → CoreDNS → L1缓存检查 → L2缓存检查 → 数据库 → 缓存回填 → 返回结果\n因为之前设计有个致命缺陷，不管是 L2 还是 L3 都是一种“强依赖性的单点故障架构”。这种架构的致命缺陷就是缺乏“故障隔离”而导致“级联失效”，所有服务不可用。例如当大量攻击来的时候，dns 系统不存在这些记录，将直接打穿 L1 - L3，实际上没有什么用。这里引入了 freecache 设计了可自主控制的订阅式内存管理模型，被设计了为两组内存块，一组缓存域名，提供了更新通知功能，不依赖任何三方缓存，不存在网络IO等，直接内存模式，性能提升30倍左右。第二组为安全模块，被攻击时可有效记录IP，定期上报到舆情库，也会存储多种安全策略。当大量攻击时，可以通过切换策略的方式完成开放式转换为白名单模式，从进程上就可以防御住请求，也会记录无效域名，被攻击时只有首次会被打穿，有效的做到故障隔离功能。\n缓存级别 介质 L1 缓存 (FreeCache 插件) FreeCache L2 缓存 (数据库集群) YugabyteDB 新增功能 功能 完成度 域名资产管理，包含域名过期/续费跳转/NS切换等 无 证书资产管理 无 备用解析切换 无 DNS优选 无 防封（动态切换解析结果） 无 Load Balance 50% 谷歌舆情库检测 无 域名备案检测 无 智能调度 已完成 灰度 无 被墙检测 无 open claw skill 无 域名比价 无 ","permalink":"https://www.161616.top/coredns-serial-04-system-design/","summary":"本文是CoreDNS 开发系列第4章 CoreDNS 开发系列-1：CoreDNS核心概念和安装配置说明 CoreDNS 开发系列-2：CoreDNS插件详解 CoreDNS 开发系列-3：开发自定义插件 CoreDNS 开发系列-4：高并发域名平台架构设计 在本章节中将创建一个项目，旨在构建一个基于 CoreDNS 的域名系统，该项目目的是可以构建一个完整的 DNS 平台而不是管理平台，可以作为独立的 DNS Provider 运行业务域名解析；也可以作为企业内部 DNS的使用。\n分层架构设计 系统采用四层架构设计：\n视图层：也称为 API 层，负责提供 DNS 的管理后台， API 接口。 业务层：主要作为 CoreDNS Plugins 实现，用于集成 DNS Server。 数据层：视图层与业务层的联动，多数据库支持（SQLite/MySQL/PostgreSQL）、Redis 缓存、数据同步。 基础设施层：提供 command line 工具，并为 DNS Server 提供四层防火墙、监控告警数据。并提供整个公司生态的服务注册、服务发现等功能。 系统功能需求 功能模块 详细描述 优先级 View 支持多视图配置，智能解析 高 Zone 管理 DNS 区域管理与配置 高 域名管理 管理域中的域名解析 高 Transfer 与云 DNS 供应商数据同步 高 白名单 域名解析黑白名单控制，包含全局和单域名 高 证书管理 Let\u0026rsquo;s Encrypt 证书自动申请与续期 高 解析切换 提供多解析功能，实现故障切换 高 域名检测 域名可用性检测，可以配合切换功能完成故障自动切换 高 CDN 集成 CDN 切换与管理功能 中 运维工具 提供命令行工具 中 GeoIP 基于地理位置的智能 DNS 解析 低 外部系统注册 服务自动注册与发现 低 性能需求 性能指标 目标值 备注 QPS 100,000 一个 view 内的集群至少达到的值 项目分模块设计 视图层 模块名称 功能描述 域、域名管理 完成基础 Zone, Domain的管理功能 证书管理 Let\u0026rsquo;s Encrypt 证书自动申请与续期 View管理 管理域与业务层插件进行联动 白名单管理 域名解析黑白名单控制 Geo 管理 基于地理位置的智能 DNS 解析 CDN 集成 集成CDN联动功能 服务注册插件 提供全企业内的服务注册功能（View+业务） 业务层 模块名称 功能描述 实现方式 DNS 服务 DNS 查询处理、插件加载管理 原生 coredns 数据库 Zone, Domain 信息入库，通过库 Custom Plugin Redis 缓存插件 Zone, Domain 的热点缓存功能 Custom Plugin GeoIP 插件 基于地理位置的智能解析 Custom Plugin View View 的管理和配置 Custom Plugin 白名单 域名访问控制 Custom Plugin Transfer 区域传送与同步 Custom Plugin 基础设施层 模块名称 功能描述 实现技术 服务发现 服务注册与发现 Nacos DNS查询流程设计 多级缓存策略","title":"CoreDNS 开发系列-4：高并发域名平台架构设计"},{"content":" 本文是CoreDNS 开发系列第3章 CoreDNS 开发系列-1：CoreDNS核心概念和安装配置说明 CoreDNS 开发系列-2：CoreDNS插件详解 CoreDNS 开发系列-3：开发自定义插件 CoreDNS 开发系列-4：高并发域名平台架构设计 在上一篇文章中，我们了解了 CoreDNS 的基本概念、安装和配置文件的说明，与 CoreDNS 中的插件，本文将从零开始，深入学习 CoreDNS 插件开发的完整流程，让您能够开发出满足特定业务需求的高质量插件。在最后我们学习一下 CoreDNS 中的外部插件用以引入开发思路，来解决我们平台中的一些难点。\n代码构成 在学习开发插件之前，我们先需要了解下 CoreDNS 的代码目录构成。\ntext 1 2 3 4 5 6 7 8 9 10 11 coredns/ ├── core/ # 核心组件 │ ├── dnsserver/ # DNS 服务器实现 │ └── plugin/ # 插件管理框架 ├── plugin/ # 内置插件目录 │ ├── cache/ # 缓存插件 │ ├── forward/ # 转发插件 │ ├── kubernetes/ # Kubernetes插件 │ └── ... # 还有很多插件 ├── request/ # 请求处理相关，封装了一些处理 DNS 请求的上下文信息，这部分在插件中用的比较多 └── test/ # 测试工具和用例 插件开发核心概念 在 CoreDNS 中，插件开发核心概念是 “注册” [1] (Register) ，详细的步骤大致分为如下几步：\nConfigration, 用户通过 CoreDNS 的默认 Corefile 完成配置加载，这个文件也可以按照第一章中提到的 -conf 参数来指定。 Setup, 解析配置和内部数据结构，并完成初始化。 Handler, 是 Plugin 的入口，用户必须对自己的插件继承这个 plugin.Handler 接口。 Plugin Integration, 用户在 plugin.cfg 整合自己的插件代码，并完成编译。 注册 (Register) 和设置 (Setup) 你的插件 注册 (Register) 首先你的插件需要下面函数的调用才能完成注册的操作，每一个插件拥有一个 name（实例中叫 foo）\ngo 1 func init() { plugin.Register(\u0026#34;foo\u0026#34;, setup) } 在上面代码所示，setup 函数将会被调用以完成注册。\n设置 (Setup) Setup 函数的功能就是载入配置文件与填充内部结构，他的参数是 *caddy.Controller ，他的返回值是 error\ngo 1 2 3 4 5 6 7 8 9 func setup(c *caddy.Controller) error { if err != nil { return plugin.Error(\u0026#34;foo\u0026#34;, err) } // various other code return nil } 如果我们在配置中配置了下面的参数\ntext 1 foo gizmo 那么在获取这个参数可以使用下面代码\ngo 1 2 3 4 5 6 for c.Next() { // Skip the plugin name, \u0026#34;foo\u0026#34; in this case. if !c.NextArg() { // Expect at least one value. return c.ArgErr() // Otherwise it\u0026#39;s an error. } value := c.Val() // Use the value. } 用户可以使用 c.Next() 来迭代参数，如果 c.Next() 为 True，因为参数可能存在多个，所以可以迭代多次来完成参数的获取。需要主义的是，第一个参数通常为 Plugin name。在大多数教程中都交给用户是跳过第一个参数。\n编写插件 (Handler) Handler 可以称之为你的插件的入口，定义插件的要求如下：\n因为 plugin.Handler 接口的定义，定义 handler 必须要实现方法 ServeDNS ，ServeDNS 是处理每个 DNS 查询请求的方法。 以及至少一个属性 “next”，next 表示在 CoreDNS 的请求链 (Chain) 中的下一个插件。 必须包含 Name() 方法 实现自定义插件的伪代码如下:\ngo 1 2 3 4 5 6 7 8 9 10 11 type MyHandler struct { Next plugin.Handler } func (h MyHandler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { return h.Next.ServeDNS(ctx, w, r) } func (h MyHandler) Name() string { return \u0026#34;foo\u0026#34; } 在上面可以看到，ServeDNS() 返回两个参数，响应 (int) 和错误 (error)，如果 error 不为空，则返回 SERVFAIL 到客户端。响应代码会告诉 CoreDNS 插件链 ”是否已经写入回复“，如果没有写入，CoreDNS会自行处理这个情况。\nCoreDNS 会处理下面场景 [2] ：\nSERVFAIL (dns.RcodeServerFailure) REFUSED (dns.RcodeRefused) FORMERR (dns.RcodeFormatError) NOTIMP (dns.RcodeNotImplemented) 还有一些常用的返回码：\nRcodeSuccess: No error RcodeServerFailure: Server failure RcodeNameError: Domain doesn\u0026rsquo;t exist RcodeNotImplemented: Record type not implemented 将自己插件编译到CoreDNS中 CoreDNS 提供了两种方式可以运行自定义插件，即 “编译时配置” (Compile-time Configuration) 和 “包装源码” 方式，\n编译时配置 “编译时配置” (Compile-time Configuration) 也是官方在描述编译自定义插件的方式，就是在 CoreDNS 源码的 “plugin.cfg” 文件内增加你的插件然后运行 go generate。这个方式你必须 Clone CoreDNS 的源码进行修改和编译。\n修改 plugin.cfg\ntext 1 2 3 etcd:etcd # 编译按照下面的说明进行添加 {you-plugin-name}:github.com/{you-github-account}/{you-plugin-name} Wrapping code 也可以根据 CoreDNS 官方的模式进行修改源码，在代码 core/plugin/zplugin.go 可以看到\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package plugin import ( // Include all plugins. _ \u0026#34;github.com/coredns/caddy/onevent\u0026#34; _ \u0026#34;github.com/coredns/coredns/plugin/acl\u0026#34; _ \u0026#34;github.com/coredns/coredns/plugin/any\u0026#34; _ \u0026#34;github.com/coredns/coredns/plugin/auto\u0026#34; _ \u0026#34;github.com/coredns/coredns/plugin/autopath\u0026#34; _ \u0026#34;github.com/coredns/coredns/plugin/azure\u0026#34; _ \u0026#34;github.com/coredns/coredns/plugin/bind\u0026#34; _ \u0026#34;github.com/coredns/coredns/plugin/bufsize\u0026#34; _ \u0026#34;github.com/coredns/coredns/plugin/cache\u0026#34; _ \u0026#34;github.com/coredns/coredns/plugin/cancel\u0026#34; 然后在代码 core/coredns.go 中可以看到，wrap 了 github.com/coredns/coredns/core/dnsserver\ngo 1 2 3 4 5 6 7 // Package core registers the server and all plugins we support. package core import ( // plug in the server _ \u0026#34;github.com/coredns/coredns/core/dnsserver\u0026#34; ) 在 coredns.go 中可以看到是这样执行的\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 package main //go:generate go run directives_generate.go //go:generate go run owners_generate.go import ( _ \u0026#34;github.com/coredns/coredns/core/plugin\u0026#34; // Plug in CoreDNS. \u0026#34;github.com/coredns/coredns/coremain\u0026#34; ) func main() { coremain.Run() } 通过这个我们可以推理出两种方式来编译外部自定义插件\n把插件放置到指定位置，修改代码，将其引入。 自行引入 coremain, dnsserver；接着运行。 方法2，如下代码所示 [3]\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package main import ( _ \u0026#34;github.com/you/foo\u0026#34; \u0026#34;github.com/coredns/coredns/coremain\u0026#34; \u0026#34;github.com/coredns/coredns/core/dnsserver\u0026#34; ) var directives = []string{ \u0026#34;foo\u0026#34;, ... ... \u0026#34;whoami\u0026#34;, \u0026#34;startup\u0026#34;, \u0026#34;shutdown\u0026#34;, } func init() { dnsserver.Directives = directives } func main() { coremain.Run() } 最后编译自己的 CoreDNS Server 即可。\n外部插件源码学习 通过前面部分我们掌握了自定义 CoreDNS 插件的 Specification，下面将以 mysql 插件来深入的学习开发自定义插件。\nmysql 插件是允许用户使用 mysql 作为 zone 数据持久化的介质，通过配置可以预估他的代码内容。\ntext 1 2 3 4 5 6 7 8 9 mysql { dsn DSN [table_prefix TABLE_PREFIX] [max_lifetime MAX_LIFETIME] [max_open_connections MAX_OPEN_CONNECTIONS] [max_idle_connections MAX_IDLE_CONNECTIONS] [ttl DEFAULT_TTL] [zone_update_interval ZONE_UPDATE_INTERVAL] } 通过上面配置可以知道，在 setup 中一定是迭代所有的参数解析为插件运行的配置，正如代码 coredns_mysql/setup.go 所示\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 func mysqlParse(c *caddy.Controller) (*CoreDNSMySql, error) { mysql := CoreDNSMySql{ TablePrefix: \u0026#34;coredns_\u0026#34;, Ttl: 300, } var err error c.Next() if c.NextBlock() { for { switch c.Val() { case \u0026#34;dsn\u0026#34;: if !c.NextArg() { return \u0026amp;CoreDNSMySql{}, c.ArgErr() } mysql.Dsn = c.Val() case \u0026#34;table_prefix\u0026#34;: if !c.NextArg() { return \u0026amp;CoreDNSMySql{}, c.ArgErr() } mysql.TablePrefix = c.Val() case \u0026#34;max_lifetime\u0026#34;: if !c.NextArg() { return \u0026amp;CoreDNSMySql{}, c.ArgErr() } var val time.Duration val, err = time.ParseDuration(c.Val()) if err != nil { val = defaultMaxLifeTime } mysql.MaxLifetime = val case \u0026#34;max_open_connections\u0026#34;: if !c.NextArg() { return \u0026amp;CoreDNSMySql{}, c.ArgErr() } var val int val, err = strconv.Atoi(c.Val()) if err != nil { val = defaultMaxOpenConnections } mysql.MaxOpenConnections = val case \u0026#34;max_idle_connections\u0026#34;: if !c.NextArg() { return \u0026amp;CoreDNSMySql{}, c.ArgErr() } var val int val, err = strconv.Atoi(c.Val()) if err != nil { val = defaultMaxIdleConnections } mysql.MaxIdleConnections = val case \u0026#34;zone_update_interval\u0026#34;: if !c.NextArg() { return \u0026amp;CoreDNSMySql{}, c.ArgErr() } var val time.Duration val, err = time.ParseDuration(c.Val()) if err != nil { val = defaultZoneUpdateTime } mysql.zoneUpdateTime = val case \u0026#34;ttl\u0026#34;: if !c.NextArg() { return \u0026amp;CoreDNSMySql{}, c.ArgErr() } var val int val, err = strconv.Atoi(c.Val()) if err != nil { val = defaultTtl } mysql.Ttl = uint32(val) default: if c.Val() != \u0026#34;}\u0026#34; { return \u0026amp;CoreDNSMySql{}, c.Errf(\u0026#34;unknown property \u0026#39;%s\u0026#39;\u0026#34;, c.Val()) } } if !c.Next() { break } } } db, err := mysql.db() if err != nil { return nil, err } err = db.Ping() if err != nil { return nil, err } defer db.Close() mysql.tableName = mysql.TablePrefix + \u0026#34;records\u0026#34; return \u0026amp;mysql, nil } 上面可以看到通过迭代 c.Next()，并遗弃第一个（第一个作为插件名称）然后为为每个配置赋值。\n代码 handler.go 中有定义其配置的数据结构\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 type CoreDNSMySql struct { Next plugin.Handler Dsn string TablePrefix string MaxLifetime time.Duration MaxOpenConnections int MaxIdleConnections int Ttl uint32 tableName string lastZoneUpdate time.Time zoneUpdateTime time.Duration zones []string } 在大致了解了代码结构后，在了解下文件结构\n文件 作用 handler.go 定义了 handler 的实现 mysql.go model 相关的操作 setup.go 配置 setup函数，初始化配置，并填充到配置数据结构中 type.go 用于定义数据库表类型和查询结果的内容 开发第一个提供静态 DNS 查询的插件 通过上面的学习，在这个任务中，我们来开发一个 “静态记录插件”。这个插件可以从配置文件中读取静态的 DNS 记录并提供查询服务。\n实现基本的 DNS 记录响应 首先定义插件的数据结构\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 package staticdns import ( \u0026#34;context\u0026#34; \u0026#34;net\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;github.com/coredns/coredns/plugin\u0026#34; \u0026#34;github.com/miekg/dns\u0026#34; ) type StaticDNS struct { Next plugin.Handler Records map[string][]Record } type Record struct { Type uint16 Value string TTL uint32 } func (s StaticDNS) Name() string { return \u0026#34;staticdns\u0026#34; } // 实现核心的查询处理逻辑 func (s StaticDNS) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { if len(r.Question) == 0 { return plugin.NextOrFailure(s.Name(), s.Next, ctx, w, r) } q := r.Question[0] name := strings.ToLower(q.Name) // 查找对应的记录 records, exists := s.Records[name] if !exists { return plugin.NextOrFailure(s.Name(), s.Next, ctx, w, r) } // 构建响应 m := new(dns.Msg) m.SetReply(r) for _, record := range records { if record.Type == q.Qtype || q.Qtype == dns.TypeANY { rr := s.createResourceRecord(name, record) if rr != nil { m.Answer = append(m.Answer, rr) } } } if len(m.Answer) \u0026gt; 0 { w.WriteMsg(m) return dns.RcodeSuccess, nil } return plugin.NextOrFailure(s.Name(), s.Next, ctx, w, r) } // 创建 DNS 资源记录的辅助方法 func (s StaticDNS) createResourceRecord(name string, record Record) dns.RR { switch record.Type { case dns.TypeA: ip := net.ParseIP(record.Value) if ip == nil || ip.To4() == nil { return nil } return \u0026amp;dns.A{ Hdr: dns.RR_Header{ Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: record.TTL, }, A: ip.To4(), } case dns.TypeAAAA: ip := net.ParseIP(record.Value) if ip == nil || ip.To4() != nil { return nil } return \u0026amp;dns.AAAA{ Hdr: dns.RR_Header{ Name: name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: record.TTL, }, AAAA: ip, } case dns.TypeCNAME: return \u0026amp;dns.CNAME{ Hdr: dns.RR_Header{ Name: name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: record.TTL, }, Target: dns.Fqdn(record.Value), } } return nil } Setup： 将插件注册到 CoreDNS go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 package staticdns import ( \u0026#34;strconv\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;github.com/coredns/caddy\u0026#34; \u0026#34;github.com/coredns/coredns/core/dnsserver\u0026#34; \u0026#34;github.com/coredns/coredns/plugin\u0026#34; \u0026#34;github.com/miekg/dns\u0026#34; ) func init() { caddy.RegisterPlugin(\u0026#34;staticdns\u0026#34;, caddy.Plugin{ ServerType: \u0026#34;dns\u0026#34;, Action: setup, }) } func setup(c *caddy.Controller) error { records := make(map[string][]Record) for c.Next() { args := c.RemainingArgs() if len(args) \u0026gt; 0 { return c.ArgErr() } for c.NextBlock() { if err := parseRecord(c, records); err != nil { return err } } } plugin := \u0026amp;StaticDNS{Records: records} dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { plugin.Next = next return plugin }) return nil } func parseRecord(c *caddy.Controller, records map[string][]Record) error { args := c.RemainingArgs() if len(args) \u0026lt; 3 { return c.Errf(\u0026#34;invalid record format, expected: domain type value [ttl]\u0026#34;) } domain := strings.ToLower(args[0]) if !strings.HasSuffix(domain, \u0026#34;.\u0026#34;) { domain += \u0026#34;.\u0026#34; } recordType := parseRecordType(args[1]) if recordType == 0 { return c.Errf(\u0026#34;unsupported record type: %s\u0026#34;, args[1]) } value := args[2] ttl := uint32(3600) // 默认 TTL if len(args) \u0026gt; 3 { if t, err := strconv.ParseUint(args[3], 10, 32); err == nil { ttl = uint32(t) } } record := Record{ Type: recordType, Value: value, TTL: ttl, } records[domain] = append(records[domain], record) return nil } func parseRecordType(typeStr string) uint16 { switch strings.ToUpper(typeStr) { case \u0026#34;A\u0026#34;: return dns.TypeA case \u0026#34;AAAA\u0026#34;: return dns.TypeAAAA case \u0026#34;CNAME\u0026#34;: return dns.TypeCNAME default: return 0 } } Configration - 配置插件可以解析域 text 1 2 3 4 5 6 7 8 9 10 11 12 .:53 { staticdns { example.com A 10.0.0.110 300 example.com AAAA 2001:db8::1 300 www.example.com CNAME example.com 600 mail.example.com A 10.0.0.111 blog.example.com A 10.0.0.112 } forward . 8.8.8.8 log errors } 上面配置定义了几条静态 DNS 记录，当客户端查询对应域名时，插件会返回配置的记录。\nCompile - 将插件整合到 CoreDNS 中 因为这里我们不是作为一个公共仓库，也不是使用载入 coredns 主包的方式进行的，所以我们需要把代码放置于 coredns plugins/staticdns 中\n然后在 core/plugin/zplugin.go 中增加下面一行\ngo 1 _ \u0026#34;github.com/coredns/coredns/plugin/staticdns\u0026#34; 然后编译代码\nbash 1 go build 总结 在本章节中，详细的展开对 coredns 插件的编写部分，通过 CoreDNS 目录结构，代码构成 以及 CoreDNS 自定义插件编写规范，简单学习了 CoreDNS的源码；通过外部插件 mysql 学习了插件定义的思路，以及自定义了一个 staticdns 插件三个部分完成学习 coredns 自定义插件。\n在下一篇文章中，将从系统需求开始，一步步学习完成系统设计，以为后面实高性能 DNS 域名平台做好准备。\nReference [1] How to Register a CoreDNS Plugin?\n[2] Writing Plugins\n[3] Developing Custom Plugins for CoreDNS\n","permalink":"https://www.161616.top/coredns-serial-03-custom-plugin/","summary":"本文是CoreDNS 开发系列第3章 CoreDNS 开发系列-1：CoreDNS核心概念和安装配置说明 CoreDNS 开发系列-2：CoreDNS插件详解 CoreDNS 开发系列-3：开发自定义插件 CoreDNS 开发系列-4：高并发域名平台架构设计 在上一篇文章中，我们了解了 CoreDNS 的基本概念、安装和配置文件的说明，与 CoreDNS 中的插件，本文将从零开始，深入学习 CoreDNS 插件开发的完整流程，让您能够开发出满足特定业务需求的高质量插件。在最后我们学习一下 CoreDNS 中的外部插件用以引入开发思路，来解决我们平台中的一些难点。\n代码构成 在学习开发插件之前，我们先需要了解下 CoreDNS 的代码目录构成。\ntext 1 2 3 4 5 6 7 8 9 10 11 coredns/ ├── core/ # 核心组件 │ ├── dnsserver/ # DNS 服务器实现 │ └── plugin/ # 插件管理框架 ├── plugin/ # 内置插件目录 │ ├── cache/ # 缓存插件 │ ├── forward/ # 转发插件 │ ├── kubernetes/ # Kubernetes插件 │ └── .","title":"CoreDNS 开发系列-3：开发自定义插件"},{"content":"问题描述 当获取了 EKS kubeconfig 后，使用该 kubeconfig 提示如下报错\nbash 1 2 $ kubectl get pod --kubeconfig kubeconfig error: You must be logged in to the server (Unauthorized) 但该 IAM 用户已经存在了管理员权限了\n问题原因 该文章有对这个问题进行描述\nquote 您不是集群创建者\n如果您的 IAM 实体未用于创建集群，说明您不是集群创建者。在这种情况下，请完成以下步骤，将您的 IAM 实体映射到 aws-auth ConfigMap 以允许访问集群 [1]\n这里检查和文章描述一致, 但还是这样的问题\nbash 1 2 3 4 5 6 $ aws sts get-caller-identity { \u0026#34;UserId\u0026#34;: \u0026#34;AIDAXxxxxxxxIIWKMR22Q\u0026#34;, \u0026#34;Account\u0026#34;: \u0026#34;55555555496\u0026#34;, \u0026#34;Arn\u0026#34;: \u0026#34;arn:aws:iam::55555555496:user/eks-user\u0026#34; } quote 这是因为，必须将该用户作为集群的 Access 进行关联，而不是授权 “EKS*” 相关权限 图 - 集群用户\r选择已经存在的 IAM 用户\n图 - 选择IAM用户\r选择一个权限，这里选择了 “AmazonEKSAdminPolicy”\n图 - 分配权限\r最后点击创建\n图 - 用户与集群关联创建\r最后根据提示检查 IAM 用户对集群的权限即可\nbash 1 2 $ kubectl get pod --kubeconfig kubeconfig Error from server (Forbidden): pods is forbidden: User \u0026#34;arn:aws:iam::555555555556:user/eks-user\u0026#34; cannot list resource \u0026#34;pods\u0026#34; in API group \u0026#34;\u0026#34; in the namespace \u0026#34;default\u0026#34; 遇到问题：“Error from server (Forbidden): pods is forbidden: User \u0026ldquo;arn:aws:iam::555555555556:user/eks-user\u0026rdquo; cannot list resource \u0026ldquo;pods\u0026rdquo; in API group \u0026quot;\u0026quot; in the namespace \u0026ldquo;default\u0026rdquo;”。这是因为 IAM 用户也需要对应的权限。\n图 - IAM用户权限关联\r总结：\nIAM 用户必须和 EKS 集群关联。 IAM 用户必须授权 EKS 相关权限。 Reference [1] 当我连接到 Amazon EKS API 服务器时，如何解决错误“您必须登录到服务器（未授权）”？\n","permalink":"https://www.161616.top/aws-eks-error-you-must-be-logged-in-to-the-server/","summary":"问题描述 当获取了 EKS kubeconfig 后，使用该 kubeconfig 提示如下报错\nbash 1 2 $ kubectl get pod --kubeconfig kubeconfig error: You must be logged in to the server (Unauthorized) 但该 IAM 用户已经存在了管理员权限了\n问题原因 该文章有对这个问题进行描述\nquote 您不是集群创建者\n如果您的 IAM 实体未用于创建集群，说明您不是集群创建者。在这种情况下，请完成以下步骤，将您的 IAM 实体映射到 aws-auth ConfigMap 以允许访问集群 [1]\n这里检查和文章描述一致, 但还是这样的问题\nbash 1 2 3 4 5 6 $ aws sts get-caller-identity { \u0026#34;UserId\u0026#34;: \u0026#34;AIDAXxxxxxxxIIWKMR22Q\u0026#34;, \u0026#34;Account\u0026#34;: \u0026#34;55555555496\u0026#34;, \u0026#34;Arn\u0026#34;: \u0026#34;arn:aws:iam::55555555496:user/eks-user\u0026#34; } quote 这是因为，必须将该用户作为集群的 Access 进行关联，而不是授权 “EKS*” 相关权限 图 - 集群用户\r选择已经存在的 IAM 用户","title":"解决AWS EKS error You must be logged in to the server (Unauthorized)"},{"content":"需求分析 openvpn在调研时不支持分流配置，需求是openvpn的开启不要影响现有的网络环境。在经过调研，发现 openvpn配置文件可以使用一些指令来指定访问某些地址的路由经过openvpn的设备，这样就可以实现了流量分流。\n配置指令说明 这里主要用到了下面的参数\n指令 说明 dhcp-option 添加额外的网络参数，可以是在客户端配置，或者服务端推送，这里有指定 DNS redirect-gateway def1 使用这个 def1 flag 可以使用0.0.0.0/1 and 128.0.0.0/1 来覆盖默认路由，这里的好处是不会擦除原有的默认网关 route 可以在建立连接后，自动添加一些路由，并且在TUN/TAP设备关闭后，自动销毁 gateway 默认来自 第二参数或者默认网关，第二参数为\nvpn_gateway 指远端的vpn地址\nnet_gateway 指 per-existing IP默认网关 route-nopull 当在客户端使用时，此选项有效禁止从Server将路由添加到客户端的路由表中，但是请注意，此选项仍然允许 Server 设置TCP/IP 客户端TUN/TAP接口的属性 （这里主要用作创建openvpn自己的网络接口）。 pull-filter 忽略server端push 的资源，这些选项就是来自 —pul 或者其他选项的，例如其他选项 dhcp-option/route/gateway 等。 最终的配置为\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 client proto udp explicit-exit-notify remote x.x.x.x 1149 dev tun resolv-retry infinite nobind persist-key plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so openvpn login USERNAME password PASSWORD pin OTP reneg-sec 0 persist-tun remote-cert-tls server verify-x509-name server_xxxxxxxxxxxxxxx name auth SHA256 auth-nocache cipher AES-128-GCM tls-client tls-version-min 1.2 tls-cipher TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256 ignore-unknown-option block-outside-dns # # Prevent Windows 10 DNS leak setenv opt block-outside-dns verb 3 static-challenge \u0026#34;Enter OTP: \u0026#34; 1 # 禁止服务端推送路由 route-nopull # 配置静态路由 route x.x.x.x 255.255.255.255 vpn_gateway route x.x.x.x 255.255.255.255 vpn_gateway # 忽略服务器推送的 DNS 服务器 # Existing DNS settings dhcp-option DNS 100.100.100.100 dhcp-option DNS 100.64.x.x dhcp-option DNS 8.8.8.8 dhcp-option DNS 8.8.4.4 # Ignore server-pushed DNS to prioritize Tailscale\u0026#39;s MagicDNS pull-filter ignore \u0026#34;dhcp-option DNS\u0026#34; pull-filter ignore \u0026#34;redirect-gateway\u0026#34; # Ignore server-pushed default route auth-user-pass pass \u0026lt;ca\u0026gt; -----BEGIN CERTIFICATE----- -----END CERTIFICATE----- \u0026lt;/ca\u0026gt; \u0026lt;cert\u0026gt; -----BEGIN CERTIFICATE----- -----END CERTIFICATE----- \u0026lt;/cert\u0026gt; \u0026lt;key\u0026gt; -----BEGIN PRIVATE KEY----- -----END PRIVATE KEY----- \u0026lt;/key\u0026gt; \u0026lt;tls-crypt\u0026gt; # # 2048 bit OpenVPN static key # -----BEGIN OpenVPN Static key V1----- -----END OpenVPN Static key V1----- \u0026lt;/tls-crypt\u0026gt; 在修改配置后的路由如下\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 Internet: Destination Gateway Flags Netif Expire default 192.168.10.1 UGScg en0 default link#23 UCSIg utun4 8.8.4.4 link#23 UHWIig utun4 8.8.8.8 link#23 UHWIig utun4 8.210.206.245/32 10.8.0.1 UGSc utun5 vpn_ip/32 192.168.10.1 UGSc en0 10.8/24 10.8.0.8 UGSc utun5 10.8.0.1 10.8.0.8 UH utun5 x.x.x.x/32 10.8.0.1 UGSc utun5 x.x.x.x/32 10.8.0.1 UGSc utun5 100.64/10 link#23 UCS utun4 100.64.x.x link#23 UHWIi utun4 100.73.x.x 100.73.x.x UH utun4 100.100.100.100/32 link#23 UCS utun4 100.100.100.100 link#23 UHWIi utun4 127 127.0.0.1 UCS lo0 127.0.0.1 127.0.0.1 UH lo0 169.254 link#16 UCS en0 ! 192.168.10 link#16 UCS en0 ! 192.168.10.1/32 link#16 UCS en0 ! 192.168.10.1 3c:52:a1:34:17:ac UHLWIir en0 1182 192.168.10.129 5c:e9:1e:c1:ee:8f UHLWI en0 1153 192.168.10.131 link#16 UHLWI en0 ! 192.168.10.162/32 link#16 UCS en0 ! 192.168.10.162 5c:e9:1e:c2:56:a9 UHLWI lo0 192.168.10.224 46:95:6b:76:3c:6e UHLWI en0 922 192.168.10.245 aa:ed:46:93:4f:88 UHLWIi en0 1088 224.0.0/4 link#16 UmCS en0 ! 224.0.0/4 link#23 UmCSI utun4 224.0.0.251 1:0:5e:0:0:fb UHmLWI en0 239.255.255.250 1:0:5e:7f:ff:fa UHmLWI en0 255.255.255.255/32 link#16 UCS en0 ! 255.255.255.255/32 link#23 UCSI utun4 在不修改配置的路由表如下\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 $ netstat -nr Routing tables Internet: Destination Gateway Flags Netif Expire 0/1 10.8.0.1 UGScg utun6 default 192.168.10.1 UGScg en0 default link#23 UCSIg utun4 8.8.4.4 link#23 UHWIig utun4 8.8.8.8 link#23 UHWIig utun4 8.210.206.245/32 10.8.0.1 UGSc utun5 vpn_ip/32 192.168.10.1 UGSc en0 10.8/24 10.8.0.8 UGSc utun5 10.8.0.1 10.8.0.8 UH utun5 x.x.x.x/32 10.8.0.1 UGSc utun5 x.x.x.x/32 10.8.0.1 UGSc utun5 100.64/10 link#23 UCS utun4 100.64.x.x link#23 UHWIi utun4 100.73.x.x 100.73.x.x UH utun4 100.100.100.100/32 link#23 UCS utun4 100.100.100.100 link#23 UHWIi utun4 127 127.0.0.1 UCS lo0 127.0.0.1 127.0.0.1 UH lo0 169.254 link#16 UCS en0 ! 192.168.10 link#16 UCS en0 ! 192.168.10.1/32 link#16 UCS en0 ! 192.168.10.1 3c:52:a1:34:17:ac UHLWIir en0 1182 192.168.10.129 5c:e9:1e:c1:ee:8f UHLWI en0 1153 192.168.10.131 link#16 UHLWI en0 ! 192.168.10.162/32 link#16 UCS en0 ! 192.168.10.162 5c:e9:1e:c2:56:a9 UHLWI lo0 192.168.10.224 46:95:6b:76:3c:6e UHLWI en0 922 192.168.10.245 aa:ed:46:93:4f:88 UHLWIi en0 1088 224.0.0/4 link#16 UmCS en0 ! 224.0.0/4 link#23 UmCSI utun4 224.0.0.251 1:0:5e:0:0:fb UHmLWI en0 239.255.255.250 1:0:5e:7f:ff:fa UHmLWI en0 255.255.255.255/32 link#16 UCS en0 ! 255.255.255.255/32 link#23 UCSI utun4 可以看到会多一条 0/1 路由。\n有了这些参数就可以成功的模拟一个反向分流的配置\n问题：和其他vpn一起使用时，无法解析dns 如下所示\nbash 1 2 $ ping xx.xx.com ping: cannot resolve xx.xx.com: Unknown host 这里以 MacOS 说明如何解决，使用 scutil 配置操作系统的dns解析\nbash 1 2 3 4 5 6 7 8 $ sudo scutil open d.init d.add SearchDomains * x.com x.top d.add ServerAddresses * 100.100.100.100 100.64.x.x d.add SupplementalMatchDomains * x.com x.top set State:/Network/Service/openvpn/DNS quit 查看 DNS 配置\nbash 1 2 3 4 5 6 7 8 9 10 11 12 $ scutil --dns DNS configuration resolver #1 search domain[0] : [tail769b0e.ts.net](http://tailasdas.ts.net/) search domain[1] : [x.com](http://x.com/) search domain[2] : x.top nameserver[0] : 100.100.100.100 if_index : 23 (utun4) flags : Supplemental, Request A records, Request AAAA records reach : 0x00000003 (Reachable,Transient Connection) order : 100400 Reference Reference manual for OpenVPN 2.6\n","permalink":"https://www.161616.top/resolve-openvpn-tailscale-routers-conflict/","summary":"需求分析 openvpn在调研时不支持分流配置，需求是openvpn的开启不要影响现有的网络环境。在经过调研，发现 openvpn配置文件可以使用一些指令来指定访问某些地址的路由经过openvpn的设备，这样就可以实现了流量分流。\n配置指令说明 这里主要用到了下面的参数\n指令 说明 dhcp-option 添加额外的网络参数，可以是在客户端配置，或者服务端推送，这里有指定 DNS redirect-gateway def1 使用这个 def1 flag 可以使用0.0.0.0/1 and 128.0.0.0/1 来覆盖默认路由，这里的好处是不会擦除原有的默认网关 route 可以在建立连接后，自动添加一些路由，并且在TUN/TAP设备关闭后，自动销毁 gateway 默认来自 第二参数或者默认网关，第二参数为\nvpn_gateway 指远端的vpn地址\nnet_gateway 指 per-existing IP默认网关 route-nopull 当在客户端使用时，此选项有效禁止从Server将路由添加到客户端的路由表中，但是请注意，此选项仍然允许 Server 设置TCP/IP 客户端TUN/TAP接口的属性 （这里主要用作创建openvpn自己的网络接口）。 pull-filter 忽略server端push 的资源，这些选项就是来自 —pul 或者其他选项的，例如其他选项 dhcp-option/route/gateway 等。 最终的配置为\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 client proto udp explicit-exit-notify remote x.","title":"解决openvpn与其他vpn路由冲突问题"},{"content":"插件说明 Jenkins 有一个 aws credentials 插件，可以关联到 pipeline 中使用\nhttps://plugins.jenkins.io/aws-credentials/\n官方也有给出示例，如何使用 pipeline 关联 ecr\ntext 1 2 3 withCredentials([[ $class: \u0026#39;AmazonWebServicesCredentialsBinding\u0026#39;, credentialsId: \u0026#39;plt-ia-dev-images-ecr-use1-read\u0026#39;, roleArn: \u0026#39;arn:aws:iam::130312249203:role/PullDockerImages\u0026#39;, roleSessionName: \u0026#39;PullDockerImages\u0026#39;]]){ sh \u0026#34;aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ecr_registry } 配置过程 创建一个 iam 用户，用于 jenkins 获取 ecr token使用\n图 - IAM User\r图创建用户 - 创建ecr-user\rnote 不要选择 “Provide user access to the AWS Management Console - optional ” 权限可以选择 “AmazonEC2ContainerRegistryFullAccess”， 也可根据自己需求进行选择\n图创建用户 - 给用户授权\r点创建即可\n图创建用户 - 创建ecr-user\r点击 Security credentials，生成一个 access key\n图用户授权 - 生成access key\r配置jenkins 安装 aws credentials 插件\n图 - Jenkins插件安装\r安装好后，在路径 管理 Jenkins =》Credentials =》 System =》Global credentials (unrestricted) 添加 aws access key\n图 - Jenkins Credentials创建\r选择 AWS Credentials\n图 - Jenkins Credentials创建2\r填写上面生成的 Access key 和 Secret\n图 - Jenkins Credentials创建3\r在 pipeline 中使用 text 1 2 3 4 5 withCredentials([[ $class: \u0026#39;AmazonWebServicesCredentialsBinding\u0026#39;, credentialsId: \u0026#34;{credential_id}\u0026#34;]]){ sh \u0026#39;\u0026#39;\u0026#39; aws ecr get-login-password --region ap-east-1 \u0026#39;\u0026#39;\u0026#39; } 完整的示例 pipeline\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 pipeline { agent any // 定义变量 environment { current_time = sh(script: \u0026#34;date \u0026#39;+%Y%m%d%H%M%S\u0026#39;\u0026#34;, returnStdout: true).trim() } // 发布步骤 stages { stage(\u0026#39;test aws credentials\u0026#39;){ steps { withCredentials([[ $class: \u0026#39;AmazonWebServicesCredentialsBinding\u0026#39;, credentialsId: \u0026#34;${registry_auth}\u0026#34;]]){ sh \u0026#39;\u0026#39;\u0026#39; aws ecr get-login-password --region ap-east-1 \u0026#39;\u0026#39;\u0026#39; } } } } } ","permalink":"https://www.161616.top/jenkins-pipeline-associate-aws-access-key/","summary":"插件说明 Jenkins 有一个 aws credentials 插件，可以关联到 pipeline 中使用\nhttps://plugins.jenkins.io/aws-credentials/\n官方也有给出示例，如何使用 pipeline 关联 ecr\ntext 1 2 3 withCredentials([[ $class: \u0026#39;AmazonWebServicesCredentialsBinding\u0026#39;, credentialsId: \u0026#39;plt-ia-dev-images-ecr-use1-read\u0026#39;, roleArn: \u0026#39;arn:aws:iam::130312249203:role/PullDockerImages\u0026#39;, roleSessionName: \u0026#39;PullDockerImages\u0026#39;]]){ sh \u0026#34;aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ecr_registry } 配置过程 创建一个 iam 用户，用于 jenkins 获取 ecr token使用\n图 - IAM User\r图创建用户 - 创建ecr-user\rnote 不要选择 “Provide user access to the AWS Management Console - optional ” 权限可以选择 “AmazonEC2ContainerRegistryFullAccess”， 也可根据自己需求进行选择","title":"jenkins pipeline关联aws access key"},{"content":" 本文是CoreDNS 开发系列第2章 CoreDNS 开发系列-1：CoreDNS核心概念和安装配置说明 CoreDNS 开发系列-2：CoreDNS插件详解 CoreDNS 开发系列-3：开发自定义插件 CoreDNS 开发系列-4：高并发域名平台架构设计 在上一篇文章中，我们了解了 CoreDNS 的基本概念、安装和配置文件的说明。在本文将深入探讨 CoreDNS 的插件 (Plugins)，这是 CoreDNS 强大功能的核心所在。通过理解插件机制和核心插件的工作原理，我们将为后续开发自定义插件和构建 DNS 平台奠定坚实基础。\n插件系统设计理念 通常情况下，CoreDNS Plugins在设计上围绕下面的原则进行的\n单一职责：每个插件只负责自己负责的功能 链式处理：插件按配置顺序执行链处理请求 可插拔性：插件可以灵活组合和替换 扩展性：支持用户自定义插件开发插件来扩展功能 基于这些原则，使得 CoreDNS 非常强大，非常易于扩展性（对比与最流行的两款开源 DNS 产品来说，Bind, PowerDNS）。\nCoreDNS与主流DNS服务产品的对比 下表是调研了三款最流行的 DNS 开源产品来对最符合本项目的后端作为对比，CoreDNS的灵活性相对比较适宜\n特性 CoreDNS BIND9 PowerDNS 类型 权威/递归 权威/递归 权威/递归 AXFR/IXFR 支持 支持, trasfer 插件 支持 支持 view 支持（geo, view插件） 原生支持（view 指令） 支持（仅仅LMDB后端） 数据库支持 支持（通过自定义插件 mysql, redis） 有限 DLZ模块 (Dynamically Loadable Zones) [1] 原生有限支持 [2] DNSSEC 支持（NSEC） 全面支持（NSEC/NSEC3） 全面支持（NSEC/NSEC3） 扩展性 极佳（插件架构） 有限扩展，支持c语言库作为plugins 良好 易用性 高（简单配置，现代化） 中（配置复杂，需经验） 高（Web 界面、API） 社区与生态 活跃（CNCF 项目，Kubernetes 生态） 非常活跃（ISC 维护） 活跃（商业支持） 与本项目需求契合度 极高（插件化支持View, AXFR/IXFR, GEO） 高（原生支持VIEW, AXFR/IXFR） 高（数据库支持强，API 同步简单） CoreDNS插件的类型 CoreDNS插件分为两种类型插件，“In-tree Plugins” [3] 和 “External Plugins” [4]；“In-tree Plugins” 是 CoreDNS 官方在提供 coredns 二进制文件时就内置编译到程序内的插件，用户可以自己去配置使用这些插件；“External Plugins” 是包含 CoreDNS 官方与三方作者维护的插件，用户如果想使用的话，需要自行编译 coredns 版本使用。\n核心插件说明 cache cache 插件是性能优化的核心组件，通过缓存 DNS 查询结果显著提高响应速度。当 cache 插件在启用后，所有的记录都会被缓存 3600s（除了 transfer 和 metadata 外）； cache插件常被用于的场景是当从 backend (上游dns，数据库后端) 中拿取数据代价高时可以提高查询效率。\n配置如下\ntext 1 cache [TTL] [ZONES...] TTL 为秒，如果未指定，则使用最大 TTL，对于 NOERROR 的相应为 3600，拒绝存在 (denial of existence) 则为 1800. ZONE 为要缓存的域名 (ZONE)，如果为空，则使用 “配置块” 的zone 如果需要更多控制可以参考下面配置\ntext 1 2 3 4 5 6 7 8 9 cache [TTL] [ZONES...] { success CAPACITY [TTL] [MINTTL] denial CAPACITY [TTL] [MINTTL] prefetch AMOUNT [[DURATION] [PERCENTAGE%]] serve_stale [DURATION] [REFRESH_MODE] servfail DURATION disable success|denial [ZONES...] keepttl } 上面配置说明了，每一个 cache 部分的详细配置，缓存中的每个元素都根据其TTL缓存，在默认情况下，一个缓存被分为 256 和分块 (shares)，每个分块可防止 39 个条目。每个缓存的总大小为 9984 个条目 (TTL最大情况下)。\n上面的 “{}” 配置是针对每种类型相应进行缓存的说明：\nsuccess: 覆盖缓存成功响应的设置。CAPACITY 指示在开始驱逐（随机）之前我们缓存的数据包的最大数量。TTL 覆盖缓存的最大 TTL。MINTTL 覆盖缓存的最小 TTL（默认 5），可以用来限制对后端的查询。 CAPACITY：缓存的最大数据包数，超过后随机逐出（eviction）。 TTL：覆盖缓存的最大 TTL，控制成功响应的最长缓存时间。 MINTTL：覆盖缓存的最小 TTL（默认 5 秒），用于限制对后端的查询频率。 denial: 覆盖缓存拒绝存在 (NXDOMAIN 或 NODATA)响应的设置。CAPACITY 指示在开始驱逐（LRU）之前我们缓存的数据包的最大数量。TTL 覆盖缓存的最大 TTL。MINTTL 覆盖缓存的最小 TTL（默认 5），可以用来限制对后端的查询。第三个类别（error），不被缓存。 CAPACITY：缓存的最大数据包数，超过后使用 LRU（Least Recently Used） 逐出策略。 TTL：覆盖缓存的最大 TTL。 MINTTL：覆盖缓存的最小 TTL（默认 5 秒），用于减少后端查询。 错误响应（error）不被缓存。 prefetch: 当热门项目即将从缓存中清除时将预取该项目。 Item 在 DURATION（默认 1 分钟）内收到 AMOUNT 次查询，且无超过 DURATION 的查询间隔。 预取在 TTL 低于 PERCENTAGE（默认 10%，范围 10%-90%，需带 % 符号）或 TTL 剩余 1 秒时触发。 PERCENTAGE：作为整数处理，例如 \u0026ldquo;10%\u0026rdquo; 被视为 10。 serve_stale: 当启用 serve_stale 时，缓存会向客户端返回已过期但未超过 DURATION（默认 1 小时）的条目。 默认情况下，发送过期条目后，缓存插件会尝试刷新条目。 响应的 TTL 设为 0。 REFRESH_MODE (immediate|verify) immediate（默认）：立即返回过期条目，再检查源是否可用。 verify：先验证条目是否在源不可用，再返回过期条目（可能增加延迟，但避免返回过时数据）。 servfail: 缓存 SERVFAIL 响应的时间。 DURATION 设置为 0 禁用 SERVFAIL 缓存。 默认缓存 5 秒，最大不超过 5 分钟。 disable: 禁用指定 ZONES 的成功 (success) 或拒绝存在 (denial) 的缓存。如果未指定 ZONES，则对所有 ZONES禁用指定的缓存类型。 keepttl: 从缓存返回响应时不减少 TTL（即返回原始 TTL，而不是剩余 TTL）。 更多配置可以参考官方文档 [5]\n详细配置示例\ntext 1 2 3 4 5 6 cache 30 { success 10000 300 60 # 缓存10000个成功响应，TTL 300秒，最小TTL 60秒 denial 1000 60 10 # 缓存1000个失败响应，TTL 60秒，最小TTL 10秒 prefetch 10 10% # 当访问量\u0026gt;10且TTL\u0026lt;10%时预取 serve_stale 24h # 过期记录可服务24小时 } forward forward 插件实现 DNS 查询转发功能，是 CoreDNS 与上游 DNS 服务器交互的核心。\n基本配置语法\ntext 1 2 3 4 5 6 7 8 9 10 11 12 forward FROM TO... { except IGNORED_NAMES... force_tcp prefer_udp expire DURATION max_fails INTEGER health_check DURATION [URL] tls CERT KEY CA tls_servername NAME policy random|round_robin|sequential max_concurrent MAX } FROM 是用于匹配需要转发的请求的基础域。使用展开为多个反向区域的 CIDR 表示法的域不受完全支持；仅使用第一个展开的区域。 TO… 是转发到的目标端点。TO 语法允许你指定协议、tls://9.9.9.9 或 dns://（或不指定协议）以获取纯 DNS。上游数量限制为 15 个。 配置示例 forward 中转发策略 (Policy) 还支持多种类型的负载均衡策略\n配置示例\ntext 1 2 3 4 5 6 forward . 8.8.8.8 8.8.4.4 1.1.1.1 { policy round_robin # 轮询策略 health_check 5s # 每5秒健康检查 max_fails 3 # 最大失败次数 expire 10s # 故障服务器恢复检查间隔 } 将 “example.org.” 所有请求代理到不同端口上运行的名称服务器\ntext 1 2 3 example.org { forward . 127.0.0.1:9005 } 转发除 example.org域之外的请求\nbash 1 2 3 4 5 . { forward . 10.0.0.10:1234 { except example.org } } forward 插件支持 DNS over TLS (DoT)：\ntext 1 2 3 4 forward . tls://8.8.8.8 { tls_servername dns.google health_check 5s } 更多配置说明可以参考官网说明 [6]\nkubernetes kubernetes plugin 是 CoreDNS 在云原生环境中的核心优势，提供与 Kubernetes 集群的深度集成，该插件实现了 “基于 DNS 的 Kubernetes 服务发现规范 [7]\n配置语法如下：\ntext 1 kubernetes [ZONES...] kubernetes 插件在配置时，如果仅指定插件 (kubernetes)，那该插件使用服务器块（server block）中定义的 ZONE作为默认的权威 ZONE。例如下面所示\ntext 1 2 3 example.com { kubernetes } kubernetes 插件会处理这个 “ZONE” 下面的所有 DNS 查询。在这种场景下不会为 “Service” 提供 PTR记录 (Reverse DNS Lookups) 或 “Pod” 的 A 记录。这意味着，在默认配置下，仅仅提供 Service 的 “DNS 解析”。\n如果为插件指定了 “ZONE”，kubernetes 会成为这些指定 “ZONE” 的权威DNS。如下面配置所示，\ntext 1 2 3 kubernetes cluster.local in-addr.arpa { ... } kubernetes 插件的配置如下\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 kubernetes [ZONES...] { # endpoint 为远程 k8s API 终结点指定 URL。如果忽略，则使用集群sa连接到群集内的 k8s。 endpoint URL # CERT KEY CACERT 是远程 k8s 连接的 TLS 证书、密钥和 CA 证书文件名。 # 如果在群集内连接（即未指定endpoint），则会忽略此选项。 tls CERT KEY CACERT # KUBECONFIG [CONTEXT] 使用 kubeconfig 文件对远程 k8s 集群的连接进行身份验证。 # [CONTEXT] 是可选的，如果未设置，则将使用 kubeconfig 中指定的当前上下文。 # 它支持 TLS、用户名和密码或基于令牌的身份验证。 # 如果在群集内连接（即未指定端点），则会忽略此选项。 kubeconfig KUBECONFIG [CONTEXT] # NAMESPACE [NAMESPACE…] 仅公开列出的 k8s 命名空间。如果忽略此选项，则会公开所有命名空间。 namespaces NAMESPACE... # 只暴露匹配特定标签选择器（label selector）的 Kubernetes 对象的 DNS 记录。 # 例如 labels environment in (staging, qa),application=nginx 为 # 只暴露带有标签 environment=staging 或 environment=qa， # 且 application=nginx 的 Kubernetes 对象的 DNS 记录。 labels EXPRESSION # 控制 Pod A 记录（如 1-2-3-4.ns.pod.cluster.local 返回 A 1.2.3.4）的处理模式， # 方便直接连接 Pod 时使用 SSL 证书。 # POD-MOD 的有效值 # disabled：默认。不处理 Pod 请求，始终返回 NXDOMAIN # insecure：直接根据请求中的 IP 返回 A 记录（不检查 Kubernetes）。 #\t易受恶意使用（如结合通配符 SSL 证书），仅为兼容 kube-dns。 # verified：检查同一命名空间内是否有匹配IP的Pod，若存在则返回 A 记录。 #\t需要更多内存，因为需watch所有Pod。 pods POD-MODE # 控制endpointA 记录的名称生成方式， # 例如“endpoint-name.my-service.namespace.svc.cluster.local. in A 1.2.3.4” # 默认：endpoint-name默认使用域名，如果未设置hostname ，使用endpoint的IP的连字符模式“1-2-3-4” # 指定该指令，默认使用hostname ，如果未设置hostname ，则使用 endpoint对应的Pod名称 # 若无Pod或Pod名称超过63字符，使用IP连字符模式。 endpoint_pod_names # 允许您为响应设置自定义 TTL。 # 默认值为 5 秒。允许的最小 TTL 为 0 秒，最大值上限为 3600 秒。 # 将 TTL 设置为 0 将阻止记录被缓存。 ttl TTL # 禁用endpoints的 DNS记录解析。 # 关闭对endpoints的watch。 # 所有endpoints查询和无头服务（headless service）查询返回 NXDOMAIN。 noendpoints # 故障转移，当插件对某个权威 zone 的查询返回 NXDOMAIN 时，将查询传递到插件链中的下一个插件处理。 # 如果不指定 [ZONES…]，对所有权威 zone 生效。 # 如果指定 zone（如 in-addr.arpa 或 ip6.arpa），仅对这些 zone 生效。 fallthrough [ZONES...] # 为没有任何就绪endpoint地址（例如，就绪Pod）的服务返回 NXDOMAIN。 # 这允许查询 Pod 继续在搜索路径中搜索服务。例如，搜索路径可以包含另一个 Kubernetes 集群。 ignore empty_service } 示例配置\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 kubernetes cluster.local { # 限制解析到特定标签的资源。 labels environment in (staging, qa),application=nginx # 启用 Pod A 记录验证。 pods verified # 使用 Pod 名称作为域名名称。 endpoint_pod_names ttl 30 # 禁用endpoints记录。 noendpoints # 支持查询透传。 fallthrough in-addr.arpa ip6.arpa # 增强跨集群服务发现。 ignore empty_service } 这是 coredns 官方提供的 coredns 配置文件 [8]，启动存在几个变量，通过查看脚本可以得到他的具体信息。\nUPSTREAMNAMESERVER 是从本机 /etc/resovle.conf 中读取的 CLUSTER_DOMAIN 默认为 cluster.local REVERSE_CIDRS 设置一些反向DNS的域，通常我们一个集群使用的就是 CLUSTER_DOMAIN yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Corefile: | .:53 { errors # 记录 DNS 查询中的错误，便于调试。 health { lameduck 5s # 提供健康检查端点，表示在 CoreDNS 关闭前等待 5 秒以处理现有请求。 } ready # readiness探针，用于 Kubernetes 探针。默认 :8181/ready 200 ok, 503 not kubernetes CLUSTER_DOMAIN REVERSE_CIDRS { fallthrough in-addr.arpa ip6.arpa # 允许未解析的反向请求透传到下一个插件。 } prometheus :9153 # 暴露 Prometheus 监控指标，监听在 9153 端口 forward . UPSTREAMNAMESERVER { # 将非集群内部的 DNS 请求转发到外部 DNS 服务器 max_concurrent 1000 } cache 30 # 缓存 DNS 响应 30 秒 loop # 检测并防止 DNS 循环。 reload # 支持动态重新加载 Corefile 配置。 loadbalance # 在多个 A/AAAA 记录之间进行负载均衡。 }STUBDOMAINS 总结：\nkubernetes 插件提供多种服务发现模式：\n服务发现：service.namespace.svc.cluster.local Pod 发现：pod-ip.namespace.pod.cluster.local 端点发现：endpoint.service.namespace.svc.cluster.local K8S中 域名负载均衡模式使用的是 loadbalance 插件实现的 File note File插件在第一章介绍 coredns 时已经说明过了。 该示例采自官网的实例，通过使用内置的 file 插件实现一个 Authoritative Server，使用了 example.org. 作为示例 zone ，他的数据将被存储到文件内。\nfile 插件的域名为\ntext 1 file DBFILE [ZONES...] DBFILE 部分为 CoreDNS 读取和解析的数据库文件\nZONES 为指定授权 Zone，如果为空，则继承 Server 块的 Zone\n他的参数如下所示：\ntext 1 2 3 file DBFILE [ZONES... ] { reload DURATION } reload 表示 SOA 版本更改时执行 ZONE 重新加载的间隔。默认值 1 分钟。 0表示不扫描更改并重新加载。\n例如上面配置的扩展，example.org 的数据库由 file 模块提供，transfer 则是提供了\ntext 1 2 3 4 5 6 example.org { file example.org.db transfer { to * 10.240.1.1 } } 那么 example.org.db 数据库文件内应该配置的信息如下\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 # $ORIGIN example.org. 定义了“根” zone，这里是 example.com. # “.” 表示 FQDN # 这里表示下面的记录都是围绕 “example.com.” $ORIGIN example.org. # “@”表示当前zone的根域名（因为 $ORIGIN 是 example.org.）。 # “3600” 为TTL，这条记录缓存时间为1小时（单位秒） # “IN”表示Internet记录 # “SOA” start of authority,权威起始记录， # sns.dns.icann.org.主名字服务器的FQDN # noc.dns.icann.org. zone管理员邮箱，在dns配置中，用“.”替换“@” # 2017042745 序列号（Serial)，通常用于DNS同步 ## AXFR (full zone transfer，官方称为 Authoritative Transfer) 理解全量同步 ## IXFR (Incremental transfer)，可以理解为增量同步 # “7200”表示刷新时间，从服务器每2小时向主服务器检查是否更新（单位秒） # “3600”表示重试时间，从到主无法连接时，会在1小时后重试（单位秒）。 # “1209600” 表示过期时间“从在两周内无法从主获取更新，则数据失效。 # “3600” 表示“Minimum TTL”，定义改zone中默认的 TTL @\t3600 IN\tSOA sns.dns.icann.org. noc.dns.icann.org. 2017042745 7200 3600 1209600 3600 # “NS” Name Server，查询 example.org 的信息，可以去找 a.iana-servers.net 这个DNS服务器 3600 IN NS a.iana-servers.net. 3600 IN NS b.iana-servers.net. # A记录为IPV4 # AAAA记录为IPV6 www IN A 127.0.0.1 IN AAAA ::1 note 下面的内容为不加注释的配置文件，也是 file plugin 中配置的数据库 “example.org.db” text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ORIGIN example.org. @\t3600 IN\tSOA sns.dns.icann.org. noc.dns.icann.org. ( 2017042745 ; serial 7200 ; refresh (2 hours) 3600 ; retry (1 hour) 1209600 ; expire (2 weeks) 3600 ; minimum (1 hour) ) 3600 IN NS a.iana-servers.net. 3600 IN NS b.iana-servers.net. www IN A 127.0.0.1 IN AAAA ::1 另外在 bind9 中也有对应的配置\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 example.com. 43200 IN SOA ns1.example.com. other.example.com. ( 2011090302 ;Serial Number 86400 ;refresh 7200 ;retry 1814400 ;expire 86400 ;minimum ) @ SOA ns1 ( ; ns1.basiczone.com is the primary server for basiczone.com postmaster ; contact email for basiczone.com is postmaster@basiczone.com 2004041700 ; Serial ID in reverse date format 21600 ; Refresh interval for slave servers 1800 ; Retry interval for slave servers 604800 ; Expire limit for cached info on slave servers 900 ) ; Minimum Cache TTL in zone records Auto CoreDNS 的 auto 插件用于从磁盘上的 RFC 1035 格式的主文件（zone file）中自动加载和提供 DNS 区域数据，特别适合传统 DNS 服务器的场景。也可以将 auto 插件视作是 file 插件的扩展，支持所有 file 插件的指令（directive），但增加了自动加载和动态重新加载的功能。\ntext 1 2 3 4 auto [ZONES...] { directory DIR [REGEXP ORIGIN_TEMPLATE] reload DURATION } ZONES：CoreDNS 会对这些区域的查询返回权威响应。\ndirectory DIR [REGEXP ORIGIN_TEMPLATE] 指定 CoreDNS 从哪个 directory 加载 RFC 1035 格式的区域文件。\nREGEXP 用于匹配文件名，文件名匹配的会将他作为提取来源。 ORIGIN_TEMPLATE 将用作来源模板，例如{1}是第一个匹配项，{2}是第二个。和正则用法类似。 reload DURATION 指定 CoreDNS 扫描 directory 指定的频率，以检测区域文件的添加、删除或修改。\n如果 ZONE 文件的 SOA 记录中的序列号 (serial number) 发生变化，CoreDNS 会自动重新加载该 ZONE 的数据。 默认值 1m 示例说明\n默认的 REGEXP 为 db.(.*)，默认的 ORIGIN_TEMPLATE 为 {1} 例如，路径为 db.example.com 时，REGEXP db.(.*) 匹配，ORIGIN_TEMPLATE “(.*)” 提取出 example.com，通过 {1} 转换为 ZONE 名称 example.com。 CoreDNS 将 db.example.com 识别为区域 example.com 的区域文件。 text 1 2 3 4 5 6 7 8 9 10 11 example.org example.com:53 { auto { directory /etc/coredns/zones db\\.(.*) {1} reload 30s } transfer { to * } log errors } 上面示例提到了，这里会对文件例如，example.org 和 example.com 文件拥有权威响应，每 30 秒检查目录，重新加载发生变化的区域文件。\n这里的目录文件必须为\ntext 1 2 3 /etc/coredns/zones/ ├── db.example.org ├── db.example.com 再一个示例：从目录 /etc/coredns/zones/org 加载 org 域，并将文件名视为 www.db.example.org ，其中 example.org 为来源。每 45 秒刷新文件一次。\ntext 1 2 3 4 5 6 org { auto { directory /etc/coredns/zones/org www\\.db\\.(.*) {1} reload 45s } } 根据这个示例我们来测试 auto 插件\n准备 example.org 文件\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ORIGIN example.org. $TTL 3600 @ IN SOA ns1.example.org. admin.example.org. ( 2025041101 ; serial 7200 ; refresh 3600 ; retry 1209600 ; expire 3600 ; minimum ) @ IN NS ns1.example.org. ns1 IN A 192.0.2.1 www IN A 192.0.2.2 shop IN A 127.0.0.1 blog IN A 10.0.0.1 准备 example.top 文件\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ORIGIN example.top. $TTL 3600 @ IN SOA ns1.example.top. admin.example.top. ( 124 ; serial 7200 ; refresh 3600 ; retry 1209600 ; expire 3600 ; minimum ) @ IN NS ns1.example.top. ns1 IN A 192.0.2.1 www IN A 10.0.0.2 oom IN A 127.0.0.1 bbs IN A 10.0.0.1 coredns 配置文件为\ntext 1 2 3 4 5 6 7 8 . { auto { directory /root/coredns1 (example\\.)(.*) {1}{2} reload 5s } log errors } tips 这里在测试时，ORIGIN_TEMPLATE 必须为 xxx.xx，不能是 org, com 这种，这样会无法得到解析 他的查询结果如下\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 $ dig @127.0.0.1 www.example.top ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.18.1-1ubuntu1.2-Ubuntu \u0026lt;\u0026lt;\u0026gt;\u0026gt; @127.0.0.1 www.example.top ; (1 server found) ;; global options: +cmd ;; Got answer: ;; -\u0026gt;\u0026gt;HEADER\u0026lt;\u0026lt;- opcode: QUERY, status: NOERROR, id: 44245 ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 ; COOKIE: 866306455fa1c477 (echoed) ;; QUESTION SECTION: ;www.example.top. IN A ;; ANSWER SECTION: www.example.top. 3600 IN A 10.0.0.2 ;; AUTHORITY SECTION: example.top. 3600 IN NS ns1.example.top. ;; Query time: 1 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) ;; WHEN: Fri Jun 13 00:43:13 CST 2025 ;; MSG SIZE rcvd: 127 $ dig @127.0.0.1 shop.example.org ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.18.1-1ubuntu1.2-Ubuntu \u0026lt;\u0026lt;\u0026gt;\u0026gt; @127.0.0.1 shop.example.org ; (1 server found) ;; global options: +cmd ;; Got answer: ;; -\u0026gt;\u0026gt;HEADER\u0026lt;\u0026lt;- opcode: QUERY, status: NOERROR, id: 60134 ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 ; COOKIE: 8e1827bc713c7cda (echoed) ;; QUESTION SECTION: ;shop.example.org. IN A ;; ANSWER SECTION: shop.example.org. 3600 IN A 127.0.0.1 ;; AUTHORITY SECTION: example.org. 3600 IN NS ns1.example.org. ;; Query time: 2 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) ;; WHEN: Fri Jun 13 00:43:40 CST 2025 ;; MSG SIZE rcvd: 129 总结 在本章节中，通过 forward, kubernetes, auto, file 展开了核心插件的功能特性。插件是 CoreDNS 强大功能的基础，通过合理配置和组合不同插件，可以构建满足各种需求的 DNS 服务。\n在通过这些插件，我们可以大致了解出，在定义 DNS 系统时，这些插件的实现就是作为我们系统定义开发的一个思路，auto 可以让我们自行定义并发现域名配置。以及 kubernetes DNS 部分也有了进一步的了解\n在下一篇文章中，开始从 CoreDNS 插件的源码进行分析学习，学习他的定义范式，以及实际开发自定义 CoreDNS 插件，通过实战案例掌握插件开发的完整流程，为构建高性能 DNS 域名平台做好准备。\nReference [1] Bind DLZ\n[2] PowerDNS Backends\n[3] in-tree plugins\n[4] External Plugins\n[5] cache plugin\n[6] forward plugin\n[7] 基于 DNS 的 Kubernetes 服务发现规范\n[8] coredns.yaml.sed\n","permalink":"https://www.161616.top/coredns-serial-02-coredns-plugins/","summary":"本文是CoreDNS 开发系列第2章 CoreDNS 开发系列-1：CoreDNS核心概念和安装配置说明 CoreDNS 开发系列-2：CoreDNS插件详解 CoreDNS 开发系列-3：开发自定义插件 CoreDNS 开发系列-4：高并发域名平台架构设计 在上一篇文章中，我们了解了 CoreDNS 的基本概念、安装和配置文件的说明。在本文将深入探讨 CoreDNS 的插件 (Plugins)，这是 CoreDNS 强大功能的核心所在。通过理解插件机制和核心插件的工作原理，我们将为后续开发自定义插件和构建 DNS 平台奠定坚实基础。\n插件系统设计理念 通常情况下，CoreDNS Plugins在设计上围绕下面的原则进行的\n单一职责：每个插件只负责自己负责的功能 链式处理：插件按配置顺序执行链处理请求 可插拔性：插件可以灵活组合和替换 扩展性：支持用户自定义插件开发插件来扩展功能 基于这些原则，使得 CoreDNS 非常强大，非常易于扩展性（对比与最流行的两款开源 DNS 产品来说，Bind, PowerDNS）。\nCoreDNS与主流DNS服务产品的对比 下表是调研了三款最流行的 DNS 开源产品来对最符合本项目的后端作为对比，CoreDNS的灵活性相对比较适宜\n特性 CoreDNS BIND9 PowerDNS 类型 权威/递归 权威/递归 权威/递归 AXFR/IXFR 支持 支持, trasfer 插件 支持 支持 view 支持（geo, view插件） 原生支持（view 指令） 支持（仅仅LMDB后端） 数据库支持 支持（通过自定义插件 mysql, redis） 有限 DLZ模块 (Dynamically Loadable Zones) [1] 原生有限支持 [2] DNSSEC 支持（NSEC） 全面支持（NSEC/NSEC3） 全面支持（NSEC/NSEC3） 扩展性 极佳（插件架构） 有限扩展，支持c语言库作为plugins 良好 易用性 高（简单配置，现代化） 中（配置复杂，需经验） 高（Web 界面、API） 社区与生态 活跃（CNCF 项目，Kubernetes 生态） 非常活跃（ISC 维护） 活跃（商业支持） 与本项目需求契合度 极高（插件化支持View, AXFR/IXFR, GEO） 高（原生支持VIEW, AXFR/IXFR） 高（数据库支持强，API 同步简单） CoreDNS插件的类型 CoreDNS插件分为两种类型插件，“In-tree Plugins” [3] 和 “External Plugins” [4]；“In-tree Plugins” 是 CoreDNS 官方在提供 coredns 二进制文件时就内置编译到程序内的插件，用户可以自己去配置使用这些插件；“External Plugins” 是包含 CoreDNS 官方与三方作者维护的插件，用户如果想使用的话，需要自行编译 coredns 版本使用。","title":"CoreDNS 开发系列-2：CoreDNS插件详解"},{"content":"下载 openssh-9.9p 源码包 下载地址\n下载之后解压看 README 和 INSTALL\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 libcrypto from either of LibreSSL or OpenSSL. Building without libcrypto is supported but severely restricts the available ciphers and algorithms. - LibreSSL (https://www.libressl.org/) 3.1.0 or greater - OpenSSL (https://www.openssl.org) 1.1.1 or greater LibreSSL/OpenSSL should be compiled as a position-independent library (i.e. -fPIC, eg by configuring OpenSSL as \u0026#34;./config [options] -fPIC\u0026#34; or LibreSSL as \u0026#34;CFLAGS=-fPIC ./configure\u0026#34;) otherwise OpenSSH will not be able to link with it. If you must use a non-position-independent libcrypto, then you may need to configure OpenSSH --without-pie. If you build either from source, running the OpenSSL self-test (\u0026#34;make tests\u0026#34;) or the LibreSSL equivalent (\u0026#34;make check\u0026#34;) and ensuring that all tests pass is strongly recommended. NB. If you operating system supports /dev/random, you should configure libcrypto (LibreSSL/OpenSSL) to use it. OpenSSH relies on libcrypto\u0026#39;s direct support of /dev/random, or failing that, either prngd or egd. 可以在 INSTALL 中查看到一些依赖的版本，例如 LibreSSL \u0026gt; 3.1.0; OpenSSL \u0026gt; 1.1.1，Rocky 9 满足依赖包的最低要求很多。相对于旧版本的 zlib 变为可选了（可能是我旧版本时候没仔细看）\nnotes 具体的依赖版本都可以通过 openssh 默认的 spec 文件里查询到，具体文件在解压后的 /contrib/redhat/openssh.spec 打包OpenSSH text 1 2 3 4 5 6 7 8 9 10 11 12 13 mkdir -p ~/rpmbuild/{SOURCES,SPECS} cd ~/rpmbuild/ wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-9.9p1.tar.gz wget https://mirror-hk.koddos.net/slackware/slackware-14.2/source/xap/x11-ssh-askpass/x11-ssh-askpass-1.2.4.1.tar.gz -O ~/rpmbuild/SOURCES/x11-ssh-askpass-1.2.4.1.tar.gz tar xf openssh-9.9p1.tar.gz cp openssh-9.9p1/contrib/redhat/* ~/rpmbuild/SOURCES/ # 该步骤文件是提前准备好的，如果需要替换请自行准备 \\cp -a ~/openssh-9.9p1/contrib/redhat/{sshd.service,openssh.spec} ~/rpmbuild/SPECS/ chown sshd:sshd ~/rpmbuild/SPECS/ -R sed -i \u0026#39;s@%define no_gnome_askpass 0@%define no_gnome_askpass 1@g\u0026#39; ~/rpmbuild/SPECS/openssh.spec sed -i \u0026#39;s@%define no_x11_askpass 0@%define no_x11_askpass 1@g\u0026#39; ~/rpmbuild/SPECS/openssh.spec \\cp ~/rpmbuild/openssh-9.9p1.tar.gz ~/rpmbuild/SOURCES/ cd ~/rpmbuild/ 下面是我的 spec 文件，可以和官方的进行对比，主要做了 init 换为 service unit 文件，如果不需要可以按照官方的来\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 %global ver 9.9p1 %global rel 1%{?dist} # OpenSSH privilege separation requires a user \u0026amp; group ID %global sshd_uid 74 %global sshd_gid 74 # Version of ssh-askpass %global aversion 1.2.4.1 # Do we want to disable building of x11-askpass? (1=yes 0=no) %global no_x11_askpass 0 # Do we want to disable building of gnome-askpass? (1=yes 0=no) %global no_gnome_askpass 0 # Do we want to link against a static libcrypto? (1=yes 0=no) %global static_libcrypto 0 # Do we want smartcard support (1=yes 0=no) %global scard 0 # Use GTK2 instead of GNOME in gnome-ssh-askpass %global gtk2 1 # Use build6x options for older RHEL builds # RHEL 7 not yet supported %if 0%{?rhel} \u0026gt; 6 %global build6x 0 %else %global build6x 1 %endif %global without_openssl 0 # build without openssl where 1.1.1 is not available %if %{defined fedora} \u0026amp;\u0026amp; 0%{?fedora} \u0026lt;= 28 %global without_openssl 1 %endif %if %{defined rhel} \u0026amp;\u0026amp; 0%{?rhel} \u0026lt;= 7 %global without_openssl 1 %endif # Do we want kerberos5 support (1=yes 0=no) %global kerberos5 1 # Reserve options to override askpass settings with: # rpm -ba|--rebuild --define \u0026#39;skip_xxx 1\u0026#39; %{?skip_x11_askpass:%global no_x11_askpass 1} %{?skip_gnome_askpass:%global no_gnome_askpass 1} # Add option to build without GTK2 for older platforms with only GTK+. # RedHat \u0026lt;= 7.2 and Red Hat Advanced Server 2.1 are examples. # rpm -ba|--rebuild --define \u0026#39;no_gtk2 1\u0026#39; %{?no_gtk2:%global gtk2 0} # Is this a build for RHL 6.x or earlier? %{?build_6x:%global build6x 1} # If this is RHL 6.x, the default configuration has sysconfdir in /usr/etc. %if %{build6x} %global _sysconfdir /etc %endif # Options for static OpenSSL link: # rpm -ba|--rebuild --define \u0026#34;static_openssl 1\u0026#34; %{?static_openssl:%global static_libcrypto 1} # Options for Smartcard support: (needs libsectok and openssl-engine) # rpm -ba|--rebuild --define \u0026#34;smartcard 1\u0026#34; %{?smartcard:%global scard 1} # Is this a build for the rescue CD (without PAM)? (1=yes 0=no) %global rescue 0 %{?build_rescue:%global rescue 1} # Turn off some stuff for resuce builds %if %{rescue} %global kerberos5 0 %endif Summary: The OpenSSH implementation of SSH protocol version 2. Name: openssh Version: %{ver} %if %{rescue} Release: %{rel}rescue %else Release: %{rel} %endif URL: https://www.openssh.com/portable.html Source0: https://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz Source1: http://www.jmknoble.net/software/x11-ssh-askpass/x11-ssh-askpass-%{aversion}.tar.gz Source2: sshd.service License: BSD Group: Applications/Internet BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot Obsoletes: ssh %if %{build6x} PreReq: ©scripts \u0026gt;= 5.00 %else Requires: initscripts \u0026gt;= 5.20 %endif BuildRequires: perl %if ! %{without_openssl} BuildRequires: openssl-devel \u0026gt;= 1.1.1 %endif BuildRequires: /bin/login %if ! %{build6x} BuildRequires: glibc-devel, pam %else BuildRequires: /usr/include/security/pam_appl.h %endif %if ! %{no_x11_askpass} BuildRequires: /usr/include/X11/Xlib.h # Xt development tools BuildRequires: libXt-devel # Provides xmkmf BuildRequires: imake # Rely on relatively recent gtk BuildRequires: gtk2-devel %endif %if ! %{no_gnome_askpass} BuildRequires: pkgconfig %endif %if %{kerberos5} BuildRequires: krb5-devel BuildRequires: krb5-libs %endif %package clients Summary: OpenSSH clients. Requires: openssh = %{version}-%{release} Group: Applications/Internet Obsoletes: ssh-clients %package server Summary: The OpenSSH server daemon. Group: System Environment/Daemons Obsoletes: ssh-server Requires: openssh = %{version}-%{release}, chkconfig \u0026gt;= 0.9 %if ! %{build6x} Requires: /etc/pam.d/system-auth %endif %package askpass Summary: A passphrase dialog for OpenSSH and X. Group: Applications/Internet Requires: openssh = %{version}-%{release} Obsoletes: ssh-extras %package askpass-gnome Summary: A passphrase dialog for OpenSSH, X, and GNOME. Group: Applications/Internet Requires: openssh = %{version}-%{release} Obsoletes: ssh-extras %description SSH (Secure SHell) is a program for logging into and executing commands on a remote machine. SSH is intended to replace rlogin and rsh, and to provide secure encrypted communications between two untrusted hosts over an insecure network. X11 connections and arbitrary TCP/IP ports can also be forwarded over the secure channel. OpenSSH is OpenBSD\u0026#39;s version of the last free version of SSH, bringing it up to date in terms of security and features, as well as removing all patented algorithms to separate libraries. This package includes the core files necessary for both the OpenSSH client and server. To make this package useful, you should also install openssh-clients, openssh-server, or both. %description clients OpenSSH is a free version of SSH (Secure SHell), a program for logging into and executing commands on a remote machine. This package includes the clients necessary to make encrypted connections to SSH servers. You\u0026#39;ll also need to install the openssh package on OpenSSH clients. %description server OpenSSH is a free version of SSH (Secure SHell), a program for logging into and executing commands on a remote machine. This package contains the secure shell daemon (sshd). The sshd daemon allows SSH clients to securely connect to your SSH server. You also need to have the openssh package installed. %description askpass OpenSSH is a free version of SSH (Secure SHell), a program for logging into and executing commands on a remote machine. This package contains an X11 passphrase dialog for OpenSSH. %description askpass-gnome OpenSSH is a free version of SSH (Secure SHell), a program for logging into and executing commands on a remote machine. This package contains an X11 passphrase dialog for OpenSSH and the GNOME GUI desktop environment. %prep %if ! %{no_x11_askpass} %setup -q -a 1 %else %setup -q %endif %build %if %{rescue} CFLAGS=\u0026#34;$RPM_OPT_FLAGS -Os\u0026#34;; export CFLAGS %endif %configure \\ --sysconfdir=%{_sysconfdir}/ssh \\ --libexecdir=%{_libexecdir}/openssh \\ --datadir=%{_datadir}/openssh \\ --with-default-path=/usr/local/bin:/bin:/usr/bin \\ --with-superuser-path=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin \\ --with-privsep-path=%{_var}/empty/sshd \\ --mandir=%{_mandir} \\ --with-mantype=man \\ --disable-strip \\ %if %{without_openssl} --without-openssl \\ %endif %if %{scard} --with-smartcard \\ %endif %if %{rescue} --without-pam \\ %else --with-pam \\ %endif %if %{kerberos5} --with-kerberos5=$K5DIR \\ %endif %if %{static_libcrypto} perl -pi -e \u0026#34;s|-lcrypto|%{_libdir}/libcrypto.a|g\u0026#34; Makefile %endif make %if ! %{no_x11_askpass} pushd x11-ssh-askpass-%{aversion} %configure --libexecdir=%{_libexecdir}/openssh xmkmf -a make popd %endif # Define a variable to toggle gnome1/gtk2 building. This is necessary # because RPM doesn\u0026#39;t handle nested %if statements. %if %{gtk2} gtk2=yes %else gtk2=no %endif %if ! %{no_gnome_askpass} pushd contrib if [ $gtk2 = yes ] ; then make gnome-ssh-askpass2 mv gnome-ssh-askpass2 gnome-ssh-askpass else make gnome-ssh-askpass1 mv gnome-ssh-askpass1 gnome-ssh-askpass fi popd %endif %install rm -rf $RPM_BUILD_ROOT mkdir -p -m755 $RPM_BUILD_ROOT%{_sysconfdir}/ssh mkdir -p -m755 $RPM_BUILD_ROOT%{_libexecdir}/openssh mkdir -p -m755 $RPM_BUILD_ROOT%{_var}/empty/sshd make install DESTDIR=$RPM_BUILD_ROOT install -d $RPM_BUILD_ROOT/etc/pam.d/ install -d $RPM_BUILD_ROOT%{_libexecdir}/openssh install -d $RPM_BUILD_ROOT/usr/lib/systemd/system/ install -m644 contrib/redhat/sshd.pam $RPM_BUILD_ROOT/etc/pam.d/sshd install -m755 %{SOURCE2} $RPM_BUILD_ROOT/usr/lib/systemd/system/sshd.service %if ! %{no_x11_askpass} install x11-ssh-askpass-%{aversion}/x11-ssh-askpass $RPM_BUILD_ROOT%{_libexecdir}/openssh/x11-ssh-askpass ln -s x11-ssh-askpass $RPM_BUILD_ROOT%{_libexecdir}/openssh/ssh-askpass %endif %if ! %{no_gnome_askpass} install contrib/gnome-ssh-askpass $RPM_BUILD_ROOT%{_libexecdir}/openssh/gnome-ssh-askpass %endif %if ! %{scard} rm -f $RPM_BUILD_ROOT/usr/share/openssh/Ssh.bin %endif %if ! %{no_gnome_askpass} install -m 755 -d $RPM_BUILD_ROOT%{_sysconfdir}/profile.d/ install -m 755 contrib/redhat/gnome-ssh-askpass.csh $RPM_BUILD_ROOT%{_sysconfdir}/profile.d/ install -m 755 contrib/redhat/gnome-ssh-askpass.sh $RPM_BUILD_ROOT%{_sysconfdir}/profile.d/ %endif perl -pi -e \u0026#34;s|$RPM_BUILD_ROOT||g\u0026#34; $RPM_BUILD_ROOT%{_mandir}/man*/* %clean rm -rf $RPM_BUILD_ROOT %triggerun server -- ssh-server if [ \u0026#34;$1\u0026#34; != 0 -a -r /var/run/sshd.pid ] ; then touch /var/run/sshd.restart fi %triggerun server -- openssh-server \u0026lt; 2.5.0p1 # Count the number of HostKey and HostDsaKey statements we have. gawk \u0026#39;BEGIN {IGNORECASE=1} /^hostkey/ || /^hostdsakey/ {sawhostkey = sawhostkey + 1} END {exit sawhostkey}\u0026#39; /etc/ssh/sshd_config # And if we only found one, we know the client was relying on the old default # behavior, which loaded the the SSH2 DSA host key when HostDsaKey wasn\u0026#39;t # specified. Now that HostKey is used for both SSH1 and SSH2 keys, specifying # one nullifies the default, which would have loaded both. if [ $? -eq 1 ] ; then echo HostKey /etc/ssh/ssh_host_rsa_key \u0026gt;\u0026gt; /etc/ssh/sshd_config echo HostKey /etc/ssh/ssh_host_dsa_key \u0026gt;\u0026gt; /etc/ssh/sshd_config fi %triggerpostun server -- ssh-server if [ \u0026#34;$1\u0026#34; != 0 ] ; then /sbin/chkconfig --add sshd if test -f /var/run/sshd.restart ; then rm -f /var/run/sshd.restart /sbin/service sshd start \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 || : fi fi %pre server %{_sbindir}/groupadd -r -g %{sshd_gid} sshd 2\u0026gt;/dev/null || : %{_sbindir}/useradd -d /var/empty/sshd -s /bin/false -u %{sshd_uid} \\ -g sshd -M -r sshd 2\u0026gt;/dev/null || : %post server /sbin/chkconfig --add sshd %postun server /sbin/service sshd condrestart \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 || : %preun server if [ \u0026#34;$1\u0026#34; = 0 ] then /sbin/service sshd stop \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 || : /sbin/chkconfig --del sshd fi %files %defattr(-,root,root) %doc CREDITS ChangeLog INSTALL LICENCE OVERVIEW README* PROTOCOL* TODO %attr(0755,root,root) %{_bindir}/scp %attr(0644,root,root) %{_mandir}/man1/scp.1* %attr(0755,root,root) %dir %{_sysconfdir}/ssh %attr(0600,root,root) %config(noreplace) %{_sysconfdir}/ssh/moduli %if ! %{rescue} %attr(0755,root,root) %{_bindir}/ssh-keygen %attr(0644,root,root) %{_mandir}/man1/ssh-keygen.1* %attr(0755,root,root) %dir %{_libexecdir}/openssh %attr(4711,root,root) %{_libexecdir}/openssh/ssh-keysign %attr(0755,root,root) %{_libexecdir}/openssh/ssh-pkcs11-helper %attr(0755,root,root) %{_libexecdir}/openssh/ssh-sk-helper %attr(0644,root,root) %{_mandir}/man8/ssh-keysign.8* %attr(0644,root,root) %{_mandir}/man8/ssh-pkcs11-helper.8* %attr(0644,root,root) %{_mandir}/man8/ssh-sk-helper.8* %endif %if %{scard} %attr(0755,root,root) %dir %{_datadir}/openssh %attr(0644,root,root) %{_datadir}/openssh/Ssh.bin %endif %files clients %defattr(-,root,root) %attr(0755,root,root) %{_bindir}/ssh %attr(0644,root,root) %{_mandir}/man1/ssh.1* %attr(0644,root,root) %{_mandir}/man5/ssh_config.5* %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ssh/ssh_config %if ! %{rescue} %attr(2755,root,nobody) %{_bindir}/ssh-agent %attr(0755,root,root) %{_bindir}/ssh-add %attr(0755,root,root) %{_bindir}/ssh-keyscan %attr(0755,root,root) %{_bindir}/sftp %attr(0644,root,root) %{_mandir}/man1/ssh-agent.1* %attr(0644,root,root) %{_mandir}/man1/ssh-add.1* %attr(0644,root,root) %{_mandir}/man1/ssh-keyscan.1* %attr(0644,root,root) %{_mandir}/man1/sftp.1* %endif %if ! %{rescue} %files server %defattr(-,root,root) %dir %attr(0111,root,root) %{_var}/empty/sshd %attr(0755,root,root) %{_sbindir}/sshd %attr(0755,root,root) %{_libexecdir}/openssh/sshd-session %attr(0755,root,root) %{_libexecdir}/openssh/sftp-server %attr(0644,root,root) %{_mandir}/man8/sshd.8* %attr(0644,root,root) %{_mandir}/man5/moduli.5* %attr(0644,root,root) %{_mandir}/man5/sshd_config.5* %attr(0644,root,root) %{_mandir}/man8/sftp-server.8* %attr(0755,root,root) %dir %{_sysconfdir}/ssh %attr(0600,root,root) %config(noreplace) %{_sysconfdir}/ssh/sshd_config %attr(0600,root,root) %config(noreplace) /etc/pam.d/sshd %attr(0755,root,root) %config /usr/lib/systemd/system/sshd.service %endif %if ! %{no_x11_askpass} %files askpass %defattr(-,root,root) %doc x11-ssh-askpass-%{aversion}/README %doc x11-ssh-askpass-%{aversion}/ChangeLog %doc x11-ssh-askpass-%{aversion}/SshAskpass*.ad %{_libexecdir}/openssh/ssh-askpass %attr(0755,root,root) %{_libexecdir}/openssh/x11-ssh-askpass %endif %if ! %{no_gnome_askpass} %files askpass-gnome %defattr(-,root,root) %attr(0755,root,root) %config %{_sysconfdir}/profile.d/gnome-ssh-askpass.* %attr(0755,root,root) %{_libexecdir}/openssh/gnome-ssh-askpass %endif %changelog * Mon Jun 02 2025 Cylon \u0026lt;cylonchau@outlook.com\u0026gt; - Pactched OpenSSH CVE-2024-6387 (Only redhat 9 series) - Add systemd unit file: sshd.service * Mon Oct 16 2023 Fabio Pedretti \u0026lt;pedretti.fabio@gmail.com\u0026gt; - Remove reference of dropped sshd.pam.old file - Update openssl-devel dependency to require \u0026gt;= 1.1.1 - Build with --without-openssl elsewhere * Thu Oct 28 2021 Damien Miller \u0026lt;djm@mindrot.org\u0026gt; - Remove remaining traces of --with-md5-passwords * Mon Jul 20 2020 Damien Miller \u0026lt;djm@mindrot.org\u0026gt; - Add ssh-sk-helper and corresponding manual page. * Sat Feb 10 2018 Darren Tucker \u0026lt;dtucker@dtucker.net\u0026gt; - Update openssl-devel dependency to match current requirements. - Handle Fedora \u0026gt;=6 openssl 1.0 compat libs. - Remove SSH1 from description. - Don\u0026#39;t strip binaries at build time so that debuginfo package can be created. * Sun Nov 16 2014 Nico Kadel-Garcia \u0026lt;nakdel@gmail.com\u0026gt; - Add \u0026#39;--mandir\u0026#39; and \u0026#39;--with-mantype\u0026#39; for RHEL 5 compatibility - Add \u0026#39;dist\u0026#39; option to \u0026#39;ver\u0026#39; so package names reflect OS at build time - Always include x11-ssh-askpass tarball in SRPM - Add openssh-x11-aspass BuildRequires for libXT-devel, imake, gtk2-devel - Discard \u0026#39;K5DIR\u0026#39; reporting, not usable inside \u0026#39;mock\u0026#39; for RHEL 5 compatibility - Discard obsolete \u0026#39;--with-rsh\u0026#39; configure option - Update openssl-devel dependency to 0.9.8f, as found in autoconf * Wed Jul 14 2010 Tim Rice \u0026lt;tim@multitalents.net\u0026gt; - test for skip_x11_askpass (line 77) should have been for no_x11_askpass * Mon Jun 2 2003 Damien Miller \u0026lt;djm@mindrot.org\u0026gt; - Remove noip6 option. This may be controlled at run-time in client config file using new AddressFamily directive * Mon May 12 2003 Damien Miller \u0026lt;djm@mindrot.org\u0026gt; - Don\u0026#39;t install profile.d scripts when not building with GNOME/GTK askpass (patch from bet@rahul.net) * Tue Oct 01 2002 Damien Miller \u0026lt;djm@mindrot.org\u0026gt; - Install ssh-agent setgid nobody to prevent ptrace() key theft attacks * Mon Sep 30 2002 Damien Miller \u0026lt;djm@mindrot.org\u0026gt; - Use contrib/ Makefile for building askpass programs * Fri Jun 21 2002 Damien Miller \u0026lt;djm@mindrot.org\u0026gt; - Merge in spec changes from seba@iq.pl (Sebastian Pachuta) - Add new {ssh,sshd}_config.5 manpages - Add new ssh-keysign program and remove setuid from ssh client * Fri May 10 2002 Damien Miller \u0026lt;djm@mindrot.org\u0026gt; - Merge in spec changes from RedHat, reorgansie a little - Add Privsep user, group and directory * Thu Mar 7 2002 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; 3.1p1-2 - bump and grind (through the build system) * Thu Mar 7 2002 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; 3.1p1-1 - require sharutils for building (mindrot #137) - require db1-devel only when building for 6.x (#55105), which probably won\u0026#39;t work anyway (3.1 requires OpenSSL 0.9.6 to build), but what the heck - require pam-devel by file (not by package name) again - add Markus\u0026#39;s patch to compile with OpenSSL 0.9.5a (from http://bugzilla.mindrot.org/show_bug.cgi?id=141) and apply it if we\u0026#39;re building for 6.x * Thu Mar 7 2002 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; 3.1p1-0 - update to 3.1p1 * Tue Mar 5 2002 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; SNAP-20020305 - update to SNAP-20020305 - drop debug patch, fixed upstream * Wed Feb 20 2002 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; SNAP-20020220 - update to SNAP-20020220 for testing purposes (you\u0026#39;ve been warned, if there\u0026#39;s anything to be warned about, gss patches won\u0026#39;t apply, I don\u0026#39;t mind) * Wed Feb 13 2002 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; 3.0.2p1-3 - add patches from Simon Wilkinson and Nicolas Williams for GSSAPI key exchange, authentication, and named key support * Wed Jan 23 2002 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; 3.0.2p1-2 - remove dependency on db1-devel, which has just been swallowed up whole by gnome-libs-devel * Sat Dec 29 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - adjust build dependencies so that build6x actually works right (fix from Hugo van der Kooij) * Tue Dec 4 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; 3.0.2p1-1 - update to 3.0.2p1 * Fri Nov 16 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; 3.0.1p1-1 - update to 3.0.1p1 * Tue Nov 13 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - update to current CVS (not for use in distribution) * Thu Nov 8 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; 3.0p1-1 - merge some of Damien Miller \u0026lt;djm@mindrot.org\u0026gt; changes from the upstream 3.0p1 spec file and init script * Wed Nov 7 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - update to 3.0p1 - update to x11-ssh-askpass 1.2.4.1 - change build dependency on a file from pam-devel to the pam-devel package - replace primes with moduli * Thu Sep 27 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; 2.9p2-9 - incorporate fix from Markus Friedl\u0026#39;s advisory for IP-based authorization bugs * Thu Sep 13 2001 Bernhard Rosenkraenzer \u0026lt;bero@redhat.com\u0026gt; 2.9p2-8 - Merge changes to rescue build from current sysadmin survival cd * Thu Sep 6 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; 2.9p2-7 - fix scp\u0026#39;s server\u0026#39;s reporting of file sizes, and build with the proper preprocessor define to get large-file capable open(), stat(), etc. (sftp has been doing this correctly all along) (#51827) - configure without --with-ipv4-default on RHL 7.x and newer (#45987,#52247) - pull cvs patch to fix support for /etc/nologin for non-PAM logins (#47298) - mark profile.d scriptlets as config files (#42337) - refer to Jason Stone\u0026#39;s mail for zsh workaround for exit-hanging quasi-bug - change a couple of log() statements to debug() statements (#50751) - pull cvs patch to add -t flag to sshd (#28611) - clear fd_sets correctly (one bit per FD, not one byte per FD) (#43221) * Mon Aug 20 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; 2.9p2-6 - add db1-devel as a BuildPrerequisite (noted by Hans Ecke) * Thu Aug 16 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - pull cvs patch to fix remote port forwarding with protocol 2 * Thu Aug 9 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - pull cvs patch to add session initialization to no-pty sessions - pull cvs patch to not cut off challengeresponse auth needlessly - refuse to do X11 forwarding if xauth isn\u0026#39;t there, handy if you enable it by default on a system that doesn\u0026#39;t have X installed (#49263) * Wed Aug 8 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - don\u0026#39;t apply patches to code we don\u0026#39;t intend to build (spotted by Matt Galgoci) * Mon Aug 6 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - pass OPTIONS correctly to initlog (#50151) * Wed Jul 25 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - switch to x11-ssh-askpass 1.2.2 * Wed Jul 11 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - rebuild in new environment * Mon Jun 25 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - disable the gssapi patch * Mon Jun 18 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - update to 2.9p2 - refresh to a new version of the gssapi patch * Thu Jun 7 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - change Copyright: BSD to License: BSD - add Markus Friedl\u0026#39;s unverified patch for the cookie file deletion problem so that we can verify it - drop patch to check if xauth is present (was folded into cookie patch) - don\u0026#39;t apply gssapi patches for the errata candidate - clear supplemental groups list at startup * Fri May 25 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - fix an error parsing the new default sshd_config - add a fix from Markus Friedl (via openssh-unix-dev) for ssh-keygen not dealing with comments right * Thu May 24 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - add in Simon Wilkinson\u0026#39;s GSSAPI patch to give it some testing in-house, to be removed before the next beta cycle because it\u0026#39;s a big departure from the upstream version * Thu May 3 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - finish marking strings in the init script for translation - modify init script to source /etc/sysconfig/sshd and pass $OPTIONS to sshd at startup (change merged from openssh.com init script, originally by Pekka Savola) - refuse to do X11 forwarding if xauth isn\u0026#39;t there, handy if you enable it by default on a system that doesn\u0026#39;t have X installed * Wed May 2 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - update to 2.9 - drop various patches that came from or went upstream or to or from CVS * Wed Apr 18 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - only require initscripts 5.00 on 6.2 (reported by Peter Bieringer) * Sun Apr 8 2001 Preston Brown \u0026lt;pbrown@redhat.com\u0026gt; - remove explicit openssl requirement, fixes builddistro issue - make initscript stop() function wait until sshd really dead to avoid races in condrestart * Mon Apr 2 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - mention that challengereponse supports PAM, so disabling password doesn\u0026#39;t limit users to pubkey and rsa auth (#34378) - bypass the daemon() function in the init script and call initlog directly, because daemon() won\u0026#39;t start a daemon it detects is already running (like open connections) - require the version of openssl we had when we were built * Fri Mar 23 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - make do_pam_setcred() smart enough to know when to establish creds and when to reinitialize them - add in a couple of other fixes from Damien for inclusion in the errata * Thu Mar 22 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - update to 2.5.2p2 - call setcred() again after initgroups, because the \u0026#34;creds\u0026#34; could actually be group memberships * Tue Mar 20 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - update to 2.5.2p1 (includes endianness fixes in the rijndael implementation) - don\u0026#39;t enable challenge-response by default until we find a way to not have too many userauth requests (we may make up to six pubkey and up to three password attempts as it is) - remove build dependency on rsh to match openssh.com\u0026#39;s packages more closely * Sat Mar 3 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - remove dependency on openssl -- would need to be too precise * Fri Mar 2 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - rebuild in new environment * Mon Feb 26 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Revert the patch to move pam_open_session. - Init script and spec file changes from Pekka Savola. (#28750) - Patch sftp to recognize \u0026#39;-o protocol\u0026#39; arguments. (#29540) * Thu Feb 22 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Chuck the closing patch. - Add a trigger to add host keys for protocol 2 to the config file, now that configuration file syntax requires us to specify it with HostKey if we specify any other HostKey values, which we do. * Tue Feb 20 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Redo patch to move pam_open_session after the server setuid()s to the user. - Rework the nopam patch to use be picked up by autoconf. * Mon Feb 19 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Update for 2.5.1p1. - Add init script mods from Pekka Savola. - Tweak the init script to match the CVS contrib script more closely. - Redo patch to ssh-add to try to adding both identity and id_dsa to also try adding id_rsa. * Fri Feb 16 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Update for 2.5.0p1. - Use $RPM_OPT_FLAGS instead of -O when building gnome-ssh-askpass - Resync with parts of Damien Miller\u0026#39;s openssh.spec from CVS, including update of x11 askpass to 1.2.0. - Only require openssl (don\u0026#39;t prereq) because we generate keys in the init script now. * Tue Feb 13 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Don\u0026#39;t open a PAM session until we\u0026#39;ve forked and become the user (#25690). - Apply Andrew Bartlett\u0026#39;s patch for letting pam_authenticate() know which host the user is attempting a login from. - Resync with parts of Damien Miller\u0026#39;s openssh.spec from CVS. - Don\u0026#39;t expose KbdInt responses in debug messages (from CVS). - Detect and handle errors in rsa_{public,private}_decrypt (from CVS). * Wed Feb 7 2001 Trond Eivind Glomsrxd \u0026lt;teg@redhat.com\u0026gt; - i18n-tweak to initscript. * Tue Jan 23 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - More gettextizing. - Close all files after going into daemon mode (needs more testing). - Extract patch from CVS to handle auth banners (in the client). - Extract patch from CVS to handle compat weirdness. * Fri Jan 19 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Finish with the gettextizing. * Thu Jan 18 2001 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Fix a bug in auth2-pam.c (#23877) - Gettextize the init script. * Wed Dec 20 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Incorporate a switch for using PAM configs for 6.x, just in case. * Tue Dec 5 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Incorporate Bero\u0026#39;s changes for a build specifically for rescue CDs. * Wed Nov 29 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Don\u0026#39;t treat pam_setcred() failure as fatal unless pam_authenticate() has succeeded, to allow public-key authentication after a failure with \u0026#34;none\u0026#34; authentication. (#21268) * Tue Nov 28 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Update to x11-askpass 1.1.1. (#21301) - Don\u0026#39;t second-guess fixpaths, which causes paths to get fixed twice. (#21290) * Mon Nov 27 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Merge multiple PAM text messages into subsequent prompts when possible when doing keyboard-interactive authentication. * Sun Nov 26 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Disable the built-in MD5 password support. We\u0026#39;re using PAM. - Take a crack at doing keyboard-interactive authentication with PAM, and enable use of it in the default client configuration so that the client will try it when the server disallows password authentication. - Build with debugging flags. Build root policies strip all binaries anyway. * Tue Nov 21 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Use DESTDIR instead of %%makeinstall. - Remove /usr/X11R6/bin from the path-fixing patch. * Mon Nov 20 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Add the primes file from the latest snapshot to the main package (#20884). - Add the dev package to the prereq list (#19984). - Remove the default path and mimic login\u0026#39;s behavior in the server itself. * Fri Nov 17 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Resync with conditional options in Damien Miller\u0026#39;s .spec file for an errata. - Change libexecdir from %%{_libexecdir}/ssh to %%{_libexecdir}/openssh. * Tue Nov 7 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Update to OpenSSH 2.3.0p1. - Update to x11-askpass 1.1.0. - Enable keyboard-interactive authentication. * Mon Oct 30 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Update to ssh-askpass-x11 1.0.3. - Change authentication related messages to be private (#19966). * Tue Oct 10 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Patch ssh-keygen to be able to list signatures for DSA public key files it generates. * Thu Oct 5 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Add BuildRequires on /usr/include/security/pam_appl.h to be sure we always build PAM authentication in. - Try setting SSH_ASKPASS if gnome-ssh-askpass is installed. - Clean out no-longer-used patches. - Patch ssh-add to try to add both identity and id_dsa, and to error only when neither exists. * Mon Oct 2 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Update x11-askpass to 1.0.2. (#17835) - Add BuildRequiress for /bin/login and /usr/bin/rsh so that configure will always find them in the right place. (#17909) - Set the default path to be the same as the one supplied by /bin/login, but add /usr/X11R6/bin. (#17909) - Try to handle obsoletion of ssh-server more cleanly. Package names are different, but init script name isn\u0026#39;t. (#17865) * Wed Sep 6 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Update to 2.2.0p1. (#17835) - Tweak the init script to allow proper restarting. (#18023) * Wed Aug 23 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Update to 20000823 snapshot. - Change subpackage requirements from %%{version} to %%{version}-%%{release} - Back out the pipe patch. * Mon Jul 17 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Update to 2.1.1p4, which includes fixes for config file parsing problems. - Move the init script back. - Add Damien\u0026#39;s quick fix for wackiness. * Wed Jul 12 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Update to 2.1.1p3, which includes fixes for X11 forwarding and strtok(). * Thu Jul 6 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Move condrestart to server postun. - Move key generation to init script. - Actually use the right patch for moving the key generation to the init script. - Clean up the init script a bit. * Wed Jul 5 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Fix X11 forwarding, from mail post by Chan Shih-Ping Richard. * Sun Jul 2 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Update to 2.1.1p2. - Use of strtok() considered harmful. * Sat Jul 1 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Get the build root out of the man pages. * Thu Jun 29 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Add and use condrestart support in the init script. - Add newer initscripts as a prereq. * Tue Jun 27 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Build in new environment (release 2) - Move -clients subpackage to Applications/Internet group * Fri Jun 9 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Update to 2.2.1p1 * Sat Jun 3 2000 Nalin Dahyabhai \u0026lt;nalin@redhat.com\u0026gt; - Patch to build with neither RSA nor RSAref. - Miscellaneous FHS-compliance tweaks. - Fix for possibly-compressed man pages. * Wed Mar 15 2000 Damien Miller \u0026lt;djm@ibs.com.au\u0026gt; - Updated for new location - Updated for new gnome-ssh-askpass build * Sun Dec 26 1999 Damien Miller \u0026lt;djm@mindrot.org\u0026gt; - Added Jim Knoble\u0026#39;s \u0026lt;jmknoble@pobox.com\u0026gt; askpass * Mon Nov 15 1999 Damien Miller \u0026lt;djm@mindrot.org\u0026gt; - Split subpackages further based on patch from jim knoble \u0026lt;jmknoble@pobox.com\u0026gt; * Sat Nov 13 1999 Damien Miller \u0026lt;djm@mindrot.org\u0026gt; - Added \u0026#39;Obsoletes\u0026#39; directives * Tue Nov 09 1999 Damien Miller \u0026lt;djm@ibs.com.au\u0026gt; - Use make install - Subpackages * Mon Nov 08 1999 Damien Miller \u0026lt;djm@ibs.com.au\u0026gt; - Added links for slogin - Fixed perms on manpages * Sat Oct 30 1999 Damien Miller \u0026lt;djm@ibs.com.au\u0026gt; - Renamed init script * Fri Oct 29 1999 Damien Miller \u0026lt;djm@ibs.com.au\u0026gt; - Back to old binary names * Thu Oct 28 1999 Damien Miller \u0026lt;djm@ibs.com.au\u0026gt; - Use autoconf - New binary names * Wed Oct 27 1999 Damien Miller \u0026lt;djm@ibs.com.au\u0026gt; - Initial RPMification, based on Jan \u0026#34;Yenya\u0026#34; Kasprzak\u0026#39;s \u0026lt;kas@fi.muni.cz\u0026gt; spec. 包构建过程需要安装下面依赖包（仅构建时需要）\nbash 1 sudo dnf install libX11-devel gtk2-devel imake krb5-devel libXt-devel perl pam-devel 由于默认仓库没有 imake，还需要先执行下面步骤安装 epel\ntext 1 2 sudo dnf install epel-release sudo dnf config-manager --set-enabled crb notes 如果某些包（如 gtk2-devel 或 imake）不可用，检查 EPEL 或 CRB 仓库是否启用。如果仍然找不到 “imake”，可能需要从源码安装 xorg-x11-util-macros 或手动下载 RPM，例如：\nbash 1 sudo dnf install https://dl.fedoraproject.org/pub/epel/8/Everything/x86_64/Packages/i/imake-1.0.8-7.el8.x86_64.rpm 如果不安装，在构建时可能出现下面报错，例如下面就是 pam-devel 没有安装的的报错\nbash 1 2 3 4 5 6 7 8 9 10 11 configure: error: PAM headers not found error: Bad exit status from /var/tmp/rpm-tmp.mjpqfL (%build) RPM build errors: line 95: It\u0026#39;s not recommended to have unversioned Obsoletes: Obsoletes: ssh line 132: It\u0026#39;s not recommended to have unversioned Obsoletes: Obsoletes: ssh-clients line 137: It\u0026#39;s not recommended to have unversioned Obsoletes: Obsoletes: ssh-server line 147: It\u0026#39;s not recommended to have unversioned Obsoletes: Obsoletes: ssh-extras line 153: It\u0026#39;s not recommended to have unversioned Obsoletes: Obsoletes: ssh-extras Bad exit status from /var/tmp/rpm-tmp.mjpqfL (%build) 再例如 libX11-devel 没有安装会遇到下面报错\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 warning: line 95: It\u0026#39;s not recommended to have unversioned Obsoletes: Obsoletes: ssh warning: line 132: It\u0026#39;s not recommended to have unversioned Obsoletes: Obsoletes: ssh-clients warning: line 137: It\u0026#39;s not recommended to have unversioned Obsoletes: Obsoletes: ssh-server warning: line 147: It\u0026#39;s not recommended to have unversioned Obsoletes: Obsoletes: ssh-extras warning: line 153: It\u0026#39;s not recommended to have unversioned Obsoletes: Obsoletes: ssh-extras setting SOURCE_DATE_EPOCH=1748822400 error: Failed build dependencies: /usr/include/X11/Xlib.h is needed by openssh-9.9p1-1.el9.x86_64 gtk2-devel is needed by openssh-9.9p1-1.el9.x86_64 imake is needed by openssh-9.9p1-1.el9.x86_64 krb5-devel is needed by openssh-9.9p1-1.el9.x86_64 libXt-devel is needed by openssh-9.9p1-1.el9.x86_64 perl is needed by openssh-9.9p1-1.el9.x86_64 构建好的包在 rpmbuild/RPMS 目录下\n更新自己打包OpenSSH-9.9p 直接更新这些包即可\ntext 1 $ dnf update openssh-9.9p1-1.el9.x86_64.rpm openssh-clients-9.9p1-1.el9.x86_64.rpm openssh-server-9.9p1-1.el9.x86_64.rpm 安装后查看各项依赖 openssl 的匀使用正常。这么安装比编译安装要好很多。\ntext 1 2 3 4 $ rpm -qa | grep openssh openssh-9.9p1-1.el9.x86_64 openssh-clients-9.9p1-1.el9.x86_64 openssh-server-9.9p1-1.el9.x86_64 更新完成后需要执行下面的步骤\nbash 1 2 3 4 5 6 # 默认权限是640，会导致 sshd 启动失败 sudo chmod 600 /etc/ssh/ssh_host_ecdsa_key # 重启 sshd sudo systemctl restart sshd # 这里有在阿里云ecs上遇到，默认的ecs-user是被锁定账户，更新后无法登录，需要提前解锁，否则无法登录 sudo passwd -u ecs-user -f notes 在 /etc/shadow 中看到自己的用户第二列有 “!” 表示为用户是锁定的，网上找到的资料是这样说明的，但是在 ecs-user 创建时，默认的就是锁定的也可以登录。\ntext 1 2 3 4 -u, --unlock This is the reverse of the -l option - it will unlock the account password by removing the ! prefix. This option is available to root only. By default passwd will refuse to create a passwordless account (it will not unlock an account that has only \u0026#34;!\u0026#34; as a password). The force option -f will override this protection. 修复完成后，记得在安全中心验证下\nReference [1] Rocky Linux 9 - CVE-2024-6387: regreSSHion\n[2] CVE-2024-6387漏洞修复教程（环境openeuler22.03 sp3 LTS）\n[3] OpenSSH Release Notes\n[4] Ubuntu EC2 server not accepting key following OpenSSH upgrade\n[5] Understanding /etc/shadow file format on Linux\n","permalink":"https://www.161616.top/upgrade-openssh-in-rocky/","summary":"下载 openssh-9.9p 源码包 下载地址\n下载之后解压看 README 和 INSTALL\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 libcrypto from either of LibreSSL or OpenSSL. Building without libcrypto is supported but severely restricts the available ciphers and algorithms. - LibreSSL (https://www.libressl.org/) 3.1.0 or greater - OpenSSL (https://www.openssl.org) 1.1.1 or greater LibreSSL/OpenSSL should be compiled as a position-independent library (i.e. -fPIC, eg by configuring OpenSSL as \u0026#34;.","title":"CVE-2024-6387漏洞修复(Rocky9)"},{"content":" 本文是CoreDNS 开发系列第1章 CoreDNS 开发系列-1：CoreDNS核心概念和安装配置说明 CoreDNS 开发系列-2：CoreDNS插件详解 CoreDNS 开发系列-3：开发自定义插件 CoreDNS 开发系列-4：高并发域名平台架构设计 CoreDNS 概述 什么是CoreDNS CoreDNS 是一个由 Go 编写的灵活、可扩展的 DNS 服务器。他非常灵活，几乎全部功能都由 plugin 来实现，CoreDNS 官网也有说 “CoreDNS is powered by plugins.” 。Plugins 能够作为 “独立” 或和 CoreDNS “共同” 来执行 “DNS Function” 在 CoreDNS 中被设计为是一个由 “软件实现的 CoreDNS Plugin API”，例如 Kubernetes Provider 可以提供在 k8s 集群里的服务发现。再例如 file 可以提供作为一个 DB 来使用。\nCoreDNS 的设计与 与 Caddy 一样也是通过 “插件” 进行扩展，并原生支持 Prometheus 也是 CoreDNS 的一大优势，这意味着我们可以将其连接到现有的 Prometheus 基础架构中进行监控、警报和仪表板管理。\nCoreDNS 核心架构 Server：负责监听 DNS 查询请求 Plugins Chain：按照配置顺序串联起来的插件序列 Handler：每个插件实现的请求处理器 Middleware：在请求处理过程中的中间件 DNS 请求在 CoreDNS 中的处理流程如下：\nServer 接收 DNS 查询请求 请求按顺序通过插件链 每个插件决定是处理请求还是传递给下一个 如果某个插件成功处理，生成响应返回 如果整个插件链都无法处理，返回 SERVFAIL 安装 CoreDNS 由于 CoreDNS 是基于 Go语言开发的，官方提供了预编译包，再不进行插件编写的情况下可以自行部署，也可以使用官方的 Docker 镜像运行。本文基于整理文档时最新版 CoreDNS (v1.12.1) 进行演示。\ncoredns 的参数很少，可以使用 \u0026ndash;help 来查看\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ ./coredns --help Usage of ./coredns: -conf string Corefile to load (default \u0026#34;Corefile\u0026#34;) -dns.port string Default port (default \u0026#34;53\u0026#34;) -p string Default port (default \u0026#34;53\u0026#34;) -pidfile string Path to write pid file -plugins List installed plugins -quiet Quiet mode (no initialization output) -version Show version 可以通过 -plugins 参数来查看内置的所有 “plugins” 的列表\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 $ ./coredns -plugins acl any auto autopath azure bind bufsize cache cancel chaos clouddns debug dns64 dnssec dnstap erratic errors etcd file forward geoip grpc header health hosts k8s_external kubernetes loadbalance local log loop metadata minimal multisocket nsid pprof prometheus ready reload rewrite root route53 secondary sign template timeouts tls trace transfer tsig view whoami on 这里和官网实例一样使用 1053 端口来展示 coredns 测试示例\nbash 1 ./coredns -dns.port=1053 note 在不指定配置文件启动时，它返回的是客户端的IP和端口 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $ dig @10.0.0.1 -p 1053 a whoami.example.org ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7 \u0026lt;\u0026lt;\u0026gt;\u0026gt; @10.0.0.1 -p 1053 a whoami.example.org ; (1 server found) ;; global options: +cmd ;; Got answer: ;; -\u0026gt;\u0026gt;HEADER\u0026lt;\u0026lt;- opcode: QUERY, status: NOERROR, id: 61873 ;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 3 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;whoami.example.org.\tIN\tA ;; ADDITIONAL SECTION: whoami.example.org.\t0\tIN\tA\t10.0.0.3 _udp.whoami.example.org. 0\tIN\tSRV\t0 0 40279 . ;; Query time: 3 msec ;; SERVER: 10.0.0.1#1053(10.0.0.1) ;; WHEN: Sat May 24 00:17:50 HKT 2025 ;; MSG SIZE rcvd: 123 coredns配置原理 CoreDNS 的配置文件的配置内容要求必须是编译在 coredns 中的插件才可以使用，添加和删除插件都很容易，但是需要重新编译 coredns。\n在使用配置文件来启动 coredns 时，首先可以通过 -conf 参数来指定配置文件。但当未指定时，会从当前目录下寻找 “Corefile” 作为配置文件。\n配置文件内容由一个或多个 Server 块 (Blocks) 组成，每一个 Server 块包含了一个或多个插件。插件也可以通过插件的 “指令” 来对插件做更多配置。\nCorefile 中插件配置的顺序并不决定插件链的执行顺序。插件执行顺序由 plugin.cfg 中的顺序决定。\n配置文件中以 “#” 作为注释\nnote CoreDNS 也支持在配置文件任何地方使用环境变量，Linux 中为 “{$ENV_VAR}”；Windows中为 “{%ENV_VAR%}” coredns 配置说明 Server Block server 块的开头可以对多个域名表示权威性（authoritative 在 DNS 系统属于中通常指，可以可以正确的解析 domain 到 IP，通过上下文来理解就是对一个 zone 拥有的权威性；也可以理解为对一个域名的最终的权威性） 多个 zone 通过 “空格” 进行分开。 Server 块是以 “{” (opening brace) 开始，以 “}” (closing brace) 结束。其中 “.” 表示根 zone，可以处理所有查询。 quote This type of DNS server holds a copy of the regional phone book that matches IP addresses with domain names. These are called authoritative DNS servers [4] text 1 2 3 . { # Plugins defined here. } 下面是一个实例\ntext 1 2 3 4 5 example.com example.org example.net { forward . 8.8.8.8 8.8.4.4 cache log } Server 部分也可以指定监听端口，默认为 “53” （标准DNS服务的端口），使用 “:” 来划分 zone 和它的监听端口，例如 1053\ntext 1 2 3 .:1053 { # Plugins defined here. } warning 需要注意的是，如果在这里指定的端口，那么使用选项 -dns.port 指定了参数没法覆盖配置文件中指定的端口。 使用同个 zone 指定已经分配的 Server 块，或者他们运行在同一个端口上，这个配置文件在启动时会抛出错误。例如下面配置所示。\ntext 1 2 3 4 5 6 7 .:1054 { } .:1054 { } 可以通过 bind 插件来让他绑定到不同的网卡上，可以实现使用不同网络接口或者IP地址的情况下使用相同的端口来提供服务。\ntext 1 2 3 4 5 6 7 8 9 .:1054 { bind lo whoami } .:1054 { bind eth0 whoami } 配置协议 截至目前 CoreDNS (v1.12.1) 支持四种不同类型的协议\nDNS：dns://，也是默认的协议，如果 scheme 未指定，则默认为 dns 协议 DNS over TLS (DoT): tls:// DNS over HTTP/2 (DoH): https:// DNS over gRPC: grpc:// 插件配置 在一个 Server 块中，可以通过 “插件名称” 来使用对应的插件，例如\ntext 1 2 3 . { chaos } 对于每个插件的配置，和 Server 块相同，以 “{” (opening brace) 开始，以 “}” (closing brace) 结束，来表示这个插件的配置项，如下所示\ntext 1 2 3 4 5 . { plugin { # Plugin Block } } 下面是四个 zone 在两个不同的端口上提供服务\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 coredns.io:5300 { file db.coredns.io } example.io:53 { log errors file db.example.io } example.net:53 { file db.example.net } .:53 { kubernetes forward . 8.8.8.8 log errors cache } 配置示例：来自 file plugin 的 Authoritative Serving 该示例采自官网的实例，通过使用内置的 file 插件实现一个 Authoritative Server，使用了 example.org. 作为示例 zone ，他的数据将被存储到文件内。\nfile 插件的域名为\ntext 1 file DBFILE [ZONES...] DBFILE 部分为 CoreDNS 读取和解析的数据库文件\nZONES 为指定授权 Zone，如果为空，则继承 Server 块的 Zone\n他的参数如下所示：\ntext 1 2 3 file DBFILE [ZONES... ] { reload DURATION } reload 表示 SOA 版本更改时执行 ZONE 重新加载的间隔。默认值 1 分钟。 0表示不扫描更改并重新加载。\n例如上面配置的扩展，example.org 的数据库由 file 模块提供，transfer 则是提供了\ntext 1 2 3 4 5 6 example.org { file example.org.db transfer { to * 10.240.1.1 } } 那么 example.org.db 数据库文件内应该配置的信息如下\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 # $ORIGIN example.org. 定义了“根” zone，这里是 example.com. # “.” 表示 FQDN # 这里表示下面的记录都是围绕 “example.com.” $ORIGIN example.org. # “@”表示当前zone的根域名（因为 $ORIGIN 是 example.org.）。 # “3600” 为TTL，这条记录缓存时间为1小时（单位秒） # “IN”表示Internet记录 # “SOA” start of authority,权威起始记录， # sns.dns.icann.org.主名字服务器的FQDN # noc.dns.icann.org. zone管理员邮箱，在dns配置中，用“.”替换“@” # 2017042745 序列号（Serial)，通常用于DNS同步 ## AXFR (full zone transfer，官方称为 Authoritative Transfer) 理解全量同步 ## IXFR (Incremental transfer)，可以理解为增量同步 # “7200”表示刷新时间，从服务器每2小时向主服务器检查是否更新（单位秒） # “3600”表示重试时间，从到主无法连接时，会在1小时后重试（单位秒）。 # “1209600” 表示过期时间“从在两周内无法从主获取更新，则数据失效。 # “3600” 表示“Minimum TTL”，定义改zone中默认的 TTL @\t3600 IN\tSOA sns.dns.icann.org. noc.dns.icann.org. 2017042745 7200 3600 1209600 3600 # “NS” Name Server，查询 example.org 的信息，可以去找 a.iana-servers.net 这个DNS服务器 3600 IN NS a.iana-servers.net. 3600 IN NS b.iana-servers.net. # A记录为IPV4 # AAAA记录为IPV6 www IN A 127.0.0.1 IN AAAA ::1 note 下面的内容为不加注释的配置文件，也是 file plugin 中配置的数据库 “example.org.db” text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ORIGIN example.org. @\t3600 IN\tSOA sns.dns.icann.org. noc.dns.icann.org. ( 2017042745 ; serial 7200 ; refresh (2 hours) 3600 ; retry (1 hour) 1209600 ; expire (2 weeks) 3600 ; minimum (1 hour) ) 3600 IN NS a.iana-servers.net. 3600 IN NS b.iana-servers.net. www IN A 127.0.0.1 IN AAAA ::1 另外在 bind9 中也有对应的配置\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 example.com. 43200 IN SOA ns1.example.com. other.example.com. ( 2011090302 ;Serial Number 86400 ;refresh 7200 ;retry 1814400 ;expire 86400 ;minimum ) @ SOA ns1 ( ; ns1.basiczone.com is the primary server for basiczone.com postmaster ; contact email for basiczone.com is postmaster@basiczone.com 2004041700 ; Serial ID in reverse date format 21600 ; Refresh interval for slave servers 1800 ; Retry interval for slave servers 604800 ; Expire limit for cached info on slave servers 900 ) ; Minimum Cache TTL in zone records 配置示例：transfer 和AXFR配置 transfer插件可以响应 AXFR (full zone transfer) 请求和 IXFR (incremental zone transfer) 请求，当 server 端无法通过 IXFR 更新时，会回退为 AXFR (AXFR fallback)，就是如果 IXFR 客户端没法通过 IXFR 更新数据，那么服务端会返回 “整个域” 给 IXFR 客户端 [7]\n配置语法\ntext 1 2 3 transfer [ZONE...] { to ADDRESS... } ZONE 部分表示相应 zone transfer 请求的域，如果左边为空，这些 zone 将继承于 “server 块”\nto ADDRESS\u0026hellip; 部分表示允许 trasfer 的地址，使用 “*” 表示允许 transfer 到所有的 “ADDRESS”，当 zone 改变时通知将会发送到所有 “ADDRESS” ；“ADDRESS” 可以是 IP，也可以是 IP:PORT，也可以指定多次。\n例如，下面配置表示 transfer 到一个 secondary DNS server (10.240.1.1)，这里可以使用一个或多个 transfer 指令，也可以使用 CIRD，在这个Zone发生任何改变时，CoreDNS将通知这些 “secondary DNS server”\ntext 1 2 3 4 5 6 7 8 9 10 transfer { to 10.240.1.1 } foo.example { file db.foo.example { transfer to 10.0.1.53 transfer to * } } 总结 本文从零带入了 CoreDNS 的安装配置和主要功能特性。CoreDNS 作为一个现代化的 DNS 服务器，以其模块化设计、插件扩展性和云原生特性，为构建高性能 DNS 平台提供了坚实基础。\n在后续文章中，将进一步探讨 CoreDNS 的插件部分，并深入研究如何开发自定义插件来扩展 CoreDNS 功能，最终实现一个完整的 DNS 域名管理平台。\nReference [1] what-is-coredns\n[2] Installation\n[3] configuration\n[4] What is the difference between authoritative and recursive DNS nameservers?\n[5] RR TYPE IANA Considerations\n[6] bind: what is the zonefile SOA RR grammar?\n[7] An IXFR Fallback to AXFR Case draft-song-dnsop-ixfr-fallback-01\n","permalink":"https://www.161616.top/coredns-serial-01-coredns-concept/","summary":"本文是CoreDNS 开发系列第1章 CoreDNS 开发系列-1：CoreDNS核心概念和安装配置说明 CoreDNS 开发系列-2：CoreDNS插件详解 CoreDNS 开发系列-3：开发自定义插件 CoreDNS 开发系列-4：高并发域名平台架构设计 CoreDNS 概述 什么是CoreDNS CoreDNS 是一个由 Go 编写的灵活、可扩展的 DNS 服务器。他非常灵活，几乎全部功能都由 plugin 来实现，CoreDNS 官网也有说 “CoreDNS is powered by plugins.” 。Plugins 能够作为 “独立” 或和 CoreDNS “共同” 来执行 “DNS Function” 在 CoreDNS 中被设计为是一个由 “软件实现的 CoreDNS Plugin API”，例如 Kubernetes Provider 可以提供在 k8s 集群里的服务发现。再例如 file 可以提供作为一个 DB 来使用。\nCoreDNS 的设计与 与 Caddy 一样也是通过 “插件” 进行扩展，并原生支持 Prometheus 也是 CoreDNS 的一大优势，这意味着我们可以将其连接到现有的 Prometheus 基础架构中进行监控、警报和仪表板管理。\nCoreDNS 核心架构 Server：负责监听 DNS 查询请求 Plugins Chain：按照配置顺序串联起来的插件序列 Handler：每个插件实现的请求处理器 Middleware：在请求处理过程中的中间件 DNS 请求在 CoreDNS 中的处理流程如下：","title":"CoreDNS开发系列-1：CoreDNS核心概念和安装配置说明"},{"content":"Amazon ECR 目前不支持镜像存储库的自动创建。当开发人员向 Amazon ECR 推送一个新进项，如果与其对应的存储库不存在，推送就会失败。Amazon ECR 相比起其他公有云，他的镜像是必须要求 dockerhub.io/test/nginx:version, 这里 nginx 才会被是为一个镜像存储库，而 GCP 的 Artifact registry 是 test (Project )才会被视为一个镜像存储库。这样对于存储仓库就不需要额外创建了。\n下文阐述 Amazon ECR创建仓库的方法\n方法1：awscli awscli 中有 create-repository 子命令，允许用户创建一个仓库\nbash 1 aws ecr create-repository --repository-name ${REPO_NAME} 可以使用脚本来检测仓库是否存在 [1]\nbash 1 2 3 4 5 6 7 8 9 output=$(aws ecr describe-repositories --repository-names ${REPO_NAME} 2\u0026gt;\u0026amp;1) if [ $? -ne 0 ]; then if echo ${output} | grep -q RepositoryNotFoundException; then aws ecr create-repository --repository-name ${REPO_NAME} else \u0026gt;\u0026amp;2 echo ${output} fi fi 或 [1]\nbash 1 2 3 4 5 6 7 #!/bin/bash aws ecr describe-repositories --repository-names $1 2\u0026gt;\u0026amp;1 \u0026gt; /dev/null status=$? if [[ ! \u0026#34;${status}\u0026#34; -eq 0 ]]; then aws ecr create-repository --repository-name $1 fi 方法2：Lambda + EventBridge Lambda 是 AWS 中的 serverless computing service, 他的名字来源于数学中的 Lambda Calculus（λ演算），在计算机科学中，他称为 lambda functions (anonymous functions)。在 AWS 中就是，程序执行不需要部署服务器，工作负载等程序，只需要提供代码就可以了。\nEventBridge 是一个使用事件连接不同应用的 serverless service，可以很容易的构建出基于事件驱动 ( event-driven ) 的应用程序。EventBridge 包含两个组件，“事件总线” 和 “管道”。“事件总线” 接收事件，并将其传送到零个或多个目标的路由器。管道是源和目标之间的点到点继承管道，用于处理和传递单个目标。[2]\n前提准备 创建 Lambda 执行角色 给 Amazon Lambda 函数创建一个执行角色（例如：ecr-repo-auto-create），让 Amazon Lambda 可以创建 Amazon ECR 映像库并将日志发送给 Amazon CloudTrail。这个执行角色至少需要拥有以下权限：\nAWSCloudTrail_FullAccess 将日志发送给 Amazon CloudTrail\nAmazonEC2ContainerRegistryFullAccess ECR所有访问权限\nAWSLambda_FullAccess Lambda的所有权限\nAmazonEventBridgeFullAccess EventBridge的所有权限\nAmazonS3FullAccess S3 的所有权限\nIAMReadOnlyAccess IAM 只读权限\n最终创建出的角色如下图所示\n图：cr-repo-auto-create所需权限\r创建 Amazon Lambda 步骤1：在 Amazon Lambda 服务中选择创建函数\n图：创建Amazon Lambda步骤1\r步骤2：输入函数的名字（这里为：ecr-repo-auto-create），并选择 Python 3.13 作为运行环境\n图：创建Amazon Lambda步骤2\r步骤3：点击权限下方的“更改默认执行角色”，选择“使用现有角色”，使用在前提条件当中创建 Amazon Lambda 的执行角色（这里选择上面创建的 ecr-repo-auto-create 用户）\n图：创建Amazon Lambda步骤3\r步骤4：点击“创建函数”\n图：创建Amazon Lambda步骤4\r步骤5：在 “代码” 部分创建文件 lambda_function.py，并将下面代码复制进入\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import boto3 import json def lambda_handler(event, context): ecr_client = boto3.client(\u0026#39;ecr\u0026#39;) repo_name = event[\u0026#39;detail\u0026#39;][\u0026#39;requestParameters\u0026#39;][\u0026#39;repositoryName\u0026#39;] try: # 创建 ECR 仓库 ecr_client.create_repository(repositoryName=repo_name) print(f\u0026#34;Repository {repo_name} created successfully.\u0026#34;) # 定义生命周期策略：保留最新的 10 个镜像版本，删除较旧的 lifecycle_policy = { \u0026#34;rules\u0026#34;: [ { \u0026#34;rulePriority\u0026#34;: 1, \u0026#34;description\u0026#34;: \u0026#34;Keep only the last 10 images\u0026#34;, \u0026#34;selection\u0026#34;: { # 适用于所有镜像（无论是否有标签） \u0026#34;tagStatus\u0026#34;: \u0026#34;any\u0026#34;, \u0026#34;countType\u0026#34;: \u0026#34;imageCountMoreThan\u0026#34;, # 超过 10 个镜像时触发清理 \u0026#34;countNumber\u0026#34;: 10 }, \u0026#34;action\u0026#34;: { # 删除超出的镜像 \u0026#34;type\u0026#34;: \u0026#34;expire\u0026#34; } } ] } # 将生命周期策略应用到仓库 ecr_client.put_lifecycle_policy( repositoryName=repo_name, lifecyclePolicyText=json.dumps(lifecycle_policy) ) print(f\u0026#34;Lifecycle policy applied to {repo_name}: keep last 10 images.\u0026#34;) except Exception as e: print(f\u0026#34;Error: {str(e)}\u0026#34;) return { \u0026#39;statusCode\u0026#39;: 500, \u0026#39;body\u0026#39;: json.dumps(f\u0026#34;Failed to process repository {repo_name}: {str(e)}\u0026#34;) } return { \u0026#39;statusCode\u0026#39;: 200, \u0026#39;body\u0026#39;: json.dumps(\u0026#39;Repository creation and lifecycle policy applied successfully\u0026#39;) } 步骤6：在 “配置”部分，选择 “环境变量”，添加 IMAGE_TAG_MUTABILITY，REPO_SCAN_ON_PUSH，REPO_TAGS 三个环境变量，并根据实际需求给环境变量赋值。\n环境变量 值 IMAGE_TAG_MUTABILITY MUTABLE REPO_SCAN_ON_PUSH false REPO_TAGS` 图：创建Amazon Lambda步骤6\r创建 Amazon EventBridge 事件 步骤1：在 Amazon EventBridge 服务中选择“创建规则”\n图：创建 Amazon EventBridge 事件步骤1\r步骤2：填写规则的名字（这里使用：ecr-repo-auto-create），并点击“下一步”\n图：创建 Amazon EventBridge 事件步骤2\r图：创建 Amazon EventBridge 事件步骤3\r步骤3：选择“编辑模式”，将下面内容复制粘贴到空白处。选择下一步\njson 1 2 3 4 5 6 7 8 9 10 11 { \u0026#34;source\u0026#34;: [\u0026#34;aws.ecr\u0026#34;], \u0026#34;detail-type\u0026#34;: [\u0026#34;AWS API Call via CloudTrail\u0026#34;], \u0026#34;detail\u0026#34;: { // 修改你的区域 \u0026#34;awsRegion\u0026#34;: [\u0026#34;ap-southeast-1\u0026#34;], \u0026#34;eventSource\u0026#34;: [\u0026#34;ecr.amazonaws.com\u0026#34;], \u0026#34;eventName\u0026#34;: [\u0026#34;InitiateLayerUpload\u0026#34;], \u0026#34;errorCode\u0026#34;: [\u0026#34;RepositoryNotFoundException\u0026#34;] } } 步骤4：将目标设置为“创建 Amazon Lambda” 中创建的 Amazon Lambda 函数 (ecr-repo-auto-create)\n图：创建 Amazon EventBridge 事件步骤4\r最后可以在 “规则” 中的监控查看函数自动执行的情况，或者报错。\n图：创建 Amazon EventBridge 事件步骤5\r下图展示了 Amazon EventBridge 执行的一些日志\n图：Amazon EventBridge 事件日志1\r上图展示了仓库已存在的日志报错。\n图：Amazon EventBridge 事件日志2\r上图展示了代码错误的日志\n图：Amazon EventBridge 事件日志3\r上图展示了成功创建仓库的日志。\n通过这些日志可以很好的去对你的脚本进行错误排查\nReference [1] Create AWS ECR repository if it doesn\u0026rsquo;t exist\n[2] 什么是亚马逊 EventBridge？\n[3] 实现 Amazon ECR 映像存储库的自动创建\n[4] create-repository\n[5] Dynamically create repositories upon image push to Amazon ECR\n","permalink":"https://www.161616.top/aws-ecr-auto-create-repository/","summary":"Amazon ECR 目前不支持镜像存储库的自动创建。当开发人员向 Amazon ECR 推送一个新进项，如果与其对应的存储库不存在，推送就会失败。Amazon ECR 相比起其他公有云，他的镜像是必须要求 dockerhub.io/test/nginx:version, 这里 nginx 才会被是为一个镜像存储库，而 GCP 的 Artifact registry 是 test (Project )才会被视为一个镜像存储库。这样对于存储仓库就不需要额外创建了。\n下文阐述 Amazon ECR创建仓库的方法\n方法1：awscli awscli 中有 create-repository 子命令，允许用户创建一个仓库\nbash 1 aws ecr create-repository --repository-name ${REPO_NAME} 可以使用脚本来检测仓库是否存在 [1]\nbash 1 2 3 4 5 6 7 8 9 output=$(aws ecr describe-repositories --repository-names ${REPO_NAME} 2\u0026gt;\u0026amp;1) if [ $? -ne 0 ]; then if echo ${output} | grep -q RepositoryNotFoundException; then aws ecr create-repository --repository-name ${REPO_NAME} else \u0026gt;\u0026amp;2 echo ${output} fi fi 或 [1]","title":"Amazon ECR自动创建不存在的存储库"},{"content":"创建 Dockerfile, 因为需要 aws 命令，就不特别去安装了，直接使用 awslinux 的镜像\ndocker 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 FROM amazonlinux:2 RUN yum update -y \u0026amp;\u0026amp; \\ yum install -y awscli jq \u0026amp;\u0026amp; \\ yum clean all WORKDIR /app COPY refresh-ecr-credentials.sh ENV AWS_REGION=${AWS_REGION:-us-east-1} ENV REFRESH_INTERVAL=${REFRESH_INTERVAL:-3600} ENV CREDENTIALS_DIR=${CREDENTIALS_DIR:-/shared_credentials} ENV AWS_CREDENTIALS_FILE=${AWS_CREDENTIALS_FILE:-/opt/password} RUN mkdir -p ${CREDENTIALS_DIR} ~/.aws /opt \u0026amp;\u0026amp; \\ chmod 700 ${CREDENTIALS_DIR} ~/.aws /opt \u0026amp;\u0026amp; \\ chmod +x /app/refresh-ecr-credentials.sh CMD [\u0026#34;/app/refresh-ecr-credentials.sh\u0026#34;] VOLUME [\u0026#34;${CREDENTIALS_DIR}\u0026#34;] 准备 refresh-ecr-credentials.sh\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #!/bin/bash log() { echo \u0026#34;[$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;)] $1\u0026#34; \u0026gt;\u0026amp;2 } # 循环刷新 ECR 凭证并输出到 AWS_CREDENTIALS_FILE while true; do log \u0026#34;刷新ECR凭证...\u0026#34; if aws ecr get-login-password --region ${AWS_REGION} \u0026gt; ${AWS_CREDENTIALS_FILE} 2\u0026gt;/dev/null; then chmod 600 \u0026#34;${AWS_CREDENTIALS_FILE}\u0026#34; log \u0026#34;ECR密钥已生工刷新\u0026#34; else log \u0026#34;刷新ECR密钥失败\u0026#34; fi done ","permalink":"https://www.161616.top/aws-ecr-auto-refresh-sidecar/","summary":"创建 Dockerfile, 因为需要 aws 命令，就不特别去安装了，直接使用 awslinux 的镜像\ndocker 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 FROM amazonlinux:2 RUN yum update -y \u0026amp;\u0026amp; \\ yum install -y awscli jq \u0026amp;\u0026amp; \\ yum clean all WORKDIR /app COPY refresh-ecr-credentials.sh ENV AWS_REGION=${AWS_REGION:-us-east-1} ENV REFRESH_INTERVAL=${REFRESH_INTERVAL:-3600} ENV CREDENTIALS_DIR=${CREDENTIALS_DIR:-/shared_credentials} ENV AWS_CREDENTIALS_FILE=${AWS_CREDENTIALS_FILE:-/opt/password} RUN mkdir -p ${CREDENTIALS_DIR} ~/.aws /opt \u0026amp;\u0026amp; \\ chmod 700 ${CREDENTIALS_DIR} ~/.aws /opt \u0026amp;\u0026amp; \\ chmod +x /app/refresh-ecr-credentials.","title":"创建一个aws ecr自动刷新密钥的sidecar"},{"content":"原理 通常 Pod 体面终止的过程为：kubelet 先发送一个带有体面超时限期的 TERM（又名 SIGTERM，根据参数terminationGracePeriodSeconds来决定） 信号到每个容器中的主进程，将请求发送到容器运行时来尝试停止 Pod 中的容器。 停止容器的这些请求由容器运行时以异步方式处理。 这些请求的处理顺序无法被保证。许多容器运行时遵循容器镜像内定义的 STOPSIGNAL 值， 如果不同，则发送容器镜像中配置的 STOPSIGNAL，而不是 TERM 信号。 一旦超出了体面终止限期，容器运行时会向所有剩余进程发送 KILL 信号，之后 Pod 就会被从 API 服务器上移除。 如果 kubelet 或者容器运行时的管理服务在等待进程终止期间被重启， 集群会从头开始重试，赋予 Pod 完整的体面终止限期。\n通常终止流程按照下面约束进行：\n如果 Pod 中的容器之一定义了 preStop 回调 且 Pod 规约中的 terminationGracePeriodSeconds 未设为 0， kubelet 开始在容器内运行该回调逻辑。默认的 terminationGracePeriodSeconds 设置为 30 秒.\n如果 Pod 未定义 preStop 回调，根据默认的 terminationGracePeriodSeconds 设置为 30 秒。进行 kill -9（无论terminationGracePeriodSeconds 有没有配置）\n如果 preStop 回调在体面期结束后仍在运行，kubelet 将请求短暂的、一次性的体面期延长 2 秒。即 30 + 2 s 后删除Pod。\n如果 preStop 回调配置的值大于 \u0026gt; terminationGracePeriodSeconds ， 仍按照 terminationGracePeriodSeconds 去执行。\nkubelet 向每个容器的 pid = 1的进程发送 SIGTERM。\n发送后 Pod 被设置为 Terminating，并关闭Pod流量调度（service 是根据 Running Pod进行调度）。\n待Pod自动完成，或者 到达 terminationGracePeriodSeconds + 2 时，将强制退出。\n演示 使用的 Dockerfile\ndockerfile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 FROM golang:1.22.12-bookworm AS builder MAINTAINER seiya WORKDIR /app COPY ./main.go /app COPY 1.sh /app/ ENV GOPROXY https://goproxy.cn,direct RUN \\ apt update \u0026amp;\u0026amp; apt-get install -y libx11-dev dumb-init RUN \\ #sed -i \u0026#39;s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g\u0026#39; /etc/apk/repositories \u0026amp;\u0026amp; \\ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags \u0026#34;-s -w\u0026#34; -o signal main.go \u0026amp;\u0026amp; \\ chmod +x signal #FROM gcr.io/distroless/base-debian12 AS runner FROM amd64/debian:12-slim AS runner WORKDIR /app #COPY --from=builder /usr/bin/dumb-init /usr/bin/dumb-init COPY --from=builder /app/1.sh /app/ COPY --from=builder /app/signal /usr/sbin/ RUN \\ apt update \u0026amp;\u0026amp; apt-get install -y dumb-init busybox VOLUME [\u0026#34;/app\u0026#34; ] ENTRYPOINT [\u0026#34;dumb-init\u0026#34;,\u0026#34;--single-child\u0026#34;, \u0026#34;--\u0026#34;] CMD [\u0026#34;/app/1.sh\u0026#34;] 使用的部署清单\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 apiVersion: apps/v1 kind: Deployment metadata: labels: app: test name: test-signal namespace: debug spec: selector: matchLabels: app: test-signal template: metadata: labels: app: test-signal spec: containers: - image: cylonchau/signal-test:6.2 imagePullPolicy: Always name: test-signal lifecycle: preStop: exec: command: - /home/busybox - sleep - \u0026#39;40\u0026#39; restartPolicy: Always 使用的启动脚本\nbash 1 2 3 4 5 6 7 8 9 10 11 #!/usr/bin/busybox sh # 这是捕获到 kill -15 时，向 child_pid 发送 kill -15，然后等待 child_pid 进程退出 trap \u0026#39;kill -TERM $child_pid; wait $child_pid\u0026#39; TERM # 启动真正的进程 /usr/sbin/signal \u0026amp; child_pid=$! # 等待子进程退出 wait $child_pid 当发起redeploy，现象立马重启，dumb-init吧信号传递给脚本，脚本没有把信号传递给子进程，父进程退出后容器就退出了。\n会被立即杀死\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 process 5 : 16 process 2 : 14 process 4 : 16 process 1 : 13 process 6 : 18 process 3 : 15 process 8 : 20 process 7 : 19 process 9 : 21 process 5 : 17 process 0 : 12 kill -15 进程退出 等待进程完成 正常不适用脚本的推出追踪\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 process 1 : 1 2025-02-11T03:09:39.724905088Z process 9 : 9 2025-02-11T03:09:39.724909372Z process 2 : 2 2025-02-11T03:09:39.724913167Z waiting signal... 2025-02-11T03:09:39.724916314Z process 6 : 6 2025-02-11T03:09:39.724920035Z process 7 : 7 process 0 : 0 ... 2025-02-11T03:09:56.736808435Z process 1 : 18 2025-02-11T03:09:56.736822881Z process 5 : 22 2025-02-11T03:09:56.736826547Z process 2 : 19 kill -15 进程退出 等待进程完成 process 5 : 23 2025-02-11T03:09:57.737872349Z process 3 : 21 2025-02-11T03:09:57.737880369Z process 0 : 18 2025-02-11T03:09:57.737885600Z process 9 : 27 process 8 : 26 2025-02-11T03:09:57.737896163Z process 7 : 25 2025-02-11T03:09:57.737901236Z process 4 : 22 2025-02-11T03:09:57.737907145Z process 6 : 24 2025-02-11T03:09:57.737912198Z process 1 : 19 2025-02-11T03:09:57.737916878Z process 2 : 20 process 2 : 21 2025-02-11T03:09:58.738805260Z process 1 : 20 2025-02-11T03:09:58.738812505Z process 5 : 24 2025-02-11T03:09:58.738824443Z process 3 : 22 2025-02-11T03:09:58.738829906Z process 0 : 19 2025-02-11T03:09:58.738834665Z process 9 : 28 process 8 : 27 2025-02-11T03:09:58.738845432Z process 7 : 26 2025-02-11T03:09:58.738850652Z process 4 : 23 2025-02-11T03:09:58.738872801Z process 6 : 25 process 4 : 24 2025-02-11T03:09:59.739946678Z process 1 : 21 2025-02-11T03:09:59.739954313Z process 5 : 25 2025-02-11T03:09:59.739959821Z process 3 : 23 process 0 : 20 2025-02-11T03:09:59.739971261Z process 9 : 29 2025-02-11T03:09:59.739977126Z process 8 : 28 2025-02-11T03:09:59.739982936Z process 7 : 27 2025-02-11T03:09:59.739988609Z process 6 : 26 2025-02-11T03:09:59.739994269Z process 2 : 22 process 7 : 28 process 4 : 25 2025-02-11T03:10:00.740901309Z process 1 : 22 2025-02-11T03:10:00.740904915Z process 5 : 26 2025-02-11T03:10:00.740908607Z process 3 : 24 2025-02-11T03:10:00.740912412Z process 0 : 21 ... 如果命令是在脚本中使用，需要增加 trap\nbash 1 2 3 4 5 6 7 8 9 10 11 #!/usr/bin/busybox sh # 这是捕获到 kill -15 时，向 child_pid 发送 kill -15，然后等待 child_pid 进程退出 trap \u0026#39;kill -TERM $child_pid; wait $child_pid\u0026#39; TERM # 启动真正的进程 /usr/sbin/signal \u0026amp; child_pid=$! # 等待子进程退出 wait $child_pid 然后测试\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 2025-02-11T09:02:23.499272204Z process 5 : 444 kill -15 进程退出 等待进程完成 process 2 : 442 2025-02-11T09:02:24.500288953Z process 6 : 446 2025-02-11T09:02:24.500296520Z process 9 : 449 2025-02-11T09:02:24.500303369Z process 1 : 441 2025-02-11T09:02:24.500309809Z process 0 : 440 2025-02-11T09:02:24.500316118Z process 8 : 448 process 4 : 444 2025-02-11T09:02:24.500329508Z process 5 : 445 2025-02-11T09:02:24.500336311Z process 3 : 443 2025-02-11T09:02:24.500342658Z process 7 : 447 process 5 : 446 2025-02-11T09:02:25.501432488Z process 3 : 444 process 4 : 445 2025-02-11T09:02:25.501462805Z process 7 : 448 2025-02-11T09:02:25.501469120Z process 1 : 442 2025-02-11T09:02:25.501474984Z process 6 : 447 2025-02-11T09:02:25.501481006Z process 9 : 450 process 0 : 441 2025-02-11T09:02:25.501493018Z process 2 : 443 2025-02-11T09:02:25.501498026Z process 8 : 449 process 9 : 451 2025-02-11T09:02:26.502288615Z process 5 : 447 2025-02-11T09:02:26.502296794Z process 3 : 445 process 4 : 446 2025-02-11T09:02:26.502309015Z process 1 : 443 2025-02-11T09:02:26.502315239Z process 6 : 448 2025-02-11T09:02:26.502321715Z process 8 : 450 2025-02-11T09:02:26.502327547Z process 2 : 444 2025-02-11T09:02:26.502333990Z process 0 : 442 2025-02-11T09:02:26.502340285Z process 7 : 449 process 2 : 445 2025-02-11T09:02:27.503276894Z process 9 : 452 2025-02-11T09:02:27.503284666Z process 5 : 448 2025-02-11T09:02:27.503290863Z process 3 : 446 process 4 : 447 2025-02-11T09:02:27.503303407Z process 1 : 444 process 6 : 449 2025-02-11T09:02:27.503315512Z process 7 : 450 2025-02-11T09:02:27.503322049Z process 8 : 451 process 0 : 443 process 6 : 450 2025-02-11T09:02:28.504232236Z process 2 : 446 process 9 : 453 2025-02-11T09:02:28.504248187Z process 5 : 449 2025-02-11T09:02:28.504255023Z process 3 : 447 process 4 : 448 2025-02-11T09:02:28.504267497Z process 1 : 445 2025-02-11T09:02:28.504274473Z process 0 : 444 process 7 : 451 2025-02-11T09:02:28.504286872Z process 8 : 452 process 1 : 446 ","permalink":"https://www.161616.top/kubernetes-terminate-processing/","summary":"原理 通常 Pod 体面终止的过程为：kubelet 先发送一个带有体面超时限期的 TERM（又名 SIGTERM，根据参数terminationGracePeriodSeconds来决定） 信号到每个容器中的主进程，将请求发送到容器运行时来尝试停止 Pod 中的容器。 停止容器的这些请求由容器运行时以异步方式处理。 这些请求的处理顺序无法被保证。许多容器运行时遵循容器镜像内定义的 STOPSIGNAL 值， 如果不同，则发送容器镜像中配置的 STOPSIGNAL，而不是 TERM 信号。 一旦超出了体面终止限期，容器运行时会向所有剩余进程发送 KILL 信号，之后 Pod 就会被从 API 服务器上移除。 如果 kubelet 或者容器运行时的管理服务在等待进程终止期间被重启， 集群会从头开始重试，赋予 Pod 完整的体面终止限期。\n通常终止流程按照下面约束进行：\n如果 Pod 中的容器之一定义了 preStop 回调 且 Pod 规约中的 terminationGracePeriodSeconds 未设为 0， kubelet 开始在容器内运行该回调逻辑。默认的 terminationGracePeriodSeconds 设置为 30 秒.\n如果 Pod 未定义 preStop 回调，根据默认的 terminationGracePeriodSeconds 设置为 30 秒。进行 kill -9（无论terminationGracePeriodSeconds 有没有配置）\n如果 preStop 回调在体面期结束后仍在运行，kubelet 将请求短暂的、一次性的体面期延长 2 秒。即 30 + 2 s 后删除Pod。","title":"Demo - Pod终止的生命周期"},{"content":"Hi, I am Cylon I am a programmer.\nAt the same time, I work full time is Kubernetes Engineer, If I were to express my occupation about this sector with three words, it would be engineering, research and practicality.\nI liked Linux, Network and Programming. In a free time, I generally study programming language and network technology 🙂\nabout site 2024 May Switch domain \u0026ldquo;oomkill.com\u0026rdquo;. You can =\u0026gt; see my projects on github/cylonchau Contact to me with email/telegram/twitter Thank you for reading this page.\n","permalink":"https://www.161616.top/about/","summary":"Hi, I am Cylon I am a programmer.\nAt the same time, I work full time is Kubernetes Engineer, If I were to express my occupation about this sector with three words, it would be engineering, research and practicality.\nI liked Linux, Network and Programming. In a free time, I generally study programming language and network technology 🙂\nabout site 2024 May Switch domain \u0026ldquo;oomkill.com\u0026rdquo;. You can =\u0026gt; see my projects on github/cylonchau Contact to me with email/telegram/twitter Thank you for reading this page.","title":"🙋🏻‍♂️关于"},{"content":"ceph版本 nautilus\n处理过程 查看 ceph 集群状态\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ceph -s cluster: id: baf87797-3ec1-4f2c-8126-bf0a44051b13 health: HEALTH_WARN 3 osds down 1 host (3 osds) down 1 pools have many more objects per pg than average Degraded data redundancy: 1167403/4841062 objects degraded (24.115%), 391 pgs degraded, 412 pgs undersized services: mon: 3 daemons, quorum 10.10.20.4,10.10.20.123,10.10.20.186 (age 9m) mgr: 10.10.20.4(active, since 9m), standbys: 10.10.20.123, 10.10.20.186 mds: kubefs:2 {0=10.10.20.123=up:active,1=10.10.20.186=up:active} 1 up:standby osd: 13 osds: 10 up (since 11m), 13 in (since 14M) rgw: 4 daemons active (10.10.20.4, 10.10.20.8, 10.10.20.123, 10.10.20.186) data: pools: 9 pools, 896 pgs objects: 2.42M objects, 1.8 TiB usage: 2.8 TiB used, 4.0 TiB / 6.8 TiB avail pgs: 1167403/4841062 objects degraded (24.115%) 484 active+clean 391 active+undersized+degraded 21 active+undersized io: client: 1.6 MiB/s rd, 303 KiB/s wr, 6 op/s rd, 22 op/s wr 看服务正常，哪些OSD有问题\nbash 1 2 3 4 5 6 7 8 # ceph health detail HEALTH_WARN 3 osds down; 1 host (3 osds) down; 1 pools have many more objects per pg than average; Degraded data redundancy: 1167404/4841068 objects degraded (24.115%), 391 pgs degraded, 412 pgs undersized OSD_DOWN 3 osds down osd.2 (root=default,host=10.10.20.4) is down osd.3 (root=default,host=10.10.20.4) is down osd.6 (root=default,host=10.10.20.4) is down OSD_HOST_DOWN 1 host (3 osds) down host 10.10.20.4 (root=default) (3 osds) is down 查看下 ceph 进程\nbash 1 2 3 4 5 6 7 8 9 $ ps -ef|grep ceph root 1260 1 0 15:21 ? 00:00:00 /usr/bin/python2.7 /usr/bin/ceph-crash ceph 1858 1 0 15:21 ? 00:00:00 /usr/bin/ceph-mds -f --cluster ceph --id 10.10.20.4 --setuser ceph --setgroup ceph ceph 1861 1 0 15:21 ? 00:00:01 /usr/bin/radosgw -f --cluster ceph --name client.rgw.10.10.20.4 --setuser ceph --setgroup ceph ceph 1872 1 0 15:21 ? 00:00:06 /usr/bin/ceph-mon -f --cluster ceph --id 10.10.20.4 --setuser ceph --setgroup ceph ceph 1917 1 6 15:21 ? 00:00:37 /usr/bin/ceph-mgr -f --cluster ceph --id 10.10.20.4 --setuser ceph --setgroup ceph root 1944 2 0 15:21 ? 00:00:00 [ceph-msgr] root 1994 2 0 15:21 ? 00:00:00 [ceph-watch-noti] root 9095 8488 0 15:31 pts/0 00:00:00 grep --color=auto ceph\t手动启动 ceph osd 进程，全部无法启动\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ systemctl status ceph-osd@2.service ● ceph-osd@2.service - Ceph object storage daemon osd.2 Loaded: loaded (/usr/lib/systemd/system/ceph-osd@.service; enabled-runtime; vendor preset: disabled) Active: failed (Result: start-limit) since Wed 2025-04-16 15:56:31 +08; 1s ago Process: 14689 ExecStart=/usr/bin/ceph-osd -f --cluster ${CLUSTER} --id %i --setuser ceph --setgroup ceph (code=exited, status=5) Process: 14684 ExecStartPre=/usr/lib/ceph/ceph-osd-prestart.sh --cluster ${CLUSTER} --id %i (code=exited, status=0/SUCCESS) Main PID: 14689 (code=exited, status=5) Apr 16 15:56:31 10.10.20.4 systemd[1]: Unit ceph-osd@2.service entered failed state. Apr 16 15:56:31 10.10.20.4 systemd[1]: ceph-osd@2.service failed. Apr 16 15:56:31 10.10.20.4 systemd[1]: ceph-osd@2.service holdoff time over, scheduling restart. Apr 16 15:56:31 10.10.20.4 systemd[1]: Stopped Ceph object storage daemon osd.2. Apr 16 15:56:31 10.10.20.4 systemd[1]: start request repeated too quickly for ceph-osd@2.service Apr 16 15:56:31 10.10.20.4 systemd[1]: Failed to start Ceph object storage daemon osd.2. Apr 16 15:56:31 10.10.20.4 systemd[1]: Unit ceph-osd@2.service entered failed state. Apr 16 15:56:31 10.10.20.4 systemd[1]: ceph-osd@2.service failed. 查看 ceph 对应 osd 的日志\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 $ tail -200 /var/log/ceph/ceph-osd.2.log cache_index_and_filter_blocks: 1 cache_index_and_filter_blocks_with_high_priority: 1 pin_l0_filter_and_index_blocks_in_cache: 1 pin_top_level_index_and_filter: 1 index_type: 0 data_block_index_type: 0 data_block_hash_table_util_ratio: 0.750000 hash_index_allow_collision: 1 checksum: 1 no_block_cache: 0 block_cache: 0x560db5e76e50 block_cache_name: BinnedLRUCache block_cache_options: capacity : 429496729 num_shard_bits : 4 strict_capacity_limit : 0 high_pri_pool_ratio: 0.000 block_cache_compressed: (nil) persistent_cache: (nil) block_size: 4096 block_size_deviation: 10 block_restart_interval: 16 index_block_restart_interval: 1 metadata_block_size: 4096 partition_filters: 0 use_delta_encoding: 1 filter_policy: rocksdb.BuiltinBloomFilter whole_key_filtering: 1 verify_compression: 0 read_amp_bytes_per_bit: 0 format_version: 2 enable_index_compression: 1 block_align: 0 -74\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.write_buffer_size: 268435456 -73\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.max_write_buffer_number: 4 -72\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.compression: NoCompression -71\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.bottommost_compression: Disabled -70\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.prefix_extractor: nullptr -69\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.memtable_insert_with_hint_prefix_extractor: nullptr -68\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.num_levels: 7 -67\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.min_write_buffer_number_to_merge: 1 -66\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.max_write_buffer_number_to_maintain: 0 -65\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.bottommost_compression_opts.window_bits: -14 -64\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.bottommost_compression_opts.level: 32767 -63\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.bottommost_compression_opts.strategy: 0 -62\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.bottommost_compression_opts.max_dict_bytes: 0 -61\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.bottommost_compression_opts.zstd_max_train_bytes: 0 -60\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.bottommost_compression_opts.enabled: false -59\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.compression_opts.window_bits: -14 -58\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.compression_opts.level: 32767 -57\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.compression_opts.strategy: 0 -56\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.compression_opts.max_dict_bytes: 0 -55\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.compression_opts.zstd_max_train_bytes: 0 -54\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.compression_opts.enabled: false -53\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.level0_file_num_compaction_trigger: 4 -52\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.level0_slowdown_writes_trigger: 20 -51\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.level0_stop_writes_trigger: 36 -50\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.target_file_size_base: 67108864 -49\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.target_file_size_multiplier: 1 -48\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.max_bytes_for_level_base: 268435456 -47\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.level_compaction_dynamic_level_bytes: 0 -46\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.max_bytes_for_level_multiplier: 10.000000 -45\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.max_bytes_for_level_multiplier_addtl[0]: 1 -44\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.max_bytes_for_level_multiplier_addtl[1]: 1 -43\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.max_bytes_for_level_multiplier_addtl[2]: 1 -42\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.max_bytes_for_level_multiplier_addtl[3]: 1 -41\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.max_bytes_for_level_multiplier_addtl[4]: 1 -40\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.max_bytes_for_level_multiplier_addtl[5]: 1 -39\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.max_bytes_for_level_multiplier_addtl[6]: 1 -38\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.max_sequential_skip_in_iterations: 8 -37\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.max_compaction_bytes: 1677721600 -36\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.arena_block_size: 33554432 -35\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.soft_pending_compaction_bytes_limit: 68719476736 -34\u0026gt; 2025-04-16 15:56:30.913 7ffbbdfbfdc0 4 rocksdb: Options.hard_pending_compaction_bytes_limit: 274877906944 -33\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.rate_limit_delay_max_milliseconds: 100 -32\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.disable_auto_compactions: 0 -31\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.compaction_style: kCompactionStyleLevel -30\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.compaction_pri: kMinOverlappingRatio -29\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.compaction_options_universal.size_ratio: 1 -28\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.compaction_options_universal.min_merge_width: 2 -27\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.compaction_options_universal.max_merge_width: 4294967295 -26\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.compaction_options_universal.max_size_amplification_percent: 200 -25\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.compaction_options_universal.compression_size_percent: -1 -24\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.compaction_options_universal.stop_style: kCompactionStopStyleTotalSize -23\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.compaction_options_fifo.max_table_files_size: 1073741824 -22\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.compaction_options_fifo.allow_compaction: 0 -21\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.table_properties_collectors: -20\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.inplace_update_support: 0 -19\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.inplace_update_num_locks: 10000 -18\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.memtable_prefix_bloom_size_ratio: 0.000000 -17\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.memtable_whole_key_filtering: 0 -16\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.memtable_huge_page_size: 0 -15\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.bloom_locality: 0 -14\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.max_successive_merges: 0 -13\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.optimize_filters_for_hits: 0 -12\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.paranoid_file_checks: 0 -11\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.force_consistency_checks: 0 -10\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.report_bg_io_stats: 0 -9\u0026gt; 2025-04-16 15:56:30.914 7ffbbdfbfdc0 4 rocksdb: Options.ttl: 0 -8\u0026gt; 2025-04-16 15:56:30.919 7ffbbdfbfdc0 4 rocksdb: [db/version_set.cc:3757] Recovered from manifest file:db/MANIFEST-002160 succeeded,manifest_file_number is 2160, next_file_number is 8221, last_sequence is 433449593, log_number is 8218,prev_log_number is 0,max_column_family is 0,min_log_number_to_keep is 0 -7\u0026gt; 2025-04-16 15:56:30.919 7ffbbdfbfdc0 4 rocksdb: [db/version_set.cc:3766] Column family [default] (ID 0), log number is 8218 -6\u0026gt; 2025-04-16 15:56:30.919 7ffbbdfbfdc0 4 rocksdb: EVENT_LOG_v1 {\u0026#34;time_micros\u0026#34;: 1744790190920696, \u0026#34;job\u0026#34;: 1, \u0026#34;event\u0026#34;: \u0026#34;recovery_started\u0026#34;, \u0026#34;log_files\u0026#34;: [8216, 8218]} -5\u0026gt; 2025-04-16 15:56:30.919 7ffbbdfbfdc0 4 rocksdb: [db/db_impl_open.cc:583] Recovering log #8216 mode 0 -4\u0026gt; 2025-04-16 15:56:31.035 7ffbbdfbfdc0 4 rocksdb: [db/db_impl_open.cc:583] Recovering log #8218 mode 0 -3\u0026gt; 2025-04-16 15:56:31.321 7ffbbdfbfdc0 -1 bdev(0x560db6af5500 /var/lib/ceph/osd/ceph-2/block) _sync_write sync_file_range error: (5) Input/output error -2\u0026gt; 2025-04-16 15:56:31.385 7ffbae5ac700 -1 bdev(0x560db6af5500 /var/lib/ceph/osd/ceph-2/block) _aio_thread got r=-5 ((5) Input/output error) -1\u0026gt; 2025-04-16 15:56:31.387 7ffbae5ac700 -1 /home/jenkins-build/build/workspace/ceph-build/ARCH/x86_64/AVAILABLE_ARCH/x86_64/AVAILABLE_DIST/centos7/DIST/centos7/MACHINE_SIZE/huge/release/14.2.4/rpm/el7/BUILD/ceph-14.2.4/src/os/bluestore/KernelDevice.cc: In function \u0026#39;void KernelDevice::_aio_thread()\u0026#39; thread 7ffbae5ac700 time 2025-04-16 15:56:31.386670 /home/jenkins-build/build/workspace/ceph-build/ARCH/x86_64/AVAILABLE_ARCH/x86_64/AVAILABLE_DIST/centos7/DIST/centos7/MACHINE_SIZE/huge/release/14.2.4/rpm/el7/BUILD/ceph-14.2.4/src/os/bluestore/KernelDevice.cc: 534: ceph_abort_msg(\u0026#34;Unexpected IO error. This may suggest a hardware issue. Please check your kernel log!\u0026#34;) ceph version 14.2.4 (75f4de193b3ea58512f204623e6c5a16e6c1e1ba) nautilus (stable) 1: (ceph::__ceph_abort(char const*, int, char const*, std::string const\u0026amp;)+0xdd) [0x560dab22909d] 2: (KernelDevice::_aio_thread()+0xca8) [0x560dab869918] 3: (KernelDevice::AioCompletionThread::entry()+0xd) [0x560dab86b07d] 4: (()+0x7e65) [0x7ffbbabf9e65] 5: (clone()+0x6d) [0x7ffbb9abd88d] 0\u0026gt; 2025-04-16 15:56:31.389 7ffbae5ac700 -1 *** Caught signal (Aborted) ** in thread 7ffbae5ac700 thread_name:bstore_aio ceph version 14.2.4 (75f4de193b3ea58512f204623e6c5a16e6c1e1ba) nautilus (stable) 1: (()+0xf5f0) [0x7ffbbac015f0] 2: (gsignal()+0x37) [0x7ffbb99f5337] 3: (abort()+0x148) [0x7ffbb99f6a28] 4: (ceph::__ceph_abort(char const*, int, char const*, std::string const\u0026amp;)+0x1a5) [0x560dab229165] 5: (KernelDevice::_aio_thread()+0xca8) [0x560dab869918] 6: (KernelDevice::AioCompletionThread::entry()+0xd) [0x560dab86b07d] 7: (()+0x7e65) [0x7ffbbabf9e65] 8: (clone()+0x6d) [0x7ffbb9abd88d] NOTE: a copy of the executable, or `objdump -rdS \u0026lt;executable\u0026gt;` is needed to interpret this. 上面日志是比较全的错误输出，在这里有看到比较关键的报错内容\nbash 1 2 -3\u0026gt; 2025-04-16 15:56:31.321 7ffbbdfbfdc0 -1 bdev(0x560db6af5500 /var/lib/ceph/osd/ceph-2/block) _sync_write sync_file_range error: (5) Input/output error -2\u0026gt; 2025-04-16 15:56:31.385 7ffbae5ac700 -1 bdev(0x560db6af5500 /var/lib/ceph/osd/ceph-2/block) _aio_thread got r=-5 ((5) Input/output error) lsblk 查看\nbash 1 2 3 4 5 6 7 8 9 10 11 12 #lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 200G 0 disk └─sda1 8:1 0 200G 0 part / sdb 8:16 0 700G 0 disk └─ceph--4834f9df--5dd2--434c--ac3b--5da26b702766-osd--block--40ce0a60--8b70--4fc9--b160--3d2f27ca51a0 253:2 0 699G 0 lvm sdc 8:32 0 700G 0 disk └─ceph--19a5530a--d904--4373--9c06--5de4a2aedb4c-osd--block--a9765670--aab3--47ed--a849--307bef9c2c13 253:0 0 699G 0 lvm sdd 8:48 0 700G 0 disk └─ceph--cfc9f372--53c3--4646--8661--0f23e873baf2-osd--block--a0cde5de--a2a4--43ae--9c7b--71e7cec2bdfd 253:1 0 699G 0 lvm sde 8:64 0 100G 0 disk sdf 使用下面命令去列出 OSD\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 sudo -u root ceph-volume lvm list\t====== osd.2 ======= [block] /dev/ceph-4834f9df-5dd2-434c-ac3b-5da26b702766/osd-block-40ce0a60-8b70-4fc9-b160-3d2f27ca51a0 block device /dev/ceph-4834f9df-5dd2-434c-ac3b-5da26b702766/osd-block-40ce0a60-8b70-4fc9-b160-3d2f27ca51a0 block uuid 5Dg5lG-CtP9-jf0H-QJGD-DXuS-cWym-b2IwCW cephx lockbox secret cluster fsid baf87797-3ec1-4f2c-8126-bf0a44051b13 cluster name ceph crush device class None encrypted 0 osd fsid 40ce0a60-8b70-4fc9-b160-3d2f27ca51a0 osd id 2 type block vdo 0 devices /dev/sdb ====== osd.3 ======= [block] /dev/ceph-cfc9f372-53c3-4646-8661-0f23e873baf2/osd-block-a0cde5de-a2a4-43ae-9c7b-71e7cec2bdfd block device /dev/ceph-cfc9f372-53c3-4646-8661-0f23e873baf2/osd-block-a0cde5de-a2a4-43ae-9c7b-71e7cec2bdfd block uuid cJ7oS3-olgL-U6PM-3WkY-wePU-4bY4-FpqYOO cephx lockbox secret cluster fsid baf87797-3ec1-4f2c-8126-bf0a44051b13 cluster name ceph crush device class None encrypted 0 osd fsid a0cde5de-a2a4-43ae-9c7b-71e7cec2bdfd osd id 3 type block vdo 0 devices /dev/sdd ====== osd.6 ======= [block] /dev/ceph-19a5530a-d904-4373-9c06-5de4a2aedb4c/osd-block-a9765670-aab3-47ed-a849-307bef9c2c13 block device /dev/ceph-19a5530a-d904-4373-9c06-5de4a2aedb4c/osd-block-a9765670-aab3-47ed-a849-307bef9c2c13 block uuid OH3Fv2-ebt0-E1qA-R347-f7G6-fdeW-28OwhK cephx lockbox secret cluster fsid baf87797-3ec1-4f2c-8126-bf0a44051b13 cluster name ceph crush device class None encrypted 0 osd fsid a9765670-aab3-47ed-a849-307bef9c2c13 osd id 6 type block vdo 0 devices /dev/sdc 尝试擦掉这个磁盘后重新添加\nbash 1 2 #wipefs -a /dev/sdb wipefs: error: /dev/sdb: probing initialization failed: Device or resource busy 发现无法去对这个设备读写\n初步断定是申请的虚拟磁盘设备故障无法连接，暂时只能删除掉这个设备重新申请新设备加入到集群内。通过下面命令下线对应故障OSD\nbash 1 2 3 4 5 6 ceph osd set noout ceph osd out osd.2 ceph -w ceph osd crush remove osd.2 ceph osd rm osd.2 ceph osd df ","permalink":"https://www.161616.top/ch10-3-troubeshooting-osd-crash-by-poweroff/","summary":"ceph版本 nautilus\n处理过程 查看 ceph 集群状态\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ceph -s cluster: id: baf87797-3ec1-4f2c-8126-bf0a44051b13 health: HEALTH_WARN 3 osds down 1 host (3 osds) down 1 pools have many more objects per pg than average Degraded data redundancy: 1167403/4841062 objects degraded (24.115%), 391 pgs degraded, 412 pgs undersized services: mon: 3 daemons, quorum 10.","title":"物理机断电导致osd故障排查记录"},{"content":"问题描述 当在使用 docker login 登录 ECR 时需要获取对应的密钥，而在 aws 中可以运行 aws ecr get-login 以获取登录的密钥。这里存在的问题是，AWS ECR 的密钥有效期是 12 小时，必须做到每 12 小时轮换一次。在配置时，就需要解决 clouddriver 可以实时使用最新密钥登录来获取镜像仓库中容器版本。那么问题就变成了：如何可以解决这个限制并允许 Spinnaker 与 AWS ECR 交互？\n问题参考 ecr-token-refresh 在实施过程中，参考了 “Using AWS ECR with Spinnaker and Kubernetes” [1] 文章，这篇文章属于官方博客。在这里提到一个方法：“使用 sidecar 来间接刷新 aws ecr 密钥，来完成 clouddriver 可以正常获取密钥。\n这里提到的一个项目 ”ecr-token-refresh“ [2] ，这是作者自己编写的一个程序，该程序会定期刷新 ecr 的身份验证令牌。\n在参考这个项目实施时，官方提示是下面配置，但和其他传统的 spinnaker 镜像仓库的配置是相同的，要让 --password-file 是动态的。\nbash 1 2 3 4 5 $ hal config provider docker-registry account add my-docker-registry \\ --address https://\u0026lt;aws-account-number\u0026gt;.dkr.ecr.\u0026lt;region\u0026gt;.aws.com\\ --repositories $REPOSITORIES \\ --username AWS \\ --password-file /opt/passwords/my-registry.pass 问题1：但最终使用了 hal 部署的 spinnaker 会动态生成配置文件名称，无法做到固定名字。\n问题2：ecr-token-refresh 项目其实没法去指定一个 aws cli 所用的 config 文件。\n根据 ecr-token-refresh 来自定义sidecar 具体的内容如下：\n具体出现的问题和上述一样，但最终使用了 hal 部署的 spinnaker 会动态生成配置文件名称，无法做到固定名字。\n问题解决 解决方法 在阅读 spinnaker 官方文档的 docker-registry [3] 部分有提到 aws ecr 应该如何设置。\n设置仓库地址 可以使用 aws ecr describe-repositories 命令，或者在 console 中检索你的账号的区域\nbash 1 2 ADDRESS=012345678910.dkr.ecr.us-east-1.amazonaws.com REGION=us-east-1 启用 provider bash 1 hal config provider docker-registry enable 配置认证 在 Halyard 中，使用参数 “\u0026ndash;password-command” 来配置使用 aws cli 来检索 ECR authentication token。在具体实施过程中，Halyard 容器内已经安装了 awscli ，所以这步骤可以忽略。\n配置 Provider 的 account bash 1 2 3 4 hal config provider docker-registry account add aws-ecr \\ --address $ADDRESS \\ --username AWS \\ --password-command \u0026#34;aws --region $REGION ecr get-authorization-token --output text --query \u0026#39;authorizationData[].authorizationToken\u0026#39; | base64 -d | sed \u0026#39;s/^AWS://\u0026#39;\u0026#34; 关于Halyard docker-registry provider 配置参数说明 这里主要使用了选项 “\u0026ndash;password-command”\nexample --password-command: Command to retrieve docker token/password, commands must be available in environment [4] 实施过程 创建一个 aws 密钥的 secret 把他挂载到对应的名称空间\nbash 1 2 3 4 5 6 7 8 9 10 11 apiVersion: v1 kind: Secret metadata: name: aws-credentials namespace: spinnaker type: Opaque stringData: aws_credentials: | [default] aws_access_key_id = {YOUR AWS ACCESS KEY} aws_secret_access_key = {YOUR AWS SECRET KEY} 容器内的文件\nbash 1 2 bash-5.0$ ls ~/.aws/ credentials 在对应 halyard 的配置文件中增加下面这部分\nconfig/default/service-settings 下所有 clouddriver 类的服务的配置文件增加下面的配置。\nclouddriver-rw.yml clouddriver.yml clouddriver-caching.yml clouddriver-ro-deck.yml clouddriver-ro.yml 并把上面创建的 secret 挂载到对应的 clouddriver pod内。\nyaml 1 2 3 4 5 kubernetes: volumes: - id: aws-credentials type: secret mountPath: /home/spinnaker/.aws 开启后的配置文件如下\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 dockerRegistry: enabled: true accounts: - name: aws requiredGroupMembership: [] permissions: {} address: https://{your-aws-id}.dkr.ecr.ap-east-1.amazonaws.com username: AWS passwordCommand: aws --region ap-east-1 ecr get-authorization-token --output text --query \u0026#39;authorizationData[].authorizationToken\u0026#39; | base64 -d | sed \u0026#39;s/^AWS://\u0026#39; email: fake.email@spinnaker.io cacheIntervalSeconds: 30 clientTimeoutMillis: 60000 cacheThreads: 1 paginateSize: 100 sortTagsByDate: false trackDigests: false insecureRegistry: false repositories: [] 上面有提到了 config/default/service-settings 文件的配置，这个是 spinnaker 中的 “自定义设置” [4] 。在这里提到的 .hal/default/service-settings 是属于 deployment profile folder。\nReference [1] Using AWS ECR with Spinnaker and Kubernetes\n[2] ecr-token-refresh\n[3] Docker Registry\n[4] hal config provider docker-registry account add\n[5] Custom Settings\n","permalink":"https://www.161616.top/spinnaker-aws-ecr-integration/","summary":"问题描述 当在使用 docker login 登录 ECR 时需要获取对应的密钥，而在 aws 中可以运行 aws ecr get-login 以获取登录的密钥。这里存在的问题是，AWS ECR 的密钥有效期是 12 小时，必须做到每 12 小时轮换一次。在配置时，就需要解决 clouddriver 可以实时使用最新密钥登录来获取镜像仓库中容器版本。那么问题就变成了：如何可以解决这个限制并允许 Spinnaker 与 AWS ECR 交互？\n问题参考 ecr-token-refresh 在实施过程中，参考了 “Using AWS ECR with Spinnaker and Kubernetes” [1] 文章，这篇文章属于官方博客。在这里提到一个方法：“使用 sidecar 来间接刷新 aws ecr 密钥，来完成 clouddriver 可以正常获取密钥。\n这里提到的一个项目 ”ecr-token-refresh“ [2] ，这是作者自己编写的一个程序，该程序会定期刷新 ecr 的身份验证令牌。\n在参考这个项目实施时，官方提示是下面配置，但和其他传统的 spinnaker 镜像仓库的配置是相同的，要让 --password-file 是动态的。\nbash 1 2 3 4 5 $ hal config provider docker-registry account add my-docker-registry \\ --address https://\u0026lt;aws-account-number\u0026gt;.","title":"spinnaker对接AWS ECR"},{"content":" 本文是关于深入理解Kubernetes网络原理系列第3章 深入理解Kubernetes Pod网络原理 - 网络名称空间 深入理解Kubernetes Pod网络原理 - Linux虚拟网络技术 深入理解Kubernetes Pod网络原理 - CNI 深入理解Kubernetes Pod网络原理 - 跟随 flannel 学习CNI原理 深入理解Kubernetes Pod网络原理 - 跟随 flannel + multus 剖析 Chained Plugins 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 1 (Shell) 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 2 (libcni) 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 1 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 2 深入理解Kubernetes Pod网络原理 - Pod网络排错思路 概述 这是我在 kubernetes 网络之旅中的中的第4部分，主要学习 CNI 规范（5要素，旧版本是4要素）。要深深记住这些，在后面旅程中常常被用到。\nNotes 本文讲解均按照 1.1.0 规范进行，但目前流行的 CNI 可能并不使用该版本，例如 2014 年维护的 k8s 1.29 等版本，安装的最新 cilium 使用的规范也是 0.3.1，这些在后续 cni 联系部分会提到。\n本文中 SPEC 是 CNI 规范的意思，Plugins/插件 为 CNI Plugin 意思。\nCNI 在 k8s blogs [1] 中有提到 cri-containerd (是 containerd 为 kubernetes CRI plugin 的实现，现被合并到 containerd 项目中)，这里有一张图很详细的说明了当在创建一个 Pod cri-containerd 是如何工作的。\n图1：how CRI talks to CNI plugin to attach a network interface to the newly created pod. Source：https://blog.devops.dev/simplifying-multi-network-connectivity-in-openshift-with-multus-cni-dabe4da209f8\n图1 表示了 CNI 在 kubernetes 云原生环境中的概略图，左上角我们有一个Pod (Kubernetes中称为一组容器), 我们将把 kubernetes 网络附加到 Pod中。CNI 是 一个通用的接口，它能够用于任何容器运行时和任何网络，Kubernetes 与 CNI 之间没有很强的联系。runtime 会调用 CNI，图中有一个动词 “ADD” ，还有其他的动词，例如 “DEL” 。“ADD” 是向容器添加网络接口，将想要完成的操作放置在 “JSON” 文本中，该文件是提供给 CNI Plugins 调用时使用。例如告知 Plugin 网络 provider (OVS? Linux bridge? or BPF)。\nCNI Plugin 的行为就像 “胶水” 在 kubernetes 网络中，只需要一些 “胶水” 操作网络插件，就可以将你的网络和容器进行连接\n在这里不再详细的阐述 CRI-Containerd 中 Pod 启动的原理，仅仅了解 CNI 如何适配即可。\n什么是CNI CNI 包含两部分，“规范” (Specification) 和 “插件” (Plugins)\n规范文档：和编程语言无关 (The CNI spec is language agnostic.)，定义了配置的格式，用于如何提供 CNI 插件，例如 CNI 插件应该做什么，使用哪个 CNI 的操作详情，和 CNI 返回 k8s runtime 的结果。 libcni，CNI 运行时的实现 CNI skel 插件：网络插件是包含一组示例插件，他们不依赖于特定的云系统 (Cloud sytem, 这里指的是容器编排环境，k8s, nomad)，实际使用 cli 在命令行运行。 接口插件 (Interface plugins)：ptp, bridge, macvlan\u0026hellip; 链式插件 (Chained plugins)：portmap, bandwidth, tuning。 tip CNI 规范部分适用于许多不同符合 CNI SPEC 标准的 CNI 插件。 CNI规范 CNI规范 (CNI Specification) 是一个和编程语言无关的，供应商中立的 (vendor-neutral)。不仅仅是用于 Kubernetes 的标准，也被用于 nomad, mesos 等 orch。他定义了网络配置格式（包含 runtime 和 cni 之间传递的参数，版本控制，返回结果等），执行流和网络操作，正如 overview 中提到的 “动词” 。\nCNI五要素 定义了 Kubernetes 网络插件 (plugins) 的标准，就是 runtime 与 Plugin 之间的接口。主要分为“五要素”。\n网络配置格式 (Network configuration format)，通过这部分，可以让管理员能够使用“什么”类型的配置来定义网络。 执行协议 (Execution Protocol)，约定了容器运行时与网络插件之间通信的协议。 网络配置的执行 (Execution of Network Configurations)，根据配置的定义来执行相应的网络插件。 插件委派 (Plugin Delegation)；允许插件链式调用其他插件，以实现更复杂的网络功能。通过这个约束，可以将本插件的功能委托给另外一个插件执行。 结果类型 (Result Types)。规范了插件返回给容器运行时的结果格式。 Section 1 网络配置格式 网络配置格式是对配置的概述，通常实现了下面的功能。\n基于 JSON 的配置，在插件执行时，此配置格式由 runtime 解析。 标准 KEY（CNI SPEC规范定义的），也有特定的 KEY（传递给 CNI 使用的参数的 KEY）。 每一次操作使用 stdin 将配置传递给 Plugin。 网络配置存储在磁盘，或 rumtime。 下面是一个示例配置文件，定义了一个 mynet 配置，使用了 3 个插件 (bridge 和 ipam) ，这里 plugins 部分还包含了一个 “dns” KEY，这是 SPEC 中的 KEY，用于定义 dns server 相关配置。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 { \u0026#34;cniVersion\u0026#34;: \u0026#34;1.1.0\u0026#34;, \u0026#34;cniVersions\u0026#34;: [\u0026#34;0.3.1\u0026#34;, \u0026#34;0.4.0\u0026#34;, \u0026#34;1.0.0\u0026#34;, \u0026#34;1.1.0\u0026#34;], \u0026#34;name\u0026#34;: \u0026#34;dbnet\u0026#34;, \u0026#34;plugins\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;bridge\u0026#34;, // plugin specific parameters \u0026#34;bridge\u0026#34;: \u0026#34;cni0\u0026#34;, \u0026#34;keyA\u0026#34;: [\u0026#34;some more\u0026#34;, \u0026#34;plugin specific\u0026#34;, \u0026#34;configuration\u0026#34;], \u0026#34;ipam\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;host-local\u0026#34;, // ipam specific \u0026#34;subnet\u0026#34;: \u0026#34;10.1.0.0/16\u0026#34;, \u0026#34;gateway\u0026#34;: \u0026#34;10.1.0.1\u0026#34;, \u0026#34;routes\u0026#34;: [ {\u0026#34;dst\u0026#34;: \u0026#34;0.0.0.0/0\u0026#34;} ] }, \u0026#34;dns\u0026#34;: { \u0026#34;nameservers\u0026#34;: [ \u0026#34;10.1.0.1\u0026#34; ] } }, { \u0026#34;type\u0026#34;: \u0026#34;tuning\u0026#34;, \u0026#34;capabilities\u0026#34;: { \u0026#34;mac\u0026#34;: true }, \u0026#34;sysctl\u0026#34;: { \u0026#34;net.core.somaxconn\u0026#34;: \u0026#34;500\u0026#34; } }, { \u0026#34;type\u0026#34;: \u0026#34;portmap\u0026#34;, \u0026#34;capabilities\u0026#34;: {\u0026#34;portMappings\u0026#34;: true} } ] } Section 2 执行流 执行协议也成为“执行流” (execution flow)，在这部分中\n基本操作 (Verb)，ADD, DEL, CHECK, GC (1.1.0), VERSION。 网络插件是可以执行的文件。在 kubernetes 环境中作为单独程序执行。 网络操作由运行时启动或创建。 通过 stdin 将“配置文件” (JSON) 和 “环境变量” 传递给 plugins。 通过配置文件可以传递指定参数给 plugin (via stdin)、 通过 stdout 报告执行结果。 runtime执行插件时通常在 “runtime” 的 network namespace。(通常指的是root) 参数 (Parameters) CNI_COMMAND：指示期望的操作 (Verb); 例如 ADD, DEL, CHECK, GC, or VERSION. CNI_CONTAINERID: Container ID. 由runtime分配的container的唯一标识符 (Plaintext)；该参数必须不为空，字母数字开头，可以包含 点(.), 连字符 (-), 下划线 (_)。 CNI_NETNS: A reference to the container’s “isolation domain”. If using network namespaces, then a path to the network namespace (e.g. /run/netns/[nsname]) CNI_IFNAME: 容器内创建的网络接口的名称，如果不能使用这个名称创建网络接口，则必须返回错误 CNI_ARGS: 传递给 CNI Plugin 的额外参数。用分号 (;) 分割的 key value对, “FOO=BAR;ABC=123” CNI_PATH: CNI Plugins 可执行文件的路径列表，根据不同的操作系统使用不同的路径分隔符; 例如 Linux 是 “:”， Windows 是 “;”。 错误 (Error) 当 CNI 执行成功时退出码为 “0”，不为“0” 时 为错误，并且 CNI Plugin 应该输出 result 结构的“错误” (Result部分见 section 5)。\nVerb (CNI 操作) CNI 1.1.0 定义了 5 个动词，版本 1.1.0 为 4 个 (without GC)，0.4.0之前的版本为 3 个 (without CHECK), 包含 ADD, DEL, CHECK, GC, 和 VERSION. 通过环境变量 CNI_COMMAND 传递。\nADD: 为容器添加网络，或者应用网络配置的变更 当收到 ADD 命令后，至少要执行下面两个操作之一：\n使用环境变量 CNI_IFNAME 和 CNI_NETNS创建网络接口。 或者根据环境变量 CNI_IFNAME 和 CNI_NETNS 来调整网络配置。 tip 这里需要注意的是，\nruntime不应该对相同的 (CNI_CONTAINERID, CNI_IFNAME) 调用两次 ADD。 插件执行成功，必须通过 stdout (standard out) 输出标准的 result 结构。 如果插件提供了 prevResult 作为 input 部分，那么必须处理 prevResult 如果“网络接口”在容器内已经存在，网络插件必须返回 result error 结构 ADD 的输入是通过标准输入 (STDIN) 输入 JSON 序列化后的配置文件，输出是标准输出 (STDOUT) 的标准 result 结构。\n输入\n参数 说明 CNI_COMMAND 运行的 verb CNI_CONTAINERID 容器的唯一ID CNI_IFNAME 要创建的网卡接口名称 CNI_NETNS 容器的网络名称空间 CNI_ARGS（可选） 传递给 CNI 插件的额外参数 CNI_PATH（可选） CNI 插件路径 DEL: 删除容器的网络 容器收到 DEL 命令后，至少要执行下面两个操作之一：\n删除 CNI_NETNS 定义网络名称空间内的，CNI_IFNAME 定义的容器内的网络接口。 撤掉 ADD 函数内应用的任何修改。 DEL 的输入是通过标准输入 (STDIN) 输入 JSON 序列化后的配置文件，输出是标准输出 (STDOUT) 的标准 result 结构。\ntip 这里需要注意的是，网络插件必须支持针对相同的网络对 (CNI_CONTAINERID, CNI_IFNAME) 的多次 DEL 调用，并且在缺少相关网络接口或任意添加的更改不存在时返回 success 输入\n参数 说明 CNI_COMMAND 运行的 verb CNI_CONTAINERID 容器的唯一ID CNI_IFNAME 要创建的网卡接口名称 CNI_NETNS（可选） 容器的网络名称空间 CNI_ARGS（可选） 传递给 CNI 插件的额外参数 CNI_PATH（可选） CNI 插件路径 CHECK: 检查容器网络是否符合预期 CHEK是 0.4.0 版本新增的功能，在 CNCF 演讲中也有提到，CNI 开发者意识到在 ADD 后可能会出现错误（因为有时网络是异步创建的；有时 runtime 会发生改变），CHECK的目的是让 runtime 定期调用，检查网络是否按照预期方式进行设置。\nPlugin 考虑事项：\n插件必须咨询 prevResult（上一次 ADD 命令的结果）来了解预期的网络接口和地址。 插件必须允许后面链式插件可修改后的网络资源。例如在 ADD 操作后添加路由。 以下情况插件必须返回错误： 如果 CNI Result 中包含资源 (interface, address or route) 是由插件创建的，并且在 prevResult 中有列出，但是丢失或无效的状态。 如果 CNI Result 类型中没有记录的其他资源（例如防火墙规则 (Firewall rules)、流量控制 (Traffic shaping controls)、IP 保留 (IP reservations)、外部依赖 (External dependencies) 等）缺失或无效。 如果插件意识到容器不可达时。 插件必须能处理在 ADD 后立即调用的 CHECK，并且需要考虑到一些资源可能是异步加载的，因此需要给出合理的延迟时间来完成。 插件应该调用任何委派插件（例如 IPAM 插件）的 CHECK，并将错误传递给调用者。 runtime 考虑事项：\nruntime 掉要 CHECK 必须遵循： 没有执行 ADD 操作的不能调用 CHECK。 已经在 ADD 之后执行过 DEL 操作的不能调用 CHECK。 在配置文件中配置了 disableCheck=true 的不能调用 CHECK。 runtime 需要在网络配置中包含 prevResult 字段，该字段存储容器上一次 ADD 操作的结果。runtime 可以通过使用 libcni 提供的缓存功能来实现。 runtime 可以选择在某个插件返回错误时停止执行后续的 CHECK 操作。 runtime 可以在成功执行 ADD 后，直到容器被删除之前，执行 CHECK。 运行时可以假定错误的/失败的 CHECK ，容器处于永久性配置错误的状态。 tip 这里需要注意的是，所有的参数除了 CNI_PATH，必须和这个容器的 ADD 操作时参数一致。 输入\n参数 说明 CNI_COMMAND 运行的 verb CNI_CONTAINERID 容器的唯一ID CNI_NETNS 容器的网络名称空间 CNI_IFNAME 要创建的网卡接口名称 CNI_ARGS（可选） 传递给 CNI 插件的额外参数 CNI_PATH（可选） CNI 插件路径 STATUS: 检查插件状态 tip STATUS 是绝定网络插件就绪的一种方式，是 CNI 1.1.0 中新增的规范，可能其他 CNI 解决方案并未支持。 如果“插件”准备就绪去完成 ADD 请求，那么插件的退出码必须为0，反之如果“插件”知道无法完成 ADD 请求，那么它的退出码必须为“非0”并且通过标准输出 (STDOUT) 输出标准 result 结构（详见 Section 5）。\n下列错误码在 STATUS 的上下文中定义：\n50: The plugin is not available (i.e. cannot service ADD requests) 51: The plugin is not available, and existing containers in the network may have limited connectivity. 插件需考虑的内容：\nStatus is purely informational. A plugin MUST NOT rely on STATUS being called. 插件必须不能依赖 STATUS 调用，即 Status 仅仅提供信息参考 当 STATUS 返回了一个错误，例如 ADD, DEL 操作仍然被允许调用， Status 不会作为一个前置条件，不能避免 Runtime 的其他请求。 如果插件依赖另外的委托插件 (delegated plugin), 去完成 ADD 请求，在他自己收到了一个 STATUS 请求，也必须对此执行 STATUS 请求。如果委托插件返回一个错误 result，那么执行插件 (通常为当前插件) 也应该返回一个错误 result。 输入\n参数 说明 CNI_PATH（可选） CNI 插件路径 VERSION : 探测插件版本的支持 该插件应通过标准输出 (STDOUT) JSON 序列化后的对象。包含 cniVersion：使用协议的版本\n输入参数 说明 CNI_COMMAND 执行的 Verb GC: 清理任意的旧资源 tip GC 操作允许 runtime 指定 “期望” 的网络附件集合 “插件”，其后网络插件可以删除与此网络附加集合中不存在的相关的任何资源。例如，预留的IPAM；防火墙规则。\nGC 是 CNI 1.1.0 中新增的规范，可能其他 CNI 解决方案并未支持。\n这个操作的目标和遵守如下所述几点：\n插件应该尽可能清理无效资源。 插件可以假设隔离域（通常为网络命名空间）已被删除。 GC 过程中插件应该尽可能完成清理，不应因错误中断。 插件必须转发 GC 请求到它依赖的插件。 runtime 不能用 GC 代替 DEL。 输入\n参数 说明 CNI_COMMAND 执行的 Verb CNI_PATH CNI 插件路径 Section 3 网络配置的执行 这一部分是 section 2 执行流的一个补充，主要阐述了了 “runtime 如何解析网络配置并调用 CNI 插件”，这个操作被称为 “attachment”。通过 (CNI_CONTAINERID, CNI_IFNAME) 来识别唯一的 attachment。\n生命周期和顺序 (Lifecycle \u0026amp; Ordering) 容器运行时在调用任何插件前，必须为容器创建一个网络名称空间。 容器运行时不能为同一个容器进行并行操作，但是允许调用并行操作对不同的容器，这包含多个 attachment。 异常：容器运行时独占执行 “gc” 或 “ADD,” “DEL”，容器运行时必须确保在执行 “GC” 没有 “ADD,” 或 “DEL” 在处理中（或执行中）。并且在执行新的 “ADD,” 或 “DEL” 前必须等待 “GC” 完成。 网络插件在执行时，当处理在不同容器上同时执行的场景。如有必要，必须对共享资源上实施锁定（例如操作 IPAM数据库）。 容器运行时必须确保每次执行 “add” 操作后，最终会跟随一个对应的 “delete” 操作；（唯一的例外是在发生灾难性故障（例如节点丢失）时。即使 “add” 操作失败，也必须执行 “delete” 操作。） “delete” 操作后可以跟随其他的 “deletes” 操作。 在执行 “add” 和 “delete” 操作之间，网络配置不应发生变化。 在 “attachment” 之间，网络配置不应发生变化。 容器运行时负责清理容器的网络命名空间。 Attachment 参数 对于配置，在 “attachment” 之间是不应发生变化的，但是容器运行时为每个 “attachment” 提供了特定的参数。\n参数 说明 Container ID 容器的唯一明文标识符，由“容器运行时”分配。不能为空。必须以字母或数字字符开头，后面可以跟着字母、数字字符、下划线（_）、点（.）或连字符（-）。在执行过程中，设置为 CNI_CONTAINERID 参数。 Namespace 容器的“隔离域”引用。如果使用网络命名空间，则是网络命名空间的路径（例如 /run/netns/[nsname]）。在执行过程中，始终设置为 CNI_NETNS 参数。 Container interface name 要在容器内创建的接口名称。在执行过程中，应始终设置为 CNI_IFNAME 参数。 Generic Arguments 特定 attachment 相关的附加参数，以 key, value 对的形式提供。在执行过程中，始终设置为 CNI_ARGS 参数。 **Capability Arguments ** 这些也是 key, value 对。key 是字符串，而 value 是任何可以 JSON 序列化的类型。key 和 value 由协定 (Conventions) 定义。 另外容器运行时必须提供 CNI Plugin 的路径列表，在执行过程中通过环境变量 “CNI_PATH” 提供给网络插件。\n添加一个Attachment 对于每个网络配置插件 key 中定义的每一个配置\n查找在 type 字段中指定的可执行文件。如果该文件不存在，则为错误。 从插件配置中派生请求配置，具有以下参数： 如果这是列表中的第一个插件，则没有提供先前一个 result； 对于所有后续插件，先前的 result 是前一个插件的执行 result。 执行二进制网络插件文件，并将 Verb 设置为 CNI_COMMAND=ADD。将上述定义的参数作为环境变量提供。通过标准输入 (STDIN) 提供派生的配置。 如果插件返回错误，停止执行并将错误返回给调用者。 由于执行 check 和 delete 时需要，容器运行时必须永久存储最终插件执行返回的结果， 删除一个Attachment 删除动作与添加相似，但有一些不同之处：\n网络配置中的插件顺序是以相反的顺序执行，例如，清除防火墙规则，删除预留 IPAM，删除网络接口。 提供的前一个 result 始终是 add 操作的最终 result。 对于网络配置中 plugins 字段定义的每个插件，按相反顺序执行：\n查找在 type 字段中指定的可执行文件。如果该文件不存在，则为错误。 从插件配置中派生请求配置，并使用初始 add 操作的先前 result。 执行二进制插件文件，并将 Verb 设置为 CNI_COMMAND=DEL。将上述定义的参数作为环境变量提供。通过标准输入 (STDIN) 提供派生的配置。 如果插件返回错误，停止执行并将错误返回给调用者。 如果所有插件都执行成功，返回成功给调用者（通常为 rumtime）\n垃圾回收网络 容器运行时还可以请求网络配置中的每个插件通过 GC 命令清理任何过期的资源。该步骤没有 Attachment 参数，但对于网络配置文件中 plugins 部分定义的每个插件的执行协定 (convention [2] , 这是 CNI 的另一个部分)，有下面要求\n查找在 type 字段中指定的可执行文件。如果该文件不存在，则为错误。 从插件配置中派生请求配置。 执行二进制插件文件，并将 Verb 设置为 CNI_COMMAND=GC。通过标准输入 (STDIN) 提供派生的配置。 如果插件返回错误，继续执行，并将所有错误返回给调用者。 如果所有插件都执行成功，返回成功给调用者（通常为 rumtime）\n从插件配置中派生请求配置 网络配置格式（即要执行的插件配置列表）必须转换为插件可以理解的格式（即对单个执行的插件配置）。派生配置 (Deriving request configuration) 就是转换过程。\n由于在执行单个插件调用的请求配置也是 JSON 格式。它由插件配置组成，除了指定的 ADD 和 DEL 外，其他部分通常不变。\n运行时必须始终插入以下字段到请求配置中：\ncniVersion：运行时选择的协议版本 - 例如，字符串 \u0026ldquo;1.1.0\u0026rdquo; name：来自网络配置中的 name 字段，例如，flannel, cilium 对于特定的 Attachment (ADD, DEL, CHECK)，还需要额外的字段：\nruntimeConfig：运行时必须插入一个对象，包含插件提供的功能和容器运行时请求的功能的并集。 prevResult：运行时必须插入包含 “前一个” 插件返回的 result 类型。 “前一个” 的含义由具体的操作 (ADD, DEL, CHECK) 来定义 。在链中的第一个添加操作中此字段不用配置。 capabilities：不能配置。 Deriving 配置说明 虽然 CNI_ARGS 会提供给所有插件，并且没有指示它们是否会被使用，但 capabilities 参数需要在配置中明确声明。因此，运行时可以确定指定的网络配置是否支持特定的功能。capabilities不是由 SPEC 定义的，而是由 CNI 协定 [2] 文档进行说明。\n下面是一个 capabilities 参数的示例，他标注了该插件有端口映射的能力。\njson 1 2 3 4 5 6 { \u0026#34;type\u0026#34;: \u0026#34;myPlugin\u0026#34;, \u0026#34;capabilities\u0026#34;: { \u0026#34;portMappings\u0026#34;: true } } runtimeConfig 参数是从网络配置中的 capabilities 和运行时生成的 capabilities 参数中派生出来的。具体来说，任何由插件配置支持并由运行时提供的 capabilities 都应插入到 runtimeConfig 配置中。\n因此，上述示例中的说明配置可能导致以下内容作为执行配置的一部分传递给插件：\njson 1 2 3 4 5 6 7 { \u0026#34;type\u0026#34;: \u0026#34;myPlugin\u0026#34;, \u0026#34;runtimeConfig\u0026#34;: { \u0026#34;portMappings\u0026#34;: [ { \u0026#34;hostPort\u0026#34;: 8080, \u0026#34;containerPort\u0026#34;: 80, \u0026#34;protocol\u0026#34;: \u0026#34;tcp\u0026#34; } ] } ... } Section 4: 插件委托 (Plugin Delegation) 在执行操作时，CNI 插件可能希望将一些功能委托给其他插件。例如 IPAM。\n作为其操作的一部分，CNI 插件需要为接口分配并维护 IP 地址，并创建与该网络接口相关的必要路由。因此 CNI 插件可以选择将 IP 管理委托给另一个插件。\n但在使用中，为了避免在许多 CNI 插件使用时可能需要相同的代码来支持用户可能需要的几种 IP 管理的方案。\n需要注意的是，是CNI 插件调用 IPAM 插件 ，而不是运行时。CNI 插件必须在适当的时刻调用 IPAM 插件。IPAM 插件必须确定接口的 IP, Mask, Gateway, Routers，并将这些信息返回给 “主” 插件以便应用。IPAM 插件也可以通过协议 (DHCP), 本地文件系统上的存储数, 网络配置文件中的 “ipam” 部分定义等方式获取这些信息。 委托插件的执行过程如下：\n在环境变量 CNI_PATH 中提供的插件路径列表中找到插件的二进制文件。 使用收到的环境变量和配置执行插件。 确保委托插件的 STDERR 输出是输出到调用插件的 STDERR。 如果执行操作为 CNI_COMMAND=CHECK, DEL, 也可以执行任意的委托插件，当任意委托插件返回错误，错误也将返回给前一个插件。\n如果执行操作为 CNI_COMMAND=ADD，如果委托插件执行错误，则上个插件应在返回错误之前执行 DEL。\nSection 5: Result 类型 Result 类型是插件在执行后输出的一个结果信息的标准，他是一个 JSON 结构的内容。通过标准输出传递给运行时。\nADD 成功时应输出的类型的 schema 当执行 ADD 时，输出的结果必须为下面的结构\nKEY 说明 cniVersion 和输入时相同的版本，字符串类型，例如 “1.1.0” interfaces attachment 创建所有成功的接口，数组类型，包含主机级别 (host-level) 的接口\nname (string): 接口的名称.\nmac (string): 接口的mac地址 (if applicable).\nmtu: (uint) 接口的 MTU (maximum transmission unit) 值 (if applicable).\nsandbox (string): 接口引用的隔离域，如果是 host 网络则为空。对于容器内创建接口通过环境变量 CNI_NETNS 传递. socketPath (string, optional): 该接口对应的 socket 文件的绝对路径 (if applicable).\npciID (string, optional): 如果适用，表示与该接口对应的 PCI 设备的特定平台标识符 ips 这个 attachment 分配的IP，\naddress (string): CIDR标识的IP地址 (eg “192.168.1.3/24”). gateway (string): 如果存在为这个子网的默认网关 interface (uint): CNI 插件 Result 接口列表中的索引，用于指定这个个接口应该被应用这个 IP 配置。 routes 这个 attachment 创建的路由\ndst：CIDR 表示的路由的目标地址。\ngw：下一跳地址。如果未设置，可以使用 ips 数组中 gateway 的值。\nmtu (uint)：最大传输单元（MTU）。\npriority (uint)：路由的优先级，数值越小优先级越高。\ntable (uint)：路由添加到的路由表。\nscope (uint)：路由前缀所覆盖的目标的范围 (global (0), link (253), host (254))。 dns nameservers (list of strings): 网络中 nameserver 的优先级列表，包含字符串 IPV4 和 IPV6 domain (string): 本地域 search (list of strings): 搜索域列表，优先于 domain options (list of strings): 传递给 resolver 的列表 插件在其请求配置中提供了prevResult key，必须将其作为结果输出，并包括该插件可能做出的任何修改。如果插件没有做出任何在 Success 结果类型中反映的更改，则必须输出一个与提供的 prevResult 等效的结果。 用于 Delegated plugins (IPAM) 返回类型 Delegated plugins 必须返回一个缺少 interfaces, ips 的简版 Success 对象。\nVERSION Success VERSION 操作返回的 schema\ncniVersion: 输入中指定的cniVersion 的值 supportedVersions: 支持的规格版本的列表 json 1 2 3 4 { \u0026#34;cniVersion\u0026#34;: \u0026#34;1.0.0\u0026#34;, \u0026#34;supportedVersions\u0026#34;: [ \u0026#34;0.1.0\u0026#34;, \u0026#34;0.2.0\u0026#34;, \u0026#34;0.3.0\u0026#34;, \u0026#34;0.3.1\u0026#34;, \u0026#34;0.4.0\u0026#34;, \u0026#34;1.0.0\u0026#34; ] } Error cniVersion: 使用的版本协议 code: 数字错误码，错误代码 [3] 0-99 用于保留。 100+ 的值可自由用于分配插件特定错误。 msg: 描述错误的短说明，例如异常类型 details: 错误详细描述 json 1 2 3 4 5 6 { \u0026#34;cniVersion\u0026#34;: \u0026#34;1.1.0\u0026#34;, \u0026#34;code\u0026#34;: 7, \u0026#34;msg\u0026#34;: \u0026#34;Invalid Configuration\u0026#34;, \u0026#34;details\u0026#34;: \u0026#34;Network 192.168.0.0/31 too small to allocate from.\u0026#34; } 通过实例理解 CNI SPEC 本章节内容采用的官方给出的示例来说明 CNI 配置一些示例配置文件\nADD 容器运行时将执行下面的步骤来执行 ADD 操作。\n使用下面 JSON 格式的配置 调用 bridge plugin，执行 ADD 操作 (CNI_COMMAND=ADD ) json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { \u0026#34;cniVersion\u0026#34;: \u0026#34;1.1.0\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;dbnet\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;bridge\u0026#34;, \u0026#34;bridge\u0026#34;: \u0026#34;cni0\u0026#34;, \u0026#34;keyA\u0026#34;: [\u0026#34;some more\u0026#34;, \u0026#34;plugin specific\u0026#34;, \u0026#34;configuration\u0026#34;], \u0026#34;ipam\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;host-local\u0026#34;, \u0026#34;subnet\u0026#34;: \u0026#34;10.1.0.0/16\u0026#34;, \u0026#34;gateway\u0026#34;: \u0026#34;10.1.0.1\u0026#34; }, \u0026#34;dns\u0026#34;: { \u0026#34;nameservers\u0026#34;: [ \u0026#34;10.1.0.1\u0026#34; ] } } 接下来 ipam 作为 bridge 的代理，执行本地的二进制文件 host-local，输入参数为 CNI_COMMAND=ADD.\nhost-local 将返回下面内容\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { \u0026#34;ips\u0026#34;: [ { \u0026#34;address\u0026#34;: \u0026#34;10.1.0.5/16\u0026#34;, \u0026#34;gateway\u0026#34;: \u0026#34;10.1.0.1\u0026#34; } ], \u0026#34;routes\u0026#34;: [ { \u0026#34;dst\u0026#34;: \u0026#34;0.0.0.0/0\u0026#34; } ], \u0026#34;dns\u0026#34;: { \u0026#34;nameservers\u0026#34;: [ \u0026#34;10.1.0.1\u0026#34; ] } } bridge 根据委托插件 ipam 来配置接口，并返回下面信息\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 { \u0026#34;ips\u0026#34;: [ { \u0026#34;address\u0026#34;: \u0026#34;10.1.0.5/16\u0026#34;, \u0026#34;gateway\u0026#34;: \u0026#34;10.1.0.1\u0026#34;, \u0026#34;interface\u0026#34;: 2 } ], \u0026#34;routes\u0026#34;: [ { \u0026#34;dst\u0026#34;: \u0026#34;0.0.0.0/0\u0026#34; } ], \u0026#34;interfaces\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;cni0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:55\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;veth3243\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;55:44:33:22:11:11\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;eth0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;99:88:77:66:55:44\u0026#34;, \u0026#34;sandbox\u0026#34;: \u0026#34;/var/run/netns/blue\u0026#34; } ], \u0026#34;dns\u0026#34;: { \u0026#34;nameservers\u0026#34;: [ \u0026#34;10.1.0.1\u0026#34; ] } } 接下来 tuning 插件使用环境变量 CNI_COMMAND=ADD 指定操作 verb。需要注意的是，这里提供了 prevResult 以及 MAC 配置相关参数。返回的结果如下 json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 { \u0026#34;cniVersion\u0026#34;: \u0026#34;1.1.0\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;dbnet\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;tuning\u0026#34;, \u0026#34;sysctl\u0026#34;: { \u0026#34;net.core.somaxconn\u0026#34;: \u0026#34;500\u0026#34; }, \u0026#34;runtimeConfig\u0026#34;: { \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:66\u0026#34; }, \u0026#34;prevResult\u0026#34;: { \u0026#34;ips\u0026#34;: [ { \u0026#34;address\u0026#34;: \u0026#34;10.1.0.5/16\u0026#34;, \u0026#34;gateway\u0026#34;: \u0026#34;10.1.0.1\u0026#34;, \u0026#34;interface\u0026#34;: 2 } ], \u0026#34;routes\u0026#34;: [ { \u0026#34;dst\u0026#34;: \u0026#34;0.0.0.0/0\u0026#34; } ], \u0026#34;interfaces\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;cni0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:55\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;veth3243\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;55:44:33:22:11:11\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;eth0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;99:88:77:66:55:44\u0026#34;, \u0026#34;sandbox\u0026#34;: \u0026#34;/var/run/netns/blue\u0026#34; } ], \u0026#34;dns\u0026#34;: { \u0026#34;nameservers\u0026#34;: [ \u0026#34;10.1.0.1\u0026#34; ] } } } 插件返回的结果如下，MAC 的配置将被改变\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 { \u0026#34;ips\u0026#34;: [ { \u0026#34;address\u0026#34;: \u0026#34;10.1.0.5/16\u0026#34;, \u0026#34;gateway\u0026#34;: \u0026#34;10.1.0.1\u0026#34;, \u0026#34;interface\u0026#34;: 2 } ], \u0026#34;routes\u0026#34;: [ { \u0026#34;dst\u0026#34;: \u0026#34;0.0.0.0/0\u0026#34; } ], \u0026#34;interfaces\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;cni0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:55\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;veth3243\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;55:44:33:22:11:11\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;eth0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:66\u0026#34;, \u0026#34;sandbox\u0026#34;: \u0026#34;/var/run/netns/blue\u0026#34; } ], \u0026#34;dns\u0026#34;: { \u0026#34;nameservers\u0026#34;: [ \u0026#34;10.1.0.1\u0026#34; ] } } 最终调用 portmap 插件，使用 CNI_COMMAND=ADD，需要注意的是，prevResult 匹配通过之前的 tuning 插件返回的 result。 json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 { \u0026#34;cniVersion\u0026#34;: \u0026#34;1.1.0\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;dbnet\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;portmap\u0026#34;, \u0026#34;runtimeConfig\u0026#34;: { \u0026#34;portMappings\u0026#34; : [ { \u0026#34;hostPort\u0026#34;: 8080, \u0026#34;containerPort\u0026#34;: 80, \u0026#34;protocol\u0026#34;: \u0026#34;tcp\u0026#34; } ] }, \u0026#34;prevResult\u0026#34;: { \u0026#34;ips\u0026#34;: [ { \u0026#34;address\u0026#34;: \u0026#34;10.1.0.5/16\u0026#34;, \u0026#34;gateway\u0026#34;: \u0026#34;10.1.0.1\u0026#34;, \u0026#34;interface\u0026#34;: 2 } ], \u0026#34;routes\u0026#34;: [ { \u0026#34;dst\u0026#34;: \u0026#34;0.0.0.0/0\u0026#34; } ], \u0026#34;interfaces\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;cni0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:55\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;veth3243\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;55:44:33:22:11:11\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;eth0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:66\u0026#34;, \u0026#34;sandbox\u0026#34;: \u0026#34;/var/run/netns/blue\u0026#34; } ], \u0026#34;dns\u0026#34;: { \u0026#34;nameservers\u0026#34;: [ \u0026#34;10.1.0.1\u0026#34; ] } } } PortMap 插件输出的结果与 Bridge 插件返回的结果完全相同，因为该插件没有修改任何会更改 result。\n这个示例是作为 section 1 中示例配置ide说明，他的完整网络配置如下：\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 { \u0026#34;cniVersion\u0026#34;: \u0026#34;1.1.0\u0026#34;, \u0026#34;cniVersions\u0026#34;: [\u0026#34;0.3.1\u0026#34;, \u0026#34;0.4.0\u0026#34;, \u0026#34;1.0.0\u0026#34;, \u0026#34;1.1.0\u0026#34;], \u0026#34;name\u0026#34;: \u0026#34;dbnet\u0026#34;, \u0026#34;plugins\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;bridge\u0026#34;, // plugin specific parameters \u0026#34;bridge\u0026#34;: \u0026#34;cni0\u0026#34;, \u0026#34;keyA\u0026#34;: [\u0026#34;some more\u0026#34;, \u0026#34;plugin specific\u0026#34;, \u0026#34;configuration\u0026#34;], \u0026#34;ipam\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;host-local\u0026#34;, // ipam specific \u0026#34;subnet\u0026#34;: \u0026#34;10.1.0.0/16\u0026#34;, \u0026#34;gateway\u0026#34;: \u0026#34;10.1.0.1\u0026#34;, \u0026#34;routes\u0026#34;: [ {\u0026#34;dst\u0026#34;: \u0026#34;0.0.0.0/0\u0026#34;} ] }, \u0026#34;dns\u0026#34;: { \u0026#34;nameservers\u0026#34;: [ \u0026#34;10.1.0.1\u0026#34; ] } }, { \u0026#34;type\u0026#34;: \u0026#34;tuning\u0026#34;, \u0026#34;capabilities\u0026#34;: { \u0026#34;mac\u0026#34;: true }, \u0026#34;sysctl\u0026#34;: { \u0026#34;net.core.somaxconn\u0026#34;: \u0026#34;500\u0026#34; } }, { \u0026#34;type\u0026#34;: \u0026#34;portmap\u0026#34;, \u0026#34;capabilities\u0026#34;: {\u0026#34;portMappings\u0026#34;: true} } ] } Delete 对于同一网络配置JSON列表，容器运行时将对删除操作执行下面步骤。需要注意的一点是，插件对比 ADD 和 CHECK 操作是以相反顺序执行的。\n首先， portmap 使用下列配置文件 调用 DEL 操作 (CNI_COMMAND=DEL) json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 { \u0026#34;cniVersion\u0026#34;: \u0026#34;1.1.0\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;dbnet\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;portmap\u0026#34;, \u0026#34;runtimeConfig\u0026#34;: { \u0026#34;portMappings\u0026#34; : [ { \u0026#34;hostPort\u0026#34;: 8080, \u0026#34;containerPort\u0026#34;: 80, \u0026#34;protocol\u0026#34;: \u0026#34;tcp\u0026#34; } ] }, \u0026#34;prevResult\u0026#34;: { \u0026#34;ips\u0026#34;: [ { \u0026#34;address\u0026#34;: \u0026#34;10.1.0.5/16\u0026#34;, \u0026#34;gateway\u0026#34;: \u0026#34;10.1.0.1\u0026#34;, \u0026#34;interface\u0026#34;: 2 } ], \u0026#34;routes\u0026#34;: [ { \u0026#34;dst\u0026#34;: \u0026#34;0.0.0.0/0\u0026#34; } ], \u0026#34;interfaces\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;cni0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:55\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;veth3243\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;55:44:33:22:11:11\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;eth0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:66\u0026#34;, \u0026#34;sandbox\u0026#34;: \u0026#34;/var/run/netns/blue\u0026#34; } ], \u0026#34;dns\u0026#34;: { \u0026#34;nameservers\u0026#34;: [ \u0026#34;10.1.0.1\u0026#34; ] } } } 其次，tuning 使用下面配置执行 DEL 请求 (CNI_COMMAND=DEL) json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 { \u0026#34;cniVersion\u0026#34;: \u0026#34;1.1.0\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;dbnet\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;tuning\u0026#34;, \u0026#34;sysctl\u0026#34;: { \u0026#34;net.core.somaxconn\u0026#34;: \u0026#34;500\u0026#34; }, \u0026#34;runtimeConfig\u0026#34;: { \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:66\u0026#34; }, \u0026#34;prevResult\u0026#34;: { \u0026#34;ips\u0026#34;: [ { \u0026#34;address\u0026#34;: \u0026#34;10.1.0.5/16\u0026#34;, \u0026#34;gateway\u0026#34;: \u0026#34;10.1.0.1\u0026#34;, \u0026#34;interface\u0026#34;: 2 } ], \u0026#34;routes\u0026#34;: [ { \u0026#34;dst\u0026#34;: \u0026#34;0.0.0.0/0\u0026#34; } ], \u0026#34;interfaces\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;cni0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:55\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;veth3243\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;55:44:33:22:11:11\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;eth0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:66\u0026#34;, \u0026#34;sandbox\u0026#34;: \u0026#34;/var/run/netns/blue\u0026#34; } ], \u0026#34;dns\u0026#34;: { \u0026#34;nameservers\u0026#34;: [ \u0026#34;10.1.0.1\u0026#34; ] } } } 最后调用 bridge 插件 json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 { \u0026#34;cniVersion\u0026#34;: \u0026#34;1.1.0\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;dbnet\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;bridge\u0026#34;, \u0026#34;bridge\u0026#34;: \u0026#34;cni0\u0026#34;, \u0026#34;keyA\u0026#34;: [\u0026#34;some more\u0026#34;, \u0026#34;plugin specific\u0026#34;, \u0026#34;configuration\u0026#34;], \u0026#34;ipam\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;host-local\u0026#34;, \u0026#34;subnet\u0026#34;: \u0026#34;10.1.0.0/16\u0026#34;, \u0026#34;gateway\u0026#34;: \u0026#34;10.1.0.1\u0026#34; }, \u0026#34;dns\u0026#34;: { \u0026#34;nameservers\u0026#34;: [ \u0026#34;10.1.0.1\u0026#34; ] }, \u0026#34;prevResult\u0026#34;: { \u0026#34;ips\u0026#34;: [ { \u0026#34;address\u0026#34;: \u0026#34;10.1.0.5/16\u0026#34;, \u0026#34;gateway\u0026#34;: \u0026#34;10.1.0.1\u0026#34;, \u0026#34;interface\u0026#34;: 2 } ], \u0026#34;routes\u0026#34;: [ { \u0026#34;dst\u0026#34;: \u0026#34;0.0.0.0/0\u0026#34; } ], \u0026#34;interfaces\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;cni0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:55\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;veth3243\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;55:44:33:22:11:11\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;eth0\u0026#34;, \u0026#34;mac\u0026#34;: \u0026#34;00:11:22:33:44:66\u0026#34;, \u0026#34;sandbox\u0026#34;: \u0026#34;/var/run/netns/blue\u0026#34; } ], \u0026#34;dns\u0026#34;: { \u0026#34;nameservers\u0026#34;: [ \u0026#34;10.1.0.1\u0026#34; ] } } } bridge 插件返回前执行委托插件 host-local 操作 DEL ( CNI_COMMAND=DEL) 。\nReference [1] Containerd Brings More Container Runtime Options for Kubernetes\n[2] CNI Conventions\n[3] CNI result error code\n","permalink":"https://www.161616.top/deep-dive-k8s-network-cni/","summary":"本文是关于深入理解Kubernetes网络原理系列第3章 深入理解Kubernetes Pod网络原理 - 网络名称空间 深入理解Kubernetes Pod网络原理 - Linux虚拟网络技术 深入理解Kubernetes Pod网络原理 - CNI 深入理解Kubernetes Pod网络原理 - 跟随 flannel 学习CNI原理 深入理解Kubernetes Pod网络原理 - 跟随 flannel + multus 剖析 Chained Plugins 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 1 (Shell) 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 2 (libcni) 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 1 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 2 深入理解Kubernetes Pod网络原理 - Pod网络排错思路 概述 这是我在 kubernetes 网络之旅中的中的第4部分，主要学习 CNI 规范（5要素，旧版本是4要素）。要深深记住这些，在后面旅程中常常被用到。\nNotes 本文讲解均按照 1.","title":"深入理解Kubernetes Pod网络原理 - CNI"},{"content":"本文说明如何使用 Windows 10 创建单节点的 kind 集群 (KinD Kubernetes in Docker)。\n先提条件 条件 版本 操作系统：Windows 10 或 Windows 11 May 2020 Update (build 19041) Docker in WSL2 推荐安装Docker Desktop WSL2 distro 镜像 这里采用Debian 内存 至少 8GB 内存 方式1：直接使用 docker desktop进行安装 软件 版本 Docker Desktop 4.8.1 WSL Linux Ubuntu 22 kubectl 使用 wsl ubuntu 22 作为客户端 安装 Docker Desktop 首先完成 Windows 开启 WSL2 和 安装好 Docker Desktop, 可以参考附录1 [1]\n安装 kubectl 这里使用和 kind 版本一致的 kubectl 1.24, 安装方式采用了官方给予的 package manager [3] 方式安装\nUpdate the apt package index and install packages needed to use the Kubernetes apt repository:\n更新 apt 仓库索引并添加Kubernetes apt 仓库所需的安装软件包：\nbash 1 2 3 sudo apt-get update # apt-transport-https may be a dummy package; if so, you can skip that package sudo apt-get install -y apt-transport-https ca-certificates curl gnupg 下载公钥\nnote 这里需要注意，kind 对应版本的 1.24 已经不存在了，这里参考 reddit 上信息，改为 1.28 后正常 [4] 修改对应版本为 1.24\nbash 1 2 3 4 5 6 KUBE_VERSION=1.24 If the folder `/etc/apt/keyrings` does not exist, it should be created before the curl command, read the note below. # sudo mkdir -p -m 755 /etc/apt/keyrings curl -fsSL https://pkgs.k8s.io/core:/stable:/v${KUBE_VERSION}/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg # allow unprivileged APT programs to read this keyring sudo chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg 添加 k8s apt 仓库\nbash 1 2 3 4 5 KUBE_VERSION=1.28 # This overwrites any existing configuration in /etc/apt/sources.list.d/kubernetes.list echo \u0026#34;deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v${KUBE_VERSION}/deb/ /\u0026#34; | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo chmod 644 /etc/apt/sources.list.d/kubernetes.list # helps tools such as command-not-found to work correctly 方式2：通过子系统方式 安装windows子系统步骤可以参考 [1]\n启用子系统 Open PowerShell as Administrator (Start menu \u0026gt; PowerShell \u0026gt; right-click \u0026gt; Run as Administrator) and enter this command:\nbash 1 dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart 查看 wsl 状态\nbash 1 2 3 4 5 6 7 8 9 10 PS C:\\Windows\\system32\u0026gt; wsl --status 默认分发：docker-desktop 默认版本：2 适用于 Linux 的 Windows 子系统最后更新于 2023/8/3 适用于 Linux 的 Windows 子系统内核可以使用“wsl --update”手动更新，但由于你的系统设置，无法进行自动更新。 若要接收自动内核更新，请启用 Windows 更新设置:“在更新 Windows 时接收其他 Microsoft 产品的更新”。 有关详细信息，请访问https://aka.ms/wsl2kernel。 内核版本： 5.4.72 列出可用的 Linux 發行版本\nbash 1 2 3 4 5 6 # wsl --list --verbose wsl -l -v NAME STATE VERSION * docker-desktop Running 2 docker-desktop-data Running 2 ubuntu22 Running 1 更多命令参考 [1]\n下载WSL可用的Linux发行版 使用命令 wsl \u0026ndash;list \u0026ndash;online 可以查看网络源上有那些 Linux distributions 可以进行选择\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ wsl --list --online 以下是可安装的有效分发的列表。 使用 \u0026#39;wsl.exe --install \u0026lt;Distro\u0026gt;\u0026#39; 安装。 NAME FRIENDLY NAME Ubuntu Ubuntu Debian Debian GNU/Linux kali-linux Kali Linux Rolling Ubuntu-18.04 Ubuntu 18.04 LTS Ubuntu-20.04 Ubuntu 20.04 LTS Ubuntu-22.04 Ubuntu 22.04 LTS Ubuntu-24.04 Ubuntu 24.04 LTS OracleLinux_7_9 Oracle Linux 7.9 OracleLinux_8_7 Oracle Linux 8.7 OracleLinux_9_1 Oracle Linux 9.1 openSUSE-Leap-15.6 openSUSE Leap 15.6 SUSE-Linux-Enterprise-15-SP5 SUSE Linux Enterprise 15 SP5 SUSE-Linux-Enterprise-15-SP6 SUSE Linux Enterprise 15 SP6 openSUSE-Tumbleweed openSUSE Tumbleweed 通过命令安装 如果需要安装 Debian\nbash 1 wsl --install --distribution Debian\t通过离线安装包安装 在 github Downloading distributions [1] 部分可以看到对应的下载地址，这里下载 debian 镜像，文件名为 “TheDebianProject.DebianGNULinux_1.12.2.0_neutral___76v4gfsz19hv4.AppxBundle” AppxBundle 结尾。\n解压文件，方式一：MakeAppx\nbash 1 2 3 4 # 语法 MakeAppx unbundle /p bundle_name.appxbundle /d output_directory MakeAppx unbundle /p TheDebianProject.DebianGNULinux_1.12.2.0_neutral___76v4gfsz19hv4.AppxBundle /d output_directory 解压文件，方式二：直接使用 zip 类的压缩软件进行解压，解压完成后，获取 x64 版本的 .appx， 例如 “DistroLauncher-Appx_1.12.2.0_x64.appx”，然后再用 zip 类的压缩软件进行解压，获取里面的 “install.tar.gz”。\n使用下面命令进行安装\nbash 1 LxRunOffline.exe install -n debian -d D:\\src\\wsl -f install.tar.gz https://cdimage.debian.org/mirror/cdimage/archive/\nhttps://www.debian.org/doc/manuals/project-history/detailed.en.html\nhttps://learn.microsoft.com/en-us/windows/wsl/install-manual#step-4---download-the-linux-kernel-update-package\nhttps://insightsdude.uk/2023/02/wsl2-debian-image-prep/\nhttps://wsl.dev/swarmenetes/\nhttps://blog.rayfalling.com/2020/04/wsl2%E8%87%AA%E5%AE%9A%E4%B9%89-%E6%9B%B4%E6%96%B0%E5%86%85%E6%A0%B8.html\nhttps://learn.microsoft.com/en-us/windows/wsl/install-manual#step-4---download-the-linux-kernel-update-package\nReference [1] Downloading distributions\n[2] sirredbeard/awesome-wsl\n[3] Install using native package management\n[4] Minimal example of using kine\n[5] resource-profiling\n[6] [*Goodbye\n","permalink":"https://www.161616.top/kind-wsl2-windows10-installation/","summary":"本文说明如何使用 Windows 10 创建单节点的 kind 集群 (KinD Kubernetes in Docker)。\n先提条件 条件 版本 操作系统：Windows 10 或 Windows 11 May 2020 Update (build 19041) Docker in WSL2 推荐安装Docker Desktop WSL2 distro 镜像 这里采用Debian 内存 至少 8GB 内存 方式1：直接使用 docker desktop进行安装 软件 版本 Docker Desktop 4.8.1 WSL Linux Ubuntu 22 kubectl 使用 wsl ubuntu 22 作为客户端 安装 Docker Desktop 首先完成 Windows 开启 WSL2 和 安装好 Docker Desktop, 可以参考附录1 [1]\n安装 kubectl 这里使用和 kind 版本一致的 kubectl 1.","title":"使用WSL2安装kind (Win10 \u0026 DockerDesktop)"},{"content":"filebeat helm chart 在配置 filebeatConfig 想配置成多配置文件模式，但是网上没有找到对应配置，单配置文件模式如下所示\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 filebeatConfig: filebeat.yml: | filebeat.inputs: - type: container paths: - /var/log/containers/*.log processors: - add_kubernetes_metadata: host: ${NODE_NAME} matchers: - logs_path: logs_path: \u0026#34;/var/log/containers/\u0026#34; output.elasticsearch: host: \u0026#39;${NODE_NAME}\u0026#39; hosts: \u0026#39;[\u0026#34;https://${ELASTICSEARCH_HOSTS:elasticsearch-master:9200}\u0026#34;]\u0026#39; username: \u0026#39;${ELASTICSEARCH_USERNAME}\u0026#39; password: \u0026#39;${ELASTICSEARCH_PASSWORD}\u0026#39; protocol: https ssl.certificate_authorities: [\u0026#34;/usr/share/filebeat/certs/ca.crt\u0026#34;] 在网上有找到一个配置 [1] 是讲启用 filebeat module\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 filebeatConfig: filebeat.yml: | filebeat.config: modules: enabled: true path: ${path.config}/module.*.yml reload.enabled: true module.okta.yml: | - module: okta system: enabled: true var.url: https://yourOktaDomain/api/v1/logs var.api_key: \u0026#39;test\u0026#39; 根据这个进行配置文件进行扩展\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 filebeatConfig: # filebeat.yaml 是必须的，这个是启动filebeat的配置文件 filebeat.yml: | # 这里根据filebeat config的配置说明，直接使用config loading的功能 # https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-configuration-reloading.html inputs: enabled: true path: module.*.yml output.elasticsearch: host: \u0026#39;${NODE_NAME}\u0026#39; module.xxx.yml: | - type: log paths: - /var/log/mysql.log scan_frequency: 10s module.xxx.yaml: | - type: container paths: - \u0026#39;/var/log/containers/*.log\u0026#39; Reference ​[1] Cannot enable Filebeat modules through values.yml\n​[2] Container input\n","permalink":"https://www.161616.top/filebeat/","summary":"filebeat helm chart 在配置 filebeatConfig 想配置成多配置文件模式，但是网上没有找到对应配置，单配置文件模式如下所示\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 filebeatConfig: filebeat.yml: | filebeat.inputs: - type: container paths: - /var/log/containers/*.log processors: - add_kubernetes_metadata: host: ${NODE_NAME} matchers: - logs_path: logs_path: \u0026#34;/var/log/containers/\u0026#34; output.elasticsearch: host: \u0026#39;${NODE_NAME}\u0026#39; hosts: \u0026#39;[\u0026#34;https://${ELASTICSEARCH_HOSTS:elasticsearch-master:9200}\u0026#34;]\u0026#39; username: \u0026#39;${ELASTICSEARCH_USERNAME}\u0026#39; password: \u0026#39;${ELASTICSEARCH_PASSWORD}\u0026#39; protocol: https ssl.certificate_authorities: [\u0026#34;/usr/share/filebeat/certs/ca.crt\u0026#34;] 在网上有找到一个配置 [1] 是讲启用 filebeat module\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 filebeatConfig: filebeat.","title":"filebeat helm chart多配置文件配置"},{"content":"loki的部署模式 grafana loki 在 helm 中版本变化很大，理解部署模式有助于快速 run 一组 loki 服务；loki 服务是一个单体二进制文件运行的微服务架构模式，具体运行的角色通过 -target 参数来区分运行什么组件。\n单体模式 单体模式又称整体模式，源自官网 (Monolithic mode)，这个模式指的是 -target=all 时运行的服务。需要注意的是单体模式下，每天读写量为 20GB.\nMonolithic mode is useful for getting started quickly to experiment with Loki, as well as for small read/write volumes of up to approximately 20GB per day. [1]\n该模式下loki 所有的组件都为二进制文件或者容器作为单一进程运行。\nSSD SSD (Simple Scalable deployment mode) 简单可扩展部署模式，是 loki 对内部组件进行分类，当参数 target 我i -target=write , -target=read, -target=backend 时，是作为 SSD mode 启动。其中 write 和 backend 是有状态服务，read 是无状态服务。\n微服务模式 微服务模式 (Microservices mode) 指的是，参数 target 制定了对应的组件名字，即每个组件作为独立的微服务运行。可以通过 -list-targets 参数来获取 loki 所有组件。\nbash 1 2 docker run docker.io/grafana/loki:2.9.2 \\ -config.file=/etc/loki/local-config.yaml -list-targets 微服务模式官方推荐在大规模 loki 集群时使用。\nMicroservices mode is only recommended for very large Loki clusters or for operators who need more precise control over scaling and cluster operations. [2]\nLoki的存储 Loki存储日志的方式是将日志的元数据信息作为类似 Prometheus 的 key/value 对的格式进行索引，而日志内容不索引进行存储，大致一条信息的组成为：\ntext 1 2 3 4 5 6 2024-12-25T10:01:02.123456789Z {key1=\u0026#34;value\u0026#34;, key2=\u0026#34;value2\u0026#34;} GET /ping |______________________________| |_____________________________| |____________| 时间戳 Prometheus-style label 日志内容 nano second precision Key/Value Paris logline |______________________________________________________________| |____________| Indexed Unindented 在 Loki 2.0 之前版本，loki存储将索引数据和未索引数据分别存储，索引数据包含了相对应的标签，常见为 {app=\u0026quot;api\u0026quot;, env=\u0026quot;production\u0026quot;, filename=\u0026quot;/var/logs/app.log\u0026quot;} 这种起了唯一的标识；对象存储则负责存储和压缩日志。索引提供负责快速查询的标签。\n在 Loki 2.0 后的版本提供了 Single Store，这里存储了包含“索引数据”和“非索引数据”，即只提供了一个存储。\nnote 这里均提到了“对象存储”的概念，这里对象存储和传统理解上的 S3 是有区别的。Loki 中提到的对象存储是 块存储 Chunk storage，包含了 “文件系统” 和 “对象存储”，这里的 “对象存储” 才是传统意义上的 S3 [3] Helm 安装 Loki 2.9 添加 loki 仓库\nbash 1 helm repo add grafana https://grafana.github.io/helm-charts helm的版本差异 loki helm 有很多不同的模式，官方也提供了诸如 SSD 的 helm，这里采用 loki helm 进行安装。在安装时需要区分版本，loki helm 2.x (最高对应 loki 2.6.1 版本)，这里 helm 运行为单一进程的。在 loki helm 3 以上，编默认作为 SSD 模式运行，如果还想运行为“单体模式” 需要自行修改。\n下面时 loki helm 2.16.0 的 helm values.yaml\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 image: repository: grafana/loki tag: 2.6.1 pullPolicy: IfNotPresent ## Optionally specify an array of imagePullSecrets. ## Secrets must be manually created in the namespace. ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ ## # pullSecrets: # - myRegistryKeySecretName ingress: enabled: false # For Kubernetes \u0026gt;= 1.18 you should specify the ingress-controller via the field ingressClassName # See https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/#specifying-the-class-of-an-ingress # ingressClassName: nginx annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: \u0026#34;true\u0026#34; hosts: - host: chart-example.local paths: [] tls: [] # - secretName: chart-example-tls # hosts: # - chart-example.local ## Affinity for pod assignment ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity affinity: {} # podAntiAffinity: # requiredDuringSchedulingIgnoredDuringExecution: # - labelSelector: # matchExpressions: # - key: app # operator: In # values: # - loki # topologyKey: \u0026#34;kubernetes.io/hostname\u0026#34; ## StatefulSet annotations annotations: {} # enable tracing for debug, need install jaeger and specify right jaeger_agent_host tracing: jaegerAgentHost: config: # existingSecret: auth_enabled: false memberlist: join_members: # the value must be defined as string to be evaluated when secret manifest is being generating - \u0026#39;{{ include \u0026#34;loki.fullname\u0026#34; . }}-memberlist\u0026#39; ingester: chunk_idle_period: 3m chunk_block_size: 262144 chunk_retain_period: 1m max_transfer_retries: 0 wal: dir: /data/loki/wal lifecycler: ring: replication_factor: 1 ## Different ring configs can be used. E.g. Consul # ring: # store: consul # replication_factor: 1 # consul: # host: \u0026#34;consul:8500\u0026#34; # prefix: \u0026#34;\u0026#34; # http_client_timeout: \u0026#34;20s\u0026#34; # consistent_reads: true limits_config: enforce_metric_name: false reject_old_samples: true reject_old_samples_max_age: 168h max_entries_limit_per_query: 5000 schema_config: configs: - from: 2020-10-24 store: boltdb-shipper object_store: filesystem schema: v11 index: prefix: index_ period: 24h server: http_listen_port: 3100 grpc_listen_port: 9095 storage_config: boltdb_shipper: active_index_directory: /data/loki/boltdb-shipper-active cache_location: /data/loki/boltdb-shipper-cache cache_ttl: 24h # Can be increased for faster performance over longer query periods, uses more disk space shared_store: filesystem filesystem: directory: /data/loki/chunks chunk_store_config: max_look_back_period: 0s table_manager: retention_deletes_enabled: false retention_period: 0s compactor: working_directory: /data/loki/boltdb-shipper-compactor shared_store: filesystem # Needed for Alerting: https://grafana.com/docs/loki/latest/rules/ # This is just a simple example, for more details: https://grafana.com/docs/loki/latest/configuration/#ruler_config # ruler: # storage: # type: local # local: # directory: /rules # rule_path: /tmp/scratch # alertmanager_url: http://alertmanager.svc.namespace:9093 # ring: # kvstore: # store: inmemory # enable_api: true ## Additional Loki container arguments, e.g. log level (debug, info, warn, error) extraArgs: {} # log.level: debug extraEnvFrom: [] livenessProbe: httpGet: path: /ready port: http-metrics initialDelaySeconds: 45 ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ networkPolicy: enabled: false ## The app name of loki clients client: {} # name: ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ nodeSelector: {} ## ref: https://kubernetes.io/docs/concepts/storage/persistent-volumes/ ## If you set enabled as \u0026#34;True\u0026#34;, you need : ## - create a pv which above 10Gi and has same namespace with loki ## - keep storageClassName same with below setting persistence: enabled: false accessModes: - ReadWriteOnce size: 10Gi labels: {} annotations: {} # selector: # matchLabels: # app.kubernetes.io/name: loki # subPath: \u0026#34;\u0026#34; # existingClaim: # storageClassName: ## Pod Labels podLabels: {} ## Pod Annotations podAnnotations: prometheus.io/scrape: \u0026#34;true\u0026#34; prometheus.io/port: \u0026#34;http-metrics\u0026#34; podManagementPolicy: OrderedReady ## Assign a PriorityClassName to pods if set # priorityClassName: rbac: create: true pspEnabled: true readinessProbe: httpGet: path: /ready port: http-metrics initialDelaySeconds: 45 replicas: 1 resources: {} # limits: # cpu: 200m # memory: 256Mi # requests: # cpu: 100m # memory: 128Mi securityContext: fsGroup: 10001 runAsGroup: 10001 runAsNonRoot: true runAsUser: 10001 containerSecurityContext: readOnlyRootFilesystem: true service: type: ClusterIP nodePort: port: 3100 annotations: {} labels: {} targetPort: http-metrics serviceAccount: create: true name: annotations: {} automountServiceAccountToken: true terminationGracePeriodSeconds: 4800 ## Tolerations for pod assignment ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ tolerations: [] ## Topology spread constraint for multi-zone clusters ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ topologySpreadConstraints: enabled: false # The values to set in the PodDisruptionBudget spec # If not set then a PodDisruptionBudget will not be created podDisruptionBudget: {} # minAvailable: 1 # maxUnavailable: 1 updateStrategy: type: RollingUpdate serviceMonitor: enabled: false interval: \u0026#34;\u0026#34; additionalLabels: {} annotations: {} # scrapeTimeout: 10s # path: /metrics scheme: null tlsConfig: {} prometheusRule: enabled: false additionalLabels: {} # namespace: rules: [] # Some examples from https://awesome-prometheus-alerts.grep.to/rules.html#loki # - alert: LokiProcessTooManyRestarts # expr: changes(process_start_time_seconds{job=~\u0026#34;loki\u0026#34;}[15m]) \u0026gt; 2 # for: 0m # labels: # severity: warning # annotations: # summary: Loki process too many restarts (instance {{ $labels.instance }}) # description: \u0026#34;A loki process had too many restarts (target {{ $labels.instance }})\\n VALUE = {{ $value }}\\n LABELS = {{ $labels }}\u0026#34; # - alert: LokiRequestErrors # expr: 100 * sum(rate(loki_request_duration_seconds_count{status_code=~\u0026#34;5..\u0026#34;}[1m])) by (namespace, job, route) / sum(rate(loki_request_duration_seconds_count[1m])) by (namespace, job, route) \u0026gt; 10 # for: 15m # labels: # severity: critical # annotations: # summary: Loki request errors (instance {{ $labels.instance }}) # description: \u0026#34;The {{ $labels.job }} and {{ $labels.route }} are experiencing errors\\n VALUE = {{ $value }}\\n LABELS = {{ $labels }}\u0026#34; # - alert: LokiRequestPanic # expr: sum(increase(loki_panic_total[10m])) by (namespace, job) \u0026gt; 0 # for: 5m # labels: # severity: critical # annotations: # summary: Loki request panic (instance {{ $labels.instance }}) # description: \u0026#34;The {{ $labels.job }} is experiencing {{ printf \\\u0026#34;%.2f\\\u0026#34; $value }}% increase of panics\\n VALUE = {{ $value }}\\n LABELS = {{ $labels }}\u0026#34; # - alert: LokiRequestLatency # expr: (histogram_quantile(0.99, sum(rate(loki_request_duration_seconds_bucket{route!~\u0026#34;(?i).*tail.*\u0026#34;}[5m])) by (le))) \u0026gt; 1 # for: 5m # labels: # severity: critical # annotations: # summary: Loki request latency (instance {{ $labels.instance }}) # description: \u0026#34;The {{ $labels.job }} {{ $labels.route }} is experiencing {{ printf \\\u0026#34;%.2f\\\u0026#34; $value }}s 99th percentile latency\\n VALUE = {{ $value }}\\n LABELS = {{ $labels }}\u0026#34; initContainers: [] ## Init containers to be added to the loki pod. # - name: my-init-container # image: busybox:latest # command: [\u0026#39;sh\u0026#39;, \u0026#39;-c\u0026#39;, \u0026#39;echo hello\u0026#39;] extraContainers: [] ## Additional containers to be added to the loki pod. # - name: reverse-proxy # image: angelbarrera92/basic-auth-reverse-proxy:dev # args: # - \u0026#34;serve\u0026#34; # - \u0026#34;--upstream=http://localhost:3100\u0026#34; # - \u0026#34;--auth-config=/etc/reverse-proxy-conf/authn.yaml\u0026#34; # ports: # - name: http # containerPort: 11811 # protocol: TCP # volumeMounts: # - name: reverse-proxy-auth-config # mountPath: /etc/reverse-proxy-conf extraVolumes: [] ## Additional volumes to the loki pod. # - name: reverse-proxy-auth-config # secret: # secretName: reverse-proxy-auth-config ## Extra volume mounts that will be added to the loki container extraVolumeMounts: [] extraPorts: [] ## Additional ports to the loki services. Useful to expose extra container ports. # - port: 11811 # protocol: TCP # name: http # targetPort: http # Extra env variables to pass to the loki container env: [] # Specify Loki Alerting rules based on this documentation: https://grafana.com/docs/loki/latest/rules/ # When specified, you also need to add a ruler config section above. An example is shown in the rules docs. alerting_groups: [] # - name: example # rules: # - alert: HighThroughputLogStreams # expr: sum by(container) (rate({job=~\u0026#34;loki-dev/.*\u0026#34;}[1m])) \u0026gt; 1000 # for: 2m useExistingAlertingGroup: enabled: false configmapName: \u0026#34;\u0026#34; 单体模式进行部署 修改 values.yaml, 搜索 singleBinary，修改为 true\ntext 1 2 3 4 5 6 singleBinary: # -- Number of replicas for the single binary replicas: 0 autoscaling: # -- Enable autoscaling enabled: false 注意修改存储模式，在使用到单体的状态下，通常都是进行最小化部署，所以使用 “文件系统” 模式，这里修改文件系统模式\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 # -- Storage config. Providing this will automatically populate all necessary storage configs in the templated config. storage: bucketNames: chunks: chunks ruler: ruler admin: admin # type 这里修改为 filesystem type: filesystem # s3可以是 minio, ceph rgw等 s3: s3: null endpoint: null region: null secretAccessKey: null accessKeyId: null signatureVersion: null s3ForcePathStyle: false insecure: false http_config: {} gcs: chunkBufferSize: 0 requestTimeout: \u0026#34;0s\u0026#34; enableHttp2: true azure: accountName: null accountKey: null useManagedIdentity: false useFederatedToken: false userAssignedId: null requestTimeout: null endpointSuffix: null filesystem: chunks_directory: /var/loki/chunks rules_directory: /var/loki/rules 需要注意的是，使用了 filesystem，对应的你的 k8s 集群需要提供 storageClass，以供应用自动获取 pvc\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 persistence: # -- Enable StatefulSetAutoDeletePVC feature enableStatefulSetAutoDeletePVC: false # -- Size of persistent disk size: 10Gi # -- Storage class to be used. # If defined, storageClassName: \u0026lt;storageClass\u0026gt;. # If set to \u0026#34;-\u0026#34;, storageClassName: \u0026#34;\u0026#34;, which disables dynamic provisioning. # If empty or set to null, no storageClassName spec is # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). storageClass: null # -- Selector for persistent disk selector: null 部署 Loki\nbash 1 2 3 4 5 6 7 ## 部署 Loki helm upgrade --install loki \\ --namespace logging \\ --create-namespace \\ -f values.yaml \\ --version {loki version} \\ grafana/loki SSD模式部署 默认模式下，就是 SSD 模式，\nyaml 1 2 3 4 5 commonConfig: path_prefix: /var/loki # 系数决定了，后面的 read, write, backend 是最少需要多少个实例 replication_factor: 3 compactor_address: \u0026#39;{{ include \u0026#34;loki.compactorAddress\u0026#34; . }}\u0026#39; 而 Ingress 中会根据用户启用了什么模式而分配不同的路由，在文件 grafana/loki/blob/v2.9.2/production/helm/loki/templates/ingress.yaml 中定义\nyaml 1 2 3 4 5 6 7 rules: {{- range $.Values.ingress.hosts }} - host: {{ . | quote }} http: paths: {{- include \u0026#34;loki.ingress.servicePaths\u0026#34; $ | indent 10}} {{- end }} 在文件 grafana/loki/blob/v2.9.2/production/helm/loki/templates/_helpers.tpl 可以看到\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 {{/* Generate list of ingress service paths based on deployment type */}} {{- define \u0026#34;loki.ingress.servicePaths\u0026#34; -}} {{- if (eq (include \u0026#34;loki.deployment.isScalable\u0026#34; .) \u0026#34;true\u0026#34;) -}} {{- include \u0026#34;loki.ingress.scalableServicePaths\u0026#34; . }} {{- else -}} {{- include \u0026#34;loki.ingress.singleBinaryServicePaths\u0026#34; . }} {{- end -}} {{- end -}} {{/* Ingress service paths for scalable deployment */}} {{- define \u0026#34;loki.ingress.scalableServicePaths\u0026#34; -}} {{- include \u0026#34;loki.ingress.servicePath\u0026#34; (dict \u0026#34;ctx\u0026#34; . \u0026#34;svcName\u0026#34; \u0026#34;read\u0026#34; \u0026#34;paths\u0026#34; .Values.ingress.paths.read )}} {{- include \u0026#34;loki.ingress.servicePath\u0026#34; (dict \u0026#34;ctx\u0026#34; . \u0026#34;svcName\u0026#34; \u0026#34;write\u0026#34; \u0026#34;paths\u0026#34; .Values.ingress.paths.write )}} {{- end -}} # 上面传入了 key/value的值，例如 svcName=write, path=.Values.ingress.paths.write # 下面会根据路由选择对应的service # https://github.com/grafana/loki/blob/v2.9.2/production/helm/loki/templates/_helpers.tpl#L471C1-L486C12 {{- range .paths }} - path: {{ . }} {{- if $ingressSupportsPathType }} pathType: Prefix {{- end }} backend: {{- if $ingressApiIsStable }} {{- $serviceName := include \u0026#34;loki.ingress.serviceName\u0026#34; (dict \u0026#34;ctx\u0026#34; $.ctx \u0026#34;svcName\u0026#34; $.svcName) }} service: name: {{ $serviceName }} port: number: 3100 {{- else }} serviceName: {{ $serviceName }} servicePort: 3100 {{- end -}} # isScalable就是 singleBinary 为0，或者使用了对象存储 {{- define \u0026#34;loki.deployment.isScalable\u0026#34; -}} {{- and (eq (include \u0026#34;loki.isUsingObjectStorage\u0026#34; . ) \u0026#34;true\u0026#34;) (eq (int .Values.singleBinary.replicas) 0) }} {{- end -}} {{/* Determine if deployment is using object storage */}} {{- define \u0026#34;loki.isUsingObjectStorage\u0026#34; -}} {{- or (eq .Values.loki.storage.type \u0026#34;gcs\u0026#34;) (eq .Values.loki.storage.type \u0026#34;s3\u0026#34;) (eq .Values.loki.storage.type \u0026#34;azure\u0026#34;) -}} {{- end -}} 最后就是 values.yaml 中定义的路由，根据不同的服务，吧请求路由转发到对应的服务上。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 paths: write: - /api/prom/push - /loki/api/v1/push read: - /api/prom/tail - /loki/api/v1/tail - /loki/api - /api/prom/rules - /loki/api/v1/rules - /prometheus/api/v1/rules - /prometheus/api/v1/alerts singleBinary: - /api/prom/push - /loki/api/v1/push - /api/prom/tail - /loki/api/v1/tail - /loki/api - /api/prom/rules - /loki/api/v1/rules - /prometheus/api/v1/rules - /prometheus/api/v1/alerts 后面就配置你要启用的服务即可，read, write, backend, gateway, loki有维护一个表格，包含了该模式下的组件有哪些，backend可以看到是不属于 read, write 模式。\nComponent individual all read write backend Distributor x x x Ingester x x x Query Frontend x x x Query Scheduler x x x Querier x x x Index Gateway x x Compactor x x x Ruler x x x Bloom Planner (Experimental) x x Bloom Builder (Experimental) x x Bloom Gateway (Experimental) x x 表：loki部署模式中包含的组件 Source：https://grafana.com/docs/loki/latest/get-started/components/\n官方也有提供一些简单的部署模式在文件夹 production/helm/loki/ci\n至此 loki 使用 helm 部署的两种模式就完成了部署，再配合 data ingestion, 例如 Promtail, Filebeat 等就可以完成日志的收集。\nReference ​[1] Loki deployment modes\n​[2] Microservices mode\n​[3] Chunk storage\n​[4] Loki components\n","permalink":"https://www.161616.top/grafana-loki-2-9/","summary":"loki的部署模式 grafana loki 在 helm 中版本变化很大，理解部署模式有助于快速 run 一组 loki 服务；loki 服务是一个单体二进制文件运行的微服务架构模式，具体运行的角色通过 -target 参数来区分运行什么组件。\n单体模式 单体模式又称整体模式，源自官网 (Monolithic mode)，这个模式指的是 -target=all 时运行的服务。需要注意的是单体模式下，每天读写量为 20GB.\nMonolithic mode is useful for getting started quickly to experiment with Loki, as well as for small read/write volumes of up to approximately 20GB per day. [1]\n该模式下loki 所有的组件都为二进制文件或者容器作为单一进程运行。\nSSD SSD (Simple Scalable deployment mode) 简单可扩展部署模式，是 loki 对内部组件进行分类，当参数 target 我i -target=write , -target=read, -target=backend 时，是作为 SSD mode 启动。其中 write 和 backend 是有状态服务，read 是无状态服务。","title":"grafana loki的理解与配置(2.9)"},{"content":"因为 www.sulvblog.cn 域名过期，正好需要查看这个配置，发现搜索引擎已经被删除了特备份一份\n1.逻辑控制 定位到layouts/_default/terms.html，把之前的terms-tags标签控制代码注释掉，换成如下代码\nhtml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 \u0026lt;ul class=\u0026#34;terms-tags\u0026#34;\u0026gt; {{- $type := .Type }} {{- range $key, $value := .Data.Terms.Alphabetical }} {{- $name := .Name }} {{- $count := .Count }} {{- with $.Site.GetPage (printf \u0026#34;/%s/%s\u0026#34; $type $name) }} \u0026lt;li\u0026gt; {{ $largestFontSize := 1.5 }} {{ $smallestFontSize := 1 }} {{ $fontSpread := sub $largestFontSize $smallestFontSize }} {{ $max := add (len (index $.Site.Taxonomies.tags.ByCount 0).Pages) 1 }} {{ $min := len (index $.Site.Taxonomies.tags.ByCount.Reverse 0).Pages }} {{ $spread := sub $max $min }} {{ $fontStep := div $fontSpread $spread }} {{ $weigth := div (sub (math.Log $count) (math.Log $min)) (sub (math.Log $max) (math.Log $min)) }} {{ $currentFontSize := (add $smallestFontSize (mul (sub $largestFontSize $smallestFontSize) $weigth)) }} \u0026lt;a href=\u0026#34;{{ .Permalink }}\u0026#34; style=\u0026#34;font-size: {{ $currentFontSize }}rem; font-weight: {{ mul $currentFontSize 200 }};\u0026#34;\u0026gt; {{ .Name }} \u0026lt;sup\u0026gt;\u0026lt;strong\u0026gt;\u0026lt;sup\u0026gt;{{ $count }}\u0026lt;/sup\u0026gt;\u0026lt;/strong\u0026gt;\u0026lt;/sup\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;/li\u0026gt; {{- end }} {{- end }} \u0026lt;/ul\u0026gt; 2.css修饰 定位到assets/css/extended/blank.css，添加如下代码\ncss 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 /*标签*/ .terms-tags { text-align: center; } .terms-tags a:hover { background: none; webkit-transform: scale(1.2); -moz-transform: scale(1.2); -ms-transform: scale(1.2); -o-transform: scale(1.2); transform: scale(1.3); } .terms-tags a { border-radius: 30px; background: none; transition: transform 0.5s; } .dark .terms-tags a { background: none; } .dark .terms-tags a:hover { background: none; webkit-transform: scale(1.2); -moz-transform: scale(1.2); -ms-transform: scale(1.2); -o-transform: scale(1.2); transform: scale(1.3); } .terms-tags li { margin: 5px; } ","permalink":"https://www.161616.top/hugo-add-tag-cloud/","summary":"因为 www.sulvblog.cn 域名过期，正好需要查看这个配置，发现搜索引擎已经被删除了特备份一份\n1.逻辑控制 定位到layouts/_default/terms.html，把之前的terms-tags标签控制代码注释掉，换成如下代码\nhtml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 \u0026lt;ul class=\u0026#34;terms-tags\u0026#34;\u0026gt; {{- $type := .Type }} {{- range $key, $value := .Data.Terms.Alphabetical }} {{- $name := .Name }} {{- $count := .Count }} {{- with $.Site.GetPage (printf \u0026#34;/%s/%s\u0026#34; $type $name) }} \u0026lt;li\u0026gt; {{ $largestFontSize := 1.5 }} {{ $smallestFontSize := 1 }} {{ $fontSpread := sub $largestFontSize $smallestFontSize }} {{ $max := add (len (index $.","title":"Hugo博客添加标签云"},{"content":"今日在部署旧版本 k8s 集群 (1.16.10) 时出现错误，主要是在新版本操作系统上部署老版本 k8s，kubelet会出现如下错误\nbash 1 2 W1123 22:31:47.383423 3686 server.go:605] failed to get the kubelet\u0026#39;s cgroup: mountpoint for cpu not found. Kubelet system container metrics may be missing. W1123 22:31:47.383572 3686 server.go:612] failed to get the container runtime\u0026#39;s cgroup: failed to get container name for docker process: mountpoint for cpu not found. Runtime system container metrics may be missing. 错误原因 上面的报错是 Kubelet 无法正确访问 Docker 容器运行时的 cgroup 信息，特别是关于 CPU 使用的 cgroup 信息\n从 Linux 内核 4.5 起，cgroup v2 被引入并逐渐取代了 cgroup v1。如果系统启用了 cgroup v2，而 Docker 或 Kubelet 并没有正确配置，可能会导致 Kubelet 无法访问 cgroup 信息。\n查看系统 cgroup 版本\nbash 1 2 $ mount | grep cgroup cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot) 更改Docker和Kubelet的cgroup版本 更改 Docker 的 cgroup 版本\nbash 1 2 3 $ docker info |grep Cgroup Cgroup Driver: systemd Cgroup Version: 2 强制 Docker 使用 cgroup v1\nbash 1 2 3 4 { \u0026#34;exec-opts\u0026#34;: [\u0026#34;native.cgroupdriver=cgroupfs\u0026#34;], \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34; } 查看结果\nbash 1 2 root@debian-template:~# docker info|grep cgroup Cgroup Driver: cgroupfs 配置 kubelet 使用 cgroup v1\n修改 kubelet 参数\nbash 1 --cgroup-driver=cgroupfs 让操作系统挂载 cgroup v1 查看系统 cgroup 版本\nbash 1 2 $ mount | grep cgroup cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot) 手动挂载\n通常手动挂载是不成功的\nbash 1 2 mount: /sys/fs/cgroup: cgroup already mounted or mount point busy. dmesg(1) may have more information after failed mount system call. 需要修改启动参数让操作系统挂载上 cgroup v1\nbash 1 vi /etc/default/grub 在 GRUB_CMDLINE_LINUX_DEFAULT 行中，添加 cgroup_no_v2=1 和 systemd.unified_cgroup_hierarchy=0 选项，确保系统使用 cgroup v1\nbash 1 GRUB_CMDLINE_LINUX_DEFAULT=\u0026#34;quiet cgroup_no_v2=1 systemd.unified_cgroup_hierarchy=0\u0026#34; 更新 GRUB 配置\nbash 1 update-grub 重启后查看挂载信息\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ mount | grep group tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,size=4096k,nr_inodes=1024,mode=755,inode64) cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate) cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd) cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory) cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event) cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma) cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids) cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio) cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer) cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio) cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices) cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct) cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset) cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb) cgroup on /sys/fs/cgroup/misc type cgroup (rw,nosuid,nodev,noexec,relatime,misc) 此时 kubelet 可以正常启动了。\n","permalink":"https://www.161616.top/debian12-install-k8s-1.16/","summary":"今日在部署旧版本 k8s 集群 (1.16.10) 时出现错误，主要是在新版本操作系统上部署老版本 k8s，kubelet会出现如下错误\nbash 1 2 W1123 22:31:47.383423 3686 server.go:605] failed to get the kubelet\u0026#39;s cgroup: mountpoint for cpu not found. Kubelet system container metrics may be missing. W1123 22:31:47.383572 3686 server.go:612] failed to get the container runtime\u0026#39;s cgroup: failed to get container name for docker process: mountpoint for cpu not found. Runtime system container metrics may be missing. 错误原因 上面的报错是 Kubelet 无法正确访问 Docker 容器运行时的 cgroup 信息，特别是关于 CPU 使用的 cgroup 信息","title":"debian12 - 高版本系统安装旧版本k8s异常处理"},{"content":" 本文是关于Kubernetes 4A解析的第5章 深入理解Kubernetes 4A - Authentication源码解析 深入理解Kubernetes 4A - Authorization源码解析 深入理解Kubernetes 4A - Admission Control源码解析 深入理解Kubernetes 4A - Audit源码解析 TLS Everywhere - 解密kubernetes集群的安全认证 所有关于Kubernetes 4A部分代码上传至仓库 github.com/cylonchau/hello-k8s-4A\n在 kubernetes 集群中，所有的通讯都是用 TLS 进行加密和认证，本文使用一次老集群（二进制部署集群）证书更换作为记录，通过这种方式深入对 kubernetes 的认证方式来了解更换证书的步骤，以及一次模拟老集群的更换步骤。\n本文使用证书生成工具为 “kubernetes-generator” [1] 专用于 k8s 二进制部署生成证书和安装包的工具。\nnote 本文中的引用文档使用 web.archive 进行保存，避免官方版本更新，其概念与文档中的概念有变动导致，资料引用失败。 Kubernetes认证方式 为了了解证书更换需要做那些步骤，所以必须了解 k8s 的认证方式，这样才能更好的在更换证书时对集群上部署的业务系统的影响降低到最低。\nX509 证书 kube-apiserver 的启动参数 --client-ca-file，可以使用客户端办法机构，英文代号就是熟悉的 “CA” (Certificate authority)，当这个证书传递后，可以验证 kube-apiserver 用于验证向 kube-apiserver 提供的客户端证书，这里包含 k8s 中提供的用户种类的两种：\n用户名：对应证书的 CN (Common Name) 用户组：对应证书的O (organization)，用于对一个用户组进行授权，例如 “system:masters” 表示一个组 [1]，允许不受限制地访问 kube-apiserver 静态token kube-apiserver 的启动参数 --token-auth-file，是以文件提供给 kube-apiserver，该 Token 会长期有效，并且如果不重启服务 Token 是不会更新。\ntip 通常情况下，应避免使用静态 token 功能。 令牌文件的定义必须符合 [a-z0-9]{6}\\.[a-z0-9]{16} 的格式，以 “.” 分割，第一部分是 “Token ID”； 第二部分是“令牌秘密（Token Secret” ；其后是这个用户的 Group，Group 可以包含多个，如果包含多个，则对应的列必须用双引号括起来。\nbash 1 2 token,user,uid,group token,user,uid,\u0026#34;group1,group2,group3\u0026#34; 例如\nbash 1 123456.1234567890abcdef,dev,1,system:masters note system:masters 除非有必要，否则，应避免向该组分发证书。该组用户无法通过 ClusterRole 和 clusterRoleBinding 来控制权限，即使删除了所有集群角色，或者从来就没有分配过集群角色。该组的用户，仍然可以直接请求 kube-apiserver 执行任何操作。 使用时可以这样传入\nbash 1 2 curl -k -X GET https://\u0026lt;k8s-apiserver\u0026gt;:6443/api/v1/nodes \\ -H \u0026#34;Authorization: Bearer 3c9984.b947d02c14fe0a7f\u0026#34; Bootstrap Tokens Bootstrap Tokens 和 “静态Token”是属于相同类型，并存在于kube-system 名称空间中 Secret, 他的类型是 bootstrap.kubernetes.io/token ，==这种设计是为了能够支持 kubeadm==。 的情况下启动集群。[3]\nBootstrap Tokens 和 静态 token 是相同的格式类型，这里就不多做阐述。\n这里我们看一下我们生成的 kubelet 相关证书与配置文件\nkubelet $ cat kubelet # kubernetes kubelet config # # You can add your configuration own! KUBELET_ARGS=\u0026#34;--v=0 \\ --logtostderr=true \\ --network-plugin=cni \\ --config=/etc/kubernetes/kubelet-config.yaml \\ --kubeconfig=/etc/kubernetes/auth/kubelet.conf \\ --cgroup-driver=cgroupfs \\ --bootstrap-kubeconfig=/etc/kubernetes/auth/bootstrap.conf\u0026#34; 再查看一下 /etc/kubernetes/auth/bootstrap.conf 的配置文件\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 apiVersion: v1 clusters: - cluster: certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNxRENDQVpBQ0UwYlFJV1BEazYwK3dib05ZczJnZzA0bkM1QXdEUVlKS29aSWh2Y05BUUVMQlFBd0VURVAKTUEwR0ExVUVBd3dHYXpoekxXTmhNQjRYRFRJME1URXlNekF4TVRnMU9Gb1hEVEkwTVRFeU5EQXhNVGcxT0ZvdwpFVEVQTUEwR0ExVUVBd3dHYXpoekxXTmhNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDCkFRRUFuZ1hXRzBpK3N0WXpwRjYyMzJZNlpEZGZjOVZIR3M0THRWbU5sT1RSWnVWeGplZ2JySnhnTGVwLzNvalcKYWNDWWJpejZ1WmdTcEZHWmwrV3RKWXZMaURlOU5iampuaTgzWUYyYWY1MDVBT2gvNjMwc1Z5L2pIdWhvVGNQeApkOVc3YW42TlFiYlF0eVhlaVNHMldDdDlnbmlmdlpnN3VPbzdZYUhqc2pMYWtRbUt6WUhhKy9KaGFHR09LOEFtCkVxay9IV3grR3FSMnFpVDBXbndkTXhWdnluRFNwYTBrTGt6dWZsbFNtTktPL3VxbTQ2azBpWWNta1h4TWF2SlEKQjNQTVF5aFdtOFRJRVdGUWcvTWE0ZytMdDdJcjh2dmIwRWlyemtOZVFtRFJyaHN4K1hieHlUdnpBRVE2aE9MMgpvMldNR09CZGpIdVlSc0NzQW9abE1JdFFoUUlEQVFBQk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQTN4NlpWCjJZNjlhTWtKYmJpQnA0RmM3YkRaektadk9xOWp2aUgwTjIvNERId2tPQ3FzenlOSkRkN3hVTUV4NE1BSGp0Vm4KSWVOSit4Sk4xM010eW1lYlhkbEw1b2pDTHArTkZSeEdibXdqM2tSUFBUQ3JZa3NEcGdReGlXVjZ5U2ZXMUNsdAo3UmowOU5jTEpqaU1aMjQyVW9qMzh0dmpEdkw3OFdod0FZb2VSaFBMcHptUndPc0plem9ON2FXZ0FXMDJsM0ZxCm9xVFFDUkJ2SkNDSjhMM09IVmZOYW9vNmI0YzNDbVM2RVdXdThuQmYxNHJVaTJNWCszSGJpbVg2R0w4ZTVnbTYKTXp4Q2VKcGxkbndYeDVRekdPWnpzQ1VQZ2xMRFdhMkxBRzg4V0VFVlRqWkVXbDdZZXVrdTBLV2tVSCs0VjR3WQpsbmFnNFM3ZWw0cFMwRVgrCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K server: https://10.0.0.4:6443 name: kubernetes contexts: - context: cluster: kubernetes user: system:bootstrapper name: system:bootstrapper@kubernetes current-context: system:bootstrapper@kubernetes kind: Config preferences: {} users: - name: system:bootstrapper user: token: 3c9984.b947d02c14fe0a7f 可以用命令去求证 certificate-authority-data 是否与 CA 一致。\nbash 1 openssl x509 -noout -text -in \u0026lt;(grep \u0026#39;certificate-authority-data:\u0026#39; auth/bootstrap.conf | awk \u0026#39;{print $2}\u0026#39;|base64 -d) 使用命令可以查询到这个 bootstrap token 可以操作的权限\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ kubectl --token 3c9984.b947d02c14fe0a7f auth can-i --kubeconfig /dev/null --server=https://localhost:6443 --insecure-skip-tls-verify --list Resources Non-Resource URLs Resource Names Verbs *.* [] [] [*] [*] [] [*] selfsubjectaccessreviews.authorization.k8s.io [] [] [create] selfsubjectrulesreviews.authorization.k8s.io [] [] [create] [/api/*] [] [get] [/api] [] [get] [/apis/*] [] [get] [/apis] [] [get] [/healthz] [] [get] [/healthz] [] [get] [/livez] [] [get] [/livez] [] [get] [/openapi/*] [] [get] [/openapi] [] [get] [/readyz] [] [get] [/readyz] [] [get] [/version/] [] [get] [/version/] [] [get] [/version] [] [get] [/version] [] [get] 因为我们在创建集群时，手动执行过这条命令\nbash 1 kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers 这是我们在证书生成脚本中的定义的用户与组\nbash 1 echo \u0026#34;$BOOTSTRAP_TOKEN,\\\u0026#34;system:bootstrapper\\\u0026#34;,10001,\\\u0026#34;system:bootstrappers\\\u0026#34;\u0026#34; \u0026gt; /tmp/token.csv tip 在使用 bootstrap token 认证时，system:bootstrappers 组会被默认添加 例如 kubeadm 是使用了 controller-manager 中的 CSRApprovingController 自动对 bootstrap token 进行的签发。当接收到 CSR 时，CSRApprovingController 会验证他的 CSR 的合法性。可以从代码中看出。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 func ValidateKubeletClientCSR(req *x509.CertificateRequest, usages sets.String) error { if !reflect.DeepEqual([]string{\u0026#34;system:nodes\u0026#34;}, req.Subject.Organization) { return organizationNotSystemNodesErr } if len(req.DNSNames) \u0026gt; 0 { return dnsSANNotAllowedErr } if len(req.EmailAddresses) \u0026gt; 0 { return emailSANNotAllowedErr } if len(req.IPAddresses) \u0026gt; 0 { return ipSANNotAllowedErr } if len(req.URIs) \u0026gt; 0 { return uriSANNotAllowedErr } if !strings.HasPrefix(req.Subject.CommonName, \u0026#34;system:node:\u0026#34;) { return commonNameNotSystemNode } if !kubeletClientRequiredUsages.Equal(usages) \u0026amp;\u0026amp; !kubeletClientRequiredUsagesNoRSA.Equal(usages) { return fmt.Errorf(\u0026#34;usages did not match %v\u0026#34;, kubeletClientRequiredUsages.List()) } return nil } 上述代码中表明了 CSR 的签发需满足的条件：\nSAN Email, IP, URI 必须不提供。 证书的 Organization 字段必须为 system:nodes，例如: O = system:nodes 证书的 commonName 字段必须为 sytem:node: 开头，通常格式定义为 sytem:node:hostname，例如: CN = system:node:debian-demo。 证书 Usage 必须配置 client auth 和 digital signature， 这部分在生成脚本中有配置。 这里做一个小实验，创建一个 匹配上述条件的 CSR，并使用 CertificateSigningRequest 去申请一个证书。\n使用 cfssl 工具准备 CSR\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ cat \u0026lt;\u0026lt; EOF | cfssl genkey - | cfssljson -bare demo { \u0026#34;CN\u0026#34;: \u0026#34;system:node:bootstap-demo\u0026#34;, \u0026#34;key\u0026#34;: { \u0026#34;algo\u0026#34;: \u0026#34;rsa\u0026#34;, \u0026#34;size\u0026#34;: 2048 }, \u0026#34;names\u0026#34;: [ { \u0026#34;O\u0026#34;: \u0026#34;system:nodes\u0026#34; } ] } EOF 使用 openssl 工具准备 CSR\nbash 1 2 3 4 openssl req -new -newkey rsa:2048 -nodes \\ -keyout demo-key.pem \\ -out demo.csr \\ -subj \u0026#34;/CN=system:node:bootstap-demo/O=system:nodes\u0026#34; 查看 demo.csr 的详情\nbash 1 2 3 4 5 6 7 8 9 $ openssl req -noout -text -in demo.csr Certificate Request: Data: Version: 1 (0x0) Subject: CN = system:node:bootstap-demo, O = system:nodes Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 使用 bootstrap token吧 CSR 发送给 apiserver 去申请证书, 需要注意的是 csr 资源的版本 旧版本certificates.k8s.io/v1beta1； certificates.k8s.io/v1 v1.19 。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 $ cat \u0026lt;\u0026lt; EOF | kubectl --token 3c9984.b947d02c14fe0a7f --insecure-skip-tls-verify --server=https://localhost:6443 apply -f - apiVersion: certificates.k8s.io/v1beta1 kind: CertificateSigningRequest metadata: name: demo-bootstrap-token-csr spec: signerName: kubernetes.io/kube-apiserver-client request: $(openssl req -new -newkey rsa:2048 -nodes -keyout demo-key.pem -subj \u0026#34;/CN=system:node:bootstap-demo/O=system:nodes\u0026#34; 2\u0026gt;/dev/null| base64 | tr -d \u0026#39;\\n\u0026#39;) usages: - digital signature - key encipherment - client auth EOF 在新版本中（v1.19）kube-controller-manager 中的它会自动审批 signerName 为 kubernetes.io/kube-apiserver-client-kubelet 的 CSR [4]。\n而旧版本的 CertificateSigningRequest 资源不包含 spec.CertificateSigningRequest\nbash 1 2 3 4 5 6 7 8 9 10 11 12 $ cat \u0026lt;\u0026lt; EOF | kubectl --token 3c9984.b947d02c14fe0a7f --insecure-skip-tls-verify --server=https://localhost:6443 apply -f - apiVersion: certificates.k8s.io/v1beta1 kind: CertificateSigningRequest metadata: name: demo-bootstrap-token-csr spec: request: $(openssl req -new -newkey rsa:2048 -nodes -keyout demo-key.pem -subj \u0026#34;/CN=system:node:bootstap-demo/O=system:nodes\u0026#34; 2\u0026gt;/dev/null| base64 | tr -d \u0026#39;\\n\u0026#39;) usages: - digital signature - key encipherment - client auth EOF 查看\nbash 1 2 $ kubectl get csr demo-bootstrap-token-csr 5s system:bootstrapper Pending 可以使用下面命令查看被签发的证书\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 $ openssl x509 -noout -text -in \u0026lt;(kubectl get csr demo-bootstrap-token-csr -o jsonpath=\u0026#39;{.status.certificate}\u0026#39; | base64 -d) Certificate: Data: Version: 3 (0x2) Serial Number: 32:f8:19:58:a5:4c:d0:24:82:6a:09:c4:0b:8a:2d:6c:7a:26:f3:7d Signature Algorithm: sha256WithRSAEncryption Issuer: CN = k8s-ca Validity Not Before: Nov 23 22:59:00 2024 GMT Not After : Nov 24 01:14:01 2024 GMT Subject: O = system:nodes, CN = system:node:bootstap-demo Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:97:ec:7c:55:f5:e4:1b:68:55:a1:74:28:2f:6d: 27:14:72:35:97:dd:b6:a2:ea:e4:29:8e:59:ca:0c: fe:2b:9e:a8:5f:25:b8:b3:d7:3b:b7:26:b5:3a:27: 8f:8d:cc:b4:46:fc:88:b9:0f:55:6f:d1:ca:20:15: 16:2d:58:aa:1f:c9:46:01:b3:a7:10:45:39:5d:fc: 96:c6:ae:59:e0:a5:f0:61:13:f6:49:69:f6:fc:46: 56:5f:db:ac:3b:3f:c8:70:fb:bb:d8:4d:3c:c4:e8: d3:43:12:0f:c8:a2:db:af:62:91:5e:37:2d:ce:5a: 04:a6:d9:66:6c:37:2a:b0:d0:78:2e:a7:02:87:f6: 78:ae:7a:b7:7e:52:19:d1:24:7e:94:c4:b2:ee:32: e7:c3:90:b7:b3:fc:b8:5b:c8:44:af:5d:72:de:81: b8:b5:86:a4:35:80:8f:97:36:77:bb:16:8d:5a:f2: ef:b7:26:67:03:c7:2f:c7:f4:b1:6e:47:fa:a7:ad: 2c:f1:45:e6:3e:19:38:a1:81:52:20:5e:42:85:1c: 1a:30:c6:7b:04:c0:b6:e2:13:82:0b:d8:76:8a:a8: c4:12:9a:66:1e:8a:e6:5f:a4:f2:7a:d9:28:af:d5: 8a:37:89:26:b8:b3:38:98:6d:a9:33:e6:54:3d:87: 8a:81 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Client Authentication X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Key Identifier: B2:15:FC:C5:FA:2A:53:1A:49:B7:B3:51:32:E4:29:29:2C:76:1E:10 kubelet 配置 bootstrap token\n如果使用 bootstrap token 需要启用对应的配置，需要具备下列条件：\nkube-apiserver 相关配置\nkube-apiserver 启用参数 --enable-bootstrap-token-auth=true kubelet 相关配置\n一个用来存储所生成的密钥和证书的路径\n一个用来指向尚不存在的 kubeconfig 文件的路径；kubelet 会将 bootstrap token 配置文件放到这个位置。\n一个指向 bootstrap token 的 kubeconfig 文件的路径，用来提供 API 服务器的 URL 和启动 bootstrap token。\ntip 在启动 kubelet 时，如果参数 \u0026ndash;kubeconfig 所指定的文件并不存在，会使用通过参数 \u0026ndash;bootstrap-kubeconfig 所指定的启动引导 kubeconfig 配置来向 apiserver 请求客户端证书。 在证书请求被批复并被 kubelet 收回时，一个引用所生成的密钥和所获得证书的 kubeconfig 文件会被写入到通过 \u0026ndash;kubeconfig 所指定的文件路径下。 证书和密钥文件会被放到 \u0026ndash;cert-dir 所指定的目录中 [5]。 其他认证方式 其他认证方式指的是 serviceaccount, openid, webhook token等方式，因为这些不属于 kubernetes 集群中的基础认证方式，而是属于扩展认证方式，故这里不会在提及。\n实验 - 证书更换 构建 kubernetes 1.16.10 实验环境 实验环境使用 kubernetes 1.16.10 来模拟常见的老旧集群（通过二进制方式进行部署）证书过期进行更换的问题。通过上面了解到的“基础认证”方式可以快速做到对集群进行证书更换的问题。\n生成证书列表如下\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ├── apiserver-etcd.crt # kube-apiserver访问etcd使用的客户端证书 ├── apiserver-etcd.key # kube-apiserver访问etcd使用的客户端证书 ├── apiserver-kubelet-client.crt # kube-apiserver访问kubelet使用的证书 ├── apiserver-kubelet-client.key # kube-apiserver访问kubelet使用的证书 ├── ca.crt # k8s 集群使用的ca ├── ca.key # k8s 集群使用的ca ├── front-proxy-ca.crt # kube-aggregation使用ca是独立的一套 ├── front-proxy-ca.key # kube-aggregation使用ca是独立的一套 ├── front-proxy-client.crt # kube-aggregation使用 ├── front-proxy-client.key # kube-aggregation使用 ├── kube-apiserver.crt # kube-apiserver使用 ├── kube-apiserver.key # kube-apiserver使用 ├── kube-controller-manager.crt # kube-controller-manager使用 ├── kube-controller-manager.key # kube-controller-manager使用 ├── kube-proxy.crt # kube-proxy使用 ├── kube-proxy.key # kube-proxy使用 ├── kube-scheduler.crt # kube-scheduler使用 ├── kube-scheduler.key # kube-scheduler使用 ├── sa.key # serviceaccount 使用 └── sa.pub # serviceaccount 使用 etcd 证书更换 etcd 证书更换部分主要围绕 etcd 所使用的证书进行规划，例如：\nca server peer client 这里使用脚本套件会根据 kubernetes 官方给的一个 kubernetes 集群所需要的一套证书的标准进行生成。手动更换即可。更换完成后检查集群状态。\ntip 注意 etcd API 版本，3.5可以直接执行，3.5 之下要使用 V3。 bash 1 2 3 4 5 etcdctl --key=/etc/kubernetes/ssl/etcd-cluster-key.pem\\ --cert=/etc/kubernetes/ssl/etcd-cluster.pem \\ --cacert=/etc/kubernetes/ssl/ca.pem \\ --endpoints=\u0026#34;https://10.0.0.221:2379\u0026#34; \\ endpoint health 遇到的问题 text 1 2 3 etcd: {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2024-11-24T12:00:18.546+0800\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:465\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;starting with peer TLS\u0026#34;,\u0026#34;tls-info\u0026#34;:\u0026#34;cert = /etc/etcd/ssl/etcd-cluster.pem, key = /etc/etcd/ssl/etcd-cluster-key.pem, trusted-ca = /etc/etcd/ssl/ca.pem, client-cert-auth = true, crl-file = \u0026#34;,\u0026#34;cipher-suites\u0026#34;:[]} etcd: {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2024-11-24T12:00:18.546+0800\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/etcd.go:360\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;closing etcd server\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;cert-expired-1\u0026#34;,\u0026#34;data-dir\u0026#34;:\u0026#34;/var/lib/etcd/{etcd-cluster-name}\u0026#34;,\u0026#34;advertise-peer-urls\u0026#34;:[\u0026#34;https://10.0.0.138:2380\u0026#34;],\u0026#34;advertise-client-urls\u0026#34;:[\u0026#34;https://10.0.0.138:2379\u0026#34;]} 启动后立马停止，这里检查 etcd 证书是否正确。\nkube-apiserver 证书更换 kube-apiserver 所使用的配置参数\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 KUBE_APISERVER_ARGS=\u0026#34;\\ --advertise-address=10.0.0.221 \\ --allow-privileged=true \\ --anonymous-auth=false \\ --apiserver-count=3 \\ --audit-log-maxage=7 \\ --audit-log-maxbackup=10 \\ --audit-log-maxsize=100 \\ --audit-policy-file=/etc/kubernetes/config/audit-policy.yaml \\ --audit-log-path=/var/log/kubernetes/audit/kubernetes-audit.log \\ --authorization-mode=Node,RBAC \\ --bind-address=10.0.0.221 \\ --client-ca-file=/etc/kubernetes/ssl/ca.pem \\ --enable-aggregator-routing=true \\ --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeClaimResize,TaintNodesByCondition,PodNodeSelector,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,NodeRestriction,Priority,ResourceQuota,PodSecurityPolicy \\ --enable-swagger-ui=true \\ --etcd-cafile=/etc/etcd/ssl/ca.pem \\ --etcd-certfile=/etc/kubernetes/ssl/etcd-cluster.pem \\ --etcd-keyfile=/etc/kubernetes/ssl/etcd-cluster-key.pem \\ --etcd-servers=https://10.0.0.221:2379,https://10.0.0.138:2379,https://10.0.0.131:2379 \\ --event-ttl=1h \\ --feature-gates=ExpandInUsePersistentVolumes=true,ExpandPersistentVolumes=true,RotateKubeletServerCertificate=true \\ --encryption-provider-config=/etc/kubernetes/pki/encryption-config.yaml \\ --insecure-port=0 \\ --kubelet-certificate-authority=/etc/kubernetes/ssl/ca.pem \\ --kubelet-client-certificate=/etc/kubernetes/ssl/admin.pem \\ --kubelet-client-key=/etc/kubernetes/ssl/admin-key.pem \\ --kubelet-https=true \\ --requestheader-client-ca-filel=/etc/kubernetes/ssl/ca.pem \\ --requestheader-allowed-names=aggregator \\ --requestheader-extra-headers-prefix=X-Remote-Extra- \\ --requestheader-group-headers=X-Remote-Group \\ --requestheader-username-headers=X-Remote-User \\ --profiling=false \\ --proxy-client-cert-file=/etc/kubernetes/ssl/kube-aggregration.pem \\ --proxy-client-key-file=/etc/kubernetes/ssl/kube-aggregration-key.pem \\ --runtime-config=api/all \\ --secure-port=6443 \\ --service-account-lookup=true \\ --service-account-key-file=/etc/kubernetes/ssl/service-account.pem \\ --service-cluster-ip-range=10.191.196.0/24 \\ --service-node-port-range=30000-52767 \\ --tls-cert-file=/etc/kubernetes/ssl/kube-apiserver.pem \\ --tls-min-version=VersionTLS12 \\ --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256 \\ --tls-private-key-file=/etc/kubernetes/ssl/kube-apiserver-key.pem\u0026#34; 注意需要替换的证书部分\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ## etcd 部分 # etcd 的ca, 套件生成的 etcd 和 k8s 使用的是不同 ca --etcd-cafile=/etc/etcd/ssl/ca.pem \\ # etcd 客户端证书的 key 和 crt --etcd-certfile=/etc/kubernetes/ssl/etcd-cluster.pem \\ --etcd-keyfile=/etc/kubernetes/ssl/etcd-cluster-key.pem \\ ## kube-apiserver 和 kubelet 通讯部分 # k8s 集群中使用的ca --kubelet-certificate-authority=/etc/kubernetes/ssl/ca.pem \\ # kube-apiserver 请求到 kubelet 使用的证书 # 例如在 kubectl logs / kubectl exec 时，kube-apiserver会直接请求 kubelet 这里需要证书正确 --kubelet-client-certificate=/etc/kubernetes/ssl/admin.pem \\ --kubelet-client-key=/etc/kubernetes/ssl/admin-key.pem \\ ## front-proxy部分，这部分主要适用于 kube-apiserver 与外部 apiserver通讯使用 --proxy-client-cert-file=/etc/kubernetes/ssl/kube-aggregration.pem \\ --proxy-client-key-file=/etc/kubernetes/ssl/kube-aggregration-key.pem \\ # serviceaccount --service-account-key-file=/etc/kubernetes/ssl/service-account.pem \\ 遇到的问题 text 1 2 3 clientconn.go:1120] grpc: addrConn.createTransport failed to connect to {https://10.0.0.221:2379 0 \u0026lt;nil\u0026gt;}. Err :connection error: desc = \u0026#34;transport: authentication handshake failed: x509: certificate signed by unknown authority (possibly because of \\\u0026#34;crypto/rsa: verification error\\\u0026#34; while trying to verify candidate authority certificate \\\u0026#34;Kubernetes\\\u0026#34;)\u0026#34;. Reconnecting... clientconn.go:1120] grpc: addrConn.createTransport failed to connect to {https://10.0.0.221:2379 0 \u0026lt;nil\u0026gt;}. Err :connection error: desc = \u0026#34;transport: authentication handshake failed: x509: certificate signed by unknown authority (possibly because of \\\u0026#34;crypto/rsa: verification error\\\u0026#34; while trying to verify candidate authority certificate \\\u0026#34;Kubernetes\\\u0026#34;)\u0026#34;. Reconnecting... 报错提示握手认证失败，手动使用 cert 和 key 访问是正常\nbash 1 2 $ etcdctl --key=/etc/kubernetes/ssl/etcd-cluster-key.pem --cert=/etc/kubernetes/ssl/etcd-cluster.pem --cacert=/etc/etcd/ssl/ca.pem --endpoints=\u0026#34;https://10.0.0.221:2379\u0026#34; endpoint health https://10.0.0.221:2379 is healthy: successfully committed proposal: took = 11.661676ms 原因：注意 etcd ca 是否正确。要确认下面的均为正确配置才可以\nbash 1 2 3 --etcd-cafile=/etc/etcd/ssl/ca.pem \\ --etcd-certfile=/etc/kubernetes/ssl/etcd-cluster.pem \\ --etcd-keyfile=/etc/kubernetes/ssl/etcd-cluster-key.pem \\ 无法查看 pod 日志 You must be logged in to the server\nbash 1 error: You must be logged in to the server (the server has asked for the client to provide credentials ( pods/log xxx-xxx-xxx)) 这个就是上面提到的，要保证 kube-apiserver 和 kubelet 使用的证书是否一致\nbash 1 2 --kubelet-client-certificate --kubelet-client-key kube-controller-manager 证书更换 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 KUBE_CONTROLLER_MANAGER_ARGS=\u0026#34;\\ --allocate-node-cidrs=false \\ --bind-address=10.0.0.221 \\ --cluster-cidr=10.96.192.0/22 \\ --authorization-always-allow-paths=/healthz,/metrics \\ --cluster-name=kubernetes \\ --cluster-signing-cert-file=/etc/kubernetes/ssl/ca.pem \\ --cluster-signing-key-file=/etc/kubernetes/ssl/ca-key.pem \\ --controllers=*,bootstrapsigner,tokencleaner \\ --feature-gates=ExpandInUsePersistentVolumes=true,ExpandPersistentVolumes=true,RotateKubeletServerCertificate=true \\ --kubeconfig=/etc/kubernetes/config/kube-controller-manager.kubeconfig \\ --leader-elect=true \\ --profiling=false \\ --port=0 \\ --node-monitor-grace-period=40s \\ --node-monitor-period=5s \\ --pod-eviction-timeout=2m \\ --root-ca-file=/etc/kubernetes/ssl/ca.pem \\ --service-account-private-key-file=/etc/kubernetes/ssl/service-account-key.pem \\ --service-cluster-ip-range=10.96.0.0/24 \\ --secure-port=10257 \\ --terminated-pod-gc-threshold=250 \\ --tls-cert-file=/etc/kubernetes/ssl/kube-controller-manager.pem \\ --tls-min-version=VersionTLS12 \\ --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256 \\ --tls-private-key-file=/etc/kubernetes/ssl/kube-controller-manager-key.pem \\ --use-service-account-credentials=true\u0026#34; 除了上面提到的，ca 外，只需要替换 serviceaccount 所需的 key 即可\nbash 1 2 3 4 --cluster-signing-cert-file=/etc/kubernetes/ssl/ca.pem \\ --cluster-signing-key-file=/etc/kubernetes/ssl/ca-key.pem \\ # 这个参数和 kube-apiserver 的是一对 --tls-cert-file=/etc/kubernetes/ssl/kube-controller-manager.pem \\ kube-scheduler 证书更换 bash 1 2 3 4 5 6 7 8 9 10 11 12 KUBE_SCHEDULER_ARGS=\u0026#34;\\ --bind-address=10.0.0.221 \\ --config=/etc/kubernetes/config/kube-scheduler.yaml \\ --profiling=false \\ --client-ca-file=/etc/kubernetes/ssl/ca.pem \\ --port=0 \\ --secure-port=10259 \\ --feature-gates=ExpandInUsePersistentVolumes=true,ExpandPersistentVolumes=true,RotateKubeletServerCertificate=true \\ --tls-cert-file=/etc/kubernetes/ssl/kube-scheduler.pem \\ --tls-min-version=VersionTLS12 \\ --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256 \\ --tls-private-key-file=/etc/kubernetes/ssl/kube-scheduler-key.pem\u0026#34; tip 需要注意的是，除了 kube-apiserver 服务外，其他的组件需要手动更换 kubeconfig 文件。 工作节点证书更换 如果节点使用 bootstrap 证书进行部署的，通常证书不正确 kubelet 会从新自动申请 csr 获取新证书，这部分根据 kubernetes 集群版本不同需要手动进行批准 (kubectl certificate approve)。\n另外一种方式就是，手动重新给每一个节点的 kubelet 重新签发证书，并且替换每一个节点的 kube-proxy 的 kubeconfig 文件 (==这里使用的套件是 bootstrap token==)。生成证书的要求按照 bootstrap token 部分介绍的：\nSAN Email, IP, URI 必须不提供。 证书的 Organization 字段必须为 system:nodes，例如: O = system:nodes 证书的 commonName 字段必须为 sytem:node: 开头，通常格式定义为 sytem:node:hostname，例如: CN = system:node:debian-demo。 证书 Usage 必须配置 client auth 和 digital signature， 这部分在生成脚本中有配置。 更换cni使用证书 例如 calico 使用了etcd时，etcd为外部etcd时，这里的证书也要替换，否则 calico-controller 会无法连接etcd，下图是对应的日志。\n图：calico-controller无法连接etcd 更换serviceaccount 在 kubernetes 官方的 “手动轮换 CA 证书” [6] 一文中有提到，在更新了 CA 之后，集群内 Pod 需要重启以获得新的 Secret 数据。下图是当 Pod 没有重启时，在启动新 Pod 的报错\n图：network: Unauthorized network: Unauthorized 原因如下：\ncni node 没有启动 旧的 default secret 还是旧的，没办法请求到 apiserver 去创建网络空间 当更换完成证书后，检查 node cni 服务全部没问题，但是新建的Pod无法创建网络，提示 network: Unauthorized；重建了所有的 secret后重启 Pod 就恢复了。这时 kubelet 出现日志如下：\ntext kubelet报错日志1 1 2 3 4 5 6 W0113 12:11:06.526394 11195 docker_sandbox.go:394] failed to read pod IP from plugin/docker: networkPlugin cni failed on the status hook for pod \u0026#34;a01-im-web-5f6f9b4459-p6gnt_a01\u0026#34;: CNI failed to retrieve network namespace path: cannot find network namespace for the terminated container \u0026#34;c8145349eebe397d4c7f30cb326f20338b668b67ebdfddf5e51be14992a18894\u0026#34; W0113 12:11:06.528734 11195 docker_sandbox.go:394] failed to read pod IP from plugin/docker: networkPlugin cni failed on the status hook for pod \u0026#34;a01-im-web-5f6f9b4459-p6gnt_a01\u0026#34;: CNI failed to retrieve network namespace path: cannot find network namespace for the terminated container \u0026#34;1823c082be12441a1df17875eb9cc9efdb19d7b37d71653c5182b23b7fcab694\u0026#34; W0113 12:11:06.532607 11195 docker_sandbox.go:394] failed to read pod IP from plugin/docker: networkPlugin cni failed on the status hook for pod \u0026#34;a01-im-web-5f6f9b4459-p6gnt_a01\u0026#34;: CNI failed to retrieve network namespace path: cannot find network namespace for the terminated container \u0026#34;1bae78ef6fa9dbb48002984d96b2d6222ddc14caf2e9bf04a9abb8fce7ad4a18\u0026#34; W0113 12:11:06.534956 11195 docker_sandbox.go:394] failed to read pod IP from plugin/docker: networkPlugin cni failed on the status hook for pod \u0026#34;a01-im-web-5f6f9b4459-p6gnt_a01\u0026#34;: CNI failed to retrieve network namespace path: cannot find network namespace for the terminated container \u0026#34;528754d0e7ca862dc6723a573c032f53326d53aff3e768694a11b01090cdf7d1\u0026#34; W0113 12:11:06.537408 11195 docker_sandbox.go:394] failed to read pod IP from plugin/docker: networkPlugin cni failed on the status hook for pod \u0026#34;a01-im-web-5f6f9b4459-p6gnt_a01\u0026#34;: CNI failed to retrieve network namespace path: cannot find network namespace for the terminated container \u0026#34;39e65a5a7250bd14305412a041fce9873f9dee88ecfc85b9b8c21c5db472a353\u0026#34; W0113 12:11:06.539590 11195 docker_sandbox.go:394] failed to read pod IP from plugin/docker: networkPlugin cni failed on the status hook for pod \u0026#34;a01-im-web-5f6f9b4459-p6gnt_a01\u0026#34;: CNI failed to retrieve network namespace path: cannot find network namespace for the terminated container \u0026#34;d52e93aa1163041e20ab0b5e8b200f14a70581252c2424c943d53478e8656a79\u0026#34; text kubelet报错日志2 1 2 3 4 5 6 7 8 9 10 11 12 6300 kubelet_volumes.go:154] orphaned pod \u0026#34;a3f45f45-b6e5-4fb8-9d32-d691f966173a\u0026#34; found, but volume subpaths are still present on disk : There were a total of 1 errors similar to this. Turn up verbosity to see them. 6300 cni.go:364] Error adding push-web-api-6bcc765c4-8r242/0ed92e1b512ecc25414e0540a3bc06060cf4d4ea09ebd0fb9dbe20f1c9e0389e to network calico/k8s-pod-network: Unauthorized 6300 dns.go:135] Nameserver limits were exceeded, some nameservers have been omitted, the applied nameserver line is: 10.190.16.41 10.190.16.42 10.240.16.41 6300 remote_runtime.go:105] RunPodSandbox from runtime service failed: rpc error: code = Unknown desc = failed to set up sandbox container \u0026#34;0ed92e1b512ecc25414e0540a3bc06060cf4d4ea09ebd0fb9dbe20f1c9e0389e\u0026#34; network for pod \u0026#34;push-web-api-6bcc765c4-8r242\u0026#34;: networkPlugin cni failed to set up pod \u0026#34;push-web-api-6bcc765c4-8r242_prd_user_32as1\u0026#34; network: Unauthorized 6300 kuberuntime_sandbox.go:68] CreatePodSandbox for pod \u0026#34;push-web-api-6bcc765c4-8r242_prd_user_32as1(c87c4346-def7-4326-9406-48de0c641a94)\u0026#34; failed: rpc error: code = Unknown desc = failed to set up sandbox container \u0026#34;0ed92e1b512ecc25414e0540a3bc06060cf4d4ea09ebd0fb9dbe20f1c9e0389e\u0026#34; network for pod \u0026#34;push-web-api-6bcc765c4-8r242\u0026#34;: networkPlugin cni failed to set up pod \u0026#34;push-web-api-6bcc765c4-8r242_prd_user_32as1\u0026#34; network: Unauthorized ./kubelet.localhost.root.log.ERROR.20250113-171637.6300:E0113 17:32:27.121142 6300 kuberuntime_manager.go:710] createPodSandbox for pod \u0026#34;push-web-api-6bcc765c4-8r242_prd_user_32as1(c87c4346-def7-4326-9406-48de0c641a94)\u0026#34; failed: rpc error: code = Unknown desc = failed to set up sandbox container \u0026#34;0ed92e1b512ecc25414e0540a3bc06060cf4d4ea09ebd0fb9dbe20f1c9e0389e\u0026#34; network for pod \u0026#34;push-web-api-6bcc765c4-8r242\u0026#34;: networkPlugin cni failed to set up pod \u0026#34;push-web-api-6bcc765c4-8r242_prd_user_32as1\u0026#34; network: Unauthorized 6300 pod_workers.go:191] Error syncing pod c87c4346-def7-4326-9406-48de0c641a94 (\u0026#34;push-web-api-6bcc765c4-8r242_prd_user_32as1(c87c4346-def7-4326-9406-48de0c641a94)\u0026#34;), skipping: failed to \u0026#34;CreatePodSandbox\u0026#34; for \u0026#34;push-web-api-6bcc765c4-8r242_prd_user_32as1(c87c4346-def7-4326-9406-48de0c641a94)\u0026#34; with CreatePodSandboxError: \u0026#34;CreatePodSandbox for pod \\\u0026#34;push-web-api-6bcc765c4-8r242_prd_user_32as1(c87c4346-def7-4326-9406-48de0c641a94)\\\u0026#34; failed: rpc error: code = Unknown desc = failed to set up sandbox container \\\u0026#34;0ed92e1b512ecc25414e0540a3bc06060cf4d4ea09ebd0fb9dbe20f1c9e0389e\\\u0026#34; network for pod \\\u0026#34;push-web-api-6bcc765c4-8r242\\\u0026#34;: networkPlugin cni failed to set up pod \\\u0026#34;push-web-api-6bcc765c4-8r242_prd_user_32as1\\\u0026#34; network: Unauthorized\u0026#34; 6300 cni.go:385] Error deleting logging_filebeat-filebeat-68525/131e91d83d51925a5298be7c21f739c5341db9ecde26bf58509c60278caae46e from network calico/k8s-pod-network: context deadline exceeded 6300 remote_runtime.go:128] StopPodSandbox \u0026#34;131e91d83d51925a5298be7c21f739c5341db9ecde26bf58509c60278caae46e\u0026#34; from runtime service failed: rpc error: code = Unknown desc = networkPlugin cni failed to teardown pod \u0026#34;filebeat-filebeat-68525_logging\u0026#34; network: context deadline exceeded 6300 kuberuntime_manager.go:878] Failed to stop sandbox {\u0026#34;docker\u0026#34; \u0026#34;131e91d83d51925a5298be7c21f739c5341db9ecde26bf58509c60278caae46e\u0026#34;} 6300 cni.go:385] Error deleting public_public-gameinterface-office-74fc5bdcc-sfp5d/8a049c280b256e6afc5832b2f77d1e90b7b0ae8590e0403b5efedb391c4a1a7f from network calico/k8s-pod-network: context deadline exceeded 6300 remote_runtime.go:128] StopPodSandbox \u0026#34;8a049c280b256e6afc5832b2f77d1e90b7b0ae8590e0403b5efedb391c4a1a7f\u0026#34; from runtime service failed: rpc error: code = Unknown desc = networkPlugin cni failed to teardown pod \u0026#34;public-gameinterface-office-74fc5bdcc-sfp5d_public\u0026#34; network: context deadline exceeded 遇到的问题 tls: bad certificate 问题原因，更新完成证书后，Pod 中 CA 会更新，但是 secret token 不会自动更新，容器内请求 API 使用 serviceaccount token，当更换 CA 后必须重启 Pod 才能重新在 Pod 内建立新的 token。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 I0114 11:25:25.925418 1063 log.go:172] http: TLS handshake error from 10.190.15.40:3418: remote error: tls: bad certificate I0114 11:25:25.931262 1063 log.go:172] http: TLS handshake error from 10.190.15.40:6413: remote error: tls: bad certificate I0114 11:25:25.931643 1063 log.go:172] http: TLS handshake error from 10.190.15.40:61463: remote error: tls: bad certificate I0114 11:25:25.937855 1063 log.go:172] http: TLS handshake error from 10.190.15.40:20758: remote error: tls: bad certificate I0114 11:25:25.939317 1063 log.go:172] http: TLS handshake error from 10.190.15.40:49564: remote error: tls: bad certificate I0114 11:25:25.945868 1063 log.go:172] http: TLS handshake error from 10.190.15.40:54670: remote error: tls: bad certificate I0114 11:25:25.945937 1063 log.go:172] http: TLS handshake error from 10.190.15.40:10809: remote error: tls: bad certificate I0114 11:25:25.954650 1063 log.go:172] http: TLS handshake error from 10.190.15.40:19277: remote error: tls: bad certificate I0114 11:25:25.955125 1063 log.go:172] http: TLS handshake error from 10.190.15.40:12765: remote error: tls: bad certificate I0114 11:25:25.963410 1063 log.go:172] http: TLS handshake error from 10.190.15.40:61484: remote error: tls: bad certificate I0114 11:25:25.963924 1063 log.go:172] http: TLS handshake error from 10.190.15.40:21905: remote error: tls: bad certificate I0114 11:25:25.972193 1063 log.go:172] http: TLS handshake error from 10.190.15.40:47752: remote error: tls: bad certificate I0114 11:25:25.972608 1063 log.go:172] http: TLS handshake error from 10.190.15.40:12960: remote error: tls: bad certificate I0114 11:25:25.972863 1063 log.go:172] http: TLS handshake error from 10.190.15.40:1635: remote error: tls: bad certificate I0114 11:25:25.975030 1063 log.go:172] http: TLS handshake error from 10.190.15.40:23673: remote error: tls: bad certificate I0114 11:25:25.975148 1063 log.go:172] http: TLS handshake error from 10.190.15.40:57676: remote error: tls: bad certificate I0114 11:25:25.982164 1063 log.go:172] http: TLS handshake error from 10.190.15.40:40683: remote error: tls: bad certificate I0114 11:25:25.982192 1063 log.go:172] http: TLS handshake error from 10.190.15.40:11049: remote error: tls: bad certificate I0114 11:25:25.990024 1063 log.go:172] http: TLS handshake error from 10.190.15.40:25287: remote error: tls: bad certificate I0114 11:25:25.990288 1063 log.go:172] http: TLS handshake error from 10.190.15.40:30061: remote error: tls: bad certificate I0114 11:25:25.992142 1063 log.go:172] http: TLS handshake error from 10.190.15.40:38174: remote error: tls: bad certificate I0114 11:25:26.000204 1063 log.go:172] http: TLS handshake error from 10.190.15.40:63942: remote error: tls: bad certificate I0114 11:25:26.002349 1063 log.go:172] http: TLS handshake error from 10.190.15.40:55625: remote error: tls: bad certificate I0114 11:25:26.027144 1063 log.go:172] http: TLS handshake error from 10.190.15.40:46402: remote error: tls: bad certificate I0114 11:25:26.027458 1063 log.go:172] http: TLS handshake error from 10.190.15.40:2822: remote error: tls: bad certificate I0114 11:25:26.036703 1063 log.go:172] http: TLS handshake error from 10.190.15.40:57254: remote error: tls: bad certificate I0114 11:25:26.037100 1063 log.go:172] http: TLS handshake error from 10.190.15.40:53104: remote error: tls: bad certificate 验证上述问题\n使用 SA token 访问 API\nbash 1 2 3 curl -k \\ -H \u0026#34;Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\u0026#34; \\ https://kubernetes.default.svc/api/v1/pods 未重启过的Pod\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ ls /var/run/secrets/kubernetes.io/serviceaccount/token /var/run/secrets/kubernetes.io/serviceaccount/token $ curl -k \\un/secrets/kubernetes.io/serviceaccount/token -H \u0026#34;Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\u0026#34; \\ https://kubernetes.default.svc/api/v1/podsecrets/kubernetes.io/serviceaccount/t { https://kubernetes.default.svc/api/v1/pods \u0026#34;kind\u0026#34;: \u0026#34;Status\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: {}, \u0026#34;status\u0026#34;: \u0026#34;Failure\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;Unauthorized\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;Unauthorized\u0026#34;, \u0026#34;code\u0026#34;: 401 } 重启更换过 Token 的 Pod\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 curl -k \\ -H \u0026#34;Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\u0026#34; \\ https://kubernetes.default.svc/api/v1/pods { \u0026#34;kind\u0026#34;: \u0026#34;Status\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: {}, \u0026#34;status\u0026#34;: \u0026#34;Failure\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;pods is forbidden: User \\\u0026#34;system:serviceaccount:netbox:default\\\u0026#34; cannot list resource \\\u0026#34;pods\\\u0026#34; in API group \\\u0026#34;\\\u0026#34; at the cluster scope\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;Forbidden\u0026#34;, \u0026#34;details\u0026#34;: { \u0026#34;kind\u0026#34;: \u0026#34;pods\u0026#34; }, \u0026#34;code\u0026#34;: 403 } 总结 了解了 k8s 集群认证模式（TLS Everywhere）。 通过基础知识知道了换证书需要更换那些地方。 没有尝试官方提到的滚动更新CA的方式。 Reference ​[1] kubernetes-generator\n​[2] 身份认证策略\n​[3] 使用启动引导令牌（Bootstrap Tokens）认证\n​[4] Kubernetes 签名者\n​[5] kubelet 配置\n​[6] 手动轮换 CA 证书\n​[7] So I became a node: exploiting bootstrap tokens in Azure Kubernetes Service\n​[8] How to regenerate Service Account tokens in Kubernetes\n​[9] Updating Kubernetes CA certificates the hard way\n","permalink":"https://www.161616.top/k8s-auth-and-regenerate-cert/","summary":"本文是关于Kubernetes 4A解析的第5章 深入理解Kubernetes 4A - Authentication源码解析 深入理解Kubernetes 4A - Authorization源码解析 深入理解Kubernetes 4A - Admission Control源码解析 深入理解Kubernetes 4A - Audit源码解析 TLS Everywhere - 解密kubernetes集群的安全认证 所有关于Kubernetes 4A部分代码上传至仓库 github.com/cylonchau/hello-k8s-4A\n在 kubernetes 集群中，所有的通讯都是用 TLS 进行加密和认证，本文使用一次老集群（二进制部署集群）证书更换作为记录，通过这种方式深入对 kubernetes 的认证方式来了解更换证书的步骤，以及一次模拟老集群的更换步骤。\n本文使用证书生成工具为 “kubernetes-generator” [1] 专用于 k8s 二进制部署生成证书和安装包的工具。\nnote 本文中的引用文档使用 web.archive 进行保存，避免官方版本更新，其概念与文档中的概念有变动导致，资料引用失败。 Kubernetes认证方式 为了了解证书更换需要做那些步骤，所以必须了解 k8s 的认证方式，这样才能更好的在更换证书时对集群上部署的业务系统的影响降低到最低。\nX509 证书 kube-apiserver 的启动参数 --client-ca-file，可以使用客户端办法机构，英文代号就是熟悉的 “CA” (Certificate authority)，当这个证书传递后，可以验证 kube-apiserver 用于验证向 kube-apiserver 提供的客户端证书，这里包含 k8s 中提供的用户种类的两种：\n用户名：对应证书的 CN (Common Name) 用户组：对应证书的O (organization)，用于对一个用户组进行授权，例如 “system:masters” 表示一个组 [1]，允许不受限制地访问 kube-apiserver 静态token kube-apiserver 的启动参数 --token-auth-file，是以文件提供给 kube-apiserver，该 Token 会长期有效，并且如果不重启服务 Token 是不会更新。","title":"TLS Everywhere - 解密kubernetes集群的安全认证"},{"content":"下面是一个完整 openssl.cnf 配置文件\nini 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 BASE_DOMAIN= CLUSTER_NAME= CERT_DIR= APISERVER_CLUSTER_IP= MASTER_NAME= [ ca ] # man ca default_ca = CA_default [ CA_default ] # Directory and file locations. dir = \\${ENV::CERT_DIR} certs = \\$dir crl_dir = \\$dir/crl new_certs_dir = \\$dir database = \\$dir/index.txt serial = \\$dir/serial # certificate revocation lists. crlnumber = \\$dir/crlnumber crl = \\$dir/crl/intermediate-ca.crl crl_extensions = crl_ext default_crl_days = 30 default_md = sha256 name_opt = ca_default cert_opt = ca_default default_days = 375 preserve = no policy = policy_loose [ policy_loose ] # Allow the CA to sign a range of certificates. countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] # man req default_bits = 4096 distinguished_name = req_distinguished_name string_mask = utf8only default_md = sha256 [ req_distinguished_name ] countryName = Country Name (2 letter code) stateOrProvinceName = State or Province Name localityName = Locality Name 0.organizationName = Organization Name organizationalUnitName = Organizational Unit Name commonName = Common Name # Certificate extensions (man x509v3_config) [ v3_ca ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:true, pathlen:0 keyUsage = critical, digitalSignature, cRLSign, keyCertSign [ client_cert ] basicConstraints = CA:FALSE nsCertType = client nsComment = \u0026#34;OpenSSL Generated Client Certificate\u0026#34; subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = clientAuth, serverAuth subjectAltName = @etcd_client [ server_cert ] basicConstraints = CA:FALSE nsCertType = server nsComment = \u0026#34;OpenSSL Generated Server Certificate\u0026#34; subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, clientAuth [ identity_server_cert ] basicConstraints = CA:FALSE nsCertType = server nsComment = \u0026#34;OpenSSL Generated Server Certificate\u0026#34; subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth subjectAltName = DNS.1:tectonic-identity-api.tectonic-system.svc.cluster.local [ etcd_server_cert ] basicConstraints = CA:FALSE nsCertType = server nsComment = \u0026#34;OpenSSL Generated Server Certificate\u0026#34; subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, clientAuth subjectAltName = @etcd_server_and_peer_dns [ etcd_peer_cert ] basicConstraints = CA:FALSE nsCertType = server nsComment = \u0026#34;OpenSSL Generated Server Certificate\u0026#34; subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, clientAuth subjectAltName = @etcd_server_and_peer_dns [ apiserver_cert ] basicConstraints = CA:FALSE nsCertType = server nsComment = \u0026#34;OpenSSL Generated Server Certificate\u0026#34; subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, clientAuth subjectAltName = @apiserver_names [ master_component_client_cert ] basicConstraints = CA:FALSE nsCertType = client nsComment = \u0026#34;OpenSSL Generated Client Certificate\u0026#34; subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = clientAuth subjectAltName = @master_component_names [etcd_server_and_peer_dns] DNS.1 = \\${ENV::BASE_DOMAIN} DNS.2 = localhost IP.1 = 10.0.0.5 IP.2 = 127.0.0.1 IP.3 = 127.0.0.5 [apiserver_names] DNS.1 = \\${ENV::CLUSTER_NAME}-\\${ENV::BASE_DOMAIN} DNS.2 = \\${ENV::BASE_DOMAIN} DNS.3 = kubernetes DNS.4 = kubernetes.default DNS.5 = kubernetes.default.svc DNS.6 = kubernetes.default.svc.cluster.local IP.1 = \\${ENV::APISERVER_CLUSTER_IP} IP.2 = 10.0.0.5 IP.3 = 10.0.0.4 [ master_component_names ] DNS.1 = \\${ENV::MASTER_NAME}.\\${ENV::BASE_DOMAIN} DNS.2 = \\${ENV::BASE_DOMAIN} IP.1 = 10.0.0.5 IP.2 = 10.0.0.4 # used for etcd_client [ etcd_client ] DNS.1 = localhost IP.1 = 10.0.0.5 IP.2 = 10.0.0.4 IP.3 = 10.0.0.6 IP.4 = 127.0.0.1 环境变量部分，用于定义配置文件内的配置会读取这个环境变量来替换\nini 1 2 3 4 5 BASE_DOMAIN= # 基础域名，通常用于 Kubernetes 集群或 API 服务器的域名配置 CLUSTER_NAME= # 集群名称，通常作为集群的标识符 CERT_DIR= # 证书文件存放的目录 APISERVER_CLUSTER_IP= # API 服务器的集群内部 IP 地址 MASTER_NAME= # Kubernetes 主节点的名称 CA 配置部分 指定默认的 CA 配置部分，即下面的 [CA_default] 部分\ntext 1 2 [ ca ] default_ca = CA_default [CA_default] 部分 该部分定义了 CA (证书颁发机构) 的各种配置参数。\nini 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # Directory and file locations. dir = \\${ENV::CERT_DIR} # 证书存放的根目录 certs = \\$dir # 证书文件夹 crl_dir = \\$dir/crl # CRL（证书吊销列表）文件夹 new_certs_dir = \\$dir # 新证书文件夹 database = \\$dir/index.txt # 用于存储证书请求、签发等信息的数据库文件 serial = \\$dir/serial # 用于跟踪签发证书的序列号文件 crlnumber = \\$dir/crlnumber # CRL 证书吊销序列号 crl = \\$dir/crl/intermediate-ca.crl # 吊销列表文件 crl_extensions = crl_ext # CRL 扩展配置 default_crl_days = 30 # 默认 CRL 有效期为 30 天 default_md = sha256 # 默认使用 sha256 哈希算法 name_opt = ca_default # 证书的名称选项 cert_opt = ca_default # 证书的选项 default_days = 375 # 证书默认有效期为 375 天 preserve = no # 是否保留 CA 对已签发证书的审计日志 policy = policy_loose # 使用 \u0026#39;policy_loose\u0026#39; 签发证书 [policy_loose] 部分 该部分定义了证书签发策略，允许一些字段为 optional，即证书可以包含这些字段，但不要求必须存在。\nini 1 2 3 4 5 6 7 countryName = optional # 国家名称可选 stateOrProvinceName = optional # 省/州名称可选 localityName = optional # 地市名称可选 organizationName = optional # 组织名称可选 organizationalUnitName = optional # 组织单位名称可选 commonName = supplied # 公共名称是必须提供的（通常是域名或主机名） emailAddress = optional # 邮件地址可选 [req] 和 [req_distinguished_name] 部分 distinguished执意为，显著的，杰出的。用于生成证书请求（CSR）。配置了默认的密钥长度、签名算法等信息。\nini 1 2 3 4 5 6 7 8 9 10 11 12 13 [ req ] default_bits = 4096 # 默认的密钥位数为 4096 位 distinguished_name = req_distinguished_name # 引用 \u0026#39;req_distinguished_name\u0026#39; 部分来定义 DN（区分名称） string_mask = utf8only # 使用 UTF-8 字符编码 default_md = sha256 # 默认使用 sha256 作为散列算法 [ req_distinguished_name ] countryName = Country Name (2 letter code) # 国家（2个字母的国家代码） stateOrProvinceName = State or Province Name # 省/州名称 localityName = Locality Name # 城市/地区名称 0.organizationName = Organization Name # 组织名称 organizationalUnitName = Organizational Unit Name # 组织单位名称 commonName = Common Name # 通用名称（通常是域名或主机名） 证书扩展部分 这些扩展是用于指定证书的用途和其它属性的配置，通常包括证书的使用场景（例如，客户端、服务器认证等）。\n扩展部分的名字，例如[v3_ca] 并不是一个固定的格式，只是一个 OpenSSL 配置文件中的命名部分，通常用于定义颁发 CA（证书颁发机构）证书时所需要的扩展（extensions）。你可以根据需要自定义该部分的内容，但它通常包含一些通用的证书扩展，尤其是 基本约束 (basicConstraints)、密钥用法 (keyUsage) 和 证书签发权限。\n[v3_ca] 部分 该部分用于设置根证书（CA）的扩展，包括证书签发权限、密钥使用等。\nbash 1 2 3 4 5 6 subjectKeyIdentifier = hash # 用于标识证书的主题密钥标识符 authorityKeyIdentifier = keyid:always,issuer # 用于标识证书的颁发者密钥标识符 # 基本约束，标明该证书为 CA 证书，且路径长度为 0（不能作为其他证书的颁发机构） # CA:true 表示这个为ca basicConstraints = critical, CA:true, pathlen:0 keyUsage = critical, digitalSignature, cRLSign, keyCertSign # 证书使用场景：数字签名、证书吊销列表签名、证书签发 客户端、服务器证书扩展部分 这些部分定义了不同类型证书（如客户端证书、服务器证书）的扩展选项：\n[client_cert] 和 [server_cert] 定义了客户端和服务器证书的基本约束和使用场景。 [identity_server_cert] 和 [etcd_server_cert] 定义了特定的服务器证书，通常用于指定 API 服务、etcd 服务等。 这些扩展中的 subjectAltName 字段用于指定证书的备用名称（例如，DNS 或 IP 地址），这是证书有效性的一个重要部分。\nini 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [ client_cert ] # 不允许作为 CA 证书使用 basicConstraints = CA:FALSE # 客户端证书使用场景 keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment # 扩展用途：客户端身份验证、服务器身份验证 extendedKeyUsage = clientAuth, serverAuth # 使用 \u0026#39;etcd_client\u0026#39; 部分中定义内容用作的 SAN 配置 subjectAltName = @etcd_client [ server_cert ] # 不允许作为 CA 证书使用 basicConstraints = CA:FALSE # 服务器证书使用场景 keyUsage = critical, digitalSignature, keyEncipherment # 扩展用途：服务器身份验证、客户端身份验证 extendedKeyUsage = serverAuth, clientAuth # 使用 \u0026#39;etcd_server_and_peer_dns\u0026#39; 配置 subjectAltName = @etcd_server_and_peer_dns keyUsage：通常包括 digitalSignature 和 keyEncipherment，表明证书可以用于签名和加密。 extendedKeyUsage：指定该证书可用于 serverAuth（服务器认证）和 clientAuth（客户端认证）。 subjectAltName：通常会列出服务器的 DNS 名称或 IP 地址，用于验证服务器身份。 当 extendedKeyUsage = serverAuth, clientAuth 同时设定时，表示该证书可以同时用于服务器身份验证和客户端身份验证。这意味着该证书可以被用于 服务器端 来验证客户端的请求，也可以被用作 客户端 来向服务器进行身份验证。例如 Mutual TLS，mTLS。\n单独配置：extendedKeyUsage = serverAuth 或 extendedKeyUsage = clientAuth 时：\nextendedKeyUsage = serverAuth：表示证书的用途是 服务器身份验证，即浏览器可以用它来验证服务器的真实性，确保用户正在访问的是正确的、合法的服务器。此配置通常用于大多数网站的 SSL/TLS 服务器证书。 extendedKeyUsage = clientAuth：表示证书仅用于客户端身份验证，例如用于 SSL/TLS 客户端证书。 Subject Alternative Name (SAN) 部分 SAN 是证书中的一个字段，允许将多个 DNS 名称、IP 地址或 URI 作为证书的有效域名或 IP 地址。它可以包含多个 DNS 名称和 IP 地址。\nini 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [ etcd_server_and_peer_dns ] DNS.1 = \\${ENV::BASE_DOMAIN} # 基础域名 DNS.2 = localhost # 本地主机名 IP.1 = 10.0.0.5 # IP 地址 IP.2 = 127.0.0.1 # 本地回环地址 IP.3 = 127.0.0.5 # 另一个本地回环地址 [ apiserver_names ] # 使用集群名称和基础域名组成的 DNS 名称 # 使用上面的变量 DNS.1 = \\${ENV::CLUSTER_NAME}-\\${ENV::BASE_DOMAIN} # 基础域名 DNS.2 = \\${ENV::BASE_DOMAIN} # API 服务器的 IP 地址 IP.1 = \\${ENV::APISERVER_CLUSTER_IP} # 备用 IP 地址 IP.2 = 10.0.0.5 ","permalink":"https://www.161616.top/openssl-cnf/","summary":"下面是一个完整 openssl.cnf 配置文件\nini 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 BASE_DOMAIN= CLUSTER_NAME= CERT_DIR= APISERVER_CLUSTER_IP= MASTER_NAME= [ ca ] # man ca default_ca = CA_default [ CA_default ] # Directory and file locations.","title":"openssl.cnf详解"},{"content":"最近，我不得不将 Zookeeper 3.4.18 集群升级到 3.6+。要求是：无感升级，不丢失数据，并且尽量不向任何用户发出通知。在调研zookeeper 版本后，发现 3.6+ 支持了 metrics 模块，比较符合需求，所以需要从 3.4.18 升级至 3.6.4\n3.5 + 支持动态配置 3.6.0+ 支持内置 metrics 模块 现有集群配置 集群IP 当前目录 新版本目录 192.240.16.18 /usr/local/zookeeper-3.4.14/ /usr/local/apache-zookeeper-3.6.4-bin/ 192.240.16.21 /usr/local/zookeeper-3.4.14/ /usr/local/apache-zookeeper-3.6.4-bin/ 192.240.16.28 /usr/local/zookeeper-3.4.14/ /usr/local/apache-zookeeper-3.6.4-bin/ 192.240.16.147 /usr/local/zookeeper-3.4.14/ /usr/local/apache-zookeeper-3.6.4-bin/ 192.240.16.202 /usr/local/zookeeper-3.4.14/ /usr/local/apache-zookeeper-3.6.4-bin/ 下载安装包 在官方 archive 找到对应安装包\n从 zk 3.5 起安装包分为带 “bin” 和不带 “bin” 的\n带 “bin” 的包含所需jar包 不带 “bin” 的需要自行编译 bash 1 wget https://archive.apache.org/dist/zookeeper/zookeeper-3.6.4/ 解压\nbash 1 tar Czxf /usr/local/ apache-zookeeper-3.6.4-bin.tar.gz \u0026amp;\u0026amp; cd /usr/local 升级版本 注意以下步骤需要对每个 zk 服务器都执行一边\n检查状态 检查每个 zk 实例的角色，注意，Leader 要留着最后升级\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ echo status | nc localhost 2181 Zookeeper version: 3.4.14-4c25d480e66aadd371de8bd2fd8da255ac140bcf, built on 03/06/2019 16:18 GMT Clients: /10.222.16.147:58936[1](queued=0,recved=24290,sent=24434) /10.222.16.18:56736[1](queued=0,recved=24213,sent=24363) /127.0.0.1:60822[0](queued=0,recved=1,sent=0) Latency min/avg/max: 0/0/77 Received: 48505 Sent: 48798 Connections: 3 Outstanding: 0 Zxid: 0x5c000027ee Mode: leader Node count: 440526 Proposal sizes last/min/max: 139/32/1039 准备配置文件 产生默认配置文件\nbash 1 cp apache-zookeeper-3.6.4-bin/conf/zoo_sample.cfg apache-zookeeper-3.6.4-bin/conf/zoo.cfg 启用 prometheus metrics 相关配置\nbash 1 2 3 sed -i \u0026#34;s@#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider@metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider@g\u0026#34; apache-zookeeper-3.6.4-bin/conf/zoo.cfg sed -i \u0026#34;s@#metricsProvider.httpPort=7000@metricsProvider.httpPort=7000@g\u0026#34; apache-zookeeper-3.6.4-bin/conf/zoo.cfg sed -i \u0026#34;s@#metricsProvider.exportJvmInfo=true@metricsProvider.exportJvmInfo=true@g\u0026#34; apache-zookeeper-3.6.4-bin/conf/zoo.cfg 检查配置\nbash 1 tail -5 apache-zookeeper-3.6.4-bin/conf/zoo.cfg 增加旧的配置\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 增加旧的配置 cat \u0026lt;\u0026lt;EOF \u0026gt;\u0026gt; apache-zookeeper-3.6.4-bin/conf/zoo.cfg dataDir=/usr/local/zookeeper/data dataLogDir=/usr/local/zookeeper/logs # the port at which the clients will connect clientPort=2181 server.1=10.240.16.18:2888:3888 server.2=10.240.16.21:2888:3888 server.3=10.240.16.28:2888:3888 server.4=10.240.16.147:2888:3888 server.5=10.240.16.202:2888:3888 autopurge.snapRetainCount=3 autopurge.purgeInterval=24 EOF 检查配置\nbash 1 tail -13 apache-zookeeper-3.6.4-bin/conf/zoo.cfg 直接转移数据文件\nbash 1 2 3 ln -svnf /usr/local/zookeeper-3.4.14/data /usr/local/apache-zookeeper-3.6.4-bin/data ln -svnf /usr/local/zookeeper-3.4.14/logs /usr/local/apache-zookeeper-3.6.4-bin/logs ls -l /usr/local/apache-zookeeper-3.6.4-bin/ 修改新版启动脚本\n此步骤是为了可以使用 stat 命令\nbash 1 2 sed -i \u0026#39;77a\\ZOOMAIN=\u0026#34;-Dzookeeper.4lw.commands.whitelist=* ${ZOOMAIN}\u0026#34;\u0026#39; apache-zookeeper-3.6.4-bin/bin/zkServer.sh tail -n +70 apache-zookeeper-3.6.4-bin/bin/zkServer.sh | head -n 9 停止旧版本\nbash 1 zookeeper/bin/zkServer.sh stop 连接到新版本\nbash 1 ln -svnf apache-zookeeper-3.6.4-bin zookeeper 启动服务\nbash 1 zookeeper/bin/zkServer.sh start 验证服务\nbash 1 echo status | nc localhost 2181 检查内容\nbash 1 2 zookeeper/bin/zkCli.sh ls /clickhouse/tables/01-01 恢复旧版本 bash 1 ln -svnf zookeeper-3.4.14 zookeeper Reference [1] zookeeper启动报错：Error: Could not find or load main class org.apache.zookeeper.server.quorum.QuorumPeerMain\n[2] zookeeper 异常 ：stat is not executed because it is not in the whitelist. Connection closed b\n[3] ZooKeeper Monitor Guide\n[4] KIP-902: Upgrade Zookeeper to 3.8.2\n","permalink":"https://www.161616.top/zk-upgrade-3.4-to-3.6/","summary":"最近，我不得不将 Zookeeper 3.4.18 集群升级到 3.6+。要求是：无感升级，不丢失数据，并且尽量不向任何用户发出通知。在调研zookeeper 版本后，发现 3.6+ 支持了 metrics 模块，比较符合需求，所以需要从 3.4.18 升级至 3.6.4\n3.5 + 支持动态配置 3.6.0+ 支持内置 metrics 模块 现有集群配置 集群IP 当前目录 新版本目录 192.240.16.18 /usr/local/zookeeper-3.4.14/ /usr/local/apache-zookeeper-3.6.4-bin/ 192.240.16.21 /usr/local/zookeeper-3.4.14/ /usr/local/apache-zookeeper-3.6.4-bin/ 192.240.16.28 /usr/local/zookeeper-3.4.14/ /usr/local/apache-zookeeper-3.6.4-bin/ 192.240.16.147 /usr/local/zookeeper-3.4.14/ /usr/local/apache-zookeeper-3.6.4-bin/ 192.240.16.202 /usr/local/zookeeper-3.4.14/ /usr/local/apache-zookeeper-3.6.4-bin/ 下载安装包 在官方 archive 找到对应安装包\n从 zk 3.5 起安装包分为带 “bin” 和不带 “bin” 的\n带 “bin” 的包含所需jar包 不带 “bin” 的需要自行编译 bash 1 wget https://archive.apache.org/dist/zookeeper/zookeeper-3.6.4/ 解压\nbash 1 tar Czxf /usr/local/ apache-zookeeper-3.6.4-bin.tar.gz \u0026amp;\u0026amp; cd /usr/local 升级版本 注意以下步骤需要对每个 zk 服务器都执行一边","title":"zookeeper版本升级 - from 3.4 to 3.8"},{"content":"centos\u0026amp;rocky 升级 xz，需要和目前系统对比每个xz包，和每个xz包内的文件有哪些；然后，根据不同类型的包（base, devel, lib)，三个进行打包操作。\n下载curl源码包 升级至少需要更新至 xz 5.4.6 ，首先从官网下载源码包 [1]\n将xz作为rpm 下面是 rpm 的规格文件\nspec 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 Name: xz Version: 5.4.6 Release: 1%{?dist} Summary: LZMA compression utilities License: MIT URL: https://tukaani.org/xz Source0: xz-5.4.6.tar.gz BuildRequires: glibc %package libs Summary: Libraries for decoding LZMA compression Group: xz Vendor: Cylon URL: https://tukaani.org/xz/ BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot %package devel Summary: Devel libraries for decoding LZMA compression Group: xz Vendor: Cylon URL: https://tukaani.org/xz/ BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot %description XZ Utils are a complete C99 implementation of the .xz file format. XZ Utils were originally written for POSIX systems but have been ported to a few non-POSIX systems as well. %description libs Libraries for decoding files compressed with LZMA or XZ utils. %description devel Devel libraries and headers for liblzma. %prep %autosetup -p1 %build %configure \\ --libdir=/usr/lib64/ \\ --bindir=/usr/bin/ \\ --docdir=/usr/share/doc/ \\ --mandir=/usr/share/man/ make %{?_smp_mflags} %install make install DESTDIR=%{buildroot} %files %defattr(-,root,root,-) %attr(0755,root,root) /usr/bin/* %attr(0755,root,root) /usr/share/man/* %attr(0755,root,root) /usr/share/doc/AUTHORS %attr(0755,root,root) /usr/share/doc/COPYING %attr(0755,root,root) /usr/share/doc/COPYING.GPLv2 %attr(0755,root,root) /usr/share/doc/NEWS %attr(0755,root,root) /usr/share/doc/README %attr(0755,root,root) /usr/share/doc/THANKS %attr(0755,root,root) /usr/share/doc/TODO %files libs %defattr(-,root,root,-) %attr(0755,root,root) /usr/lib64/liblzma.so.5 %attr(0755,root,root) /usr/lib64/liblzma.so.5.4.6 %attr(0755,root,root) /usr/share/doc/AUTHORS %attr(0755,root,root) /usr/share/doc/COPYING %attr(0755,root,root) /usr/share/doc/COPYING.GPLv2 %attr(0755,root,root) /usr/share/doc/NEWS %attr(0755,root,root) /usr/share/doc/README %attr(0755,root,root) /usr/share/doc/THANKS %attr(0755,root,root) /usr/share/doc/TODO %files devel %defattr(-,root,root,-) %attr(0755,root,root) /usr/lib64/liblzma.so %attr(0644,root,root) /usr/include/* %attr(0644,root,root) /usr/lib64/pkgconfig/liblzma.pc %changelog * Tue Nov 01 2024 Cylon \u0026lt;cylonchau@outlook.com\u0026gt; - 5.4.6 - Initial package release 执行对应的更新操作\nbash 1 2 3 4 rpmbuild \\ --define \u0026#34;_topdir `pwd`/rpmbuild\u0026#34; \\ --define=\u0026#34;PMackaged_files_terminate_build 0\u0026#34; \\ -ba `pwd`/rpmbuild/SPECS/xz.spec Reference ​[1] XZ data compression\n​[2] CVE-2022-1271 Detail\n","permalink":"https://www.161616.top/xz-upgrade/","summary":"centos\u0026amp;rocky 升级 xz，需要和目前系统对比每个xz包，和每个xz包内的文件有哪些；然后，根据不同类型的包（base, devel, lib)，三个进行打包操作。\n下载curl源码包 升级至少需要更新至 xz 5.4.6 ，首先从官网下载源码包 [1]\n将xz作为rpm 下面是 rpm 的规格文件\nspec 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 Name: xz Version: 5.","title":"CentOS7\u0026Rocky9更新xz"},{"content":"面试复盘（2411） 总结 整体感觉偏向工具使用类问答，技术深度较弱，问题主要集中在：\nXXX 会不会写 XXX 会不会用 例如：\nnginx 会不会配置 Dockerfile 会不会写 这类问题偏向经验确认，没有太多技术讨论。\nKubernetes：Pod 处于 Terminating 状态 其中一个相对有效的问题：\n删除 Pod 时，Pod 一直处于 Terminating 怎么办？\n回答\nTerminating 状态有多种情况，需要先确认具体原因。\n例如：\nNode 节点异常，无法与 APIServer 通讯 Pod 挂载的 Volume 无法卸载 Finalizer 未清理完成 kubelet 无法完成 Pod 删除流程 如果无法优雅删除，可以使用：\nbash 1 kubectl delete pod xxx --force --grace-period=0 进行强制删除。\nFinalizers 面试过程中提到了 finalizers。\ntext 1 2 3 4 5 6 7 finalizers in the metadata.finalizers field. When you attempt to delete the resource, the API server handling the delete request notices the values in the finalizers field and does the following: - Modifies the object to add a metadata.deletionTimestamp field with the time you started the deletion. - Prevents the object from being removed until all items are removed from its metadata.finalizers field - Returns a 202 status code (HTTP \u0026#34;Accepted\u0026#34;) 知识点补充\nfinalizers 用于资源删除保护机制。\n当资源被删除时：\nKubernetes 不会立即删除对象 会先写入 deletionTimestamp 等待所有 finalizers 被移除 finalizer 对应控制器完成清理逻辑后，资源才真正删除 常见场景：\n云资源回收 PVC/PV 清理 Operator 自定义资源回收 排查方式：\nbash 1 kubectl get pod xxx -o yaml 查看：\nyaml 1 2 metadata: finalizers: 如果 finalizer 无法完成，可以手动删除。\n结果总结 自我介绍阶段需要主动引导面试方向，否则面试官会根据自身认知展开问题 当双方技术背景差异较大时，容易出现认知不一致，导致沟通效率较低 对于纯“会不会用”类型的问题，可以快速结束，不需要过度展开 如果本身没有入职意向，可以直接提高期望薪资 ","permalink":"https://www.161616.top/interview-retrospective-2411/","summary":"面试复盘（2411） 总结 整体感觉偏向工具使用类问答，技术深度较弱，问题主要集中在：\nXXX 会不会写 XXX 会不会用 例如：\nnginx 会不会配置 Dockerfile 会不会写 这类问题偏向经验确认，没有太多技术讨论。\nKubernetes：Pod 处于 Terminating 状态 其中一个相对有效的问题：\n删除 Pod 时，Pod 一直处于 Terminating 怎么办？\n回答\nTerminating 状态有多种情况，需要先确认具体原因。\n例如：\nNode 节点异常，无法与 APIServer 通讯 Pod 挂载的 Volume 无法卸载 Finalizer 未清理完成 kubelet 无法完成 Pod 删除流程 如果无法优雅删除，可以使用：\nbash 1 kubectl delete pod xxx --force --grace-period=0 进行强制删除。\nFinalizers 面试过程中提到了 finalizers。\ntext 1 2 3 4 5 6 7 finalizers in the metadata.finalizers field. When you attempt to delete the resource, the API server handling the delete request notices the values in the finalizers field and does the following: - Modifies the object to add a metadata.","title":"interview retrospective 2411"},{"content":"12 Api 2026 面试问题 整体问题偏向项目追问，主要围绕实际使用场景展开。\n1. 你这个做了什么 主要是项目经历追问。\n更关注：\n实际负责内容 是否真正参与核心实现 在项目中的角色 注释 这类问题通常需要提前整理：\n项目背景 自己负责的模块 技术选型原因 遇到的问题 如何解决 最终效果 避免只描述：\ntext 1 用了什么技术 而需要强调：\ntext 1 2 为什么这么做 解决了什么问题 2. Elasticsearch 丢日志怎么处理 知识点补充\n日志丢失通常需要从几个方向排查：\n数据写入链路 例如：\ntext 1 应用 -\u0026gt; Filebeat/FluentBit -\u0026gt; Kafka -\u0026gt; Logstash -\u0026gt; Elasticsearch 需要确认：\n哪一层出现丢失 是否存在 buffer 满 是否出现 backpressure Elasticsearch 本身 重点包括：\n磁盘是否写满 JVM Heap 是否不足 是否触发 GC refresh / flush 压力 shard 是否过多 replica 是否异常 常见排查命令 bash 1 2 3 GET _cluster/health GET _cat/nodes?v GET _cat/shards?v 常见优化方向 增加 buffer Kafka 做削峰 减少 shard 数量 调整 bulk 大小 调整 refresh_interval 增加 data node 3. HAProxy 替代是怎么替代 kube-proxy的 问题主要在于：\n为什么替换 如何迁移 替换后架构变化 注释 常见替代方向：\n原组件 替代方案 HAProxy Envoy HAProxy Nginx HAProxy 云厂商 LB HAProxy Kubernetes Ingress 一般迁移需要考虑 四层还是七层 会话保持 健康检查 TLS 配置兼容 性能影响 灰度切换 4. LB 是怎么实现 注释 LB（LoadBalancer）实现通常分几个方向：\n四层 LB 基于：\nIP TCP/UDP 常见：\nLVS HAProxy 云厂商 NLB 七层 LB 基于：\nHTTP Host Path 常见：\nNginx Envoy Ingress Controller Kubernetes 场景 通常链路：\ntext 1 Service -\u0026gt; kube-proxy -\u0026gt; iptables/ipvs 云环境：\ntext 1 2 3 4 Service(type=LoadBalancer) -\u0026gt; cloud-controller-manager -\u0026gt; Cloud API -\u0026gt; 云厂商 LB 裸金属环境：\nMetalLB BGP ARP 结果总结 自我介绍相比之前有优化，但对面试方向的引导仍然不够 面试问题更多偏向接手后的实际问题，而不是基础原理 面试官更关注“是否能处理现有问题”，而不是知识体系本身 整体交流过程中，对方更偏向结果导向 30 Api 2026 面试问题 遇到的都是纯流水线问题：\nJava 项目和其他项目有什么区别 如果遇到了 P0 级别故障你怎么处理 日志你们更倾向于用什么样的 新入职遇到问题更倾向于 AI 查资料还是问同事 阿里云的 Terway 和 Flannel 区别 我的薄弱点 ELK ELK 需要重点补的是完整日志链路，而不是只知道 Elasticsearch。 面试中可以按 采集层 -\u0026gt; 缓冲层 -\u0026gt; 处理层 -\u0026gt; 存储层 -\u0026gt; 查询展示层 来回答，例如： Filebeat / FluentBit -\u0026gt; Kafka -\u0026gt; Logstash / Vector -\u0026gt; Elasticsearch -\u0026gt; Kibana / Grafana。 排查日志丢失时，要先确认是采集端丢、队列丢、消费端丢，还是 Elasticsearch 写入失败。\nOpenTracing OpenTracing 主要用于分布式链路追踪，核心概念包括 Trace、Span、Context、Propagation。 面试中可以说明：一个请求经过多个服务时，每个服务内部的操作可以形成 Span，多个 Span 组成一个 Trace。 现在 OpenTracing 已经逐渐被 OpenTelemetry 统一，回答时可以顺带提到 OpenTelemetry 是当前更主流的标准。\n解决问题回答 解决问题类问题不要直接给结论，要按故障处理流程回答。 推荐结构： 现象确认 -\u0026gt; 影响范围 -\u0026gt; 止血恢复 -\u0026gt; 定位根因 -\u0026gt; 修复验证 -\u0026gt; 复盘改进。 P0 故障重点不是展示自己多懂技术，而是展示优先级意识：先恢复业务，再分析根因，最后做长期治理。\n平时专注的都是调度框架原理，根本没看基础信息，用到的也少，没回答好亲和性、反亲和性 Kubernetes 亲和性主要分为 Node Affinity 和 Pod Affinity。 Node Affinity 用来控制 Pod 调度到哪些节点上，例如按照节点标签选择机器。 Pod Affinity 用来让 Pod 尽量和某些 Pod 调度在一起。 Pod Anti-Affinity 用来让 Pod 尽量分散，常用于高可用场景，避免多个副本落在同一个节点或同一个可用区。 面试中可以结合 Deployment 多副本高可用来说：反亲和性可以减少单节点故障导致多个副本同时不可用的问题。\n结果总结 面试都是模棱两可问题，没有具体的技术方向 自己的项目完全没有被问到 个人感觉这样的公司不要去，他们是要干活的，不是解决问题的人，没有发展 还有一种可能是面试官的技术水平也是合约 第二次面试网络不好直接终止 ","permalink":"https://www.161616.top/interview-retrospective-260412/","summary":"12 Api 2026 面试问题 整体问题偏向项目追问，主要围绕实际使用场景展开。\n1. 你这个做了什么 主要是项目经历追问。\n更关注：\n实际负责内容 是否真正参与核心实现 在项目中的角色 注释 这类问题通常需要提前整理：\n项目背景 自己负责的模块 技术选型原因 遇到的问题 如何解决 最终效果 避免只描述：\ntext 1 用了什么技术 而需要强调：\ntext 1 2 为什么这么做 解决了什么问题 2. Elasticsearch 丢日志怎么处理 知识点补充\n日志丢失通常需要从几个方向排查：\n数据写入链路 例如：\ntext 1 应用 -\u0026gt; Filebeat/FluentBit -\u0026gt; Kafka -\u0026gt; Logstash -\u0026gt; Elasticsearch 需要确认：\n哪一层出现丢失 是否存在 buffer 满 是否出现 backpressure Elasticsearch 本身 重点包括：\n磁盘是否写满 JVM Heap 是否不足 是否触发 GC refresh / flush 压力 shard 是否过多 replica 是否异常 常见排查命令 bash 1 2 3 GET _cluster/health GET _cat/nodes?","title":"interview retrospective 2604"},{"content":"近日创建GKE集群，需要使用现有的VPC进行创建，所以需要掌握两个步骤，导入资源，创建集群\nterraform 导入命令 GCP中的资源地址和 ID。资源地址是指向配置中的资源实例的标识符。ID 是标识 Google Cloud 中要导入的资源的标识符\n资源地址通常为 terraform在定义这类资源时配置的（对应提供商支持），以 GCP 为例 Cloud Storage 存储桶， google_storage_bucket.sample，sample 为 id，定义如下\nyaml 1 2 3 4 5 6 resource \u0026#34;google_storage_bucket\u0026#34; \u0026#34;sample\u0026#34; { name = \u0026#34;my-bucket\u0026#34; project = \u0026#34;sample-project\u0026#34; location = \u0026#34;US\u0026#34; force_destroy = true } 示例 - 导入现有GKE集群 语法\nbash 1 terraform import \u0026lt;resource_name\u0026gt;.\u0026lt;name\u0026gt; \u0026lt;project\u0026gt;/\u0026lt;locations\u0026gt;/\u0026lt;real_resource_name\u0026gt; 实例\nbash 1 terraform import google_container_cluster.gke project20231124/asia-east2/gke-prd-cluster-02 输出结果如下\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ terraform import google_container_cluster.gke project20231124/asia-east2/gke-prd-cluster-02 data.google_compute_network.available: Reading... data.google_compute_zones.available: Reading... data.google_container_engine_versions.gke_version: Reading... data.google_container_engine_versions.gke_version: Read complete after 1s [id=2024-08-06 07:29:53.793630041 +0000 UTC] data.google_compute_zones.available: Read complete after 1s [id=projects/project20231124/regions/asia-east2] data.google_compute_network.available: Read complete after 2s [id=projects/project20231124/global/networks/gke-cluster-02-vpc-network] google_container_cluster.gke: Importing from ID \u0026#34;project20231124/asia-east2/gke-prd-cluster-02\u0026#34;... google_container_cluster.gke: Import prepared! Prepared google_container_cluster for import google_container_cluster.gke: Refreshing state... [id=projects/project20231124/locations/asia-east2/clusters/gke-prd-cluster-02] Import successful! The resources that were imported are shown above. These resources are now in your Terraform state and will henceforth be managed by Terraform. 导入GKE集群节点池 bash 1 2 3 4 5 6 7 8 9 10 11 12 $ terraform import google_container_node_pool.gcp-available-b project20231124/asia-east2/gke-prd-cluster-02 data.google_container_engine_versions.gke_version: Reading... data.google_compute_network.available: Reading... data.google_compute_zones.available: Reading... data.google_compute_zones.available: Read complete after 0s [id=projects/project20231124/regions/asia-east2] data.google_container_engine_versions.gke_version: Read complete after 0s [id=2024-08-06 07:51:46.183235726 +0000 UTC] data.google_compute_network.available: Read complete after 0s [id=projects/project20231124/global/networks/gke-cluster-02-vpc-network] Import successful! The resources that were imported are shown above. These resources are now in your Terraform state and will henceforth be managed by Terraform. 节点池这类资源地址不能查看，只能通过上一级 google_container_cluster 去查看\nbash 1 2 3 4 5 6 $ terraform state show -state=./terraform.tfstate google_container_node_pool.gcp-available-b No instance found for the given address! This command requires that the address references one specific instance. To view the available instances, use \u0026#34;terraform state list\u0026#34;. Please modify the address to reference a specific instance. 资源地址必须在 terraform state list 中才可以使用 terraform state show 去查看\nbash 1 2 3 4 5 6 7 $ terraform state list data.google_compute_network.available data.google_compute_zones.available data.google_container_engine_versions.gke_version google_compute_subnetwork.vpc-subnet google_container_cluster.gke 导入 VPC bash 1 2 3 4 5 6 7 8 9 10 11 12 13 $ terraform import google_compute_network.vpc-network /project20231124/asia-east2/gke-cluster-02-vpc-network data.google_compute_zones.available: Reading... data.google_compute_network.available: Reading... data.google_container_engine_versions.gke_version: Reading... data.google_container_engine_versions.gke_version: Read complete after 0s [id=2024-08-06 07:41:30.318550432 +0000 UTC] data.google_compute_network.available: Read complete after 0s [id=projects/project20231124/global/networks/gke-cluster-02-vpc-network] data.google_compute_zones.available: Read complete after 0s [id=projects/project20231124/regions/asia-east2] Import successful! The resources that were imported are shown above. These resources are now in your Terraform state and will henceforth be managed by Terraform. 导入VPC子网 语法\nbash 1 terraform import \u0026lt;resource_name\u0026gt;.\u0026lt;name\u0026gt; \u0026lt;project\u0026gt;/\u0026lt;locations\u0026gt;/\u0026lt;real_resource_name\u0026gt; 示例\nbash 1 terraform import google_compute_subnetwork.vpc-subnet project20231124/asia-east2/gke-cluster-02-vpc-network 输出\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 terraform import google_compute_subnetwork.vpc-subnet project20231124/asia-east2/gke-cluster-02-vpc-network data.google_compute_zones.available: Reading... data.google_container_engine_versions.gke_version: Reading... data.google_compute_network.available: Reading... data.google_compute_zones.available: Read complete after 0s [id=projects/project20231124/regions/asia-east2] data.google_container_engine_versions.gke_version: Read complete after 0s [id=2024-08-06 03:43:01.406680586 +0000 UTC] data.google_compute_network.available: Read complete after 1s [id=projects/project20231124/global/networks/gke-cluster-02-vpc-network] google_compute_subnetwork.vpc-subnet: Importing from ID \u0026#34;project20231124/asia-east2/gke-cluster-02-vpc-network\u0026#34;... google_compute_subnetwork.vpc-subnet: Import prepared! Prepared google_compute_subnetwork for import google_compute_subnetwork.vpc-subnet: Refreshing state... [id=projects/project20231124/regions/asia-east2/subnetworks/gke-cluster-02-vpc-network] Import successful! The resources that were imported are shown above. These resources are now in your Terraform state and will henceforth be managed by Terraform. 最后查看导入后的资源 bash 1 2 3 4 5 6 7 $ terraform state list data.google_compute_network.available data.google_compute_zones.available data.google_container_engine_versions.gke_version google_compute_subnetwork.vpc-subnet google_container_cluster.gke terraform 查看本地state文件资源 terraform state list 查看本地state文件资源\n语法\nbash 1 2 #读取 state 或 plan file，不指定 [path] 则output当前文件夹下 terraform show [options] [path] 查看某个资源路径的信息 例如查看 VPC 子网\n语法\nbash 1 terraform [global options] state show [options] ADDRESS 示例\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 # terraform state show -state=./terraform.tfstate google_compute_subnetwork.vpc-subnet # google_compute_subnetwork.vpc-subnet: resource \u0026#34;google_compute_subnetwork\u0026#34; \u0026#34;vpc-subnet\u0026#34; { creation_timestamp = \u0026#34;2022-06-25T01:51:49.868-07:00\u0026#34; description = \u0026#34;public prod service network\u0026#34; gateway_address = \u0026#34;192.233.10.1\u0026#34; id = \u0026#34;projects/project20231124/regions/asia-east2/subnetworks/gke-cluster-02-vpc-network\u0026#34; ip_cidr_range = \u0026#34;192.233.10.0/24\u0026#34; name = \u0026#34;gke-cluster-02-vpc-network\u0026#34; network = \u0026#34;https://www.googleapis.com/compute/v1/projects/project20231124/global/networks/gke-cluster-02-vpc-network\u0026#34; private_ip_google_access = true private_ipv6_google_access = \u0026#34;DISABLE_GOOGLE_ACCESS\u0026#34; project = \u0026#34;project20231124\u0026#34; purpose = \u0026#34;PRIVATE\u0026#34; region = \u0026#34;asia-east2\u0026#34; secondary_ip_range = [ { ip_cidr_range = \u0026#34;10.126.0.0/22\u0026#34; range_name = \u0026#34;gke-gke-prd-cluster-02-services-621a7058\u0026#34; }, { ip_cidr_range = \u0026#34;10.116.0.0/16\u0026#34; range_name = \u0026#34;gke-gke-prd-cluster-02-pods-621a7058\u0026#34; }, ] self_link = \u0026#34;https://www.googleapis.com/compute/v1/projects/project20231124/regions/asia-east2/subnetworks/gke-cluster-02-vpc-network\u0026#34; stack_type = \u0026#34;IPV4_ONLY\u0026#34; timeouts {} } ","permalink":"https://www.161616.top/gke-create-exists-resources/","summary":"近日创建GKE集群，需要使用现有的VPC进行创建，所以需要掌握两个步骤，导入资源，创建集群\nterraform 导入命令 GCP中的资源地址和 ID。资源地址是指向配置中的资源实例的标识符。ID 是标识 Google Cloud 中要导入的资源的标识符\n资源地址通常为 terraform在定义这类资源时配置的（对应提供商支持），以 GCP 为例 Cloud Storage 存储桶， google_storage_bucket.sample，sample 为 id，定义如下\nyaml 1 2 3 4 5 6 resource \u0026#34;google_storage_bucket\u0026#34; \u0026#34;sample\u0026#34; { name = \u0026#34;my-bucket\u0026#34; project = \u0026#34;sample-project\u0026#34; location = \u0026#34;US\u0026#34; force_destroy = true } 示例 - 导入现有GKE集群 语法\nbash 1 terraform import \u0026lt;resource_name\u0026gt;.\u0026lt;name\u0026gt; \u0026lt;project\u0026gt;/\u0026lt;locations\u0026gt;/\u0026lt;real_resource_name\u0026gt; 实例\nbash 1 terraform import google_container_cluster.gke project20231124/asia-east2/gke-prd-cluster-02 输出结果如下\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ terraform import google_container_cluster.","title":"使用terraform利用已有资源创建GKE集群"},{"content":"rclone工具的特点 支持增量，配置简单，支持参数调节吞吐量（不同吞吐量使用内存不同，传输差异也不同） copy是复制 source 到 dst sync是根据 src 的内容对比 dst，删除dst不存在的内容 下面是写了一同步的脚本\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 #!/bin/bash # Function to perform full sync (all files) for a specific bucket sync_full() { local bucket_name=$1 local batch_id=$2 local differ_log=\u0026#34;/tmp/${bucket_name}_differ_${batch_id}.log\u0026#34; local combined_log=\u0026#34;/tmp/${bucket_name}_${batch_id}.log\u0026#34; local missing_dst=\u0026#34;/tmp/${bucket_name}_md_${batch_id}.log\u0026#34; echo \u0026#34;Performing full sync from $bucket_name (ceph-1 to ceph-2)...\u0026#34; rclone --config=/root/.config/rclone/rclone.conf sync --fast-list --combined=\u0026#34;$combined_log\u0026#34; --differ=\u0026#34;$differ_log\u0026#34; --missing-on-dst=\u0026#34;$missing_dst\u0026#34; \u0026#34;ceph-1:$bucket_name\u0026#34; \u0026#34;ceph-220:$bucket_name\u0026#34; --multi-thread-streams=20 if [ $? -eq 0 ]; then echo \u0026#34;$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;) - Full sync for $bucket_name successful.\u0026#34; \u0026gt;\u0026gt; \u0026#34;$log_file\u0026#34; else echo \u0026#34;$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;) - Full sync for $bucket_name failed.\u0026#34; \u0026gt;\u0026gt; \u0026#34;$log_file\u0026#34; fi } # Function to perform partial sync based on --max-age for a specific bucket sync_partial() { local bucket_name=$1 local max_age=$2 local batch_id=$3 local differ_log=\u0026#34;/tmp/${bucket_name}_differ_${batch_id}.log\u0026#34; local combined_log=\u0026#34;/tmp/${bucket_name}_${batch_id}.log\u0026#34; local missing_dst=\u0026#34;/tmp/${bucket_name}_md_${batch_id}.log\u0026#34; echo \u0026#34;Performing partial sync from $bucket_name (ceph-1 to ceph-2) with --max-age=$max_age...\u0026#34; rclone --config=/root/.config/rclone/rclone.conf sync --fast-list --combined=\u0026#34;$combined_log\u0026#34; --differ=\u0026#34;$differ_log\u0026#34; --missing-on-dst=\u0026#34;$missing_dst\u0026#34; \u0026#34;ceph-1:$bucket_name\u0026#34; \u0026#34;ceph-220:$bucket_name\u0026#34; --max-age \u0026#34;$max_age\u0026#34; --multi-thread-streams=20 if [ $? -eq 0 ]; then echo \u0026#34;$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;) - Partial sync for $bucket_name successful.\u0026#34; \u0026gt;\u0026gt; \u0026#34;$log_file\u0026#34; else echo \u0026#34;$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;) - Partial sync for $bucket_name failed.\u0026#34; \u0026gt;\u0026gt; \u0026#34;$log_file\u0026#34; fi } # Main function to perform sync based on input parameters main() { local bucket_name=$1 local sync_type=$2 local max_age=$3 local batch_id=$(date \u0026#39;+%Y%m%d%H%M%S\u0026#39;) # Unique batch ID based on timestamp # Start time local start_time=$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;) local start_timestamp=$(date +%s) # Log file path local log_file=\u0026#34;/tmp/rclone_sync_${batch_id}.log\u0026#34; echo \u0026#34;Script started at: $start_time\u0026#34; \u0026gt;\u0026gt; \u0026#34;$log_file\u0026#34; # Perform sync based on type case \u0026#34;$sync_type\u0026#34; in full) sync_full \u0026#34;$bucket_name\u0026#34; \u0026#34;$batch_id\u0026#34; \u0026gt;\u0026gt; \u0026#34;$log_file\u0026#34; 2\u0026gt;\u0026amp;1 ;; partial) if [ -z \u0026#34;$max_age\u0026#34; ]; then echo \u0026#34;Error: max_age is required for partial sync.\u0026#34; \u0026gt;\u0026gt; \u0026#34;$log_file\u0026#34; exit 1 fi sync_partial \u0026#34;$bucket_name\u0026#34; \u0026#34;$max_age\u0026#34; \u0026#34;$batch_id\u0026#34; \u0026gt;\u0026gt; \u0026#34;$log_file\u0026#34; 2\u0026gt;\u0026amp;1 ;; *) echo \u0026#34;Error: Invalid sync type. Choose \u0026#39;full\u0026#39; or \u0026#39;partial\u0026#39;.\u0026#34; \u0026gt;\u0026gt; \u0026#34;$log_file\u0026#34; echo \u0026#34;Usage: $0 \u0026lt;bucket_name\u0026gt; \u0026lt;sync_type\u0026gt; [max_age]\u0026#34; \u0026gt;\u0026gt; \u0026#34;$log_file\u0026#34; exit 1 ;; esac # End time local end_time=$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;) local end_timestamp=$(date +%s) # Calculate duration local duration=$((end_timestamp - start_timestamp)) echo \u0026#34;Script finished at: $end_time\u0026#34; \u0026gt;\u0026gt; \u0026#34;$log_file\u0026#34; echo \u0026#34;Total execution time: ${duration}s\u0026#34; \u0026gt;\u0026gt; \u0026#34;$log_file\u0026#34; echo \u0026#34;Log file saved at: $log_file\u0026#34; } # Check arguments count if [ \u0026#34;$#\u0026#34; -lt 2 ]; then echo \u0026#34;Usage: $0 \u0026lt;bucket_name\u0026gt; \u0026lt;sync_type\u0026gt; [max_age]\u0026#34; echo \u0026#34;\u0026lt;sync_type\u0026gt;: full or partial\u0026#34; echo \u0026#34;[max_age]: Required for partial sync (e.g., 24h, 7d)\u0026#34; exit 1 fi main \u0026#34;$@\u0026#34; 本次传输吞吐量测试 传输环境的数据类型\n文件类型 文件数量 Bucket大小 传输用时 小文件和大文件结合 326338 58.263 GiB 首次传输 25分钟 (1538s)\n--size-only 可以把上述缩减到 26s 执行输出\nbash 1 2 3 4 5 6 7 8 Script started at: 2024-09-24 08:35:04 Performing full sync from hk-im (ceph-1 to ceph-2)... 2024-09-24 09:00:41 - Full sync for hk-im successful. Script finished at: 2024-09-24 09:00:42 Total execution time: 1538s $ wc -l hk-im_20240924083504.log 326338 hk-im_20240924083504.log 图：集群在传输时网络流量 查看 bucket 文件总大小和数量\nbash 1 2 3 $ rclone size ceph-1:hk-im Total objects: 326.338k (326338) Total size: 58.263 GiB (62559469310 Byte) 传输时CPU使用情况\nbash 1 2 3 4 5 6 $ ps aux --sort=-%mem|head -10 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 24405 150 2.9 1696216 239824 pts/0 Sl 10:51 6:00 rclone --config=/root/.config/rclone/rclone.conf sync --fast-list --combined=/tmp/hk-im_20240924105109.log --differ=/tmp/hk-im_differ_20240924105109.log --missing-on-dst=/tmp/hk-im_md_20240924105109.log ceph-1:hk-im ceph-2:hk-im --multi-thread-streams=200 --multi-thread-chunk-size=512Mi root 1031 0.0 0.2 574296 23704 ? Ssl Sep23 0:16 /usr/bin/python2 -Es /usr/sbin/tuned -l -P polkitd 740 0.0 0.2 612376 18040 ? Ssl Sep23 0:02 /usr/lib/polkit-1/polkitd --no-debug root 538 0.0 0.2 47652 17360 ? Ss Sep23 0:15 /usr/lib/systemd/systemd-journald 使用 --size-only 吞吐量会大很多，但是内存会使用多一些，\nbash 1 2 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 25387 137 5.6 1694040 464452 pts/0 Sl 11:00 0:30 rclone --config=/root/.config/rclone/rclone.conf sync --size-only --fast-list --combined=/tmp/hk-im_20240924110024.log --di 再不使用 --size-only 时，IOPS会高一些，使用 \u0026ndash;size-only时应该对比条件减少，增加了速度\n图：CEPH cluster dst 图：CEPH cluster src Reference Difference between rclone sync for s3 and aws s3 sync ","permalink":"https://www.161616.top/rgw-bucket-sync-with-rclone/","summary":"rclone工具的特点 支持增量，配置简单，支持参数调节吞吐量（不同吞吐量使用内存不同，传输差异也不同） copy是复制 source 到 dst sync是根据 src 的内容对比 dst，删除dst不存在的内容 下面是写了一同步的脚本\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 #!","title":"使用rclone工具完成bucket数据同步"},{"content":"问题：当使用的结构体为嵌套格式，会提示 recursion detected 或 cannot find type definition\ngo 1 2 3 4 5 6 7 8 9 10 11 type Instance struct { metav1.TypeMeta Instances []InstanceItem `json:\u0026#34;instances\u0026#34; yaml:\u0026#34;instances\u0026#34; form:\u0026#34;instances\u0026#34; binding:\u0026#34;required\u0026#34;` ServiceSelector map[string]string `json:\u0026#34;serivce_selector\u0026#34; yaml:\u0026#34;serivce_selector\u0026#34; form:\u0026#34;serivce_selector\u0026#34;` } type InstanceItem struct { Name string `json:\u0026#34;name\u0026#34; yaml:\u0026#34;name\u0026#34; form:\u0026#34;name\u0026#34; binding:\u0026#34;required\u0026#34;` PromEndpoint string `json:\u0026#34;prom_endpoint\u0026#34; yaml:\u0026#34;prom_endpoint\u0026#34; form:\u0026#34;prom_endpoint\u0026#34; binding:\u0026#34;required\u0026#34;` Labels map[string]string `json:\u0026#34;labels\u0026#34; yaml:\u0026#34;labels\u0026#34; form:\u0026#34;labels\u0026#34;` } go swagger 注释为\ntext 1 2 3 4 5 6 7 8 9 10 // deleteInstance godoc // @Summary Remove prometheus instance. // @Description Remove prometheus instance. // @Tags Instances // @Accept json // @Produce json // @Param query body instance.Instance false \u0026#34;body\u0026#34; // @securityDefinitions.apikey BearerAuth // @Success 200 {object} interface{} // @Router /ph/v1/instance [DELETE] 执行命令时报错如下：\nbash 1 2 3 4 5 6 7 8 9 10 $ swag init -g cmd/ph-server/main.go --output ./docs/ --packageName docs 2024/09/22 19:56:19 Generate swagger docs.... 2024/09/22 19:56:19 Generate general API Info, search dir:./ 2024/09/22 19:56:19 warning: failed to get package name in dir: ./, error: execute go list command, exit status 1, stdout:, stderr:no Go files in /mnt/d/src/go/work/prometheus-hub 2024/09/22 19:56:19 Generating instance.Instance 2024/09/22 19:56:19 Error parsing type definition \u0026#39;instance.Instance\u0026#39;: : cannot find type definition: metav1.TypeMeta 2024/09/22 19:56:19 Skipping \u0026#39;instance.Instance\u0026#39;, recursion detected. 2024/09/22 19:56:19 Skipping \u0026#39;instance.Instance\u0026#39;, recursion detected. 2024/09/22 19:56:19 Skipping \u0026#39;instance.Instance\u0026#39;, recursion detected. 2024/09/22 19:56:19 Generating target.TargetItem 解决： --parseDependency bash 1 2 3 4 5 6 7 8 9 10 11 $ swag init -g cmd/ph-server/main.go --output ./docs/ \\ --packageName docs \\ --parseDependency --parseInternal 2024/09/22 20:20:40 Generate swagger docs.... 2024/09/22 20:20:40 Generate general API Info, search dir:./ 2024/09/22 20:20:40 warning: failed to get package name in dir: ./, error: execute go list command, exit status 1, stdout:, stderr:no Go files in /mnt/d/src/go/work/prometheus-hub 2024/09/22 20:20:41 warning: failed to evaluate const mProfCycleWrap at /usr/local/go/src/runtime/mprof.go:165:7, reflect: call of reflect.Value.Len on zero Value 2024/09/22 20:20:41 Generating github_com_cylonchau_prometheus-hub_pkg_apis_instance.Instance 2024/09/22 20:20:41 Generating github_com_cylonchau_prometheus-hub_pkg_apis_meta_v1.TypeMeta 2024/09/22 20:20:41 Generating github_com_cylonchau_prometheus-hub_pkg_apis_instance.InstanceItem Reference How to use a type definition in another file with swaggo?\n","permalink":"https://www.161616.top/goswagger-skipping-recursion-detected/","summary":"问题：当使用的结构体为嵌套格式，会提示 recursion detected 或 cannot find type definition\ngo 1 2 3 4 5 6 7 8 9 10 11 type Instance struct { metav1.TypeMeta Instances []InstanceItem `json:\u0026#34;instances\u0026#34; yaml:\u0026#34;instances\u0026#34; form:\u0026#34;instances\u0026#34; binding:\u0026#34;required\u0026#34;` ServiceSelector map[string]string `json:\u0026#34;serivce_selector\u0026#34; yaml:\u0026#34;serivce_selector\u0026#34; form:\u0026#34;serivce_selector\u0026#34;` } type InstanceItem struct { Name string `json:\u0026#34;name\u0026#34; yaml:\u0026#34;name\u0026#34; form:\u0026#34;name\u0026#34; binding:\u0026#34;required\u0026#34;` PromEndpoint string `json:\u0026#34;prom_endpoint\u0026#34; yaml:\u0026#34;prom_endpoint\u0026#34; form:\u0026#34;prom_endpoint\u0026#34; binding:\u0026#34;required\u0026#34;` Labels map[string]string `json:\u0026#34;labels\u0026#34; yaml:\u0026#34;labels\u0026#34; form:\u0026#34;labels\u0026#34;` } go swagger 注释为\ntext 1 2 3 4 5 6 7 8 9 10 // deleteInstance godoc // @Summary Remove prometheus instance.","title":"Goswagger - Skipping '', recursion detected"},{"content":"遇到问题：gin 使用 Bind 时无法填充，改成下面代码可以获取到\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 type User struct { Name string `form:\u0026#34;name,default=user1\u0026#34; json:\u0026#34;name,default=user2\u0026#34;` Age int `form:\u0026#34;age,default=10\u0026#34; json:\u0026#34;age,default=20\u0026#34;` } r := gin.Default() // way1 curl 127.0.0.1:8900/bind?name=aa // way2 curl -X POST 127.0.0.1:8900/bind -d \u0026#34;name=aa\u0026amp;age=30\u0026#34; // way3 curl -X POST 127.0.0.1:8900/bind -H \u0026#34;Content-Type: application/json\u0026#34; -d \u0026#34;{\\\u0026#34;name\\\u0026#34;: \\\u0026#34;aa\\\u0026#34;}\u0026#34; r.Any(\u0026#34;/bind\u0026#34;, func(c *gin.Context) { var user User //user = User{Name: \u0026#34;bb\u0026#34;, Age: 11} //way4:A variable of type User can be generated with the default value before bind if c.ContentType() == binding.MIMEJSON { //way5:A variable of type User can be generated with the default value before bind. _ = binding.MapFormWithTag(\u0026amp;user, nil, \u0026#34;json\u0026#34;) } _ = c.Bind(\u0026amp;user) //Note that because bind is used here to request json, you specify the Content-Type header c.String(200, \u0026#34;Hello %v age %v\u0026#34;, user.Name, user.Age) }) // The above 4 way. // way1/2 structTag is work.because gin at queryBinding/formBinding execute mapFormByTag logic, will check formTag // way3 structTag not work. gin at jsonBinding non-execution mapFormByTag logic // way4/way5 no matter query/form/json All valid // way5 is work. Because the mapFormByTag logic is triggered in addition r.Run(\u0026#34;:8900\u0026#34;) Reference Bind should support default values\n","permalink":"https://www.161616.top/gin-param-default-value/","summary":"遇到问题：gin 使用 Bind 时无法填充，改成下面代码可以获取到\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 type User struct { Name string `form:\u0026#34;name,default=user1\u0026#34; json:\u0026#34;name,default=user2\u0026#34;` Age int `form:\u0026#34;age,default=10\u0026#34; json:\u0026#34;age,default=20\u0026#34;` } r := gin.Default() // way1 curl 127.0.0.1:8900/bind?name=aa // way2 curl -X POST 127.0.0.1:8900/bind -d \u0026#34;name=aa\u0026amp;age=30\u0026#34; // way3 curl -X POST 127.0.0.1:8900/bind -H \u0026#34;Content-Type: application/json\u0026#34; -d \u0026#34;{\\\u0026#34;name\\\u0026#34;: \\\u0026#34;aa\\\u0026#34;}\u0026#34; r.","title":"Gin - 参数默认值问题"},{"content":"遇到问题：BeforeDelete 在删除时获取 SQL 不正确\nBeforeDelete 代码如下\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func (t *Target) BeforeDelete(tx *gorm.DB) (err error) { // 找到与此 Target 相关的所有 Labels var labels []Label if err := tx.Model(t).Association(\u0026#34;Labels\u0026#34;).Find(\u0026amp;labels); err != nil { klog.V(4).Infof(\u0026#34;Error fetching labels: %v\u0026#34;, err) return err } for _, label := range labels { if err := tx.Delete(\u0026amp;label).Error; err != nil { klog.V(4).Infof(\u0026#34;Error deleting label:\u0026#34;, err) return err } } // 注意：由于设置了 OnDelete:SET NULL，因此在删除 Labels 后，会清理 target_labels 表中的关联 return nil } 删除写法\ngo 1 2 3 4 5 6 7 8 9 func DeleteTargets(target *target.TargetItem) (enconterError error) { // 创建 Target existingTarget := \u0026amp;Target{} if enconterError = DB.Model(\u0026amp;Target{}).Where(\u0026#34;address = ? AND metric_path = ? AND scrape_time = ? AND scrape_timeout = ?\u0026#34;, target.Address, target.MetricPath, target.ScrapeTime, target.ScrapeTimeout).Delete(existingTarget).Error; enconterError != nil { return enconterError // 如果找不到记录，则返回错误 } return enconterError } 删除时遇到的问题：SQL 生成不正确 target_id IN (NULL)\nbash 1 2 3 4 BeforeDelete hook triggered 2024/09/22 00:33:36 /mnt/d/src/go/work/prometheus-hub/pkg/model/target.go:37 [0.184ms] [rows:0] SELECT `labels`.`id`,`labels`.`key`,`labels`.`value` FROM `labels` JOIN `target_labels` ON `target_labels`.`label_id` = `labels`.`id` AND `target_labels`.`target_id` IN (NULL) 问题原因，在删除这条记录时，默认 (t *Target) 必须 id 存在，如果不存在就是 NULL，所以先用 find 查询保证这个操作的 t 中存在主键才可以。\ngo 1 2 3 4 5 6 7 8 9 func DeleteTargets(target *target.TargetItem) (enconterError error) { // 创建 Target existingTarget := \u0026amp;Target{} if enconterError = DB.Model(\u0026amp;Target{}).Where(\u0026#34;address = ? AND metric_path = ? AND scrape_time = ? AND scrape_timeout = ?\u0026#34;, target.Address, target.MetricPath, target.ScrapeTime, target.ScrapeTimeout).Find(existingTarget).Delete(existingTarget).Error; enconterError != nil { return enconterError // 如果找不到记录，则返回错误 } return enconterError } 修改后的输出 SQL\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 2024/09/22 00:41:21 /mnt/d/src/go/work/prometheus-hub/pkg/model/target.go:33 [0.126ms] [rows:3] SELECT `labels`.`id`,`labels`.`key`,`labels`.`value` FROM `labels` JOIN `target_labels` ON `target_labels`.`label_id` = `labels`.`id` AND `target_labels`.`target_id` = 17 2024/09/22 00:41:21 /mnt/d/src/go/work/prometheus-hub/pkg/model/target.go:39 [5.229ms] [rows:1] DELETE FROM `labels` WHERE `labels`.`id` = 7 2024/09/22 00:41:21 /mnt/d/src/go/work/prometheus-hub/pkg/model/target.go:39 [0.078ms] [rows:1] DELETE FROM `labels` WHERE `labels`.`id` = 8 2024/09/22 00:41:21 /mnt/d/src/go/work/prometheus-hub/pkg/model/target.go:39 [0.062ms] [rows:1] DELETE FROM `labels` WHERE `labels`.`id` = 9 2024/09/22 00:41:21 /mnt/d/src/go/work/prometheus-hub/pkg/model/target.go:70 [15.661ms] [rows:1] DELETE FROM `targets` WHERE (address = \u0026#34;10.0.0.14:9090\u0026#34; AND metric_path = \u0026#34;/metrics\u0026#34; AND scrape_time = 30 AND scrape_timeout = 10) AND `targets`.`id` = 17 ","permalink":"https://www.161616.top/gorm-before-delete/","summary":"遇到问题：BeforeDelete 在删除时获取 SQL 不正确\nBeforeDelete 代码如下\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func (t *Target) BeforeDelete(tx *gorm.DB) (err error) { // 找到与此 Target 相关的所有 Labels var labels []Label if err := tx.Model(t).Association(\u0026#34;Labels\u0026#34;).Find(\u0026amp;labels); err != nil { klog.V(4).Infof(\u0026#34;Error fetching labels: %v\u0026#34;, err) return err } for _, label := range labels { if err := tx.Delete(\u0026amp;label).Error; err != nil { klog.","title":"Gorm - BeforeDelete无法获取正确条目"},{"content":"nacos-k8s nacos-k8s 是Nacos官方维护的项目，可以使用 helm 直接在 k8s 集群中部署 nacos 集群（包含公有云）\n部署步骤 找到数据库表结构 在你要安装的版本号的配置中找到 SQL 文件进行创建库操作，例如 github.com/alibaba/nacos/tree/2.4.1/distribution/conf\n自定义 helm 资源和配置 在公有云上部署，还需要修改下对应资源的类型，例如建立 LB\nservice 增加了自动获取 gcp 预留的 IP，和service改为LB类型\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 service: #type: ClusterIP #type: NodePort type: LoadBalancer port: 8848 nodePort: 30000 # 这样可以使用静态IP loadBalancerIP: 192.168.0.1 annotations: # 这个annotation 原自官方创建 load-balancer的方式 cloud.google.com/load-balancer-type: Internal labels: {} cloud.google.com/load-balancer-type GKE 的 service LB 类型的参数 [1]\nservice.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type: \u0026ldquo;intranet\u0026rdquo; ACK 的 service LB 类型的参数 [2]\ntemplate yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 # cat templates/service.yaml # 集群模式建立 headless service {{- if and (eq .Values.global.mode \u0026#34;cluster\u0026#34;) }} apiVersion: v1 kind: Service metadata: name: nacos-hs namespace: {{ .Values.namespace }} spec: clusterIP: None publishNotReadyAddresses: true ports: - port: {{ .Values.service.port }} targetPort: {{ .Values.nacos.serverPort }} protocol: TCP name: http - port: {{ add .Values.service.port 1000}} name: client-rpc targetPort: {{add .Values.nacos.serverPort 1000}} - port: {{add .Values.service.port 1001}} name: raft-rpc targetPort: {{add .Values.nacos.serverPort 1001}} ## 兼容1.4.x版本的选举端口 - port: 7848 name: old-raft-rpc targetPort: 7848 protocol: TCP selector: app.kubernetes.io/name: {{ include \u0026#34;nacos.name\u0026#34; . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} --- {{- if and (eq .Values.global.mode \u0026#34;cluster\u0026#34;) }} apiVersion: v1 kind: Service metadata: name: nacos-hs-internal namespace: {{ .Values.namespace }} annotations: {{- range $key, $value := .Values.service.annotations }} {{ $key }}: {{ $value | quote }} {{- end }} spec: externalTrafficPolicy: Cluster internalTrafficPolicy: Cluster ipFamilyPolicy: SingleStack {{- if .Values.service.loadBalancerIP }} loadBalancerIP: {{ .Values.service.loadBalancerIP }} {{- end }} ports: - port: {{ .Values.service.port }} targetPort: {{ .Values.nacos.serverPort }} protocol: TCP name: http - port: {{ add .Values.service.port 1000}} name: client-rpc targetPort: {{add .Values.nacos.serverPort 1000}} - port: {{add .Values.service.port 1001}} name: raft-rpc targetPort: {{add .Values.nacos.serverPort 1001}} ## 兼容1.4.x版本的选举端口 - port: 7848 name: old-raft-rpc targetPort: 7848 protocol: TCP selector: app.kubernetes.io/name: {{ include \u0026#34;nacos.name\u0026#34; . }} app.kubernetes.io/instance: {{ .Release.Name }} type: {{ .Values.service.type }} {{- end }} --- # 单机模式使用cs，不增加 LB {{- if and (eq .Values.global.mode \u0026#34;standalone\u0026#34;) }} apiVersion: v1 kind: Service metadata: name: nacos-cs namespace: {{ .Values.namespace }} labels: {{- toYaml .Values.service.labels | nindent 4 }} annotations: {{- range $key, $value := .Values.service.annotations }} {{ $key }}: {{ $value | quote }} {{- end }} spec: type: {{ .Values.service.type }} {{- if .Values.service.loadBalancerIP }} loadBalancerIP: {{ .Values.service.loadBalancerIP }} {{- end }} ports: - port: {{ .Values.service.port }} targetPort: {{ .Values.nacos.serverPort }} protocol: TCP name: http - port: {{ add .Values.service.port 1000}} name: client-rpc targetPort: {{add .Values.nacos.serverPort 1000}} - port: {{add .Values.service.port 1001}} name: raft-rpc targetPort: {{add .Values.nacos.serverPort 1001}} ## 兼容1.4.x版本的选举端口 - port: 7848 name: old-raft-rpc targetPort: 7848 protocol: TCP {{- if eq .Values.service.type \u0026#34;NodePort\u0026#34; }} nodePort: {{ .Values.service.nodePort }} {{- end }} selector: app.kubernetes.io/name: {{ include \u0026#34;nacos.name\u0026#34; . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} 最后直接使用 helm 安装即可\nbash 1 helm upgrade nacos . -f values.yaml --install --create-namespace -n nacos --dry-run Reference [1] LoadBalancer Service 参数 [2] 通过Annotation配置传统型负载均衡CLB\n","permalink":"https://www.161616.top/nacos-deploy-with-gcp-eks/","summary":"nacos-k8s nacos-k8s 是Nacos官方维护的项目，可以使用 helm 直接在 k8s 集群中部署 nacos 集群（包含公有云）\n部署步骤 找到数据库表结构 在你要安装的版本号的配置中找到 SQL 文件进行创建库操作，例如 github.com/alibaba/nacos/tree/2.4.1/distribution/conf\n自定义 helm 资源和配置 在公有云上部署，还需要修改下对应资源的类型，例如建立 LB\nservice 增加了自动获取 gcp 预留的 IP，和service改为LB类型\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 service: #type: ClusterIP #type: NodePort type: LoadBalancer port: 8848 nodePort: 30000 # 这样可以使用静态IP loadBalancerIP: 192.168.0.1 annotations: # 这个annotation 原自官方创建 load-balancer的方式 cloud.google.com/load-balancer-type: Internal labels: {} cloud.google.com/load-balancer-type GKE 的 service LB 类型的参数 [1]\nservice.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type: \u0026ldquo;intranet\u0026rdquo; ACK 的 service LB 类型的参数 [2]","title":"Kubernetes公有云集群中部署Nacos集群"},{"content":"本文记录了在使用 ceph 集群时遭遇到的内存问题，以及引用和参考一些资料用于对在 ceph 集群使用时的内存预估。\nOSD的内存需求 如何评估 Ceph OSD 所需的硬件也是对于集群选型，集群优化的一个必要条件，这里主要找到两个可靠的参考资料用于评估 OSD 内存配置大小\nIBM Storage Ceph IBM Storage Ceph 提供了一个运行 Ceph 用于预估系统配置的一个最小推荐列表 [1]，个人感觉可以参考这些信息用于自己集群的优化。主要用于容器化的 Ceph 集群\nProcess Criteria Minimum Recommended ceph-osd-container Processor 1x AMD64 or Intel 64 CPU CORE per OSD container RAM Minimum of 5 GB of RAM per OSD container OS Disk 1x OS disk per host OSD Storage 1x storage drive per OSD container. Cannot be shared with OS Disk. block.db Optional, but IBM recommended, 1x SSD or NVMe or Optane partition or lvm per daemon. Sizing is 4% of block.data for BlueStore for object, file, and mixed workloads and 1% of block.data for the BlueStore for Block Device, Openstack cinder, and Openstack cinder workloads. block.wal Optionally, 1x SSD or NVMe or Optane partition or logical volume per daemon. Use a small size, for example 10 GB, and only if it’s faster than the block.db device. Network 2x 10 GB Ethernet NICs ceph-mon-container Processor 1x AMD64 or Intel 64 CPU CORE per mon-container RAM 3 GB per mon-container Disk Space 10 GB per mon-container, 50 GB Recommended Monitor Disk Optionally, 1x SSD disk for Monitor rocksdb data Network 2x 1 GB Ethernet NICs, 10 GB Recommended Prometheus 20 GB to 50 GB under /var/lib/ceph/ directory created as a separate file system to protect the contents under /var/ directory. ceph-mgr-container Processor 1x AMD64 or Intel 64 CPU CORE per mgr-container RAM 3 GB per mgr-container Network 2x 1 GB Ethernet NICs, 10 GB Recommended ceph-radosgw-container Processor 1x AMD64 or Intel 64 CPU CORE per radosgw-container RAM 1 GB per daemon Disk Space 5 GB per daemon Network 1x 1 GB Ethernet NICs ceph-mds-container Processor 1x AMD64 or Intel 64 CPU CORE per mds-container RAM 3 GB per mds-container This number is highly dependent on the configurable MDS cache size. The RAM requirement is typically twice as much as the amount set in the mds_cache_memory_limit configuration setting. Note also that this is the memory for your daemon, not the overall system memory. Disk Space 2 GB per mds-container, plus considering any additional space required for possible debug logging, 20 GB is a good start. Network 2x 1 GB Ethernet NICs, 10 GB Recommended Note that this is the same network as the OSD containers. If you have a 10 GB network on your OSDs you should use the same on your MDS so that the MDS is not disadvantaged when it comes to latency. Hardware Recommendations Ceph 官方也提供了相应的硬件配置推荐，关键参数写的比较清晰，但实际的规模比较模棱两可，也是可以提供一些参考的，并且每个版本的 Ceph 所推荐的硬件也是不相同的。\n下表是 Ceph nautilus 的推荐最小硬件 [2]\nProcess Criteria Minimum Recommended ceph-osd Processor 1x 64-bit AMD-64 1x 32-bit ARM dual-core or better RAM ~1GB for 1TB of storage per daemon Volume Storage 1x storage drive per daemon Journal 1x SSD partition per daemon (optional) Network 2x 1GB Ethernet NICs ceph-mon Processor 1x 64-bit AMD-64 1x 32-bit ARM dual-core or better RAM 1 GB per daemon Disk Space 10 GB per daemon Network 2x 1GB Ethernet NICs ceph-mds Processor 1x 64-bit AMD-64 quad-core 1x 32-bit ARM quad-core RAM 1 GB minimum per daemon Disk Space 1 MB per daemon Network 2x 1GB Ethernet NICs 下表是 reef 版本的官方推荐最小配置 [3]\nProcess Criteria Bare Minimum and Recommended ceph-osd Processor 1 core minimum, 2 recommended 1 core per 200-500 MB/s throughput 1 core per 1000-3000 IOPS Results are before replication. Results may vary across CPU and drive models and Ceph configuration: (erasure coding, compression, etc) ARM processors specifically may require more cores for performance. SSD OSDs, especially NVMe, will benefit from additional cores per OSD. Actual performance depends on many factors including drives, net, and client throughput and latency. Benchmarking is highly recommended. RAM 4GB+ per daemon (more is better) 2-4GB may function but may be slow Less than 2GB is not recommended Storage Drives 1x storage drive per OSD DB/WAL (optional) 1x SSD partion per HDD OSD 4-5x HDD OSDs per DB/WAL SATA SSD \u0026lt;= 10 HDD OSDss per DB/WAL NVMe SSD Network 1x 1Gb/s (bonded 10+ Gb/s recommended) ceph-mon Processor 2 cores minimum RAM 5GB+ per daemon (large / production clusters need more) Storage 100 GB per daemon, SSD is recommended Network 1x 1Gb/s (10+ Gb/s recommended) ceph-mds Processor 2 cores minimum RAM 2GB+ per daemon (more for production) Disk Space 1 GB per daemon Network 1x 1Gb/s (10+ Gb/s recommended) 我们使用Ceph环境的示例 用于 Openstack 环境的 Ceph OSD 使用内存记录，主要使用于RDB，机器配置为 1.8T, 900G 的混合硬盘，内存配置 512G， 可以看到 OSD 内存使用率在 0.3% 大概每个 OSD 使用内存量为 2GB。\nbash 1 2 3 4 5 6 7 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 225398 ceph 20 0 3849464 1.7g 22200 S 11.9 0.3 14501:14 ceph-osd 224860 ceph 20 0 3612380 1.7g 22424 S 9.2 0.3 12697:04 ceph-osd 223902 ceph 20 0 3340844 1.7g 22172 S 8.6 0.3 21003:18 ceph-osd 223440 ceph 20 0 3213884 1.7g 22288 S 5.9 0.3 8548:00 ceph-osd 224368 ceph 20 0 3292848 1.6g 22204 S 4.0 0.3 8655:56 ceph-osd 222889 ceph 20 0 3231012 1.7g 22180 S 3.3 0.3 8190:03 ceph-osd 用于业务使用的 Ceph OSD，主要用于对象存储，机器配置为 8c/16G，硬盘是 700G 每块，可以看到每个 OSD 使用的内存大概为 1.8-2G，大概 OSD 的分布是每个节点最多三个 OSD。\nCeph node 01\nbash 1 2 3 4 5 6 7 8 9 10 11 12 # ceph node 01 $ ps aux --sort=-%mem | head -10 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ceph 1702 0.4 27.9 10128296 4550760 ? Ssl May03 919:18 /usr/bin/radosgw -f --cluster ceph --name client.rgw.node01 --setuser ceph --setgroup ceph ceph 1721 0.6 12.8 3318456 2088704 ? Ssl May03 1216:59 /usr/bin/ceph-osd -f --cluster ceph --id 6 --setuser ceph --setgroup ceph ceph 1983 0.6 12.3 3358788 2012844 ? Ssl May03 1273:25 /usr/bin/ceph-osd -f --cluster ceph --id 3 --setuser ceph --setgroup ceph ceph 1991 0.9 11.7 3451788 1912008 ? Ssl May03 1719:04 /usr/bin/ceph-osd -f --cluster ceph --id 2 --setuser ceph --setgroup ceph ceph 1709 0.5 7.4 1646276 1212576 ? Ssl May03 1047:48 /usr/bin/ceph-mds -f --cluster ceph --id node01 --setuser ceph --setgroup ceph ceph 18979 1.0 4.5 1330064 742680 ? Ssl May03 1932:51 /usr/bin/ceph-mon -f --cluster ceph --id node01 --setuser ceph --setgroup ceph ceph 529617 3.7 4.4 1909588 721492 ? Ssl Jul15 3140:39 /usr/bin/ceph-mgr -f --cluster ceph --id node01 --setuser ceph --setgroup ceph root 801 0.0 0.6 182536 98516 ? Ss May03 105:28 /usr/lib/systemd/systemd-journald root 1704 0.0 0.3 701284 50132 ? Ssl May03 53:48 /usr/sbin/rsyslogd -n Ceph node02\nbash 1 2 3 4 5 6 7 8 $ ps aux --sort=-%mem | head -10 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ceph 28650 1.4 12.8 3958988 2104296 ? Ssl 2023 6214:07 /usr/bin/ceph-osd -f --cluster ceph --id 9 --setuser ceph --setgroup ceph ceph 163854 1.4 12.7 3782156 2096396 ? Ssl 2023 6092:28 /usr/bin/ceph-osd -f --cluster ceph --id 10 --setuser ceph --setgroup ceph ceph 3801660 1.5 11.9 3389284 1959812 ? Ssl Jul10 1384:08 /usr/bin/ceph-osd -f --cluster ceph --id 11 --setuser ceph --setgroup ceph root 3348820 0.1 0.1 510848 27732 ? Sl Jun27 171:24 /var/ossec/bin/wazuh-modulesd root 1045 0.0 0.1 574296 21468 ? Ssl 2023 85:44 /usr/bin/python2 -Es /usr/sbin/tuned -l -P polkitd 670 0.0 0.0 612348 14992 ? Ssl 2023 10:40 /usr/lib/polkit-1/polkitd --no-debug Ceph node03\nbash 1 2 3 4 5 6 7 8 9 10 $ ps aux --sort=-%mem | head -10 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ceph 1942206 0.9 12.8 4214720 2092280 ? Ssl 2023 7866:23 /usr/bin/ceph-osd -f --cluster ceph --id 7 --setuser ceph --setgroup ceph ceph 2824 0.8 12.6 4274848 2051800 ? Ssl 2022 7205:58 /usr/bin/ceph-osd -f --cluster ceph --id 4 --setuser ceph --setgroup ceph ceph 2802022 0.7 12.5 3831320 2047440 ? Ssl 2023 4078:51 /usr/bin/ceph-osd -f --cluster ceph --id 1 --setuser ceph --setgroup ceph ceph 1693 0.7 4.7 1439428 771228 ? Ssl 2022 6767:46 /usr/bin/ceph-mon -f --cluster ceph --id node03 --setuser ceph --setgroup ceph ceph 1058494 0.3 2.2 7492512 367288 ? Ssl 2023 3388:44 /usr/bin/radosgw -f --cluster ceph --name client.rgw.node03 --setuser ceph --setgroup ceph ceph 1812870 2.6 0.8 970928 133116 ? Ssl Mar21 6749:43 /usr/bin/ceph-mgr -f --cluster ceph --id node03 --setuser ceph --setgroup ceph root 778 0.0 0.1 76412 28084 ? Ss 2022 113:06 /usr/lib/systemd/systemd-journald ceph 1739 0.4 0.1 384760 28064 ? Ssl 2022 4086:33 /usr/bin/ceph-mds -f --cluster ceph --id node03 --setuser ceph --setgroup ceph Ceph node04，该节点上只有一个 OSD\nbash 1 2 3 4 $ ps aux --sort=-%mem | head -10 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ceph 83779 1.0 12.7 3911168 2087332 ? Ssl Jan23 3473:50 /usr/bin/ceph-osd -f --cluster ceph --id 12 --setuser ceph --setgroup ceph root 6568 0.0 0.0 113020 7808 ? Ss Jan22 0:00 /usr/sbin/sshd -D 配置的一个需求 osd 在运行在没有限制的情况下运行会消耗所有的可用内存，所以当数据节点配置不当，也会引起 oomkiller\nThe OSDs are designed to consume all the available memory if they are run without limits. So it is recommended to apply the resource limits, and the OSDs will stay within the bounds you set. Typically 4GB is sufficient per OSD. [4]\n当 OSD 经历恢复时，它们的内存利用率会达到峰值。如果可用的 RAM 不足，OSD 性能会显着降低，守护进程甚至可能崩溃或被 Linux OOM Killer杀死。[5]\n使用 cephadm 部署的机器群可以通过下面命令查看内存使用情况\nbash 1 ceph orch ps 通常只有两种类型的守护进程有内存限制：mon 和 osd，这些内存限制参数由如下配置进行控制的\nbash 1 2 3 4 sudo ceph config get mon mon_memory_target # in bytes sudo ceph config get mon mon_memory_autotune sudo ceph config get osd osd_memory_target # in bytes sudo ceph config get osd osd_memory_target_autotune 通过 orch ps 查看的内存限制是不同于 ceph osd 的目标值的，BlueStore 将 OSD 堆内存使用量保留在指定目标大小下，并使用 osd_memory_target 配置选项。\n选项 osd_memory_target 根据系统中可用的 RAM 来设置 OSD 内存。当 TCMalloc 配置为内存分配器，BlueStore 中的 bluestore_cache_autotune 选项设为 true 时，则使用此选项。\n查看现有集群 osd 的配置\nbash 1 2 3 4 # 显示存储集群中的所有 OSD osd_memory_target sudo ceph config get osd osd_memory_target # 显示指定 OSD osd_memory_target sudo ceph config get osd.0 osd_memory_target 配置集群 OSD osd_memory_target\nbash 1 2 3 4 # 为存储集群中的所有 OSD 设置 osd_memory_target ceph config set osd osd_memory_target VALUE # 为存储集群中的指定 OSD 设置 osd_memory_target，.id 是 OSD 的 ID ceph config set osd.id osd_memory_target VALUE 网上案例 下面有两个网上搜到的案例，osd具有无限制的内存增长的案例\nosd(s) with unlimited ram growth [6] How to solve “the Out of Memory Killer issue that kills your OSDs due to bad entries in PG logs” [7] 内存查看 使用统计命令，该命令的统计信息不需要运行探查器，也不会将堆分配信息转储到文件中。\nbash 1 ceph tell osd.0 heap stats 使用内存池命令\nbash 1 ceph daemon osd.NNN dump_mempools 使用 google-perftools，该命令会运行探针，来检测运行的命令\nbash 1 2 3 google-pprof --text {path-to-daemon} {log-path/filename} # 例如 pprof --text /usr/bin/ceph-mon /var/log/ceph/mon.node1.profile.0001.heap Reference [1] Minimum hardware considerations\n[2] minimum-hardware-recommendations nautilus\n[3] minimum-hardware-recommendations reef\n[4] Excessive OSD memory usage #12078\n[5] Ceph OSD 故障排除之内存不足\n[6] osd(s) with unlimited ram growth\n[7] How to solve “the Out of Memory Killer issue that kills your OSDs due to bad entries in PG logs”\n[8] Memory Profiling\n","permalink":"https://www.161616.top/ch03-3-ceph-osd-performance-recommendation/","summary":"本文记录了在使用 ceph 集群时遭遇到的内存问题，以及引用和参考一些资料用于对在 ceph 集群使用时的内存预估。\nOSD的内存需求 如何评估 Ceph OSD 所需的硬件也是对于集群选型，集群优化的一个必要条件，这里主要找到两个可靠的参考资料用于评估 OSD 内存配置大小\nIBM Storage Ceph IBM Storage Ceph 提供了一个运行 Ceph 用于预估系统配置的一个最小推荐列表 [1]，个人感觉可以参考这些信息用于自己集群的优化。主要用于容器化的 Ceph 集群\nProcess Criteria Minimum Recommended ceph-osd-container Processor 1x AMD64 or Intel 64 CPU CORE per OSD container RAM Minimum of 5 GB of RAM per OSD container OS Disk 1x OS disk per host OSD Storage 1x storage drive per OSD container. Cannot be shared with OS Disk.","title":"Ceph OSD内存优化与建议"},{"content":" 本文是Ceph集群部署系列第4章 使用cephadm纯离线安装Ceph集群 使用cephadm纯离线安装Ceph集群 2 Ceph集群安装 - ceph-deploy Ceph集群安装 - ceph-deploy下线rgw 记录一次因着急没有检查原因而直接下线 ceph 对象存储的的失败记录\n操作流程 ceph 节点内存持续超过90%，因为本身有三个 OSD，检查内存使用情况发现 radosgw\nbash 1 2 3 4 5 6 7 8 9 10 11 $ ps aux --sort=-%mem | head -10 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ceph 1702 0.4 32.9 10128296 4550760 ? Ssl May03 919:18 /usr/bin/radosgw -f --cluster ceph --name client.rgw.node01 --setuser ceph --setgroup ceph ceph 1721 0.6 12.8 3318456 2088704 ? Ssl May03 1216:59 /usr/bin/ceph-osd -f --cluster ceph --id 6 --setuser ceph --setgroup ceph ceph 1983 0.6 12.3 3358788 2012844 ? Ssl May03 1273:25 /usr/bin/ceph-osd -f --cluster ceph --id 3 --setuser ceph --setgroup ceph ceph 1991 0.9 11.7 3451788 1912008 ? Ssl May03 1719:04 /usr/bin/ceph-osd -f --cluster ceph --id 2 --setuser ceph --setgroup ceph ceph 1709 0.5 7.4 1646276 1212576 ? Ssl May03 1047:48 /usr/bin/ceph-mds -f --cluster ceph --id node01 --setuser ceph --setgroup ceph ceph 18979 1.0 4.5 1330064 742680 ? Ssl May03 1932:51 /usr/bin/ceph-mon -f --cluster ceph --id node01 --setuser ceph --setgroup ceph ceph 529617 3.7 4.4 1909588 721492 ? Ssl Jul15 3140:39 /usr/bin/ceph-mgr -f --cluster ceph --id node01 --setuser ceph --setgroup ceph root 801 0.0 0.6 182536 98516 ? Ss May03 105:28 /usr/lib/systemd/systemd-journald root 1704 0.0 0.3 701284 50132 ? Ssl May03 53:48 /usr/sbin/rsyslogd -n 因为这台节点包含3个 OSD, ceph-mon, ceph-mds 等全功能使用，所以最初的想法是 radosgw 转移到其他节点上，而不是分析为什么 radosgw 进程使用内存较高\n申请一个新节点部署 radosgw，部署时出现错误没有提示日志\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 $ ceph-deploy rgw create node06 [ceph_deploy.conf][DEBUG ] found configuration file at: /home/ceph/.cephdeploy.conf [ceph_deploy.cli][INFO ] Invoked (2.0.1): /bin/ceph-deploy rgw create node06 [ceph_deploy.cli][INFO ] ceph-deploy options: [ceph_deploy.cli][INFO ] username : None [ceph_deploy.cli][INFO ] verbose : False [ceph_deploy.cli][INFO ] rgw : [(\u0026#39;node06\u0026#39;, \u0026#39;rgw.node06\u0026#39;)] eph_deploy.cli][INFO ] overwrite_conf : False [ceph_deploy.cli][INFO ] subcommand : create [ceph_deploy.cli][INFO ] quiet : False [ceph_deploy.cli][INFO ] cd_conf : \u0026lt;ceph_deploy.conf.cephdeploy.Conf instance at 0x7fe8a9b583f8\u0026gt; [ceph_deploy.cli][INFO ] cluster : ceph [ceph_deploy.cli][INFO ] func : \u0026lt;function rgw at 0x7fe8aa412050\u0026gt; [ceph_deploy.cli][INFO ] ceph_conf : None [ceph_deploy.cli][INFO ] default_release : False [ceph_deploy.rgw][DEBUG ] Deploying rgw, cluster ceph hosts node06:rgw.node06 Warning: Permanently added \u0026#39;[node06]:55556,[192.168.20.88]:55556\u0026#39; (ECDSA) to the list of known hosts. [node06][DEBUG ] connection detected need for sudo Warning: Permanently added \u0026#39;[node06]:55556,[192.168.20.88]:55556\u0026#39; (ECDSA) to the list of known hosts. We trust you have received the usual lecture from the local System Administrator. It usually boils down to these three things: #1) Respect the privacy of others. #2) Think before you type. #3) With great power comes great responsibility. sudo: no tty present and no askpass program specified [ceph_deploy.rgw][ERROR ] connecting to host: node06 resulted in errors: IOError cannot send (already closed?) [ceph_deploy][ERROR ] GenericError: Failed to create 1 RGWs 通过 journalctl -u 查看到如下错误 (新节点)\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Sep 12 14:51:42 node06 sshd[30495]: error: Could not load host key: /etc/ssh/ssh_host_dsa_key Sep 12 14:51:42 node06 sshd[30495]: Accepted publickey for ceph from 192.168.20.88 port 37872 ssh2: RSA SHA256:XBYUcCiYBhdw+V32qwx6x0wex1EhaMiSHuz0gQVayTQ Sep 12 14:51:42 node06 systemd[1]: Created slice User Slice of ceph. Sep 12 14:51:42 node06 systemd-logind[743]: New session 97 of user ceph. Sep 12 14:51:42 node06 systemd[1]: Started Session 97 of user ceph. Sep 12 14:51:42 node06 sshd[30495]: pam_unix(sshd:session): session opened for user ceph by (uid=0) Sep 12 14:51:42 node06 sshd[30497]: Received disconnect from 192.168.20.88 port 37872:11: disconnected by user Sep 12 14:51:42 node06 sshd[30497]: Disconnected from 192.168.20.88 port 37872 Sep 12 14:51:42 node06 sshd[30495]: pam_unix(sshd:session): session closed for user ceph Sep 12 14:51:42 node06 systemd-logind[743]: Removed session 97. Sep 12 14:51:42 node06 systemd[1]: Removed slice User Slice of ceph. Sep 12 14:51:42 node06 sshd[30521]: error: Could not load host key: /etc/ssh/ssh_host_dsa_key Sep 12 14:51:42 node06 sshd[30521]: Accepted publickey for ceph from 192.168.20.88 port 37874 ssh2: RSA SHA256:XBYUcCiYBhdw+V32qwx6x0wex1EhaMiSHuz0gQVayTQ Sep 12 14:51:42 node06 systemd[1]: Created slice User Slice of ceph. Sep 12 14:51:42 node06 systemd-logind[743]: New session 98 of user ceph. Sep 12 14:51:42 node06 systemd[1]: Started Session 98 of user ceph. Sep 12 14:51:42 node06 sshd[30521]: pam_unix(sshd:session): session opened for user ceph by (uid=0) Sep 12 14:51:42 node06 sudo[30526]: pam_unix(sudo:auth): conversation failed Sep 12 14:51:42 node06 sudo[30526]: pam_unix(sudo:auth): auth could not identify password for [ceph] Sep 12 14:51:45 node06 sudo[30526]: ceph : user NOT in sudoers ; TTY=unknown ; PWD=/home/ceph ; USER=root ; COMMAND=/bin/python2 -c import sys;exec(eval(sys.stdin.readline())) Sep 12 14:51:45 node06 sshd[30525]: Received disconnect from 192.168.20.88 port 37874:11: disconnected by user Sep 12 14:51:45 node06 sshd[30525]: Disconnected from 192.168.20.88 port 37874 Sep 12 14:51:45 node06 sshd[30521]: pam_unix(sshd:session): session closed for user ceph Sep 12 14:51:45 node06 postfix/sendmail[30549]: fatal: parameter inet_interfaces: no local interface found for ::1 配置 sudo\nbash 1 2 #cat /etc/sudoers.d/ceph ceph ALL = (root) NOPASSWD:ALL 配置完成后部署 radosgw\nbash 1 2 3 4 5 6 # 拷贝配置文件 ceph-deploy --overwrite-conf config push node06 # 安装软件包 ceph-deploy install --no-adjust-repos --nogpgcheck node06 # new一个新 rgw 实例，ceph-deploy 只支持new ceph-deploy rgw create node06 完整的输出\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 $ ceph-deploy --overwrite-conf config push node06 [ceph_deploy.conf][DEBUG ] found configuration file at: /home/ceph/.cephdeploy.conf [ceph_deploy.cli][INFO ] Invoked (2.0.1): /bin/ceph-deploy --overwrite-conf config push node06 [ceph_deploy.cli][INFO ] ceph-deploy options: [ceph_deploy.cli][INFO ] username : None [ceph_deploy.cli][INFO ] verbose : False [ceph_deploy.cli][INFO ] overwrite_conf : True [ceph_deploy.cli][INFO ] subcommand : push [ceph_deploy.cli][INFO ] quiet : False [ceph_deploy.cli][INFO ] cd_conf : \u0026lt;ceph_deploy.conf.cephdeploy.Conf instance at 0x7f3e96c9e8c0\u0026gt; [ceph_deploy.cli][INFO ] cluster : ceph [ceph_deploy.cli][INFO ] client : [\u0026#39;node06\u0026#39;] [ceph_deploy.cli][INFO ] func : \u0026lt;function config at 0x7f3e96ec9c08\u0026gt; [ceph_deploy.cli][INFO ] ceph_conf : None [ceph_deploy.cli][INFO ] default_release : False [ceph_deploy.config][DEBUG ] Pushing config to node06 Warning: Permanently added \u0026#39;[node06]:55556,[192.168.20.88]:55556\u0026#39; (ECDSA) to the list of known hosts. [node06][DEBUG ] connection detected need for sudo Warning: Permanently added \u0026#39;[node06]:55556,[192.168.20.88]:55556\u0026#39; (ECDSA) to the list of known hosts. [node06][DEBUG ] connected to host: node06 [node06][DEBUG ] detect platform information from remote host [node06][DEBUG ] detect machine type [node06][DEBUG ] write cluster configuration to /etc/ceph/{cluster}.conf $ ceph-deploy install --no-adjust-repos --nogpgcheck node06 [ceph_deploy.conf][DEBUG ] found configuration file at: /home/ceph/.cephdeploy.conf [ceph_deploy.cli][INFO ] Invoked (2.0.1): /bin/ceph-deploy install --no-adjust-repos --nogpgcheck node06 [ceph_deploy.cli][INFO ] ceph-deploy options: [ceph_deploy.cli][INFO ] verbose : False [ceph_deploy.cli][INFO ] testing : None [ceph_deploy.cli][INFO ] cd_conf : \u0026lt;ceph_deploy.conf.cephdeploy.Conf instance at 0x7fc503ac4758\u0026gt; [ceph_deploy.cli][INFO ] cluster : ceph [ceph_deploy.cli][INFO ] dev_commit : None [ceph_deploy.cli][INFO ] install_mds : False [ceph_deploy.cli][INFO ] stable : None [ceph_deploy.cli][INFO ] default_release : False [ceph_deploy.cli][INFO ] username : None [ceph_deploy.cli][INFO ] adjust_repos : False [ceph_deploy.cli][INFO ] func : \u0026lt;function install at 0x7fc5041125f0\u0026gt; [ceph_deploy.cli][INFO ] install_mgr : False [ceph_deploy.cli][INFO ] install_all : False [ceph_deploy.cli][INFO ] repo : False [ceph_deploy.cli][INFO ] host : [\u0026#39;node06\u0026#39;] [ceph_deploy.cli][INFO ] install_rgw : False [ceph_deploy.cli][INFO ] install_tests : False [ceph_deploy.cli][INFO ] repo_url : None [ceph_deploy.cli][INFO ] ceph_conf : None [ceph_deploy.cli][INFO ] install_osd : False [ceph_deploy.cli][INFO ] version_kind : stable [ceph_deploy.cli][INFO ] install_common : False [ceph_deploy.cli][INFO ] overwrite_conf : False [ceph_deploy.cli][INFO ] quiet : False [ceph_deploy.cli][INFO ] dev : master [ceph_deploy.cli][INFO ] nogpgcheck : True [ceph_deploy.cli][INFO ] local_mirror : None [ceph_deploy.cli][INFO ] release : None [ceph_deploy.cli][INFO ] install_mon : False [ceph_deploy.cli][INFO ] gpg_url : None [ceph_deploy.install][DEBUG ] Installing stable version mimic on cluster ceph hosts node06 [ceph_deploy.install][DEBUG ] Detecting platform for host node06 ... Warning: Permanently added \u0026#39;[node06]:55556,[192.168.20.88]:55556\u0026#39; (ECDSA) to the list of known hosts. [node06][DEBUG ] connection detected need for sudo Warning: Permanently added \u0026#39;[node06]:55556,[192.168.20.88]:55556\u0026#39; (ECDSA) to the list of known hosts. [node06][DEBUG ] connected to host: node06 [node06][DEBUG ] detect platform information from remote host [node06][DEBUG ] detect machine type [ceph_deploy.install][INFO ] Distro info: CentOS Linux 7.9.2009 Core [node06][INFO ] installing Ceph on node06 [node06][INFO ] Running command: sudo yum clean all [node06][DEBUG ] Loaded plugins: fastestmirror [node06][DEBUG ] Cleaning repos: base centos-sclo-rh centos-sclo-sclo devops-Extra epel extras [node06][DEBUG ] : openresty remi-php72 remi-php73 remi-php74 remi-safe salt-latest [node06][DEBUG ] : tools-repo updates zabbix [node06][DEBUG ] Cleaning up list of fastest mirrors [node06][DEBUG ] Other repos take up 23 M of disk space (use --verbose for details) [node06][INFO ] Running command: sudo yum -y install ceph ceph-radosgw [node06][DEBUG ] Loaded plugins: fastestmirror [node06][DEBUG ] Determining fastest mirrors [node06][DEBUG ] No package ceph available. [node06][DEBUG ] Nothing to do [node06][INFO ] Running command: sudo ceph --version [node06][DEBUG ] ceph version 14.2.22 (ca74598065096e6fcbd8433c8779a2be0c889351) nautilus (stable) 查看节点是否上线\nbash 1 2 3 4 5 6 7 8 9 10 11 12 $ ceph -s cluster: id: baf87797-3ec1-4f2c-8126-bf0a44051b13 health: HEALTH_WARN 1 pools have many more objects per pg than average services: mon: 3 daemons, quorum node01,node02,node03 (age 2w) mgr: node01(active, since 8w), standbys: node02, node03 mds: kubefs:2 {0=node01=up:active,1=node02=up:active} 1 up:standby osd: 13 osds: 13 up (since 6d), 13 in (since 7M) rgw: 4 daemons active (node01, node02, node03, node06) 流量的请求时访问 radosgw 服务，这个时候新实例是没有引入流量的，需要修改负载均衡器增加新的节点进来，流量引入后需要确认旧服务已经不在处理业务请求后可以下线 确认请求，查看活跃连接\nbash 1 2 3 $ netstat -an|grep 7480 tcp 0 0 0.0.0.0:7480 0.0.0.0:* LISTEN tcp 0 0 192.168.20.84:7480 192.168.20.84:33152 ESTABLISHED 确认请求，查看服务日志\nbash 1 $ tail -f /var/log/ceph/ceph-client.rgw.node01.log 确认无误可以下线，ceph-deploy 部署的服务没有 cephadm ceph orch rgw delete xx 这类工具进行下线，直接通过 systemd 停止服务即可\nbash 1 2 3 4 $ systemctl -l|grep rados ceph-radosgw@rgw.node01.service loaded active running Ceph rados gateway system-ceph\\x2dradosgw.slice loaded active active system-ceph\\x2dradosgw.slice ceph-radosgw.target loaded active active ceph target allowing to start/stop all ceph-radosgw@.service instances at once 停止服务并检查内存状态\nbash 1 2 3 4 5 6 7 8 9 $ systemctl stop ceph-radosgw@rgw.node01.service #ps axu --sort=-%mem|head -10 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ceph 1983 0.6 12.8 3358788 2084324 ? Ssl May03 1275:36 /usr/bin/ceph-osd -f --cluster ceph --id 3 --setuser ceph --setgroup ceph ceph 1991 0.9 12.5 3451788 2033560 ? Ssl May03 1722:12 /usr/bin/ceph-osd -f --cluster ceph --id 2 --setuser ceph --setgroup ceph ceph 1721 0.6 11.8 3318456 1920876 ? Ssl May03 1219:27 /usr/bin/ceph-osd -f --cluster ceph --id 6 --setuser ceph --setgroup ceph ceph 1709 0.5 7.4 1646276 1212516 ? Ssl May03 1050:21 /usr/bin/ceph-mds -f --cluster ceph --id node01 --setuser ceph --setgroup ceph ceph 18979 1.0 4.5 1330064 744972 ? Ssl May03 1937:16 /usr/bin/ceph-mon -f --cluster ceph --id node01 --setuser ceph --setgroup ceph ceph 529617 3.7 4.4 1914452 726436 ? Ssl Jul15 3153:14 /usr/bin/ceph-mgr -f --cluster ceph --id node01 --setuser ceph --setgroup ceph 总结 本次操作没有分析为什么使用内存高，只是着急做了迁移，这样导致在事后无法确定问题的根本原因，后期遇到问题要先分析并保留证据，其次在做迁移之类动作。\n","permalink":"https://www.161616.top/ch02-4-ceph-deploy-offline-rgw/","summary":"本文是Ceph集群部署系列第4章 使用cephadm纯离线安装Ceph集群 使用cephadm纯离线安装Ceph集群 2 Ceph集群安装 - ceph-deploy Ceph集群安装 - ceph-deploy下线rgw 记录一次因着急没有检查原因而直接下线 ceph 对象存储的的失败记录\n操作流程 ceph 节点内存持续超过90%，因为本身有三个 OSD，检查内存使用情况发现 radosgw\nbash 1 2 3 4 5 6 7 8 9 10 11 $ ps aux --sort=-%mem | head -10 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ceph 1702 0.4 32.9 10128296 4550760 ? Ssl May03 919:18 /usr/bin/radosgw -f --cluster ceph --name client.rgw.node01 --setuser ceph --setgroup ceph ceph 1721 0.","title":"记录一次失败的radosgw问题排查记录"},{"content":"今日 GKE EOL，kubelet 自动升级至1.28后，Java程序在启动后无法识别资源清单中的限制，被大量OOMKill\nDeployment清单中已经配置了资源限制，例如下面的参数\nyaml 1 2 3 4 5 resources: limits: memory: \u0026#34;1Gi\u0026#34; requests: memory: \u0026#34;600Mi\u0026#34; JAVA_OPS参数配置是使用百分比\nbash 1 -XX:+UseContainerSupport -XX:InitialRAMPercentage=70.0 -XX:MaxRAMPercentage=70.0 但是启动后无法识别参数，使用 gcloud 登录到主机内查看 jvm 运行状态（因为容器使用 distroless）\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 project-20220325-asia-east-2-pool-221ab289-hgnf ~ # nsenter -t 274655 --mount --uts --ipc --net --pid /opt/java/openjdk/bin/java -XX:+UnlockDiagnosticVMOptions -XX:+PrintContainerInfo -version OSContainer::init: Initializing Container Support Detected cgroups v2 unified hierarchy Path to /cpu.max is /sys/fs/cgroup/system.slice/sshd.service/cpu.max Open of file /sys/fs/cgroup/system.slice/sshd.service/cpu.max failed, No such file or directory CPU Quota is: -2 Path to /cpu.max is /sys/fs/cgroup/system.slice/sshd.service/cpu.max Open of file /sys/fs/cgroup/system.slice/sshd.service/cpu.max failed, No such file or directory CPU Period is: -2 Path to /cpu.weight is /sys/fs/cgroup/system.slice/sshd.service/cpu.weight Open of file /sys/fs/cgroup/system.slice/sshd.service/cpu.weight failed, No such file or directory Raw value for CPU Shares is: -2 OSContainer::active_processor_count: 16 CgroupSubsystem::active_processor_count (cached): 16 total physical memory: 67435528192 Path to /memory.max is /sys/fs/cgroup/system.slice/sshd.service/memory.max Open of file /sys/fs/cgroup/system.slice/sshd.service/memory.max failed, No such file or directory Memory Limit is: -2 container memory limit failed: -2, using host value 67435528192 CgroupSubsystem::active_processor_count (cached): 16 Path to /cpu.max is /sys/fs/cgroup/system.slice/sshd.service/cpu.max Open of file /sys/fs/cgroup/system.slice/sshd.service/cpu.max failed, No such file or directory CPU Quota is: -2 Path to /cpu.max is /sys/fs/cgroup/system.slice/sshd.service/cpu.max Open of file /sys/fs/cgroup/system.slice/sshd.service/cpu.max failed, No such file or directory CPU Period is: -2 Path to /cpu.weight is /sys/fs/cgroup/system.slice/sshd.service/cpu.weight Open of file /sys/fs/cgroup/system.slice/sshd.service/cpu.weight failed, No such file or directory Raw value for CPU Shares is: -2 OSContainer::active_processor_count: 16 openjdk version \u0026#34;1.8.0_422\u0026#34; OpenJDK Runtime Environment (Temurin)(build 1.8.0_422-b05) OpenJDK 64-Bit Server VM (Temurin)(build 25.422-b05, mixed mode) 异常的主机\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 project-20220325-asia-east-2-pool-221ab289-hgnf ~ # nsenter -t 281811 --mount --uts --ipc --net --pid /usr/local/jdk1.8.0_351/bin/java -XX:+UnlockDiagnosticVMOptions -XX:+PrintContainerInfo -version OSContainer::init: Initializing Container Support Required cgroup memory subsystem not found java version \u0026#34;1.8.0_351\u0026#34; Java(TM) SE Runtime Environment (build 1.8.0_351-b10) Java HotSpot(TM) 64-Bit Server VM (build 25.351-b10, mixed mode) project-20220325-asia-east-2-145239dd-0fzj ~ # nsenter -t 1517879 --mount --uts --ipc --net --pid /usr/local/jdk1.8.0_351/bin/java -XX:+UnlockDiagnosticVMOptions -XX:+PrintContainerInfo -version OSContainer::init: Initializing Container Support subsystem_file_line_contents: subsystem path is NULL subsystem_file_line_contents: subsystem path is NULL subsystem_file_line_contents: subsystem path is NULL subsystem_file_line_contents: subsystem path is NULL subsystem_file_line_contents: subsystem path is NULL OSContainer::active_processor_count: 16 OSContainer::active_processor_count (cached): 16 container memory limit failed: -2, using host value container memory limit failed: -2, using host value subsystem_file_line_contents: subsystem path is NULL subsystem_file_line_contents: subsystem path is NULL subsystem_file_line_contents: subsystem path is NULL OSContainer::active_processor_count: 16 subsystem_file_line_contents: subsystem path is NULL subsystem_file_line_contents: subsystem path is NULL subsystem_file_line_contents: subsystem path is NULL OSContainer::active_processor_count: 16 java version \u0026#34;1.8.0_351\u0026#34; Java(TM) SE Runtime Environment (build -b10) Java HotSpot(TM) 64-Bit Server VM (build 25.351-b10, mixed mode) 查询到 jdk 版本不同，搜索 jdk 版本，发现 jdk 低版本对 cgroup2 无法识别出内存限制, 8u381 后版本才修复，而使用的是 8u351\nDetected container memory limit may exceed physical machine memory\nReference Release Note: Cgroup v2 Support and Improvements in 8u381 Detected container memory limit may exceed physical machine memory JVM内存配置最佳实践 ","permalink":"https://www.161616.top/gke-invalid-pod-limits/","summary":"今日 GKE EOL，kubelet 自动升级至1.28后，Java程序在启动后无法识别资源清单中的限制，被大量OOMKill\nDeployment清单中已经配置了资源限制，例如下面的参数\nyaml 1 2 3 4 5 resources: limits: memory: \u0026#34;1Gi\u0026#34; requests: memory: \u0026#34;600Mi\u0026#34; JAVA_OPS参数配置是使用百分比\nbash 1 -XX:+UseContainerSupport -XX:InitialRAMPercentage=70.0 -XX:MaxRAMPercentage=70.0 但是启动后无法识别参数，使用 gcloud 登录到主机内查看 jvm 运行状态（因为容器使用 distroless）\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 project-20220325-asia-east-2-pool-221ab289-hgnf ~ # nsenter -t 274655 --mount --uts --ipc --net --pid /opt/java/openjdk/bin/java -XX:+UnlockDiagnosticVMOptions -XX:+PrintContainerInfo -version OSContainer::init: Initializing Container Support Detected cgroups v2 unified hierarchy Path to /cpu.","title":"GKE强制升级后JAVA Pod无法识别limit限制"},{"content":"What is an Uranus? Uranus is a Linux firewalld central controller. In Greek mythology, Uranus king of gods. The firewall gateway is the Uranus for iptables.\nPrerequisites Hardware requirements We recommend these hardware requirements for production systems or for development systems that are designed to demonstrate production use cases:\nItem Description Minimum requirements Recommended Per instance You can install on one node but many features require at least one node. 1 instance \u0026gt; 1 instances RAM per instance Defining your RAM size must be part of the capacity planning for your Uranus usage. 512 Mb \u0026gt;= 1GB Persistent Storage The amount of storage space for each node. 1 GB \u0026gt;= 10GB Software requirements Item Description Recommended OS / Platform Linux, Kubernetes Debian 11\nCentos 7 Firewalld 0.6.3\n0.9.2 Centos 7 default version\nDebian 11 default version Build and run Uranus Setup an Uranus with Binary mode Build and run Uranus backend bash 1 git clone https://github.com/cylonchau/firewalld-gateway.git Compile\nbash 1 cd firewalld-gateway \u0026amp;\u0026amp; make build Frist time you need migrate database\nbash 1 2 # currently sql-driver support sqlite or mysql ./_output/firewalld-gateway --migration --sql-driver=sqlite --config firewalld-gateway.toml -v 10 Inital API Doc\nbash 1 swag init -g cmd/main.go --output ./docs/ --packageName docs Run Uranus\nbash 1 ./_output/firewalld-gateway --sql-driver=sqlite --config firewalld-gateway.toml -v 5 Setup Uranus frontend Install Nginx\nbash 1 2 3 yum install nginx -y # or apt install nginx -y Configure nginx\nbash 1 2 3 4 5 cd /etc/nginx/ \u0026amp;\u0026amp; \\ mv nginx.conf nginx.conf.default grep -Ev \u0026#39;^$|#\u0026#39; nginx.conf.default \u0026gt; nginx.conf \u0026amp;\u0026amp; \\ sed -i \u0026#39;/include/i \\ include /etc/nginx/conf.d/*.conf;\u0026#39; nginx.conf \u0026amp;\u0026amp; \\ cd conf.d Create fw.conf in conf.d directory\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 cat \u0026lt;\u0026lt;EOF \u0026gt; fw.conf server { listen 80; root /var/run/dist; location / { try_files \\$uri \\$uri/ @router; index index.html; } location ~ /fw/(?\u0026lt;section\u0026gt;.*) { proxy_pass http://10.0.0.1:2952/fw/\\$section\\$is_args\\$args; proxy_set_header X-Forwarded-Host \\$server_name; proxy_set_header X-Forwarded-Port \\$server_port; proxy_set_header X-Forwarded-Server \\$host; proxy_set_header X-Forwarded-Scheme \\$scheme; proxy_set_header X-Forwarded-URI \\$request_uri; proxy_set_header X-Real-IP \\$remote_addr; proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for; } location ~ /security/(?\u0026lt;section\u0026gt;.*) { proxy_pass http://10.0.0.1:2952/security/\\$section\\$is_args\\$args; proxy_set_header X-Forwarded-Host \\$server_name; proxy_set_header X-Forwarded-Port \\$server_port; proxy_set_header X-Forwarded-Server \\$host; proxy_set_header X-Forwarded-Scheme \\$scheme; proxy_set_header X-Forwarded-URI \\$request_uri; proxy_set_header X-Real-IP \\$remote_addr; proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for; } location ~ /sso/(?\u0026lt;section\u0026gt;.*) { proxy_pass http://10.0.0.1:2952/sso/\\$section\\$is_args\\$args; proxy_set_header X-Forwarded-Host \\$server_name; proxy_set_header X-Forwarded-Port \\$server_port; proxy_set_header X-Forwarded-Server \\$host; proxy_set_header X-Forwarded-Scheme \\$scheme; proxy_set_header X-Forwarded-URI \\$request_uri; proxy_set_header X-Real-IP \\$remote_addr; proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for; } location /ping { proxy_pass http://10.0.0.1:2952/ping; proxy_set_header X-Forwarded-Port \\$server_port; proxy_set_header X-Forwarded-Server \\$host; proxy_set_header X-Forwarded-Scheme \\$scheme; proxy_set_header X-Forwarded-URI \\$request_uri; proxy_set_header X-Real-IP \\$remote_addr; proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for; } } EOF Copy dist directory to /var/run/\nbash 1 mv dist /var/run/ Start nginx\nbash 1 systemctl start nginx Setup an Uranus with Docker build docker image bash 1 git clone https://github.com/cylonchau/firewalld-gateway.git Build image\nbash 1 docker build -t cylonchau/uranus:v0.0.5 . Run\nbash 1 docker run -d --rm --name uranus -p 2953:2953 cylonchau/uranus:v0.0.5 Notes: this mode default using sqlite, so if you want use external database, please change config file, then build image\nSetup firewalld Download Default, we provide 2 version firewalld variant version\nCentos 7 or Centos 6 Debian 11 You can download and install those firewalld vesion in you Linux\nhttps://github.com/cylonchau/firewalld/releases\nInstall Centos 7\nbash 1 2 rpm -e python-firewall-0.6.3-11 --nodeps \u0026amp;\u0026amp; rpm -ivh python-firewall-0.6.3-4.el7.noarch.rpm Debian 11\nbash 1 2 dpkg -r python3-firewall \u0026amp;\u0026amp; \\ dpkg -i python3-firewall_0.9.3-2_amd64.deb Configure Enable dbug remote mode Centos Edit /etc/dbus-1/system.conf\nxml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 \u0026lt;!-- This configuration file is no longer required and may be removed. In older versions of dbus, this file defined the behaviour of the well-known system bus. That behaviour is now determined by /usr/share/dbus-1/system.conf, which should not be edited. For local configuration changes, create a file system-local.conf or files matching system.d/*.conf in the same directory as this one, with a \u0026lt;busconfig\u0026gt; element containing configuration directives. These directives can override D-Bus or OS defaults. For upstream or distribution-wide defaults that can be overridden by a local sysadmin, create files matching /usr/share/dbus-1/system.d/*.conf instead. --\u0026gt; \u0026lt;!DOCTYPE busconfig PUBLIC \u0026#34;-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN\u0026#34; \u0026#34;http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\u0026#34;\u0026gt; \u0026lt;busconfig\u0026gt; \u0026lt;listen\u0026gt;tcp:host=10.0.0.3,bind=*,port=55556,family=ipv4\u0026lt;/listen\u0026gt; \u0026lt;listen\u0026gt;unix:tmpdir=/tmp\u0026lt;/listen\u0026gt; \u0026lt;!-- Add this part --\u0026gt; \u0026lt;policy context=\u0026#34;default\u0026#34;\u0026gt; \u0026lt;allow user=\u0026#34;root\u0026#34; /\u0026gt; \u0026lt;allow own=\u0026#34;com.github.cylonchau.Uranus\u0026#34; /\u0026gt; \u0026lt;!-- allow uranus resiger to dbus-daemon --\u0026gt; \u0026lt;!-- if requseter is com.github.cylonchau.Uranus and request path is /org/fedoraproject/FirewallD1, then allow --\u0026gt; \u0026lt;allow receive_sender=\u0026#34;com.github.cylonchau.Uranus\u0026#34; receive_path=\u0026#34;/org/fedoraproject/FirewallD1\u0026#34; /\u0026gt; \u0026lt;/policy\u0026gt; \u0026lt;auth\u0026gt;ANONYMOUS\u0026lt;/auth\u0026gt; \u0026lt;allow_anonymous/\u0026gt; \u0026lt;/busconfig\u0026gt; Enable dbus tcp port Edit /usr/lib/systemd/system/dbus.socket\ntext 1 2 3 4 5 6 [Unit] Description=D-Bus System Message Bus Socket [Socket] ListenStream=/var/run/dbus/system_bus_socket ListenStream=55556 # \u0026lt;- Add this Reload service\nbash 1 systemctl reload firewalld Debian Edit /etc/dbus-1/system.conf\nxml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 \u0026lt;!DOCTYPE busconfig PUBLIC \u0026#34;-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN\u0026#34; \u0026#34;http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\u0026#34;\u0026gt; \u0026lt;busconfig\u0026gt; \u0026lt;listen\u0026gt;tcp:host=10.0.0.3,bind=*,port=55556,family=ipv4\u0026lt;/listen\u0026gt; \u0026lt;listen\u0026gt;unix:tmpdir=/tmp\u0026lt;/listen\u0026gt; \u0026lt;!-- Add this part --\u0026gt; \u0026lt;policy context=\u0026#34;default\u0026#34;\u0026gt; \u0026lt;allow user=\u0026#34;root\u0026#34; /\u0026gt; \u0026lt;allow own=\u0026#34;com.github.cylonchau.Uranus\u0026#34; /\u0026gt; \u0026lt;!-- allow uranus resiger to dbus-daemon --\u0026gt; \u0026lt;!-- if requseter is com.github.cylonchau.Uranus and request path is /org/fedoraproject/FirewallD1, then allow --\u0026gt; \u0026lt;allow receive_sender=\u0026#34;com.github.cylonchau.Uranus\u0026#34; receive_path=\u0026#34;/org/fedoraproject/FirewallD1\u0026#34; /\u0026gt; \u0026lt;/policy\u0026gt; \u0026lt;auth\u0026gt;ANONYMOUS\u0026lt;/auth\u0026gt; \u0026lt;allow_anonymous/\u0026gt; \u0026lt;/busconfig\u0026gt; Edit /usr/lib/systemd/system/dbus.socket\ntext 1 2 3 4 5 6 7 [Unit] Description=D-Bus System Message Bus Socket # Add this part [Socket] ListenStream=/var/run/dbus/system_bus_socket ListenStream=55556 Add managed firewalld Linux host to Uranus Add host\n","permalink":"https://www.161616.top/uranus-installation/","summary":"What is an Uranus? Uranus is a Linux firewalld central controller. In Greek mythology, Uranus king of gods. The firewall gateway is the Uranus for iptables.\nPrerequisites Hardware requirements We recommend these hardware requirements for production systems or for development systems that are designed to demonstrate production use cases:\nItem Description Minimum requirements Recommended Per instance You can install on one node but many features require at least one node. 1 instance \u0026gt; 1 instances RAM per instance Defining your RAM size must be part of the capacity planning for your Uranus usage.","title":"Uranus installation"},{"content":"Manual Judgment Stage “Manual Judgment Stage” 是 Spinnaker Pipeline 中的一种阶段 (\u0026ldquo;Stage\u0026rdquo;) 类型，该类型可以作为流水线的门户，作为带外 (Out-of-Bound) 流水线检查，等待手动检查，并且判断结果会终止或继续流水线的执行。\n创建一个基于Manual Judgment的流水线 创建一个流水线，添加一个新的 Stage，选择 “Manual Judgment”\n图：Manual Judgment创建页面 Jugement Inputs 部分添加对应的选项，选项可以带入变量 $judgment 中\n图：Manual Judgment 在执行时 Jugement Inputs Option 的展示 或者是不添加任何选项，那么这个时候就会只有 Stop 和 Continue 两个按钮\n图：当 Manual Judgment 没有配置Jugement Inputs Option 的展示 如果添加了选项，可以根据 “选项” 来判断执行的分支\n判断可以使用每个阶段内的条件表达式 ”Conditional on Expression“，或者 Check Precondition 类型的阶段\n图：根据 “Stage Conditional on Expression” 来定义的选项 “Check Precondition” 是 Spinnaker 流水线中的一个阶段，它可以先前条件并且判断是否继续，这里主要检查该流水线之前所有的流水线你定义要检查的内容，并继续执行接下分支或者阶段\n图：“Check Precondition” 的三种类型 图：“Check Precondition” 选择添加表达式的页面 这里选择使用 表达式 (Expression) 来判断前置条件，例如我判断前置 Stage 的选择是否为 “aaaa”\n${#judgment(\u0026quot;Ask for next step\u0026quot;) == 'aaa'}\n接下来根据正常的流水线来创建即可，例如要 Deploy K8S 资源\n图：“Deploy” 类型的阶段配置页面 Reference ​[1] Changing pipeline behavior based on selected judgment\n","permalink":"https://www.161616.top/spinnaker-branching-judgment/","summary":"Manual Judgment Stage “Manual Judgment Stage” 是 Spinnaker Pipeline 中的一种阶段 (\u0026ldquo;Stage\u0026rdquo;) 类型，该类型可以作为流水线的门户，作为带外 (Out-of-Bound) 流水线检查，等待手动检查，并且判断结果会终止或继续流水线的执行。\n创建一个基于Manual Judgment的流水线 创建一个流水线，添加一个新的 Stage，选择 “Manual Judgment”\n图：Manual Judgment创建页面 Jugement Inputs 部分添加对应的选项，选项可以带入变量 $judgment 中\n图：Manual Judgment 在执行时 Jugement Inputs Option 的展示 或者是不添加任何选项，那么这个时候就会只有 Stop 和 Continue 两个按钮\n图：当 Manual Judgment 没有配置Jugement Inputs Option 的展示 如果添加了选项，可以根据 “选项” 来判断执行的分支\n判断可以使用每个阶段内的条件表达式 ”Conditional on Expression“，或者 Check Precondition 类型的阶段\n图：根据 “Stage Conditional on Expression” 来定义的选项 “Check Precondition” 是 Spinnaker 流水线中的一个阶段，它可以先前条件并且判断是否继续，这里主要检查该流水线之前所有的流水线你定义要检查的内容，并继续执行接下分支或者阶段\n图：“Check Precondition” 的三种类型 图：“Check Precondition” 选择添加表达式的页面 这里选择使用 表达式 (Expression) 来判断前置条件，例如我判断前置 Stage 的选择是否为 “aaaa”","title":"Spinnaker 基于判断的条件分支流水线"},{"content":"流水线模板组合思路 官方流水线示例中没有给出完整的流水线模板和完整的字段，只给出了一个大致的 schema [1]，如下所示\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 { \u0026#34;schema\u0026#34;: \u0026#34;v2\u0026#34;, \u0026#34;variables\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;\u0026lt;type\u0026gt;\u0026#34;, \u0026#34;defaultValue\u0026#34;: \u0026lt;value\u0026gt;, \u0026#34;description\u0026#34;: \u0026#34;\u0026lt;description\u0026gt;\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;\u0026lt;varName\u0026gt;\u0026#34; } ], \u0026#34;id\u0026#34;: \u0026#34;\u0026lt;templateName\u0026gt;\u0026#34;, # The pipeline instance references the template using this \u0026#34;protect\u0026#34;: \u0026lt;true | false\u0026gt;, \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;displayName\u0026#34;, # The display name shown in Deck \u0026#34;description\u0026#34;: \u0026#34;\u0026lt;description\u0026gt;\u0026#34;, \u0026#34;owner\u0026#34;: \u0026#34;example@example.com\u0026#34;, \u0026#34;scopes\u0026#34;: [\u0026#34;global\u0026#34;] # Not used }, \u0026#34;pipeline\u0026#34;: { # Contains the templatized pipeline itself \u0026#34;lastModifiedBy\u0026#34;: \u0026#34;anonymous\u0026#34;, # Not used \u0026#34;updateTs\u0026#34;: \u0026#34;0\u0026#34;, # Not used \u0026#34;parameterConfig\u0026#34;: [], # Same as in a regular pipeline \u0026#34;limitConcurrent\u0026#34;: true, # Same as in a regular pipeline \u0026#34;keepWaitingPipelines\u0026#34;: false, # Same as in a regular pipeline \u0026#34;description\u0026#34;: \u0026#34;\u0026#34;, # Same as in a regular pipeline \u0026#34;triggers\u0026#34;: [], # Same as in a regular pipeline \u0026#34;notifications\u0026#34;: [], # Same as in a regular pipeline \u0026#34;stages\u0026#34;: [ # Contains the templated stages { # This one is an example stage: \u0026#34;waitTime\u0026#34;: \u0026#34;${ templateVariables.waitTime }\u0026#34;, # Templated field. \u0026#34;name\u0026#34;: \u0026#34;My Wait Stage\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;wait\u0026#34;, \u0026#34;refId\u0026#34;: \u0026#34;wait1\u0026#34;, \u0026#34;requisiteStageRefIds\u0026#34;: [] } ] } } 这个流水线模板的 Schema 只包含了大的框架，但是 Spinnaker 的每种类型的 Stage 的 Schema 并没有包含里面，需要自行定义，最好的一个思路就是，先手动先创建一个 {ipeline，然后编辑这个 Pipeline 的 Json 复制出想要的 Stage 的内容，然后再根据 Pipeline Template 的模板进行填充。\n例如，我们想顶一个选择 “多路分支” 的流水线，那么流水线类型如下\n图：spinnaker流水线结构 然后选择 “Edit stage as Json” 或者选择 “Pipeline Actions” 中 “Edit as JSON” 可以查看到该 Stage 或该 Pipelin 完整的 JSON 格式数据。\n图：Pipeline Stage JSON 图：Pipeline JSON 最后将 Stage 的 JSON 复制 到 template Stage 的 数组中即可，下面是一个 Check Precondition 类型的 State 的 JSON\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { \u0026#34;completeOtherBranchesThenFail\u0026#34;: false, \u0026#34;continuePipeline\u0026#34;: false, \u0026#34;failPipeline\u0026#34;: false, \u0026#34;name\u0026#34;: \u0026#34;选择禁用是否启用配置注册\u0026#34;, \u0026#34;notifications\u0026#34;: [], \u0026#34;preconditions\u0026#34;: [ { \u0026#34;context\u0026#34;: { \u0026#34;expression\u0026#34;: \u0026#34;${ #judgment(\u0026#39;check condition\u0026#39;) == \u0026#39;disable\u0026#39; }\u0026#34; }, \u0026#34;failPipeline\u0026#34;: true, \u0026#34;type\u0026#34;: \u0026#34;expression\u0026#34; } ], \u0026#34;type\u0026#34;: \u0026#34;checkPreconditions\u0026#34; } 完整的基于分支判断的流水线模板示例 该模板是一个为 k8s deployment 启用/禁用 HPA 功能的流水线模板\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 { \u0026#34;id\u0026#34;: \u0026#34;a422e02d0ab94ce23jk92a6d2bf8ff9c\u0026#34;, \u0026#34;lastModifiedBy\u0026#34;: \u0026#34;cylon\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;description\u0026#34;: \u0026#34;启用或停用 HPA\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;HPA Turn on or Turn off\u0026#34;, \u0026#34;owner\u0026#34;: \u0026#34;cylonchau@outlook.com\u0026#34;, \u0026#34;scopes\u0026#34;: [ \u0026#34;global\u0026#34; ] }, \u0026#34;pipeline\u0026#34;: { \u0026#34;expectedArtifacts\u0026#34;: [ { \u0026#34;defaultArtifact\u0026#34;: { \u0026#34;artifactAccount\u0026#34;: \u0026#34;k8s-cluster-102\u0026#34;, \u0026#34;reference\u0026#34;: \u0026#34;https://git.chinamobile.cn/api/v4/projects/154/repository/files/hpa%2Fk8s-cluster-102%2F${ templateVariables.appName }.yaml/raw\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;gitlab/file\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;master\u0026#34; }, \u0026#34;displayName\u0026#34;: \u0026#34;${ templateVariables.appName }-hpa-file\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;${ templateVariables.appName }-hpa\u0026#34;, \u0026#34;matchArtifact\u0026#34;: { \u0026#34;artifactAccount\u0026#34;: \u0026#34;k8s-cluster-102\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;https://git.chinamobile.cn/api/v4/projects/154/repository/files/hpa%2Fk8s-cluster-102%2F${ templateVariables.appName }.yaml/raw\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;gitlab/file\u0026#34; }, \u0026#34;useDefaultArtifact\u0026#34;: true, \u0026#34;usePriorArtifact\u0026#34;: false } ], \u0026#34;keepWaitingPipelines\u0026#34;: false, \u0026#34;limitConcurrent\u0026#34;: true, \u0026#34;parameterConfig\u0026#34;: [], \u0026#34;stages\u0026#34;: [ { \u0026#34;instructions\u0026#34;: \u0026#34;请选择条件: \\n\\tenable 启用 ${ templateVariables.appName } HPA Autoscaler\\n\\tdisable 禁用HPA Autoscaler\\n\u0026#34;, \u0026#34;judgmentInputs\u0026#34;: [ { \u0026#34;value\u0026#34;: \u0026#34;enable\u0026#34; }, { \u0026#34;value\u0026#34;: \u0026#34;disable\u0026#34; } ], \u0026#34;name\u0026#34;: \u0026#34;Apply\u0026#34;, \u0026#34;completeOtherBranchesThenFail\u0026#34;: false, \u0026#34;continuePipeline\u0026#34;: false, \u0026#34;failPipeline\u0026#34;: false, \u0026#34;notifications\u0026#34;: [], \u0026#34;propagateAuthenticationContext\u0026#34;: false, \u0026#34;refId\u0026#34;: \u0026#34;7\u0026#34;, \u0026#34;requisiteStageRefIds\u0026#34;: [], \u0026#34;type\u0026#34;: \u0026#34;manualJudgment\u0026#34; }, { \u0026#34;completeOtherBranchesThenFail\u0026#34;: false, \u0026#34;continuePipeline\u0026#34;: false, \u0026#34;failPipeline\u0026#34;: false, \u0026#34;name\u0026#34;: \u0026#34;选择禁用 ${ templateVariables.appName } HPA\u0026#34;, \u0026#34;preconditions\u0026#34;: [ { \u0026#34;context\u0026#34;: { \u0026#34;expression\u0026#34;: \u0026#34;${ #judgment(\u0026#39;Apply\u0026#39;) == \u0026#39;disable\u0026#39; }\u0026#34; }, \u0026#34;failPipeline\u0026#34;: true, \u0026#34;type\u0026#34;: \u0026#34;expression\u0026#34; } ], \u0026#34;refId\u0026#34;: \u0026#34;9\u0026#34;, \u0026#34;requisiteStageRefIds\u0026#34;: [ \u0026#34;7\u0026#34; ], \u0026#34;type\u0026#34;: \u0026#34;checkPreconditions\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;选择启用 ${ templateVariables.appName } HPA\u0026#34;, \u0026#34;completeOtherBranchesThenFail\u0026#34;: false, \u0026#34;continuePipeline\u0026#34;: false, \u0026#34;failPipeline\u0026#34;: false, \u0026#34;preconditions\u0026#34;: [ { \u0026#34;context\u0026#34;: { \u0026#34;expression\u0026#34;: \u0026#34;${ #judgment(\u0026#39;Apply HPA\u0026#39;) == \u0026#39;enable\u0026#39; }\u0026#34; }, \u0026#34;failPipeline\u0026#34;: true, \u0026#34;type\u0026#34;: \u0026#34;expression\u0026#34; } ], \u0026#34;refId\u0026#34;: \u0026#34;10\u0026#34;, \u0026#34;requisiteStageRefIds\u0026#34;: [ \u0026#34;7\u0026#34; ], \u0026#34;type\u0026#34;: \u0026#34;checkPreconditions\u0026#34; }, { \u0026#34;account\u0026#34;: \u0026#34;${ templateVariables.dcName }\u0026#34;, \u0026#34;app\u0026#34;: \u0026#34;${ templateVariables.appName }\u0026#34;, \u0026#34;cloudProvider\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;completeOtherBranchesThenFail\u0026#34;: false, \u0026#34;continuePipeline\u0026#34;: false, \u0026#34;failPipeline\u0026#34;: false, \u0026#34;isNew\u0026#34;: true, \u0026#34;location\u0026#34;: \u0026#34; ${ templateVariables.appLocation }\u0026#34;, \u0026#34;manifestName\u0026#34;: \u0026#34;horizontalpodautoscaler ${ templateVariables.appName }-autoscaler\u0026#34;, \u0026#34;mode\u0026#34;: \u0026#34;static\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;禁用 ${ templateVariables.appName } HPA\u0026#34;, \u0026#34;options\u0026#34;: { \u0026#34;cascading\u0026#34;: false, \u0026#34;gracePeriodSeconds\u0026#34;: 300 }, \u0026#34;refId\u0026#34;: \u0026#34;12\u0026#34;, \u0026#34;requisiteStageRefIds\u0026#34;: [ \u0026#34;9\u0026#34; ], \u0026#34;type\u0026#34;: \u0026#34;deleteManifest\u0026#34; }, { \u0026#34;account\u0026#34;: \u0026#34;${ templateVariables.dcName }\u0026#34;, \u0026#34;cloudProvider\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;manifestArtifactAccount\u0026#34;: \u0026#34;k8s-cluster-102\u0026#34;, \u0026#34;manifestArtifactId\u0026#34;: \u0026#34;${ templateVariables.appName }-hpa\u0026#34;, \u0026#34;completeOtherBranchesThenFail\u0026#34;: false, \u0026#34;continuePipeline\u0026#34;: false, \u0026#34;failPipeline\u0026#34;: false, \u0026#34;moniker\u0026#34;: { \u0026#34;app\u0026#34;: \u0026#34;${ templateVariables.appName }\u0026#34; }, \u0026#34;name\u0026#34;: \u0026#34;启用 ${ templateVariables.appName } HPA\u0026#34;, \u0026#34;refId\u0026#34;: \u0026#34;11\u0026#34;, \u0026#34;requisiteStageRefIds\u0026#34;: [ \u0026#34;10\u0026#34; ], \u0026#34;skipExpressionEvaluation\u0026#34;: false, \u0026#34;source\u0026#34;: \u0026#34;artifact\u0026#34;, \u0026#34;stageTimeoutMs\u0026#34;: 500000, \u0026#34;trafficManagement\u0026#34;: { \u0026#34;enabled\u0026#34;: false, \u0026#34;options\u0026#34;: { \u0026#34;enableTraffic\u0026#34;: false, \u0026#34;services\u0026#34;: [] } }, \u0026#34;type\u0026#34;: \u0026#34;deployManifest\u0026#34; } ], \u0026#34;triggers\u0026#34;: [] }, \u0026#34;protect\u0026#34;: false, \u0026#34;schema\u0026#34;: \u0026#34;v2\u0026#34;, \u0026#34;tag\u0026#34;: null, \u0026#34;updateTs\u0026#34;: \u0026#34;1701832158029\u0026#34;, \u0026#34;variables\u0026#34;: [ { \u0026#34;defaultValue\u0026#34;: \u0026#34;k8s-cluster-102\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;要部署到的集群\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;dcName\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34; }, { \u0026#34;defaultValue\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;模块名称\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;appName\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34; }, { \u0026#34;defaultValue\u0026#34;: \u0026#34;public\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;名称空间\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;appLocation\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34; } ] } Reference [1] Pipeline template JSON\n","permalink":"https://www.161616.top/spinnaker-custom-template/","summary":"流水线模板组合思路 官方流水线示例中没有给出完整的流水线模板和完整的字段，只给出了一个大致的 schema [1]，如下所示\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 { \u0026#34;schema\u0026#34;: \u0026#34;v2\u0026#34;, \u0026#34;variables\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;\u0026lt;type\u0026gt;\u0026#34;, \u0026#34;defaultValue\u0026#34;: \u0026lt;value\u0026gt;, \u0026#34;description\u0026#34;: \u0026#34;\u0026lt;description\u0026gt;\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;\u0026lt;varName\u0026gt;\u0026#34; } ], \u0026#34;id\u0026#34;: \u0026#34;\u0026lt;templateName\u0026gt;\u0026#34;, # The pipeline instance references the template using this \u0026#34;protect\u0026#34;: \u0026lt;true | false\u0026gt;, \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;displayName\u0026#34;, # The display name shown in Deck \u0026#34;description\u0026#34;: \u0026#34;\u0026lt;description\u0026gt;\u0026#34;, \u0026#34;owner\u0026#34;: \u0026#34;example@example.","title":"Spinnaker 自定义Pipeline模板思路"},{"content":"需求 在页面底部增加相关阅读区域：\n简单实现与文章相同 tag 的文章列出到文章底部 实验步骤 新增 related 模板 在 layouts/partials/related.html 新创建一个模板，增加如下内容，本文主题为 PaperModX ，不同的主题，文件在不同目录下\nhtml 1 2 3 4 5 6 7 8 9 {{ $related := .Site.RegularPages.Related . | first 3 }} {{ with $related }} \u0026lt;h3\u0026gt;Related Posts\u0026lt;/h3\u0026gt; \u0026lt;ul\u0026gt; {{ range . }} \u0026lt;li\u0026gt;\u0026lt;a href=\u0026#34;{{ .RelPermalink }}\u0026#34;\u0026gt;{{ .Title }}\u0026lt;/a\u0026gt;\u0026lt;/li\u0026gt; {{ end }} \u0026lt;/ul\u0026gt; {{ end }} 将模板加载到文章列表模板内 然后需要在文章列表页底部包含这个 “模板” _default/single.html 不同主题在不同的目录下\nhtml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ... \u0026lt;!-- 分割线 --\u0026gt; \u0026lt;!-- Related --\u0026gt; \u0026lt;!-- 这个判断因为相关阅读增加了分割线，所以判断是指定页面就不在加载相关阅读和分割线了 --\u0026gt; {{ if and (not (strings.Contains .RelPermalink \u0026#34;/tags\u0026#34;)) (not (strings.Contains .RelPermalink \u0026#34;/posts\u0026#34;)) (not (strings.Contains .RelPermalink \u0026#34;/archives\u0026#34;)) (not (strings.Contains .RelPermalink \u0026#34;/search\u0026#34;)) (not (strings.Contains .RelPermalink \u0026#34;/about\u0026#34;)) }} \u0026lt;div class=\u0026#34;comments-separator\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; {{ partial \u0026#34;related.html\u0026#34; . }} {{- end }} \u0026lt;!-- /Related --\u0026gt; \u0026lt;!-- gittalk --\u0026gt; {{- if not (.Param \u0026#34;noComments\u0026#34;) }} {{ if and (not (strings.Contains .RelPermalink \u0026#34;/tags\u0026#34;)) (not (strings.Contains .RelPermalink \u0026#34;/posts\u0026#34;)) (not (strings.Contains .RelPermalink \u0026#34;/archives\u0026#34;)) (not (strings.Contains .RelPermalink \u0026#34;/search\u0026#34;)) (not (strings.Contains .RelPermalink \u0026#34;/about\u0026#34;)) }} \u0026lt;div class=\u0026#34;comments-separator\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; {{- end }} {{- partial \u0026#34;comments.html\u0026#34; . }} {{- end }} Reference ​[1] How to Add Related Posts Section in Hugo\n","permalink":"https://www.161616.top/hugo-add-related-content-section/","summary":"需求 在页面底部增加相关阅读区域：\n简单实现与文章相同 tag 的文章列出到文章底部 实验步骤 新增 related 模板 在 layouts/partials/related.html 新创建一个模板，增加如下内容，本文主题为 PaperModX ，不同的主题，文件在不同目录下\nhtml 1 2 3 4 5 6 7 8 9 {{ $related := .Site.RegularPages.Related . | first 3 }} {{ with $related }} \u0026lt;h3\u0026gt;Related Posts\u0026lt;/h3\u0026gt; \u0026lt;ul\u0026gt; {{ range . }} \u0026lt;li\u0026gt;\u0026lt;a href=\u0026#34;{{ .RelPermalink }}\u0026#34;\u0026gt;{{ .Title }}\u0026lt;/a\u0026gt;\u0026lt;/li\u0026gt; {{ end }} \u0026lt;/ul\u0026gt; {{ end }} 将模板加载到文章列表模板内 然后需要在文章列表页底部包含这个 “模板” _default/single.html 不同主题在不同的目录下\nhtml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 .","title":"Hugo - 为文章页面增加相关阅读区域"},{"content":"查询结果删除某些指标 without without 属于聚合查询的子句，必须在聚合查询中使用\n语法：\nbash 1 \u0026lt;aggr-op\u0026gt; [without|by (\u0026lt;label list\u0026gt;)] ([parameter,] \u0026lt;vector expression\u0026gt;) 可以看到属于 \u0026lt;aggr-op\u0026gt;\n例如\ntext 1 sum without(instance) (http_requests_total) ignoring ignoring 属于 “向量匹配” (Vector matching) 关键词，可以在 一对多，和多对多查询中使用\n语法\nbash 1 \u0026lt;vector expr\u0026gt; \u0026lt;bin-op\u0026gt; ignoring(\u0026lt;label list\u0026gt;) \u0026lt;vector expr\u0026gt; 例如\nbash 1 method_code:http_errors:rate5m{code=\u0026#34;500\u0026#34;} / ignoring(code) method:http_requests:rate5m 与查询 可以查询满足多个条件的指标，例如下列是查询 jvm 内存 $\\frac{used}{committed} \u0026gt; 80%$ 并且 Pod WSS 使用大于 80% 的指标\npromql 1 2 3 4 5 6 sum by(pod) (jvm_memory_used_bytes{}) / sum by(pod) (jvm_memory_committed_bytes{}) \u0026gt; .8 and sum(node_namespace_pod_container:container_memory_working_set_bytes{}) by (pod) / on(pod) group_left sum(kube_pod_container_resource_limits{resource=\u0026#34;memory\u0026#34;}) by (pod) \u0026gt; .8 向量查询 向量查询关键词为 on 和 ignoring\non 仅考虑表达式内提供的标签相同的，ignoring 允许在匹配时间序列时忽略指定的标签。\n语法 一对一\nbash 1 2 \u0026lt;vector expr\u0026gt; \u0026lt;bin-op\u0026gt; ignoring(\u0026lt;label list\u0026gt;) \u0026lt;vector expr\u0026gt; \u0026lt;vector expr\u0026gt; \u0026lt;bin-op\u0026gt; on(\u0026lt;label list\u0026gt;) \u0026lt;vector expr\u0026gt; 多对一或一对多\nbash 1 2 3 4 \u0026lt;vector expr\u0026gt; \u0026lt;bin-op\u0026gt; ignoring(\u0026lt;label list\u0026gt;) group_left(\u0026lt;label list\u0026gt;) \u0026lt;vector expr\u0026gt; \u0026lt;vector expr\u0026gt; \u0026lt;bin-op\u0026gt; ignoring(\u0026lt;label list\u0026gt;) group_right(\u0026lt;label list\u0026gt;) \u0026lt;vector expr\u0026gt; \u0026lt;vector expr\u0026gt; \u0026lt;bin-op\u0026gt; on(\u0026lt;label list\u0026gt;) group_left(\u0026lt;label list\u0026gt;) \u0026lt;vector expr\u0026gt; \u0026lt;vector expr\u0026gt; \u0026lt;bin-op\u0026gt; on(\u0026lt;label list\u0026gt;) group_right(\u0026lt;label list\u0026gt;) \u0026lt;vector expr\u0026gt; 向量查询的高级用法 通过一个 metrcs 上的 label 的值去查询另外一个 metric 上这个标签的值\nbash 1 container_memory_rss{container=~\u0026#34;.*$module.*\u0026#34;} on(pod) vm_memory_used_bytes{instance=~\u0026#34;$module.*\u0026#34;} 查询不同标签上的相同标签值的内容，例如，我想通过 **jvm_memory_used_bytes **指标上 pod label 为 “aaaa” 的 label，去查询 container_memory_rss 上 container label 为 “aaaa” 的指标内容，这个时候可以使用 label_replace 来重写不同的 label 为相同的值，on() 函数中添加相同指标的值即可完成查询。\n如下所示\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 sum without( node,instance,job,name,id,image,metrics_path,endpoint,service,pod)( label_replace( container_memory_rss{container=~\u0026#34;.*$module.*\u0026#34;,} ,\u0026#34;ints\u0026#34;,\u0026#34;$1\u0026#34;,\u0026#34;pod\u0026#34;, \u0026#34;(.*)\u0026#34; ) ) != on(ints) group_left() sum by(ints) (sum without (job,pod,container) ( label_replace( jvm_memory_used_bytes{instance=\u0026#34;$instance\u0026#34;} ,\u0026#34;ints\u0026#34;,\u0026#34;$1\u0026#34;,\u0026#34;pod\u0026#34;, \u0026#34;(.*)\u0026#34; ) ) ) Reference [1] Optimizing, and Logical Grouping of Queries\n[2] Using group_left to calculate label proportions\n[3] Inside some complex Prometheus queries\n[4] Operators\n","permalink":"https://www.161616.top/promql-advanced/","summary":"查询结果删除某些指标 without without 属于聚合查询的子句，必须在聚合查询中使用\n语法：\nbash 1 \u0026lt;aggr-op\u0026gt; [without|by (\u0026lt;label list\u0026gt;)] ([parameter,] \u0026lt;vector expression\u0026gt;) 可以看到属于 \u0026lt;aggr-op\u0026gt;\n例如\ntext 1 sum without(instance) (http_requests_total) ignoring ignoring 属于 “向量匹配” (Vector matching) 关键词，可以在 一对多，和多对多查询中使用\n语法\nbash 1 \u0026lt;vector expr\u0026gt; \u0026lt;bin-op\u0026gt; ignoring(\u0026lt;label list\u0026gt;) \u0026lt;vector expr\u0026gt; 例如\nbash 1 method_code:http_errors:rate5m{code=\u0026#34;500\u0026#34;} / ignoring(code) method:http_requests:rate5m 与查询 可以查询满足多个条件的指标，例如下列是查询 jvm 内存 $\\frac{used}{committed} \u0026gt; 80%$ 并且 Pod WSS 使用大于 80% 的指标\npromql 1 2 3 4 5 6 sum by(pod) (jvm_memory_used_bytes{}) / sum by(pod) (jvm_memory_committed_bytes{}) \u0026gt; .","title":"PromQL复杂使用示例"},{"content":"需求 在不禁用分类的情况下，关闭对应 sitemap.xml 中的条目以优化 SEO\n去除所有tag 去除所有分类 去除 search about me 这类页面 实验步骤 sitemap 是通过 go-template 目标进行的，只要可以使用对应语法过滤了相关路径即可以排除对应的页面\n首先在配置文件增加 taxonomiesExcludedFromSitemap 选项，这个选项排除了 “hugo中分类法” 中的所有子类；其次使用 if not 来排除所有对应首页面。如下列所示\n本文已 PaperModX 为例，修改文件 layouts\\sitemap.xml\nxml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 {{ printf \u0026#34;\u0026lt;?xml version=\\\u0026#34;1.0\\\u0026#34; encoding=\\\u0026#34;utf-8\\\u0026#34; standalone=\\\u0026#34;yes\\\u0026#34;?\u0026gt;\u0026#34; | safeHTML }} \u0026lt;urlset xmlns=\u0026#34;http://www.sitemaps.org/schemas/sitemap/0.9\u0026#34; xmlns:xhtml=\u0026#34;http://www.w3.org/1999/xhtml\u0026#34;\u0026gt; {{ range .Data.Pages }} {{ if and (not (in .Site.Params.taxonomiesExcludedFromSitemap .Data.Plural)) (not (strings.Contains .RelPermalink \u0026#34;/tags\u0026#34;)) (not (strings.Contains .RelPermalink \u0026#34;/posts\u0026#34;)) (not (strings.Contains .RelPermalink \u0026#34;/archives\u0026#34;)) (not (strings.Contains .RelPermalink \u0026#34;/search\u0026#34;)) (not (strings.Contains .RelPermalink \u0026#34;/about\u0026#34;)) }} \u0026lt;url\u0026gt; \u0026lt;loc\u0026gt;{{ .Permalink }}\u0026lt;/loc\u0026gt; {{ if not .Lastmod.IsZero }} \u0026lt;lastmod\u0026gt;{{ safeHTML ( .Lastmod.Format \u0026#34;2006-01-02T15:04:05-07:00\u0026#34; ) }}\u0026lt;/lastmod\u0026gt; {{ end }} {{ with .Sitemap.ChangeFreq }} \u0026lt;changefreq\u0026gt;{{ . }}\u0026lt;/changefreq\u0026gt; {{ end }} {{- if ge .Sitemap.Priority 0.0 -}} {{- $weeks := div (sub now.Unix .Lastmod.Unix) 604800 -}} {{- $priority := sub 1 (div $weeks 10.0 ) -}} {{- if ge .Sitemap.Priority $priority -}} \u0026lt;priority\u0026gt;{{ .Sitemap.Priority }}\u0026lt;/priority\u0026gt; {{- else -}} {{- if ge $priority 1.0 -}} \u0026lt;priority\u0026gt;1.0\u0026lt;/priority\u0026gt; {{- else -}}\t\u0026lt;priority\u0026gt;{{ $priority }}\u0026lt;/priority\u0026gt; {{- end -}} {{- end -}} {{- end -}} {{ if .IsTranslated }} {{ range .Translations }} \u0026lt;xhtml:link rel=\u0026#34;alternate\u0026#34; hreflang=\u0026#34;{{ .Language.Lang }}\u0026#34; href=\u0026#34;{{ .Permalink }}\u0026#34; /\u0026gt;{{ end }} \u0026lt;xhtml:link rel=\u0026#34;alternate\u0026#34; hreflang=\u0026#34;{{ .Language.Lang }}\u0026#34; href=\u0026#34;{{ .Permalink }}\u0026#34; /\u0026gt;{{ end }} \u0026lt;/url\u0026gt; {{ end }} {{ end }} \u0026lt;/urlset\u0026gt; Reference ​[1] Hugo去除Sitemap中的tags\n","permalink":"https://www.161616.top/hugo-sitemap-without-tag-page/","summary":"需求 在不禁用分类的情况下，关闭对应 sitemap.xml 中的条目以优化 SEO\n去除所有tag 去除所有分类 去除 search about me 这类页面 实验步骤 sitemap 是通过 go-template 目标进行的，只要可以使用对应语法过滤了相关路径即可以排除对应的页面\n首先在配置文件增加 taxonomiesExcludedFromSitemap 选项，这个选项排除了 “hugo中分类法” 中的所有子类；其次使用 if not 来排除所有对应首页面。如下列所示\n本文已 PaperModX 为例，修改文件 layouts\\sitemap.xml\nxml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 {{ printf \u0026#34;\u0026lt;?","title":"Hugo - 去除Sitemap中的tags search等页面"},{"content":"在本文中，将探讨使用 k3s 的 kine 项目来替换掉 etcd，并通过实验使用 kubeadm 去 run 一个 k8s 集群，并用 k3s 的 kine 项目来替换掉 etcd。\n为什么使用 kine etcd 在 Kubernetes 之外基本上没有应用的场景，并且 etcd 迭代也比较慢，由于没有人愿意维护因此一直在衰退 [1]，并且，Kubernetes 集群中，etcd 也是一个影响集群规模的重大因素。并且 K3S 存在一个项目 Kine 可以使用关系型数据库运行，这样对集群维护者来说可以不需要维护复杂的 etcd 集群，由于关系型数据库有很多高可用方案，这将使得 k8s 集群规模变成了无限可能。\nKine 介绍 前文提到，kubernetes (kube-apiserver) 与 etcd 是耦合的，如果我们要使用 RDBMS 去替换 etcd 就需要实现 etcd 的接口，那么这个项目就是 Kine [2]。\nKine 是一个 etcdshim，处于 kube-apiserver 和 RDBMS 的中间层，它实现了 etcdAPI的子集（不是etcd的全部功能），Kine 在 RDBMS 数据库之上实现了简单的多版本并发控制；将所有信息存储在一个表中；每行存储此 key 的修订, key, 当前值, 先前值, 先前修订，以及表示该 Key 是已创建还是已删除的标记，通过这种机制可以作为 shim 层来替换 etcd。\n简单提一句，shim 是计算机程序设计中的术语，表现为一个小型函数库，服务等，通过截取 API 调用，修改传入参数，来处理自行处理对应操作或者将操作交由其它地方执行。\n总的来说 shim 是一种可以在新环境中支持老 API，也可以在老环境里支持新 API 辅助运行库或服务，在云原生场景中，我们经常看到 docker-shim，cri-shim 等。\n前提条件 本文实验环境使用的软件版本如下\n软件/硬件 版本 操作系统 Debian 11(bullseye) 2C/4G Kubernetes版本 v1.28.11(截至文章编写时间的最新版) Kubernetes集群部署工具 kubeadm Kine v0.11.10 (截至文章编写时间的最新版) MySQL Docker运行，镜像 mysql:5.7 使用 kubeadm 构建控制平面 为了展现 kine 的作用，首先我们需要准备一个 k8s 集群，这里简单使用 kubeadm + containerd 来构建一个 kuebrnetes 集群。\n安装 containerd 载入内核依赖项 containerd 或 docker 的安装都需要内核支持 overlay 和 br_netfilter 模块，overlay 为 containerd 运行的文件系统，netfiler 用于维护容器内 (inter-container) 的网络。所以我们需要加载对应的内核模块。\nbash 1 2 3 4 cat \u0026lt;\u0026lt;EOF | tee /etc/modules-load.d/containerd.conf overlay br_netfilter EOF 手动执行下面命令\nbash 1 2 modprobe overlay \u0026amp;\u0026amp; \\ modprobe br_netfilter 通过仓库 containerd contanerd 是作为 docker-ce 的下层，所以很多 Linux 发行版都有对应的包管理工具的仓库，这里面维护了基本上比较新的版本，可以直接在对应操作系统下载\nCentOS\nbash 1 2 3 yum install yum-utils -y \u0026amp;\u0026amp; \\ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo \u0026amp;\u0026amp; \\ yum install containerd.io -y Debian\nDebian仓库中通常都有比较新版本的 containerd，可以直接安装\nbash 1 apt list|grep containerd 安装\nbash 1 apt -y install containerd 离线安装 如果需要离线环境安装的话，可以在手动下载 containerd 和 runc 后传入内网\n下载 Containerd 的二进制包，这里下载containerd-\u0026lt;VERSION\u0026gt;-\u0026lt;OS\u0026gt;-\u0026lt;ARCH\u0026gt;.tar.gz 格式名称的发行版，后边在单独下载安装 runc\nbash 1 wget https://github.com/containerd/containerd/releases/download/v1.7.3/containerd-1.7.3-linux-amd64.tar.gz 将其解压缩到 /usr/local 下:\nbash 1 tar Cxzvf /usr/local containerd-1.7.3-linux-amd64.tar.gz 接下来从 runc 的 github 上下载安装 runc，该二进制文件是静态构建的，并且应该适用于任何Linux发行版。\nbash 1 2 wget https://github.com/opencontainers/runc/releases/download/v1.1.9/runc.amd64 install -m 755 runc.amd64 /usr/local/sbin/runc 为了通过 systemd 管理 containerd，请还需要从仓库中下载 containerd.service 单元文件\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 cat \u0026lt;\u0026lt; EOF \u0026gt; /usr/lib/systemd/system/containerd.service [Unit] Description=containerd container runtime Documentation=https://containerd.io After=network.target local-fs.target [Service] #uncomment to enable the experimental sbservice (sandboxed) version of containerd/cri integration #Environment=\u0026#34;ENABLE_CRI_SANDBOXES=sandboxed\u0026#34; ExecStartPre=-/sbin/modprobe overlay ExecStart=/usr/local/bin/containerd Type=notify Delegate=yes KillMode=process Restart=always RestartSec=5 # Having non-zero Limit*s causes performance problems due to accounting overhead # in the kernel. We recommend using cgroups to do container-local accounting. LimitNPROC=infinity LimitCORE=infinity LimitNOFILE=infinity # Comment TasksMax if your systemd version does not supports it. # Only systemd 226 and above support this version. TasksMax=infinity OOMScoreAdjust=-999 [Install] WantedBy=multi-user.target EOF 配置配置文件 bash 1 2 mkdir -p /etc/containerd \u0026amp;\u0026amp; \\ containerd config default | sudo tee /etc/containerd/config.toml 配置驱动为 systemd 将配置文件修改为实例所述\nyaml 1 2 3 4 5 6 7 8 9 10 [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes] [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes.runc] runtime_type = \u0026#34;io.containerd.runc.v2\u0026#34; runtime_engine = \u0026#34;\u0026#34; runtime_root = \u0026#34;\u0026#34; privileged_without_host_devices = false base_runtime_spec = \u0026#34;\u0026#34; [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes.runc.options] SystemdCgroup = true 一键修改命令\nbash 1 sed -i \u0026#34;s/SystemdCgroup = false/SystemdCgroup = true/g\u0026#34; \u0026#34;${CONTAINDERD_CONFIG_PATH}\u0026#34; 启动服务 bash 1 2 systemctl enable --now containerd \u0026amp;\u0026amp; \\ systemctl restart containerd 使用kubeadm构建集群 加载内核依赖项 bash 1 2 3 4 5 6 cat \u0026gt; /etc/modules-load.d/kubernetes.conf \u0026lt;\u0026lt;EOF ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh EOF 执行以下命令使配置立即生效:\nbash 1 2 3 4 modprobe ip_vs \u0026amp;\u0026amp; \\ modprobe ip_vs_rr \u0026amp;\u0026amp; \\ modprobe ip_vs_wrr \u0026amp;\u0026amp; \\ modprobe ip_vs_sh 安装kubeadm kubelet kubectl 安装 kubeadm 可以参考官网的步骤来 [3]\n使用基于debian 包管理仓库 使用 Kubernetes apt 仓库\nbash 1 apt-get install -y apt-transport-https ca-certificates curl gpg 下载公共签名key\nbash 1 curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg 添加适合的 k8s 版本仓库，这里是 1.28\nbash 1 2 # This overwrites any existing configuration in /etc/apt/sources.list.d/kubernetes.list echo \u0026#39;deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /\u0026#39; | sudo tee /etc/apt/sources.list.d/kubernetes.list 更新包索引\nbash 1 2 apt update \u0026amp;\u0026amp; \\ apt install -y kubelet=1.28.11-1.1 kubeadm=1.28.11-1.1 kubectl=1.28.11-1.1 不使用包管理工具 下载 kubeadm, kubelet, kubectl 二进制文件\nbash 1 2 3 4 5 6 7 8 9 10 # 这个文件内包含的是 kubernetes 最新稳定版的版本号，如果要安装最新版可以取消掉这行注释 # RELEASE=\u0026#34;$(curl -sSL https://dl.k8s.io/release/stable.txt)\u0026#34; RELEASE=\u0026#34;v1.28.11\u0026#34; ARCH=\u0026#34;amd64\u0026#34; DOWNLOAD_DIR=\u0026#34;/usr/local/bin\u0026#34; mkdir -p \u0026#34;$DOWNLOAD_DIR\u0026#34; cd $DOWNLOAD_DIR sudo curl -L --remote-name-all https://dl.k8s.io/release/${RELEASE}/bin/linux/${ARCH}/{kubeadm,kubelet} sudo chmod +x {kubeadm,kubelet} 下载 kubelet 的 system单元文件 或手动添加所需的 systemd 单元文件\nbash 1 2 3 4 5 6 7 8 # v0.16.2 是一个固定的版本号，不是 kubernetes 版本 RELEASE_VERSION=\u0026#34;v0.16.2\u0026#34; curl -sSL \u0026#34;https://raw.githubusercontent.com/kubernetes/release/${RELEASE_VERSION}/cmd/krel/templates/latest/kubelet/kubelet.service\u0026#34; | sed \u0026#34;s:/usr/bin:${DOWNLOAD_DIR}:g\u0026#34; | sudo tee /etc/systemd/system/kubelet.service # kubelet.service 是一个单元文件 # systemd 的 service.d 目录是一个固定写法，这里表示可以使用 .conf 结尾的文件来覆盖这个服务的单元文件 mkdir -p /etc/systemd/system/kubelet.service.d curl -sSL \u0026#34;https://raw.githubusercontent.com/kubernetes/release/${RELEASE_VERSION}/cmd/krel/templates/latest/kubeadm/10-kubeadm.conf\u0026#34; | sed \u0026#34;s:/usr/bin:${DOWNLOAD_DIR}:g\u0026#34; | sudo tee /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 或者手动创建 kubelet.serivce 的 systemd 的单元文件\n这个文件是将 rpm 或 dpkg 包的 kubelet.service 和上述 10-kubeadm.conf 融合为一起的，效果是相同的\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 cat \u0026lt;\u0026lt; EOF \u0026gt; /usr/lib/systemd/system/kubelet.serivce [Unit] Description=kubelet: The Kubernetes Node Agent Documentation=https://kubernetes.io/docs/ Wants=network-online.target After=network-online.target # Note: This dropin only works with kubeadm and kubelet v1.11+ [Service] Environment=\u0026#34;KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf\u0026#34; Environment=\u0026#34;KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml\u0026#34; # This is a file that \u0026#34;kubeadm init\u0026#34; and \u0026#34;kubeadm join\u0026#34; generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env # This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use # the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file. EnvironmentFile=-/etc/sysconfig/kubelet ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS Restart=always StartLimitInterval=0 RestartSec=10 [Install] WantedBy=multi-user.target EOF 离线环境镜像下载 列出所使用的镜像\nbash 1 2 3 4 5 6 7 8 $ kubeadm config images list --kubernetes-version=1.28.8 registry.k8s.io/kube-apiserver:v1.28.8 registry.k8s.io/kube-controller-manager:v1.28.8 registry.k8s.io/kube-scheduler:v1.28.8 registry.k8s.io/kube-proxy:v1.28.8 registry.k8s.io/pause:3.9 registry.k8s.io/etcd:3.5.12-0 registry.k8s.io/coredns/coredns:v1.10.1 下载对应镜像，并上传到私有仓库\nbash 1 2 3 4 5 for n in `./kubeadm config images list --kubernetes-version=1.28.11`; do docker pull $n; docker tag $n `echo $n | sed \u0026#39;s|registry.k8s.io|img.xxxx.com/system|\u0026#39;` docker push `echo $n | sed \u0026#39;s|registry.k8s.io|img.xxx.com/system|\u0026#39;` done 生成配置文件\nbash 1 2 3 4 5 6 7 ./kubeadm config images list --image-repository img.xxx.com/system --kubernetes-version=v1.28.11 # 生成对应组件的的 kubeconfig # kubelet kubeadm config print init-defaults --component-configs KubeletConfiguration|grep -A 1000 \u0026#39;apiVersion: kubelet.config.k8s.io\u0026#39;|sed \u0026#39;s|0s|30s|g\u0026#39; # kube-proxy kubeadm config print init-defaults --component-configs KubeProxyConfiguration|grep -A 1000 \u0026#39;kubeproxy.config.k8s.io/\u0026#39;|sed \u0026#39;s|0s|30s|g\u0026#39; 使用配置文件安装\nbash 1 kubeadm init --config kube.yaml -v 10 使用命令初始化\nbash 1 2 3 4 5 6 7 8 9 kubeadm init \\ --image-repository=img.xxx.com/system \\ --pod-network-cidr=10.10.0.0/16 \\ --service-cidr=10.11.0.0/24 \\ --kubernetes-version=v1.28.11 \\ --control-plane-endpoint=`hostname -I` \\ --apiserver-advertise-address=`hostname -I` \\ --apiserver-cert-extra-sans=`hostname -I` \\ --v=10 这个时候控制平面已经可以正常工作了\nbash 1 2 3 4 5 6 7 8 9 $ kubectl --kubeconfig /etc/kubernetes/admin.conf get pods -n kube-system NAME READY STATUS RESTARTS AGE coredns-5dd5756b68-nvqwf 0/1 Pending 0 16h coredns-5dd5756b68-t2tj5 0/1 Pending 0 16h etcd-node 1/1 Running 0 16h kube-apiserver-node 1/1 Running 0 16h kube-controller-manager-node 1/1 Running 0 16h kube-proxy-g6fpc 1/1 Running 0 16h kube-scheduler-node 1/1 Running 0 16h 使用 kine 来替换 etcd 查看官方示例 首先根据 kine 官方 example 来查看最小示例的来学习如何使用 kine [4]，通过文章得知，kine 运行有两种方式，kine 与数据库之间的使用 ssl 链接。\nmysql\nbash 1 2 kine --endpoint \u0026#34;mysql://root:$PASSWORD@tcp(localhost:3306)/kine\u0026#34; --ca-file ca.crt --cert-file server.crt --key-file server.key postgres\nbash 1 2 3 4 kine --endpoint=\u0026#34;postgres://$(POSTGRES_USERNAME):$(POSTGRES_PASSWORD)@localhost:5432/postgres\u0026#34; --ca-file=/var/lib/postgresql/ca.crt --cert-file=/var/lib/postgresql/server.crt --key-file=/var/lib/postgresql/server.key 这时我们需要查看一下 kine 的参数\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 GLOBAL OPTIONS: --listen-address value (default: \u0026#34;0.0.0.0:2379\u0026#34;) --endpoint value Storage endpoint (default is sqlite) --ca-file value CA cert for DB connection --cert-file value Certificate for DB connection --server-cert-file value Certificate for etcd connection --server-key-file value Key file for etcd connection --datastore-max-idle-connections value Maximum number of idle connections retained by datastore. If value = 0, the system default will be used. If value \u0026lt; 0, idle connections will not be reused. (default: 0) --datastore-max-open-connections value Maximum number of open connections used by datastore. If value \u0026lt;= 0, then there is no limit (default: 0) --datastore-connection-max-lifetime value Maximum amount of time a connection may be reused. If value \u0026lt;= 0, then there is no limit. (default: 0s) --key-file value Key file for DB connection --metrics-bind-address value The address the metric endpoint binds to. Default :8080, set 0 to disable metrics serving. (default: \u0026#34;:8080\u0026#34;) --slow-sql-threshold value The duration which SQL executed longer than will be logged. Default 1s, set \u0026lt;= 0 to disable slow SQL log. (default: 1s) --metrics-enable-profiling Enable net/http/pprof handlers on the metrics bind address. Default is false. (default: false) --watch-progress-notify-interval value Interval between periodic watch progress notifications. Default is 10m. (default: 10m0s) --debug (default: false) --help, -h show help --version, -v print the version 通过参数得知，上面的除了官方给出的，kine 与数据库之间的连接也可以不使用 ssl，并通过 --server-cert-file 与 --server-key-file 来作为 kube-apiserver 连接 etcd 所使用的证书指定给 kine 就可以启动了。\n编写静态文件 这里我们只需要删除 /etc/kubernetes/manifests/etcd.yaml 并将 etcd 使用的证书挂载到 kine pod 中，那么我们编写 /etc/kubernetes/manifests/kine.yaml 文件。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 cat /etc/kubernetes/manifests/kine.yaml apiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: component: kine tier: control-plane name: kine namespace: kube-system spec: containers: - name: kine command: [ \u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;--\u0026#34; ] args: [ \u0026#39;kine --endpoint=\u0026#34;mysql://root:111@tcp(10.0.0.1:3306)/kine\u0026#34; --server-cert-file=/etc/kubernetes/pki/etcd/server.crt --server-key-file=/etc/kubernetes/pki/etcd/server.key\u0026#39; ] image: docker.io/rancher/kine:v0.11.10-amd64 imagePullPolicy: IfNotPresent resources: requests: cpu: 250m volumeMounts: - mountPath: /etc/kubernetes/pki/etcd name: etcd-certs hostNetwork: true volumes: - hostPath: path: /etc/kubernetes/pki/etcd type: DirectoryOrCreate name: etcd-certs status: {} kubuadm 生成的 kubelet 的 KubeletConfiguration 文件，中静态文件得路径参数 “staticPodPath”\nyaml 1 2 3 4 5 $ cat /var/lib/kubelet/config.yaml apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration ... staticPodPath: /etc/kubernetes/manifests 这个时候可以启动 kubelet 服务，然后查看静态 Pod，此时可以看到， kube-system 名称空间 已经没有 etcd pod了\nbash 1 2 3 4 5 6 $ kubectl --kubeconfig /etc/kubernetes/admin.conf get pod -n kube-system NAMESPACE NAME READY STATUS RESTARTS AGE kube-system kine-node 1/1 Running 0 17s kube-system kube-apiserver-node 1/1 Running 19 7m15s kube-system kube-controller-manager-node 1/1 Running 6 7m5s kube-system kube-scheduler-node 1/1 Running 6 7m2s 此时就可以继续部署 k8s 的 worker 节点和 CNI 了\n探索 kine 我们可以查看数据库表结构，来探索 kine 是如何实现的 etcdAPI 转换的，我们可以看到，kine 会在启动参数中配置的库名 创建对应的数据库，并且仅有一个表 kine\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 mysql\u0026gt; show databases; +--------------------+ | Database | +--------------------+ | information_schema | | kine | | mysql | | performance_schema | | sys | +--------------------+ 5 rows in set (0.01 sec) mysql\u0026gt; show tables; +----------------+ | Tables_in_kine | +----------------+ | kine | +----------------+ 观察表结构\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 mysql\u0026gt; desc kine; +-----------------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------------+---------------------+------+-----+---------+----------------+ | id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | name | varchar(630) | YES | MUL | NULL | | | created | int(11) | YES | | NULL | | | deleted | int(11) | YES | | NULL | | | create_revision | bigint(20) unsigned | YES | | NULL | | | prev_revision | bigint(20) unsigned | YES | MUL | NULL | | | lease | int(11) | YES | | NULL | | | value | mediumblob | YES | | NULL | | | old_value | mediumblob | YES | | NULL | | +-----------------+---------------------+------+-----+---------+----------------+ 9 rows in set (0.00 sec) 查看数据是如何存储的\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 mysql\u0026gt; select count(id),name from kine group by name; +-----------+---------------------------------------------------------------------------------------------+ | count(id) | name | +-----------+---------------------------------------------------------------------------------------------+ | 1 | /registry/apiregistration.k8s.io/apiservices/v1. | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.admissionregistration.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.apiextensions.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.apps | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.authentication.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.authorization.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.autoscaling | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.batch | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.certificates.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.coordination.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.discovery.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.events.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.networking.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.node.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.policy | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.rbac.authorization.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.scheduling.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1.storage.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1beta2.flowcontrol.apiserver.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v1beta3.flowcontrol.apiserver.k8s.io | | 1 | /registry/apiregistration.k8s.io/apiservices/v2.autoscaling | | 1 | /registry/clusterrolebindings/cluster-admin | | 1 | /registry/clusterrolebindings/system:basic-user | | 1 | /registry/clusterrolebindings/system:controller:attachdetach-controller | | 1 | /registry/clusterrolebindings/system:controller:certificate-controller | | 1 | /registry/clusterrolebindings/system:controller:clusterrole-aggregation-controller | | 1 | /registry/clusterrolebindings/system:controller:cronjob-controller | | 1 | /registry/clusterrolebindings/system:controller:daemon-set-controller | | 1 | /registry/clusterrolebindings/system:controller:deployment-controller | | 1 | /registry/clusterrolebindings/system:controller:disruption-controller | | 1 | /registry/clusterrolebindings/system:controller:endpoint-controller | | 1 | /registry/clusterrolebindings/system:controller:endpointslice-controller | | 1 | /registry/clusterrolebindings/system:controller:endpointslicemirroring-controller | | 1 | /registry/clusterrolebindings/system:controller:ephemeral-volume-controller | | 1 | /registry/clusterrolebindings/system:controller:expand-controller | | 1 | /registry/clusterrolebindings/system:controller:generic-garbage-collector | | 1 | /registry/clusterrolebindings/system:controller:horizontal-pod-autoscaler | | 1 | /registry/clusterrolebindings/system:controller:job-controller | | 1 | /registry/clusterrolebindings/system:controller:namespace-controller | | 1 | /registry/clusterrolebindings/system:controller:node-controller | | 1 | /registry/clusterrolebindings/system:controller:persistent-volume-binder | | 1 | /registry/clusterrolebindings/system:controller:pod-garbage-collector | | 1 | /registry/clusterrolebindings/system:controller:pv-protection-controller | | 1 | /registry/clusterrolebindings/system:controller:pvc-protection-controller | | 1 | /registry/clusterrolebindings/system:controller:replicaset-controller | | 1 | /registry/clusterrolebindings/system:controller:replication-controller | | 1 | /registry/clusterrolebindings/system:controller:resourcequota-controller | | 1 | /registry/clusterrolebindings/system:controller:root-ca-cert-publisher | | 1 | /registry/clusterrolebindings/system:controller:route-controller | | 1 | /registry/clusterrolebindings/system:controller:service-account-controller | | 1 | /registry/clusterrolebindings/system:controller:service-controller | | 1 | /registry/clusterrolebindings/system:controller:statefulset-controller | | 1 | /registry/clusterrolebindings/system:controller:ttl-after-finished-controller | | 1 | /registry/clusterrolebindings/system:controller:ttl-controller | | 1 | /registry/clusterrolebindings/system:discovery | | 1 | /registry/clusterrolebindings/system:kube-controller-manager | | 1 | /registry/clusterrolebindings/system:kube-dns | | 1 | /registry/clusterrolebindings/system:kube-scheduler | | 1 | /registry/clusterrolebindings/system:monitoring | | 1 | /registry/clusterrolebindings/system:node | | 1 | /registry/clusterrolebindings/system:node-proxier | | 1 | /registry/clusterrolebindings/system:public-info-viewer | | 1 | /registry/clusterrolebindings/system:service-account-issuer-discovery | | 1 | /registry/clusterrolebindings/system:volume-scheduler | | 1 | /registry/clusterroles/admin | | 1 | /registry/clusterroles/cluster-admin | | 1 | /registry/clusterroles/edit | | 1 | /registry/clusterroles/system:aggregate-to-admin | | 1 | /registry/clusterroles/system:aggregate-to-edit | | 1 | /registry/clusterroles/system:aggregate-to-view | | 1 | /registry/clusterroles/system:auth-delegator | | 1 | /registry/clusterroles/system:basic-user | | 1 | /registry/clusterroles/system:certificates.k8s.io:certificatesigningrequests:nodeclient | | 1 | /registry/clusterroles/system:certificates.k8s.io:certificatesigningrequests:selfnodeclient | | 1 | /registry/clusterroles/system:certificates.k8s.io:kube-apiserver-client-approver | | 1 | /registry/clusterroles/system:certificates.k8s.io:kube-apiserver-client-kubelet-approver | | 1 | /registry/clusterroles/system:certificates.k8s.io:kubelet-serving-approver | | 1 | /registry/clusterroles/system:certificates.k8s.io:legacy-unknown-approver | | 1 | /registry/clusterroles/system:controller:attachdetach-controller | | 1 | /registry/clusterroles/system:controller:certificate-controller | | 1 | /registry/clusterroles/system:controller:clusterrole-aggregation-controller | | 1 | /registry/clusterroles/system:controller:cronjob-controller | | 1 | /registry/clusterroles/system:controller:daemon-set-controller | | 1 | /registry/clusterroles/system:controller:deployment-controller | | 1 | /registry/clusterroles/system:controller:disruption-controller | | 1 | /registry/clusterroles/system:controller:endpoint-controller | | 1 | /registry/clusterroles/system:controller:endpointslice-controller | | 1 | /registry/clusterroles/system:controller:endpointslicemirroring-controller | | 1 | /registry/clusterroles/system:controller:ephemeral-volume-controller | | 1 | /registry/clusterroles/system:controller:expand-controller | | 1 | /registry/clusterroles/system:controller:generic-garbage-collector | | 1 | /registry/clusterroles/system:controller:horizontal-pod-autoscaler | | 1 | /registry/clusterroles/system:controller:job-controller | | 1 | /registry/clusterroles/system:controller:namespace-controller | | 1 | /registry/clusterroles/system:controller:node-controller | | 1 | /registry/clusterroles/system:controller:persistent-volume-binder | | 1 | /registry/clusterroles/system:controller:pod-garbage-collector | | 1 | /registry/clusterroles/system:controller:pv-protection-controller | | 1 | /registry/clusterroles/system:controller:pvc-protection-controller | | 1 | /registry/clusterroles/system:controller:replicaset-controller | | 1 | /registry/clusterroles/system:controller:replication-controller | | 1 | /registry/clusterroles/system:controller:resourcequota-controller | | 1 | /registry/clusterroles/system:controller:root-ca-cert-publisher | | 1 | /registry/clusterroles/system:controller:route-controller | | 1 | /registry/clusterroles/system:controller:service-account-controller | | 1 | /registry/clusterroles/system:controller:service-controller | | 1 | /registry/clusterroles/system:controller:statefulset-controller | | 1 | /registry/clusterroles/system:controller:ttl-after-finished-controller | | 1 | /registry/clusterroles/system:controller:ttl-controller | | 1 | /registry/clusterroles/system:discovery | | 1 | /registry/clusterroles/system:heapster | | 1 | /registry/clusterroles/system:kube-aggregator | | 1 | /registry/clusterroles/system:kube-controller-manager | | 1 | /registry/clusterroles/system:kube-dns | | 1 | /registry/clusterroles/system:kube-scheduler | | 1 | /registry/clusterroles/system:kubelet-api-admin | | 1 | /registry/clusterroles/system:monitoring | | 1 | /registry/clusterroles/system:node | | 1 | /registry/clusterroles/system:node-bootstrapper | | 1 | /registry/clusterroles/system:node-problem-detector | | 1 | /registry/clusterroles/system:node-proxier | | 1 | /registry/clusterroles/system:persistent-volume-provisioner | | 1 | /registry/clusterroles/system:public-info-viewer | | 1 | /registry/clusterroles/system:service-account-issuer-discovery | | 1 | /registry/clusterroles/system:volume-scheduler | | 1 | /registry/clusterroles/view | | 1 | /registry/configmaps/default/kube-root-ca.crt | | 1 | /registry/configmaps/kube-node-lease/kube-root-ca.crt | | 1 | /registry/configmaps/kube-public/kube-root-ca.crt | | 1 | /registry/configmaps/kube-system/extension-apiserver-authentication | | 1 | /registry/configmaps/kube-system/kube-apiserver-legacy-service-account-token-tracking | | 1 | /registry/configmaps/kube-system/kube-root-ca.crt | | 1 | /registry/csinodes/node | | 1 | /registry/endpointslices/default/kubernetes | | 1 | /registry/events/default/node.17de1624f3c1624f | | 1 | /registry/events/default/node.17de1624f3c1e6bb | | 1 | /registry/events/default/node.17de1624f3c25c4f | | 1 | /registry/events/default/node.17de1624f5b37dfb | | 1 | /registry/events/default/node.17de1639e7890c71 | | 1 | /registry/events/default/node.17de168dce4cdb68 | | 1 | /registry/events/default/node.17de16a194521b80 | | 1 | /registry/events/kube-system/kine-node.17de162525650d9b | | 1 | /registry/events/kube-system/kine-node.17de1625275ca2d7 | | 1 | /registry/events/kube-system/kine-node.17de16252f773864 | | 1 | /registry/events/kube-system/kine-node.17de1625a5af90c0 | | 1 | /registry/events/kube-system/kine-node.17de169d120062cc | | 1 | /registry/events/kube-system/kine-node.17de169d1361dab8 | | 1 | /registry/events/kube-system/kine-node.17de169d1855aee6 | | 1 | /registry/events/kube-system/kine-node.17de16a1969b1ed6 | | 1 | /registry/events/kube-system/kube-apiserver-node.17de162513417e64 | | 1 | /registry/events/kube-system/kube-apiserver-node.17de1625158e863e | | 1 | /registry/events/kube-system/kube-apiserver-node.17de162525e8ebd2 | | 1 | /registry/events/kube-system/kube-apiserver-node.17de1629c37f0b35 | | 1 | /registry/events/kube-system/kube-apiserver-node.17de1629f6bc718f | | 1 | /registry/events/kube-system/kube-apiserver-node.17de162ecf004a1d | | 1 | /registry/events/kube-system/kube-apiserver-node.17de162eff4060dd | | 1 | /registry/events/kube-system/kube-apiserver-node.17de1637f005507c | | 1 | /registry/events/kube-system/kube-apiserver-node.17de1661f1bd6879 | | 1 | /registry/events/kube-system/kube-apiserver-node.17de16620f441326 | | 1 | /registry/events/kube-system/kube-apiserver-node.17de16a1985f63bd | | 1 | /registry/events/kube-system/kube-controller-manager-node.17de162511a4b3be | | 1 | /registry/events/kube-system/kube-controller-manager-node.17de162512f837ca | | 1 | /registry/events/kube-system/kube-controller-manager-node.17de16251d8b658b | | 1 | /registry/events/kube-system/kube-controller-manager-node.17de169b0537b6d0 | | 1 | /registry/events/kube-system/kube-controller-manager-node.17de16a1971f3999 | | 1 | /registry/events/kube-system/kube-controller-manager.17de1638e6568ffc | | 1 | /registry/events/kube-system/kube-controller-manager.17de168d8ed8b9cc | | 1 | /registry/events/kube-system/kube-controller-manager.17de16a150704739 | | 1 | /registry/events/kube-system/kube-scheduler-node.17de162512917b00 | | 1 | /registry/events/kube-system/kube-scheduler-node.17de16251515909b | | 1 | /registry/events/kube-system/kube-scheduler-node.17de16252295ae29 | | 1 | /registry/events/kube-system/kube-scheduler-node.17de162a7ee366d4 | | 1 | /registry/events/kube-system/kube-scheduler-node.17de169c038ba9cc | | 1 | /registry/events/kube-system/kube-scheduler-node.17de169cf8755bf3 | | 1 | /registry/events/kube-system/kube-scheduler-node.17de16a19797a620 | | 1 | /registry/events/kube-system/kube-scheduler.17de1643dd024555 | | 1 | /registry/events/kube-system/kube-scheduler.17de168dbd6f19b1 | | 1 | /registry/events/kube-system/kube-scheduler.17de16a24a03d6c8 | | 1 | /registry/flowschemas/catch-all | | 1 | /registry/flowschemas/endpoint-controller | | 1 | /registry/flowschemas/exempt | | 1 | /registry/flowschemas/global-default | | 1 | /registry/flowschemas/kube-controller-manager | | 1 | /registry/flowschemas/kube-scheduler | | 1 | /registry/flowschemas/kube-system-service-accounts | | 1 | /registry/flowschemas/probes | | 1 | /registry/flowschemas/service-accounts | | 1 | /registry/flowschemas/system-leader-election | | 1 | /registry/flowschemas/system-node-high | | 1 | /registry/flowschemas/system-nodes | | 1 | /registry/flowschemas/workload-leader-election | | 1 | /registry/health | | 97 | /registry/leases/kube-node-lease/node | | 97 | /registry/leases/kube-system/apiserver-6cazmjvz5glfjbabvahmi5cwfy | | 484 | /registry/leases/kube-system/kube-controller-manager | | 485 | /registry/leases/kube-system/kube-scheduler | | 99 | /registry/masterleases/10.0.0.14 | | 33 | /registry/minions/node | | 1 | /registry/namespaces/default | | 1 | /registry/namespaces/kube-node-lease | | 1 | /registry/namespaces/kube-public | | 1 | /registry/namespaces/kube-system | | 1 | /registry/pods/kube-system/kine-node | | 1 | /registry/pods/kube-system/kube-apiserver-node | | 1 | /registry/pods/kube-system/kube-controller-manager-node | | 1 | /registry/pods/kube-system/kube-scheduler-node | | 1 | /registry/priorityclasses/system-cluster-critical | | 1 | /registry/priorityclasses/system-node-critical | | 1 | /registry/prioritylevelconfigurations/catch-all | | 1 | /registry/prioritylevelconfigurations/exempt | | 1 | /registry/prioritylevelconfigurations/global-default | | 1 | /registry/prioritylevelconfigurations/leader-election | | 1 | /registry/prioritylevelconfigurations/node-high | | 1 | /registry/prioritylevelconfigurations/system | | 1 | /registry/prioritylevelconfigurations/workload-high | | 1 | /registry/prioritylevelconfigurations/workload-low | | 1 | /registry/ranges/serviceips | | 1 | /registry/ranges/servicenodeports | | 1 | /registry/rolebindings/kube-public/system:controller:bootstrap-signer | | 1 | /registry/rolebindings/kube-system/system::extension-apiserver-authentication-reader | | 1 | /registry/rolebindings/kube-system/system::leader-locking-kube-controller-manager | | 1 | /registry/rolebindings/kube-system/system::leader-locking-kube-scheduler | | 1 | /registry/rolebindings/kube-system/system:controller:bootstrap-signer | | 1 | /registry/rolebindings/kube-system/system:controller:cloud-provider | | 1 | /registry/rolebindings/kube-system/system:controller:token-cleaner | | 1 | /registry/roles/kube-public/system:controller:bootstrap-signer | | 1 | /registry/roles/kube-system/extension-apiserver-authentication-reader | | 1 | /registry/roles/kube-system/system::leader-locking-kube-controller-manager | | 1 | /registry/roles/kube-system/system::leader-locking-kube-scheduler | | 1 | /registry/roles/kube-system/system:controller:bootstrap-signer | | 1 | /registry/roles/kube-system/system:controller:cloud-provider | | 1 | /registry/roles/kube-system/system:controller:token-cleaner | | 1 | /registry/serviceaccounts/default/default | | 1 | /registry/serviceaccounts/kube-node-lease/default | | 1 | /registry/serviceaccounts/kube-public/default | | 1 | /registry/serviceaccounts/kube-system/attachdetach-controller | | 1 | /registry/serviceaccounts/kube-system/bootstrap-signer | | 1 | /registry/serviceaccounts/kube-system/certificate-controller | | 1 | /registry/serviceaccounts/kube-system/clusterrole-aggregation-controller | | 1 | /registry/serviceaccounts/kube-system/cronjob-controller | | 1 | /registry/serviceaccounts/kube-system/daemon-set-controller | | 1 | /registry/serviceaccounts/kube-system/default | | 1 | /registry/serviceaccounts/kube-system/deployment-controller | | 1 | /registry/serviceaccounts/kube-system/disruption-controller | | 1 | /registry/serviceaccounts/kube-system/endpoint-controller | | 1 | /registry/serviceaccounts/kube-system/endpointslice-controller | | 1 | /registry/serviceaccounts/kube-system/endpointslicemirroring-controller | | 1 | /registry/serviceaccounts/kube-system/ephemeral-volume-controller | | 1 | /registry/serviceaccounts/kube-system/expand-controller | | 1 | /registry/serviceaccounts/kube-system/generic-garbage-collector | | 1 | /registry/serviceaccounts/kube-system/horizontal-pod-autoscaler | | 1 | /registry/serviceaccounts/kube-system/job-controller | | 1 | /registry/serviceaccounts/kube-system/namespace-controller | | 1 | /registry/serviceaccounts/kube-system/node-controller | | 1 | /registry/serviceaccounts/kube-system/persistent-volume-binder | | 1 | /registry/serviceaccounts/kube-system/pod-garbage-collector | | 1 | /registry/serviceaccounts/kube-system/pv-protection-controller | | 1 | /registry/serviceaccounts/kube-system/pvc-protection-controller | | 1 | /registry/serviceaccounts/kube-system/replicaset-controller | | 1 | /registry/serviceaccounts/kube-system/replication-controller | | 1 | /registry/serviceaccounts/kube-system/resourcequota-controller | | 1 | /registry/serviceaccounts/kube-system/root-ca-cert-publisher | | 1 | /registry/serviceaccounts/kube-system/service-account-controller | | 1 | /registry/serviceaccounts/kube-system/service-controller | | 1 | /registry/serviceaccounts/kube-system/statefulset-controller | | 1 | /registry/serviceaccounts/kube-system/token-cleaner | | 1 | /registry/serviceaccounts/kube-system/ttl-after-finished-controller | | 1 | /registry/serviceaccounts/kube-system/ttl-controller | | 1 | /registry/services/endpoints/default/kubernetes | | 1 | /registry/services/specs/default/kubernetes | | 1 | compact_rev_key | +-----------+---------------------------------------------------------------------------------------------+ 271 rows in set (0.00 sec) 如上所示，有一个名为的表 “kine”包含所有数据。Kine 使用数据库作为日志结构存储，因此来自 API 服务器的每次写入都会创建一个新行来存储已创建或更新的 Kubernetes 对象，“name” 列使用与 etcd 相同的存储结构 “/registry/RESOURCE_TYPE/NAMESPACE/NAME” 表示集群中对象。\nk3s 资源分析 k3s 官方提供了 Resource Profiling [5] 来对比了 RDBMS 与 etcd 的性能对比。\n总结 因为 RDBMS 大家都很熟悉，并且更高性能的分布式解决方案也有很多，例如 YugabyteDB (PostgreSQL兼容的分布式数据库)，也可以预创建 kine 表，通过分区形式将不同数据存储到不同的分区内。而且 k8s 对象的历史数据也是可以根据一定的规则进行删除，因为 kubernetes 中的对象都是实时协调的，所以也不怕误删除，这样就会使得 kubernetes 规模有更大扩展的可能。\nReference ​[1] Worrying state of Etcd community\n​[2] Kine (Kine is not etcd)\n​[3] Installing kubeadm, kubelet and kubectl\n​[4] Minimal example of using kine\n​[5] resource-profiling\n​[6] Goodbye etcd, Hello PostgreSQL: Running Kubernetes with an SQL Database\n","permalink":"https://www.161616.top/kubernetes-without-etcd-step-by-step/","summary":"在本文中，将探讨使用 k3s 的 kine 项目来替换掉 etcd，并通过实验使用 kubeadm 去 run 一个 k8s 集群，并用 k3s 的 kine 项目来替换掉 etcd。\n为什么使用 kine etcd 在 Kubernetes 之外基本上没有应用的场景，并且 etcd 迭代也比较慢，由于没有人愿意维护因此一直在衰退 [1]，并且，Kubernetes 集群中，etcd 也是一个影响集群规模的重大因素。并且 K3S 存在一个项目 Kine 可以使用关系型数据库运行，这样对集群维护者来说可以不需要维护复杂的 etcd 集群，由于关系型数据库有很多高可用方案，这将使得 k8s 集群规模变成了无限可能。\nKine 介绍 前文提到，kubernetes (kube-apiserver) 与 etcd 是耦合的，如果我们要使用 RDBMS 去替换 etcd 就需要实现 etcd 的接口，那么这个项目就是 Kine [2]。\nKine 是一个 etcdshim，处于 kube-apiserver 和 RDBMS 的中间层，它实现了 etcdAPI的子集（不是etcd的全部功能），Kine 在 RDBMS 数据库之上实现了简单的多版本并发控制；将所有信息存储在一个表中；每行存储此 key 的修订, key, 当前值, 先前值, 先前修订，以及表示该 Key 是已创建还是已删除的标记，通过这种机制可以作为 shim 层来替换 etcd。","title":"构建集群kubernetes v1.28并使用kine和mysql替换etcd"},{"content":"设置一个网络接口 debian 中网络接口的配置文件是在 /etc/network/interfaces，可以设置 “静态地址” 或 “DHCP”\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 cat /etc/network/interfaces # This file describes the network interfaces available on your system # and how to activate them. For more information, see interfaces(5). source /etc/network/interfaces.d/* # The loopback network interface auto lo auto ens33 iface lo inet loopback # The primary network interface allow-hotplug ens33 iface ens33 inet static address 10.0.0.14 netmast 255.255.255.0 gateway 10.0.0.2 root@debian-template:~# cat /etc/network/interfaces # This file describes the network interfaces available on your system # and how to activate them. For more information, see interfaces(5). source /etc/network/interfaces.d/* # The loopback network interface # 网络接口自动启动 auto lo auto ens33 # 使用dhcp配置网络接口 iface eth0 inet dhcp iface lo inet loopback # The primary network interface allow-hotplug ens33 # 使用静态地址配置网络接口 iface ens33 inet static address 10.0.0.14 netmast 255.255.255.0 gateway 10.0.0.2 Enjoy 👏👏\nReference NetworkConfiguration\n","permalink":"https://www.161616.top/debian-network-configration/","summary":"设置一个网络接口 debian 中网络接口的配置文件是在 /etc/network/interfaces，可以设置 “静态地址” 或 “DHCP”\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 cat /etc/network/interfaces # This file describes the network interfaces available on your system # and how to activate them. For more information, see interfaces(5). source /etc/network/interfaces.","title":"Debian网络配置"},{"content":"Preparation debian12 几乎可以使用任何旧的计算机硬件，因为最小安装的要求非常低。以下是最低要求和推荐要求：\n最低要求 推荐要求 存储：10 Gigabytes\n内存：512 Megabytes\nCPU: 1 GigaHertz 存储：10 Gigabytes内存：2 GigabytesCPU: 1 GigaHertz or more 如何选择下载安装包 offical mirror aliyun mirror 官网提供了安装包的下载，其中CD是网络安装，DVD是离线安装\ndebian官方下载页面 Notes：CD安装包很小，下载下来是 debian-11.4.0-amd64-netinst.iso 如名所示，这是一个网络安装包，所以推荐下载DVD部分，可以达到离线安装的效果\n截至文章编写日期，debian-12.5.0-amd64-DVD-1.iso 大小是 3.7G\nDebian12 EOL：June 30th, 2028\n安装步骤 在界面中选择“Install”，安装将开始。如果图形化安装可以选择“Graphical install”，这里选择“Install”。\n欢迎页面 完成后，系统将提示选择安装时的“语言”。选择喜欢的语言，然后按“Enter”。这里选择英文\n选择语言页面 这将是接下来安装步骤\n安装步骤概述 选择位置与键盘布局 选择地区，这里选择美国\n选择区域 下面部署时选择键盘布局：中国大陆使用的键盘布局是美国-英语，不要选择英国-英语之类，布局是不一样的，会存在按键输出的结果会不同\n选择键盘布局 完成上述操作后，将开始加载镜像。等待扫描完成。。。。\n等待扫描组件 设置主机名和域名 这步骤中将配置一个“主机名”。与一个“域”名称。\n配置主机名 “域” 可以选择留空确定\n配置域 完成上述操作后，安装程序将提示需要设置 root 密码。输入您的 root 密码，然后在重新输入以进行验证后继续。\nTips: 这里建议设置密码，不设置密码会导致安装完成后无法进入 root 用户**，**这样就需要根据先登录普通用户，然后通过 sudo切换过去。\n设置Root密码 设置Root密码 - 二次确认 设置非ROOT用户名、账户和密码 下一步创建一个非ROOT用户，这个步骤是必须的，并为这个新创建的帐户分配一个密码。以下截图将描述将如何完成此操作。\n配置普通用户 为这个用户配置密码\n为普通用户配置密码 为普通用户配置密码——二次确认 设置时钟时区 当下面进度条完成后，则会进入设置时区的界面\n从互联网设置网络时间服务 Eastern 美东时间\nCentral 北美中部\nMountain 北美山区时区\nPacific 太平洋时区\nAlaska 阿拉斯加夏令时间\nHawaii 夏威夷时区\nArizona 亞利桑时区\nEast Indiana 印第安纳时区\nSamoa 萨摩亚时间\n配置时区 对磁盘分区 此步骤磁盘进行分区。这里选择 “手动” 选项\n选择分区模式 选择手动进行划分为所需的分区。\n选择硬盘 创建新的分区表\n创建分区表 选择空闲的空间进行分区\n选择空闲空间 创建一个新分区\n创建一个新分区 为/boot划分分区\n为/boot分区分配空间 挂载点选择 boot\n选择挂载点 最终划分的分区 这里选No就行，提示是指不使用swap分区，No就是继续，Yes将返回分区页面\n对于swap分区的提示 创建新分区需要格式化，当前的分区将会被删除，如果是新磁盘选择Yes格式化分区\n确认格式化，进行分区 Base System安装 这里等待安装基础系统\n确认格式化，进行分区 几分钟后， 安装后会弹出一个界面，这里会扫描其他的媒介 (media)，这里因为没有，选择No就行。\n扫描其他媒介 包管理镜像配置，这里选择 “No” 以加快安装速度\n包管理提示 离线安装 会扫描安装的媒介，这里也有提示，如果没有额外的媒介，可以跳过该步骤\n扫描其他媒介 配置网络镜像，建议配置下，如果不需要No即可\nTips: 如果选择 “No” , 那么安装完后，是没有apt包管理的仓库配置的，需要自行修改 /etc/apt/sources.list 文件\n配置网络镜像 如果上面选择了 “No” 下面配置镜像地址部分则不会出现，可以直接跳转到 “根据要求调整安装” 的步骤\n接下来会弹出一个界面，请选择 “Debian镜像国家” 。这个是配置镜像地址的，选择自己的国家和镜像站即可\n选择镜像国家 这里选择的是中国和中科大镜像\n选择镜像地址 配置HTTP代理，不选择跳过\n配置http代理 如果选择网络安装，到这步骤时安装程序现在将在选择相关的。首先，选择离您所在国家最近的位置。Debian 镜像位置和域后检索剩余的文件\n这里提示有一个匿名调查，这里选No即可\n匿名调查 根据要求调整安装 在检索过程中，系统将提示需要自行选择以下预定义软件中的一个或多个。最小化安装仅选择基础系统与SSH即可\n安装组件选择 接下来等待安装即可\n安装过程 Notes：选择了DVD ISO将离线完成安装，如果使用了CD ISO，将从互联网上检索包并安装，这个时间将很长。\n其中会提示一个引导按章，直接Yes即可\n到了这里即将安装完成\n到了这里即将安装完成 完成Debian12最小化安装 看到到这里已经完成了安装，按“Continue”继续重启后即可\n完成安装 看到系统的引导界面 Enjoy 👏👏\n","permalink":"https://www.161616.top/debian12-install-tutorial-step-by-step/","summary":"Preparation debian12 几乎可以使用任何旧的计算机硬件，因为最小安装的要求非常低。以下是最低要求和推荐要求：\n最低要求 推荐要求 存储：10 Gigabytes\n内存：512 Megabytes\nCPU: 1 GigaHertz 存储：10 Gigabytes内存：2 GigabytesCPU: 1 GigaHertz or more 如何选择下载安装包 offical mirror aliyun mirror 官网提供了安装包的下载，其中CD是网络安装，DVD是离线安装\ndebian官方下载页面 Notes：CD安装包很小，下载下来是 debian-11.4.0-amd64-netinst.iso 如名所示，这是一个网络安装包，所以推荐下载DVD部分，可以达到离线安装的效果\n截至文章编写日期，debian-12.5.0-amd64-DVD-1.iso 大小是 3.7G\nDebian12 EOL：June 30th, 2028\n安装步骤 在界面中选择“Install”，安装将开始。如果图形化安装可以选择“Graphical install”，这里选择“Install”。\n欢迎页面 完成后，系统将提示选择安装时的“语言”。选择喜欢的语言，然后按“Enter”。这里选择英文\n选择语言页面 这将是接下来安装步骤\n安装步骤概述 选择位置与键盘布局 选择地区，这里选择美国\n选择区域 下面部署时选择键盘布局：中国大陆使用的键盘布局是美国-英语，不要选择英国-英语之类，布局是不一样的，会存在按键输出的结果会不同\n选择键盘布局 完成上述操作后，将开始加载镜像。等待扫描完成。。。。\n等待扫描组件 设置主机名和域名 这步骤中将配置一个“主机名”。与一个“域”名称。\n配置主机名 “域” 可以选择留空确定\n配置域 完成上述操作后，安装程序将提示需要设置 root 密码。输入您的 root 密码，然后在重新输入以进行验证后继续。\nTips: 这里建议设置密码，不设置密码会导致安装完成后无法进入 root 用户**，**这样就需要根据先登录普通用户，然后通过 sudo切换过去。\n设置Root密码 设置Root密码 - 二次确认 设置非ROOT用户名、账户和密码 下一步创建一个非ROOT用户，这个步骤是必须的，并为这个新创建的帐户分配一个密码。以下截图将描述将如何完成此操作。","title":"安装Debian12 (Bookworm) Step-by-Step"},{"content":"OpenAPI 什么是OpenAPI Swagger 是一套围绕 OpenAPI 规范构建的开源工具，可帮助我们设计，构建，记录和使用 REST API。\nOpenAPI 规范（前名称为 Swagger 规范）是 REST API 的 API 描述格式。包括：\n可用端点 ( 例如 /users) 以及每个 endpoint 上的操作 (例如 GET /users, POST /users) 操作参数，每个操作的输入和输出 认证方法 联系信息，许可证，使用条款等其他信息。 什么是 Swagger？ Swagger 是一组围绕 OpenAPI 规范构建的开源工具，有助于用户设计，构建，记录和使用 REST API，支持整个 API 生命周期的开发，从设计和文档到测试和部署。\n使用 Swagger 的目的 标准化文档格式：Swagger (OpenAPI) 采用了准化 API 文档格式。通过使用 Swaggo（将注释转换为 Swagger2.0文档的包） 生成 Swagger 文档，Swagger 的结构化格式的文档，使开发人员更容易理解产品的 API 交互。 交互式文档体验：Swagger UI 与 Swaggo 集成，提供交互式且用户友好的界面，用于测试 API。Swaggo提供了一个自动生成的界面，允许开发人员浏览 Endpoint，查看请求/响应示例，甚至可以直接从文档执行 API 请求。这种交互式体验可提高开发人员的工作效率并加速 API 的采用。 自动且最新的文档：Swaggo 可自动从用户的 Go 代码生成 API 文档。这种自动化无需手动维护单独的文档文件。Swaggo 直接从用户的代码库中提取信息，包括 endpoint 详细信息，请求/响应模型和注释。使用这种方法可确保用户的 API 文档随着代码的更新而保持最新。 Swagger 与 Gin 的集成 拉取 Swaggo 使用如下命令下载swag\nbash 1 $ go install github.com/swaggo/swag/cmd/swag 与 Gin 的集成 添加通用注释 swaggo 中包含两种注释，通用注释与 API 注释，通用注释是用于程序 main.go 中，标记文档的信息，API 注释是用于标注每个接口的信息。\n下载相关包 bash 1 2 3 $ go install github.com/swaggo/swag/cmd/swag $ go get -v github.com/swaggo/gin-swagger # gin-swagger middleware $ go get -v github.com/swaggo/files # swagger embed files 在项目 main.go 源代码中添加通用的 API 注释： go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // @title Swagger Example API // @version 1.0 // @description This is a sample server celler server. // @termsOfService http://swagger.io/terms/ // @contact.name API Support // @contact.url http://www.swagger.io/support // @contact.email support@swagger.io // @license.name Apache 2.0 // @license.url http://www.apache.org/licenses/LICENSE-2.0.html // @host localhost:8080 // @BasePath /api/v1 // @securityDefinitions.basic BasicAuth // @externalDocs.description OpenAPI // @externalDocs.url https://swagger.io/resources/open-api/ func main() { r := gin.Default() ... } 为 Swagger Docs 添加路由 以将中间件添加到您的 Gin 应用程序中。在您的路由函数中或者主函数中，添加如下下代码：\n此代码为 Swagger UI 设置了一条路由，并告诉它在指定的 URL 处查找 Swagger 文档。\ngo 1 2 3 4 5 6 7 8 9 10 11 // 默认路由 r := gin.Default() url := ginSwagger.URL(\u0026#34;http://localhost:8080/swagger/doc.json\u0026#34;) r.GET(\u0026#34; /swagger/*any\u0026#34;, ginSwagger.WrapHandler(swaggerFiles.Handler, url)) // 路由组 // 如果已存在路由组，可以在对应注册路由的函数中添加 docs 路径 func RegisteredRouter(e *gin.Engine) { e.Handle(\u0026#34;GET\u0026#34;, \u0026#34;/swagger/*any\u0026#34;, ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.URL(\u0026#34;/swagger/doc.json\u0026#34;))) ... } 向 Gin API 服务器添加 API 注释 go 1 2 3 4 5 6 7 8 9 10 11 12 13 // ShowAccount godoc // @Summary Show an account // @Description get string by ID // @Tags accounts // @Accept json // @Produce json // @Param id path int true \u0026#34;Account ID\u0026#34; // @Success 200 {object} model.Account // @Failure 400 {object} httputil.HTTPError // @Failure 404 {object} httputil.HTTPError // @Failure 500 {object} httputil.HTTPError // @Router /accounts/{id} [get] func (c *Controller) ShowAccount(ctx *gin.Context) { 生成 Swagger 文档 将注释添加到代码后，可以通过运行以下命令生成 Swagger 文档\nbash 1 $ swag init -g cmd/main.go --output ./docs/ --packageName docs 此命令将会在项目中 docks 文件夹中生成一个文件 doc.json 。我们之前添加的中间件将使用此文件在 Swagger UI 中显示文档。\n确保导入了生成的包 docs/docs.go 文件，这样特定的配置文件才会被初始化。如果通用 API 注释没有写在 main.go 中，可以使用 -g 参数来告知 swag-cli\nbash 1 $ swag init -g http/api.go 访问 swagger 文档 在完成上述步骤后，可以浏览器中访问 http://{project_domain}/swagger/index.html，来查看 swagger 文档，这个路径取决于在 gin 路由里配置的路径。\nswagger 注释 通用 API 注释 注释 说明 示例 title 必填 应用程序的名称。 // @title Swagger Example API version 必填 提供应用程序API的版本。 // @version 1.0 description 应用程序的简短描述。 // @description This is a sample server celler server. tag.name 标签的名称。 // @tag.name This is the name of the tag tag.description 标签的描述。 // @tag.description Cool Description tag.docs.url 标签的外部文档的URL。 // @tag.docs.url https://example.com tag.docs.description 标签的外部文档说明。 // @tag.docs.description Best example documentation termsOfService API的服务条款。 // @termsOfService http://swagger.io/terms/ contact.name 公开的API的联系信息。 // @contact.name API Support contact.url 联系信息的URL。 必须采用网址格式。 // @contact.url http://www.swagger.io/support contact.email 联系人/组织的电子邮件地址。 必须采用电子邮件地址的格式。 // @contact.email support@swagger.io license.name 必填 用于API的许可证名称。 // @license.name Apache 2.0 license.url 用于API的许可证的URL。 必须采用网址格式。 // @license.url http://www.apache.org/licenses/LICENSE-2.0.html host 运行API的主机（主机名或IP地址）。 // @host localhost:8080 BasePath 运行API的基本路径。 // @BasePath /api/v1 accept API 可以使用的 MIME 类型列表。 请注意，Accept 仅影响具有请求正文的操作，例如 POST、PUT 和 PATCH。 值必须如“Mime类型”中所述。 // @accept json produce API可以生成的MIME类型的列表。值必须如“Mime类型”中所述。 // @produce json query.collection.format 请求URI query里数组参数的默认格式：csv，multi，pipes，tsv，ssv。 如果未设置，则默认为csv。 // @query.collection.format multi schemes 用空格分隔的请求的传输协议。 // @schemes http https externalDocs.description Description of the external document. // @externalDocs.description OpenAPI externalDocs.url URL of the external document. // @externalDocs.url https://swagger.io/resources/open-api/ x-name 扩展的键必须以x-开头，并且只能使用json值 // @x-example-key {\u0026ldquo;key\u0026rdquo;: \u0026ldquo;value\u0026rdquo;} API 注释 注释 描述 description 操作行为的详细说明。 description.markdown 应用程序的简短描述。该描述将从名为 endpointname.md 的文件中读取。 id 用于标识操作的唯一字符串。在所有 API 操作中必须唯一。 tags 每个 API 操作的标签列表，以逗号分隔。 summary 该操作的简短摘要。 accept API 可以使用的 MIME 类型列表。 请注意，Accept 仅影响具有请求正文的操作，例如 POST、PUT 和 PATCH。 值必须如 “Mime类型” 中所述。 produce API可以生成的MIME类型的列表。值必须如 “Mime类型” 中所述。 param 用空格分隔的参数。\nparam name,\nparam type,\ndata type,\nis mandatory?,\ncomment attribute(optional) security 每个 API 操作的安全性。 success 以空格分隔的成功响应。return code,{param type},data type,comment failure 以空格分隔的故障响应。return code,{param type},data type,comment response 与success、failure作用相同 header 以空格分隔的头字段。 return code,{param type},data type,comment router 以空格分隔的路径定义。 path,[httpMethod] deprecatedrouter 与router相同，但是是deprecated的。 x-name 扩展字段必须以 x- 开头，并且只能使用 json 值。 deprecated 将当前 API 操作的所有路径设置为deprecated 安全相关参数 注释 描述 参数 示例 securitydefinitions.basic Basic auth. // @securityDefinitions.basic BasicAuth securitydefinitions.apikey API key auth. in, name // @securityDefinitions.apikey ApiKeyAuth securitydefinitions.oauth2.application OAuth2 application auth. tokenUrl, scope // @securitydefinitions.oauth2.application OAuth2Application securitydefinitions.oauth2.implicit OAuth2 implicit auth. authorizationUrl, scope // @securitydefinitions.oauth2.implicit OAuth2Implicit securitydefinitions.oauth2.password OAuth2 password auth. tokenUrl, scope // @securitydefinitions.oauth2.password OAuth2Password securitydefinitions.oauth2.accessCode OAuth2 access code auth. tokenUrl, authorizationUrl, scope // @securitydefinitions.oauth2.accessCode OAuth2AccessCode troubleshooting Failed to load API definition. 引用了 swaggo 的文件必须添加下面包引用\ngo 1 _ \u0026#34;project_root_dir/cmd/docs\u0026#34; failed to load api definition. not found /swagger/v1/swagger.json 指定 doc 文档路径必须写对 [ginSwagger.URL(\u0026quot;/swagger/doc.json\u0026quot;)]\ngo 1 2 3 4 5 6 7 8 9 // 默认路由 r := gin.Default() url := ginSwagger.URL(\u0026#34;http://localhost:8080/swagger/doc.json\u0026#34;) r.GET(\u0026#34; /swagger/*any\u0026#34;, ginSwagger.WrapHandler(swaggerFiles.Handler, url)) // 路由组 // 如果已存在路由组，可以在对应注册路由的函数中添加 docs 路径 func RegisteredRouter(e *gin.Engine) { e.Handle(\u0026#34;GET\u0026#34;, \u0026#34;/swagger/*any\u0026#34;, ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.URL(\u0026#34;/swagger/doc.json\u0026#34;))) cannot find type definition: api_query.UserQuery 如果参数引用了一个对象，不可以写成 \u0026amp;api_query.UserQuery 的格式\nParseComment error in file :missing required param comment parameters \u0026ldquo;xxx\u0026rdquo; 你的项目内其他非 API 接口的函数不能使用与 API 注释相同格式的注释\ncannot find type definition: errcode.Error Reference ​[1] \u0026ldquo;Failed to load API definition\u0026rdquo; / \u0026ldquo;not yet registered swag\u0026rdquo; error when rendering docs page #830\n​[2] localhost:8080/swagger returns 404 page not found #2\n​[3] Implementing Swagger in Go Projects\n​[4] How to add Swagger in Golang Gin.\n​[5] Swagger使用的时候报错：Failed to load API definition\n​[6] README_zh-CN.md\n​[7] Golang swaggo rendering error: \u0026ldquo;Failed to load API definition\u0026rdquo; and \u0026ldquo;Fetch error doc.json\u0026rdquo;\n","permalink":"https://www.161616.top/golib-go-swagger/","summary":"OpenAPI 什么是OpenAPI Swagger 是一套围绕 OpenAPI 规范构建的开源工具，可帮助我们设计，构建，记录和使用 REST API。\nOpenAPI 规范（前名称为 Swagger 规范）是 REST API 的 API 描述格式。包括：\n可用端点 ( 例如 /users) 以及每个 endpoint 上的操作 (例如 GET /users, POST /users) 操作参数，每个操作的输入和输出 认证方法 联系信息，许可证，使用条款等其他信息。 什么是 Swagger？ Swagger 是一组围绕 OpenAPI 规范构建的开源工具，有助于用户设计，构建，记录和使用 REST API，支持整个 API 生命周期的开发，从设计和文档到测试和部署。\n使用 Swagger 的目的 标准化文档格式：Swagger (OpenAPI) 采用了准化 API 文档格式。通过使用 Swaggo（将注释转换为 Swagger2.0文档的包） 生成 Swagger 文档，Swagger 的结构化格式的文档，使开发人员更容易理解产品的 API 交互。 交互式文档体验：Swagger UI 与 Swaggo 集成，提供交互式且用户友好的界面，用于测试 API。Swaggo提供了一个自动生成的界面，允许开发人员浏览 Endpoint，查看请求/响应示例，甚至可以直接从文档执行 API 请求。这种交互式体验可提高开发人员的工作效率并加速 API 的采用。 自动且最新的文档：Swaggo 可自动从用户的 Go 代码生成 API 文档。这种自动化无需手动维护单独的文档文件。Swaggo 直接从用户的代码库中提取信息，包括 endpoint 详细信息，请求/响应模型和注释。使用这种方法可确保用户的 API 文档随着代码的更新而保持最新。 Swagger 与 Gin 的集成 拉取 Swaggo 使用如下命令下载swag","title":"Go每日一库 - 使用 gin + goswagger 构建 REST API 文档"},{"content":"创建服务账户 首先可以到「 IAM管理 -\u0026gt; 服务帐户」新增帐户。在新增完成后，会得到一把 key，将它下载后请妥善保管，因为所有相关的身份认证都会用到，这个 key 在下载后就无法继续下载了。\n授权 接着到「IAM -\u0026gt; 新增」成员，并且选择角色，这里选择「Cloud Storage -\u0026gt; 储存空间物件检视者」，让此帐户具备有 read（读取） storage 的功能。\n登录 bash 1 2 cat KEY-FILE | docker login -u KEY-TYPE --password-stdin \\ https://LOCATION-docker.pkg.dev GCP 的 KEY-TYPE 通为 json_key，但这里包含两种类型 _json_key 和 _json_key_base64\nKEY-FILE 就是下载的 Service account key 的文件\nbash 1 2 cat KEY-FILE | docker login -u _json_key --password-stdin \\ https://LOCATION-docker.pkg.dev 通常 Service account key 文件内容如下\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 { \u0026#34;type\u0026#34;: \u0026#34;service_account\u0026#34;, \u0026#34;project_id\u0026#34;: \u0026#34;project2024-0101\u0026#34;, \u0026#34;private_key_id\u0026#34;: \u0026#34;bdfsd612779509406bb8452c3ek12d730ed547e722d\u0026#34;, \u0026#34;private_key\u0026#34;: \u0026#34;-----BEGIN PRIVATE KEY----....-----END PRIVATE KEY-----\\n\u0026#34;, \u0026#34;client_email\u0026#34;: \u0026#34;gcr@project2024-0101.iam.gserviceaccount.com\u0026#34;, \u0026#34;client_id\u0026#34;: \u0026#34;206651723512339084907274\u0026#34;, \u0026#34;auth_uri\u0026#34;: \u0026#34;https://accounts.google.com/o/oauth2/auth\u0026#34;, \u0026#34;token_uri\u0026#34;: \u0026#34;https://oauth2.googleapis.com/token\u0026#34;, \u0026#34;auth_provider_x509_cert_url\u0026#34;: \u0026#34;https://www.googleapis.com/oauth2/v1/certs\u0026#34;, \u0026#34;client_x509_cert_url\u0026#34;: \u0026#34;https://www.googleapis.com/robot/v1/metadata/x509/manager-image%40project2024-0101.iam.gserviceaccount.com\u0026#34;, \u0026#34;universe_domain\u0026#34;: \u0026#34;googleapis.com\u0026#34; } ","permalink":"https://www.161616.top/docker-push-gcr/","summary":"创建服务账户 首先可以到「 IAM管理 -\u0026gt; 服务帐户」新增帐户。在新增完成后，会得到一把 key，将它下载后请妥善保管，因为所有相关的身份认证都会用到，这个 key 在下载后就无法继续下载了。\n授权 接着到「IAM -\u0026gt; 新增」成员，并且选择角色，这里选择「Cloud Storage -\u0026gt; 储存空间物件检视者」，让此帐户具备有 read（读取） storage 的功能。\n登录 bash 1 2 cat KEY-FILE | docker login -u KEY-TYPE --password-stdin \\ https://LOCATION-docker.pkg.dev GCP 的 KEY-TYPE 通为 json_key，但这里包含两种类型 _json_key 和 _json_key_base64\nKEY-FILE 就是下载的 Service account key 的文件\nbash 1 2 cat KEY-FILE | docker login -u _json_key --password-stdin \\ https://LOCATION-docker.pkg.dev 通常 Service account key 文件内容如下\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 { \u0026#34;type\u0026#34;: \u0026#34;service_account\u0026#34;, \u0026#34;project_id\u0026#34;: \u0026#34;project2024-0101\u0026#34;, \u0026#34;private_key_id\u0026#34;: \u0026#34;bdfsd612779509406bb8452c3ek12d730ed547e722d\u0026#34;, \u0026#34;private_key\u0026#34;: \u0026#34;-----BEGIN PRIVATE KEY----.","title":"使用docker管理谷歌物件仓库gcr上的镜像"},{"content":"Kubernetes监控架构设计 k8s监控设计背景说明 根据 Kubernetes监控架构 1，Kubernetes 集群中的 metrcis 可以分为 系统指标 (Core Metrics) 和 服务指标 (service metrics) ; 系统指标(System metrics) 是通用的指标，通常可以从每一个被监控的实体中获得（例如，容器和节点的CPU和内存使用情况）。服务指标(Service metrics) 是在应用程序代码中显式定义并暴露的 (例如，API Server 处理的 500 错误数量)。\nKubernetes将系统指标分为两部分：\n核心指标 (core metrics) 是 Kubernetes 理解和用于其内部组件和核心工具操作的指标，例如：用于调度的指标 (包括资源估算算法的输入, 初始资源/VPA (vertical autoscaling)，集群自动扩缩 (cluster autoscaling)，水平Pod自动扩缩 (horizontal pod autoscaling ) 除自定义指标之外的指标)；Kube Dashboard 使用的指标，以及 “kubectl top” 命令使用的指标。 非核心指标 (non-core metrics) 是指不被 Kubernetes 解释的指标。我们一般假设这些指标包含核心指标 (但不一定是 Kubernetes 可理解的格式)，以及其他额外的指标。 所以，kubernetes monitoring 的架构被设计拥有如下特点：\n通过标准的主 API (当前为主监控 API) 提供关于Node, Pod 和容器的核心系统指标，使得核心 Kubernetes 功能不依赖于非核心组件 kubelet 只导出有限的指标集，即核心 Kubernetes 组件正常运行所需的指标。 \u0026hellip; 监控管道 Kubernetes 监控管道分为两个：\n核心指标管道 (core metrics pipeline) 由 Kubelet、资源估算器, 一个精简版 Heapster (metrics-server)，以及 api-server 中 master metrics API 组成。这些指标被核心系统组件使用，例如调度逻辑（如调度器和基于系统指标的HPA）和一些简单 UI 组件（如 kubectl top），这个管道并不打算与第三方监控系统集成。 监控管道：一个用于收集系统中的各种指标并将其暴露给最终用户端，以及通过适配器暴露给 HPA(用于自定义指标) 和 Infrastore 的。用户可以选择多种监控系统供应商（例如 Prometheus, metric-server），也可以完全不使用。 Core Metrics Pipeline 根据 kubernetes 监控设计文档可以得知，核心指标指\n使用这组核心指标，由Kubelet收集，并仅供 Kubernetes 系统组件使用，支持\u0026quot;第一类资源隔离和利用特性\u0026quot;。 不设计成面向用户的 API，而是尽可能通用，以支持未来的用户级组件。 核心指标的包含三类：\nCpuUsage: 记录从创建对象开始的累计CPU使用时间。 MemoryUsage: 记录工作集内存使用量。 FilesystemUsage: 记录文件系统使用情况,包括已用字节数和已用Inode数。 Monitoring Pipeline 根据 Kubernetes 监控设计文档 1 得知，监控管道用于与核心Kubernetes组件分开的系统，可以更加灵活。并且监控管道可以收集不同类型的指标：\nCore system metrics Non-core system metrics Service metrics from user application containers Service metrics from Kubernetes infrastructure containers (using Prometheus instrumentation) 监控管道主要用于根据自定义指标进行 HPA，监控管道提供了一个无状态的 API Adapter，用于拉去监控给 HPA\n指标API API类别 根据监控架构设计文档，Kubernetes 定义了两套指标 API，资源指标 API 和 自定义指标 API；Kubernetes 为资源指标 API 提供了两种实现：Heapster 和 metrics-server，而自定义指标 API 由不同的监控供应商实现。下面将详细描述每个 API。\n资源指标 API (Resource Metrics API)：该 API 允许消费者访问 Pod 和 Node 的资源指标（CPU \u0026amp; Memory）\nThe API is implemented by metrics-server and prometheus-adapter. 自定义指标 API (Custom Metrics API)：该 API 允许消费者访问描述 Kubernetes 资源的任意指标。\n用户可以根据 kubernetes-sigs/custom-metrics-apiserver 仓库来自定义 API-server API的访问 资源指标，该 API 是在 /apis/metrics.k8s.io/ ，可以使用 kubectl proxy --port 8080 代理后进行访问，\nbash 1 2 $ kubectl proxy --port=8080 $ curl localhost:8080/apis/metrics.k8s.io/v1beta1/nodes 或者使用 kubectl get --raw 进行获取\nbash 1 $ kubectl get --raw \u0026#34;/apis/metrics.k8s.io/v1beta1/nodes\u0026#34; | jq 自定义指标，该 API 是在 /apis/custom.metrics.k8s.io/ ，访问的方式相同，用户通过该 PATH 进行访问。\nbash 1 2 # 查看有哪些指标可用 $ kubectl get --raw \u0026#34;/apis/custom.metrics.k8s.io/v1beta1/\u0026#34; | jq Prometheus-adapter 通过上一章节介绍了kubernetes监控体系，这已经可以了解到了 prometheus-adapter 的定位；prometheus-adapter 是通过 kubernetes custom-metrics-apiserver 标准实现的一个 custom.metrics.k8s.io API，用于提供给 HPA 的一种指标适配器，可以将任何指标转化为 HPA 可用的指标。他全名为 Kubernetes Custom Metrics Adapter for Prometheus。\nprometheus-adapter配置文件详解 prometheus-adapter负责确定哪些指标以及如何去发现这些指标，根据这个标准，配置文件分为四个步骤来完成这套 “发现” 规则\n每一个指标可以大致分为四个部分，对应在配置文件中：\nDiscovery ，用于指定 adapter 应如何查找此规则的所有Prometheus指标。 Association ，用于指定 adapter 应如何确定特定指标与哪些 Kubernetes 资源相关联。 Naming ，用于指定 adapter 应如何在自定义指标 API 中公开该指标。 Querying ，用于指定如何将针对一个或多个 Kubernetes 对象的特定指标请求转换为对 Prometheus 的查询。 配置文件如下所示，这是官方给出的样板配置文件（文章编写时版本为0.12）\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 rules: # Each rule represents a some naming and discovery logic. # Each rule is executed independently of the others, so # take care to avoid overlap. As an optimization, rules # with the same `seriesQuery` but different # `name` or `seriesFilters` will use only one query to # Prometheus for discovery. # some of these rules are taken from the \u0026#34;default\u0026#34; configuration, which # can be found in pkg/config/default.go # this rule matches cumulative cAdvisor metrics measured in seconds - seriesQuery: \u0026#39;{__name__=~\u0026#34;^container_.*\u0026#34;,container!=\u0026#34;POD\u0026#34;,namespace!=\u0026#34;\u0026#34;,pod!=\u0026#34;\u0026#34;}\u0026#39; resources: # skip specifying generic resource\u0026lt;-\u0026gt;label mappings, and just # attach only pod and namespace resources by mapping label names to group-resources overrides: namespace: {resource: \u0026#34;namespace\u0026#34;} pod: {resource: \u0026#34;pod\u0026#34;} # specify that the `container_` and `_seconds_total` suffixes should be removed. # this also introduces an implicit filter on metric family names name: # we use the value of the capture group implicitly as the API name # we could also explicitly write `as: \u0026#34;$1\u0026#34;` matches: \u0026#34;^container_(.*)_seconds_total$\u0026#34; # specify how to construct a query to fetch samples for a given series # This is a Go template where the `.Series` and `.LabelMatchers` string values # are available, and the delimiters are `\u0026lt;\u0026lt;` and `\u0026gt;\u0026gt;` to avoid conflicts with # the prometheus query language metricsQuery: \u0026#34;sum(rate(\u0026lt;\u0026lt;.Series\u0026gt;\u0026gt;{\u0026lt;\u0026lt;.LabelMatchers\u0026gt;\u0026gt;,container!=\u0026#34;POD\u0026#34;}[2m])) by (\u0026lt;\u0026lt;.GroupBy\u0026gt;\u0026gt;)\u0026#34; # this rule matches cumulative cAdvisor metrics not measured in seconds - seriesQuery: \u0026#39;{__name__=~\u0026#34;^container_.*_total\u0026#34;,container!=\u0026#34;POD\u0026#34;,namespace!=\u0026#34;\u0026#34;,pod!=\u0026#34;\u0026#34;}\u0026#39; resources: overrides: namespace: {resource: \u0026#34;namespace\u0026#34;} pod: {resource: \u0026#34;pod\u0026#34;} seriesFilters: # since this is a superset of the query above, we introduce an additional filter here - isNot: \u0026#34;^container_.*_seconds_total$\u0026#34; name: {matches: \u0026#34;^container_(.*)_total$\u0026#34;} metricsQuery: \u0026#34;sum(rate(\u0026lt;\u0026lt;.Series\u0026gt;\u0026gt;{\u0026lt;\u0026lt;.LabelMatchers\u0026gt;\u0026gt;,container!=\u0026#34;POD\u0026#34;}[2m])) by (\u0026lt;\u0026lt;.GroupBy\u0026gt;\u0026gt;)\u0026#34; # this rule matches cumulative non-cAdvisor metrics - seriesQuery: \u0026#39;{namespace!=\u0026#34;\u0026#34;,__name__!=\u0026#34;^container_.*\u0026#34;}\u0026#39; name: {matches: \u0026#34;^(.*)_total$\u0026#34;} resources: # specify an a generic mapping between resources and labels. This # is a template, like the `metricsQuery` template, except with the `.Group` # and `.Resource` strings available. It will also be used to match labels, # so avoid using template functions which truncate the group or resource. # Group will be converted to a form acceptible for use as a label automatically. template: \u0026#34;\u0026lt;\u0026lt;.Resource\u0026gt;\u0026gt;\u0026#34; # if we wanted to, we could also specify overrides here metricsQuery: \u0026#34;sum(rate(\u0026lt;\u0026lt;.Series\u0026gt;\u0026gt;{\u0026lt;\u0026lt;.LabelMatchers\u0026gt;\u0026gt;,container!=\u0026#34;POD\u0026#34;}[2m])) by (\u0026lt;\u0026lt;.GroupBy\u0026gt;\u0026gt;)\u0026#34; # this rule matches only a single metric, explicitly naming it something else # It\u0026#39;s series query *must* return only a single metric family - seriesQuery: \u0026#39;cheddar{sharp=\u0026#34;true\u0026#34;}\u0026#39; # this metric will appear as \u0026#34;cheesy_goodness\u0026#34; in the custom metrics API name: {as: \u0026#34;cheesy_goodness\u0026#34;} resources: overrides: # this should still resolve in our cluster brand: {group: \u0026#34;cheese.io\u0026#34;, resource: \u0026#34;brand\u0026#34;} metricsQuery: \u0026#39;count(cheddar{sharp=\u0026#34;true\u0026#34;})\u0026#39; # external rules are not tied to a Kubernetes resource and can reference any metric # https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-metrics-not-related-to-kubernetes-objects externalRules: - seriesQuery: \u0026#39;{__name__=\u0026#34;queue_consumer_lag\u0026#34;,name!=\u0026#34;\u0026#34;}\u0026#39; metricsQuery: sum(\u0026lt;\u0026lt;.Series\u0026gt;\u0026gt;{\u0026lt;\u0026lt;.LabelMatchers\u0026gt;\u0026gt;}) by (name) - seriesQuery: \u0026#39;{__name__=\u0026#34;queue_depth\u0026#34;,topic!=\u0026#34;\u0026#34;}\u0026#39; metricsQuery: sum(\u0026lt;\u0026lt;.Series\u0026gt;\u0026gt;{\u0026lt;\u0026lt;.LabelMatchers\u0026gt;\u0026gt;}) by (name) # Kubernetes metric queries include a namespace in the query by default # but you can explicitly disable namespaces if needed with \u0026#34;namespaced: false\u0026#34; # this is useful if you have an HPA with an external metric in namespace A # but want to query for metrics from namespace B resources: namespaced: false # TODO: should we be able to map to a constant instance of a resource # (e.g. `resources: {constant: [{resource: \u0026#34;namespace\u0026#34;, name: \u0026#34;kube-system\u0026#34;}}]`)? Discovery Discovery 部分控制了查找要在自定义指标 API 中公开的指标的过程。其中有两个关键字段：seriesQuery 和 seriesFilters。\nseriesQuery 指定了用于查找某些 Prometheus series 的 Prometheus series 查询(作为传递给 Prometheus /api/v1/series)。适配器将从这些系列中剥离标签值，然后在后续步骤中使用得到的“指标名称—标签名称”的组合。\n在许多情况下，seriesQuery 就足以缩小 Prometheus series 的列表。但有时(特别是当两个规则可能重叠时)，对指标名称进行额外的过滤是很有用的。在这种情况下,可以使用 seriesFilters。在从 seriesQuery 返回 series 列表后，每个 series 的指标名称都会通过指定的任何过滤器进行过滤。\n过滤器可以是以下两种形式之一:\nis: ，匹配名称符合指定正则表达式的任何序列。 isNot: ，匹配名称不符合指定正则表达式的任何序列。 例如\nyaml 1 2 3 4 # match all cAdvisor metrics that aren\u0026#39;t measured in seconds seriesQuery: \u0026#39;{__name__=~\u0026#34;^container_.*_total\u0026#34;,container!=\u0026#34;POD\u0026#34;,namespace!=\u0026#34;\u0026#34;,pod!=\u0026#34;\u0026#34;}\u0026#39; seriesFilters: - isNot: \u0026#34;^container_.*_seconds_total\u0026#34; Association Association 部分控制了确定序列指标可以附加到哪些 Kubernetes 资源的过程。resources 字段控制了这个过程。\n有两种方式来关联资源与特定指标。在这两种情况下,标签的值都会成为特定对象的名称。\n一种方式是指定，任何符合某个特定模式的标签名称都指向基于标签名称的某个“group_resource”。这可以使用 template 字段来完成。pattern 被指定为一个 Go 模板，其中 Group 和 Resource 字段分别代表“组”和“资源”。\nyaml 1 2 3 # any label `kube_\u0026lt;group\u0026gt;_\u0026lt;resource\u0026gt;` becomes \u0026lt;group\u0026gt;.\u0026lt;resource\u0026gt; in Kubernetes resources: template: \u0026#34;kube_\u0026lt;\u0026lt;.Group\u0026gt;\u0026gt;_\u0026lt;\u0026lt;.Resource\u0026gt;\u0026gt;\u0026#34; 另一种方式是指定某个特定标签代表某个特定的 Kubernetes 资源。这可以使用 overrides 字段来完成。每个 override 将一个 Prometheus 标签映射到一个 Kubernetes group-resource。例如:\nyaml 1 2 3 4 5 6 # the microservice label corresponds to the apps.deployment resource resources: overrides: microservice: group: \u0026#34;apps\u0026#34; resource: \u0026#34;deployment\u0026#34; Association 部分提供了两种关联 Prometheus 指标和 Kubernetes 资源的方式，可以根据需要灵活地组合使用。这是实现自定义指标 API 的关键一环。\nNaming Naming 部分控制了将 Prometheus 指标名称转换为自定义指标 API 中的指标，这是通过 name 字段来实现的。\nNaming 的控制通过指定一个从 Prometheus 名称中提取 API 名称的模式，以及对提取值进行的可选转换来实现。\n模式由 matches 字段指定，这是一个正则表达式。如果没有指定,它默认为 .* 。\n转换由 as 字段指定。你可以使用 matches 字段中定义的任何捕获组。如果 matches 字段没有捕获组，as 字段默认为 $0 。如果只包含一个捕获组，as 字段默认为 $1 。否则，如果没有指定 as 字段就会出错。例如\nyaml 1 2 3 4 5 # match turn any name \u0026lt;name\u0026gt;_total to \u0026lt;name\u0026gt;_per_second # e.g. http_requests_total becomes http_requests_per_second name: matches: \u0026#34;^(.*)_total$\u0026#34; as: \u0026#34;${1}_per_second\u0026#34; Querying Querying 部分控制了实际获取特定指标值的过程。它由 metricsQuery 字段来控制。\nmetricsQuery 字段是一个 Go 模板,它会被转换成一个 Prometheus 查询，使用从特定的自定义指标 API 调用获取的输入数据。对自定义指标 API 的一次调用会被简化为一个指标名称、一个 “group-resource” 和一个或多个该 “group-resource” 的对象。这些会被转换成模板中的以下字段:\nSeries: 指标名称 LabelMatchers: 一个逗号分隔的标签匹配器列表，匹配给定的对象。当前包括特定的 “group-resource” 标签，以及 namespace 标。 GroupBy: 一个逗号分隔的用于分组的标签列表。当前包括用于 LabelMatchers 的组-资源标签。 例如，假设我们有一个 http_requests_total 序列 (在 API 中公开为 http_requests_per_second )，具有 service、pod、ingress、namespace 和 verb 标签。前四个对应于 Kubernetes 资源。那么,如果有人请求了 pods/http_request_per_second 指标，那么针对 somens 命名空间中的 pod1 和 pod2，我们会有:\nSeries: \u0026ldquo;http_requests_total\u0026rdquo; LabelMatchers: \u0026quot;pod=~\u0026quot;pod1|pod2\u0026quot;,namespace=\u0026quot;somens\u0026quot;\u0026quot; GroupBy: pod 对应 prometheus promql 如下所示\nbash 1 sum(http_requests_total{pod=~\u0026#34;pod1|pod2\u0026#34;,namespace=\u0026#34;somens\u0026#34;}) by (pod) 此外,还有两个高级字段是其他字段的\u0026quot;原始\u0026quot;形式:\nLabelValuesByName: 映射。将 LabelMatchers 字段中的标签和值对应起来。值是用 | 预先连接的 (用于在 Prometheus 中使用 =~ 匹配器)。 GroupBySlice: GroupBy 字段的切片形式。 通常，我们可能会想使用 Series、LabelMatchers 和 GroupBy 字段。其他两个是用于高级用法的。\nQuerying 预计会为每个请求的对象返回一个值。适配器会使用返回的系列上的标签，将给定的系列关联回其相应的对象。例如:\nbash 1 2 # convert cumulative cAdvisor metrics into rates calculated over 2 minutes metricsQuery: \u0026#34;sum(rate(\u0026lt;\u0026lt;.Series\u0026gt;\u0026gt;{\u0026lt;\u0026lt;.LabelMatchers\u0026gt;\u0026gt;,container!=\u0026#34;POD\u0026#34;}[2m])) by (\u0026lt;\u0026lt;.GroupBy\u0026gt;\u0026gt;)\u0026#34; 完整的配置文件实例 例如，我们想使用 springboot 的 actuator 提供的 jvm_memory_used_bytes 和 jvm_memory_max_bytes 计算内存使用率，如下所式\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 rules: - seriesQuery: \u0026#39;jvm_memory_used_bytes\u0026#39; resources: overrides: namespace: resource: \u0026#34;namespace\u0026#34; pod: resource: \u0026#34;pod\u0026#34; name: matches: \u0026#39;jvm_memory_used_bytes\u0026#39; as: memory_percent metricsQuery: \u0026#39;sum(jvm_memory_used_bytes{\u0026lt;\u0026lt;.LabelMatchers\u0026gt;\u0026gt;}) by (\u0026lt;\u0026lt;.GroupBy\u0026gt;\u0026gt;) / sum(jvm_memory_max_bytes{\u0026lt;\u0026lt;.LabelMatchers\u0026gt;\u0026gt;}) by (\u0026lt;\u0026lt;.GroupBy\u0026gt;\u0026gt;) * 100\u0026#39; - seriesQuery: \u0026#39;process_cpu_usage\u0026#39; resources: overrides: namespace: resource: \u0026#34;namespace\u0026#34; pod: resource: \u0026#34;pod\u0026#34; name: matches: \u0026#39;process_cpu_usage\u0026#39; as: process_cpu_percent metricsQuery: \u0026#39;sum(avg_over_time(process_cpu_usage{\u0026lt;\u0026lt;.LabelMatchers\u0026gt;\u0026gt;}[1m])) by (\u0026lt;\u0026lt;.GroupBy\u0026gt;\u0026gt;)\u0026#39; 这里用到了一个技巧，就是使用查询多个指标，这里参考了 prometheus-adapter 的说明 4\n这很好理解,虽然一开始可能看起来不太明显。\n基本上，你只需要选择一个指标作为 \u0026ldquo;Discovery\u0026rdquo; 和 \u0026ldquo;naming\u0026rdquo; 指标，然后使用它来配置配置中的 \u0026ldquo;discovery\u0026rdquo; 和 \u0026ldquo;naming\u0026rdquo; 部分。之后，你就可以在 metricsQuery 中写任何你想要的指标了！ ==Querying 的序列可以包含任何你想要的指标，只要它们有正确的标签集合即可==。\n例如，假设你有两个指标 foo_total 和 foo_count，它们都有一个标签 system_name，用于表示节点资源，那么如下配置所示\nyaml 1 2 3 4 5 6 7 rules: - seriesQuery: \u0026#39;foo_total\u0026#39; resources: {overrides: {system_name: {resource: \u0026#34;node\u0026#34;}}} name: matches: \u0026#39;foo_total\u0026#39; as: \u0026#39;foo\u0026#39; metricsQuery: \u0026#39;sum(foo_total{\u0026lt;\u0026lt;.LabelMatchers\u0026gt;\u0026gt;}) by (\u0026lt;\u0026lt;.GroupBy\u0026gt;\u0026gt;) / sum(foo_count{\u0026lt;\u0026lt;.LabelMatchers\u0026gt;\u0026gt;}) by (\u0026lt;\u0026lt;.GroupBy\u0026gt;\u0026gt;)\u0026#39; 由于我们使用了 jvm_memory_used_bytes 和 jvm_memory_max_bytes ，那么我们可以在 \u0026ldquo;discovery\u0026rdquo; 和 \u0026ldquo;naming\u0026rdquo; 部分写任意指标，在 ”quering“ 中使用真是的指标进行替换，就可以完成\n查询 kubernetes 的指标 完成配置后，可以使用下面命令进行查询\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 kubectl get --raw \u0026#34;/apis/custom.metrics.k8s.io/v1beta1\u0026#34;|jq { \u0026#34;kind\u0026#34;: \u0026#34;APIResourceList\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;groupVersion\u0026#34;: \u0026#34;custom.metrics.k8s.io/v1beta1\u0026#34;, \u0026#34;resources\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;pods/process_cpu_percent\u0026#34;, \u0026#34;singularName\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;namespaced\u0026#34;: true, \u0026#34;kind\u0026#34;: \u0026#34;MetricValueList\u0026#34;, \u0026#34;verbs\u0026#34;: [ \u0026#34;get\u0026#34; ] }, { \u0026#34;name\u0026#34;: \u0026#34;pods/memory_percent\u0026#34;, \u0026#34;singularName\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;namespaced\u0026#34;: true, \u0026#34;kind\u0026#34;: \u0026#34;MetricValueList\u0026#34;, \u0026#34;verbs\u0026#34;: [ \u0026#34;get\u0026#34; ] }, { \u0026#34;name\u0026#34;: \u0026#34;namespaces/memory_percent\u0026#34;, \u0026#34;singularName\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;namespaced\u0026#34;: false, \u0026#34;kind\u0026#34;: \u0026#34;MetricValueList\u0026#34;, \u0026#34;verbs\u0026#34;: [ \u0026#34;get\u0026#34; ] }, { \u0026#34;name\u0026#34;: \u0026#34;namespaces/process_cpu_percent\u0026#34;, \u0026#34;singularName\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;namespaced\u0026#34;: false, \u0026#34;kind\u0026#34;: \u0026#34;MetricValueList\u0026#34;, \u0026#34;verbs\u0026#34;: [ \u0026#34;get\u0026#34; ] } ] } 可以通过 custom API 进程查询具体获取的值，如下所示\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 $ kubectl get --raw \u0026#34;/apis/custom.metrics.k8s.io/v1beta1/namespaces/public/pods/*/process_cpu_percent\u0026#34;|jq { \u0026#34;kind\u0026#34;: \u0026#34;MetricValueList\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;custom.metrics.k8s.io/v1beta1\u0026#34;, \u0026#34;metadata\u0026#34;: {}, \u0026#34;items\u0026#34;: [ { \u0026#34;describedObject\u0026#34;: { \u0026#34;kind\u0026#34;: \u0026#34;Pod\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;msg\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;message-gateway-api-78c4d5cdbf-9k2g7\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;/v1\u0026#34; }, \u0026#34;metricName\u0026#34;: \u0026#34;process_cpu_percent\u0026#34;, \u0026#34;timestamp\u0026#34;: \u0026#34;2024-05-31T11:40:25Z\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;404m\u0026#34;, \u0026#34;selector\u0026#34;: null }, ... { \u0026#34;describedObject\u0026#34;: { \u0026#34;kind\u0026#34;: \u0026#34;Pod\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;msg\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;message-core-79fdc6fdd-lkpdm\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;/v1\u0026#34; }, \u0026#34;metricName\u0026#34;: \u0026#34;process_cpu_percent\u0026#34;, \u0026#34;timestamp\u0026#34;: \u0026#34;2024-05-31T11:40:25Z\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;31m\u0026#34;, \u0026#34;selector\u0026#34;: null }, { \u0026#34;describedObject\u0026#34;: { \u0026#34;kind\u0026#34;: \u0026#34;Pod\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;msg\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;message-push-admin-554f5d96fd-xlnhj\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;/v1\u0026#34; }, \u0026#34;metricName\u0026#34;: \u0026#34;process_cpu_percent\u0026#34;, \u0026#34;timestamp\u0026#34;: \u0026#34;2024-05-31T11:40:25Z\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;487m\u0026#34;, \u0026#34;selector\u0026#34;: null } ] } 我们可以看到，返回值是带有 ”m“ 的单位，这里 issue 是这样回答的\nThe m-suffix means milli, Quantity Values are explained here: https://github.com/kubernetes-sigs/prometheus-adapter/blob/master/docs/walkthrough.md#quantity-values 5\n在指标 API 中最常见的是 m 后缀,它表示毫单位，即单位的千分之一；由于我们返回值是一个百分比，例如 4.87%，那么实际值是 0.0487，那么他的毫单位为就是 “487m” ，和上面返回值一样。\nPrometheus-adapter的安装 在这里采用 helm 方式进行安装，只需要修改对应参数即可\nbash 1 2 3 4 helm install prometheus-adapter -n monitoring prometheus-community/prometheus-adapter \\ --set prometheus.url=http://prometheus.default.svc \\ --set logLevel=2 \\ --set rules.external=xxx # 如果使用外部规则替换默认的config.yaml,则需要提前创建一个configmap，然后这里指定这个名称 Reference ​[1] Kubernetes monitoring architecture\n​[2] core-metrics-pipeline\n​[3] kubernetes/metrics\n​[4] my-query-contains-multiple-metrics-how-do-i-make-that-work\n​[5] why i request rest-api, returned requeslt for item value has \u0026rsquo;m\u0026rsquo; unit!! #376\n​[6] Guide to Kubernetes Metrics\n​[7] Kubernetes 监控架构(译)\n","permalink":"https://www.161616.top/prometheus-adapter-intro/","summary":"Kubernetes监控架构设计 k8s监控设计背景说明 根据 Kubernetes监控架构 1，Kubernetes 集群中的 metrcis 可以分为 系统指标 (Core Metrics) 和 服务指标 (service metrics) ; 系统指标(System metrics) 是通用的指标，通常可以从每一个被监控的实体中获得（例如，容器和节点的CPU和内存使用情况）。服务指标(Service metrics) 是在应用程序代码中显式定义并暴露的 (例如，API Server 处理的 500 错误数量)。\nKubernetes将系统指标分为两部分：\n核心指标 (core metrics) 是 Kubernetes 理解和用于其内部组件和核心工具操作的指标，例如：用于调度的指标 (包括资源估算算法的输入, 初始资源/VPA (vertical autoscaling)，集群自动扩缩 (cluster autoscaling)，水平Pod自动扩缩 (horizontal pod autoscaling ) 除自定义指标之外的指标)；Kube Dashboard 使用的指标，以及 “kubectl top” 命令使用的指标。 非核心指标 (non-core metrics) 是指不被 Kubernetes 解释的指标。我们一般假设这些指标包含核心指标 (但不一定是 Kubernetes 可理解的格式)，以及其他额外的指标。 所以，kubernetes monitoring 的架构被设计拥有如下特点：\n通过标准的主 API (当前为主监控 API) 提供关于Node, Pod 和容器的核心系统指标，使得核心 Kubernetes 功能不依赖于非核心组件 kubelet 只导出有限的指标集，即核心 Kubernetes 组件正常运行所需的指标。 \u0026hellip; 监控管道 Kubernetes 监控管道分为两个：","title":"深入解析Kubernetes监控体系与prometheus-adapter"},{"content":"工作流模型是指 Stackstorm 中 Orquesta 工作流的定义，包含工作流的执行方式，也可以理解为工作流模型就是 Workflow DSL 定义任务执行的有向图\n工作流模型 下表是工作流模型 (Workflow Model) 的属性。工作流接受 Input，按预定义顺序执行一组任务 (Task)，并返回输出 (Output)。此处的工作流模型是一个有向图 (directed graph)，其中 Task 是节点，Taskt 之间的转换及其条件形成边。组成工作流的任务将在 DSL 中定义为名为 Task 的字典， 其中 Key 和 Value 分别是任务名称和任务模型。\nAttribute Required Description version Yes The version of the spec being used in this workflow DSL. description No The description of the workflow. input No A list of input arguments for this workflow. vars No A list of variables defined for the scope of this workflow. tasks Yes A dictionary of tasks that defines the intent of this workflow. output No A list of variables defined as output for the workflow. 下面示例是一个 Workflow 的 DSL 定义，说明基础的工作流模型中各个部分的组成\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 version: 1.0 description: A simple workflow. # 字符串列表，假设将在运行时提供值或键值对，其中当运行时未提供值时，值使用默认值。 # 这些属性的定义在 Action 的元数据文件中定义，包含默认值 input: - arg1 - arg2: abc # 一组键值对的变量，可以用于存储数据 vars: - var1: 123 - var2: True - var3: null # 由字典组成任务定义，执行任务顺序由入栈任务转换和出栈条件组成 tasks: # 定义两个任务，next为下一个执行的任务 task1: action: core.noop next: - do: task2 task2: action: core.noop # 要输出的键值对列表 output: - var3: \u0026lt;% ctx().arg1 %\u0026gt; - var4: var41: 456 var42: def - var5: - 1.0 - 2.0 - 3.0 with-item 模型 with-item 模型 是workflow 批量处理任务的一种方式，with-item 将遍历每个 item，然后作为参数传给 Action，默认情况下，所有 Item 将同时处理。当配置 concurrency 指定时，将处理最多 concurrency 值的 item 数，其余项目将排队等待执行，当 Item 的 Action 执行完成后，将处理列表中的下一个item。\n任务结果是按照与item相同的顺序排列的Action执行结果的列表。所有Action执行必须成功完成，任务才能达到成功状态。如果一个或多个Action执行失败，则该任务将为失败状态。\n当有取消或暂停工作流的请求时，任务将分别处于取消或暂停状态，直到执行过程中的所有Action执行完成。一旦这些Action执行完成时，任务将分别进入取消或暂停状态。如果指定了任务的 concurrency 并且还有剩余item，则不会请求新的 Action 执行。当暂停的工作流程恢复时，任务将继续处理剩余的 Item。\nItem 可配置参数如下：\nAttribute Required Description items Yes The list of items to execute the action with. concurrency No The number of items being processed concurrently. 一个简单的with-item 模型示例 以下是一个简单示例，其中包含任务中定义的单个 Item 列表。该任务会收到一个要回显的消息列表。对于不需要并发的项目 Item，有一个速记符号可以将列表直接传递给 with 语句。可以使用 item 函数将各个项目作为输入传递到 Action 中以供执行。\nbash 1 2 3 4 5 6 7 8 9 version: 1.0 input: - messages tasks: task1: with: \u0026lt;% ctx(messages) %\u0026gt; action: core.echo message=\u0026lt;% item() %\u0026gt; 当需要并发执行时，使用 with-items 的 schema 去定义 ，如下所示\nyaml 1 2 3 4 5 6 7 8 9 10 11 version: 1.0 input: - messages tasks: task1: with: items: \u0026lt;% ctx(messages) %\u0026gt; concurrency: 2 action: core.echo message=\u0026lt;% item() %\u0026gt; 进阶：为Item命名 Item 也可以被命名，下面示例时和上面相同功能的 workflow，但他为 items 使用 “message” 的作为名称进行标注。在标记时，指定了 message in \u0026lt;% ctx(messages) %\u0026gt; 进行命名，这里 item 被指定为 “message”，在引用时，item 函数也必须指定名称 item(message)，这种场景返回的就不是列表，而是一个字典，类似 {\u0026quot;message\u0026quot;: \u0026quot;value\u0026quot;} 在处理多个项目时比较有用。\nyaml 1 2 3 4 5 6 7 8 9 version: 1.0 input: - messages tasks: task1: with: message in \u0026lt;% ctx(messages) %\u0026gt; action: core.echo message=\u0026lt;% item(message) %\u0026gt; 为多个Item命名 在执行 Action 时，可以将多个 Item 作为 input 传入到 Action中，这里就利用到 item 命名的方式与另外 stackstrom 提供的 zip 函数。zip 与 python zip 相同，将多个元组进行迭代，然后将他们（多个元组相同下标项）的每项压缩为一个元组。\nyaml 1 2 3 4 5 6 7 8 9 10 11 个元组进行迭代，然后将他们的每项压缩为一个元组。 version: 1.0 input: - hosts - commands tasks: task1: with: host, command in \u0026lt;% zip(ctx(hosts), ctx(commands)) %\u0026gt; action: core.remote hosts=\u0026lt;% item(host) %\u0026gt; cmd=\u0026lt;% item(command) %\u0026gt; 上面示例为，input 有两个列表，hosts 和 commands，这里使用 zip 压缩后将便成为 ，({hosts:xxx}, {commands:xxx}) 这样在执行时通过 item 函数指定名称 item(host)，就可以获取到对应的迭代器位的 host，通过这样的方式来进行多列表参数的传入。\n下面是一个多参数 with-item 模型的执行示例\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 version: 1.0 description: A workflow demonstrating with items. input: - members - test tasks: task1: with: host, command in \u0026lt;% zip(ctx(members), ctx(test)) %\u0026gt; action: core.echo message=\u0026#34;\u0026lt;% item() %\u0026gt;, resistance is futile!\u0026#34; output: - items: \u0026lt;% task(task1).result.items.select($.result.stdout) %\u0026gt; 通过执行结果可以看出 multiple-Item 的机制\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 st2 execution get 664dd003169d72f36729cb70 id: 664dd003169d72f36729cb70 action.ref: frist.orquesta-with-items parameters: None status: succeeded (1s elapsed) start_timestamp: Wed, 22 May 2024 10:59:15 UTC end_timestamp: Wed, 22 May 2024 10:59:16 UTC log: - status: requested timestamp: \u0026#39;2024-05-22T10:59:15.698000Z\u0026#39; - status: scheduled timestamp: \u0026#39;2024-05-22T10:59:15.813000Z\u0026#39; - status: running timestamp: \u0026#39;2024-05-22T10:59:15.865000Z\u0026#39; - status: succeeded timestamp: \u0026#39;2024-05-22T10:59:16.918000Z\u0026#39; result: output: items: - \u0026#39;{\u0026#39;\u0026#39;host\u0026#39;\u0026#39;: \u0026#39;\u0026#39;Lakshmi\u0026#39;\u0026#39;, \u0026#39;\u0026#39;command\u0026#39;\u0026#39;: \u0026#39;\u0026#39;t1\u0026#39;\u0026#39;}, resistance is futile!\u0026#39; - \u0026#39;{\u0026#39;\u0026#39;host\u0026#39;\u0026#39;: \u0026#39;\u0026#39;Lindsay\u0026#39;\u0026#39;, \u0026#39;\u0026#39;command\u0026#39;\u0026#39;: \u0026#39;\u0026#39;t2\u0026#39;\u0026#39;}, resistance is futile!\u0026#39; - \u0026#39;{\u0026#39;\u0026#39;host\u0026#39;\u0026#39;: \u0026#39;\u0026#39;Tomaz\u0026#39;\u0026#39;, \u0026#39;\u0026#39;command\u0026#39;\u0026#39;: \u0026#39;\u0026#39;t3\u0026#39;\u0026#39;}, resistance is futile!\u0026#39; +--------------------------+------------------------+-------+-----------+------------------------------+ | id | status | task | action | start_timestamp | +--------------------------+------------------------+-------+-----------+------------------------------+ | 664dd004ed940d64824e33fb | succeeded (0s elapsed) | task1 | core.echo | Wed, 22 May 2024 10:59:16 UTC| | 664dd004ed940d64824e33fe | succeeded (0s elapsed) | task1 | core.echo | Wed, 22 May 2024 10:59:16 UTC| | 664dd004ed940d64824e3402 | succeeded (0s elapsed) | task1 | core.echo | Wed, 22 May 2024 10:59:16 UTC| +--------------------------+------------------------+-------+-----------+------------------------------+ Reference ​[1] Sensors and Triggers\n​[2] How many do you need? - Argo CD Architectures Explained\n","permalink":"https://www.161616.top/stackstorm-sensors/","summary":"工作流模型是指 Stackstorm 中 Orquesta 工作流的定义，包含工作流的执行方式，也可以理解为工作流模型就是 Workflow DSL 定义任务执行的有向图\n工作流模型 下表是工作流模型 (Workflow Model) 的属性。工作流接受 Input，按预定义顺序执行一组任务 (Task)，并返回输出 (Output)。此处的工作流模型是一个有向图 (directed graph)，其中 Task 是节点，Taskt 之间的转换及其条件形成边。组成工作流的任务将在 DSL 中定义为名为 Task 的字典， 其中 Key 和 Value 分别是任务名称和任务模型。\nAttribute Required Description version Yes The version of the spec being used in this workflow DSL. description No The description of the workflow. input No A list of input arguments for this workflow. vars No A list of variables defined for the scope of this workflow.","title":"StackStorm自动化 - 工作流模型"},{"content":"应用连接 nacos 时报错 403，用户密码均正确，用户存在，并且权限正确\ntext 1 2 2024-05-20 10:36:11,593 - [ERROR] - [ropertySourceBuilder][main][n.c.NacosPropertySourceBuilder: 101][]: get data from Nacos error,dataId:admin-server.yaml - com.alibaba.nacos.api.exception.NacosException: http error, code=403,msg=user not found!,dataId=admin-server.yaml,group=DEFAULT_GROUP,tenant=cced143f-5adb-4cb8-a580-28802ea8f203 at com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient.queryConfig(ClientWorker.java:979) 原因：nacos 地址应该和应用连接的不一致，修改一致后恢复正常\nnacos 配置文件配置 “/nacos”， 应用直接连接 “/”\nbash 1 server.servlet.contextPath=/nacos ","permalink":"https://www.161616.top/nacos-403-user-not-found/","summary":"应用连接 nacos 时报错 403，用户密码均正确，用户存在，并且权限正确\ntext 1 2 2024-05-20 10:36:11,593 - [ERROR] - [ropertySourceBuilder][main][n.c.NacosPropertySourceBuilder: 101][]: get data from Nacos error,dataId:admin-server.yaml - com.alibaba.nacos.api.exception.NacosException: http error, code=403,msg=user not found!,dataId=admin-server.yaml,group=DEFAULT_GROUP,tenant=cced143f-5adb-4cb8-a580-28802ea8f203 at com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient.queryConfig(ClientWorker.java:979) 原因：nacos 地址应该和应用连接的不一致，修改一致后恢复正常\nnacos 配置文件配置 “/nacos”， 应用直接连接 “/”\nbash 1 server.servlet.contextPath=/nacos ","title":"nacos code=403,msg=user not found!"},{"content":"本文档使用以下术语 机器系列：针对特定工作负载优化的一组精选处理器和硬件配置。创建虚拟机时，您可以从首选机器系列中选择预定义或自定义机器类型。\n机器系列：机器系列按系列和世代进一步分类。例如，通用机器系列中的 N1 系列是 N2 系列的旧版本。世代编号或系列号越高，表示底层 CPU 平台或技术较新。例如，M3 系列是 M2 系列的较新世代。\n机器类型：每个机器类型都有一个预定义机器类型，用于为您的虚拟机提供一组资源。如果预定义机器类型不能满足您的需求，您还可以为某些机器系列创建自定义机器类型。\n代 Intel AMD Arm 第 4 代机器系列 N4, C4, X4 N/A C4A 第 3 代机器系列 C3、Z3、H3、M3、A3 C3D N/A 第 2 代机器系列 E2、N2、C2、M2、A2、G2 N2D、C2D、T2D、E2 T2A 第 1 代机器系列 N1、M1 机器系列和系列建议 下表提供了针对不同工作负载的建议。\n通用工作负载 E2 N2、N2D、N1 C3、C3D Tau T2D、Tau T2A 以更低的费用进行日常计算 在多种机器类型之间实现均衡的性价比 在各种工作负载中始终如一地保持高性能 最佳每核心性能/费用（适用于横向扩容工作负载） 低流量 Web 服务器\n后台应用\n容器化的微服务\n微服务\n虚拟桌面\n开发和测试环境 中低流量 Web 和应用服务器\n容器化的微服务\n商业智能应用\n虚拟桌面\nCRM 应用\nData Pipelines 高流量 Web 和应用服务器\n数据库\n内存缓存\n广告服务器\nGame Servers\n数据分析\n媒体流式传输和转码\n基于 CPU 的机器学习训练和推断 横向扩容工作负载\nWeb 服务\n容器化的微服务\n媒体转码\n大规模 Java 应用 优化的工作负载 存储优化 计算优化 内存优化 加速器优化 Z3 H3、C2、C2D M3、M2、M1 A3、A2、G2 块存储与计算比率最高，适合处理存储密集型工作负载 超高性能，适合处理计算密集型工作负载 内存与计算比率最高，适合处理内存密集型工作负载 针对加速的高性能计算工作负载进行了优化 文件服务器\n专为闪存优化的数据库\n扩容分析\n其他数据库 受计算限制的工作负载\n高性能 Web 服务器\nGame Servers\n高性能计算 (HPC)\n媒体转码\n建模和模拟工作负载\nAI/机器学习 大中型 SAP HANA 内存数据库\n内存中数据存储区，例如 Redis\n模拟\n高性能数据库，例如 Microsoft SQL Server、MySQL\n电子设计自动化 生成式 AI 模型，如以下所示： 大语言模型 (LLM)\n扩散模型\n生成对抗网络 (GAN)\n支持 CUDA 的机器学习训练和推断\n高性能计算 (HPC)\n大规模并行计算\nBERT 自然语言处理\n深度学习推荐模型 (DLRM)\n视频转码\n远程可视化工作站 创建虚拟机后，您可以使用“合理容量建议”来根据工作负载优化资源利用率。如需了解详情，请参阅为虚拟机应用机器类型建议\n通用机器类型 机器系列 工作负载 C3、C3D 高流量 Web、应用和广告服务器\n数据库和缓存\n游戏服务器\n数据分析\n媒体流式传输和转码\n网络设备\n基于 CPU 的机器学习训练和推理 E2 低流量 Web 服务器\n后台应用\n容器化的微服务\n小型数据库\n虚拟桌面\n开发和测试环境 N2、N2D、Tau T2D、N1 中低流量 Web 和应用服务器\n容器化的微服务\n商业智能应用\n虚拟桌面\nCRM 应用\n数据流水线 Tau T2A 横向扩容工作负载\n网络服务器\n容器化的微服务\n媒体转码\n大规模 Java 应用 C3D 机器系列 C3D 虚拟机由第 4 代 AMD EPYC™ (Genoa) 处理器提供支持，最高频率为 3.7 Ghz。C3D 机器类型针对底层硬件架构进行了优化，以提供可靠且一致的最佳性能。\nC3D 使用 Google 的自定义 IPU，可实现更高级别的网络性能、隔离和安全性。C3D 机器系列支持高达 100 Gbps 的默认网络带宽和高达 200 Gbps 的每个虚拟机的 Tier_1 网络性能。\n总的来说，C3D 机器系列：\n由第 4 代 AMD EPYC™ 处理器和 Google 的 IPU 提供支持。 最多支持 360 个 vCPU 和 2880 GB 的 DDR5 内存。 支持具有高达 100 Gbps 带宽的标准网络配置和高达 200 Gbps 带宽的 Tier_1 网络。 C3D 虚拟机有 standard、highcpu、highmem 和 lssd 配置可供选择，其大小范围为 4 到 360 个 vCPU 和高达 2880 GB 的内存。highcpu 配置为不需要大量内存的计算受限工作负载提供了最佳性价比。\n如何快速通过代号定位价格 Cx / CxD 计算优化 (Compute-optimized)，D代表AMD处理器\nMx 内存优化 (Memory-optimized)\nAx 加速器优化 (Accelerator-optimized)\nGx 图形优化 (Graphics-optimized)\nNx, C3(x), E2 Tau 通用型(General-purpose)\nEx 经济型 (Economy) 最多32个vCPU，每个vCPU 8G内存， E2是在 N1, N2 之间， N1 最多 96 个 vCPU，每个 vCPU 最多 6.5 GB，Intel Sandy Bridge、Ivy Bridge、Haswell、Broadwell、Skylake 等 CPU 平台上使用。 N2 最多128个vCPU，每个vCPU 8G内存， Intel Ice Lake 和 Intel Cascade Lake CPU 平台 N2D 最多224 个 vCPU，每个vCPU 8G内存 AMD EPYC Rome 和第三代 AMD EPYC Milan C3 176 个 vCPU 以及每个 vCPU 配备 2、4 或 8 GB 内存 Intel Sapphire Rapids CPU 平台 C3D 360 个 vCPU 和每个 vCPU 2、4 或 8 GB 内存 AMD EPYC Genoa CPU Tau T2A/D ARM/AMD Zx 存储优化(storage-optimized), Z表示Zonal，表明存储时本地的，最多 176 个 vCPU、1408 GB 内存和 36 TiB 本地 SSD\nReference 机器系列资源和比较指南 ","permalink":"https://www.161616.top/type-of-machine-for-gcp/","summary":"本文档使用以下术语 机器系列：针对特定工作负载优化的一组精选处理器和硬件配置。创建虚拟机时，您可以从首选机器系列中选择预定义或自定义机器类型。\n机器系列：机器系列按系列和世代进一步分类。例如，通用机器系列中的 N1 系列是 N2 系列的旧版本。世代编号或系列号越高，表示底层 CPU 平台或技术较新。例如，M3 系列是 M2 系列的较新世代。\n机器类型：每个机器类型都有一个预定义机器类型，用于为您的虚拟机提供一组资源。如果预定义机器类型不能满足您的需求，您还可以为某些机器系列创建自定义机器类型。\n代 Intel AMD Arm 第 4 代机器系列 N4, C4, X4 N/A C4A 第 3 代机器系列 C3、Z3、H3、M3、A3 C3D N/A 第 2 代机器系列 E2、N2、C2、M2、A2、G2 N2D、C2D、T2D、E2 T2A 第 1 代机器系列 N1、M1 机器系列和系列建议 下表提供了针对不同工作负载的建议。\n通用工作负载 E2 N2、N2D、N1 C3、C3D Tau T2D、Tau T2A 以更低的费用进行日常计算 在多种机器类型之间实现均衡的性价比 在各种工作负载中始终如一地保持高性能 最佳每核心性能/费用（适用于横向扩容工作负载） 低流量 Web 服务器\n后台应用\n容器化的微服务\n微服务\n虚拟桌面\n开发和测试环境 中低流量 Web 和应用服务器\n容器化的微服务\n商业智能应用\n虚拟桌面\nCRM 应用","title":"GCP机器类型"},{"content":"GKE添加VPC防火墙规则有与GCE有一些区别，必须找到GKE的”网络标记“才可以，如下\n选择 Kubernetes Engine =\u0026gt; 选择 ”集群“ 进入 GKE 集群主页\n随便选择一个集群，进入集群详细页面\n选择集群中的任意一个节点池\n找到节点池中任意一个节点，点击进入\n进入后向下拉找到”网络标记“部分，这个网络标记可以标记这个集群的所有节点\n如果想自定义”网络标记“的名称，可以在集群首页选择标记进行修改\n","permalink":"https://www.161616.top/gke-vpc-firewall/","summary":"GKE添加VPC防火墙规则有与GCE有一些区别，必须找到GKE的”网络标记“才可以，如下\n选择 Kubernetes Engine =\u0026gt; 选择 ”集群“ 进入 GKE 集群主页\n随便选择一个集群，进入集群详细页面\n选择集群中的任意一个节点池\n找到节点池中任意一个节点，点击进入\n进入后向下拉找到”网络标记“部分，这个网络标记可以标记这个集群的所有节点\n如果想自定义”网络标记“的名称，可以在集群首页选择标记进行修改","title":"GKE - 为GKE集群增加VPC防火墙规则"},{"content":" 工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 Pod 检查 Pod 就绪探针\nbash 1 kubectl get pods \u0026lt;pod-name\u0026gt; -n \u0026lt;namespace\u0026gt; -o jsonpath=\u0026#39;{.status.conditions[?(@.type==\u0026#34;Ready\u0026#34;)].status}\u0026#39; 查看 Pod 事件\nbash 1 kubectl get events -n \u0026lt;namespace\u0026gt; --field-selector involvedObject.name=\u0026lt;pod-name\u0026gt; 获取 Pod Affinity 和 Anti-Affinity\nbash 1 kubectl get pod \u0026lt;pod-name\u0026gt; -n \u0026lt;namespace\u0026gt; -o=jsonpath=\u0026#39;{.spec.affinity}\u0026#39; 列出 Pod 的 anti-affinity 规则\nbash 1 kubectl get pod \u0026lt;pod-name\u0026gt; -n \u0026lt;namespace\u0026gt; -o=jsonpath=\u0026#39;{.spec.affinity.podAntiAffinity}\u0026#39; Pod Network 运行 Debug Pod 进行调试\nbash 1 kubectl run -it --rm --restart=Never --image=busybox net-debug-pod -- /bin/sh 测试从 Pod 到 Endpoint 的连接性\nbash 1 kubectl exec -it \u0026lt;pod-name\u0026gt; -n \u0026lt;namespace\u0026gt; -- curl \u0026lt;endpoint-url\u0026gt; trace 一个 Pod 到另一个 Pod 的网络\nbash 1 kubectl exec -it \u0026lt;source-pod-name\u0026gt; -n \u0026lt;namespace\u0026gt; -- traceroute \u0026lt;destination-pod-ip\u0026gt; 检查 Pod DNS配置\nbash 1 kubectl exec -it \u0026lt;pod-name\u0026gt; -n \u0026lt;namespace\u0026gt; -- cat /etc/resolv.conf Workload Deployment 查看 rollout 状态\nbash 1 kubectl rollout status deployment/\u0026lt;deployment-name\u0026gt; -n \u0026lt;namespace\u0026gt; 查看 rollout 历史记录\nbash 1 kubectl rollout history deployment/\u0026lt;deployment-name\u0026gt; -n \u0026lt;namespace\u0026gt; 调整 deployment 数量\nbash 1 kubectl scale deploy \u0026lt;deployment-name\u0026gt; --replicas=0 -n \u0026lt;namespace\u0026gt; 调整名称空间内的所有 Deployment 工作负载数量为 0\nbash 1 2 3 for d in `kubectl get deploy -n ${ns} -o jsonpath=\u0026#39;{range $.items[@]}{.metadata.name}{end}\u0026#39;`; do kubectl scale deploy ${d} --replicas=0 -n ${ns} done 调整整个集群的 Deployment 工作负载数量为 0\nbash 1 2 3 4 5 6 7 for namespace in `kubectl get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;` do for workload in `kubectl get deploy -n ${namespace} -o jsonpath=\u0026#39;{range $.items[@]}{.metadata.name}{end}\u0026#39;` do kubectl scale deploy ${workload} --replicas=0 -n ${namespace} done done 设置 deployment 的自动缩放\nbash 1 2 3 4 5 kubectl autoscale deployment \u0026lt;deployment-name\u0026gt; \\ --min=\u0026lt;min-pods\u0026gt; \\ --max=\u0026lt;max-pods\u0026gt; \\ --cpu-percent=\u0026lt;cpu-percent\u0026gt; \\ -n \u0026lt;namespace\u0026gt; DaemonSet 调整一个 Daemonset 资源为 0\nbash 1 kubectl -n \u0026lt;namespace\u0026gt; patch daemonset \u0026lt;name-of-daemon-set\u0026gt; -p \u0026#39;{\u0026#34;spec\u0026#34;: {\u0026#34;template\u0026#34;: {\u0026#34;spec\u0026#34;: {\u0026#34;nodeSelector\u0026#34;: {\u0026#34;disable\u0026#34;: \u0026#34;true\u0026#34;}}}}}\u0026#39; 恢复\nbash 1 kubectl -n \u0026lt;namespace\u0026gt; patch daemonset \u0026lt;name-of-daemon-set\u0026gt; --type json -p=\u0026#39;[{\u0026#34;op\u0026#34;: \u0026#34;remove\u0026#34;, \u0026#34;path\u0026#34;: \u0026#34;/spec/template/spec/nodeSelector/disable\u0026#34;}]\u0026#39; 调整名称空间内的所有 Daemonset 工作负载数量为 0\nbash 1 2 3 4 NAMESPACE=default for daemonset in `kubectl get daemonset -n ${NAMESPACE} -o jsonpath=\u0026#39;{range $.items[@]}{.metadata.name}{end}\u0026#39;`; do kubectl -n ${NAMESPACE} patch daemonset ${daemonset} -p \u0026#39;{\u0026#34;spec\u0026#34;: {\u0026#34;template\u0026#34;: {\u0026#34;spec\u0026#34;: {\u0026#34;nodeSelector\u0026#34;: {\u0026#34;disable\u0026#34;: \u0026#34;true\u0026#34;}}}}}\u0026#39; done HPA 检查 HPA 状态\nbash 1 kubectl get hpa -n \u0026lt;namespace\u0026gt; CRD 批量删除 CRD 资源\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 KUBE_CMD=\u0026#39;kubectl --kubeconfig=\u0026#39; for r in `${KUBE_CMD} get crd -ojsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;` do echo $r for ns in `${KUBE_CMD} get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;` do for crd_name in `${KUBE_CMD} get $r -n $ns -ojsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;` do ${KUBE_CMD} delete $crd_name -n $ns done done echo $n | grep \u0026#34;cattle.io\u0026#34; \u0026amp;\u0026amp; ${KUBE_CMD} delete crd $sn --force done `` ## Networking 显示命名空间中 Pod 的 IP 地址： ```bash kubectl get pods -n \u0026lt;namespace -o custom-columns=POD:metadata.name,IP:status.podIP --no-headers Node 获取特定 Node 上运行的 Pod 列表\n方法1\nbash 1 kubectl get pods --field-selector spec.nodeName=\u0026lt;node-name\u0026gt; -n \u0026lt;namespace\u0026gt; 方法2\nbash 1 2 kubectl get pod -n {ns} -o \\ jsonpath=\u0026#39;{range $.items[?(@.spec.nodeName == \u0026#34;xxxx\u0026#34;)]}{.metadata.name}{\u0026#34;\\n\u0026#34;}\u0026#39; 方法3：检查多个 Node 上运行的 Pod 列表\nbash 1 2 3 4 kubectl get pod -A -ojson | jq -r \u0026#39; .items[] | select(.status.hostIP as $ip | $ip == \u0026#34;10.10.10.124\u0026#34; or $ip == \u0026#34;10.10.10.153\u0026#34; )| .metadata.namespace + \u0026#34;/\u0026#34; + .metadata.name + \u0026#34; / \u0026#34; + .status.hostIP\u0026#39; 方法4：使用 JQ 简化查询（test）\nbash 1 2 3 4 5 6 7 8 9 KUBE_CMD=\u0026#39;kubectl kubeconfig=\u0026#39; EXCLUDE_NS=\u0026#34;^kube.*|debug|logging|monitoring|default|cattle|fleet|infra\u0026#34; HOSTS=\u0026#34;\u0026#34; ${KUBE_CMD} get pods -A -o json | jq --arg EXCLUDE_NS ${EXCLUDE_NS} --arg HOSTS ${HOSTS} -r \u0026#39; .items[] | select( .metadata.namespace | test($EXCLUDE_NS) | not ) | select( .status.hostIP | test($HOSTS) ) | \u0026#34;\\(.metadata.namespace)/\\(.metadata.name)/\\(.status.hostIP)\u0026#34;\u0026#39; 获取 Node 的 IP\nbash 1 2 kubectl get node -n {ns} -o \\ -o=jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\t\u0026#34;}{.status.addresses[0].address}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; 获取 Node 上 指定标签\nbash 1 2 kubectl get nodes \\ -o=jsonpath=\u0026#39;{range .items[*]}{.metadata.name}{\u0026#34;\\t\u0026#34;}{.metadata.labels.\u0026lt;xxx\u0026gt;}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; 查找属于这个标签 Node 的数量\nbash 1 kubectl get node -l xxx=xxxx --no-headers | wc -l 查看node上所有的标签，json格式\nbash 1 kubectl get node -o json | jq \u0026#39;.items[] | {name: .metadata.name, labels: .metadata.labels }\u0026#39; --compact-output | jq -r 根据 NodeSelector 查询 application\nbash 1 2 kubectl get deployment -A \\ -o=jsonpath=\u0026#39;{range .items[*]}{@.metadata.name}{\u0026#34;\\t\u0026#34;}{range @.spec.template.spec.nodeSelector}{.xxxx}{\u0026#34;\\n\u0026#34;}{end}{end}\u0026#39; 根据条件列出 Node 1.17+\nbash 1 2 kubectl get nodes -o \\ custom-columns=NODE:.metadata.name,READY:.status.conditions[?(@.type==\u0026#34;Ready\u0026#34;)].status -l \u0026#39;node-role.kubernetes.io/worker=xxx\u0026#39; 获取 Node 的操作系统信息\nbash 1 kubectl get node \u0026lt;node-name\u0026gt; -o jsonpath=\u0026#39;{.status.nodeInfo.osImage}\u0026#39; 查询应用 (Deployment) 使用的 Node\nbash 1 2 kubectl deployment -n {ns} -o \\ jsonpath=\u0026#39;{range $.item[*]}{\u0026#34;Deployment: \u0026#34;}{.metadata.name}{\u0026#34;\\n\u0026#34;}{\u0026#34;\\t\u0026#34;}{.spec.template.spec.nodeSelector.service}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; 仅获取 Node 的指定标签和 Pod 名称\nbash 1 2 k get nodes -o \\ jsonpath=\u0026#39;{range .items[*]}{.metadata.labels.nodeSelector.xxxxx}{\u0026#34;\\t\u0026#34;}{.status.addresses[?(@.type==\u0026#34;InternalIP\u0026#34;)].address}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; 仅获取 Node IP\nbash 1 k get nodes -o jsonpath=\u0026#39;{.items[*].status.addresses[?(@.type==\u0026#34;InternalIP\u0026#34;)].address}\u0026#39; 获取指定标签的 Node，并输出他的标签和 IP\nbash 1 2 k get nodes -o \\ jsonpath=\u0026#39;{range .items[?(@.metadata.labels.xxxx==\u0026#34;xxxx\u0026#34;)]}{.metadata.labels.service}{\u0026#34;\\t\u0026#34;}{.status.addresses[?(@.type==\u0026#34;InternalIP\u0026#34;)].address}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; 获取指定 Role 的 Node 列表，只输出他的主机名\nbash 1 2 k get node --all-namespaces \\ -o=jsonpath=\u0026#39;{range .items[?(@.metadata.labels.kubernetes.io/role==\u0026#34;xxxx\u0026#34;)]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; 修改指定 Role 的 Node 的角色值 (jq)\nbash 1 2 3 4 5 # 假设将 k8s role 为 aaa 的主机修改为 role=bbb for n in `k get nodes -o json |jq -r \u0026#39;.items[] | select(.metadata.labels.\u0026#34;kubernetes.io/role\u0026#34; == \u0026#34;aaa\u0026#34;) | .metadata.name\u0026#39;`; do k label node $n kubernetes.io/role=bbb --overwrite done 修改指定 Role 的 Node 的角色值 (jsonpath)\nbash 1 2 3 4 5 # 假设将 k8s node 标签 xxx 为 aaa 的主机修改为 xxx=bbb for n in `k get nodes -o jsonpath=\u0026#39;{range .items[?(@.metadata.labels.xxx==\u0026#34;aaa\u0026#34;)]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;`;\\ do k label node $n xxx=bbb --overwrite done 打印 Node 所在的 Node 不要其他垃圾字段\nbash 1 2 3 kubectl get pod -A -owide --no-headers \\ --field-selector=\u0026#39;metadata.namespace!=infra,metadata.namespace!=debug,metadata.namespace!=kube-system,metadata.namespace!=logging,metadata.namespace!=monitoring\u0026#39; \\ -o=custom-columns=NODE:.spec.nodeName,NAMESPACE:.metadata.namespace,NAME:.metadata.name|grep ${n} 打印 所需要的 Node 不要其他垃圾字段\nbash 1 2 3 4 5 6 7 8 9 NODES=\u0026#34;xxxx xxxx xxxx\u0026#34; for n in ${NODES} do echo ${n} kubectl get pod -A -owide --no-headers \\ --field-selector=\u0026#39;metadata.namespace!=infra,metadata.namespace!=debug,metadata.namespace!=kube-system,metadata.namespace!=logging,metadata.namespace!=monitoring\u0026#39; \\ -o=custom-columns=NODE:.spec.nodeName,NAMESPACE:.metadata.namespace,NAME:.metadata.name|grep ${n} done Resource Quotas 列出命名空间中的资源配额\nbash 1 kubectl get resourcequotas -n \u0026lt;namespace\u0026gt; 查看资源配额详情\nbash 1 kubectl describe resourcequota \u0026lt;resource-quota-name\u0026gt; -n \u0026lt;namespace\u0026gt; Volumes 按容量排序的列出PV\nbash 1 kubectl get pv --sort-by=.spec.capacity.storage 检查 PV reclaim policy\nbash 1 kubectl get pv \u0026lt;pv-name\u0026gt; -o=jsonpath=\u0026#39;{.spec.persistentVolumeReclaimPolicy}\u0026#39; Ephemeral Containers 1.18+\n运行一个 ephemeral debugging 容器\nbash 1 kubectl debug -it \u0026lt;pod-name\u0026gt; -n \u0026lt;namespace\u0026gt; --image=\u0026lt;debug-image\u0026gt; -- /bin/sh Pod Disruption Budget (PDB) 列出一个ns内的PDB\nbash 1 kubectl get pdb -n \u0026lt;namespace\u0026gt; ConfigMap 批量备份所有 configmap\nbash 1 2 3 4 5 6 7 8 9 10 KUBE_CMD=\u0026#39;kubectl --kubeconfig=\u0026#39; BACKUP_PATH={your backup path} for ns in `${KUBE_CMD} get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;` do for cn in `${KUBE_CMD} get cm -n ${ns} -ojsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;` do ${KUBE_CMD} get cm -n ${ns} ${cn} -o yaml | sed -e \u0026#39;/resourceVersion:/d; /uid:/d; /selfLink:/d ; /creationTimestamp:/d; /lifecycle.cattle.io/d\u0026#39; \u0026gt; ${BACKUP_PATH}/$ns.$sn.yaml done done Secret 从一个 namespace 导出所有 secret 到另一个 namespace\nbash 1 2 3 4 5 6 7 8 9 OLD_NS= NEW_NS= KUBE_CMD=\u0026#39;kubectl --kubeconfig=\u0026#39; for n in `${KUBE_CMD} get secret -n ${OLD_NS} -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;`;do ${KUBE_CMD} get secret -n ${OLD_NS} $n -o yaml | \\ sed -e \u0026#39;/resourceVersion:/d;/uid:/d; /selfLink:/d; /creationTimestamp:/d; /namespace: /d;\u0026#39; | \\ ${KUBE_CMD} create -n ${NEW_NS} -f - done 其他方式\nbash 1 2 3 4 5 6 7 8 9 10 KUBE_CMD=\u0026#39;kubectl --kubeconfig=\u0026#39; NAMESPACE= for n in `${KUBE_CMD} get secret -n ${NAMESPACE} -ojson | jq -r \u0026#39;.items[].metadata.name\u0026#39;`; do ${KUBE_CMD} get secret -n ${NAMESPACE} $n -o json \\ | jq -r \u0026#39;del(.metadata[\u0026#34;creationTimestamp\u0026#34;,\u0026#34;resourceVersion\u0026#34;,\u0026#34;selfLink\u0026#34;,\u0026#34;uid\u0026#34;,\u0026#34;annotations\u0026#34;, \u0026#34;namespace\u0026#34;])\u0026#39; \\ ${KUBE_CMD} create -n ${NAMESPACE} -f - done ``` 复制集群中所有 namespace 的 secret 到另外一个新集群，并排除部分 namespace\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 KUBE_CMD=\u0026#39;kubectl --kubeconfig=\u0026#39; NAMESPACE= EXCLUDE_NS=\u0026#39;default|debug|cattle|test|local|logging|skywalking|gke|ingress|kube|infra|opentelemetry\u0026#39; KUBE_CMD_NEW=kubectl --kubeconfig= for namespace in `${KUBE_CMD} get ns -ojsonpath=\u0026#39;{range .items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;|grep -Ev ${EXCLUDE_NS}`; do for n in `${KUBE_CMD} get secret -n ${namespace} -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;`;do ${KUBE_CMD} get secret -n ${namespace} $n -o json \\ | jq -r \u0026#39;del(.metadata[\u0026#34;creationTimestamp\u0026#34;,\u0026#34;resourceVersion\u0026#34;,\u0026#34;selfLink\u0026#34;,\u0026#34;uid\u0026#34;,\u0026#34;finalizers\u0026#34;,\u0026#34;generation\u0026#34;],.metadata.annotations[\u0026#34;kubectl.kubernetes.io/last-applied-configuration\u0026#34;])\u0026#39; \\ | ${KUBE_CMD_NEW} apply -f -\tdone done 拷贝一个 secret 到 另外一个名称空间内\nbash 1 2 3 4 5 6 7 SRC_NS= DST_NS= SECRET_NAME= kubectl get secret -n ${SRC_NS} ${SECRET_NAME} -o json \\ | jq -r \u0026#39;del(.metadata[\u0026#34;creationTimestamp\u0026#34;,\u0026#34;resourceVersion\u0026#34;,\u0026#34;selfLink\u0026#34;,\u0026#34;uid\u0026#34;,\u0026#34;annotations\u0026#34;,\u0026#34;namespace\u0026#34;])\u0026#39; \\ | kubectl create -n ${DST_NS} -f - 拷贝一个集群所有名称空间的 secret 到另一个集群\nbash 1 2 3 4 5 6 7 8 9 10 11 12 KUBE_CMD=\u0026#39;kubectl --kubeconfig=\u0026#39; KUBE_CMD_NEW=\u0026#39;kubectl --kubeconfig=\u0026#39; EXCLUDE_NS=\u0026#39;default|debug|cattle|test|local|logging|skywalking|spinnaker|gke.*|ingress|kube|infra|cicd|opentelemetry\u0026#39; for i in `${KUBE_CMD} get ns -ojsonpath=\u0026#39;{range .items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; | grep -Ev ${EXCLUDE_NS}`; do for n in `${KUBE_CMD} get secert -n ${i} -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;`;do ${KUBE_CMD} get secert -n ${i} $n -o json \\ | jq -r \u0026#39;del(.metadata[\u0026#34;creationTimestamp\u0026#34;,\u0026#34;resourceVersion\u0026#34;,\u0026#34;selfLink\u0026#34;,\u0026#34;uid\u0026#34;,\u0026#34;finalizers\u0026#34;,\u0026#34;generation\u0026#34;],.metadata.annotations[\u0026#34;kubectl.kubernetes.io/last-applied-configuration\u0026#34;])\u0026#39; \\ | ${KUBE_CMD_NEW} apply -f -\tdone done 查看 secret 域名是多少\nbash 1 2 3 4 5 6 7 8 9 10 11 12 NAMESPACE=bussniss SECRET_NAME=chinamobile.com KEYWORKS=crt KUBE_CMD=\u0026#34;kubectl --kubeconfig=\u0026#34; openssl x509 -in \u0026lt;( \\ ${KUBE_CMD} get secret -n ${NAMESPACE} ${SECRET_NAME} -ojson | \\ jq --arg secret_key $( ${KUBE_CMD} get secret -n ${NAMESPACE} ${SECRET_NAME} -ojson | \\ jq -r \u0026#39;.data | keys[]\u0026#39; | awk -v kw=${KEYWORKS} \u0026#39;$0 ~ kw { print }\u0026#39; ) -r \u0026#39;.data | .[$secret_key]\u0026#39; | base64 -d ) -text -noout | grep -i \u0026#34;subject: \u0026#34; 查看证书是否过期\nbash 1 openssl x509 -in \u0026lt;(kubectl get secret -n bussniss xxx -ojson |jq -r \u0026#39;.data.\u0026#34;tls.crt\u0026#34;\u0026#39;|base64 -d) -text -noout|grep \u0026#39;Not After\u0026#39; 查看一个名称空间内的证书是否过期\nbash 1 2 3 4 5 6 7 8 9 10 KUBE_CMD=\u0026#39;\u0026#39; KEYWORDS= for ns in `${KUBE_CMD} get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;` do for sn in `${KUBE_CMD} get secret -n $ns -o json|jq -r \u0026#39;.items[] | select( .type == \u0026#34;kubernetes.io/tls\u0026#34;) | .metadata.name\u0026#39;|awk \u0026#39;$1 ~ /$KEYWORDS/ { print }\u0026#39;` do openssl x509 -in \u0026lt;(${KUBE_CMD} get secret -n $ns $sn -ojson |jq -r \u0026#39;.data.\u0026#34;tls.crt\u0026#34;\u0026#39;|base64 -d) -text -noout|grep \u0026#39;Not After\u0026#39; done done 查询所有tls的secret的域名\nbash 1 2 3 4 5 6 KUBE_CMD=\u0026#39;\u0026#39; for n in `${KUBE_CMD} get secret -A -o json|jq -r \u0026#39;.items[] | select( .type == \u0026#34;kubernetes.io/tls\u0026#34; ) | .data | select(keys[0] == \u0026#34;tls.crt\u0026#34;) | .\u0026#34;tls.crt\u0026#34;\u0026#39;`; do openssl x509 -in \u0026lt;(echo $n|base64 -d) -text -noout|grep -i \u0026#39;subject\u0026#39; done 批量备份集群内指定类型的 secret 命令\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 KEYWORDS= KUBE_CMD=\u0026#39;kubectl --kubeconfig=\u0026#39; for ns in `${_command} get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; `;do echo \u0026#34;$ns ....\u0026#34; for secret_name in `${_command} get secret -n $ns -o json | jq -r \u0026#39;.items[] | select( .type == \u0026#34;kubernetes.io/tls\u0026#34; ) | .metadata.name\u0026#39;`;do cert_key=$(${_command} get secret -n $ns $secret_name -o json | jq -r \u0026#39;.data | keys[]\u0026#39; | awk \u0026#39;/.crt$/ { print }\u0026#39;) if [ -n \u0026#34;$cert_key\u0026#34; ]; then echo \u0026#34; $ns/$secret_name\u0026#34; openssl x509 -subject -noout -in \u0026lt;( ${_command} get secret -n ${ns} ${secret_name} -ojson | \\ jq -r --arg secret_key \u0026#34;$cert_key\u0026#34; -r \u0026#39;.data|.[$secret_key]\u0026#39; | base64 -d ) | \\ awk -v ns=$ns -v secret_name=$secret_name -v kw=${keywords} -v cmd=\u0026#34;${_command}\u0026#34; \u0026#39;index($0, kw) \u0026gt; 0 { system(cmd\u0026#34; get secret -n \u0026#34;ns\u0026#34; \u0026#34;secret_name\u0026#34; -oyaml \u0026gt; \u0026#34;ns\u0026#34;.\u0026#34;secret_name\u0026#34;.yaml\u0026#34;) }\u0026#39; fi done sleep $((1 + RANDOM % 3)) done 查询 tls 签发域名，并进行替换\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 KEYWORDS= KUBE_CMD=\u0026#39;kubectl --kubeconfig=\u0026#39; for ns in `${KUBE_CMD} get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;`; do echo \u0026#34;$ns ....\u0026#34; for secret_name in `${KUBE_CMD} get secret -n $ns -o json | jq -r \u0026#39;.items[] | select( .type == \u0026#34;kubernetes.io/tls\u0026#34;) | .metadata.name\u0026#39;`; do cert_key=$(${KUBE_CMD} get secret -n $ns $secret_name -o json | jq -r \u0026#39;.data | keys[]\u0026#39; | awk \u0026#39;/.crt$/ { print }\u0026#39;) if [ -n \u0026#34;$cert_key\u0026#34; ]; then echo \u0026#34; $ns/$secret_name\u0026#34; openssl x509 -subject -noout -in \u0026lt;( ${KUBE_CMD} get secret -n ${ns} ${secret_name} -ojson | \\ jq -r --arg secret_key \u0026#34;$cert_key\u0026#34; -r \u0026#39;.data|.[$secret_key]\u0026#39; | base64 -d ) | \\ awk -v ns=$ns -v secret_name=$secret_name -v kw=${KEYWORDS} -v cmd=\u0026#34;${KUBE_CMD}\u0026#34; \u0026#39;index($0, kw) \u0026gt; 0 { system(cmd\u0026#34; create secret tls -n \u0026#34;ns\u0026#34; \u0026#34;secret_name\u0026#34; --cert=ca.crt --key=ca.key --dry-run -oyaml|\u0026#34;cmd\u0026#34; replace -f -\u0026#34;) }\u0026#39; fi done done 使用本地固定证书作为新证书，替换集群内符合条件的secret\n本命令只是打印生产的执行命令，主要使用 print\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 KUBE_CMD=kubectl --kubeconfig= KEYWORDS=\u0026#34;chinamobile\u0026#34; for ns in `${KUBE_CMD} get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;` do\tfor secret_name in `${KUBE_CMD} get secret -n $ns -o json|jq -r \u0026#39;.items[] | select( .type == \u0026#34;kubernetes.io/tls\u0026#34;) | .metadata.name\u0026#39;`; do openssl x509 -in \u0026lt;( ${KUBE_CMD} get secret -n $ns $secret_name -ojson | \\ jq --arg secret_key $(${KUBE_CMD} get secret -n $ns $secret_name -ojson | \\ jq -r \u0026#39;.data|keys[]\u0026#39; | awk \u0026#39;$0 ~ /crt/ { print }\u0026#39; ) -r \u0026#39;.data|.[$secret_key]\u0026#39; | base64 -d ) -text -noout | grep -i \u0026#39;subject: \u0026#39; | \\ awk -v ns=$ns -v secret_name=$secret_name -v cmd=\u0026#34;${KUBE_CMD}\u0026#34; -v kw=${KEYWORDS} \u0026#39;$0 ~ kw { print \u0026#34;kubectl create secret tls \u0026#34; secret_name \u0026#34; -n \u0026#34; ns \u0026#34; --cert=xxx.crt --key=xxx.key --dry-run -oyaml | \u0026#34; cmd \u0026#34; replace -f -\u0026#34; }\u0026#39; done done tip 该命令中用到的技巧\nbash 1 2 3 4 5 6 kubectl get secret -n ms chinamobile.com -ojson | jq -r \u0026#39;.data|keys[]\u0026#39; # 获取 data下面所有属性的key awk \u0026#39;$0 ~ /crt/ { print }\u0026#39; # 匹配有crt的行，这里是正则，不能编写变量 jq --arg secret_key $(kubectl get secret -n monitoring chinamobile.com -ojson | jq -r \u0026#39;.data|keys[]\u0026#39; | awk \u0026#39;$0 ~ /crt/ { print }\u0026#39;) -r \u0026#39;.data|.[$secret_key]\u0026#39; # 前面获取到的json，然后定义变量 secret_key 他的值就是这个 json 内的 xxx.crt 最后通过 -r 解析原始json .data.[$secret_key] 就是拿到 xxx.crt 的 base64值 本命令是真实执行的命令\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 KUBE_CMD=kubectl --kubeconfig= KEYWORDS=\u0026#34;chinamobile\u0026#34; for ns in `${KUBE_CMD} get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;`; do\tfor secret_name in `${KUBE_CMD} get secret -n $ns -o json|jq -r \u0026#39;.items[] | select( .type == \u0026#34;kubernetes.io/tls\u0026#34;) | .metadata.name\u0026#39;`; do openssl x509 -in \u0026lt;( ${KUBE_CMD} get secret -n $ns $secret_name -ojson | \\ jq --arg secret_key $(${KUBE_CMD} get secret -n $ns $secret_name -ojson | \\ jq -r \u0026#39;.data|keys[]\u0026#39; | awk \u0026#39;$0 ~ /crt/ { print }\u0026#39; ) -r \u0026#39;.data|.[$secret_key]\u0026#39; | base64 -d ) -text -noout | grep -i \u0026#39;subject: \u0026#39; | \\ awk -v ns=$ns -v secret_name=$secret_name -v cmd=\u0026#34;${KUBE_CMD}\u0026#34; -v kw=${KEYWORDS} \u0026#39;$0 ~ kw { system(\u0026#34;${KUBE_CMD} create secret tls \u0026#34; secret_name \u0026#34; -n \u0026#34; ns \u0026#34; --cert=ca.crt --key=chinamobile.key --dry-run -oyaml | \u0026#34; cmd \u0026#34; replace -f -\u0026#34;) }\u0026#39; done done ","permalink":"https://www.161616.top/kubernetes-kubectl-diagnose/","summary":"工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 Pod 检查 Pod 就绪探针\nbash 1 kubectl get pods \u0026lt;pod-name\u0026gt; -n \u0026lt;namespace\u0026gt; -o jsonpath=\u0026#39;{.status.conditions[?(@.type==\u0026#34;Ready\u0026#34;)].status}\u0026#39; 查看 Pod 事件\nbash 1 kubectl get events -n \u0026lt;namespace\u0026gt; --field-selector involvedObject.name=\u0026lt;pod-name\u0026gt; 获取 Pod Affinity 和 Anti-Affinity\nbash 1 kubectl get pod \u0026lt;pod-name\u0026gt; -n \u0026lt;namespace\u0026gt; -o=jsonpath=\u0026#39;{.spec.affinity}\u0026#39; 列出 Pod 的 anti-affinity 规则\nbash 1 kubectl get pod \u0026lt;pod-name\u0026gt; -n \u0026lt;namespace\u0026gt; -o=jsonpath=\u0026#39;{.","title":"探索kubectl - kubectl诊断命令集合"},{"content":"本文将介绍 Harbor 从 v1.10.7 升级到 v2.10.0，以及如何将 Harbor 从 v2.10 回滚到 v1.10.7。\n升级条件 Linux服务器 4 个 CPU 和 8 GB 内存（强要求），100G可用空间（跨多版本时存放备份文件以及镜像文件，这部分要求） Docker-compose \u0026gt; 1.19.0+ 备份现有的 Harbor /data/database 目录 本次升级主要是使用了 harbor 内置的数据库，所以升级步骤比较容易。\n官方升级路线 harbor 的升级，是不能跨很多版本进行升级，官方对此有详细说明 [1] ，可以看到路线为：\n1.10.0 [1] =\u0026gt; 2.4.0 [2] =\u0026gt; 2.6.0 [3] =\u0026gt; 2.8.0 [4] =\u0026gt; 2.10.0 [5]\n模拟升级步骤 github release 页下载对应的安装包\n解压\nbash 1 2 # 命令主要为将harbor压缩包内文件解压到指定目录中，由于 harbor 解压后文件名无论版本如何都为“harbor” $ mkdir ./harbor-v1.10 \u0026amp;\u0026amp; tar -xf harbor-offline-installer-v1.10.0.tgz -C ./harbor-v1.10 --strip-components 1 备份默认的配置文件（仅限于 v1.10.x，v2.x均为 harbor.tmpl）\nbash 1 cp harbor.yml harbor.yaml.backup 清除注释\nbash 1 grep -Ev \u0026#39;^#|^$|^\\s*(#|//)\u0026#39; harbor.yml.tmpl \u0026gt; harbor.yml.clean_annotation 修改一些默认配置\nbash 1 2 3 4 5 6 7 \\cp -a harbor.yml.clean_annotation harbor.yml # 如果需要则关闭https sed -i \u0026#39;/https:/,+3s/.*/# \u0026amp;/\u0026#39; harbor.yml # 替换默认harbor郁闷 sed -i \u0026#34;s@hostname: reg.mydomain.com@hostname: img.test.com@g\u0026#34; harbor.yml # 修改默认目录 sed -i \u0026#34;s@data_volume: /data@data_volume: /data/harbor@g\u0026#34; harbor.yml 启动服务\nbash 1 ./install.sh 升级步骤 在升级前首先要缕清升级的内容，官方在升级时存在两个步骤，配置文件升级与数据库 schema 升级；并且需要知道升级的路线图，这里是从 1.10.0 升级至本文撰写时最新版本 2.10.0，所以查看官方升级路线为 1.10.0 =\u0026gt; 2.4.0 =\u0026gt; 2.6.0 =\u0026gt; 2.8.0 =\u0026gt; 2.10.0。总结升级所需变更如下：\nharbor的配置文件升级 数据库 schema 升级，由 harbor-core 组件自动完成 升级路线：1.10.0 =\u0026gt; 2.4.0 =\u0026gt; 2.6.0 =\u0026gt; 2.8.0 =\u0026gt; 2.10.0 备份当前 harbor 版本 备份当前版本是为了如果需要回滚的话，可以快速的回滚到所需的版本\nbash 1 2 cd harbor docker-compose down 备份 Harbor 的当前文件，以便您可以在必要时回滚到当前版本。\n备份数据库文件 我们知道了，升级主要是对数据库 schema 进行reschema，Harbor 的每次新版本发布时新的功能及对老功能、代码的重构都会导致数据库模型的变更，因此几乎每次升级都需要升级数据库模式。配置文件数据，是指 Harbor 组件的配置文件，在部分新功能或者新的组件出现时，都需要在配置文件中新增其参数；在老功能、组件重构或者废弃时，也会对配置文件进行更新。\nbash 1 cp -r /data/database /my_backup_dir/ reschema的工作是由 harbor-core 完成的，所以我们只需要备份即可，当新版本在启动时，第一次会 reschema，这个步骤的时间会随着 harbor 的使用量而增加，这里数据库目录为 13G，1.10.7 =\u0026gt; 2.4.0 时间大概在20分钟左右。\nharbor的配置文件升级 harbor 配置的升级是需要手动执行的，命令是包含在 offline 安装包中，被包含在 “goharbor/prepare:v2.x.0” 镜像中。用户可以在 Harbor 的离线安装包中找到它，也可以在 Docker Hub 上获取，官方给出升级指南中的命令如下\nbash 1 2 3 4 5 6 7 8 9 10 # 1.10 docker run -it --rm -v ./harbor.yml:/harbor-migration/harbor-cfg/harbor.yml goharbor/harbor-migrator:v1.10.0 --cfg up # 2.4 # 后的yaml文件必须是旧版本的 # 这步骤是将旧的 harbor.yaml 配置文件升级到新版本 # 升级后旧版本的配置文件就没有了，如果需要需要自行备份 # docker run -it --rm -v /:/hostfs goharbor/prepare:[tag] migrate -i ${path to harbor.yml} docker run -it --rm -v /:/hostfs goharbor/prepare:v2.4.0 migrate -i ./harbor.yml “-v /:/hostfs” 是将主机的根目录 “/” 挂载到容器中的 “/hostfs” 目录中。因为命令是运行在容器中的，而文件是在宿主机上的，为了能在容器中访问到指定的文件，需要这样挂载，之后 prepare 会对 “/hostfs” 这个文件做特殊处理，使得在容器里也能访问主机上的指定文件。\n”-i“ 是指定旧版本的 harbor 配置文件\nmigrate 命令有如下3个参数。\n​\t\u0026ndash;input（缩写形式为“-i”）：是输入文件的绝对路径，也就是需要升级的原配置文件。\n​\t\u0026ndash;output（缩写形式为“-o”）：是输出文件的绝对路径，也是升级后的配置文件，是可选参数，如果取默认值，则升级后的文件会被写回输入文件中。\n​\t\u0026ndash;target（缩写形式为“-t”）：是目标版本，也就是打算升级到的版本，也是可选参数，如果取默认值，则版本为此工具发布时所支持的最新版本。\n这里我们可以使用如下命令\nbash 1 docker run -v :/hostfs goharbor/prepare:v2.4.0 migrate -i home/harbor/upgrade/harbor.yml 升级成功会有如下输出\nbash 1 2 3 4 5 6 migrating to version 2.0.0 migrating to version 2.1.0 migrating to version 2.2.0 migrating to version 2.3.0 migrating to version 2.4.0 Written new values to home/harbor/upgrade/harbor.yml 启动新服务 在新版本 harbor 目录中，运行 ./install.sh 脚本来安装新的 Harbor 实例，这里会导入离线安装包，生成配置文件，启动服务等操作\n替换 docker-compose 文件 docker-compose 的生成是在 prepare 脚本中执行的，可以看出，是调用的 prepare 镜像\nbash 1 2 3 4 5 6 7 8 # Run prepare script docker run --rm -v $input_dir:/input \\ -v $data_path:/data \\ -v $harbor_prepare_path:/compose_location \\ -v $config_dir:/config \\ -v /:/hostfs \\ --privileged \\ goharbor/prepare:dev prepare $@ 这种情况下，如果我们需要自定义的 docker-compose.yaml 就可以挂在到对应目录即可，模板文件可以在 photon/prepare/templates/docker_compose 处下载进行替换。\n替换后，使用 sed 命令，替换 prepare 脚本中的启动命令即可。\nbash 1 sed -i \u0026#34;/-v \\/:\\/hostfs/a \\\\\\t\\\\t -v /root/docker_compose/docker-compose.yml.jinjia.${version}:/usr/src/app/templates/docker_compose/dockercompose.yml.jinjia \\\\\\\\\u0026#34; ./prepare 批量升级脚本 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 #!/bin/bash # 根据当前版本和目标版本选择对应的下载地址 declare -A harbor_versions=( [\u0026#34;v1.10.0\u0026#34;]=\u0026#34;https://github.com/goharbor/harbor/releases/download/v1.10.0/harbor-offline-installer-v1.10.0.tgz\u0026#34; [\u0026#34;v2.4.0\u0026#34;]=\u0026#34;https://github.com/goharbor/harbor/releases/download/v2.4.0/harbor-offline-installer-v2.4.0.tgz\u0026#34; [\u0026#34;v2.6.1\u0026#34;]=\u0026#34;https://github.com/goharbor/harbor/releases/download/v2.6.1/harbor-offline-installer-v2.6.1.tgz\u0026#34; [\u0026#34;v2.8.0\u0026#34;]=\u0026#34;https://github.com/goharbor/harbor/releases/download/v2.8.0/harbor-offline-installer-v2.8.0.tgz\u0026#34; [\u0026#34;v2.10.0\u0026#34;]=\u0026#34;https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz\u0026#34; ) # # Set Colors # bold=$(tput bold) underline=$(tput sgr 0 1) reset=$(tput sgr0) red=$(tput setaf 1) green=$(tput setaf 76) white=$(tput setaf 7) tan=$(tput setaf 202) blue=$(tput setaf 25) # # Headers and Logging # underline() { printf \u0026#34;${underline}${bold}%s${reset}\\n\u0026#34; \u0026#34;$@\u0026#34; } h1() { printf \u0026#34;\\n${underline}${bold}${blue}%s${reset}\\n\u0026#34; \u0026#34;$@\u0026#34; } h2() { printf \u0026#34;\\n${underline}${bold}${white}%s${reset}\\n\u0026#34; \u0026#34;$@\u0026#34; } debug() { printf \u0026#34;${white}%s${reset}\\n\u0026#34; \u0026#34;$@\u0026#34; } info() { printf \u0026#34;${white}➜ %s${reset}\\n\u0026#34; \u0026#34;$@\u0026#34; } success() { printf \u0026#34;${green}✔ %s${reset}\\n\u0026#34; \u0026#34;$@\u0026#34; } error() { printf \u0026#34;${red}✖ %s${reset}\\n\u0026#34; \u0026#34;$@\u0026#34; } warn() { printf \u0026#34;${tan}➜ %s${reset}\\n\u0026#34; \u0026#34;$@\u0026#34; } bold() { printf \u0026#34;${bold}%s${reset}\\n\u0026#34; \u0026#34;$@\u0026#34; } note() { printf \u0026#34;\\n${underline}${bold}${blue}Note:${reset} ${blue}%s${reset}\\n\u0026#34; \u0026#34;$@\u0026#34; } set -e # 设置根目录和工作目录 ROOT_DIR=$(cd $(dirname $0); pwd) export ROOT_DIR # 设置步骤变量 usage() { cat \u0026lt;\u0026lt;EOF Usage: ${CMD} [ OPTION ] Commands: Some commands take arguments or -h for usage. -d download harbor rolling dependencies offline installer package -u upgrade harbor to latest -r rollback to old version -h this message EOF return 0 } compare_versions() { local current_version=$1 local target_version=$2 if [[ $current_version == v* ]]; then current_version=${current_version#v} fi if [[ $target_version == v* ]]; then target_version=${target_version#v} fi if [[ $current_version == \u0026#34;$target_version\u0026#34; ]]; then return 2 fi IFS=\u0026#39;.\u0026#39; read -ra current_version_parts \u0026lt;\u0026lt;\u0026lt; \u0026#34;$current_version\u0026#34; IFS=\u0026#39;.\u0026#39; read -ra target_version_parts \u0026lt;\u0026lt;\u0026lt; \u0026#34;$target_version\u0026#34; for (( i=0; i\u0026lt;${#current_version_parts[@]}; i++ )); do if (( ${target_version_parts[$i]} \u0026gt; ${current_version_parts[$i]} )); then return 0 elif (( ${target_version_parts[$i]} \u0026lt; ${current_version_parts[$i]} )); then return 1 fi done return 2 } set_env() { # 设置工作目录 WORK_DIR=\u0026#34;${ROOT_DIR}/_work\u0026#34; # 获取当前版本和目标版本 read -p \u0026#34;Please input current version [1.10.0]: \u0026#34; CURRENT_VERSION CURRENT_VERSION=${CURRENT_VERSION:-v1.10.0} if [[ ${CURRENT_VERSION:0:1} != \u0026#34;v\u0026#34; ]]; then CURRENT_VERSION=\u0026#34;v${CURRENT_VERSION}\u0026#34; fi read -p \u0026#34;Please input target version [2.10.0]: \u0026#34; TARGET_VERSION TARGET_VERSION=${TARGET_VERSION:-v2.10.0} if [[ ${TARGET_VERSION:0:1} != \u0026#34;v\u0026#34; ]]; then TARGET_VERSION=\u0026#34;v${TARGET_VERSION}\u0026#34; fi read -p \u0026#34;Please input harbor path [/root/harbor-v1.10]: \u0026#34; CURR_HARBOR_PATH CURR_HARBOR_PATH=${CURR_HARBOR_PATH:-/root/harbor-v1.10} read -p \u0026#34;Please input harbor database [/data/harbor]: \u0026#34; DATA_DIR DATA_DIR=${DATA_DIR:-/data/harbor} # 设置备份目录 BACKUP_DIR=\u0026#34;${ROOT_DIR}/harbor_backup\u0026#34; # 当前版本的备份路径 CURRENT_VERSION_BACKUP_PATH=\u0026#34;$BACKUP_DIR/${CURRENT_VERSION}\u0026#34; export WORK_DIR CURRENT_VERSION TARGET_VERSION CURR_HARBOR_PATH DATA_DIR BACKUP_DIR CURRENT_VERSION_BACKUP_PATH versions=\u0026#34;\u0026#34; sorted_versions=\u0026#34;\u0026#34; export versions sorted_versions } set_env_rollback() { # 设置工作目录 WORK_DIR=\u0026#34;${ROOT_DIR}/_work\u0026#34; # 获取当前版本和目标版本 read -p \u0026#34;Please input current version [2.10.0]: \u0026#34; CURRENT_VERSION CURRENT_VERSION=${CURRENT_VERSION:-v2.10.0} if [[ ${CURRENT_VERSION:0:1} != \u0026#34;v\u0026#34; ]]; then CURRENT_VERSION=\u0026#34;v${CURRENT_VERSION}\u0026#34; fi read -p \u0026#34;Please input target version [1.10.0]: \u0026#34; TARGET_VERSION TARGET_VERSION=${TARGET_VERSION:-v1.10.0} if [[ ${TARGET_VERSION:0:1} != \u0026#34;v\u0026#34; ]]; then TARGET_VERSION=\u0026#34;v${TARGET_VERSION}\u0026#34; fi read -p \u0026#34;Please input old harbor dirname [harbor-v1.10.0]: \u0026#34; CURR_HARBOR_PATH CURR_HARBOR_PATH=${CURR_HARBOR_PATH:-harbor-v1.10.0} read -p \u0026#34;Please input harbor database [/data/harbor]: \u0026#34; DATA_DIR DATA_DIR=${DATA_DIR:-/data/harbor} # 设置备份目录 BACKUP_DIR=\u0026#34;${ROOT_DIR}/harbor_rollback_backup\u0026#34; # 当前版本的备份路径 CURRENT_VERSION_BACKUP_PATH=\u0026#34;$BACKUP_DIR/${CURRENT_VERSION}\u0026#34; export WORK_DIR CURRENT_VERSION TARGET_VERSION CURR_HARBOR_PATH DATA_DIR BACKUP_DIR CURRENT_VERSION_BACKUP_PATH } initialize_workspace() { # 创建工作目录（如果不存在） [ -d ${WORK_DIR} ] || mkdir -pv ${WORK_DIR} # 创建备份目录（如果不存在） [ -d ${BACKUP_DIR} ] || mkdir -pv ${BACKUP_DIR} } clean_annotation() { find ${WORK_DIR}/${version}/ \\ -name \u0026#34;harbor.yml.*\u0026#34; \\ ! -name \u0026#34;harbor.yml.clean_annotation\u0026#34; \\ ! -name \u0026#34;harbor.yml.tmpl\u0026#34; -type f -exec \\ grep -Ev \u0026#39;^#|^$|^\\s*(#|//)\u0026#39; {} + \u0026gt; ${WORK_DIR}/${version}/harbor.yml.clean_annotation } replace_configuration() { cp -a ${WORK_DIR}/${version}/harbor.yml.clean_annotation ${WORK_DIR}/${version}/harbor.yml sed -i \u0026#34;s@hostname: reg.mydomain.com@hostname: your-harbor-domain.com@g\u0026#34; ${WORK_DIR}/${version}/harbor.yml sed -i \u0026#34;s@data_volume: /data@data_volume: ${DATA_DIR}@g\u0026#34; harbor.yml } compare_versions() { local current_version=$1 local target_version=$2 if [[ $current_version == v* ]]; then current_version=${current_version#v} fi if [[ $target_version == v* ]]; then target_version=${target_version#v} fi if [[ $current_version == $target_version ]]; then return 2 fi IFS=\u0026#39;.\u0026#39; read -ra current_version_parts \u0026lt;\u0026lt;\u0026lt; \u0026#34;$current_version\u0026#34; IFS=\u0026#39;.\u0026#39; read -ra target_version_parts \u0026lt;\u0026lt;\u0026lt; \u0026#34;$target_version\u0026#34; for (( i=0; i\u0026lt;${#current_version_parts[@]}; i++ )); do if (( ${target_version_parts[$i]} \u0026gt; ${current_version_parts[$i]} )); then return 0 elif (( ${target_version_parts[$i]} \u0026lt; ${current_version_parts[$i]} )); then return 1 fi done return 2 } check_version_number() { set +e # 校验版本号 compare_versions \u0026#34;$version\u0026#34; \u0026#34;${CURRENT_VERSION}\u0026#34; if [ $? -eq 0 ]; then warn \u0026#34;${CURRENT_VERSION} Greater than ${version}.\u0026#34; continue fi compare_versions \u0026#34;$version\u0026#34; \u0026#34;$CURRENT_VERSION\u0026#34; if [ $? -eq 2 ]; then warn \u0026#34;${TARGET_VERSION} equal ${version}.\u0026#34; continue fi set -e } swtich_version() { CURRENT_VERSION=${version:-$CURRENT_VERSION} # 当前版本的备份路径 CURRENT_VERSION_BACKUP_PATH=\u0026#34;${BACKUP_DIR}/${CURRENT_VERSION}\u0026#34; # 当前harbor的启动路径 CURR_HARBOR_PATH=\u0026#34;${WORK_DIR}/${CURRENT_VERSION}\u0026#34; export CURRENT_VERSION CURRENT_VERSION_BACKUP_PATH CURR_HARBOR_PATH } pause() { success \u0026#34;Press enter to continue...\u0026#34; read -r } sorted_version() { # 获取harbor版本列表 versions=(\u0026#34;${!harbor_versions[@]}\u0026#34;) # 对版本列表进行排序 sorted_versions=($(printf \u0026#39;%s\\n\u0026#39; \u0026#34;${versions[@]}\u0026#34; | sort -V)) export versions sorted_versions } initial_backup_dir() { h2 \u0026#34;[Backup initialization]: Starting backup ${CURRENT_VERSION} ...\u0026#34; # 创建备份目录（如果不存在） [ -d ${CURRENT_VERSION_BACKUP_PATH} ] || mkdir -pv ${CURRENT_VERSION_BACKUP_PATH} [ -d ${CURRENT_VERSION_BACKUP_PATH}\u0026#34;_database\u0026#34; ] || mkdir -pv ${CURRENT_VERSION_BACKUP_PATH}\u0026#34;_database\u0026#34; [ -d ${CURRENT_VERSION_BACKUP_PATH}\u0026#34;_redis\u0026#34; ] || mkdir -pv ${CURRENT_VERSION_BACKUP_PATH}\u0026#34;_redis\u0026#34; note \u0026#34;backup dir is: ${CURRENT_VERSION_BACKUP_PATH} is checked.\u0026#34; note \u0026#34;backup database dir ${CURRENT_VERSION_BACKUP_PATH}_database is checked.\u0026#34; } backup_current_version() { h2 \u0026#34;[Backup progess]: starting backup harbor ${CURRENT_VERSION} ...\u0026#34; # 备份harbor cp -r ${CURR_HARBOR_PATH} ${CURRENT_VERSION_BACKUP_PATH}/ # 备份harbor数据目录的database cp -Rpf ${DATA_DIR}/database ${CURRENT_VERSION_BACKUP_PATH}\u0026#34;_database\u0026#34; cp -Rpf ${DATA_DIR}/redis ${CURRENT_VERSION_BACKUP_PATH}\u0026#34;_redis\u0026#34; # 备份 harbor.yml 防止升级被覆盖从而无法回滚 cp ${CURR_HARBOR_PATH}/harbor.yml ${CURRENT_VERSION_BACKUP_PATH}/harbor.yml.$(date +%F) note \u0026#34;harbor ${CURRENT_VERSION} is backup completed, in ${CURRENT_VERSION_BACKUP_PATH}\u0026#34; } stop_old_harbor_progress() { # 停止旧版容器组 h2 \u0026#34;[Progress stop]: stopping ${CURRENT_VERSION} ...\u0026#34; cd ${CURR_HARBOR_PATH} \u0026amp;\u0026amp; docker-compose down \u0026amp;\u0026amp; cd ${ROOT_DIR} \u0026amp;\u0026amp; note \u0026#34;harbor ${CURRENT_VERSION} is stopped ...\u0026#34; } upgrade_configfile() { h2 \u0026#34;[Upgrade]: upgrade ${CURRENT_VERSION} to ${version} ...\u0026#34; docker run -v /:/hostfs goharbor/prepare:${version} migrate -i ${CURRENT_VERSION_BACKUP_PATH}/harbor.yml.$(date +%F) -o ${WORK_DIR}/${version}/harbor.yml note \u0026#34;harbor config version is ${version}\u0026#34; } rollback() { step=0 h2 \u0026#34;[Step $step]: set up rollback env ...\u0026#34;; let step+=1 set_env_rollback # 停止容器 h2 \u0026#34;[Progress stop]: stopping ${CURRENT_VERSION} ...\u0026#34; cd ${WORK_DIR}/${CURRENT_VERSION}/ \u0026amp;\u0026amp; docker-compose down \u0026amp;\u0026amp; cd ${ROOT_DIR} \u0026amp;\u0026amp; note \u0026#34;harbor ${CURRENT_VERSION} is stopped ...\u0026#34; # 备份当前版本 initial_backup_dir backup_current_version FILE_NAME=${WORK_DIR}/${CURRENT_VERSION} # 检查工作目录是否存在已下载文件 if [ ! -d ${FILE_NAME} ]; then error \u0026#34;${CURRENT_VERSION} not found\u0026#34; exit 1 fi h2 \u0026#34;[Step $step]: starting switch harbor ${TARGET_VERSION} ...\u0026#34; ; let step+=1 rm -fr ${DATA_DIR}/database \u0026amp;\u0026amp; rm -fr ${DATA_DIR}/redis \u0026amp;\u0026amp; \\ cp -Rpf \u0026#34;${ROOT_DIR}/harbor_backup/${TARGET_VERSION}_database/database\u0026#34; ${DATA_DIR}/database cp -Rpf \u0026#34;${ROOT_DIR}/harbor_backup/${TARGET_VERSION}_redis/redis\u0026#34; ${DATA_DIR}/redis note \u0026#34;Switch database to ${TARGET_VERSION} is completed.\u0026#34; sleep $((RANDOM % 6 + 10)) h2 \u0026#34;[Step $step]: starting harbor ${TARGET_VERSION} ...\u0026#34; cd \u0026#34;${ROOT_DIR}/harbor_backup/${TARGET_VERSION}/${CURR_HARBOR_PATH}\u0026#34; \u0026amp;\u0026amp; ./install.sh success $\u0026#34;----Harbor ${CURRENT_VERSION} to ${TARGET_VERSION} has been rollback.----\u0026#34; } download() { sorted_version # 更新harbor for version in \u0026#34;${sorted_versions[@]}\u0026#34;; do DOWNLOAD_URL=${harbor_versions[$version]} FILE_NAME=$(basename $DOWNLOAD_URL) FOLDER_NAME=${FILE_NAME%.*} ARCHIVE_FILE=\u0026#34;${ROOT_DIR}/$FILE_NAME\u0026#34; if [ -f ${ARCHIVE_FILE} ]; then warn \u0026#34;${FILE_NAME} existed, skip download..\u0026#34; continue fi note \u0026#34;[Downloader]: ${harbor_versions[$version]}\u0026#34; wget \u0026#34;${harbor_versions[$version]}\u0026#34; done } rotate_upgrade_harbor_versions() { # 获取harbor版本列表 sorted_version # 更新harbor for version in \u0026#34;${sorted_versions[@]}\u0026#34;; do # 向下传递变量 export version h2 \u0026#34;[Install ${version}]: starting upgrade ...\u0026#34; # 检查更新是否合法 # 如果当前版本等于要更新的版本，则不更新 check_version_number # 停止旧版本的服务 stop_old_harbor_progress # 备份当前版本 harbor initial_backup_dir backup_current_version h2 \u0026#34;[install checking]: Starting install checking ...\u0026#34; # 检查offline安装包是否下载 DOWNLOAD_URL=${harbor_versions[$version]} FILE_NAME=$(basename $DOWNLOAD_URL) FOLDER_NAME=${FILE_NAME%.*} ARCHIVE_FILE=\u0026#34;${ROOT_DIR}/$FILE_NAME\u0026#34; # 检查工作目录是否存在已下载文件 if [ ! -f ${ARCHIVE_FILE} ]; then error \u0026#34;Offline installer ${FILE_NAME} not found，Please download first ${DOWNLOAD_URL}\u0026#34; exit 1 fi # 创建对应版本的工作目录（如果不存在） [ -d \u0026#34;${WORK_DIR}\u0026#34;/\u0026#34;${version}\u0026#34; ] || mkdir -pv \u0026#34;${WORK_DIR}\u0026#34;/\u0026#34;${version}\u0026#34; note \u0026#34;install checked\u0026#34; h2 \u0026#34;[Uncompress]: starting uncompress harbor offline installer ...\u0026#34; # 解压对应版本安装包 mkdir -pv \u0026#34;${WORK_DIR}\u0026#34;/\u0026#34;${version}\u0026#34; \u0026amp;\u0026amp; tar -xf \u0026#34;${ROOT_DIR}\u0026#34;/\u0026#34;${FILE_NAME}\u0026#34; -C \u0026#34;${WORK_DIR}\u0026#34;/\u0026#34;${version}\u0026#34;/ --strip-components 1 note \u0026#34;harbor ${version}: ${WORK_DIR}/${version}\u0026#34; # 导入镜像 set +e h2 \u0026#34;[Load image]: starting load harbor ${version} ...\u0026#34; cd \u0026#34;${WORK_DIR}/${version}\u0026#34; \u0026amp;\u0026amp; docker image load -i harbor.\u0026#34;${version}\u0026#34;.tar.gz note \u0026#34;harbor image loaded\u0026#34; set -e h2 \u0026#34;[Replace configration]: start replace default config file ...\u0026#34; # 清除 harbor.yml 中的注释 clean_annotation # 替换为所需的config replace_configuration note \u0026#34;replaced\u0026#34; # 更新操作 upgrade_configfile h2 \u0026#34;[Installer]: start install harbor ${version} ...\u0026#34; # 如果使用了定制化 docker-compose 则开启这行 # 使用准备好的模板来更换容器内部的模板 # 这样保证了可以随意定制 docker-compose的文件 cd \u0026#34;${WORK_DIR}/${version}\u0026#34; \u0026amp;\u0026amp; sed -i \u0026#34;/-v \\/:\\/hostfs/a \\\\\\t\\\\t -v /root/docker_compose/docker-compose.yml.jinjia.${version}:/usr/src/app/templates/docker_compose/dockercompose.yml.jinjia \\\\\\\\\u0026#34; ./prepare cd \u0026#34;${WORK_DIR}/${version}\u0026#34; \u0026amp;\u0026amp; ./install.sh note \u0026#34;${version} installed\u0026#34; success $\u0026#34;----Harbor ${CURRENT_VERSION} to ${version} has been upgraded.----\u0026#34; # 切换变量，把这次更新好的版本作为下次要更新的旧版本进行传递 swtich_version pause done # 完成升级 success $\u0026#34;----Harbor ${TARGET_VERSION} has been installed and started successfully.----\u0026#34; } upgrade() { step=0 h2 \u0026#34;[Step $step]: set up env ...\u0026#34;; (( step+1 )) set_env # 初始化目录 h2 \u0026#34;[Step $step]: initailizaion workspace ...\u0026#34;; (( step+1 )) initialize_workspace # 滚动版本更新harbor h2 \u0026#34;[Step $step]: Starting upgrade gradually ...\u0026#34;; (( step+1 )) rotate_upgrade_harbor_versions } MAIN(){ if [ $# -eq 0 ]; then warn \u0026#34;Non option ...\u0026#34; usage exit 1 fi while getopts \u0026#34;duhr\u0026#34; option; do case ${option} in d) download R=$? ;; u) upgrade R=$? ;; r) rollback R=$? ;; h) usage R=$? ;; \\?) usage ;; esac done exit ${R} } MAIN ${@} Reference ​[1] Upgrade Harbor and Migrate Data - v1.10.0\n​[2] Upgrade Harbor and Migrate Data - v2.4.0\n​[3] Upgrade Harbor and Migrate Data - v2.6.0\n​[4] Upgrade Harbor and Migrate Data - v2.8.0\n​[5] Upgrade Harbor and Migrate Data - v2.10.0\n​[6] Prepare 脚本\n​[6] 生产系统中升级 Harbor 的完整流程\n​[6] Upgrade Harbor from v1.10.7 to v2.4.0 then 2.6.0\n","permalink":"https://www.161616.top/upgrade-harbor/","summary":"本文将介绍 Harbor 从 v1.10.7 升级到 v2.10.0，以及如何将 Harbor 从 v2.10 回滚到 v1.10.7。\n升级条件 Linux服务器 4 个 CPU 和 8 GB 内存（强要求），100G可用空间（跨多版本时存放备份文件以及镜像文件，这部分要求） Docker-compose \u0026gt; 1.19.0+ 备份现有的 Harbor /data/database 目录 本次升级主要是使用了 harbor 内置的数据库，所以升级步骤比较容易。\n官方升级路线 harbor 的升级，是不能跨很多版本进行升级，官方对此有详细说明 [1] ，可以看到路线为：\n1.10.0 [1] =\u0026gt; 2.4.0 [2] =\u0026gt; 2.6.0 [3] =\u0026gt; 2.8.0 [4] =\u0026gt; 2.10.0 [5]\n模拟升级步骤 github release 页下载对应的安装包\n解压\nbash 1 2 # 命令主要为将harbor压缩包内文件解压到指定目录中，由于 harbor 解压后文件名无论版本如何都为“harbor” $ mkdir ./harbor-v1.10 \u0026amp;\u0026amp; tar -xf harbor-offline-installer-v1.10.0.tgz -C ./harbor-v1.10 --strip-components 1 备份默认的配置文件（仅限于 v1.","title":"批量更新harbor版本 1.10 to 2.10"},{"content":"tls 证书在 k8s 集群上大量使用的话，当到期时会存在批量替换的难度，比如说每个名称空间，多个业务的使用，在这篇博文中，将尝试批量替换整个集群的证书（前提，在没有使用 vault, cert-manager这类组件的集群之上）。\n基本操作 步骤1：首先不知道有多少个名称空间使用了这个证书，所以需要遍历所有的名称空间，这里使用 kubectl 的 json path 实现\nbash 1 $ kubectl get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; 步骤2：拿到名称空间的名字后，就需要循环这个名称空间下所有的 secret\nbash 1 2 3 4 for ns in `kubectl get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;` do kubectl get secret -n $ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; done 步骤3：找到与这个匹配的证书进行替换\n把步骤3拆解为下面几个步骤：\n拿去到符合要求的secret的名字 匹配名称是否为修改的secret 做替换操作 由于步骤2使用的是 jsonpath 拿到的 secret name，由于 kubectl 并不支持高级jsonpath语法，官方推荐使用jq，那么使用jq获取名字\nbash 1 kubectl get secret -n $ns -o json|jq -r .items[].metadata.name 使用 awk 做字符串匹配，匹配是否包含对应的字符串关键词\nbash 1 awk \u0026#39;$1 ~ /xxxx/ { print }\u0026#39; 最后使用 kubectl replace 替换现有的 secret\nbash 1 kubectl create secret tls $secertName -n $ns --cert=xxx.crt --key=xxx.key --dry-run -o yaml| kubectl replace -f - 三个步骤整个为一个命令，如下所示：\nbash 1 2 3 4 5 6 7 for ns in `kubectl get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;` do for secertName in `kubectl get secret -n $ns -o json|jq -r .items[].metadata.name|awk \u0026#39;$1 ~ /xxxx/ { print }\u0026#39;; do kubectl create secret tls $secertName -n $ns --cert=xxx.crt --key=xxx.key --dry-run -o yaml| kubectl replace -f - done done 注意：\ningress 使用的证书会立即更新 如果是作为 secret 挂在到 Pod 中需要重启，这是因为 kubelet volume 机制导致的。 高级用法 查询证书内容，并根据域名进行替换\n查看证书域名的命令\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 ns=xxx secret_name=xxx keywords=xxx _command=xxxx openssl x509 -in \u0026lt;( ${_command} get secret -n $ns $secret_name -ojson | \\ jq --arg secret_key $( ${_command} get secret -n ${ns} ${secret_name} -ojson | \\ jq -r \u0026#39;.data | keys[]\u0026#39; | \\ awk -v keywords=${keywords} \u0026#39;$0 ~ keywords { print }\u0026#39; ) -r \u0026#39;.data | .[$secret_key]\u0026#39; | base64 -d ) -text -noout | \\ grep -i \u0026#34;subject: \u0026#34; 步骤拆解\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # 替换的名称空间 ns=kube-system # 替换的secret_name secret_name=test # 替换key的关键词，通常secret作为tls存在时，为xxx.crt keywords=crt # kubectl命令 _command=kubectl --kubeconfig=kubeconfig # shell语法，将一个命令的输出作为openssl的输入 openssl x509 -in \u0026lt;( ${_command} get secret -n $ns $secret_name -ojson | \\ # jq --arg secret_key 是 jq语法，将 $() 命名为secret_key作为变量传入到下个命令使用 jq --arg secret_key $( ${_command} get secret -n ${ns} ${secret_name} -ojson | \\ jq -r \u0026#39;.data | keys[]\u0026#39; | \\ # awk -v keywords=${keywords} 是awk语法，将keyworkd作为变量传递到awk内部 # \u0026#39;$0 ~ keywords { print }\u0026#39; 打印出符合条件的行 awk -v keywords=${keywords} \u0026#39;$0 ~ keywords { print }\u0026#39; ) -r \u0026#39;.data | .[$secret_key]\u0026#39; | base64 -d ) -text -noout | \\ grep -i \u0026#34;subject: \u0026#34; 更新前没出意外需要备份下对应资源，批量备份集群内指定域名的 secret 命令\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 KEYWORDS= KUBE_CMD=\u0026#39;kubectl\u0026#39; for ns in `${KUBE_CMD} get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; `;do echo \u0026#34;$ns ....\u0026#34; for secret_name in `${KUBE_CMD} get secret -n $ns -o json | jq -r \u0026#39;.items[] | select( .type == \u0026#34;kubernetes.io/tls\u0026#34; ) | .metadata.name\u0026#39;`;do cert_key=$(${KUBE_CMD} get secret -n $ns $secret_name -o json | jq -r \u0026#39;.data | keys[]\u0026#39; | awk \u0026#39;/.crt$/ { print }\u0026#39;) if [ -n \u0026#34;$cert_key\u0026#34; ]; then echo \u0026#34; $ns/$secret_name\u0026#34; openssl x509 -subject -noout -in \u0026lt;( ${KUBE_CMD} get secret -n ${ns} ${secret_name} -ojson | \\ jq -r --arg secret_key \u0026#34;$cert_key\u0026#34; -r \u0026#39;.data|.[$secret_key]\u0026#39; | base64 -d ) | \\ awk -v ns=$ns -v secret_name=$secret_name -v kw=${KEYWORDS} -v cmd=\u0026#34;${KUBE_CMD}\u0026#34; \u0026#39;index($0, kw) \u0026gt; 0 { system(cmd\u0026#34; get secret -n \u0026#34;ns\u0026#34; \u0026#34;secret_name\u0026#34; -oyaml \u0026gt; \u0026#34;ns\u0026#34;.\u0026#34;secret_name\u0026#34;.yaml\u0026#34;) }\u0026#39; fi done done 批量替换命令，查询 tls 签发域名，并进行替换\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 KEYWORDS=chinamobile KUBE_CMD=\u0026#39;kubectl\u0026#39; for ns in `${KUBE_CMD} get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;`; do echo \u0026#34;$ns ....\u0026#34; for secret_name in `${KUBE_CMD} get secret -n $ns -o json | jq -r \u0026#39;.items[] | select( .type == \u0026#34;kubernetes.io/tls\u0026#34;) | .metadata.name\u0026#39;`; do cert_key=$(${KUBE_CMD} get secret -n $ns $secret_name -o json | jq -r \u0026#39;.data | keys[]\u0026#39; | awk \u0026#39;/.crt$/ { print }\u0026#39;) if [ -n \u0026#34;$cert_key\u0026#34; ]; then echo \u0026#34; $ns/$secret_name\u0026#34; openssl x509 -subject -noout -in \u0026lt;( ${KUBE_CMD} get secret -n ${ns} ${secret_name} -ojson | \\ jq -r --arg secret_key \u0026#34;$cert_key\u0026#34; -r \u0026#39;.data|.[$secret_key]\u0026#39; | base64 -d ) | \\ awk -v ns=$ns -v secret_name=$secret_name -v kw=${KEYWORDS} -v cmd=\u0026#34;${KUBE_CMD}\u0026#34; \u0026#39;index($0, kw) \u0026gt; 0 { system(cmd\u0026#34; create secret tls -n \u0026#34;ns\u0026#34; \u0026#34;secret_name\u0026#34; --cert=ca.crt --key=ca.key --dry-run -oyaml|\u0026#34;cmd\u0026#34; replace -f -\u0026#34;) }\u0026#39; fi done done ","permalink":"https://www.161616.top/kubernetes-update-secert/","summary":"tls 证书在 k8s 集群上大量使用的话，当到期时会存在批量替换的难度，比如说每个名称空间，多个业务的使用，在这篇博文中，将尝试批量替换整个集群的证书（前提，在没有使用 vault, cert-manager这类组件的集群之上）。\n基本操作 步骤1：首先不知道有多少个名称空间使用了这个证书，所以需要遍历所有的名称空间，这里使用 kubectl 的 json path 实现\nbash 1 $ kubectl get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; 步骤2：拿到名称空间的名字后，就需要循环这个名称空间下所有的 secret\nbash 1 2 3 4 for ns in `kubectl get ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;` do kubectl get secret -n $ns -o jsonpath=\u0026#39;{range $.items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; done 步骤3：找到与这个匹配的证书进行替换\n把步骤3拆解为下面几个步骤：\n拿去到符合要求的secret的名字 匹配名称是否为修改的secret 做替换操作 由于步骤2使用的是 jsonpath 拿到的 secret name，由于 kubectl 并不支持高级jsonpath语法，官方推荐使用jq，那么使用jq获取名字\nbash 1 kubectl get secret -n $ns -o json|jq -r .","title":"Kubernetes维护 - secret批量更新"},{"content":"下载 nacos-server\nbash 1 $ tar zxvf nacos-server-2.2.3.tar.gz -C /opt/ 创建nacos用户\nbash 1 useradd nacos -s /sbin/nologin -M 修改 java 环境变量\n使用openjdk启动，需要配置JAVA_HOME在启动脚本中\nbash 1 2 3 4 $ rpm -ql java-1.8.0-openjdk-headless # 找到 jre 根目录配置 JAVA_HOME # /opt/nacos/bin/startup.sh JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre 启动命令\n集群模式需要同时启动多个节点\nbash 1 2 3 4 5 6 7 启动命令 # 单机 sudo -u nacos /opt/nacos/bin/startup.sh -m standalone # 集群 sudo -u nacos /opt/nacos/bin/startup.sh -m cluster # 停止服务 sudo -u nacos /opt/nacos/bin/shutdown.sh 最终版配置文件参考\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 # Copyright 1999-2021 Alibaba Group Holding Ltd. # # Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # #*************** Spring Boot Related Configurations ***************# ### Default web context path: server.servlet.contextPath=/nacos ### Include message field server.error.include-message=ALWAYS ### Default web server port: server.port=8848 #*************** Network Related Configurations ***************# ### If prefer hostname over ip for Nacos server addresses in cluster.conf: # nacos.inetutils.prefer-hostname-over-ip=false ### Specify local server\u0026#39;s IP: # nacos.inetutils.ip-address= #*************** Config Module Related Configurations ***************# ### If use MySQL as datasource: ### Deprecated configuration property, it is recommended to use `spring.sql.init.platform` replaced. #spring.datasource.platform=mysql spring.sql.init.platform=mysql ### Count of DB: db.num=1 ### Connect URL of DB: db.url.0=jdbc:mysql://198.168.21.40:33064/nacos_config?characterEncoding=utf8\u0026amp;connectTimeout=1000\u0026amp;socketTimeout=3000\u0026amp;autoReconnect=true\u0026amp;useUnicode=true\u0026amp;useSSL=false\u0026amp;serverTimezone=UTC db.user.0=nacos db.password.0=nacos ### Connection pool configuration: hikariCP db.pool.config.connectionTimeout=30000 db.pool.config.validationTimeout=10000 db.pool.config.maximumPoolSize=20 db.pool.config.minimumIdle=2 #*************** Naming Module Related Configurations ***************# ### If enable data warmup. If set to false, the server would accept request without local data preparation: # nacos.naming.data.warmup=true ### If enable the instance auto expiration, kind like of health check of instance: # nacos.naming.expireInstance=true ### Add in 2.0.0 ### The interval to clean empty service, unit: milliseconds. # nacos.naming.clean.empty-service.interval=60000 ### The expired time to clean empty service, unit: milliseconds. # nacos.naming.clean.empty-service.expired-time=60000 ### The interval to clean expired metadata, unit: milliseconds. # nacos.naming.clean.expired-metadata.interval=5000 ### The expired time to clean metadata, unit: milliseconds. # nacos.naming.clean.expired-metadata.expired-time=60000 ### The delay time before push task to execute from service changed, unit: milliseconds. # nacos.naming.push.pushTaskDelay=500 ### The timeout for push task execute, unit: milliseconds. # nacos.naming.push.pushTaskTimeout=5000 ### The delay time for retrying failed push task, unit: milliseconds. # nacos.naming.push.pushTaskRetryDelay=1000 ### Since 2.0.3 ### The expired time for inactive client, unit: milliseconds. # nacos.naming.client.expired.time=180000 #*************** CMDB Module Related Configurations ***************# ### The interval to dump external CMDB in seconds: # nacos.cmdb.dumpTaskInterval=3600 ### The interval of polling data change event in seconds: # nacos.cmdb.eventTaskInterval=10 ### The interval of loading labels in seconds: # nacos.cmdb.labelTaskInterval=300 ### If turn on data loading task: # nacos.cmdb.loadDataAtStart=false #*************** Metrics Related Configurations ***************# ### Metrics for prometheus #management.endpoints.web.exposure.include=* ### Metrics for elastic search management.metrics.export.elastic.enabled=false #management.metrics.export.elastic.host=http://localhost:9200 ### Metrics for influx management.metrics.export.influx.enabled=false #management.metrics.export.influx.db=springboot #management.metrics.export.influx.uri=http://localhost:8086 #management.metrics.export.influx.auto-create-db=true #management.metrics.export.influx.consistency=one #management.metrics.export.influx.compressed=true #*************** Access Log Related Configurations ***************# ### If turn on the access log: server.tomcat.accesslog.enabled=true ### The access log pattern: server.tomcat.accesslog.pattern=%h %l %u %t \u0026#34;%r\u0026#34; %s %b %D %{User-Agent}i %{Request-Source}i ### The directory of access log: server.tomcat.basedir=file:. #*************** Access Control Related Configurations ***************# ### If enable spring security, this option is deprecated in 1.2.0: #spring.security.enabled=false ### The ignore urls of auth nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-ui/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/** ### The auth system to use, currently only \u0026#39;nacos\u0026#39; and \u0026#39;ldap\u0026#39; is supported: nacos.core.auth.system.type=nacos ### If turn on auth system: nacos.core.auth.enabled=true ### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay. nacos.core.auth.caching.enabled=true ### Since 1.4.1, Turn on/off white auth for user-agent: nacos-server, only for upgrade from old version. nacos.core.auth.enable.userAgentAuthWhite=false ### Since 1.4.1, worked when nacos.core.auth.enabled=true and nacos.core.auth.enable.userAgentAuthWhite=false. ### The two properties is the white list for auth and used by identity the request from other server. nacos.core.auth.server.identity.key=test nacos.core.auth.server.identity.value=test ### worked when nacos.core.auth.system.type=nacos ### The token expiration in seconds: nacos.core.auth.plugin.nacos.token.cache.enable=false nacos.core.auth.plugin.nacos.token.expire.seconds=18000 ### The default token (Base64 String): nacos.core.auth.plugin.nacos.token.secret.key=ab2345678901234567890123456789012345678901234567890123456789 ### worked when nacos.core.auth.system.type=ldap，{0} is Placeholder,replace login username #nacos.core.auth.ldap.url=ldap://localhost:389 #nacos.core.auth.ldap.basedc=dc=example,dc=org #nacos.core.auth.ldap.userDn=cn=admin,${nacos.core.auth.ldap.basedc} #nacos.core.auth.ldap.password=admin #nacos.core.auth.ldap.userdn=cn={0},dc=example,dc=org #nacos.core.auth.ldap.filter.prefix=uid #nacos.core.auth.ldap.case.sensitive=true #*************** Istio Related Configurations ***************# ### If turn on the MCP server: nacos.istio.mcp.server.enabled=false #*************** Core Related Configurations ***************# ### set the WorkerID manually # nacos.core.snowflake.worker-id= ### Member-MetaData # nacos.core.member.meta.site= # nacos.core.member.meta.adweight= # nacos.core.member.meta.weight= ### MemberLookup ### Addressing pattern category, If set, the priority is highest # nacos.core.member.lookup.type=[file,address-server] ## Set the cluster list with a configuration file or command-line argument nacos.member.list=192.168.16.101:8847?raft_port=8807,192.168.16.101?raft_port=8808,192.168.16.101:8849?raft_port=8809 ## for AddressServerMemberLookup # Maximum number of retries to query the address server upon initialization # nacos.core.address-server.retry=5 ## Server domain name address of [address-server] mode # address.server.domain=jmenv.tbsite.net ## Server port of [address-server] mode # address.server.port=8080 ## Request address of [address-server] mode # address.server.url=/nacos/serverlist #*************** JRaft Related Configurations ***************# ### Sets the Raft cluster election timeout, default value is 5 second # nacos.core.protocol.raft.data.election_timeout_ms=5000 ### Sets the amount of time the Raft snapshot will execute periodically, default is 30 minute # nacos.core.protocol.raft.data.snapshot_interval_secs=30 ### raft internal worker threads # nacos.core.protocol.raft.data.core_thread_num=8 ### Number of threads required for raft business request processing # nacos.core.protocol.raft.data.cli_service_thread_num=4 ### raft linear read strategy. Safe linear reads are used by default, that is, the Leader tenure is confirmed by heartbeat # nacos.core.protocol.raft.data.read_index_type=ReadOnlySafe ### rpc request timeout, default 5 seconds # nacos.core.protocol.raft.data.rpc_request_timeout_ms=5000 #*************** Distro Related Configurations ***************# ### Distro data sync delay time, when sync task delayed, task will be merged for same data key. Default 1 second. # nacos.core.protocol.distro.data.sync.delayMs=1000 ### Distro data sync timeout for one sync data, default 3 seconds. # nacos.core.protocol.distro.data.sync.timeoutMs=3000 ### Distro data sync retry delay time when sync data failed or timeout, same behavior with delayMs, default 3 seconds. # nacos.core.protocol.distro.data.sync.retryDelayMs=3000 ### Distro data verify interval time, verify synced data whether expired for a interval. Default 5 seconds. # nacos.core.protocol.distro.data.verify.intervalMs=5000 ### Distro data verify timeout for one verify, default 3 seconds. # nacos.core.protocol.distro.data.verify.timeoutMs=3000 ### Distro data load retry delay when load snapshot data failed, default 30 seconds. # nacos.core.protocol.distro.data.load.retryDelayMs=30000 ### enable to support prometheus service discovery #nacos.prometheus.metrics.enabled=true ### Since 2.3 #*************** Grpc Configurations ***************# ## sdk grpc(between nacos server and client) configuration ## Sets the maximum message size allowed to be received on the server. #nacos.remote.server.grpc.sdk.max-inbound-message-size=10485760 ## Sets the time(milliseconds) without read activity before sending a keepalive ping. The typical default is two hours. #nacos.remote.server.grpc.sdk.keep-alive-time=7200000 ## Sets a time(milliseconds) waiting for read activity after sending a keepalive ping. Defaults to 20 seconds. #nacos.remote.server.grpc.sdk.keep-alive-timeout=20000 ## Sets a time(milliseconds) that specify the most aggressive keep-alive time clients are permitted to configure. The typical default is 5 minutes #nacos.remote.server.grpc.sdk.permit-keep-alive-time=300000 ## cluster grpc(inside the nacos server) configuration #nacos.remote.server.grpc.cluster.max-inbound-message-size=10485760 ## Sets the time(milliseconds) without read activity before sending a keepalive ping. The typical default is two hours. #nacos.remote.server.grpc.cluster.keep-alive-time=7200000 ## Sets a time(milliseconds) waiting for read activity after sending a keepalive ping. Defaults to 20 seconds. #nacos.remote.server.grpc.cluster.keep-alive-timeout=20000 ## Sets a time(milliseconds) that specify the most aggressive keep-alive time clients are permitted to configure. The typical default is 5 minutes #nacos.remote.server.grpc.cluster.permit-keep-alive-time=300000 ","permalink":"https://www.161616.top/nacos-deploy-with-vm/","summary":"下载 nacos-server\nbash 1 $ tar zxvf nacos-server-2.2.3.tar.gz -C /opt/ 创建nacos用户\nbash 1 useradd nacos -s /sbin/nologin -M 修改 java 环境变量\n使用openjdk启动，需要配置JAVA_HOME在启动脚本中\nbash 1 2 3 4 $ rpm -ql java-1.8.0-openjdk-headless # 找到 jre 根目录配置 JAVA_HOME # /opt/nacos/bin/startup.sh JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre 启动命令\n集群模式需要同时启动多个节点\nbash 1 2 3 4 5 6 7 启动命令 # 单机 sudo -u nacos /opt/nacos/bin/startup.sh -m standalone # 集群 sudo -u nacos /opt/nacos/bin/startup.sh -m cluster # 停止服务 sudo -u nacos /opt/nacos/bin/shutdown.","title":"使用虚拟机部署nacos"},{"content":"什么是jsonnet jsonnet是用于app或开发工具的数据模板语言，主要用于json的扩展，具有下面功能：\n生成配置数据 无副作用 组织化，简化，统一化 管理无序的配置 jsonnet可以通过面向对象消除重复。或者，使用函数。与现有/自定义应用程序集成。生成 JSON、YAML、INI 和其他格式。\n安装jsonnet Jsonnet 有两种实现（C++ 和 Go）\n在 Debian/Ubuntu 之上，可以直接使用 apt 源来安装\nbash 1 apt install jsonnet -y 安装 go 实现的，可以用下面命令，前提是安装了go\nbash 1 go get github.com/google/go-jsonnet/cmd/jsonnet 什么是jsonnet-bundler jsonnet-bundler 是 Jsonnet 的包管理器，用于个简化 jsonnet 项目中依赖关系管理的工具。使用 Jsonnet Bundler 可以带来下面便利之处：\n使用 jsonnetfile.json 作为依赖关系管理 自动安装和更新依赖项。 版本选择，使用 jsonnetfile.json 可以确保项目使用正确版本的依赖。 可重复构建， 使用 jsonnetfile.json 管理的项目可以在不同环境中构建并确保结果的一致性。 更方便的与 GitOps 结合， Jsonnet Bundler 提供 与 GitOps 的集成。 jsonnet-bundler 安装 Jsonnet Bundler 有两种安装模式，实际上就是 Go 程序通用的安装方式：\nJsonnet Bundler 二进制文件 （预构建） go install 安装 在 jsonnet bundler release 页面下载对应版本\n使用 go install 安装\nbash 1 go install -a github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb@latest notes jsonnet bundler 要求 Go 版本最少是 Go 1.13 或更高版本。 [1] 使用 go install 安装的，需要将 $(go env GOPATH)/bin 加入到 $PATH jsonnetfile.json https://jsonnet.movatech.today/blog/structure-of-the-jsonnetfile.json-file/\nhttps://jsonnet.org/learning/tutorial.html\nhttps://jsonnet-libs.github.io/jsonnet-training-course/\nhttps://medium.com/@pmspraveen8/k8s-libsonnet-generating-manifests-33a5e5aff277\nReference ​[1] jsonnet\n​[2] JSONPath Syntax\n​[3] k8s学习-kubectl命令行 jsonpath的使用\n","permalink":"https://www.161616.top/k8s-jsonnet/","summary":"什么是jsonnet jsonnet是用于app或开发工具的数据模板语言，主要用于json的扩展，具有下面功能：\n生成配置数据 无副作用 组织化，简化，统一化 管理无序的配置 jsonnet可以通过面向对象消除重复。或者，使用函数。与现有/自定义应用程序集成。生成 JSON、YAML、INI 和其他格式。\n安装jsonnet Jsonnet 有两种实现（C++ 和 Go）\n在 Debian/Ubuntu 之上，可以直接使用 apt 源来安装\nbash 1 apt install jsonnet -y 安装 go 实现的，可以用下面命令，前提是安装了go\nbash 1 go get github.com/google/go-jsonnet/cmd/jsonnet 什么是jsonnet-bundler jsonnet-bundler 是 Jsonnet 的包管理器，用于个简化 jsonnet 项目中依赖关系管理的工具。使用 Jsonnet Bundler 可以带来下面便利之处：\n使用 jsonnetfile.json 作为依赖关系管理 自动安装和更新依赖项。 版本选择，使用 jsonnetfile.json 可以确保项目使用正确版本的依赖。 可重复构建， 使用 jsonnetfile.json 管理的项目可以在不同环境中构建并确保结果的一致性。 更方便的与 GitOps 结合， Jsonnet Bundler 提供 与 GitOps 的集成。 jsonnet-bundler 安装 Jsonnet Bundler 有两种安装模式，实际上就是 Go 程序通用的安装方式：","title":"k8s - jsonnet从入门到放弃"},{"content":"处理记录 Ceph版本：octopus\n首先遇到問題是，业务端无法挂在 cephfs 查看内核日志发现是 bad authorize reply ，以为是 ceph keyring被替换了\ntext 1 2 3 4 5 6 7 8 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.80.20.100:6801 bad authorize reply 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.80.20.100:6801 bad authorize reply 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.80.20.100:6801 bad authorize reply 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.80.20.100:6801 bad authorize reply 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.80.20.100:6801 bad authorize reply 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.80.20.100:6801 bad authorize reply 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.80.20.100:6801 bad authorize reply 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.80.20.100:6801 bad authorize reply 在排查完 keyring 后，手动尝试挂载 cephfs 提示 Input/output error ，此时看出是集群问题了\nbash 1 2 $ mount -t ceph 10.80.20.100:6789:/tmp /tmp/ceph -o secret=AQCoW0dgQk4qGhAAwayKv70OSyyWB3XpZ1JLYQ==,name=cephuser mount error 5 = Input/output error 因为一开始看到日志是 bad authorize reply 以为是认证错误，重新登录了一下发现是相同的提示，这时查看 ceph status 发现集群异常，除了下面报错外，还有一个 osd down 的状态。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 $ ceph health detail HEALTH_WARN 1 host fail cephadm check; 1 MDSs report slow metadata IOs; 1 MDSs report slow requests; clock skew detected on mon.localhost; Degraded data redundancy: 39560/118680 objects degraded (33.333%), 201 pgs degraded, 255 pgs undersized; 4 daemons have recently crashed [WRN] CEPHADM_HOST_CHECK_FAIL: 1 hosts fail cephadm check host localhost failed check: [\u0026#39;podman|docker (/bin/docker) is present\u0026#39;, \u0026#39;systemctl is present\u0026#39;, \u0026#39;lvcreate is present\u0026#39;, \u0026#34;No time sync service is running; checked for [\u0026#39;chrony.service\u0026#39;, \u0026#39;chronyd.service\u0026#39;, \u0026#39;systemd-timesyncd.service\u0026#39;, \u0026#39;ntpd.service\u0026#39;, \u0026#39;ntp.service\u0026#39;]\u0026#34;, \u0026#39;ERROR: No time synchronizetion is active\u0026#39;] [WRN] MDS_SLOW_METADATA_IO: 1 MDSs report slow metadata IOs mds.cephfs.localhost.mhlzaj(mds.0): 20 slow metadata IOs are blocked \u0026gt; 30 secs, oldest blocked for 4830 secs [WRN] MDS_SLOW_REQUEST: 1 MDSs report slow requests mds.cephfs.localhost.mhlzaj(mds.0): 14 slow metadata IOs are blocked \u0026gt; 30 secs [WRN] MON_CLOCK_SKEW: clock skew detected on mon.localhost, mon.localhost1 mon.localhost clock skew 29357.8s \u0026gt; max 0.05s (latency 0.0132089s) mon.localhost1 clock skew 29357.8s \u0026gt; max 0.05s (latency 0.0117421s) [WRN] PG_DEGRADED: Degraded data redundancy: 39501/118680 objects degraded (33.333%), 189 pgs degraded, 241 pgs undersized pg 1.0 is stuck undersized for 22m, current state active+undersized+degraded, last acting [1] pg 2.0 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 2.1 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 2.2 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 2.3 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 2.4 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 2.5 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 2.6 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 2.7 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 2.8 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 2.c is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 2.d is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 2.e is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 2.f is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 2.10 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 2.11 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 2.12 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 2.13 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 2.14 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 2.15 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 2.16 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 2.17 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 2.18 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 2.19 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 2.1a is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 2.1b is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.0 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.1 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 3.2 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.3 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 3.4 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.5 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.6 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 3.7 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.9 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 3.c is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 3.d is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.e is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.f is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 3.10 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.11 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 3.12 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 3.13 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.14 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.15 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 3.16 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.17 is stuck undersized for 4d, current state active+undersized+degraded, last acting [0] pg 3.18 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.19 is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] pg 3.1a is stuck undersized for 4d, current state active+undersized+degraded, last acting [1] [WRN] RECENT_CRASH: 4 daemons have recently crashed osd4 crhash on host xxxxxx at 20xx-0x-xxT04xx:xx:xx.xxxxxxz client.xxx.xxxx.hostname.xxxx crashed on host hostname at 20xx-0x-xxT04xx:xx:xx.xxxxxxz CEPHADM_HOST_CHECK_FAIL：一台或多台主机未通过基本 cephadm 主机检查，该检查验证 (1) 主机可访问并且可以在其中执行 cephadm，以及 (2) 主机满足基本先决条件，例如工作容器运行时（podman 或 docker）和工作时间同步。如果此测试失败，cephadm 将无法管理该主机上的服务。\nMDS_SLOW_METADATA_IO\nMDS_SLOW_REQUEST：N条慢请求被阻塞\nMON_CLOCK_SKEW：运行 ceph-mon 的主机上的时钟未很好同步。如果集群检测到时钟偏差大于 mon_clock_drift_allowed，则会引发此运行状况检查。\nPG_DEGRADED：一个或多个PG的健康状态受到了损害。一种常见的情况是，某个OSD发生故障或离线，导致PG进入降级状态。在这种情况下，数据副本的可用性会受到影响，并且Ceph集群的性能也可能下降。\n首先重启 chronyd 修复了时间同步的问题，因为机房内机器经常出现 chronyd 的服务导致异常，其次重启 osd 服务，让 ceph 做再平衡完成后剩下下面报错。\n并且现象有两个：\ncephfs no such file or director ceph orch 命令还是没有反应 text 1 2 3 4 5 6 7 8 9 10 $ ceph health detail HEALTH_WARN 1 host fail cephadm check; 1 MDSs report slow metadata IOs; 1 MDSs report slow requests; clock skew detected on mon.localhost; Degraded data [WRN] FS_DEGRADED: 1 filesystem is degraded fs cephfs is degraded [WRN] MDS_SLOW_METADATA_IO: 1 MDSs slow metadata IOs mds.cephfs.localhost.mhlzaj(mds.0): 20 slow metadata IOs are blocked \u0026gt; 30 secs, oldest blocked for 930 secs [WRN] RECENT_CRASH: 4 daemons have recently crashed osd4 crhash on host xxxxxx at 20xx-0x-xxT04xx:xx:xx.xxxxxxz client.xxx.xxxx.hostname.xxxx crashed on host hostname at 20xx-0x-xxT04xx:xx:xx.xxxxxxz [WRN] SLOW_OPS: 2 slow ops, oldset one blocked for 474 sec, mon.localhost has slow ops 通过 search 了一下，查询到 orch 是 MGR 模块\nThe orchestrator is a MGR module, have you checked if the containers are up and running [1]\n此时操作登录对应ceph node，docker restart ceph-mgr的模块，并且重启 mds 模块\nbash 1 2 3 4 $ ceph health detail [WRN] RECENT_CRASH: 4 daemons have recently crashed osd4 crhash on host xxxxxx at 20xx-0x-xxT04xx:xx:xx.xxxxxxz client.xxx.xxxx.hostname.xxxx crashed on host hostname at 20xx-0x-xxT04xx:xx:xx.xxxxxxz 此时集群恢复正常，cephfs 恢复\n总结 由于长期没有在处理 ceph 方向问题，对排查有以下生疏：\n无法挂载时没有及时查看 ceph 集群信息，而是盯着客户端方向日志查询了半天。 对 ceph 故障代码没有了解过，如果有了解，可以很明确的定位问题，而不用耽误2小时。 Reference [1] ceph orch status hangs forever\n[2] HEALTH CHECKS\n[3] CEPHFS HEALTH MESSAGES\n","permalink":"https://www.161616.top/ch10-2-troubeshooting-crash-record/","summary":"处理记录 Ceph版本：octopus\n首先遇到問題是，业务端无法挂在 cephfs 查看内核日志发现是 bad authorize reply ，以为是 ceph keyring被替换了\ntext 1 2 3 4 5 6 7 8 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.80.20.100:6801 bad authorize reply 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.80.20.100:6801 bad authorize reply 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.80.20.100:6801 bad authorize reply 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.80.20.100:6801 bad authorize reply 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.80.20.100:6801 bad authorize reply 2019-01-30 17:26:58 localhost kernel: libceph: mds0 10.","title":"记录一次ceph集群故障处理记录"},{"content":"Overview 阅读完本文，您当了解\nLinux oom kill Kubernetes oom 算法 Kubernetes QoS 本文只是个人理解，如果有大佬觉得不是这样的可以留言一起讨论，参考源码版本为 1.18.20，与高版本相差不大\n什么是OOM Kill 当你的Linux机器内存不足时，内核会调用Out of Memory (OOM) killer来释放一些内存。这经常在运行许多内存密集型进程的服务器上遇到。\nOOM Killer是如何选择要杀死的进程的？ Linux内核为每个运行的进程分配一个分数，称为 oom_score，==显示在内存紧张时终止该进程的可能性有多大==。该 Score 与进程使用的内存量成比例。 Score 是进程使用内存的百分比乘以10。因此，最大分数是 $100% \\times 10 = 1000$。此外，如果一个进程以特权用户身份运行，那么与普通用户进程相比，它的 oom_score 会稍低。\n在主发行版内核会将 /proc/sys/vm/overcommit_memory 的默认值设置为零，这意味着进程可以请求比系统中当前可用的内存更多的内存。这是基于以下启发式完成的：分配的内存不会立即使用，并且进程在其生命周期内也不会使用它们分配的所有内存。如果没有过度使用，系统将无法充分利用其内存，从而浪费一些内存。过量使用内存允许系统以更有效的方式使用内存，但存在 OOM 情况的风险。占用内存的程序会耗尽系统内存，使整个系统陷入瘫痪。当内存太低时，这可能会导致这样的情况：即使是单个页面也无法分配给用户进程，从而允许管理员终止适当的任务，或者内核执行重要操作，例如释放内存。在这种情况下，OOM Killer 就会介入，并将该进程识别为牺牲品，以保证系统其余部分的利益。\n用户和系统管理员经常询问控制 OOM Killer 行为的方法。为了方便控制，引入了 /proc/\u0026lt;pid\u0026gt;/oom_adj 来防止系统中的重要进程被杀死，并定义进程被杀死的顺序。 oom_adj 的可能值范围为 -17 到 +15。Score 越高，相关进程就越有可能被 OOM-killer Kill。如果 oom_adj 设置为 -17，则 OOM Killer 不会 Kill 该进程。\noom_score 分数为 1 ~ 1000，值越低，程序被杀死的机会就越小。\noom_score 0 表示该进程未使用任何可用内存。 oom_score 1000 表示该进程正在使用 100% 的可用内存，大于1000，也取1000。 谁是糟糕的进程？ 在内存不足的情况下选择要被终止的进程是基于其 oom_score 。糟糕进程 Score 被记录在 /proc/\u0026lt;pid\u0026gt;/oom_score 文件中。该值是基于系统损失的最小工作量、回收的大量内存、不终止任何消耗大量内存的无辜进程以及终止的进程数量最小化（如果可能限制在一个）等因素来确定的。糟糕程度得分是使用进程的原始内存大小、其 CPU 时间（utime + stime）、运行时间（uptime - 启动时间）以及其 oom_adj 值计算的。进程使用的内存越多，得分越高。进程在系统中存在的时间越长，得分越小。\n列出所有正在运行的进程的OOM Score bash 1 2 printf \u0026#39;PID\\tOOM Score\\tOOM Adj\\tCommand\\n\u0026#39; while read -r pid comm; do [ -f /proc/$pid/oom_score ] \u0026amp;\u0026amp; [ $(cat /proc/$pid/oom_score) != 0 ] \u0026amp;\u0026amp; printf \u0026#39;%d\\t%d\\t\\t%d\\t%s\\n\u0026#39; \u0026#34;$pid\u0026#34; \u0026#34;$(cat /proc/$pid/oom_score)\u0026#34; \u0026#34;$(cat /proc/$pid/oom_score_adj)\u0026#34; \u0026#34;$comm\u0026#34;; done \u0026lt; \u0026lt;(ps -e -o pid= -o comm=) | sort -k 2nr 如何检查进程是否已被 OOM 终止 最简单的方法是查看grep系统日志。在 Ubuntu 中：grep -i kill /var/log/syslog。如果进程已被终止，您可能会得到类似的结果\nbash 1 my_process invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0 Kubernetes的QoS是如何设计的 Kubernetes 中 Pod 存在一个 “服务质量等级” (QoS)，它保证了Kubernetes 在 Node 资源不足时使用 QoS 类来就驱逐 Pod 作出决定。这个 QoS 就是基于 OOM Kill Score 和 Adj 来设计的。\n对于用户来讲，Kubernetes Pod 的 QoS 有三类，这些设置是被自动设置的，除此之外还有两种单独的等级：“Worker 组件”，总共 Pod QoS 的级别有5种\nKubelet KubeProxy Guaranteed Besteffort Burstable 这些在 pkg/kubelet/qos/policy.go 中可以看到，其中 Burstable 属于一个动态的级别。\ngo 1 2 3 4 5 6 7 8 const ( // KubeletOOMScoreAdj is the OOM score adjustment for Kubelet KubeletOOMScoreAdj int = -999 // KubeProxyOOMScoreAdj is the OOM score adjustment for kube-proxy KubeProxyOOMScoreAdj int = -999 guaranteedOOMScoreAdj int = -998 besteffortOOMScoreAdj int = 1000 ) 其中最重要的分数就是 Burstable，这保证了驱逐的优先级，他的算法为：$1000 - \\frac{1000 \\times Request}{memoryCapacity}$ ，Request 为 Deployment 这类清单中配置的 Memory Request 的部分，memoryCapacity 则为 Node 的内存数量。\n例如 Node 为 64G，Pod Request 值配置了 2G，那么最终 oom_score_adj 的值为 $1000 - \\frac{1000 \\times Request}{memoryCapacity} = 1000 - \\frac{1000\\times2}{64} = 968$\n这部分可以在下面代码中看到，其中算出的值将被写入 /proc/{pid}/oom_score_adj 文件内\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 func GetContainerOOMScoreAdjust(pod *v1.Pod, container *v1.Container, memoryCapacity int64) int { if types.IsNodeCriticalPod(pod) { // Only node critical pod should be the last to get killed. return guaranteedOOMScoreAdj } switch v1qos.GetPodQOS(pod) { case v1.PodQOSGuaranteed: // Guaranteed containers should be the last to get killed. return guaranteedOOMScoreAdj case v1.PodQOSBestEffort: return besteffortOOMScoreAdj } // Burstable containers are a middle tier, between Guaranteed and Best-Effort. Ideally, // we want to protect Burstable containers that consume less memory than requested. // The formula below is a heuristic. A container requesting for 10% of a system\u0026#39;s // memory will have an OOM score adjust of 900. If a process in container Y // uses over 10% of memory, its OOM score will be 1000. The idea is that containers // which use more than their request will have an OOM score of 1000 and will be prime // targets for OOM kills. // Note that this is a heuristic, it won\u0026#39;t work if a container has many small processes. memoryRequest := container.Resources.Requests.Memory().Value() if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { if cs, ok := podutil.GetContainerStatus(pod.Status.ContainerStatuses, container.Name); ok { memoryRequest = cs.AllocatedResources.Memory().Value() } } oomScoreAdjust := 1000 - (1000*memoryRequest)/memoryCapacity // A guaranteed pod using 100% of memory can have an OOM score of 10. Ensure // that burstable pods have a higher OOM score adjustment. if int(oomScoreAdjust) \u0026lt; (1000 + guaranteedOOMScoreAdj) { return (1000 + guaranteedOOMScoreAdj) } // Give burstable pods a higher chance of survival over besteffort pods. if int(oomScoreAdjust) == besteffortOOMScoreAdj { return int(oomScoreAdjust - 1) } return int(oomScoreAdjust) } 到此可以了解到 Pod QoS 级别为\nKubelet = KubeProxy = -999\nGuaranteed = -998\n1000(Besteffort) \u0026gt; Burstable \u0026gt; -998 (Guaranteed)\nBesteffort = 1000\n那么在当 Node 节点内存不足时，发生驱逐的条件就会根据 oom_score_adj 完成，但当 Pod 中程序使用内存达到了 Limits 限制，此时的OOM Killed和上面阐述的无关。\nReference ​[1] Taming the OOM killer\n​[2] How does the OOM killer decide which process to kill first?\n","permalink":"https://www.161616.top/ch30-oomkill/","summary":"Overview 阅读完本文，您当了解\nLinux oom kill Kubernetes oom 算法 Kubernetes QoS 本文只是个人理解，如果有大佬觉得不是这样的可以留言一起讨论，参考源码版本为 1.18.20，与高版本相差不大\n什么是OOM Kill 当你的Linux机器内存不足时，内核会调用Out of Memory (OOM) killer来释放一些内存。这经常在运行许多内存密集型进程的服务器上遇到。\nOOM Killer是如何选择要杀死的进程的？ Linux内核为每个运行的进程分配一个分数，称为 oom_score，==显示在内存紧张时终止该进程的可能性有多大==。该 Score 与进程使用的内存量成比例。 Score 是进程使用内存的百分比乘以10。因此，最大分数是 $100% \\times 10 = 1000$。此外，如果一个进程以特权用户身份运行，那么与普通用户进程相比，它的 oom_score 会稍低。\n在主发行版内核会将 /proc/sys/vm/overcommit_memory 的默认值设置为零，这意味着进程可以请求比系统中当前可用的内存更多的内存。这是基于以下启发式完成的：分配的内存不会立即使用，并且进程在其生命周期内也不会使用它们分配的所有内存。如果没有过度使用，系统将无法充分利用其内存，从而浪费一些内存。过量使用内存允许系统以更有效的方式使用内存，但存在 OOM 情况的风险。占用内存的程序会耗尽系统内存，使整个系统陷入瘫痪。当内存太低时，这可能会导致这样的情况：即使是单个页面也无法分配给用户进程，从而允许管理员终止适当的任务，或者内核执行重要操作，例如释放内存。在这种情况下，OOM Killer 就会介入，并将该进程识别为牺牲品，以保证系统其余部分的利益。\n用户和系统管理员经常询问控制 OOM Killer 行为的方法。为了方便控制，引入了 /proc/\u0026lt;pid\u0026gt;/oom_adj 来防止系统中的重要进程被杀死，并定义进程被杀死的顺序。 oom_adj 的可能值范围为 -17 到 +15。Score 越高，相关进程就越有可能被 OOM-killer Kill。如果 oom_adj 设置为 -17，则 OOM Killer 不会 Kill 该进程。\noom_score 分数为 1 ~ 1000，值越低，程序被杀死的机会就越小。\noom_score 0 表示该进程未使用任何可用内存。 oom_score 1000 表示该进程正在使用 100% 的可用内存，大于1000，也取1000。 谁是糟糕的进程？ 在内存不足的情况下选择要被终止的进程是基于其 oom_score 。糟糕进程 Score 被记录在 /proc/\u0026lt;pid\u0026gt;/oom_score 文件中。该值是基于系统损失的最小工作量、回收的大量内存、不终止任何消耗大量内存的无辜进程以及终止的进程数量最小化（如果可能限制在一个）等因素来确定的。糟糕程度得分是使用进程的原始内存大小、其 CPU 时间（utime + stime）、运行时间（uptime - 启动时间）以及其 oom_adj 值计算的。进程使用的内存越多，得分越高。进程在系统中存在的时间越长，得分越小。","title":"深入理解Kubernetes - 基于OOMKill的QoS的设计"},{"content":"本文使用 helm 方式部署 grafana 9 并同样将 keycloak 部署在 kubernetes 集群之上；接下来使用 keycloak 作为 grafana authentication，并实现 oauth2 的用户权限管理。\n在Kubernetes 集群之上使用helm部署keycloak 在 kubernetes 集群安装 keycloak 有两种方式：\nbitnami helm offical 下面使用 offical 提供的方式进行部署\nbash 1 kubectl create -f https://raw.githubusercontent.com/keycloak/keycloak-quickstarts/latest/kubernetes/keycloak.yaml helm 部署完成后默认密码是存储在 secret 中，上面方式安装的密码默认为 admin/admin\nKeycloak configuration 创建realm Realm 管理这一组用户(users), 凭据(credentials), 角色(roles) 和 组(groups)，realm之间是相互隔离，一个用户属于并登录到某个 realm，只能管理和验证其控制的用户。\n下面为 grafana 创建一个 realm，如果你的环境已经存在通用的 realm，则可以使用这个 realm，默认 keycloak 的 realm 是 master，超级管理员属于这个 realm。\n创建 client Client 是可以请求Keycloak对用户进行身份验证的实体。最常见用途是希望使用Keycloak来保护自己并提供单点登录(SSO)解决方案的应用程序和服务。客户端也可以是只希望请求身份信息或访问令牌的实体，以便它们可以安全地调用由 Keycloak 保护的网络上的其他服务。因此，我们需要为 grafana 创建一个 client\n根据 grafana 官方的指南，我们创建标准需如下所示\nClient ID: grafana-oauth Enabled: ON Client Protocol: openid-connect Access Type: confidential Standard Flow Enabled: ON Implicit Flow Enabled: OFF Direct Access Grants Enabled: ON Root URL: \u0026lt;grafana_root_url\u0026gt; Valid Redirect URIs: \u0026lt;grafana_root_url\u0026gt;/login/generic_oauth Web Origins: \u0026lt;grafana_root_url\u0026gt; Admin URL: \u0026lt;grafana_root_url\u0026gt; Base URL: \u0026lt;grafana_root_url\u0026gt; 图：创建一个 grafana-oauth client 图：配置 capability config Realm settings =\u0026gt; Client policies =\u0026gt; Policies\n图：创建 client policy 图：创建 access type 准备grafana chart包 添加 chart repo\nbash 1 helm repo add grafana https://grafana.github.io/helm-charts 选择 chart 包下载\nbash 1 2 3 4 # 查看仓库中所有版本 helm search repo grafana/grafana -l # 下载指定版本的chart包 helm pull grafana/grafana --version 6.57.4 修改 values.yaml 中 grafana.ini 部分，增加\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 server: domain: \u0026#34;{{ if (and .Values.ingress.enabled .Values.ingress.hosts) }}{{ .Values.ingress.hosts | first }}{{ else }}\u0026#39;\u0026#39;{{ end }}\u0026#34; server: # The full public facing url you use in browser, used for redirects and emails root_url: http://10.0.0.4:30033/ serve_from_sub_path: false auth: signout_redirect_url: http://10.0.0.5:30043/auth/realms/sso/protocol/openid-connect/logout?post_logout_redirect_uri=http%3A%2F%210.0.0.5:30033%2Flogin auth.generic_oauth: enabled: true name: grafana-oauth allow_sign_up: true client_id: grafana-oauth client_secret: TF1FjfEfoeBdLa3MfpCjhC1TgChWYyPV scopes: openid email profile offline_access roles auth_url: http://10.0.0.5:30043/realms/sso/protocol/openid-connect/auth token_url: http://10.0.0.5:30043/realms/sso/protocol/openid-connect/token api_url: http://10.0.0.5:30043/realms/sso/protocol/openid-connect/userinfo role_attribute_path: contains(roles[*], \u0026#39;admin\u0026#39;) \u0026amp;\u0026amp; \u0026#39;Admin\u0026#39; || contains(roles[*], \u0026#39;editor\u0026#39;) \u0026amp;\u0026amp; \u0026#39;Editor\u0026#39; || \u0026#39;Viewer\u0026#39; 这里主要修改三个部分\nserver： root_url: 是在使用 keycloak 跳转时，redirect_uri 的地址，默认是 localhost:3000 serve_from_sub_path: 从 root_url 设置中指定的子路径提供 Grafana 服务。 出于兼容性原因，默认情况下它设置为 false。 auth: 配置登出时请求的 API，这个按照官方给出的配置即可。 auth.generic_oauth: keycloak 的配置，这个按照官方给出的配置即可。 troubleshooting Invalid redirect uri text 1 Invalid redirect uri for “Valid Redirect URIs 图：Invalid redirect uri错误 遇到 Invalid redirect uri 错误时，检查 Access settings 配置，并将 grafana 的 auth.generic_oauth 按照下面配置即可\nserver 段\nini 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 [server] # Protocol (http, https, h2, socket) protocol = http # The ip address to bind to, empty will bind to all interfaces http_addr = # The http port to use http_port = 3000 # The public facing domain name used to access grafana from a browser domain = 192.168.64.57 # Redirect to correct domain if host header does not match domain # Prevents DNS rebinding attacks enforce_domain = false # The full public facing url # root_url = %(protocol)s://%(domain)s:%(http_port)s/ root_url = http://192.168.64.57:30606 # Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons. serve_from_sub_path = false # Log web requests router_logging = false # the path relative working path static_root_path = public # enable gzip enable_gzip = false # https certs \u0026amp; key file cert_file = cert_key = # Unix socket path socket = /tmp/grafana.sock # CDN Url cdn_url = # Sets the maximum time in minutes before timing out read of an incoming request and closing idle connections. # `0` means there is no timeout for reading the request. read_timeout = 0 Generic OAuth 部分\n#ini #################################### Generic OAuth ####################### [auth.generic_oauth] name = OAuth enabled = true allow_sign_up = true client_id = Grafana client_secret = ad35e16d-96d1-46ab-88d8-7cdb1512b608 scopes = openid profile email email_attribute_name = email:primary email_attribute_path = login_attribute_path = name_attribute_path = role_attribute_path = id_token_attribute_name = auth_url = http://192.168.64.57:30708/auth/realms/devops/protocol/openid-connect/auth token_url = http://192.168.64.57:30708/auth/realms/devops/protocol/openid-connect/token api_url = http://192.168.64.57:30708/auth/realms/devops/protocol/openid-connect/userinfo allowed_domains = team_ids = allowed_organizations = tls_skip_verify_insecure = false tls_client_cert = tls_client_key = tls_client_ca = login.OAuthLogin(state mismatch) 图：登录后错误 这个错误是没有配置对应user的状态或者没有配置email导致，如果配置了 email，那么吧用户放置到对应组中就恢复了\n图：用户与组 配置权限 如果使用了 role 作为权限分配，那么按照官方的配置即可，如果使用 group，则需要使用下面的规则\nini 1 role_attribute_path = contains(groups[], \u0026#39;admin\u0026#39;) \u0026amp;\u0026amp; \u0026#39;Admin\u0026#39; || contains(groups[], \u0026#39;editer\u0026#39;) \u0026amp;\u0026amp; \u0026#39;Editor\u0026#39; || contains(groups[*], \u0026#39;viewer\u0026#39;) \u0026amp;\u0026amp; \u0026#39;Viewer\u0026#39; 这个表达式是一个条件表达式，用于根据用户所属的组分配角色属性值。下面是对每个字符和子表达式的翻译解释：\nrole_attribute_path : 角色属性路径，表示根据条件表达式的结果来确定角色属性值。 contains(groups[*], 'admin'): 检查用户所属的组列表中是否包含 admin 组。 \u0026amp;\u0026amp;: 逻辑与运算符，用于组合多个条件，表示两个条件都为真时整个表达式为真。 Admin: 如果 contains(groups[*], 'admin') 为真，将角色属性设置为 Admin。 ||: 逻辑或运算符，用于组合多个条件，表示任意一个条件为真时整个表达式为真。 contains(groups[*], 'editer'): 检查用户所属的组列表中是否包含 editer 组。 'Editor': 如果 contains(groups[*], 'editer') 为真，将角色属性设置为 Editor。 contains(groups[*], 'viewer'): 检查用户所属的组列表中是否包含 viewer 组。 'Viewer': 如果 contains(groups[*], 'viewer') 为真，将角色属性设置为 Viewer。 Reference [1] keycloak redirect_uri is always localhost:3000 #39\n[2] templates/keycloak.yaml\n[3] Configure Keycloak OAuth2 authentication\n","permalink":"https://www.161616.top/grafana-keycloak/","summary":"本文使用 helm 方式部署 grafana 9 并同样将 keycloak 部署在 kubernetes 集群之上；接下来使用 keycloak 作为 grafana authentication，并实现 oauth2 的用户权限管理。\n在Kubernetes 集群之上使用helm部署keycloak 在 kubernetes 集群安装 keycloak 有两种方式：\nbitnami helm offical 下面使用 offical 提供的方式进行部署\nbash 1 kubectl create -f https://raw.githubusercontent.com/keycloak/keycloak-quickstarts/latest/kubernetes/keycloak.yaml helm 部署完成后默认密码是存储在 secret 中，上面方式安装的密码默认为 admin/admin\nKeycloak configuration 创建realm Realm 管理这一组用户(users), 凭据(credentials), 角色(roles) 和 组(groups)，realm之间是相互隔离，一个用户属于并登录到某个 realm，只能管理和验证其控制的用户。\n下面为 grafana 创建一个 realm，如果你的环境已经存在通用的 realm，则可以使用这个 realm，默认 keycloak 的 realm 是 master，超级管理员属于这个 realm。\n创建 client Client 是可以请求Keycloak对用户进行身份验证的实体。最常见用途是希望使用Keycloak来保护自己并提供单点登录(SSO)解决方案的应用程序和服务。客户端也可以是只希望请求身份信息或访问令牌的实体，以便它们可以安全地调用由 Keycloak 保护的网络上的其他服务。因此，我们需要为 grafana 创建一个 client","title":"使用keycloak作为grafana的OAuth2认证"},{"content":"背景与架构设计 网络中存在的挑战 挑战1：Kubernetes 的每个 Pod 拥有一个独立的 IP 地址。对外部访问则依赖 NodePort、LoadBalancer 或 Ingress，出于安全考虑不可能对每个 Pod 都暴露其 8849 端口 (NodePort) 或者使用 Ingress 配置大量的域名，这样无法连接到单独的某一个 Pod (通过Service)。 挑战2：网络环境是完全隔离的，用户无法通过 Pod IP 直接访问 Pod，所有的用户流量只能通过对应 IDC 的唯一入口进入。 挑战3：出于安全考虑不可能对每一个 Java 服务 (Pod) 在他的工作周期期间所有时间都暴露对应的 Jprofiler 端口，而仅仅想使用时可以连接，用后关闭。 JProfiler网络需求 JProfiler 可以通过加载 JVM 代理（libjprofilerti.so）与远程 GUI 通信，他的实现原理如下：\nJVM 启动时加载 JProfiler 代理，绑定到一个特定端口（如 8849）。 本地 JProfiler GUI 通过该端口连接到远程 JVM。 代理与 GUI 之间通过 TCP 传输性能数据。 在 Kubernetes 中，JProfiler 代理运行在 Pod 内部，但由于网络隔离和动态 IP，GUI 无法直接连接到 Pod。因此，我们需要一种机制将 JProfiler 的端口动态映射到可访问的外部端点。\n需要实现的架构 下图是基于上面提到的问题从而设计的集群架构\n图：最终网络架构设计和流量路线图 上图可以看到，用户办公网络和 IDC 网络是完全隔离的，办公网络到 IDC 只有唯一入口，这里成为 LB，所有的流量都必须经由固定的 LB 进入，灰色背景的 Pod 是代理 Pod, 他可以动态的将流量转发到对应的用户真实想请求的 Pod 中。\n解决方案 需要完成的步骤为\n让业务Pod 接入JProfiler agent：使用 Init Container 将 JProfiler 所需库安装到 Pod 的共享卷中。 需要一个代理软件，用于转发 TCP 流量，让其可以作为一个唯一入口，但这个入口使用并不频繁。 可以实现转发的触发的工具。 综上所述，这里无法使用 ingress/gateway 资源作为映射了。\n最终技术栈选择\n软件版本 版本 下载地址 Kubernetes集群 支持 1.16 以上的版本，并且不要对 Kubernetes 版本有依赖 JProfiler 14.0 docker pull cylonchau/jprofiler-agent:14_0 [2] pod-proxier 0.4 docker pull cylonchau/pod-proxier:0.4 表中 pod-proxier 是自实现的一个控制器，主要功能完成 Pod 映射 (通过 HTTP API), 底层使用了 haproxy 实现。\n实施步骤 安装pod-proxier 下面是 pod-proxier 的部署清单\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: pod-proxier-secret-reader rules: - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: pod-proxier-rolebinding subjects: - kind: ServiceAccount name: pod-proxier-secret-sa namespace: infra roleRef: kind: ClusterRole name: pod-proxier-secret-reader apiGroup: rbac.authorization.k8s.io --- apiVersion: v1 kind: ServiceAccount metadata: namespace: infra name: pod-proxier-secret-sa --- apiVersion: apps/v1 kind: Deployment metadata: name: pod-proxier namespace: infra spec: replicas: 1 selector: matchLabels: app: pod-proxier strategy: type: Recreate template: metadata: labels: app: pod-proxier spec: serviceAccount: pod-proxier-secret-sa # 这里使用了host网络，直接对架构图LB组件 hostNetwork: true # 这里要看你的网络设施，我这里使用了固定的Node nodeName: {your_pod_running_at_node} containers: - name: pod-proxier-ints image: cylonchau/pod-proxier-ints:v2.6.1 ports: - containerPort: 8404 hostPort: 8404 name: admin-port protocol: TCP - containerPort: 5555 name: data-plan-port protocol: TCP - containerPort: 8849 hostPort: 8849 name: entry-port protocol: TCP readinessProbe: httpGet: path: /stats port: 8404 initialDelaySeconds: 2 periodSeconds: 60 livenessProbe: httpGet: path: /stats port: 8404 initialDelaySeconds: 10 periodSeconds: 60 - name: pod-proxier image: cylonchau/pod-proxier:0.4 imagePullPolicy: IfNotPresent ports: - containerPort: 3343 securityContext: runAsNonRoot: true runAsUser: 1000 command: - \u0026#34;sh\u0026#34; - \u0026#34;-c\u0026#34; - | sleep 15 \u0026amp;\u0026amp; /apps/pod-proxier-gateway -v 5 --listen-port=3343 --listen-address 0.0.0.0 --default-map-port=8848 # 这个参数指定了从Pod中映射哪个端口，只有符合这个值的端口才能被映射 --jprofiler-port-name=jprofiler-port --resync-time=600 readinessProbe: httpGet: path: /health port: 3343 initialDelaySeconds: 10 periodSeconds: 2 livenessProbe: httpGet: path: /health port: 3343 initialDelaySeconds: 10 periodSeconds: 60 配置service（可选） 如果是公有云环境，可以通过公有云 LB 来映射\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 apiVersion: v1 kind: Service metadata: name: jprofiler-proxier namespace: infra labels: app: app: pod-proxier annotations: # 下面实例是aws上配置LB的参数 service.beta.kubernetes.io/aws-load-balancer-type: \u0026#34;nlb\u0026#34; service.beta.kubernetes.io/aws-load-balancer-internal: \u0026#34;true\u0026#34; service.beta.kubernetes.io/aws-load-balancer-subnets: subnet-0af9685c4effc54a3,subnet-03795506a78646acb # 下面实例是gcp上配置LB的参数 cloud.google.com/load-balancer-type: Internal spec: type: LoadBalancer ports: - port: 8849 targetPort: 8849 protocol: TCP name: haproxy - port: 8404 targetPort: 8404 protocol: TCP name: proxier\tselector: app: app: pod-proxier loadBalancerSourceRanges: - \u0026#34;0.0.0.0/0\u0026#34; note Helm部署可以参考：github.com/cylonchau/pod-proxier/helm Pod 启动 jprofiler agent 的方式 再次之前需要了解 Jprofiler 在 kubernetes 集群中运行的方法有：\n方法1：打包至业务Pod容器内（缺点：需要侵入业务Pod内。不方便）。 方法2：使用 Init Container 将 jprofiler 安装复制到 InitContainer 和将在 Pod 中启动的其他容器之间共享卷。 方法3：使用 sidecar 方式共享业务 Pod 与 sidecar 共享名称空间。 缺点： 涉及到容器共享进程空间，与 jprofiler-agent 机制问题，所以需要共享 /tmp 目录。 需要手动触发启动 jprofiler。 JProfiler finds JVMs via the \u0026ldquo;Attach API\u0026rdquo; that is part of the JDK. Have a look at the $TMP/hsperfdata_$USER directory, which is created by the hot spot JVM. It should contain PID files for all running JVMs. If not, delete the directory and restart all JVMs.\n这里选择使用 “方法2” ；制作 initContainer [1] 方式如下：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 FROM centos:7 # Switch to root USER 0 ENV \\ JPROFILER_DISTRO=\u0026#34;jprofiler_linux_14_1_1.tar.gz\u0026#34; \\ STAGING_DIR=\u0026#34;/jprofiler-staging\u0026#34; \\ HOME=\u0026#34;/jprofiler\u0026#34; LABEL \\ io.k8s.display-name=\u0026#34;JProfiler from ${JPROFILER_DISTRO}\u0026#34; RUN yum -y update \\ \u0026amp;\u0026amp; yum -y install ca-certificates curl \\ \u0026amp;\u0026amp; mkdir -p ${HOME} ${STAGING_DIR} \\ \u0026amp;\u0026amp; cd ${STAGING_DIR} \\ # curl is expected to be available; wget would work, too # Add User-Agent header to pretend to be a browser and avoid getting HTTP 404 response \u0026amp;\u0026amp; curl -v -OL \u0026#34;https://download-keycdn.ej-technologies.com/jprofiler/${JPROFILER_DISTRO}\u0026#34; -H \u0026#34;User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36\u0026#34; \\ \u0026amp;\u0026amp; tar -xzf ${JPROFILER_DISTRO} \\ \u0026amp;\u0026amp; rm -f ${JPROFILER_DISTRO} \\ # Eliminate the version-specific directory \u0026amp;\u0026amp; cp -R */* ${HOME} \\ \u0026amp;\u0026amp; rm -Rf ${STAGING_DIR} \\ \u0026amp;\u0026amp; chmod -R 0775 ${HOME} \\ \u0026amp;\u0026amp; yum clean all # chown and switch user as needed WORKDIR ${HOME} 使用 Init Container 为 Pod 注入 jprofiler agent 更改应用程序的部署配置如下\n如果尚未定义，请在 “spec.template.spec” 下添加 “volumes” 部分并定义一个新卷，用于给 initContainer 和 mainContainer 提供初始化 yaml 1 2 3 volumes: - name: jprofiler-share-tmp emptyDir: {} 如果尚未定义，请在 “spec.template.spec” 下添加 “initContainers”（Kubernetes 1.6+），并使用 JProfiler 的镜像定义 Init Container 将 Init container 中的文件复制到共享目录\nyaml 1 2 3 4 5 6 7 initContainers: - name: jprofiler-init image: \u0026lt;JPROFILER_IMAGE:TAG\u0026gt; command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;cp -R /jprofiler/ /tmp/\u0026#34;] volumeMounts: - name: jprofiler mountPath: \u0026#34;/tmp/jprofiler\u0026#34; 将 jprofiler-agent 添加到 JVM 启动参数。\nyaml 1 -agentpath:/jprofiler/bin/linux-x64/libjprofilerti.so=port=8849 完整的Deployment 示例\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 apiVersion: apps/v1 kind: Deployment metadata: name: jprofiler-test spec: replicas: 1 selector: matchLabels: app: jprofiler template: metadata: labels: app: jprofiler spec: volumes: - name: jprofiler-share-tmp emptyDir: {} shareProcessNamespace: true initContainers: - name: jprofiler-init image: cylonchau/jprofiler:14_0 command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;cp -R /jprofiler/ /tmp/\u0026#34;] volumeMounts: - name: jprofiler-share-tmp mountPath: \u0026#34;/tmp\u0026#34; containers: - name: sprintboot-test image:javaweb:3 imagePullPolicy: IfNotPresent volumeMounts: - name: jprofiler-share-tmp mountPath: /tmp env: - name: JAVA_OPTS # nowait 表示启动时不需要手动确认，如果不加会stuck到 jprofiler，使得业务容器不能启动 # -agentpath 必须加到java参数后，而不是 java -jar xxx -agentpath 这样 value: \u0026#34;-agentpath:/tmp/jprofiler/jprofiler14.0/bin/linux-x64/libjprofilerti.so=port=8849,nowait\u0026#34; command: - \u0026#34;java\u0026#34; - \u0026#34;-jar\u0026#34; - demo-0.0.1-SNAPSHOT.jar args: - --server.port=8085 一个完整的 Deployment 示例清单\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 apiVersion: apps/v1 kind: Deployment metadata: labels: app: springboot-example-for-jprofiler name: springboot-example-for-jprofiler namespace: default spec: replicas: 1 selector: matchLabels: app: springboot-example-for-jprofiler template: metadata: labels: app: springboot-example-for-jprofiler name: springboot-example-for-jprofiler spec: initContainers: - name: jprofiler-init image: cylonchau/jprofiler-agent:14_0 command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;cp -R /jprofiler/ /tmp/\u0026#34;] volumeMounts: - name: jprofiler-share-tmp mountPath: \u0026#34;/tmp\u0026#34; containers: - name: springboot-example-for-jprofiler image: cylonchau/javaweb-example:v0.0.1 imagePullPolicy: IfNotPresent volumeMounts: - name: jprofiler-share-tmp mountPath: /tmp env: - name: JAVA_OPTS value: -server - name: JAVA_TOOL_OPTIONS value: \u0026#34;-XX:MinRAMPercentage=25.0 -XX:MaxRAMPercentage=85.0 -XX:InitialRAMPercentage=25.0 -agentpath:/tmp/jprofiler/jprofiler14.0/bin/linux-x64/libjprofilerti.so=port=8849,nowait\u0026#34; command: - \u0026#34;java\u0026#34; - \u0026#34;-jar\u0026#34; - demo-0.0.1-SNAPSHOT.jar args: - --server.port=8085 livenessProbe: failureThreshold: 3 httpGet: path: /health port: 8080 scheme: HTTP initialDelaySeconds: 240 timeoutSeconds: 1 ports: - containerPort: 8080 protocol: TCP - containerPort: 8849 name: jprofiler-port protocol: TCP Service 相关 (可选)\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: v1 kind: Service metadata: labels: app: springboot-example-for-jprofiler name: springboot-example-for-jprofiler namespace: default spec: ports: - name: tcp-80 port: 80 protocol: TCP targetPort: 8080 selector: app: springboot-example-for-jprofiler type: ClusterIP 如何动态映射 Pod 需要连接 jprofiler-agent，在启动时作为进程启动，然后映射 jprofiler-agent 的 8849 端口。使用 pod-proixer，使用 haproxy 映射 Pod，并且提供 HTTP API，可以控制映射，与映射生效时间。\n--jprofiler-port-name，将会使用业务 Pod 配置的 Port name 来选择映射，而无需经过 ingress/gateway 。pod-proxier 本身有实现 Pod 映射的超时，所以不需要在去设计关闭映射部分了。\n例如\nbash 1 2 3 # @pod_name namespace_name/pod_name # @time 最大3小时 curl -XPOST \u0026#34;10.10.10.10:3343/api/v1/mapping?pod_name=apollo/apollo-portal-5f8c6587d-g2wkx\u0026amp;time=60000\u0026#34; 交付 在可以完成所有的映射操作后，还需要可以把这部分交付给用户去使用，而不是让用户自己拼接 HTTP URL 来完成映射，这里思路可以接入到内部平台，每个 Pod 实例后增加一个按钮 “启用Jprofiler映射”；或者接入到工单系统，在审批后实现自动映射，超时自动关闭映射。可以参考 Apache Airflow, Stackstorm 之类作业平台来完成。\nReference [1] How to Connect JProfiler to a JVM Running in Kubernetes Pod\n[2] JProfiler download\n","permalink":"https://www.161616.top/jprofiler-in-k8s-cluster/","summary":"背景与架构设计 网络中存在的挑战 挑战1：Kubernetes 的每个 Pod 拥有一个独立的 IP 地址。对外部访问则依赖 NodePort、LoadBalancer 或 Ingress，出于安全考虑不可能对每个 Pod 都暴露其 8849 端口 (NodePort) 或者使用 Ingress 配置大量的域名，这样无法连接到单独的某一个 Pod (通过Service)。 挑战2：网络环境是完全隔离的，用户无法通过 Pod IP 直接访问 Pod，所有的用户流量只能通过对应 IDC 的唯一入口进入。 挑战3：出于安全考虑不可能对每一个 Java 服务 (Pod) 在他的工作周期期间所有时间都暴露对应的 Jprofiler 端口，而仅仅想使用时可以连接，用后关闭。 JProfiler网络需求 JProfiler 可以通过加载 JVM 代理（libjprofilerti.so）与远程 GUI 通信，他的实现原理如下：\nJVM 启动时加载 JProfiler 代理，绑定到一个特定端口（如 8849）。 本地 JProfiler GUI 通过该端口连接到远程 JVM。 代理与 GUI 之间通过 TCP 传输性能数据。 在 Kubernetes 中，JProfiler 代理运行在 Pod 内部，但由于网络隔离和动态 IP，GUI 无法直接连接到 Pod。因此，我们需要一种机制将 JProfiler 的端口动态映射到可访问的外部端点。\n需要实现的架构 下图是基于上面提到的问题从而设计的集群架构","title":"在隔离网络中的多k8s集群中的实现JProfiler的自动化映射方案的设计与实现"},{"content":"什么是传感器 传感器 (Sensor) 是将外部系统和事件与 StackStorm 集成的一种方式。传感器是 Python 代码片段，它们要么定期轮询某些外部系统，要么被动等待入站事件，通常示例用于每隔一段时间去轮询某一个对象，然后他们将 Trigger 注入 StackStorm，可以通过规则进行匹配，以执行潜在的 Action。\nSensor 是用 Python 编写的，并且必须遵循 StackStorm 定义的传感器接口要求。\n什么是触发器 触发器 (Trigger) 是 StackStorm 中用于识别 StackStorm 的传入事件。Trigger 是类型（字符串）和可选参数（对象）的元组。编写 Rule 是为了与 Trigger 一起使用。Sensor 通常会记录 Trigger，但这并不是严格要求的。例如，有一个向 StackStorm 注册的通用Webhooks触发器，它不需要自定义传感器。\nStackstorm内置触发器 默认情况下，StackStorm 会发出一些内部 Trigger，您可以在规则中利用它们。这些触发器可以与非系统触发器区分开来，因为它们的前缀为 “st2”。\n下面包含每个资源的可用 Trigger 列表：\nAction\nReference Description Properties core.st2.generic.actiontrigger 封装 Action 执行完成的触发器 execution_id, status, start_timestamp, action_name, action_ref, runner_ref, parameters, result core.st2.generic.notifytrigger 通知触发器 execution_id, status, start_timestamp, end_timestamp, action_ref, runner_ref, channel, route, message, data core.st2.action.file_written 触发封装 Action，将文件写入磁盘 ref, file_path, host_info core.st2.generic.inquiry 触发器指示一个新的查询，表示已经进入 “pending” 状态 id, route Sensor\nReference Description Properties core.st2.sensor.process_spawn 触发器去指示传感器，进程开始启动 object core.st2.sensor.process_exit 触发器指示传感器，进程已经结束 object 如何创建一个 Sensor 创建传感器涉及编写 Python 脚本和定义 Sensor 的 YAML 元数据文件。以下是一个最小化 sensor 的结构示例。\n元数据文件：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 --- class_name: \u0026#34;SampleSensor\u0026#34; entry_point: \u0026#34;sample_sensor.py\u0026#34; description: \u0026#34;Sample sensor that emits triggers.\u0026#34; trigger_types: - name: \u0026#34;event\u0026#34; description: \u0026#34;An example trigger.\u0026#34; payload_schema: type: \u0026#34;object\u0026#34; properties: executed_at: type: \u0026#34;string\u0026#34; format: \u0026#34;date-time\u0026#34; default: \u0026#34;2014-07-30 05:04:24.578325\u0026#34; 相关 Python 脚本的结构，在脚本中，必须遵循该结构进行编写\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 # Copyright 2020 The StackStorm Authors. # Copyright 2019 Extreme Networks, Inc. # # Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from st2reactor.sensor.base import Sensor class SampleSensor(Sensor): \u0026#34;\u0026#34;\u0026#34; * self.sensor_service - provides utilities like - get_logger() - returns logger instance specific to this sensor. - dispatch() for dispatching triggers into the system. * self._config - contains parsed configuration that was specified as config.yaml in the pack. \u0026#34;\u0026#34;\u0026#34; def setup(self): # 该方法系统仅调用一次，可以设置连接外部系统的内容到这里 pass def run(self): # 该方法是 sensor 运行的方法 # 这由系统调用一次。 #（如果您想定期睡觉并保持与外部系统交互，您将从 PollingSensor 继承。） # 例如：例如，个简单的flask应用程序。您可以在此处运行 Flask 应用程序。 # 您可以使用sensor_service 调度触发器，如下所示： # 您可以将触发器称为dict # { \u0026#34;name\u0026#34;: ${trigger_name}, \u0026#34;pack\u0026#34;: ${trigger_pack} } # 或者只是简单地作为字符串引用。 # i.e. dispatch(${trigger_pack}.${trigger_name}, payload) # E.g.: dispatch(\u0026#39;examples.foo_sensor\u0026#39;, {\u0026#39;k1\u0026#39;: \u0026#39;stuff\u0026#39;, \u0026#39;k2\u0026#39;: \u0026#39;foo\u0026#39;}) # trace_tag 是想要与dispatch的 TriggerInstance 关联的标签， # 通常，trace_tag 是唯一的并且是对外部事件的引用。 pass def cleanup(self): # 当 st2 系统宕机时调用此函数。您可以执行清理操作，例如 # 在此关闭与外部系统的连接。 pass def add_trigger(self, trigger): # 当创建触发器时调用此方法 pass def update_trigger(self, trigger): # 当触发器更新时调用此方法 pass def remove_trigger(self, trigger): # 删除触发器时调用此方法 pass 上述是一个最简单的 Sensor 示例。\n您的 Sensor 应生成 Python 字典形式的Trigger：\npython 1 2 3 4 5 trigger = \u0026#39;pack.name\u0026#39; payload = { \u0026#39;executed_at\u0026#39;: \u0026#39;2014-08-01T00:00:00.000000Z\u0026#39; } trace_tag = external_event_id Sensor 通过使用实例化时传递到 Sensor 的 sensor_service 来注入此类 Trigger。\npython 1 self.sensor_service.dispatch(trigger=trigger, payload=payload, trace_tag=trace_tag) 如果您想要一个定期轮询外部系统的传感器，您可以使用 PollingSensor 而不是 Sensor 作为基类。\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 # Copyright 2020 The StackStorm Authors. # Copyright 2019 Extreme Networks, Inc. # # Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # 这里引入的是 PollingSensor，而不是Sensor from st2reactor.sensor.base import PollingSensor class SamplePollingSensor(PollingSensor): \u0026#34;\u0026#34;\u0026#34; * self.sensor_service - provides utilities like get_logger() for writing to logs. dispatch() for dispatching triggers into the system. * self._config - contains configuration that was specified as config.yaml in the pack. * self._poll_interval - indicates the interval between two successive poll() calls. \u0026#34;\u0026#34;\u0026#34; def setup(self): # Setup stuff goes here. For example, you might establish connections # to external system once and reuse it. This is called only once by the system. pass def poll(self): # 该方法是 sensor 运行的方法 # 这由系统在每个周期与性能一次，self._poll_interval。 # 例如：假设您想要查询 ec2 并获取有关实例的运行状况信息： some_data = aws_client.get(\u0026#39;\u0026#39;) payload = self._to_payload(some_data) # _to_triggers 是您编写的用于将数据格式转换为标准 python 字典的东西。 # 这应该遵循为 Trigger 注册的有效负载架构。 self.sensor_service.dispatch(trigger, payload) # 您可以将触发器称为 dict dict = { \u0026#34;name\u0026#34;: \u0026#34;${trigger_name}\u0026#34;, \u0026#34;pack\u0026#34;: \u0026#34;${trigger_pack}\u0026#34; } # 或者简单引用一个字符串，i.e. dispatch(${trigger_pack}.${trigger_name}, payload) # 再例如 dispatch(\u0026#39;examples.foo_sensor\u0026#39;, {\u0026#39;k1\u0026#39;: \u0026#39;stuff\u0026#39;, \u0026#39;k2\u0026#39;: \u0026#39;foo\u0026#39;}) # trace_tag 是想要与dispatch的 TriggerInstance 关联的标签， # 通常，trace_tag 是唯一的并且是对外部事件的引用。 pass def cleanup(self): # 当 st2 系统宕机时调用此函数。您可以执行清理操作，例如 # 在此关闭与外部系统的连接。 pass def add_trigger(self, trigger): # 当创建触发器时调用此方法 pass def update_trigger(self, trigger): # 当触发器更新时调用此方法 pass def remove_trigger(self, trigger): # 删除触发器时调用此方法 pass 上述是一个 Poll Sensor 代码部分是结构的，setup 是装载时执行，poll 是在每个 interval 执行探测，这里的机制是当完成了派发后是不会第二次派发，这里做法是维护了一个列表到类中。\n例如\nyaml 1 2 3 aa2233 ACG-3612 {\u0026#39;ACG-3612\u0026#39;: \u0026lt;JIRA Issue: key=\u0026#39;ACG-3612\u0026#39;, id=\u0026#39;212268\u0026#39;\u0026gt;} 注：轮询传感器 (Polling Sensors) 还需要元数据文件中的 poll_interval 参数。这定义了调用 poll() 方法的频率（以秒为单位）。\nSersor如何运行 每个传感器作为单独的进程运行。 st2sensorcontainer 启动 sensor_wrapper.py ，它将您的 Sensor 类（例如上面的SampleSensor 或 SamplePollingSenso r）包装在 st2reactor.container.sensor_wrapper.SensorWrapper 中。\nSensor Service 正如您在上面的示例中看到的，sensor_service 在实例化时被传递给每个传感器类构造函数。\n传感器服务 (Sensor Service) 通过公共方法向 Sensor 提供不同的服务。最重要的一种 dispatch 方法是允许 Sensor 将 Trigger 注入系统的方法。所有公共方法描述如下：\n常用操作，Common Operations 数据存储管理操作 Datastore Management Operations Common Operations dispatch 调度：此方法允许传感器将触发器注入系统\npython 1 dispatch(trigger, payload, trace_tag) 例如：\npython 1 2 3 4 5 6 7 trigger = \u0026#39;pack.name\u0026#39; payload = { \u0026#39;executed_at\u0026#39;: \u0026#39;2014-08-01T00:00:00.000000Z\u0026#39; } trace_tag = uuid.uuid4().hex self.sensor_service.dispatch(trigger=trigger, payload=payload, trace_tag=trace_tag) get_logger 此方法允许 Sensor 实例检索特定于该传感器的记录器实例。\nyaml 1 get_logger(name) 例如：\npython 1 2 self._logger = self.sensor_service.get_logger(name=self.__class__.__name__) self._logger.debug(\u0026#39;Polling 3rd party system for information\u0026#39;) Datastore Management Operations 除了触发器注入之外，传感器服务还提供读取和操作数据存储的功能。\n每个传感器都有一个本地命名空间，默认情况下，所有数据存储操作都对该 Sensor “本地命名空间” 中的键进行操作。如果要对“全局命名空间”进行操作，则需要将参数传递 local=False 给数据存储操作方法。\n除其他原因外，如果想在传感器运行之间保留临时数据，此功能非常有用。\nTwitterSensor 就是此功能的一个很好的例子。 Twitter 传感器在每次轮询后都会在数据存储中保留最后处理的推文的 ID。这样，如果 Trigger 重新启动或崩溃，传感器可以从中断处恢复，而无需向系统注入重复的 Trigger。\nlist_values python 1 list_values(local=True, prefix=None) 该方法允许列出数据存储中的值。您还可以通过将 prefix 参数传递给方法来按键名称前缀（键名称开头）进行过滤：\npython 1 2 3 4 5 kvps = self.sensor_service.list_values(local=False, prefix=\u0026#39;cmdb.\u0026#39;) for kvp in kvps: print(kvp.name) print(kvp.value) get_value python 1 get_value(name, local=True, decrypt=False) 此方法允许您从数据存储中检索单个值：\npython 1 2 kvp = self.sensor_service.get_value(\u0026#39;cmdb.api_host\u0026#39;) print(kvp.name) set_value python 1 set_value(name, value, ttl=None, local=True, encrypt=False) 该方法允许在数据存储中存储设置一个值。您还可以选择指定存储值的生存时间 (TTL)：\npython 1 2 last_id = 12345 self.sensor_service.set_value(name=\u0026#39;last_id\u0026#39;, value=str(last_id)) Secret 值可以在存储中中加密：\npython 1 2 ma_password = \u0026#39;Sup3rS34et\u0026#39; self.sensor_service.set_value(name=\u0026#39;ma_password\u0026#39;, value=ma_password, encrypt=True) delete_value python 1 delete_value(name, local=True) 该方法允许从存储中删除现有值。如果未找到值，此方法将返回 False，否则返回 True。\npython 1 self.sensor_service.delete_value(name=\u0026#39;my_key\u0026#39;) 定义一个 Sersor 例如我们需要制作一个简单 Sensor 的工作示例，每 60 秒注入一次触发器。\n需要注意的部分：\nSensor 的 Python 的类必需继承 Sensor 或 PollingSensor类，这个由需求而定，必须实现 setup, poll, dispatch等方法\n如有需求，例如变量声明，也可以重写 _init_ 类 setup方法，用于初始化示例需要的数据或状态，例如连接三方系统的配置信息，该方法只执行一次 Sensor runtime 會把实例化并保持运行，按 poll_interval 設置周期去运行 poll 方法 Poll方法是真实执行任务的部分，可在 poll 方法中更新示例持有的数据或状态，判断数据是否匹配，\n然后运行 disptach； 按需派发数据，dispatch方法会派发数据，把数据派发给 _trigger_ref\n_trigger_ref 是 Sensor 设置的 trigger type，也就是 RabbitMQ 的 Queue 名称 元数据定义 yaml 1 2 3 4 5 6 7 8 9 10 --- class_name: \u0026#34;HelloSensor\u0026#34; entry_point: \u0026#34;sensor1.py\u0026#34; description: \u0026#34;Test sensor that emits triggers.\u0026#34; trigger_types: - name: \u0026#34;event1\u0026#34; description: \u0026#34;An example trigger.\u0026#34; payload_schema: type: \u0026#34;object\u0026#34; Python 代码部分 python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 # Copyright 2020 The StackStorm Authors. # Copyright 2019 Extreme Networks, Inc. # # Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import eventlet from st2reactor.sensor.base import Sensor class HelloSensor(Sensor): def __init__(self, sensor_service, config): super(HelloSensor, self).__init__(sensor_service=sensor_service, config=config) self._logger = self.sensor_service.get_logger(name=self.__class__.__name__) self._stop = False def setup(self): pass def run(self): while not self._stop: self._logger.debug(\u0026#34;HelloSensor dispatching trigger...\u0026#34;) count = self.sensor_service.get_value(\u0026#34;hello_st2.count\u0026#34;) or 0 payload = {\u0026#34;greeting\u0026#34;: \u0026#34;Yo, StackStorm!\u0026#34;, \u0026#34;count\u0026#34;: int(count) + 1} self.sensor_service.dispatch(trigger=\u0026#34;hello_st2.event1\u0026#34;, payload=payload) self.sensor_service.set_value(\u0026#34;hello_st2.count\u0026#34;, payload[\u0026#34;count\u0026#34;]) eventlet.sleep(60) def cleanup(self): self._stop = True # Methods required for programmable sensors. def add_trigger(self, trigger): pass def update_trigger(self, trigger): pass def remove_trigger(self, trigger): pass Sensor的运行与调试 运行 一旦完成传感器的编写，可以使用以下步骤来首次运行传感器：\n将传感器 Python 文件和 元数据文件 放入 default 包中的 /opt/stackstorm/packs/default/sensors/ ；或者您也可以根据包结构，创建出自定义包并将传感器元件放置在那里 ( /opt/stackstorm/packs/ ) 使用 st2ctl 注册传感器 。注意传感器注册中的任何错误，一旦注册时出现错误，请修复错误并使用 重新注册 。 bash 1 st2ctl reload --register-all 如果注册成功，传感器将自动运行。 调试 在编写时，很多时候需要调试 Sensor 的运行，而由于环境问题，我们无法做到正常Python程序的调试步骤，必须遵循 Stackstorm 的调试方式。\n如果只想运行包中的单个传感器并且该传感器已注册，则可以使用 st2sensorcontainer 来仅运行该单个传感器：\nbash 1 sudo /opt/stackstorm/st2/bin/st2sensorcontainer --config-file=/etc/st2/st2.conf --sensor-ref=pack.SensorClassName 例如：\nbash 1 sudo /opt/stackstorm/st2/bin/st2sensorcontainer --config-file=/etc/st2/st2.conf --sensor-ref=git.GitCommitSensor 示例：Jira服务台 python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 # See ./requirements.txt for requirements. import os import urllib3 import json from jira.client import JIRA from st2reactor.sensor.base import PollingSensor class XinMangSensorForPodMapTicket(PollingSensor): \u0026#39;\u0026#39;\u0026#39; Sensor will monitor for any new projects created in JIRA and emit trigger instance when one is created. \u0026#39;\u0026#39;\u0026#39; def __init__(self, sensor_service, config=None, poll_interval=5): super(XinMangSensorForPodMapTicket, self).__init__(sensor_service=sensor_service, config=config, poll_interval=poll_interval) self._jira_url = None self._config = { \u0026#34;url\u0026#34;: \u0026#34;https://jira.ticket.com\u0026#34;, \u0026#34;auth_method\u0026#34;: \u0026#34;basic\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;jira_automation\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;jira123\u0026#34;, \u0026#34;poll_interval\u0026#34;: 30, \u0026#34;verify\u0026#34;: False } # The Consumer Key created while setting up the \u0026#34;Incoming Authentication\u0026#34; in # JIRA for the Application Link. self._consumer_key = u\u0026#39;\u0026#39; self._rsa_key = None self._jira_client = None self._access_token = u\u0026#39;\u0026#39; self._access_secret = u\u0026#39;\u0026#39; self._projects_available = [\u0026#34;SABP\u0026#34;] self._poll_interval = 30 self._project = \u0026#34;SABP\u0026#34; self._issues_in_project = None self._customer_request_type = \u0026#34;创建Pod映射\u0026#34; self._issue_status = \u0026#34;IN PROGRESS\u0026#34; self._jql_query = None self._trigger_name = \u0026#39;issues_tracker_for_pod_map_ticket\u0026#39; self._trigger_pack = \u0026#39;jira\u0026#39; self._trigger_ref = \u0026#39;.\u0026#39;.join([self._trigger_pack, self._trigger_name]) urllib3.disable_warnings() def _read_cert(self, file_path): with open(file_path) as f: return f.read() def setup(self): self._jira_url = self._config[\u0026#39;url\u0026#39;] auth_method = self._config[\u0026#39;auth_method\u0026#39;] if auth_method == \u0026#39;oauth\u0026#39;: rsa_cert_file = self._config[\u0026#39;rsa_cert_file\u0026#39;] if not os.path.exists(rsa_cert_file): raise Exception(\u0026#39;Cert file for JIRA OAuth not found at %s.\u0026#39; % rsa_cert_file) self._rsa_key = self._read_cert(rsa_cert_file) self._poll_interval = self._config.get(\u0026#39;poll_interval\u0026#39;, self._poll_interval) oauth_creds = { \u0026#39;access_token\u0026#39;: self._config[\u0026#39;oauth_token\u0026#39;], \u0026#39;access_token_secret\u0026#39;: self._config[\u0026#39;oauth_secret\u0026#39;], \u0026#39;consumer_key\u0026#39;: self._config[\u0026#39;consumer_key\u0026#39;], \u0026#39;key_cert\u0026#39;: self._rsa_key } self._jira_client = JIRA(options={\u0026#39;server\u0026#39;: self._jira_url}, oauth=oauth_creds) elif auth_method == \u0026#39;basic\u0026#39;: basic_creds = (self._config[\u0026#39;username\u0026#39;], self._config[\u0026#39;password\u0026#39;]) self._jira_client = JIRA(options={\u0026#39;server\u0026#39;: self._jira_url, \u0026#39;verify\u0026#39;: self._config[\u0026#39;verify\u0026#39;]},basic_auth=basic_creds) #self._jira_client = JIRA(options={\u0026#39;server\u0026#39;: self._jira_url},basic_auth=basic_creds) else: msg = (\u0026#39;You must set auth_method to either \u0026#34;oauth\u0026#34;\u0026#39;, \u0026#39;or \u0026#34;basic\u0026#34; your jira.yaml config file.\u0026#39;) raise Exception(msg) self._jql_query = \u0026#39;project=\u0026#34;%s\u0026#34; and \u0026#34;Customer Request Type\u0026#34;=\u0026#34;%s\u0026#34; and status=\u0026#34;%s\u0026#34;\u0026#39; % (self._project, self._customer_request_type, self._issue_status) all_issues = self._jira_client.search_issues(self._jql_query, maxResults=None) self._issues_in_project = {issue.key: issue for issue in all_issues} def poll(self): self._detect_new_issues() def cleanup(self): pass def add_trigger(self, trigger): pass def update_trigger(self, trigger): pass def remove_trigger(self, trigger): pass def _detect_new_issues(self): new_issues = self._jira_client.search_issues(self._jql_query, maxResults=50, startAt=0) for issue in new_issues: # issue 满足需求的工单 print(self._issues_in_project) # 将服务台任务单保留到这个列表中，保证不会重复派发 if issue.key not in self._issues_in_project: # 记录下未派发的任务，并派发 self._dispatch_issues_trigger(issue) self._issues_in_project[issue.key] = issue def _dispatch_issues_trigger(self, issue): trigger = self._trigger_ref payload = {} payload[\u0026#39;project\u0026#39;] = self._project payload[\u0026#39;id\u0026#39;] = issue.id payload[\u0026#39;expand\u0026#39;] = issue.raw.get(\u0026#39;expand\u0026#39;, \u0026#39;\u0026#39;) payload[\u0026#39;issue_key\u0026#39;] = issue.key payload[\u0026#39;issue_url\u0026#39;] = issue.self payload[\u0026#39;issue_browse_url\u0026#39;] = self._jira_url + \u0026#39;/browse/\u0026#39; + issue.key fields = dict() fields = issue.raw.get(\u0026#39;fields\u0026#39;, {}) fields_dict = dict() print(fields[\u0026#39;customfield_19402\u0026#39;].strip()) fields_dict[\u0026#39;pod_name\u0026#39;] = fields[\u0026#39;customfield_19401\u0026#39;].strip() fields_dict[\u0026#39;start_time\u0026#39;] = fields[\u0026#39;customfield_19402\u0026#39;].strip() fields_dict[\u0026#39;end_time\u0026#39;] = fields[\u0026#39;customfield_19403\u0026#39;].strip() fields_dict[\u0026#39;k8s_cluster\u0026#39;] = fields[\u0026#39;customfield_19404\u0026#39;][\u0026#39;value\u0026#39;] fields_dict[\u0026#39;reporter\u0026#39;] = fields[\u0026#39;reporter\u0026#39;][\u0026#39;key\u0026#39;] payload[\u0026#39;fields\u0026#39;] = fields_dict if fields_dict[\u0026#39;reporter\u0026#39;] != \u0026#39;a@gmail.com\u0026#39;: self._sensor_service.dispatch(trigger, payload) Reference ​[1] Sensors and Triggers\n​[2] How many do you need? - Argo CD Architectures Explained\n","permalink":"https://www.161616.top/stackstorm-sensors/","summary":"什么是传感器 传感器 (Sensor) 是将外部系统和事件与 StackStorm 集成的一种方式。传感器是 Python 代码片段，它们要么定期轮询某些外部系统，要么被动等待入站事件，通常示例用于每隔一段时间去轮询某一个对象，然后他们将 Trigger 注入 StackStorm，可以通过规则进行匹配，以执行潜在的 Action。\nSensor 是用 Python 编写的，并且必须遵循 StackStorm 定义的传感器接口要求。\n什么是触发器 触发器 (Trigger) 是 StackStorm 中用于识别 StackStorm 的传入事件。Trigger 是类型（字符串）和可选参数（对象）的元组。编写 Rule 是为了与 Trigger 一起使用。Sensor 通常会记录 Trigger，但这并不是严格要求的。例如，有一个向 StackStorm 注册的通用Webhooks触发器，它不需要自定义传感器。\nStackstorm内置触发器 默认情况下，StackStorm 会发出一些内部 Trigger，您可以在规则中利用它们。这些触发器可以与非系统触发器区分开来，因为它们的前缀为 “st2”。\n下面包含每个资源的可用 Trigger 列表：\nAction\nReference Description Properties core.st2.generic.actiontrigger 封装 Action 执行完成的触发器 execution_id, status, start_timestamp, action_name, action_ref, runner_ref, parameters, result core.st2.generic.notifytrigger 通知触发器 execution_id, status, start_timestamp, end_timestamp, action_ref, runner_ref, channel, route, message, data core.","title":"StackStorm自动化 - Sensor"},{"content":"什么是包 包 “pack” 是扩展 StackStorm 的集成和自动化的部署单元。通常， pack 是沿着服务或产品边界组织的，例如 AWS、Docker、Sensu 等。 pack 包含Actions、Workflows、Rules、 Sensors和Aliases。StackStorm 内容始终是 pack 的一部分，因此了解如何创建 pack 并使用它们非常重要。\n一些 pack 扩展了 StackStorm 以将其与外部系统集成，例如 AWS，GitHub，JIRA。我们称它们为“集成 pack ”。有些 pack 捕获自动化模式：这类 pack 含特定自动化过程的 workflow, Rule 和 Action - 例如st2 演示 pack 。我们称它们为“自动化 pack ”。这种命名主要是一种约定：StackStorm 本身对两者没有区别。\n任何使用该 pack 所针对的服务的人都可以共享和重用集成 pack 。您可以在StackStorm Exchange找到许多这样的示例。自动化 pack 通常是特定于站点的，并且在特定团队或公司之外几乎没有用处；它们通常在内部共享。\n总结：Packs是StackStorm中组织工作流、动作和传感器的方式。它们是一组相关的动作、工作流和传感器的集合，通常用于实现特定的自动化任务或集成。\n包管理 StackStorm pack 通过命令进行管理：将为您提供有用的概述。st2 pack \u0026lt;...\u0026gt; / st2 pack -h 有些（例如core 基本 StackStorm action）是随 StackStorm 预装的。所有其他 pack 都需要您安装。幸运的是，这很容易！ list和get是获取有关本地 pack 信息的主要命令：\nbash 1 2 3 4 5 # List all installed packs st2 pack list # Get detailed information about an installed pack st2 pack get core 使用 StackStorm 和 pack 管理 Action 时，所有 pack 都会安装到系统 pack 目录中，默认为 /opt/stackstorm/packs/.\n除了可以安装本地的包，还可以安装社区维护的包，已有一百多个 StackStorm pack 您可以在 exchange.stackstorm.org 浏览包列表，或通过 CLI 搜索 pack 索引，st2 search 需要联网使用，大部分 stackstorm 包 已经不再更新，使用较多的为 jira, 定时任务，脚本类。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 st2 pack searchst2 pack show # Search query is applied across all pack parameters. # It will search through pack names: st2 pack search sensu # And keywords: st2 pack search monitoring # And description (use quotes for multi-word search): st2 pack search \u0026#34;Amazon Web Services\u0026#34; # And even pack author: st2 pack search \u0026#34;Jon Middleton\u0026#34; # Show an index entry for the pack # with the exact name match st2 pack show sensu 安装包 从 stackstorm 官方仓库安装 stackstorm pack 安装起来很简单\nbash 1 2 3 4 5 # Install from the Exchange by pack name st2 pack install sensu # You can also install multiple packs: st2 pack install datadog github 此命令将会从GitHub 上的 StackStorm Exchange 仓库下载包，将它们放在 下/opt/stackstorm/packs，并向 StackStorm 注册它们。\n本质上，用户可以同样轻松地使用名从 git 安装属于自己的包。\nbash 1 2 3 4 5 6 7 8 # Install your own pack from git using http(s) st2 pack install https://github.com/emedvedev/chatops_tutorial # Install your own pack from git using ssh st2 pack install git@github.com/emedvedev/chatops_tutorial # Install your own pack using gitlab URL (added in release 3.4) st2 pack install gitlab@gitlab.com:example/examplepack 默认情况下，将安装最新版本的 pack ，但您可以指定特定版本、分支、tag，甚至提交哈希。只需使用下面命令\nbash 1 2 3 4 5 6 7 8 # Fetch a specific commit st2 pack install cloudflare=776b9a4 # Or a version tag st2 pack install cloudflare=0.1.0 # Or a branch st2 pack install https://github.com/emedvedev/chatops_tutorial=testing 也可以从现有的本地目录安装 pack ：\nbash 1 2 # Install a pack from \u0026#39;/home/stanley/bitcoin\u0026#39; dir st2 pack install file:///home/stanley/bitcoin 需要注意的一点是，从 git 存储库目录安装 pack 将仅安装最新提交，并忽略对文件的任何后续未提交更改。在已安装的 pack 上运行会将其替换为请求的版本，或者如果未指定版本，则升级到最新版本。您的配置文件不会被覆盖，因此您可以轻松恢复到旧版本，但对于生产部署，我们建议始终指定版本，防止 st2 pack install latest\n包依赖 依赖是从 StackStorm 3.2 开始的新功能！\n如果您的包使用其他包中的 Action，您可以在文件 dependencies 的 部分中指定它们 pack.yaml，StackStorm 将在安装您的包时自动安装它们。\n与使用子命令类似，您可以仅使用名称来引用 StackStorm Exchange 中的包，也可以指定包的 Git 存储库 URL。您还可以使用相同的语法来安装特定版本、标签或分支：st2 pack install\nyaml 1 2 3 4 dependencies: - excel - powerpoint=0.2.2 - https://github.com/StackStorm/stackstorm-ms.git 如果存在依赖性冲突，子命令可能会出错，而不会安装任何包。如果您想强制安装包而不安装其依赖项，您可以使用以下标志：\nbash 1 st2 pack install --skip-dependencies my-custom-pack 包卸载 卸载包使用 st2 pack remove：\nbash 1 st2 pack remove sensu 配置包 集成一个包（pack）通常需要针对您的环境进行配置。例如，您需要指定 SMTP 服务器以使用电子邮件 pack 、指定 puppet master URL 以使用 Puppet pack ，或者指定 OpenStack 的 Keystone 端点和租户凭证。\n通常大多数需要配置的 pack 都可以交互配置：\nbash 1 st2 pack config \u0026lt;pack_name\u0026gt; 系统将提示您在交互式工具中输入配置参数，其中包含说明、建议和默认值。在保存之前，您还会被要求在文本编辑器中验证您的最终配置文件；它是可选的，大多数包不需要超过两个或三个字段。生成的文件将被放入 /opt/stackstorm/configs/\u0026lt;pack\u0026gt;.yaml 并加载。\n注意：StackStorm 将 pack 配置加载到 MongoDB 中。当您使用 st2 pack config 时，它会自动加载 。但是如果您手动编辑 pack 配置，或使用配置管理工具来管理这些文件，则必须告诉StackStorm 加载更新的配置。\n更新包配置\nbash 1 sudo st2ctl reload --register-configs 覆盖包的默认值 安装包时，资源的状态是从包中的元数据文件中获取的。有时，在安装社区包时，您可能不希望启用所有资源，例如您只想使用 Action 的子集，或者想要禁用传感器。\n在 StackStorm 3.7.0 之前，可以通过以下方式更改此状态：\n使用 StackStorm API 禁用资源。然而，在重新安装或包重新安装时，这将被忘记。st2ctl reload 手动更改元数据文件。这将在升级时丢失，并且无法轻易跟踪。 在StackStorm 3.7.0中，我们引入了覆盖功能，以便包资源的元数据可以被配置文件覆盖。这将始终在重新加载或包安装时被读取。 ST2 API 仍然允许您覆盖此设置，但与以前一样，StackStorm API 为启用/禁用资源所做的任何更改在重新加载或重新安装时都会被忘记。\n覆盖工具当前仅限于允许仅针对以下类型的资源覆盖启用的属性：Action、Alias、Rule 和 Sensor。它由目录 /opt/stackstorm/overrides 控制 。\n安装包或重载时，资源状态按如下方式管理：\n状态是从位于 中的包的资源元数据文件中读取的/opt/stackstorm/packs/\u0026lt;packname\u0026gt;。这些是从包的相关存储库下载的，例如 GIT, StackStorm-Exchange。 如果 /opt/stackstorm/overrides/_global.yaml 存在，这回应用这个包的默认值或特定于资源的覆盖。允许 _global.yaml 您指定特定资源类型的默认状态，例如禁用所有 Sensor。 _global.yaml 的格式如下（设置为禁用所有内容）： text 1 2 3 4 5 6 7 8 9 10 11 12 13 --- sensors: defaults: enabled: false actions: defaults: enabled: false aliases: defaults: enabled: false rules: defaults: enabled: false 如果发生了覆盖，则受影响的资源数量将在 Output 中输出，例如：st2ctl reload\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 $ st2ctl reload --register-all Registering content...[flags = --config-file /etc/st2/st2.conf --register-all] 2022-02-07 12:56:43,694 INFO [-] Connecting to database \u0026#34;st2\u0026#34; @ \u0026#34;127.0.0.1:27017\u0026#34; as user \u0026#34;None\u0026#34;. 2022-02-07 12:56:43,704 INFO [-] Successfully connected to database \u0026#34;st2\u0026#34; @ \u0026#34;127.0.0.1:27017\u0026#34; as user \u0026#34;None\u0026#34;. 2022-02-07 12:56:44,254 INFO [-] ========================================================= 2022-02-07 12:56:44,254 INFO [-] ############## Registering triggers ##################### 2022-02-07 12:56:44,254 INFO [-] ========================================================= 2022-02-07 12:56:44,549 INFO [-] Registered 1 triggers. 2022-02-07 12:56:44,549 INFO [-] ========================================================= 2022-02-07 12:56:44,549 INFO [-] ############## Registering sensors ###################### 2022-02-07 12:56:44,549 INFO [-] ========================================================= 2022-02-07 12:56:44,832 INFO [-] Registered 9 sensors. 2022-02-07 12:56:44,832 INFO [-] 7 sensors had their metadata overridden. 示例：禁用一个包中的所有Sersor 如果要禁用一个包中的所有传感器，我们将创建一个 /opt/stackstorm/overrides/\u0026lt;packname\u0026gt;.yaml 文件，包含以下内容的 sensor：\nyaml 1 2 3 4 --- sensors: defaults: enabled: false 示例：禁用所有包中的所有传感器（排除某个包） 相反，如果我们想覆盖除单个包之外的所有传感器，那么我们将创建一个/opt/stackstorm/overrides/_global.yaml来禁用所有传感器：\nbash 1 2 3 4 --- sensors: defaults: enabled: false 之后创建 /opt/stackstorm/overrides/\u0026lt;packname\u0026gt;.yaml，为所需的包启用 Sensor\nbash 1 2 3 4 --- sensors: defaults: enabled: true 示例：禁用包的Action，但排除某些Action bash 1 2 3 4 5 6 7 8 9 --- actions: defaults: enabled: false exceptions: action1: enabled: true action2: enabled: true 示例：禁用包中某一个Rule yaml 1 2 3 4 rules: exceptions: rule1: enabled: false 虚拟环境 在 StackStorm 中，所有包的所有任务都是基于Python虚拟环境运行，以便区隔及管理各个包的Python套件。系统自带包使用系统自带的虚拟环境，位于目录：/opt/stackstorm/st2/\n通常，自建的包则需在/opt/stackstorm/virtualenvs目录下创建跟包同样名称的Python虚拟环境。\nbash 1 2 3 4 5 6 7 8 9 $ tree virtualenvs/ -L 2 virtualenvs/ ├── crontab │ ├── bin │ ├── include │ ├── lib │ ├── lib64 -\u0026gt; lib │ └── pyvenv.cfg ..... 在包运行时，需要提前为包含 Python Action/Sensor 的每个包创建一个 Python 虚拟环境 /opt/stackstorm/virtualenv，Python 依赖将通过 pip -rrequirements.txt 安装在 virtualenv 中，这部分是 Stackstorm 自动处理。\nStackStorm v3.4.0 中删除了对 Python 2 的支持。请考虑更新任何仅适用于 Python 2 的包以与 Python 3 配合使用。\n如果在 offline 环境中，需要创建一个自定义包，那么需要手动创建对应的虚拟环境，例如，在/opt/stackstorm/virtualenvs目录运行下列命令创建包的虚拟环境。也可以使用 ln 命令创建文件连接。x\nbash 1 $ python -m venv package_name ","permalink":"https://www.161616.top/stackstorm-pack/","summary":"什么是包 包 “pack” 是扩展 StackStorm 的集成和自动化的部署单元。通常， pack 是沿着服务或产品边界组织的，例如 AWS、Docker、Sensu 等。 pack 包含Actions、Workflows、Rules、 Sensors和Aliases。StackStorm 内容始终是 pack 的一部分，因此了解如何创建 pack 并使用它们非常重要。\n一些 pack 扩展了 StackStorm 以将其与外部系统集成，例如 AWS，GitHub，JIRA。我们称它们为“集成 pack ”。有些 pack 捕获自动化模式：这类 pack 含特定自动化过程的 workflow, Rule 和 Action - 例如st2 演示 pack 。我们称它们为“自动化 pack ”。这种命名主要是一种约定：StackStorm 本身对两者没有区别。\n任何使用该 pack 所针对的服务的人都可以共享和重用集成 pack 。您可以在StackStorm Exchange找到许多这样的示例。自动化 pack 通常是特定于站点的，并且在特定团队或公司之外几乎没有用处；它们通常在内部共享。\n总结：Packs是StackStorm中组织工作流、动作和传感器的方式。它们是一组相关的动作、工作流和传感器的集合，通常用于实现特定的自动化任务或集成。\n包管理 StackStorm pack 通过命令进行管理：将为您提供有用的概述。st2 pack \u0026lt;...\u0026gt; / st2 pack -h 有些（例如core 基本 StackStorm action）是随 StackStorm 预装的。所有其他 pack 都需要您安装。幸运的是，这很容易！ list和get是获取有关本地 pack 信息的主要命令：","title":"StackStorm自动化 - 包"},{"content":"StackStorm 使用 Rules 和 Workflows 来捕获操作模式并进行自动化。rule将 Triggers 映射到 Actions（或 Workflow），应用匹配条件( Criteria)，并将 Triggers payloads 映射到 Actions 的 Input。\n注意\nrule不按预期工作吗？请查看rule故障排除文档。其中介绍了rule测试、检查执行、记录和故障排除等内容。\nRule 的配置结构 stackstorm 中的 Rule 是以 YAML 格式来定义。以下是 Rule 定义结构以及 “必需”和 “可选” rule 元素的列表：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 --- name: \u0026#34;rule_name\u0026#34; # required pack: \u0026#34;examples\u0026#34; # optional description: \u0026#34;Rule description.\u0026#34; # optional enabled: true # required trigger: # required type: \u0026#34;trigger_type_ref\u0026#34; criteria: # optional trigger.payload_parameter_name1: type: \u0026#34;regex\u0026#34; pattern : \u0026#34;^value$\u0026#34; trigger.payload_parameter_name2: type: \u0026#34;iequals\u0026#34; pattern : \u0026#34;watchevent\u0026#34; action: # required ref: \u0026#34;action_ref\u0026#34; parameters: # optional foo: \u0026#34;bar\u0026#34; baz: \u0026#34;{{ trigger.payload_parameter_1 }}\u0026#34; 上面 yaml 表示一个通用形式 Rule 包括：\nname: rule 的名称。 pack: rule 所属的 pack。如果未指定 pack，则默认为 default。 description: 对 rule 的描述。 enabled: rule的启用状态（true 或 false）。 trigger: 从传感器发出的要监视的 trigger 的类型，以及可选的与该 trigger 相关的参数。 criteria: 一个可选的条件集，包括： trigger payload 的属性。 criteria 比较的类型。 pattern 与之匹配的模式。 action：当 rule 匹配时要执行的 action，包括： ref: 要执行的引用 (action/workflow) parameters: 要传递给动作 action 的可选参数。 Criteria Criteria 的条件是需要匹配的规则（逻辑运算符 AND），rules 中的 criteria 表达为：\nyaml 1 2 3 4 5 6 7 8 # more variables criteria: trigger.payload_parameter_name1: type: \u0026#34;regex\u0026#34; pattern : \u0026#34;^value$\u0026#34; trigger.payload_parameter_name2: type: \u0026#34;iequals\u0026#34; pattern : \u0026#34;watchevent\u0026#34; 通过创建多个独立的 rules （每个 rules 对应一个条件表达式），可以实现逻辑运算符 OR 行为（任何一个条件表达式匹配都会触发 Action 的执行）。\n在这里，type 指定要使用哪个条件比较运算符，而 pattern 指定要传递给运算符函数的模式。在正则表达式情况下，pattern 是一个正则表达式模式， trigger 值需要与之匹配。\n如果 criteria 包含任何特殊字符（如 -），则请使用字典查找格式来指定 criteria 键。在基于 Webhook 的规则中，通常在发布的事件标头中包含这些值：\nyaml 1 2 3 4 criteria: trigger.headers[\u0026#39;X-Custom-Header\u0026#39;]: type: \u0026#34;eq\u0026#34; pattern : \u0026#34;customvalue\u0026#34; pattern 值还可以使用 Jinja 变量访问语法引用数据存储的值\nyaml 1 2 3 4 criteria: trigger.payload.build_number: type: \u0026#34;equals\u0026#34; pattern : \u0026#34;{{ st2kv.system.current_build_number }}\u0026#34; 由于 PyYAML 中已知的已报告问题，“criteria key”必须是唯一的。这在您希望对相同的 trigger 数据应用不同的运算符（例如 contains 和 ncontains）时有时会变得相关，在下面示例中，仅评估条件中的最后一个重复 key。\nyaml 1 2 3 4 5 6 7 8 9 10 criteria: trigger.payload.commit.tags: # 重复的key 忽略 type: ncontains pattern: StackStorm trigger.payload.commit.tags: # 重复的key，需要评估 type: contains pattern: pull request trigger.payload.commit.message: # 唯一key，需要评估 type: ncontains pattern: ST2 在 PyYAML 中，\u0026ldquo;ncontains\u0026rdquo; 和 \u0026ldquo;contains\u0026rdquo; 是用于规则条件比较的两种不同运算符：\ncontains：表示匹配包含某个特定模式的条件。例如，如果您想要触发某个规则，只要 trigger 数据包含特定的内容，就可以使用 \u0026ldquo;contains\u0026rdquo; 运算符。 ncontains：表示匹配不包含某个特定模式的条件。与 \u0026ldquo;contains\u0026rdquo; 相反，只有当 trigger 数据不包含指定的内容时，规则才会触发。 另外，作为一种解决方法，可以使用 “criteria tags” 来多次引用相同的 “criteria key” 。“criteria tags” 使 key 变得唯一，并可以为条件提供上下文。要创建 “criteria tags”，请在 “criteria key” 的末尾包含一个 # 符号和一些文本。在评估时，# 和 # 后面的文本将被忽略。例如：trigger.payload.level#upper 和 trigger.payload.level#lower，如下面示例\nyaml 1 2 3 4 5 6 7 8 9 10 criteria: trigger.payload.commit.tags#1: type: ncontains pattern: StackStorm trigger.payload.commit.tags#2: type: contains pattern: pull request trigger.payload.commit.message: type: ncontains pattern: ST2 在这个例子中，由于 “criteria key” 都是唯一的，所有这些条件都将被评估，即使 trigger.payload.commit.tags#1 和 trigger.payload.commit.tags#2 在 trigger 数据中指定了相同的值。使用 “criteria tag” 是为了克服 PyYAML 中 “criteria key” 必须唯一的问题，使得可以在同一 key 上多次引用并且能够指定不同的运算符和模式，从而更灵活地定义规则。\nCriteria Comparison Criteria Comparison 这部分描述了在 Criteria 中可以使用的所有可用运算符。\n运算符 描述 equals 值相等（适用于任意类型的值）。 nequals 值不相等（适用于任意类型的值）。 lessthan trigger 值小于提供的值。 greaterthan trigger 值大于提供的值。 matchwildcard trigger 值与提供的通配符字符串匹配。此运算符支持类似于 Unix shell 的通配符，您可以使用字符如 * 和 ?。对于简单的字符串匹配，此运算符比正则表达式更可取。 regex trigger 值与提供的正则表达式模式匹配。此运算符的行为类似于 re.search('pattern', trigger_value)。 iregex trigger 值与提供的正则表达式模式不区分大小写地匹配。此运算符的行为类似于 re.search('pattern', trigger_value, re.IGNORECASE)。 matchregex trigger 值与提供的正则表达式模式匹配。此运算符已弃用，推荐使用 regex 和 iregex。 iequals 字符串 trigger 值不区分大小写地等于提供的值。 contains trigger 值包含提供的值。请注意， trigger 值可以是字符串或数组（列表）。 ncontains trigger 值不包含提供的值。请注意， trigger 值可以是字符串或数组（列表）。 icontains 字符串 trigger 值不区分大小写地包含提供的值。 incontains 字符串 trigger 值不区分大小写地不包含提供的字符串值。 startswith 字符串 trigger 值的开头与提供的字符串值匹配。 istartswith 字符串 trigger 值的开头与提供的字符串值不区分大小写地匹配。 endswith 字符串 trigger 值的结尾与提供的字符串值匹配。 iendswith 字符串 trigger 值的结尾与提供的字符串值不区分大小写地匹配。 timediff_lt trigger 值与当前时间的时间差小于提供的值。 timediff_gt trigger 值与当前时间的时间差大于提供的值。 exists 在 payload 中存在键。 nexists 在 payload 中不存在键。 inside 触发 payload 在提供的值内部（例如，测试是否“trigger.payload 在 provided_value 中”）。与 contains 的相反操作（其中 contains 会测试“trigger.payload 包含 provided_value”）。 ninside 触发 payload 不在提供的值内部（例如，测试是否 “trigger.payload 不在 provided_value 中”）。与 ncontains 的相反操作（其中 contains 会测试“trigger.payload 不包含 provided_value”）。 search 在触发 payload 中搜索与子条件匹配的数组（列表）。有关更多信息和示例，请参阅高级比较部分。 高级比较运算符 “搜索运算符” 比其他运算符稍微复杂一些。它接受一个额外的条件参数以及应用于搜索列表每个元素的额外嵌套条件。\n条件参数控制搜索运算符如何匹配列表。对于 any 条件，如果 trigger payload 列表中至少有一项匹配所有子条件，搜索运算符将返回成功匹配。对于 all 条件， trigger payload 列表中的每一项都必须与所有子条件匹配，搜索运算符才会返回成功匹配。any2any 条件在任何 payload 项匹配任何条件项时返回成功匹配。最后，all2any 条件在所有 payload 项匹配任何条件项时返回成功匹配。\n以下是使用 “搜索运算符” 和 any 条件的示例条件：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 --- criteria: trigger.issue_fields: type: \u0026#34;search\u0026#34; # Controls whether all items in the trigger payload must match the child criteria, # or if any single item matching the child criteria is sufficient condition: any # \u0026lt;- *At least one* item must match all child patterns pattern: # Here our context is each item of the list # All of these patterns must match the item for the item to be considered a match # These are simply other operators applied to each item of the list item.field_name: type: \u0026#34;equals\u0026#34; pattern: \u0026#34;Status\u0026#34; item.to_value: type: \u0026#34;equals\u0026#34; pattern: \u0026#34;Approved\u0026#34; 下面内容，这个条件将匹配以下 trigger payload，因为“Status”字段已更改为“Approved”：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 { \u0026#34;issue_fields\u0026#34;: [ { \u0026#34;field_type\u0026#34;: \u0026#34;Custom\u0026#34;, \u0026#34;field_name\u0026#34;: \u0026#34;Status\u0026#34;, \u0026#34;to_value\u0026#34;: \u0026#34;Approved\u0026#34; }, { \u0026#34;field_type\u0026#34;: \u0026#34;Custom\u0026#34;, \u0026#34;field_name\u0026#34;: \u0026#34;Signed off by\u0026#34;, \u0026#34;to_value\u0026#34;: \u0026#34;Stanley\u0026#34; } ] } 以下是另一个示例，其中 condition 参数为 all，在这种情况下，列表中的所有项都必须匹配所有的子模式：\nyaml 1 2 3 4 5 6 7 8 9 --- criteria: trigger.issue_fields: type: \u0026#34;search\u0026#34; condition: all # \u0026lt;- *All* items must match all patterns pattern: item.field_type: type: \u0026#34;equals\u0026#34; pattern: \u0026#34;Custom\u0026#34; 该条件也将与上述 trigger payload 匹配。\n然而，以下 trigger payload 不会与 all 条件匹配，因为 “Summary” 字段不是 \u0026ldquo;Custom\u0026rdquo; 字段：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { \u0026#34;issue_fields\u0026#34;: [ { \u0026#34;field_type\u0026#34;: \u0026#34;Built-in\u0026#34;, \u0026#34;field_name\u0026#34;: \u0026#34;Summary\u0026#34;, \u0026#34;to_value\u0026#34;: \u0026#34;Lorem Ipsum\u0026#34; }, { \u0026#34;field_type\u0026#34;: \u0026#34;Custom\u0026#34;, \u0026#34;field_name\u0026#34;: \u0026#34;Status\u0026#34;, \u0026#34;to_value\u0026#34;: \u0026#34;Approved\u0026#34; }, { \u0026#34;field_type\u0026#34;: \u0026#34;Custom\u0026#34;, \u0026#34;field_name\u0026#34;: \u0026#34;Signed off by\u0026#34;, \u0026#34;to_value\u0026#34;: \u0026#34;Stanley\u0026#34; } ] } 以下示例是 参数 condition 为 any2any 的示例。在任何 payload 项匹配模式的任何部分的情况下，这将返回 true。此示例使用上面 “condition” 部分中描述的条件标签。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 --- criteria: trigger.body.data.tank: type: \u0026#34;search\u0026#34; condition: any2any pattern: item.chemicalLevel#1: type: \u0026#34;lessthan\u0026#34; pattern: 40 item.chemicalLevel#2: type: \u0026#34;greaterthan\u0026#34; pattern: 50 Payload\nyaml 1 2 3 4 5 6 7 8 9 10 11 { \u0026#34;tanks\u0026#34;: [ { \u0026#34;id\u0026#34;: 1, \u0026#34;chemicalLevel\u0026#34;: 43 }, { \u0026#34;id\u0026#34;: 2, \u0026#34;chemicalLevel\u0026#34;: 55 } ] } 上面示例中，由于第二个 chemicalLevel 值超过了50，因此此条件解析为 true，并且将触发操作，例如向操作员发送通知。如果第二个chemicalLevel 为45，则条件解析为 false，不会发生任何操作。\n以下是条件参数为 all2any 的示例。在所有 payload 都与所有的 condition pattern 匹配的情况下，这将返回 true\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 --- criteria: trigger.body.data.equipment: type: \u0026#34;search\u0026#34; condition: all2any pattern: item.latitude.value#1: type: \u0026#34;lessthan\u0026#34; pattern: 40 item.latitude.value#2: type: \u0026#34;greaterthan\u0026#34; pattern: 50 item.longitude.value#1: type: \u0026#34;lessthan\u0026#34; pattern: -100 item.longitude.value#2: type: \u0026#34;greaterthan\u0026#34; pattern: -90 payload 如下：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { \u0026#34;equipment\u0026#34;: [ { \u0026#34;latitude\u0026#34;: { \u0026#34;value\u0026#34;: 43 } \u0026#34;longitude\u0026#34;: { \u0026#34;value\u0026#34;: -95 } }, { \u0026#34;latitude\u0026#34;: { \u0026#34;value\u0026#34;: 44 } \u0026#34;longitude\u0026#34;: { \u0026#34;value\u0026#34;: -96 } } ] } 在这个例子中，所有设备的值都在条件指定的范围内，因此不会执行任何操作。即使将第一个 latitude 设置为 53，仍然不会有任何操作。假设第二个 longitude 值设置为 -106，那么操作将触发，因为所有设备将违反 contition 中的至少一个部分。这可以在最后一个 equipment 离开区域时触发通知。\n单一 payload 模式\n如果 payload 中只需要一个元素，则如果 payload 是字典，则搜索参数仍然可以用于测试规则的条件，例如下面示例，payload 还是和上面相同，但是实际内容不同\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 --- criteria: trigger.body.data: type: \u0026#34;search\u0026#34; condition: all2any pattern: item.latitude.value#1: type: \u0026#34;lessthan\u0026#34; pattern: 40 item.latitude.value#2: type: \u0026#34;greaterthan\u0026#34; pattern: 50 item.longitude.value#1: type: \u0026#34;lessthan\u0026#34; pattern: -100 item.longitude.value#2: type: \u0026#34;greaterthan\u0026#34; pattern: -90 payload\nyaml 1 2 3 4 5 6 7 8 9 10 { \u0026#34;data\u0026#34;: { \u0026#34;latitude\u0026#34;: { \u0026#34;value\u0026#34;: 43 } \u0026#34;longitude\u0026#34;: { \u0026#34;value\u0026#34;: -95 } } } 在使用 single payload 时，all2any 和 any2any 具有相同的结果，因为选择所有与选择任何一件相同。\ncondition 参数 count count_gt count_gte count_lt count_lte any all any2any all2any 搜索运算符的注意事项 首先，它将规则引擎转变为一个 “递归下降解析器”，这可能会降低规则引擎的性能。因此，如果有一个规则必须保持快速响应，不受系统负载影响，建议尽量避免使用 “搜索运算符”。\n其次，“搜索运算符” 的认知复杂性使其一眼难以理解。如果你与他人共享代码，可能需要详细记录规则和模式，以更清晰地解释你的意图。\n最后，“搜索运算符” 的算法复杂性与其他运算符大不相同，下面是算法的复杂度：\nO($n_{patterns}$) 是指 $n_{patterns}$，即子模式的数量。这表示算法的运行时间（复杂度）与子模式的数量成正比。当子模式数量增加时，运行时间也会相应增加。 O($n_{payloads}$) 是指 $n_{payloads}$，即 “trigger payload” 字段的数量。同样，这表示算法的运行时间与 “trigger payload” 字段的数量成正比。增加 “trigger payload” 字段的数量也会导致运行时间增加。 其中，O 表示的是算法的渐进复杂度，即算法的运行时间与输入规模的增长关系。因此，如果有大量 “子条件” 或者在 “trigger payload” 中搜索长列表，使用搜索运算符很容易编写出慢规则。\n总结：使用 “搜索运算符” 应主要限制在尝试匹配 “少量的子条件” 和 ”少量预期 payload“ 列表项的情况下。尽管搜索运算符可能使规则引擎变慢，但与无条件运行工作流并在那里进行过滤相比，在规则中使用搜索运算符仍然更快且更轻量。\n在Rule中配置Actions的执行 这一部分描述了在 ”tigger“ 成功匹配并满足一组可选条件时要执行的后续 ”actions/workflows“。至少，Rules 应指定要执行的 Actions。Rules 还可以指定在执行 Actions 时提供给 Actions 的参数。\n下面是一个 Actions 的示例\nyaml 1 2 3 4 5 action: # required ref: \u0026#34;action_ref\u0026#34; parameters: # optional foo: \u0026#34;bar\u0026#34; baz: 1 偶尔情况下，在规则 Rules 时，将 Tigger 的上下文传递给 Actions 可能是必要的。 Rule 引擎能够通过利用 Jinja 模板语法插值变量。如下所示：\nyaml 1 2 3 4 5 action: ref: \u0026#34;action_ref\u0026#34; parameters: foo: \u0026#34;bar\u0026#34; baz: \u0026#34;{{ trigger.payload_parameter_1 }}\u0026#34; 触发器属性的值可以是 null 和 None。这也是相关 Actions 参数的有效值。您需要使用 use_none Jinja 模板过滤器，以确保在调用操作时正确序列化 null/None 值。\nyaml 1 2 3 4 5 action: ref: \u0026#34;action_ref\u0026#34; parameters: foo: \u0026#34;bar\u0026#34; baz: \u0026#34;{{ trigger.payload_parameter_1 | use_none }}\u0026#34; Notes: 当使用 Jinja 模板插值时，null 或 None 的值可能会导致一些序列化问题。使用 \u0026ldquo;use_none\u0026rdquo; 过滤器可以帮助规避这些问题，确保 null 或 None 值在序列化时被正确处理。\n由于当前的 Jinja 模板系统不支持非字符串类型，因此需要此解决方法。在调用操作之前，我们被迫根据操作参数定义执行类型转换。\nRules 的管理 添加rule 如果只为了部署部署一条 rule，可以使用 st2 命令：st2 rule create ${PATH_TO_RULE}，例如：\nbash 1 st2 rule create /usr/share/doc/st2/examples/rules/sample_rule_with_webhook.yaml 完成后记得要重新加载所有规则，请使用 st2ctl reload --register-rules\n如果 rule name 已经存在将会出现下面的提示：\nyaml 1 2 ERROR: 409 Client Error: Conflict MESSAGE: Tried to save duplicate unique keys (E11000 duplicate key error index: st2.rule_d_b.$uid_1 dup key: { : \u0026#34;rule:examples:sample_rule_with_webhook\u0026#34; }) 更新rule 要更新一条规则，编辑规则定义文件并运行命令：st2 rule update，如下例所示：\nbash 1 st2 rule update examples.sample_rule_with_webhook /usr/share/doc/st2/examples/rules/sample_rule_with_webhook.yaml 获取rule 要查看所有规则或获取单个规则，请使用以下命令：\nbash 1 2 st2 rule list st2 rule get examples.sample_rule_with_webhook 要取消部署一条规则，运行命令：st2 rule delete ${RULE_NAME_OR_ID}。例如，要取消部署之前部署的名为 examples.sample_rule_with_webhook 的规则，运行：\nbash 1 st2 rule delete examples.sample_rule_with_webhook rule的放置位置 自定义规则可以放置在本地系统上的任何可访问文件夹中。自定义规则通常放置在 /opt/stackstorm/packs/\u0026lt;pack_name\u0026gt;/rules 目录中。\n测试rule 为了更方便地测试规则，我们提供了一个 st2-rule-tester 工具，它可以在不运行任何 StackStorm 组件的情况下评估规则与触发器实例的匹配。\n该工具通过接受包含规则定义的文件路径和包含触发器实例定义的文件路径来实现：\nbash 1 2 st2-rule-tester --rule=${RULE_FILE} --trigger-instance=${TRIGGER_INSTANCE_DEFINITION} --config-file=/etc/st2/st2.conf echo $? 两个文件都需要以YAML或JSON格式包含定义。对于规则，您可以使用计划部署的相同文件。\n对于触发器实例，定义文件需要包含以下键：\ntrigger：触发器的完整引用（例如，core.st2.IntervalTimer，slack.message，irc.pubmsg，twitter.matched_tweet等）。 payload：Trigger payload。负载本身是特定于所讨论触发器的。要了解触发器结构，您可以查看包的 README 或查找位于 packs/\u0026lt;pack_name\u0026gt;/sensors/ 目录中的 sensor 元数据文件中的 trigger_types 部分。 如果触发器实例匹配，将打印 === RULE MATCHES === 并且该工具将以 0 的状态代码退出。如果规则不匹配，将打印 === RULE DOES NOT MATCH === 并且该工具将以 1 的状态代码退出。\n以下是如何使用该工具的一些示例：\nrule.yaml\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 --- name: \u0026#34;relayed_matched_irc_message\u0026#34; pack: \u0026#34;irc\u0026#34; description: \u0026#34;Relay IRC message to Slack if the message contains word StackStorm\u0026#34; enabled: true trigger: type: \u0026#34;irc.pubmsg\u0026#34; parameters: {} criteria: trigger.message: # 触发器的message值 type: \u0026#34;icontains\u0026#34; # 不区分大小写包含 pattern 的值 pattern: \u0026#34;StackStorm\u0026#34; action: ref: \u0026#34;slack.post_message\u0026#34; parameters: message: \u0026#34;{{ trigger.source.nick }} on {{ trigger.channel }}: {{ trigger.message }}\u0026#34; channel: \u0026#34;#irc-relay\u0026#34; trigger_instance_1.yaml :\nyaml 1 2 3 4 5 6 7 8 9 --- trigger: \u0026#34;irc.pubmsg\u0026#34; payload: source: nick: \u0026#34;Kami_\u0026#34; host: \u0026#34;gateway/web/irccloud.com/x-uvv\u0026#34; channel: \u0026#34;#stackstorm\u0026#34; timestamp: 1419166748, message: \u0026#34;stackstorm is cool!\u0026#34; trigger_instance_2.yaml\nyaml 1 2 3 4 5 6 7 8 9 --- trigger: \u0026#34;irc.pubmsg\u0026#34; payload: source: nick: \u0026#34;Kami_\u0026#34; host: \u0026#34;gateway/web/irccloud.com/x-uvv\u0026#34; channel: \u0026#34;#stackstorm\u0026#34; timestamp: 1419166748, message: \u0026#34;blah blah\u0026#34; 下面使用 trigger_instance_1 来评估rules\nbash 1 2 st2-rule-tester --rule=./my_rule.yaml --trigger-instance=./trigger_instance_1.yaml echo $? trigger_instance_1 输出的结果为\nbash 1 2 3 4 5 2015-12-11 14:35:03,249 INFO [-] Connecting to database \u0026#34;st2\u0026#34; @ \u0026#34;0.0.0.0:27017\u0026#34; as user \u0026#34;None\u0026#34;. 2015-12-11 14:35:03,318 INFO [-] Validating rule irc.relayed_matched_irc_message for pubmsg. 2015-12-11 14:35:03,331 INFO [-] 1 rule(s) found to enforce for pubmsg. 2015-12-11 14:35:03,333 INFO [-] === RULE MATCHES === 0 使用 trigger_instance_2 来评估rules\ntext 1 2 st2-rule-tester --rule=./my_rule.yaml --trigger-instance=./trigger_instance_2.yaml echo $? trigger_instance_2 输出的结果为\nbash 1 2 3 4 5 6 7 8 9 10 2015-12-11 14:35:57,380 INFO [-] Connecting to database \u0026#34;st2\u0026#34; @ \u0026#34;0.0.0.0:27017\u0026#34; as user \u0026#34;None\u0026#34;. 2015-12-11 14:35:57,444 INFO [-] Validating rule irc.relayed_matched_irc_message for pubmsg. 2015-12-11 14:35:57,459 INFO [-] Validation for rule irc.relayed_matched_irc_message failed on - key: trigger.message pattern: StackStorm type: icontains payload: blah blah 2015-12-11 14:35:57,461 INFO [-] 0 rule(s) found to enforce for pubmsg. 2015-12-11 14:35:57,462 INFO [-] === RULE DOES NOT MATCH === 1 st2-rule-tester 进一步提供了一种事后调试的方式，您可以回答一个问题：“为什么我的 Rule 没有与刚刚触发的 Trigger 匹配？” 这表示着已经在 StackStorm 中加载了一个已知引用的 rule，类似地，还有一个带有已知 ID 的 Trigger Instance。\n假设我们有规则引用为 my_pack.fire_on_execution 和 Trigger 实例 ID 为 566b4be632ed352a09cd347d：\nbash 1 2 st2-rule-tester --rule-ref=my_pack.fire_on_execution --trigger-instance-id=566b4be632ed352a09cd347d --config-file=/etc/st2/st2.conf echo $? 输出结果为\nbash 1 2 3 4 5 6 7 8 9 2015-12-11 15:24:16,459 INFO [-] Connecting to database \u0026#34;st2\u0026#34; @ \u0026#34;0.0.0.0:27017\u0026#34; as user \u0026#34;None\u0026#34;. 2015-12-11 15:24:16,527 INFO [-] Validating rule my_pack.fire_on_execution for st2.generic.actiontrigger. 2015-12-11 15:24:16,542 INFO [-] Validation for rule my_pack.fire_on_execution failed on - key: trigger.status pattern: succeeded type: iequals payload: failed 2015-12-11 15:24:16,545 INFO [-] 0 rule(s) found to enforce for st2.generic.actiontrigger. 2015-12-11 15:24:16,546 INFO [-] === RULE DOES NOT MATCH === 输出还标识了不匹配的来源，即是 Trigger 类型不匹配还是其中一个条件不匹配。\n如果您正在调试并想要查看发送到 StackStorm 的 Trigger 实例列表，可以使用下面命令：\nbash 1 st2 trigger-instance list 还可以按触发器进行触发器实例的过滤：\nbash 1 st2 trigger-instance list --trigger=core.f9e09284-b2b1-4127-aedd-dcde7a752819 此外，还可以通过使用 timestamp_gt 和 timestamp_lt 过滤选项，在时间范围内获取触发器实例：\nbash 1 st2 trigger-instance list --trigger=\u0026#34;core.f9e09284-b2b1-4127-aedd-dcde7a752819\u0026#34; -timestamp_gt=2015-06-01T12:00:00Z -timestamp_lt=2015-06-02T12:00:00Z 请注意，您还可以指定其中一个 timestamp_lt 或 timestamp_gt。您可以使用 get 命令获取有关触发器实例的详细信息：\nbash 1 st2 trigger-instance get 556e135232ed35569ff23238 在调试规则时可能很有用的一项功能是将触发器实例重新发送到 StackStorm。您可以使用 re-emit 命令来实现这一点。\nbash 1 st2 trigger-instance re-emit 556e135232ed35569ff23238 定时器 - timers 定时器 (Timers) 允许基于定义的时间间隔重复运行特定操作，或在特定日期和时间运行。您可以将它们视为 cron 作业，但具有更多的灵活性，例如仅在提供的日期和时间运行操作一次的能力。\n目前，我们支持以下定时器触发器类型：\ncore.st2.IntervalTimer ：在预定义的时间间隔（例如每30秒，每24小时，每周等）运行一个操作。 core.st2.DateTimer ：在指定的日期和时间运行一个操作。 core.st2.CronTimer ：当前时间匹配在UNIX cron格式中定义的时间约束时运行一个操作。 定时器被实现为 Trigger，这意味着您可以在 Rule 中使用它们。在下面的部分中，您可以找到如何在规则定义中使用定时器的一些示例。\ncore.st2.IntervalTimer 使用示例 可用的参数有：unit 和 delta。\n对于 unit 参数，支持的值包括：seconds、minutes、hours、days、weeks。\n每30秒运行一次Action yaml 1 2 3 4 5 6 7 8 9 10 11 --- ... trigger: type: \u0026#34;core.st2.IntervalTimer\u0026#34; parameters: unit: \u0026#34;seconds\u0026#34; delta: 30 action: ... 每24小时运行一次Action yaml 1 2 3 4 5 6 7 8 9 10 11 --- ... trigger: type: \u0026#34;core.st2.IntervalTimer\u0026#34; parameters: unit: \u0026#34;hours\u0026#34; delta: 24 action: ... 每2星期运行一次Action yaml 1 2 3 4 5 6 7 8 9 10 11 --- ... trigger: type: \u0026#34;core.st2.IntervalTimer\u0026#34; parameters: unit: \u0026#34;weeks\u0026#34; delta: 2 action: ... core.st2.DateTimer 可用参数 timezone, date.\n在指定时间运行 action\nyaml 1 2 3 4 5 6 7 8 9 10 11 --- ... trigger: type: \u0026#34;core.st2.DateTimer\u0026#34; parameters: timezone: \u0026#34;UTC\u0026#34; date: \u0026#34;2014-12-31 23:59:59\u0026#34; action: ... core.st2.CronTimer 此定时器支持类似 cron 的表达式。\n默认情况下，如果没有为特定参数提供值，则假定为 *，这意味着在每个值上触发。\n可用参数 timezone, year, month, day, week, day_of_week, hour, minute, second，注意，时区(timezone ) 使用 pytz 格式 例如 Asia/Shanghai.\n每周日午夜运行action yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 --- ... trigger: type: \u0026#34;core.st2.CronTimer\u0026#34; parameters: timezone: \u0026#34;UTC\u0026#34; day_of_week: 6 # or day_of_week: \u0026#34;sun\u0026#34; hour: 0 minute: 0 second: 0 action: ... 每天午夜运行action yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 --- ... trigger: type: \u0026#34;core.st2.CronTimer\u0026#34; parameters: timezone: \u0026#34;UTC\u0026#34; day_of_week: \u0026#34;*\u0026#34; hour: 0 minute: 0 second: 0 action: ... 如上所述，如果没有为特定参数提供值，则假定为 *，这意味着以下内容等效于上述内容：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 --- ... trigger: type: \u0026#34;core.st2.CronTimer\u0026#34; parameters: timezone: \u0026#34;UTC\u0026#34; hour: 0 minute: 0 second: 0 action: ... 周一到周五每天午夜执行，但周六日不执行 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 --- ... trigger: type: \u0026#34;core.st2.CronTimer\u0026#34; parameters: timezone: \u0026#34;UTC\u0026#34; day_of_week: \u0026#34;mon-fri\u0026#34; hour: 0 minute: 0 second: 0 action: ... 每小时执行一次 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 --- ... trigger: type: \u0026#34;core.st2.CronTimer\u0026#34; parameters: timezone: \u0026#34;UTC\u0026#34; hour: \u0026#34;*\u0026#34; minute: 0 second: 0 action: ... 故障排除 rules 不如预期那样工作？或者只是想看看哪些 rules 已经生效？\n运行 st2 rule-enforcement list 以查看所有 rules 执行。您可以通过规则对此输出进行过滤以缩小范围。\n","permalink":"https://www.161616.top/stackstorm-rules/","summary":"StackStorm 使用 Rules 和 Workflows 来捕获操作模式并进行自动化。rule将 Triggers 映射到 Actions（或 Workflow），应用匹配条件( Criteria)，并将 Triggers payloads 映射到 Actions 的 Input。\n注意\nrule不按预期工作吗？请查看rule故障排除文档。其中介绍了rule测试、检查执行、记录和故障排除等内容。\nRule 的配置结构 stackstorm 中的 Rule 是以 YAML 格式来定义。以下是 Rule 定义结构以及 “必需”和 “可选” rule 元素的列表：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 --- name: \u0026#34;rule_name\u0026#34; # required pack: \u0026#34;examples\u0026#34; # optional description: \u0026#34;Rule description.\u0026#34; # optional enabled: true # required trigger: # required type: \u0026#34;trigger_type_ref\u0026#34; criteria: # optional trigger.","title":"StackStorm自动化 - Rules"},{"content":"在我们基于 Kubernetes 编写云原生 GoLang 代码时，通常在本地调试时，使用 kubeconfig 文件，以构建基于 clientSet 的客户端。而在将代码作为容器部署到集群时，则会使用集群 (in-cluster) 内的配置。\nclientcmd 模块用于通过传递本地 kubeconfig 文件构建 clientSet。因此，在容器内使用相同模块构建 clientSet 将需要维护容器进程可访问的 kubeconfig 文件，并设置具有访问 Kubernetes 资源权限的 serviceaccount token。\n下面是一个基于 kubeconfig 访问集群的代码模式\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var ( k8sconfig *string //使用kubeconfig配置文件进行集群权限认证 restConfig *rest.Config err error ) if home := homedir.HomeDir(); home != \u0026#34;\u0026#34; { k8sconfig = flag.String(\u0026#34;kubeconfig\u0026#34;, fmt.Sprintf(\u0026#34;./admin.conf\u0026#34;), \u0026#34;kubernetes auth config\u0026#34;) } flag.Parse() if _, err := os.Stat(*k8sconfig); err != nil { panic(err) } clientset,err := kubernetes.NewConfig(k8sconfig) if err != nil { panic(err) } 这样做可能导致 serviceaccount token 本身被潜在地暴露出去。如果任何用户能够执行到使用 kubeconfig 与集群通信的容器，那么就可以获取该 token，并可以伪装成服务账号从集群外部与 kube-apiserver 进行通信。\n为了避免这种情况，我们在 client-go 模块中使用了 rest 包。这将帮助我们从集群内部与集群通信，前提是使用适当的服务账号运行。但这需要对代码进行重写，以适应从集群外部构建 client-set 的方式。\n下面代码时使用 in-cluster 方式进行通讯的模式\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var ( k8sconfig *string //使用kubeconfig配置文件进行集群权限认证 restConfig *rest.Config err error ) if home := homedir.HomeDir(); home != \u0026#34;\u0026#34; { k8sconfig = flag.String(\u0026#34;kubeconfig\u0026#34;, fmt.Sprintf(\u0026#34;./admin.conf\u0026#34;), \u0026#34;kubernetes auth config\u0026#34;) } k8sconfig = k8sconfig flag.Parse() if _, err := os.Stat(*k8sconfig); err != nil { panic(err) } if restConfig, err = rest.InClusterConfig(); err != nil { // 这里是从masterUrl 或者 kubeconfig传入集群的信息，两者选一 // 先从 in-cluster 方式获取，如果不能获取，再执行这里 restConfig, err = clientcmd.BuildConfigFromFlags(\u0026#34;\u0026#34;, *k8sconfig) if err != nil { panic(err) } } restset, err := kubernetes.NewForConfig(restConfig) 除了这些之外，还需要创建对应的 serviceaccount 来让 Pod 在 in-cluster 有权限获取到自己要的资源，下面是一个完整的 deployment 创建这些资源的清单\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 apiVersion: v1 kind: Namespace metadata: name: infra --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: pod-proxier-secret-reader rules: - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: pod-proxier-rolebinding subjects: - kind: ServiceAccount name: pod-proxier-secret-sa namespace: infra roleRef: kind: ClusterRole name: pod-proxier-secret-reader apiGroup: rbac.authorization.k8s.io --- apiVersion: v1 kind: ServiceAccount metadata: namespace: infra name: pod-proxier-secret-sa --- apiVersion: apps/v1 kind: Deployment metadata: name: pod-proxier spec: replicas: 1 selector: matchLabels: app: pod-proxier template: metadata: labels: app: pod-proxier spec: serviceAccount: pod-proxier-secret-sa # 使用上面定义的 sa 进行in-cluster 访问 containers: - name: container-1 image: haproxytech/haproxy-debian:2.6 ports: - containerPort: 80 hostPort: 8080 # 添加 hostPort 字段 - name: container-2 image: container-2-image:tag ports: - containerPort: 8080 hostPort: 8081 # 添加 hostPort 字段 ","permalink":"https://www.161616.top/ch07-in-cluster-pod/","summary":"在我们基于 Kubernetes 编写云原生 GoLang 代码时，通常在本地调试时，使用 kubeconfig 文件，以构建基于 clientSet 的客户端。而在将代码作为容器部署到集群时，则会使用集群 (in-cluster) 内的配置。\nclientcmd 模块用于通过传递本地 kubeconfig 文件构建 clientSet。因此，在容器内使用相同模块构建 clientSet 将需要维护容器进程可访问的 kubeconfig 文件，并设置具有访问 Kubernetes 资源权限的 serviceaccount token。\n下面是一个基于 kubeconfig 访问集群的代码模式\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var ( k8sconfig *string //使用kubeconfig配置文件进行集群权限认证 restConfig *rest.Config err error ) if home := homedir.HomeDir(); home != \u0026#34;\u0026#34; { k8sconfig = flag.String(\u0026#34;kubeconfig\u0026#34;, fmt.Sprintf(\u0026#34;./admin.conf\u0026#34;), \u0026#34;kubernetes auth config\u0026#34;) } flag.","title":"client-go - Pod使用in-cluster方式访问集群"},{"content":"本文记录了在 kubernetes 环境中，使用 cephfs 时当启用了 fscache 时，由于网络问题，或者 ceph 集群问题导致的整个 k8s 集群规模的挂载故障问题。\n结合fscache的kubernetes中使用cephfs造成的集群规模故障 在了解了上面的基础知识后，就可以引入故障了，下面是故障产生环境的配置\n故障发生环境 软件 版本 Centos 7.9 Ceph nautilus (14.20) Kernel 4.18.16 故障现象 在 k8s 集群中挂在 cephfs 的场景下，新启动的 Pod 报错无法启动，报错信息如下\nbash 1 ContainerCannotRun: error while creating mount source path /var/lib/kubelet/pods/5446c441-9162-45e8-0e93-b59be74d13b/volumes/kubernetesio-cephfs/{dir name} mkcir /var/lib/kubelet/pods/5446c441-9162-45e8-de93-b59bte74d13b/volumes/kubernetes.io~cephfs/ip-ib file existe 主要表现的现象大概为如下三个特征\n对于该节点故障之前运行的 Pod 是正常运行，但是无法写入和读取数据\n无法写入数据 permission denied\n无法读取数据\nkublet 的日志报错截图如下\n彻底解决方法 需要驱逐该节点上所有挂在 cephfs 的 Pod，之后新调度来的 Pod 就可以正常启动了\n故障的分析 当网络出现问题时，如果使用了 cephfs 的 Pod 就会出现大量故障，具体故障表现方式有下面几种\n新部署的 Pod 处于 Waiting 状态\n新部署的 Pod 可以启动成功，但是无法读取 cephfs 的挂载目录，主要故障表现为下面几种形式：\nceph mount error 5 = input/output error [3] cephfs mount failure.permission denied 旧 Pod 无法被删除\n新部署的 Pod 无法启动\n注：上面故障引用都是在网络上找到相同报错的一些提示，并不完全切合本文中故障描述\n去对应节点查看节点内核日志会发现有下面几个特征\n图1：故障发生的节点报错 图2：故障发生的节点报错 图3：故障发生的节点报错 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [ 1815.029831] ceph: mds0 closed our session [ 1815.029833] ceph: mds0 reconnect start [ 1815.052219] ceph: mds0 reconnect denied [ 1815.052229] ceph: dropping dirty Fw state for ffff9d9085da1340 1099512175611 [ 1815.052231] ceph: dropping dirty+flushing Fw state for ffff9d9085da1340 1099512175611 [ 1815.273008] libceph: mds0 10.99.10.4:6801 socket closed (con state NEGOTIATING) [ 1816.033241] ceph: mds0 rejected session [ 1829.018643] ceph: mds0 hung [ 1880.088504] ceph: mds0 came back [ 1880.088662] ceph: mds0 caps renewed [ 1880.094018] ceph: get_quota_realm: ino (10000000afe.fffffffffffffffe) null i_snap_realm [ 1881.100367] ceph: get_quota_realm: ino (10000000afe.fffffffffffffffe) null i_snap_realm [ 2046.768969] conntrack: generic helper won\u0026#39;t handle protocol 47. Please consider loading the specific helper module. [ 2061.731126] ceph: get_quota_realm: ino (10000000afe.fffffffffffffffe) null i_snap_realm 故障分析 由上面的三张图我们可以得到几个关键点\nconnection reset session lost, hunting for new mon ceph: get_quota_realm() reconnection denied mds1 hung mds1 caps stale 这三张图上的日志是一个故障恢复的顺序，而问题节点（通常为整个集群 Node）内核日志都会在刷 ceph: get_quota_realm() 这种日志，首先我们需要确认第一个问题，ceph: get_quota_realm() 是什么原因导致，在互联网上找了一个 linux kernel 关于修复这个问题的提交记录，通过 commit，我们可以看到这个函数产生的原因\nget_quota_realm() enters infinite loop if quota inode has no caps. This can happen after client gets evicted. [4]\n这里可以看到，修复的内容是当客户端被驱逐时，这个函数会进入无限 loop ，当 inode 配额没有被授权的用户，常常发生在客户端被驱逐。\n通过这个 commit，我们可以确定了后面 4 - 6 问题的疑问，即客户端被 ceph mds 驱逐（加入了黑名单），在尝试重连时就会发生 reconnection denied 接着发生陈腐的被授权认证的用户 (caps stale)。接着由于本身没有真实的卸载，而是使用了一个共享的 cookie 这个时候就会发生节点新挂载的目录是没有权限写，或者是 input/output error 的错误，这些错误表象是根据不同情况下而定，比如说被拉黑和丢失的会话。\nkubelet的错误日志 此时当新的使用了 volumes 去挂载 cephfs时，由于旧的 Pod 产生的工作目录 (/var/lib/kubelet) 下的 Pod 挂载会因为 cephfs caps stale 而导致无法卸载，这是就会存在 “孤儿Pod”，“不能同步 Pod 的状态”，“不能创建新的Pod挂载，因为目录已存在”。\nkubelet 日志如下所示：\nbash 1 2 3 kubelet_volumes.go:66] pod \u0026#34;5446c441-9162-45e8-e11f46893932\u0026#34; found, but error stat /var/lib/kubelet/pods/5446c441-9162-45e8-e11f46893932/volumes/kubernetes.io~cephfs/xxxxx: permission denied occurred during checking mounted volumes from disk pod_workers.go:119] Error syncing pod \u0026#34;5446c441-9162-45e8-e11f46893932\u0026#34; (\u0026#34;xxxxx-xxx-xxx-xxxx-xxx_xxxxx(5446c441-9162-45e8-e11f46893932)\u0026#34;, skipping: failed to \u0026#34;StartContainer\u0026#34; for \u0026#34;xxxxx-xxx-xxx\u0026#34; with RunContainerError: \u0026#34;failed to start container \\\u0026#34;719346531es654113s3216e1456313d51as132156\\\u0026#34;: Error response from daemon: error while createing mount source path \u0026#39;/var/lib/kubelet/pods/5446c441-9162-45446c441-9162-45e8-e11f46893932/volumes/kubernetes.io~cephfs/xxxxxx-xx\u0026#39;: mkdir /var/lib/kubelet/pods/5446c441-9162-45446c441-9162-45e8-e11f46893932/volumes/kubernetes.io~cephfs/xxxxxx-xx: file exists\u0026#34; 问题复现 节点：192.168.155.70\n操作步骤，手动删除掉这个节点的会话复现问题：\n操作前日志\nbash 1 2 3 4 5 Nov 09 15:16:01 node88.itnet.com kernel: libceph: mon0 192.168.20.299:6789 session established Nov 09 15:16:01 node88.itnet.com kernel: libceph: mon0 192.168.20.299:6789 socket closed (con state OPEN) Nov 09 15:16:01 node88.itnet.com kernel: libceph: mon0 192.168.20.299:6789 session lost, hunting for new mon Nov 09 15:16:01 node88.itnet.com kernel: libceph: mon1 192.168.20.134:6789 session established Nov 09 15:16:01 node88.itnet.com kernel: libceph: client176873 fsid bf9495f9-726d-42d3-ac43-53938496bb29 步骤一：查找客户端id\nbash 1 2 3 4 5 $ ceph tell mds.0 client ls|grep 155.70 2023-11-09 18:07:37.063 7f204dffb700 0 client.177035 ms_handle_reset on v2:192.168.20.299:6800/1124232159 2023-11-09 18:07:37.089 7f204effd700 0 client.177041 ms_handle_reset on v2:192.168.20.299:6800/1124232159 \u0026#34;addr\u0026#34;: \u0026#34;192.168.155.70:0\u0026#34;, \u0026#34;inst\u0026#34;: \u0026#34;client.176873 v1:192.168.155.70:0/144083785\u0026#34;, 步骤二：驱逐该客户端\nbash 1 2 3 $ ceph tell mds.0 client evict id=176873 2023-11-09 18:09:13.726 7fc3cffff700 0 client.177074 ms_handle_reset on v2:192.168.20.299:6800/1124232159 2023-11-09 18:09:14.790 7fc3d97fa700 0 client.177080 ms_handle_reset on v2:192.168.20.299:6800/1124232159 步骤三：检查客户端\n查看日志，与 Openstack 全机房故障出现时日志内容一致\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Nov 09 18:09:14 node88.itnet.com kernel: libceph: mds0 192.168.20.299:6801 socket closed (con state OPEN) Nov 09 18:09:16 node88.itnet.com kernel: libceph: mds0 192.168.20.299:6801 connection reset Nov 09 18:09:16 node88.itnet.com kernel: libceph: reset on mds0 Nov 09 18:09:16 node88.itnet.com kernel: ceph: mds0 closed our session Nov 09 18:09:16 node88.itnet.com kernel: ceph: mds0 reconnect start Nov 09 18:09:16 node88.itnet.com kernel: ceph: mds0 reconnect denied Nov 09 18:09:20 node88.itnet.com kernel: libceph: mds0 192.168.20.299:6801 socket closed (con state NEGOTIATING) Nov 09 18:09:21 node88.itnet.com kernel: ceph: mds0 rejected session Nov 09 18:09:21 node88.itnet.com kernel: ceph: mds1 rejected session Nov 09 18:09:21 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:09:21 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:15:21 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:15:21 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:21:21 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:21:21 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:27:22 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:27:22 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:33:22 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:33:22 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:39:23 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:39:23 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:45:23 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:45:23 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:51:24 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:51:24 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:57:24 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm Nov 09 18:57:24 node88.itnet.com kernel: ceph: get_quota_realm: ino (10000000006.fffffffffffffffe) null i_snap_realm 问题建议与解决 取消fscache，但fscache的存在是为了实现大并发连接ceph的机制，取消比较困难 调整参数（还没有测试）：当网络异常时，保证不要拉黑客户端使其可以重连，这里理论上可以解决客户端缓存问题 首先上面我们阐述了问题出现背景以及原因，要想解决这些错误，要分为两个步骤：\n首先驱逐 Kubernetes Node 节点上所有挂载 cephfs 的 Pod，这步骤是为了优雅的结束 fscache 的 cookie cache 机制，使节点可以正常的提供服务 解决使用 fscache 因网络问题导致的会话丢失问题的重连现象 这里主要以步骤2来阐述，解决这个问题就是通过两个方式，一个是不使用 fscache，另一个则是不让 mds 拉黑客户端，关闭 fscache 的成本很难，至今没有尝试成功，这里通过配置 ceph 服务使得 ceph mds 不会拉黑因出现网络问题丢失连接的客户端。\nceph 中阐述了驱逐的概念 “当某个文件系统客户端不响应或者有其它异常行为时，有必要强制切断它到文件系统的访问，这个过程就叫做驱逐。” [5]\n问题的根本原因为：ceph mds 把客户端拉入了黑名单，缓存导致客户端无法卸载连接，但接入了 fscache 的概念导致旧 session 无法释放，新连接会被 reject。\n要想解决这个问题，ceph 提供了一个参数来解决这个问题，mds_session_blacklist_on_timeout\nIt is possible to respond to slow clients by simply dropping their MDS sessions, but permit them to re-open sessions and permit them to continue talking to OSDs. To enable this mode, set mds_session_blacklist_on_timeout to false on your MDS nodes. [6]\n最终在配置后，上述问题解决\n解决问题后测试故障是否存在 测试过程\nceph 参数的配置\n图4：故障发生的节点报错 操作驱逐 192.168.155.70 的客户端连接，用以模拟 ceph 运行的底层出现故障而非正常断开 session 的场景\n图5：驱逐客户端的操作 重新运行 Pod 检查 session 是缓存还是会重连\n图6：检查节点日志 附：ceph mds 管理客户端 查看一个客户端的连接\nbash 1 2 3 ceph daemon mds.xxxxxxxx session ls |grep -E \u0026#39;inst|hostname|kernel_version\u0026#39;|grep xxxx \u0026#34;inst\u0026#34;: \u0026#34;client.105123 v1:192.168.0.0:0/11243531\u0026#34;, \u0026#34;hostname\u0026#34;: \u0026#34;xxxxxxxxxxxxxxxxxx\u0026#34; 手动驱逐一个客户端\nbash 1 2 3 ceph tell mds.0 client evict id=105123 2023-11-12 13:25:23:381 7fa3a67fc700 0 client.105123 ms_handle_reset on v2:192.168.0.0:6800/112351231 2023-11-12 13:25:23:421 7fa3a67fc700 0 client.105123 ms_handle_reset on v2:192.168.0.0:6800/112351231 查看 ceph 的配置参数\nbash 1 2 3 4 5 6 ceph config dump WHO MASK LEVEL OPTION VALUE RO mon advanced auth_allow_insecure_global_id_reclaim false mon advanced mon_allow_pool_delete false mds advanced mds_session_blacklist_on_evict false mds advanced mds_session_blacklist_on_timeout false 查看黑名单\nbash 1 $ ceph osd blocklist ls 查看客户端连接\nbash 1 ceph tell mds.0 client ls If you are experiencing frequent client evictions, due to slow client hosts or an unreliable network, and you cannot fix the underlying issue, then you may want to ask the MDS to be less strict.\nbash 1 2 ceph config set mds mds_session_blocklist_on_timeout false ceph config set mds mds_session_blocklist_on_evict false 当出现问题无法卸载时应如何解决？\n当我们遇到问题时，卸载目录会出现被占用情况，通过 mount 和 fuser 都无法卸载\nbash 1 2 3 4 5 6 7 umount -f /tmp/998 umount： /tmp/998: target is buy. (In some cases useful info about processes that use th device is found by losf(8) or fuser(1)) the device is found by losf(8) or fuser(1) fuser -v1 /root/test Cannot stat /root/test: Input/output error 这个时候由于 cephfs 挂载问题会导致整个文件系统不可用，例如 df -h, ls dir 等，此时可以使用 umount 的懒卸载模式 umount -l，这会告诉内核当不占用时被卸载，由于这个问题是出现问题，而不是长期占用，这里用懒卸载后会立即卸载，从而解决了 stuck 的问题。\n什么是fscache fscache 是网络文件系统的通用缓存，例如 NFS, CephFS都可以使用其进行缓存从而提高 IO\nFS-Cache是在访问之前，将整个打开的每个 netfs 文件完全加载到 Cache 中，之后的挂载是从该缓存而不是 netfs 的 inode 中提供\nfscache主要提供了下列功能：\n一次可以使用多个缓存 可以随时添加/删除缓存 Cookie 分为 “卷”, “数据文件”, “缓存” 缓存 cookie 代表整个缓存，通常不可见到“网络文件系统” 卷 cookie 来表示一组 文件 数据文件 cookie 用于缓存数据 下图是一个 NFS 使用 fscache 的示意图，CephFS 原理与其类似\n图7：FS-Cache 架构 Source：https://computingforgeeks.com/how-to-cache-nfs-share-data-with-fs-cache-on-linux/\nCephFS 也是可以被缓存的一种网络文件系统，可以通过其内核模块看到对应的依赖\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 root@client:~# lsmod | grep ceph ceph 376832 1 libceph 315392 1 ceph fscache 65536 1 ceph libcrc32c 16384 3 xfs,raid456,libceph root@client:~# modinfo ceph filename: /lib/modules/4.15.0-112-generic/kernel/fs/ceph/ceph.ko license: GPL description: Ceph filesystem for Linux author: Patience Warnick \u0026lt;patience@newdream.net\u0026gt; author: Yehuda Sadeh \u0026lt;yehuda@hq.newdream.net\u0026gt; author: Sage Weil \u0026lt;sage@newdream.net\u0026gt; alias: fs-ceph srcversion: B2806F4EAACAC1E19EE7AFA depends: libceph,fscache retpoline: Y intree: Y name: ceph vermagic: 4.15.0-112-generic SMP mod_unload signat: PKCS#7 signer: sig_key: sig_hashalgo: md4 在启用了fs-cache后，内核日志可以看到对应 cephfs 挂载时 ceph 被注册到 fscache中\nbash 1 2 3 4 5 6 [ 11457.592011] FS-Cache: Loaded [ 11457.617265] Key type ceph registered [ 11457.617686] libceph: loaded (mon/osd proto 15/24) [ 11457.640554] FS-Cache: Netfs \u0026#39;ceph\u0026#39; registered for caching [ 11457.640558] ceph: loaded (mds proto 32) [ 11457.640978] libceph: parse_ips bad ip \u0026#39;mon1.ichenfu.com:6789,mon2.ichenfu.com:6789,mon3.ichenfu.com:6789\u0026#39; 当 monitor / OSD 拒绝连接时，所有该节点后续创建的挂载均会使用缓存，除非 umount 所有挂载后重新挂载才可以重新与 ceph mon 建立连接\ncephfs 中的 fscache ceph 官方在 2023年11月5日的一篇博客 [1] 中介绍了，cephfs 与 fscache 结合的介绍。这个功能的加入最显著的成功就是 ceph node 流向 OSD 网络被大大减少，尤其是在读取多的情况下。\n这个机制可以在代码 commit 中看到其原理：“在第一次通过文件引用inode时创建缓存cookie。之后，直到我们处理掉inode，我们都不会摆脱cookie” [2]\nReference [1] First Impressions Through Fscache and Ceph ​[2] ceph: persistent caching with fscache\n[3] Cannot Mount CephFS No Timeout, mount error 5 = Input/output error\n[4] ceph: fix infinite loop in get_quota_realm()\n[5] Ceph 文件系统客户端的驱逐\n[6] advanced-configuring-blacklisting\n","permalink":"https://www.161616.top/ch10-1-ceph-fscache/","summary":"本文记录了在 kubernetes 环境中，使用 cephfs 时当启用了 fscache 时，由于网络问题，或者 ceph 集群问题导致的整个 k8s 集群规模的挂载故障问题。\n结合fscache的kubernetes中使用cephfs造成的集群规模故障 在了解了上面的基础知识后，就可以引入故障了，下面是故障产生环境的配置\n故障发生环境 软件 版本 Centos 7.9 Ceph nautilus (14.20) Kernel 4.18.16 故障现象 在 k8s 集群中挂在 cephfs 的场景下，新启动的 Pod 报错无法启动，报错信息如下\nbash 1 ContainerCannotRun: error while creating mount source path /var/lib/kubelet/pods/5446c441-9162-45e8-0e93-b59be74d13b/volumes/kubernetesio-cephfs/{dir name} mkcir /var/lib/kubelet/pods/5446c441-9162-45e8-de93-b59bte74d13b/volumes/kubernetes.io~cephfs/ip-ib file existe 主要表现的现象大概为如下三个特征\n对于该节点故障之前运行的 Pod 是正常运行，但是无法写入和读取数据\n无法写入数据 permission denied\n无法读取数据\nkublet 的日志报错截图如下\n彻底解决方法 需要驱逐该节点上所有挂在 cephfs 的 Pod，之后新调度来的 Pod 就可以正常启动了\n故障的分析 当网络出现问题时，如果使用了 cephfs 的 Pod 就会出现大量故障，具体故障表现方式有下面几种\n新部署的 Pod 处于 Waiting 状态","title":"当cephfs和fscache结合时在K8s环境下的全集群规模故障"},{"content":"登录argo cd bash 1 argocd login argocd_server:argocd_port_here 执行后输入admin/sercert\nbash 1 2 3 4 5 6 $ argocd login 10.0.0.5:30908 WARNING: server certificate had error: x509: cannot validate certificate for 10.0.0.5 because it doesn\u0026#39;t contain any IP SANs. Proceed insecurely (y/n)? y Username: admin Password: \u0026#39;admin:login\u0026#39; logged in successfully Context \u0026#39;10.0.0.5:30908\u0026#39; updated argocd cli 登录后的文件保存在 ~/.argocd/config 中\n注册一个新集群 argocd 通过 kubectl 来获取集群的信息，所以 argocd 的主机上必须有 kubeconfig 文件\nNote: KUBECONFIG 文件地址必须为实际路径，比如 ~/ 这种方式不可以\nbash 1 export KUBECONFIG=\u0026#34;/root/admin.conf\u0026#34; 从 kubeconfig 中提取当前集群的上下文名称\nbash 1 kubectl config get-contexts -o name 向 argo 添加 kubernetes 集群\nbash 1 2 3 4 5 $ argocd cluster add k8s-admin@kubernetes INFO[0000] ServiceAccount \u0026#34;argocd-manager\u0026#34; created in namespace \u0026#34;kube-system\u0026#34; INFO[0000] ClusterRole \u0026#34;argocd-manager-role\u0026#34; created INFO[0000] ClusterRoleBinding \u0026#34;argocd-manager-role-binding\u0026#34; created Cluster \u0026#39;https://10.0.0.4:6443\u0026#39; added 现在可以执行 argocd 命令来列出 argo 中的所有集群，这是为了验证 argocd-cluster 是否已成功添加\nbash 1 2 3 4 $ argocd cluster list SERVER NAME VERSION STATUS MESSAGE https://10.0.0.4:6443 k8s-admin@kubernetes Unknown Cluster has no application and not being monitored. https://kubernetes.default.svc in-cluster Unknown Cluster has no application and not being monitored. 删除一个集群 命令 argocd cluster rm 用于从 argo server 中移除一个集群，例如\nbash 1 2 argocd cluster rm https://12.34.567.89 argocd cluster rm cluster-name 需要注意的是 in-cluster 集群是 argo 运行的集群，不能够被删除，如果不使用这个集群，需要修改配置 cluster.inClusterEnabled\nyaml 1 2 # cluster.inClusterEnabled indicates whether to allow in-cluster server address. This is enabled by default. cluster.inClusterEnabled: \u0026#34;true\u0026#34; 这个配置是在 argocd-cm 中保存的，可以在对应的 configMap 中添加，完整的 argocd 配置见附录1\nbash 1 2 3 4 5 6 7 8 9 10 11 kubectl get cm argocd-cm -o yaml apiVersion: v1 data: cluster.inClusterEnabled: \u0026#34;false\u0026#34; kind: ConfigMap metadata: labels: app.kubernetes.io/name: argocd-cm app.kubernetes.io/part-of: argocd name: argocd-cm namespace: default Reference ​[1] docs/operator-manual/argocd-cm.yaml\n​[2] Getting started with multi-cluster K8S deployments using Argo CD\n","permalink":"https://www.161616.top/ch03-argo-add-cluster/","summary":"登录argo cd bash 1 argocd login argocd_server:argocd_port_here 执行后输入admin/sercert\nbash 1 2 3 4 5 6 $ argocd login 10.0.0.5:30908 WARNING: server certificate had error: x509: cannot validate certificate for 10.0.0.5 because it doesn\u0026#39;t contain any IP SANs. Proceed insecurely (y/n)? y Username: admin Password: \u0026#39;admin:login\u0026#39; logged in successfully Context \u0026#39;10.0.0.5:30908\u0026#39; updated argocd cli 登录后的文件保存在 ~/.argocd/config 中\n注册一个新集群 argocd 通过 kubectl 来获取集群的信息，所以 argocd 的主机上必须有 kubeconfig 文件\nNote: KUBECONFIG 文件地址必须为实际路径，比如 ~/ 这种方式不可以","title":"初识Argo cd - 注册/删除k8s集群"},{"content":"在安装和注册集群完成后，就需要引入第一个概念 “Application”（如何管理所有我的应用程序？）\n什么是 Application 什么是 ArgoCD “Application”？ 对于 ArgoCD “Application”的快速解释：它是托管 ArgoCD 部署的 Kubernetes 集群 CRD 包含了应用程序的所有设置，如：\n要部署到哪个集群？ 与哪个 Git 存储库进行同步？ 其他部署设置 应用程序的 YAML 包含了部署您的存储库资源所需的所有信息，充当了在 ArgoCD 中管理应用程序的关键控制点。\nReference ​[1] docs/operator-manual/argocd-cm.yaml\n​[2] Getting started with multi-cluster K8S deployments using Argo CD\n​[3] https://medium.com/notive/managing-argocd-application-resources-1b2b4742ab90\n","permalink":"https://www.161616.top/ch04-application/","summary":"在安装和注册集群完成后，就需要引入第一个概念 “Application”（如何管理所有我的应用程序？）\n什么是 Application 什么是 ArgoCD “Application”？ 对于 ArgoCD “Application”的快速解释：它是托管 ArgoCD 部署的 Kubernetes 集群 CRD 包含了应用程序的所有设置，如：\n要部署到哪个集群？ 与哪个 Git 存储库进行同步？ 其他部署设置 应用程序的 YAML 包含了部署您的存储库资源所需的所有信息，充当了在 ArgoCD 中管理应用程序的关键控制点。\nReference ​[1] docs/operator-manual/argocd-cm.yaml\n​[2] Getting started with multi-cluster K8S deployments using Argo CD\n​[3] https://medium.com/notive/managing-argocd-application-resources-1b2b4742ab90","title":"深入Argo - Application resources"},{"content":"引言 在 NGINX 中常用一种 “比较变量” 的手法，在编程语言中称为 “多路分支” (Case statement)，也就是 nginx map，需要注意的一点是，太低版本 NGINX MAP 中只能使用单变量\nBefore version 0.9.0 only a single variable could be specified in the first parameter. [1]\n下面将了解下 nginx map 的具体使用方式\nnginx map使用 Nginx 配置主要是声明性的，这同样应用于 MAP 指令，NGINX MAP 是定义在 http{} 级别，最大的特点是仅在引用时进行处理， 如果请求未触及使用 NGINX MAP 变量的配置部分，则不会执行该 map 变量查找。换句话来理解，当在上下文 server, Location, if 等中使用结果变量时（指定的不是计算结果，而是在需要时计算该结果的公式），才会被使用，在 NGINX 需要使用该变量之前，NGINX MAP 不会给请求增加任何开销。\nNGINX MAP 用于根据另一个变量的值创建一个变量，如下所示：\ntext 1 2 3 4 map $variable_to_check $variable_to_set { \u0026#34;check_if_variable_matches_me\u0026#34; \u0026#34;variable_matches_checked_value\u0026#34;; default \u0026#34;no_match\u0026#34;; } 在上面的例子中， 变量 $variable_to_set 的被设置的结果为：如果 $variable_to_check 值为 “check_if_variable_matches_me”， 那么 $variable_to_set 将被设置为值 “variable_matches_checked_value” ， 否则将设置为 “no_match”。\n上面的就是一个编程语言的分支语句，例如将上面语句转换为 bash shell，那么意思为\nbash 1 2 3 4 5 if [ \u0026#34;$variable_to_check\u0026#34; == \u0026#34;check_if_variable_matches_me\u0026#34; ]; then variable_to_set=\u0026#34;variable_matches_checked_value\u0026#34; else variable_to_set=\u0026#34;no_match\u0026#34; fi 当然作为分支语句，是支持多路分支的，他的写法如下：\ntext 1 2 3 4 5 map $thing $useful_variable { \u0026#34;thing_matches_me\u0026#34; \u0026#34;thing_matched_1\u0026#34;; \u0026#34;nope_thing_matches_me\u0026#34; \u0026#34;thing_matched_2\u0026#34;; default \u0026#34;no_match\u0026#34;; } 正则表达式在map中特性 正则表达式 (regular expression） 是一种用于匹配源变量中复杂字符串模式的有用方法，但它会增加解析表达式的开销。默认情况下，NGINX MAP 指令在每个请求处理过程中只执行一次查找，即正则表达式的开销被限制为一次查找。但启用 \u0026ldquo;volatile\u0026rdquo; 参数会关闭变量缓存，这意味着每次使用 NGINX MAP 时都需要执行一次完整查找，从而增加了请求的额外开销，尤其是在高负载情况下，特别是当使用正则表达式时，可能会导致性能下降。\n另外 \u0026ldquo;volatile\u0026rdquo; 参数的副作用是关闭了依赖于 \u0026ldquo;volatile\u0026rdquo; MAP 变量的缓存。如果需要复杂的正则表达式，那么在 MAP 中不要使用 \u0026ldquo;volatile\u0026rdquo; 参数，如果关联的 MAP 变量或任何依赖于该 MAP 的变量在多个地方被引用。这种情况在流量增加并导致查找操作增多时才会变得明显，此时CPU利用率会升高；最重要的是要注意，CPU利用率上升的原因可能不明显，因此需要谨慎使用 \u0026ldquo;volatile\u0026rdquo; 参数。\nvolatile 在计算机中的术语是 “易失性”，例如IP 地址暂时保存在Web 服务器的 易失性 存储器中；随后将被立即删除\n使用正则表达式检查漏洞接口 这里提出一个关于正则表达式的示例 “用于验证某些输入数据” 将其代理到一个有漏洞的后端服务器的 URI 中包含 \u0026ldquo;//\u0026rdquo; 字符，从而绕过了某些安全保护措施，观察到的模式符合以下格式，\u0026quot;//api/product\u0026quot; 或 \u0026ldquo;/api//product\u0026rdquo;。此外，在某些时间点会出现周期性的请求峰值，URI 中包含 \u0026ldquo;%2f\u0026rdquo; 或 \u0026ldquo;%2F\u0026rdquo;，类似这样：\u0026quot;/api%2Fproduct\u0026quot; 或 \u0026ldquo;%2fapi/product\u0026rdquo;。这些模式可以使用带有正则表达式的 MAP 来匹配，并可以在安全规则中使用 MAP 变量。\n使用到的匹配模式包含内置变量 $uri （不包含请求参数），这个参数可以用于上面提到的案例来做风险过滤，因为使用 $uri 来构建匹配规则看起来很适合。然而，这里存在一个问题，即 NGINX 可能会在请求处理的执行阶段修改或规范化 $uri 的值，这可能导致匹配规则不会匹配实际发送的 URI。\n规范化编码：NGINX 在规范化 $uri 时可能会解码其中的特殊字符。例如，%2f 可能被解码为 /，这意味着匹配规则很将不会匹配实际的编码。\n这里建议使用 NGINX 变量 $request_uri 来构建匹配器，而不是 $uri，以确保准确匹配请求的 URI，同时保留查询字符串。$request_uri 是一个 NGINX 内置变量，包含了请求的完整 URI，包括查询字符串。与之前提到的 $uri 不同，$request_uri 不会在请求处理的执行阶段修改或规范化，因此匹配内容与原请求保持原始不变。\n另外，为了简化处理，可以创建两个不同的 MAP，一个用于匹配实际内容，另一个用于 shun 规则。这种分离方式将允许在其他 MAP 中重复使用包含原始不变 URI 的 $uri_only 变量，如下所示：\ntext 1 2 3 4 5 6 7 8 9 10 map $request_uri $uri_only { \u0026#34;~^(?\u0026lt;u\u0026gt;[^\\?]+)\\?(?:.*)?\u0026#34; $u; default $request_uri; } map $uri_only $shun_if_client_is_a_baddy { \u0026#34;~\\/\\/\u0026#34; 1; \u0026#34;~*%2f\u0026#34; 1; default 0; } 在上面示例中，建立了 “分离” 方式的规则，下面是针对这组 map 含义解释：\nmap \\$request_uri \\$uri_only 创建了一个 map 将 $request_uri 的值用于匹配和分析\n~：告诉解析器后面的字符串应被解释为正则表达式。 ^：表示正则表达式将从 $request_uri 的字符串值的开头进行匹配。 ^(?\u0026lt;u\u0026gt;...)：这里创建了一个 “命名捕获组”，名为 u，它会匹配括号中的表达式，并将匹配的部分分配给 $u 变量。这个捕获组只在映射内部有效，不能在其他地方使用。 ^(?\u0026lt;u\u0026gt;[^\\?]+)：这部分正则表达式使用方括号来定义捕获的字符，匹配的内容是从开头（^）到第一个问号 ? 之前的所有字符。这样，它捕获了 $request_uri 的未修改部分，即不包括查询字符串的部分。 \\?(?:.*)?：这一部分匹配一个问号 ?，后面跟着一个可选的未捕获组，包含任意数量的任何字符直到字符串的末尾。尽管这部分正则表达式不是必要的，因为前面已经捕获了整个 URI 的未修改部分，但它被添加为完整性和可能的其他情况。 map \\$uri_only \\$shun_if_client_is_a_baddy ：这个 map，用于将 $uri_only 的值用于匹配和决定是否将客户端标记为不良客户端。以下是有关这行代码的解释：\n~：告诉解析器后面的字符串应被解释为正则表达式，用于匹配 $uri_only。 \\/\\/：这部分正则表达式匹配 $uri_only 中是否包含两个连续的正斜杠。如果匹配成功，将为 $shun_if_client_is_a_baddy 赋值 \u0026ldquo;1\u0026rdquo;，否则为 \u0026ldquo;0\u0026rdquo;。 ~*：这部分告诉解析器正则表达式应该以不区分大小写的方式进行匹配。 %2f：这部分正则表达式匹配 $uri_only 中是否包含字符串 \u0026ldquo;%2f\u0026rdquo;。如果匹配成功，将为 $shun_if_client_is_a_baddy 赋值 \u0026ldquo;1\u0026rdquo;，否则为 \u0026ldquo;0\u0026rdquo;。 规则应用 要使用上面 map 生效，可以将其放置在 server{} 上（同级），用于执行上述两个 map 并在请求处理阶段的早期触发响应：\ntext 1 2 3 if ($shun_if_client_is_a_baddy = 1) { return 403 \u0026#39;You shall not pass!!!\u0026#39;; } 比较变量 在 NGINX MAP 应用中经常遇到的示例是使用纯 NGINX 指令来比较两个变量是否相等。而在 nginx location 上下文中，并不推荐使用 if [2]，更多情况下执行变量比较通常推荐借助脚本语言来处理，例如下面一个 lua 示例\ntext 1 2 3 4 5 6 7 8 9 location /compare { access_by_lua_block { if ngx.var.variable1 == ngx.var.variable2 then ngx.say(\u0026#34;Variables are equal\u0026#34;) else ngx.say(\u0026#34;Variables are not equal\u0026#34;) end } } 但使用脚本语言增加了 NGINX 配置的复杂性以及需要内嵌或在引用单独文件中管理的另一段代码，在这种情况下 map 操作比较两个变量的场景是非常有用的。\ntext 1 2 3 4 5 # if delimeter between two variables is \u0026#39;:\u0026#39; map $thing1:$thing2 $do_things_match { \u0026#34;~^([^:]+):\\1$\u0026#34; 1; default 0; } 上面的规则解析如下：\n~：字符告诉解析器后面的字符串应被解释为正则表达式。 ^：这个符号表示正则表达式将从字符串值的开头进行匹配。 ^([^:]+)：这部分正则表达式创建了一个无名捕获组，用于捕获从字符串开头（^）到冒号 : 之前的所有字符（不包含冒号）。这个捕获的内容将被分配给一个名为 \u0026ldquo;\\1\u0026rdquo; 的后向引用变量。 :\\1$：这一部分用于将捕获的内容与 $thing2 变量的值进行比较。 如果 $thing1 和 $thing2 匹配，则这个表达式将被视为匹配（或为真），并将设置一个新的变量 $do_things_match 的值为 \u0026ldquo;1\u0026rdquo;。如果 $thing1 不匹配 $thing2，则表达式不匹配，并将设置 $do_things_match 的值为 \u0026ldquo;0\u0026rdquo;。\n下面是这个示例的一个应用，请求应该具有匹配的查询字符串 $arg_foo 和 X-BAR header\ntext 1 2 3 4 map $arg_FOO:$http_x_bar $shun_mismatched_payload { \u0026#34;~^([^:]+):\\1$\u0026#34; 1; default 0; } 那么可以在 server{} 段中增加判断\ntext 1 2 3 if ($shun_mismatched_payload = 1) { return 403 \u0026#39;You shall not pass!!!\u0026#39;; } 真实请求IP的获取 例如通常我们需要在日志中打印用户的真实IP，而这个IP隐藏的很深，通常引用了多个字段，例如\nRemote Address 是nginx与客户端进行TCP连接过程中，获得的客户端真实地址。Remote Address 无法伪造，因为建立 TCP 连接需要三次握手，如果伪造了源 IP，无法建立 TCP 连接，更不会有后面的 HTTP 请求。 一般情况下，在Envoy作为最外层代理时，此IP为真实的IP客户端IP X-Real-IP 是一个自定义头。X-Real-Ip 通常被 HTTP 代理用来表示与它产生 TCP 连接的设备 IP，这个设备可能是其他代理，也可能是真正的请求端。X-Real-Ip 目前并不属于任何标准，代理和 Web 应用之间可以约定用任何自定义头来传递这个信息。 X-Forwarded-For X-Forwarded-For 是一个扩展头。HTTP/1.1（RFC 2616）协议并没有对它的定义，它最开始是由 Squid 这个缓存代理软件引入，用来表示 HTTP 请求端真实 IP，现在已经成为事实上的标准，被各大 HTTP 代理、负载均衡等转发服务广泛使用，并被写入 RFC 7239（Forwarded HTTP Extension）标准之中。通常，X-Forwarded-For可被伪造，并且使用CDN会被重写 例如，下面从 CDN 获取真实 IP 的示例\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 map $http_x_connecting_ip $client_vsip { \u0026#34;\u0026#34; $http_x_real_ip; ~^(?P\u0026lt;firstAddr\u0026gt;[0-9\\.]+),?.*$ $fristAddr; } map $client_vsip $client_ydip { \u0026#34;\u0026#34; $http_Incap_client_IP; ~^(?P\u0026lt;firstAddr\u0026gt;[0-9\\.]+),?.*$ $fristAddr; } map $client_ydip $client_ip { \u0026#34;\u0026#34; $http_x_forwarded_for; ~^(?P\u0026lt;firstAddr\u0026gt;[0-9\\.]+),?.*$ $fristAddr; } map $client_ip $clientRealIP { \u0026#34;\u0026#34; $remote_addr; ~^(?P\u0026lt;firstAddr\u0026gt;[0-9\\.]+),?.*$ $fristAddr; } 这个 NGINX 配置示例中包含了一系列的 map 指令，用于创建变量映射，以根据不同的请求头信息来设置一系列变量的值。这些变量之间形成了一种链式映射，最终将请求的真实 IP 地址存储在 $clientRealIP 变量中。让我解释这些映射的作用：\n第一个 MAP 指令： $http_x_connecting_ip 是输入变量，根据请求中的 X-Connecting-IP 头部的值。 $client_vsip 是输出变量，它根据 $http_x_real_ip 或请求头部中的 X-Real-IP 值进行映射。 这个映射检查 $http_x_connecting_ip 的值，如果为空（\u0026quot;\u0026quot;），则将 $http_x_real_ip 的值赋给 $client_vsip，否则根据正则表达式提取 $http_x_connecting_ip 中的 IP 地址，并赋给 $client_vsip。 第二个 MAP 指令： $client_vsip 是输入变量，它是前一个映射的输出。 $client_ydip 是输出变量，它根据 $http_Incap_client_IP 或 $client_vsip 进行映射。 这个映射检查 $client_vsip 的值，如果为空（\u0026quot;\u0026quot;），则将 $http_Incap_client_IP 的值赋给 $client_ydip，否则根据正则表达式提取 $client_vsip 中的 IP 地址，并赋给 $client_ydip。 第三个 MAP 指令： $client_ydip 是输入变量，它是前一个映射的输出。 $client_ip 是输出变量，它根据 $http_x_forwarded_for 或 $client_ydip 进行映射。 这个映射检查 $client_ydip 的值，如果为空（\u0026quot;\u0026quot;），则将 $http_x_forwarded_for 的值赋给 $client_ip，否则根据正则表达式提取 $client_ydip 中的 IP 地址，并赋给 $client_ip。 第四个 MAP 指令： $client_ip 是输入变量，它是前一个映射的输出。 $clientRealIP 是输出变量，它根据 $remote_addr 或 $client_ip 进行映射。 这个映射检查 $client_ip 的值，如果为空（\u0026quot;\u0026quot;），则将 $remote_addr 的值赋给 $clientRealIP，否则根据正则表达式提取 $client_ip 中的 IP 地址，并赋给 $clientRealIP。 最后在 location 段中将 $clientRealIP 向后传递\ntext 1 proxy_set_header X-Forwarded-For $clientRealIP Reference ​[1] Module ngx_http_map_module\n​[2] If is Evil… when used in location context\n​[3] NGINX Map Comparisons\n","permalink":"https://www.161616.top/ngx-map/","summary":"引言 在 NGINX 中常用一种 “比较变量” 的手法，在编程语言中称为 “多路分支” (Case statement)，也就是 nginx map，需要注意的一点是，太低版本 NGINX MAP 中只能使用单变量\nBefore version 0.9.0 only a single variable could be specified in the first parameter. [1]\n下面将了解下 nginx map 的具体使用方式\nnginx map使用 Nginx 配置主要是声明性的，这同样应用于 MAP 指令，NGINX MAP 是定义在 http{} 级别，最大的特点是仅在引用时进行处理， 如果请求未触及使用 NGINX MAP 变量的配置部分，则不会执行该 map 变量查找。换句话来理解，当在上下文 server, Location, if 等中使用结果变量时（指定的不是计算结果，而是在需要时计算该结果的公式），才会被使用，在 NGINX 需要使用该变量之前，NGINX MAP 不会给请求增加任何开销。\nNGINX MAP 用于根据另一个变量的值创建一个变量，如下所示：\ntext 1 2 3 4 map $variable_to_check $variable_to_set { \u0026#34;check_if_variable_matches_me\u0026#34; \u0026#34;variable_matches_checked_value\u0026#34;; default \u0026#34;no_match\u0026#34;; } 在上面的例子中， 变量 $variable_to_set 的被设置的结果为：如果 $variable_to_check 值为 “check_if_variable_matches_me”， 那么 $variable_to_set 将被设置为值 “variable_matches_checked_value” ， 否则将设置为 “no_match”。","title":"nginx中的多路分支 - nginx map"},{"content":"GKE标准机器通常费用组成为 “集群管理费” + 标准GCE主机费用，以香港为例\n主机规格 CPU (CORE/Mon) MEM (GB/Mon) E2( E2Balanced: N1, N2)：费用优化 $23.77 $2.99 N1 (旧型号的Intel Sandy Bridge、Ivy Bridge、Haswell、Broadwell、Skylake) $38.45 $4.55 N2 CPU $31.9 $4.01 N2D CPU $27.75 $3.48 选择核心的标准（强要求）\nintel 2的公差，例如1, 2, 4, 6, 8, 10 AMD 2, 4, 8, 16, 32 只有 “费用优化” (cose-optimized) 的可以自定义配置，计算优化，内存优化等都是固定配置\n新加坡的价格是香港价格的 \u0026ldquo;90%\u0026rdquo;\n","permalink":"https://www.161616.top/price-of-gke/","summary":"GKE标准机器通常费用组成为 “集群管理费” + 标准GCE主机费用，以香港为例\n主机规格 CPU (CORE/Mon) MEM (GB/Mon) E2( E2Balanced: N1, N2)：费用优化 $23.77 $2.99 N1 (旧型号的Intel Sandy Bridge、Ivy Bridge、Haswell、Broadwell、Skylake) $38.45 $4.55 N2 CPU $31.9 $4.01 N2D CPU $27.75 $3.48 选择核心的标准（强要求）\nintel 2的公差，例如1, 2, 4, 6, 8, 10 AMD 2, 4, 8, 16, 32 只有 “费用优化” (cose-optimized) 的可以自定义配置，计算优化，内存优化等都是固定配置\n新加坡的价格是香港价格的 \u0026ldquo;90%\u0026rdquo;","title":"GKE标准集群价格选择示例"},{"content":" 相关阅读：深入理解Kubernetes 4A - Admission Control源码解析\n准入 (Admission) 是 Kubernetes 提供 4A 安全认证中的一个步骤，在以前版本中 (1,26-)，官方提供了 webhook 功能，使用户可以自行的定义 Kubernetes 资源准入规则，但这些是有成本的，需要自行开发 webhook，下图是 Kubernetes准入控制流程。\n图：Kubernetes API 请求的请求处理步骤图 Source：https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/ 在 Kubernetes 1.26 时 引入了 ValidatingAdmissionPolicy alpha 版，这个功能等于将 Admission Webhook controller 作为了一个官方扩展版，通过资源进行自行扩展，通过这种方式带来下面优势：\n减少了准入请求延迟，提高可靠性和可用性 能够在不影响可用性的情况下失败关闭 避免 webhooks 的操作负担 ValidatingAdmissionPolicy 说明 验证准入策略提供一种声明式的、进程内的替代方案来验证准入 Webhook。\n验证准入策略使用通用表达语言 (Common Expression Language，CEL) 来声明策略的验证规则。 验证准入策略是高度可配置的，使配置策略的作者能够根据集群管理员的需要， 定义可以参数化并限定到资源的策略\n下面是一个 ValidatingAdmissionPolicy 的示例，配置 Deployment 必须拥有的副本数的限制\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: admissionregistration.k8s.io/v1alpha1 kind: ValidatingAdmissionPolicy metadata: name: \u0026#34;demo-policy.example.com\u0026#34; spec: matchConstraints: resourceRules: - apiGroups: [\u0026#34;apps\u0026#34;] apiVersions: [\u0026#34;v1\u0026#34;] operations: [\u0026#34;CREATE\u0026#34;, \u0026#34;UPDATE\u0026#34;] resources: [\u0026#34;deployments\u0026#34;] validations: - expression: \u0026#34;object.spec.replicas \u0026lt;= 5\u0026#34; 这里 规格 Spec 中有两个关键属性：\nexpression 字段包含用于验证的 CEL 表达式 matchConstraints 声明什么表达式应用的类型 在声明完规则后还需要应用到资源上才生效，这里 Kubernetes 还有另外一个资源类型 ValidatingAdmissionPolicyBinding 可以声明 “资源” 和 “策略” 绑定到一起，例如下面示例\nyaml 1 2 3 4 5 6 7 8 9 10 11 apiVersion: admissionregistration.k8s.io/v1alpha1 kind: ValidatingAdmissionPolicyBinding metadata: name: \u0026#34;demo-binding-test.example.com\u0026#34; spec: policyName: \u0026#34;demo-policy.example.com\u0026#34; validationActions: [Deny] matchResources: namespaceSelector: matchLabels: environment: test 在这里需要注意的是，每个 ValidatingAdmissionPolicyBinding 必须指定一个或多个 validationActions 来声明如何执行策略的 validations，其中 validationActions 包括：\nDeny: 验证失败会导致请求被拒绝。 Warn: 验证失败会作为警告报告给请求客户端。 Audit: 验证失败会包含在 API 请求的审计事件中。 这三个值可以同时设置，表示同时生效，例如：同时向客户端发出验证失败的警告并记录验证失败的审计记录，可以按照下面配置\nyaml 1 validationActions: [Warn, Audit] 其中，Deny 和 Warn 不能一起使用，因为这种组合会不必要地将验证失败重复输出到 API 响应体和 HTTP 警告头中。\nValidatingAdmissionPolicy 的高级可用性 ValidatingAdmissionPolicy 也是一种高度可自由配置的功能，这种方式使策略维护者能够自定义==可根据需要参数化并限定资源范围的策略== 。 例如下面一个策略示例\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: admissionregistration.k8s.io/v1alpha1 kind: ValidatingAdmissionPolicy metadata: name: \u0026#34;demo-policy.example.com\u0026#34; spec: paramKind: apiVersion: rules.example.com/v1 # 这个资源是通过CRD自定义的集群资源 kind: ReplicaLimit matchConstraints: resourceRules: - apiGroups: [\u0026#34;apps\u0026#34;] apiVersions: [\u0026#34;v1\u0026#34;] operations: [\u0026#34;CREATE\u0026#34;, \u0026#34;UPDATE\u0026#34;] resources: [\u0026#34;deployments\u0026#34;] validations: - expression: \u0026#34;object.spec.replicas \u0026lt;= params.maxReplicas\u0026#34; 在这个示例中，使用了 paramKind，这个可以使得管理员可以通过 CRD 的形式扩展策略本身，而这个 CRD 资源可以定义为下面示例所提到的\nyaml 1 2 3 4 5 apiVersion: rules.example.com/v1 # 使用 CRD 方式定义策略本身参数 kind: ReplicaLimit metadata: name: \u0026#34;demo-params-production.example.com\u0026#34; maxReplicas: 1000 最终使用了 ValidatingAdmissionPolicyBinding 资源将 “策略” , “规则” , “限制参数” 进行了解耦合，更灵活性的引用了 ValidatingAdmissionPolicy\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: admissionregistration.k8s.io/v1alpha1 kind: ValidatingAdmissionPolicyBinding metadata: name: \u0026#34;demo-binding-production.example.com\u0026#34; spec: policyName: \u0026#34;demo-policy.example.com\u0026#34; paramRef: name: \u0026#34;demo-params-production.example.com\u0026#34; matchResources: namespaceSelector: matchExpressions: - key: environment operator: In values: - production 如何启用 ValidatingAdmissionPolicy 确保 ValidatingAdmissionPolicy 启用特性门控 (feature gates)。 确保 admissionregistration.k8s.io/v1beta1 API 启用。 bash 1 --feature-gates=ValidatingAdmissionPolicy=true 总结 ValidatingAdmissionPolicy 作为 kube-apiserver 内置的功能，减少了 Kubernetes 使用者的维护成本，避免了 webhook 不可控因素影响整个集群，并带来个更便捷的管理方式，使得 Kubernetes 越来越像从工具传变成一个产品，大大加强了 Kubernetes 使用者灵活管控集群的方式。更高级的用法，以及 CEL 的使用可以参考附录官方文档\n深入理解Kubernetes 4A - Admission Control源码解析\nReference ​[1] 验证准入策略（ValidatingAdmissionPolicy）\n​[2] Kubernetes validation admission policies\n​[3] Kubernetes 1.26: Introducing Validating Admission Policies\n","permalink":"https://www.161616.top/kubernetes-validatingadmissionpolicy/","summary":"相关阅读：深入理解Kubernetes 4A - Admission Control源码解析\n准入 (Admission) 是 Kubernetes 提供 4A 安全认证中的一个步骤，在以前版本中 (1,26-)，官方提供了 webhook 功能，使用户可以自行的定义 Kubernetes 资源准入规则，但这些是有成本的，需要自行开发 webhook，下图是 Kubernetes准入控制流程。\n图：Kubernetes API 请求的请求处理步骤图 Source：https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/ 在 Kubernetes 1.26 时 引入了 ValidatingAdmissionPolicy alpha 版，这个功能等于将 Admission Webhook controller 作为了一个官方扩展版，通过资源进行自行扩展，通过这种方式带来下面优势：\n减少了准入请求延迟，提高可靠性和可用性 能够在不影响可用性的情况下失败关闭 避免 webhooks 的操作负担 ValidatingAdmissionPolicy 说明 验证准入策略提供一种声明式的、进程内的替代方案来验证准入 Webhook。\n验证准入策略使用通用表达语言 (Common Expression Language，CEL) 来声明策略的验证规则。 验证准入策略是高度可配置的，使配置策略的作者能够根据集群管理员的需要， 定义可以参数化并限定到资源的策略\n下面是一个 ValidatingAdmissionPolicy 的示例，配置 Deployment 必须拥有的副本数的限制\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: admissionregistration.","title":"K8S Admission Webhook官方扩展版 - ValidatingAdmissionPolicy"},{"content":"“IP 伪装” 通常应用于云环境中，例如 GKE, AWS, CCE 等云厂商都有使用 “IP伪装” 技术，本文将围绕 “IP伪装” 技术本身，以及这项技术在 Kubernetes 集群中的实现应用 ip-masq-agent 的源码分析，以及 ”IP伪装“ 能为 Kubernetes 带来什么作用，这三个方向阐述。\n什么是IP伪装？ IP 伪装 (IP Masquerade) 是 Linux 中的一个网络功能，一对多 (1 to Many) 的网络地址转换 (NAT) 的功能 。\nIP 伪装允许一组计算机通过 “伪装” 网关无形地访问互联网。对于互联网上的其他计算机，出站流量将看起来来自于 IP MASQ 服务器本身。互联网上任何希望发回数据包（作为答复）的主机必须将该数据包发送到网关 （IP MASQ 服务器本身）。记住，网关（IP MASQ 服务器本身）是互联网上唯一可见的主机。网关重写目标地址，用被伪装的机器的 IP 地址替换自己的地址，并将该数据包转发到本地网络进行传递。\n除了增加的功能之外，IP Masquerade 为创建一个高度安全的网络环境提供了基础。通过良好构建的防火墙，突破经过良好配置的伪装系统和内部局域网的安全性应该会相当困难。\nIP Masquerade 从 Linux 1.3.x 开始支持，目前基本所有 Linux 发行版都带有 IP 伪装的功能\n什么情况下不需要IP伪装 已经连接到互联网的独立主机 为其他主机分配了多个公共地址 IP伪装在Kubernetes集群中的应用 IP 伪装通常应用在大规模 Kubernetes 集群中，主要用于解决 “地址冲突” 的问题，例如在 GCP 中，通常是一种 IP 可路由的网络模型，例如分配给 Pod service 的 ClusterIP 只能在 Kubernetes 集群内部可用，而分配 IP CIDR 又是一种不可控的情况，假设，我们为 k8s 分配的 IP CIDR 段如下表所示：\n角色 IP CIDR Kubernetes Nodes 10.0.0.0/16 Kubernetes Services 10.1.0.0/16 Kubernetes Pods 192.168.0.0/24 其他不可控业务网段 192.168.0.0/24 通过上表可以看出，通常管理员在管理 Kubernetes 集群会配置三个网段，此时的配置，如果 Pod 需要与其他节点网络进行通讯（如我需要连接数据库），那么可能会出现 ”IP 重叠“ 的现象，尤其是在公有云环境中，用户在配置 Kubernetes 集群网络时不知道数据中心所保留的 CIDR 是什么，在这种情况下就很容易产生 ”IP 重叠“ 的现象，为了解决这个问题，Kubernetes 提出了一种使用 “IP伪装” 技术来解决这个问题。\n在不使用 IP Masquerade 的情况下， Kubernetes 集群管理员如果在规划集群 CIDR 时，必须要了解了解整个组织中已预留/未使用的 CIDR 规划。\nIP Masquerade Agent IP伪装在 kubernetes 中的应用是名为 ip-masq-agent 的项目， ip-masq-agent 是用于配置 iptables 规则，以便在将流量发送到集群节点的 IP 和集群 IP 范围之外的目标时处理伪装节点或 Pod 的 IP 地址。这本质上隐藏了集群节点 IP 地址后面的 Pod IP 地址。在某些环境中，去往\u0026quot;外部\u0026quot;地址的流量必须从已知的机器地址发出。 例如，在 GCP 中，任何到互联网的流量都必须来自 VM 的 IP。 使用容器时，如 GKE，从 Pod IP 发出的流量将被拒绝出站。 为了避免这种情况，我们必须将 Pod IP 隐藏在 VM 自己的 IP 地址后面 - 通常称为\u0026quot;伪装\u0026quot;。 默认情况下，代理配置为将 RFC 1918指定的三个私有 IP 范围视为非伪装 CIDR。 这些范围是 10.0.0.0/8、172.16.0.0/12 和 192.168.0.0/16。 默认情况下，代理还将链路本地地址（169.254.0.0/16）视为非伪装 CIDR。 代理程序配置为每隔 60 秒从 /etc/config/ip-masq-agent 重新加载其配置， 这也是可修改的。\n图：ip-masq-agent工作原理 Source：https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/ip-masq-agent/\n默认情况下，CIDR 10.0.0.0/8，172.16.0.0/12, 192.168.0.0/16 范围内的流量不会被伪装。 任何其他 CIDR 流量将被伪装。 Pod 访问本地目的地的例子，可以是其节点 (Node) 的 IP 地址，另一节点 (Node) 的地址或集群的 IP 地址 (ClusterIP) 范围内的一个 IP 地址。 默认情况下，任何其他流量都将伪装。以下条目展示了 ip-masq-agent 的默认使用的规则：\nbash 1 2 3 4 5 6 7 $ iptables -t nat -L IP-MASQ-AGENT target prot opt source destination RETURN all -- anywhere 169.254.0.0/16 /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL RETURN all -- anywhere 10.0.0.0/8 /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL RETURN all -- anywhere 172.16.0.0/12 /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL RETURN all -- anywhere 192.168.0.0/16 /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL MASQUERADE all -- anywhere anywhere /* ip-masq-agent: outbound traffic should be subject to MASQUERADE (this match must come after cluster-local CIDR matches) */ ADDRTYPE match dst-type !LOCAL 部署 ip-masq-agent ip-masq-agent 的部署可以直接使用官方提供的资源清单 [1]\nbash 1 kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/ip-masq-agent/master/ip-masq-agent.yaml 清除 ip-masq-agent\nyaml 1 kubectl delete -f https://raw.githubusercontent.com/kubernetes-sigs/ip-masq-agent/master/ip-masq-agent.yaml 部署后需要同时将对应的节点标签应用于集群中希望代理运行的任何节点\nbash 1 kubectl label nodes my-node node.kubernetes.io/masq-agent-ds-ready=true 配置好之后，需要创建配置，以对不伪装的地址增加白名单\nbash 1 2 3 nonMasqueradeCIDRs: - 10.0.0.0/8 resyncInterval: 60s ip-masq-agent 深入解析 ip-masq-agent 的代码很少，只有400多行，但是作用却很大，直接可以解决管理员集群网络规划与大拓扑网络的网络冲突问题，下面就分析他的原理，以及如何完成集群 IP 伪装功能\nip-masq-agent源码的分析 ip-masq-agent 只有这一个文件 cmd/ip-masq-agent/ip-masq-agent.go，包含了整个的业务逻辑\n首先在 main() 启动时，定义了这个链的名称，之后调用 Run()\ngo 1 2 3 4 5 masqChain = utiliptables.Chain(*masqChainFlag) .. m.Run() 在 Run() 中，只是做了周期性同步\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func (m *MasqDaemon) Run() { // Periodically resync to reconfigure or heal from any rule decay for { func() { defer time.Sleep(time.Duration(m.config.ResyncInterval)) // resync config if err := m.osSyncConfig(); err != nil { glog.Errorf(\u0026#34;error syncing configuration: %v\u0026#34;, err) return } // resync rules if err := m.syncMasqRules(); err != nil { glog.Errorf(\u0026#34;error syncing masquerade rules: %v\u0026#34;, err) return } // resync ipv6 rules if err := m.syncMasqRulesIPv6(); err != nil { glog.Errorf(\u0026#34;error syncing masquerade rules for ipv6: %v\u0026#34;, err) return } }() } } 重点就在 m.osSyncConfig() , 这里做的是同步实际的规则\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func (m *MasqDaemon) syncMasqRules() error { // 指定的链是否存在，如果不存在则创建，masqChain全局变量 是 main() 中初始化的名称，默认为IP-MASQ-AGENT m.iptables.EnsureChain(utiliptables.TableNAT, masqChain) // ensure that any non-local in POSTROUTING jumps to masqChain if err := m.ensurePostroutingJump(); err != nil { return err } // build up lines to pass to iptables-restore lines := bytes.NewBuffer(nil) writeLine(lines, \u0026#34;*nat\u0026#34;) writeLine(lines, utiliptables.MakeChainLine(masqChain)) // effectively flushes masqChain atomically with rule restore // local-link cidr 不伪装（\u0026#34;169.254.0.0/16\u0026#34;） 固定值 if !m.config.MasqLinkLocal { writeNonMasqRule(lines, linkLocalCIDR) } // 用户定义的不伪装的 CIDR 部分 for _, cidr := range m.config.NonMasqueradeCIDRs { if !isIPv6CIDR(cidr) { writeNonMasqRule(lines, cidr) } } // masquerade all other traffic that is not bound for a --dst-type LOCAL destination writeMasqRule(lines) writeLine(lines, \u0026#34;COMMIT\u0026#34;) if err := m.iptables.RestoreAll(lines.Bytes(), utiliptables.NoFlushTables, utiliptables.NoRestoreCounters); err != nil { return err } return nil } 看完同步规则后，了解到上面就是两个操作，”伪装“ 和 “不伪装” 的操作如下所示\n不伪装部分实际上就是关键词 RETURN\ngo 1 2 3 func writeNonMasqRule(lines *bytes.Buffer, cidr string) { writeRule(lines, utiliptables.Append, masqChain, nonMasqRuleComment, \u0026#34;-d\u0026#34;, cidr, \u0026#34;-j\u0026#34;, \u0026#34;RETURN\u0026#34;) } 伪装部分实际上就是关键词 MASQUERADE\ngo 1 2 3 func writeMasqRule(lines *bytes.Buffer) { writeRule(lines, utiliptables.Append, masqChain, masqRuleComment, \u0026#34;-j\u0026#34;, \u0026#34;MASQUERADE\u0026#34;, \u0026#34;--random-fully\u0026#34;) } 伪装网络包的分析 创建一个 ip-masq-agent 的配置文件\nbash 1 2 3 4 5 6 tee \u0026gt; config \u0026lt;\u0026lt;EOF nonMasqueradeCIDRs: - 10.244.0.0/16 - 192.0.0.0/8 resyncInterval: 60s EOF 创建 configmap\nyaml 1 kubectl create configmap ip-masq-agent --from-file=config --namespace=kube-system 验证规则是否生效\nbash 1 2 3 4 5 6 7 $ iptables -t nat -L IP-MASQ-AGENT Chain IP-MASQ-AGENT (1 references) target prot opt source destination RETURN all -- anywhere link-local/16 /* ip-masq-agent: local traffic is not subject to MASQUERADE */ RETURN all -- anywhere 10.244.0.0/16 /* ip-masq-agent: local traffic is not subject to MASQUERADE */ RETURN all -- anywhere 192.0.0.0/8 /* ip-masq-agent: local traffic is not subject to MASQUERADE */ MASQUERADE all -- anywhere anywhere /* ip-masq-agent: outbound traffic is subject to MASQUERADE (must be last in chain) */ 抓包查看包是否被伪装\nbash 1 2 3 4 5 $ cpid=`docker inspect --format \u0026#39;{{.State.Pid}}\u0026#39; 6b0a92ca4327` $ nsenter -t $cpid -n ifconfig eth0|grep inet inet 10.244.196.132 netmask 255.255.255.255 broadcast 10.244.196.132 $ nsenter -t $cpid -n ping 10.0.0.2 $ tcpdump -i any icmp and host 10.0.0.2 -w icap.cap 通过导出的 wireshark 包，可以很清楚的看到，去往 10.0.0.2 的已经被伪装了\n图：Kubernetes集群节点IP伪装抓包 Reference ​[1] ip-masq-agent.yaml\n​[2] IP Masquerade Agent 用户指南\n​[3] IP address management strategy — a crucial aspect of running GKE\n","permalink":"https://www.161616.top/ch24-ip-masq/","summary":"“IP 伪装” 通常应用于云环境中，例如 GKE, AWS, CCE 等云厂商都有使用 “IP伪装” 技术，本文将围绕 “IP伪装” 技术本身，以及这项技术在 Kubernetes 集群中的实现应用 ip-masq-agent 的源码分析，以及 ”IP伪装“ 能为 Kubernetes 带来什么作用，这三个方向阐述。\n什么是IP伪装？ IP 伪装 (IP Masquerade) 是 Linux 中的一个网络功能，一对多 (1 to Many) 的网络地址转换 (NAT) 的功能 。\nIP 伪装允许一组计算机通过 “伪装” 网关无形地访问互联网。对于互联网上的其他计算机，出站流量将看起来来自于 IP MASQ 服务器本身。互联网上任何希望发回数据包（作为答复）的主机必须将该数据包发送到网关 （IP MASQ 服务器本身）。记住，网关（IP MASQ 服务器本身）是互联网上唯一可见的主机。网关重写目标地址，用被伪装的机器的 IP 地址替换自己的地址，并将该数据包转发到本地网络进行传递。\n除了增加的功能之外，IP Masquerade 为创建一个高度安全的网络环境提供了基础。通过良好构建的防火墙，突破经过良好配置的伪装系统和内部局域网的安全性应该会相当困难。\nIP Masquerade 从 Linux 1.3.x 开始支持，目前基本所有 Linux 发行版都带有 IP 伪装的功能\n什么情况下不需要IP伪装 已经连接到互联网的独立主机 为其他主机分配了多个公共地址 IP伪装在Kubernetes集群中的应用 IP 伪装通常应用在大规模 Kubernetes 集群中，主要用于解决 “地址冲突” 的问题，例如在 GCP 中，通常是一种 IP 可路由的网络模型，例如分配给 Pod service 的 ClusterIP 只能在 Kubernetes 集群内部可用，而分配 IP CIDR 又是一种不可控的情况，假设，我们为 k8s 分配的 IP CIDR 段如下表所示：","title":"Kubernetes集群中的IP伪装 - ip-masq-agent"},{"content":"在Prometheus node-exporter中，存在多个网络监控指标指标标志着主机的网络状态，但是大家常常忽略这些指标，而这些指标又很重要，这些指标的来源是根据Linux网络子系统中的多个计数器定义的，本文就解开这些TCP计数器的面目。\nTcpExtListenOverflows 和 TcpExtListenDrops 当内核从客户端接收到 SYN 时，如果 TCP 接受队列已满，内核将丢弃 SYN 并将 TcpExtListenOverflows +1。同时内核也会给TcpExtListenDrops +1。当 TCP 套接字处于 LISTEN 状态，并且内核需要丢弃数据包时，内核总是将 TcpExtListenDrops +1。因此，增加 TcpExtListenOverflows 将使 TcpExtListenDrops 同时增加，但在不增加 TcpExtListenOverflows 的情况下，TcpExtListenDrops 也会增加，例如内存分配失败也会导致 TcpExtListenDrops 增加。\n以上解释基于内核 4.10 或更高版本，在旧内核上，当 TCP 接受队列已满时，TCP Stack有不同的行为。在旧内核上，TCP Stack不会丢弃 SYN，它会完成 3 次握手。当接受队列已满时，TCP 堆栈会将套接字保留在 TCP 半开队列中。由于处于半开队列中，TCP 堆栈将在指数退避计时器上发送 SYN+ACK，在客户端回复 ACK 后，TCP Stack检查接受队列是否仍满，如果未满，则将套接字移至接受队列如果队列已满，则将套接字保留在半开队列中，下次客户端回复ACK时，该套接字将有另一次机会移至接受队列。\n这两个计数器在 node_expoter 中的指标是：\nnode_netstat_TcpExt_ListenDrops node_netstat_TcpExt_ListenOverflows TcpInSegs 和 TcpOutSegs TcpInSegs 和 TcpOutSegs 都是被定义在 RFC1213 [1]\nTcpInSegs 是指 TCP layer 接收到的数据包数量，包括错误接收的数据包，例如校验和错误、无效的TCP头等。只有一个错误不会被包含在内：如果第 2 层目标地址不是 NIC 的第 2 层地址。如果数据包是多播或广播数据包，或者 NIC 处于混杂模式，则可能会发生这种情况。在这些情况下，数据包将被传递到 TCP 层，但 TCP 层将在增加 TcpInSegs 之前丢弃这些数据包。 TcpInSegs 计数器不知道 GRO (Generic Receive Offload)。因此，如果两个数据包被 GRO 合并，TcpInSegs 计数器只会增加 1。\nTcpOutSegs 是指 TCP layer 发送的数据包数量，它排除了重传的数据包。但它包括 SYN, ACK 和 RST 数据包。与 TcpInSegs 不同，TcpOutSegs 能够识别 GSO (Generic Receive Offload)，因此如果数据包被 GSO 分割为 2，TcpOutSegs 将增加 2。\n这两个计数器在 node_expoter 中的指标是：\nnode_netstat_Tcp_InSegs node_netstat_Tcp_OutRsts TcpPassiveOpens 和 TcpActiveOpens TcpPassiveOpens 和 TcpActiveOpens 都是被定义在 RFC1213 [1]\nTcpPassiveOpens 是指 TCP层收到一个SYN，回复一个 SYN+ACK，进入 SYN-RCVD 状态。\nTcpActiveOpens 是指 TCP 层发送了一个SYN，并进入 SYN-SENT 状态。每次 TcpActiveOpens +1，TcpOutSegs 应始终+1。\n这两个计数器在 node_expoter 中的指标是：\nnode_netstat_Tcp_PassiveOpens node_netstat_Tcp_ActiveOpens TcpEstabResets 和 TcpOutRsts TcpEstabResets 和 TcpOutRsts 都是被定义在 RFC1213 [1]\nTcpEstabResets 指 TCP 连接从 ESTABLISHED 状态或 CLOSE-WAIT 状态直接转换到 CLOSED 状态的次数。\nTcpOutRsts 指发送的包含 RST 标志的 TCP 段的数量。\n这两个计数器在 node_expoter 中的指标是：\nnode_netstat_Tcp_OutRsts node_netstat_Tcp_ActiveOpens TCPSynRetrans SYN 和 SYN/ACK 重传 (Retransmit )次数，将重传分为 SYN, 快速重传, 超时重传等。\n这个计数器在 node_expoter 中的指标是：\nnode_netstat_TcpExt_TCPSynRetrans Reference ​[1] RFC1213\n​[2] SNMP counter\n","permalink":"https://www.161616.top/linux-network-conunter/","summary":"在Prometheus node-exporter中，存在多个网络监控指标指标标志着主机的网络状态，但是大家常常忽略这些指标，而这些指标又很重要，这些指标的来源是根据Linux网络子系统中的多个计数器定义的，本文就解开这些TCP计数器的面目。\nTcpExtListenOverflows 和 TcpExtListenDrops 当内核从客户端接收到 SYN 时，如果 TCP 接受队列已满，内核将丢弃 SYN 并将 TcpExtListenOverflows +1。同时内核也会给TcpExtListenDrops +1。当 TCP 套接字处于 LISTEN 状态，并且内核需要丢弃数据包时，内核总是将 TcpExtListenDrops +1。因此，增加 TcpExtListenOverflows 将使 TcpExtListenDrops 同时增加，但在不增加 TcpExtListenOverflows 的情况下，TcpExtListenDrops 也会增加，例如内存分配失败也会导致 TcpExtListenDrops 增加。\n以上解释基于内核 4.10 或更高版本，在旧内核上，当 TCP 接受队列已满时，TCP Stack有不同的行为。在旧内核上，TCP Stack不会丢弃 SYN，它会完成 3 次握手。当接受队列已满时，TCP 堆栈会将套接字保留在 TCP 半开队列中。由于处于半开队列中，TCP 堆栈将在指数退避计时器上发送 SYN+ACK，在客户端回复 ACK 后，TCP Stack检查接受队列是否仍满，如果未满，则将套接字移至接受队列如果队列已满，则将套接字保留在半开队列中，下次客户端回复ACK时，该套接字将有另一次机会移至接受队列。\n这两个计数器在 node_expoter 中的指标是：\nnode_netstat_TcpExt_ListenDrops node_netstat_TcpExt_ListenOverflows TcpInSegs 和 TcpOutSegs TcpInSegs 和 TcpOutSegs 都是被定义在 RFC1213 [1]\nTcpInSegs 是指 TCP layer 接收到的数据包数量，包括错误接收的数据包，例如校验和错误、无效的TCP头等。只有一个错误不会被包含在内：如果第 2 层目标地址不是 NIC 的第 2 层地址。如果数据包是多播或广播数据包，或者 NIC 处于混杂模式，则可能会发生这种情况。在这些情况下，数据包将被传递到 TCP 层，但 TCP 层将在增加 TcpInSegs 之前丢弃这些数据包。 TcpInSegs 计数器不知道 GRO (Generic Receive Offload)。因此，如果两个数据包被 GRO 合并，TcpInSegs 计数器只会增加 1。","title":"Linux网络子系统中的计数器"},{"content":"GitOps 最初由 Weaveworks (weave cni的组织) 在 2017 年的博客中提出 [1]，使用 “Git” 作为 CI/CD 的 “单一事实来源”，将代码的更改集成到每个项目的存储库中，并使用拉取请求来管理 infra 和部署。 在理解上就可以理解为 “是一种基于 git 的操作框架”\nArgo CD 是一种 kubernetes 之上的 “声明式” (declarative) 的 gitops CD， 在本文作为了解如何在 Kubernetes 集群中安装和配置 Argo CD。\n前提准备 想要安装 Argo CD 首先环境需要具备如下：\n已经安装好 kubectl 命令行工具 拥有 kubeconfig 文件 一个可供测试的 Kubernetes 集群，如：kind, minikube, kubeadm, binary 等任意的集群 步骤1 - 选择适配 kubernetes 版本的 Argo 根据官方的解释， Argo CD 在任何给定时刻所支持的版本，这些版本是 N 和 N - 1 次要版本的最新修补版本 (x.x.new)。这些 Argo CD 版本与 Kubernetes 项目官方支持的 Kubernetes 版本相一致，通常是 Kubernetes 的最近发布的 3 个版本。\n即可以理解为 Argo N \u0026amp; N-1 支持的 Kubernetes 版本为 N-2\n举例来说，如果 Argo CD 的最新次要版本是 2.4.3 和 2.3.5, 那么所支持的 K8S 版本则如下面列表\nArgo CD 2.4.3 on Kubernetes 1.24 Argo CD 2.4.3 on Kubernetes 1.23 Argo CD 2.4.3 on Kubernetes 1.22 Argo CD 2.3.5 on Kubernetes 1.24 Argo CD 2.3.5 on Kubernetes 1.23 Argo CD 2.3.5 on Kubernetes 1.22 而在较新版本中，Argo官方给出了 Argo CD 在 K8S 什么版本之上，测试什么版本的 Argo (2.8起)，而 Argo 版本又和 K8S 版本较为吻合，例如最新版 Kubernetes 为 1.28 (2023.10)，那么 Argo 最新版 (2.8).\n假设现在你的 kubernetes 集群版本为 1.19.10, 那么按照这个规律基本上符合的为 1.8/2.0，如果 kubernetes 集群版本为 1.18.10, 那么符合的版本为 1.7/1.8，对于旧版本，尽量选择最符合的，不要按照这个规律，因为规律是 2.3+ 才提出的。\n对于每一个版本的部署文件路径，可以通过 argo 仓库 manifest 目录中寻找 github.com/argoproj/argo-cd/tree/version/manifests\n步骤2 - 执行安装 在本实例中，安装环境为 kubernetes 1.19.10 ，选择的 Argo CD 版本为 2.0，那么只需要找到其 “资源清单” 文件即可，需要注意的是，版本号要与仓库中 “tag” 保持一致\nbash 1 2 ARGO_VERSION=v2.0.5 https://raw.githubusercontent.com/argoproj/argo-cd/${ARGO_VERSION}/manifests/install.yaml 使用 kubectl 应用这个文件即可\nbash 1 kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-cd/${ARGO_VERSION}/manifests/install.yaml 如果需要部署在特定的 NS 内，可以使用下面命令\nbash 1 kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/${ARGO_VERSION}/manifests/install.yaml 安装就是应用对应资源到 Kubernetes 集群\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 customresourcedefinition.apiextensions.k8s.io/applications.argoproj.io created customresourcedefinition.apiextensions.k8s.io/appprojects.argoproj.io created serviceaccount/argocd-application-controller created serviceaccount/argocd-dex-server created serviceaccount/argocd-redis created serviceaccount/argocd-server created role.rbac.authorization.k8s.io/argocd-application-controller created role.rbac.authorization.k8s.io/argocd-dex-server created role.rbac.authorization.k8s.io/argocd-redis created role.rbac.authorization.k8s.io/argocd-server created clusterrole.rbac.authorization.k8s.io/argocd-application-controller created clusterrole.rbac.authorization.k8s.io/argocd-server created rolebinding.rbac.authorization.k8s.io/argocd-application-controller created rolebinding.rbac.authorization.k8s.io/argocd-dex-server created rolebinding.rbac.authorization.k8s.io/argocd-redis created rolebinding.rbac.authorization.k8s.io/argocd-server created clusterrolebinding.rbac.authorization.k8s.io/argocd-application-controller created clusterrolebinding.rbac.authorization.k8s.io/argocd-server created configmap/argocd-cm created configmap/argocd-gpg-keys-cm created configmap/argocd-rbac-cm created configmap/argocd-ssh-known-hosts-cm created configmap/argocd-tls-certs-cm created secret/argocd-secret created service/argocd-dex-server created service/argocd-metrics created service/argocd-redis created service/argocd-repo-server created service/argocd-server created service/argocd-server-metrics created deployment.apps/argocd-dex-server created deployment.apps/argocd-redis created deployment.apps/argocd-repo-server created deployment.apps/argocd-server created statefulset.apps/argocd-application-controller created networkpolicy.networking.k8s.io/argocd-application-controller-network-policy created networkpolicy.networking.k8s.io/argocd-dex-server-network-policy created networkpolicy.networking.k8s.io/argocd-redis-network-policy created networkpolicy.networking.k8s.io/argocd-repo-server-network-policy created networkpolicy.networking.k8s.io/argocd-server-network-policy created 步骤3 - 访问argo server 通常在部署好 Kubernetes 中的应用后，需要访问大概有四种方式：\n修改清单，将 service 端口改为 NodePort 模式 使用 Ingress 使用端口转发 ( port-forward ) 这里选择最简单方式，使用 kubectl 的 port-forward 进行访问，随机端口\nbash 1 kubectl port-forward svc/argocd-server :443 --address=\u0026#39;0.0.0.0\u0026#39; 指定本地端口\nbash 1 kubectl port-forward svc/argocd-server 8888:443 --address=\u0026#39;0.0.0.0\u0026#39; 还可以指定任意的资源进行映射，比如 deployment, Pod\nbash 1 kubectl port-forward pod/{pod_name} 8888:443 --address=\u0026#39;0.0.0.0\u0026#39; Notes: 使用 port-forwad 需要在 Kubernetes 集群所有 Node 之上安装 socat , 否则会出现下面问题\nE1030 23:34:25.973226 36174 portforward.go:400] an error occurred forwarding 8181 -\u0026gt; 8080: error forwarding port 8080 to pod xxxxxxxxx, uid : unable to do port forwarding: socat not found\n此时 WEB UI 可以开启了，还需要获得默认的用户才可以登录到集群内，这里 ArgoCD 首次登陆密码被以 secert 形式保存在集群内，使用下面命令可以获取，默认用户名是 ”admin“\ntext 1 kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath=\u0026#34;{.data.password}\u0026#34; | base64 -d \u0026amp;\u0026amp; echo 安装 ArgoCD CLI 安装 ArgoCD CLI 工具有两种主要方法，mac 之上可以通过 brew 安装，其他操作系统需要从 github release 下载安装二进制文件，下载后可以使用 argocd login 登录集群，登录的地址是 argo server 的地址\nbash 1 argocd login localhost:8080 通过 argocd cli 也可以更新密码\nbash 1 argocd account update-password 至此，一个简单的 Argo CD 就部署完了，当然 Argo CD 也有高可用版本，可以参考官方给出的高可用版本的清单进行安装 [2]\nReference ​[1] Getting Started with ArgoCD on Kubernetes\n​[2] Installation\n","permalink":"https://www.161616.top/argo-installtion/","summary":"GitOps 最初由 Weaveworks (weave cni的组织) 在 2017 年的博客中提出 [1]，使用 “Git” 作为 CI/CD 的 “单一事实来源”，将代码的更改集成到每个项目的存储库中，并使用拉取请求来管理 infra 和部署。 在理解上就可以理解为 “是一种基于 git 的操作框架”\nArgo CD 是一种 kubernetes 之上的 “声明式” (declarative) 的 gitops CD， 在本文作为了解如何在 Kubernetes 集群中安装和配置 Argo CD。\n前提准备 想要安装 Argo CD 首先环境需要具备如下：\n已经安装好 kubectl 命令行工具 拥有 kubeconfig 文件 一个可供测试的 Kubernetes 集群，如：kind, minikube, kubeadm, binary 等任意的集群 步骤1 - 选择适配 kubernetes 版本的 Argo 根据官方的解释， Argo CD 在任何给定时刻所支持的版本，这些版本是 N 和 N - 1 次要版本的最新修补版本 (x.x.new)。这些 Argo CD 版本与 Kubernetes 项目官方支持的 Kubernetes 版本相一致，通常是 Kubernetes 的最近发布的 3 个版本。","title":"初识Argo cd - 在k8s集群上安装argo cd"},{"content":"Argo组件 API Server Repository Server Application Controller API Server：一个 gRPC/REST 服务器，提供了 “Web UI”、“CLI” 和 “CI/CD” 使用的 API\n应用管理和状态报告 调用应用操作（例如同步、回滚、用户定义的操作） 存储库和 Cluster credential 管理（作为 kubernetes secret 存储） 为外部提供身份认证和代理授权功能 RBAC 试试 Git webhook 事件的 listener/forwarder Repository Server：内部服务，用于维护 git 中的应用清单 (manifests) 的本地缓存。负责接收生成和返回 kubernetes 清单\n仓库URL revision (commit, tag, branch) APP PATH 模板特定的参数 Application Controller：Kubernetes controller，主要做的工作是持续监控运行的 Application，并于当前实时状态和目标所需状态进行对比（与 Kubernetes Controller 功能是相同的），并且不仅仅是 KC 还会和存储库中指定目标状态进行比较，检测到 OutOfSync 状态将进行纠正。\nArgo 架构 Argo CD 时最常见的三种架构：单实例方案, 集群级方案，以及折衷方案 (compromise between the two)。\n单实例方案 单实例 (One instance) 是指 “通过一个实例 (Argo) 来管理多个集群”，这是一种比较流行的方式，这种方式最大的特点是在用户角度看，是用户对 Application 有单一的视图层。单一的 “视图层” 为用户简化了 API 的集成与 CLI 的登录的配置与体验；为管理员提供了一个统一配置，如 “密钥”, “CRD” 等。\n图：单实例集群架构图 Source：https://akuity.io/blog/argo-cd-architectures-explained/ 单实例方案的优缺点如下：\n跨所有集群的单一视图层。 统一控制平面，简化安装和维护。 单实例可轻松集成 API/CLI。 缺点：\n单点故障。 扩展需要调整各个组件。 需要一个单独的“管理”集群 (Kubernetes)。 所有集群的 “集群凭证” (kubeconfig) 存储在单集群上，掌握了管理集群或Argo实例，等于可以直接访问所有集群 单独的 Application Controller 去管理所有集群的资源，AC 压力较大 Argo CD 和集群之间存在大量网络流量。 集群级别方案 集群级别方案 (separation instance for cluster) 是指将 Argo 分到每一个集群内，这样可以简化安全与控制难度；为什么说这种方式相对安全，因为在这种模式下， Argo CD 在集群内运行，这意味着不需要将集群 API 暴露给外部控制平面。另外也没有任何中心化实例包含了所有集群的凭证 (kubeconfig)，这种模式下就将 “安全域” 限制为 Argo CD 所在集群，而不是共享（中心化）。\n图：SIC集群架构 Source：https://akuity.io/blog/argo-cd-architectures-explained/ 集群级方案的优缺点如下：\n每个集群一个 Argo CD workload 不需要外部访问，消除 Argo CD 离开集群的流量 一个集群中断不会引起其他集群的正常工作 集群安全凭证仅限制该集群自己 减少了网络流量的成本（集群内使用内外，跨集群可能需要公网流量） 安全域与故障半径得到控制 缺点：\n维护成本增加，需要维护不同配置的多个实例，或相同配置的多个实例 集群规模不同，Argo 实例也不同 API/CLI 需要分别绑定集群 在计算资源的总成本相对增加 折衷方案 - 根据逻辑组划分 这个方案是根据 “单实例” 与 “SIC” 两个方案的优缺点进行折衷的一种方式，是将多个 Kubernetes 集群按照 “逻辑组” 划分，分组可以按团队, 区域或环境进行。只要是对你有意义的。此架构非常有用。它消除了维护过多 Argo CD 的成文。对于实例管理的所有集群来说，RBAC, AppProject 和其他配置可能是相似的。因此，与为每个集群运行一个实例相比，减少了配置重复。\n图：折衷方案架构图 Source：https://akuity.io/blog/argo-cd-architectures-explained/ 折衷方案的优缺点如下：\n按组分配 Argo 负载 一个集群的中断不会影响其他分组 可以控制对外网络流量，进一步缩小了凭证等信息的安全域，以及限制了问题半径 减少配置 缺点：\n有相对的维护成本 也需要单独的“管理集群” Reference [1] A Comprehensive Overview of Argo CD Architectures – 2023\n[2] How many do you need? - Argo CD Architectures Explained\n","permalink":"https://www.161616.top/ch01-argo-beginning/","summary":"Argo组件 API Server Repository Server Application Controller API Server：一个 gRPC/REST 服务器，提供了 “Web UI”、“CLI” 和 “CI/CD” 使用的 API\n应用管理和状态报告 调用应用操作（例如同步、回滚、用户定义的操作） 存储库和 Cluster credential 管理（作为 kubernetes secret 存储） 为外部提供身份认证和代理授权功能 RBAC 试试 Git webhook 事件的 listener/forwarder Repository Server：内部服务，用于维护 git 中的应用清单 (manifests) 的本地缓存。负责接收生成和返回 kubernetes 清单\n仓库URL revision (commit, tag, branch) APP PATH 模板特定的参数 Application Controller：Kubernetes controller，主要做的工作是持续监控运行的 Application，并于当前实时状态和目标所需状态进行对比（与 Kubernetes Controller 功能是相同的），并且不仅仅是 KC 还会和存储库中指定目标状态进行比较，检测到 OutOfSync 状态将进行纠正。\nArgo 架构 Argo CD 时最常见的三种架构：单实例方案, 集群级方案，以及折衷方案 (compromise between the two)。","title":"初识Argo cd - argo cd架构"},{"content":"10月11日发布的 curl 8.4.0版本，在新版本中修复漏洞 CVE-2023-38545 和 CVE-2023-38546\nCVE-2023-38545: This flaw makes curl overflow a heap based buffer in the SOCKS5 proxy handshake. [1] CVE-2023-38546: This flaw allows an attacker to insert cookies at will into a running program using libcurl, if the specific series of conditions are met. [2] 安装方式有两种，“编译” 与 “更新RPM”，本文以 RPM 方式更新 curl 到 8.4.0 版本\n下载curl源码包 升级至少需要更新至 curl 8.4 ，首先从官网下载源码包 [3]\n将curl打包为rpm 因为 curl 源码包内没有提供 rpm 的规格文件，所以我们需要自己编写，但是比较麻烦，可以让 chatgpt 生成一个，这里使用 centos7 的 curl.spec 进行修改\n安装依赖 编译时需要安装一些 build 时的依赖包\nbash 1 2 3 4 5 6 7 8 9 10 11 12 yum install -y automake \\ groff \\ krb5-devel \\ libidn-devel \\ libssh2-devel \\ nss-devel \\ openldap-devel \\ openssh-clients \\ openssh-server \\ pkgconfig \\ stunnel \\ zlib-devel 安装 rpmbuild\nbash 1 sudo yum install -y rpm-build redhat-rpm-config rpmdevtools 创建工作目录 rpmbuild 构建时是需要固定格式的目录的工作目录，下面将创建\nbash 1 mkdir -pv ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} 准备资源和规格文件 将准备好的规格文件 (.spec) 和源码包放置对应目录下\n.spec 放置 SPECS 目录下 源码包放置 SOURCES 下 构建 rpm 包 bash 1 rpmbuild -ba rpmbuild/SPECS/curl.spec 如果不是在 ~ 目录执行，而是指定目录可以用下面命令，将变量 ${RPM_WORK_DIR} 替换为你的目录\nbash 1 rpmbuild --define \u0026#34;_topdir ${RPM_WORK_DIR}\u0026#34; -ba ${RPM_WORK_DIR}/SPECS/kubernetes.spec 执行成功的日志如下\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Obsoletes: curl-devel \u0026lt; 8.4.0-1.el7.1 Processing files: curl-debuginfo-8.4.0-1.el7.1.x86_64 Provides: curl-debuginfo = 8.4.0-1.el7.1 curl-debuginfo(x86-64) = 8.4.0-1.el7.1 Requires(rpmlib): rpmlib(FileDigests) \u0026lt;= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) \u0026lt;= 4.0-1 rpmlib(CompressedFileNames) \u0026lt;= 3.0.4-1 Checking for unpackaged file(s): /usr/lib/rpm/check-files /root/rpmbuild/BUILDROOT/curl-8.4.0-1.el7.1.x86_64 warning: Installed (but unpackaged) file(s) found: /usr/lib64/libcurl.a Wrote: /root/rpmbuild/SRPMS/curl-8.4.0-1.el7.1.src.rpm Wrote: /root/rpmbuild/RPMS/x86_64/curl-8.4.0-1.el7.1.x86_64.rpm Wrote: /root/rpmbuild/RPMS/x86_64/libcurl-8.4.0-1.el7.1.x86_64.rpm Wrote: /root/rpmbuild/RPMS/x86_64/libcurl-devel-8.4.0-1.el7.1.x86_64.rpm Wrote: /root/rpmbuild/RPMS/x86_64/curl-debuginfo-8.4.0-1.el7.1.x86_64.rpm Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.mC4nLf + umask 022 + cd /root/rpmbuild/BUILD + cd curl-8.4.0 + rm -rf /root/rpmbuild/BUILDROOT/curl-8.4.0-1.el7.1.x86_64 + exit 0 更新curl rpm包 构建好的包在 rpmbuild 工作目录下的 RPMS/x86_64 目录下，进入直接安装 rpm 包即可完成升级\nbash 1 2 3 4 5 6 7 8 $ cd rpmbuild/RPMS/x86_64/ $ ll total 3284 -rw-r--r-- 1 root root 358040 Oct 14 21:44 curl-8.4.0-1.el7.1.x86_64.rpm -rw-r--r-- 1 root root 1786336 Oct 14 21:44 curl-debuginfo-8.4.0-1.el7.1.x86_64.rpm -rw-r--r-- 1 root root 278012 Oct 14 21:44 libcurl-8.4.0-1.el7.1.x86_64.rpm -rw-r--r-- 1 root root 930532 Oct 14 21:44 libcurl-devel-8.4.0-1.el7.1.x86_64.rpm $ yum update libcurl-8.4.0-1.el7.1.x86_64.rpm curl-8.4.0-1.el7.1.x86_64.rpm 由图可见，直接是可以完成安装的\n更新完后检查版本信息\nbash 1 2 3 4 5 $ curl -V curl 8.4.0 (x86_64-redhat-linux-gnu) libcurl/8.4.0 OpenSSL/1.0.2k-fips zlib/1.2.7 OpenLDAP/2.4.44 Release-Date: 2023-10-11 Protocols: dict file ftp ftps gopher gophers http https imap imaps ldap ldaps mqtt pop3 pop3s rtsp smb smbs smtp smtps telnet tftp Features: alt-svc AsynchDNS HSTS HTTPS-proxy IPv6 Largefile libz NTLM SSL UnixSockets 此时完成了 curl 的更新，这里更新了 curl 与 libcurl 到 8.4.0，成功的修补了漏洞\nNotes:\nCentOS 6下构建时注意取消 regenerate Makefile.in files, valgrind 是一款提升测试覆盖率的工具，可以不依赖 其余依赖自行选择，无需官方所有依赖 Reference ​[1] CVE-2023-38545\n​[2] CVE-2023-38546\n​[3] curl download\n​[4] curl.spec\n","permalink":"https://www.161616.top/update-curl-8-4/","summary":"10月11日发布的 curl 8.4.0版本，在新版本中修复漏洞 CVE-2023-38545 和 CVE-2023-38546\nCVE-2023-38545: This flaw makes curl overflow a heap based buffer in the SOCKS5 proxy handshake. [1] CVE-2023-38546: This flaw allows an attacker to insert cookies at will into a running program using libcurl, if the specific series of conditions are met. [2] 安装方式有两种，“编译” 与 “更新RPM”，本文以 RPM 方式更新 curl 到 8.4.0 版本\n下载curl源码包 升级至少需要更新至 curl 8.4 ，首先从官网下载源码包 [3]\n将curl打包为rpm 因为 curl 源码包内没有提供 rpm 的规格文件，所以我们需要自己编写，但是比较麻烦，可以让 chatgpt 生成一个，这里使用 centos7 的 curl.","title":"CentOS6/7 curl SOCKS5堆溢出漏洞修复 CVE-2023-38545 CVE-2023-38546"},{"content":"在本文，尝试使用 Docker 运行 PostgreSQL ，为了适配 goalert 项目，因为从来没有尝试过使用 PostgreSQL\n了解PostgreSQL数据库 在我们继续运行 PostgreSQL 数据库的 Docker 容器之前，我们先来了解一下 PostgreSQL 数据库。 PostgreSQL 是一个开源 RDMS，类似于 MySQL。它是一个面向对象的数据库，但我们可以处理结构化和非结构化数据。\nPostgreSQL 数据库可以运行在各种平台上，包括 Windows、Mac OS X 和 Linux。它还提供高级数据类型和性能优化功能来存储和扩展复杂的数据库工作负载。\n一些常见的 postgres 镜像\n镜像 说明 postgres:latest 最新的一个稳定版本 postgres:17 PostgreSQL version 14.x （x为最近的一次 patch） postgres:17.4 17.4 一个具体的版本 postgres:bookworm 使用 Debian Bookworm (12) 构建的 PostgreSQL postgres:15-bookworm 使用 Debian Bookworm (12) 构建的 PostgreSQL 15 使用公共镜像运行PostgreSQL 要使用 Docker 运行 PostgreSQL，我们首先需要拉取 Docker Hub 上可用的 postgres 公共镜像：\nbash 1 docker pull postgres 在上面的命令中，我们拉取了 postgres 最新的稳定版镜像。 如果要指定版本的 postgres 镜像，可以使用以下命令\nbash 1 docker pull postgres:14.2 这里将使用 postgres:14.2 版本来运行 Postgres 的容器，这里命令主要为 Linux\ntext 1 docker run --name \u0026lt;container_name\u0026gt; -e POSTGRES_PASSWORD=\u0026lt;password\u0026gt; -e POSTGRES_USER=\u0026lt;root\u0026gt; -p 5432:5432 -d postgres 实际执行命令\nbash 1 2 3 4 5 6 docker run --name postgresql \\ -e POSTGRES_USER=admin \\ -e POSTGRES_PASSWORD=111111 \\ -p 5432:5432 \\ -v /data:/var/lib/postgresql/data \\ -d postgres:16-bookworm 如果在 window 或 wsl 上运行，可以执行下面命令\nbash 1 docker run -d -e POSTGRES_USER=admin -e POSTGRES_PASSWORD=111111 -p 5432:5432 -v /data:/var/lib/postgresql/data --name postgresql postgres POSTGRES_PASSWORD, 这个变量设置超级 PostgreSQL 用户的密码，必须设置并且不能为空\nPOSTGRES_USER, 这个变量是和上面的关联使用的，用于设置一个指定的超级用户，默认为 “postgres”\n更多变量参考 [1]\n配置 Setting up persistent storage text 1 2 3 4 docker run --name postgres-db \\ -e POSTGRES_PASSWORD=mysecretpassword \\ -v postgres-data:/var/lib/postgresql/data \\ -d postgres Exposing ports to connect to PostgreSQL text 1 2 3 4 docker run --name postgres-db \\ -e POSTGRES_PASSWORD=mysecretpassword \\ -p 5432:5432 \\ -d postgres Connecting using the psql command line tool psql 是 PostgreSQL 的官方客户端工具，可以让用户使用这个命令行工具与数据库进行交互，要连接数据库可以使用下面命令\nbash 1 docker exec -it postgres-db psql -U myuser -d mydatabase 命令 psql -l 可以列出所有正在运行的数据库\n命令 createdb 可以创建一个数据库\ntext 1 createdb mydb 创建数据库 创建数据库可以通过其他数据库创建命令进行\nbash 1 CREATE DATABASE name; 官方也提供了快速创建的命令，不需要使用 psql 链接上 postgres 后再创建\nbash 1 createdb dbname createdb 就是连接到 postgres 数据库并且发出 CREATE DATABASE 命令注意不带任何参数的 createdb 将创建一个使用当前用户名的数据库 [3]。\nReference [1] How to extend this image\n[2] PostgreSQL in Docker: A Step-by-Step Guide for Beginners\n[3] 23.2. 创建一个数据库\n[4] Chapter 10. Migrating authentication from nslcd to SSSD\n[5] OpenLDAP Client 2.4.23: TLS negotiation failure\n[6] Chapter 10. Migrating authentication from nslcd to SSSD\n[7] Configure SSSD\n[8] Configure OpenLDAP SSSD client on CentOS 6/7\n","permalink":"https://www.161616.top/postgresql-docker-setup/","summary":"在本文，尝试使用 Docker 运行 PostgreSQL ，为了适配 goalert 项目，因为从来没有尝试过使用 PostgreSQL\n了解PostgreSQL数据库 在我们继续运行 PostgreSQL 数据库的 Docker 容器之前，我们先来了解一下 PostgreSQL 数据库。 PostgreSQL 是一个开源 RDMS，类似于 MySQL。它是一个面向对象的数据库，但我们可以处理结构化和非结构化数据。\nPostgreSQL 数据库可以运行在各种平台上，包括 Windows、Mac OS X 和 Linux。它还提供高级数据类型和性能优化功能来存储和扩展复杂的数据库工作负载。\n一些常见的 postgres 镜像\n镜像 说明 postgres:latest 最新的一个稳定版本 postgres:17 PostgreSQL version 14.x （x为最近的一次 patch） postgres:17.4 17.4 一个具体的版本 postgres:bookworm 使用 Debian Bookworm (12) 构建的 PostgreSQL postgres:15-bookworm 使用 Debian Bookworm (12) 构建的 PostgreSQL 15 使用公共镜像运行PostgreSQL 要使用 Docker 运行 PostgreSQL，我们首先需要拉取 Docker Hub 上可用的 postgres 公共镜像：\nbash 1 docker pull postgres 在上面的命令中，我们拉取了 postgres 最新的稳定版镜像。 如果要指定版本的 postgres 镜像，可以使用以下命令","title":"Docker运行PostgreSQL"},{"content":" 工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 kubernetes集群工具 kubect 提供了一种强大的数据提取的模式，jsonpath，相对于 yaml 来说，jsonpath 拥有高度的自制提取功能，以及一些更便于提取字段的模式，使得过去 kubernetes 资源信息时更便捷，在本文中将解开 jsonpath 的神秘面纱。\n什么是jsonpath JSONPath 是一种用于查询 JSON 数据结构中特定元素的查询语言。它类似于 XPath 用于 XML 数据的查询。JSONPath 允许您以一种简单而灵活的方式从 JSON 对象中提取数据，而不需要编写复杂的代码来解析 JSON 结构。\nJSONPath 使用路径表达式来指定您要检索的 JSON 数据的位置。这些路径表达式类似于文件系统中的路径，但用于导航 JSON 结构。以下是一些常见的 JSONPath 表达式示例：\n$：表示 JSON 根对象。 $.store：表示从根对象中获取名为 \u0026ldquo;store\u0026rdquo; 的属性。 $.store.book：表示从根对象中获取 \u0026ldquo;store\u0026rdquo; 属性中的 \u0026ldquo;book\u0026rdquo; 属性。 $.store.book[0]：表示获取 \u0026ldquo;store\u0026rdquo; 属性中的 \u0026ldquo;book\u0026rdquo; 属性的第一个元素。 $.store.book[?(@.price \u0026lt; 10)]：表示选择 \u0026ldquo;store\u0026rdquo; 属性中的 \u0026ldquo;book\u0026rdquo; 属性中价格小于 10 的所有元素。 Function Description Example Result text the plain text kind is {.kind} kind is List @ the current object {@} the same as input . or [] child operator {.kind} or {[‘kind’]} List .. recursive descent {..name} 127.0.0.1 127.0.0.2 myself e2e * wildcard. Get all objects {.items[*].metadata.name} [127.0.0.1 127.0.0.2] [start:end :step] subscript operator {.users[0].name} myself [,] union operator {.items[*][‘metadata.name’, ‘status.capacity’]} 127.0.0.1 127.0.0.2 map[cpu:4] map[cpu:8] ?() filter {.users[?(@.name==“e2e”)].user.password} secret range, end iterate list {range .items[*]}[{.metadata.name}, {.status.capacity}] {end} [127.0.0.1, map[cpu:4]] [127.0.0.2, map[cpu:8]] “ quote interpreted string {range .items[*]}{.metadata.name}{’\\t’}{end} 127.0.0.1 127.0.0.2 JSONPath 支持各种操作符和函数，以便更复杂地筛选和操作 JSON 数据。它在 JSON 数据的导航和过滤方面非常强大，通常用于从 JSON 数据中提取所需的信息。\nJSONPath 在各种编程语言和工具中都有实现，包括 JavaScript、Python、Java 等，因此您可以根据需要选择适合您项目的工具来使用 JSONPath 查询 JSON 数据。\nkubectl中对jsonpath的支持 例如，通常在生产环境中处理 Kubernetes 问题时，您将需要查看数百个节点和数千个 Pod 的信息，例如 Deployment, Pod, Replicat, Service, Secret 等资源信息，但要获取这些类型的资源，通常会使用 kubectl 命令，然而在50%以上的高级场景下，是过滤信息并进行整理。在这种场景下，使用 kubectl + shell 命令进行整理的却没有 jsonpath 来的实在。假设在一个大规模集群中，例如 10 万个 Node 节点，这时如果你想获得一些节点信息，或者 Pod 信息，再或者某些需要循环的条件，这时候多次的请求对你在统计数据上造成的时间成本及频繁请求API都会造成压力，这个时候 jsonpath 的功能就很好的解决了这个问题，通过一次请求，快速循环可以在很短时间内得出结果，并减少了大量请求 kube-apiserver 的压力。\nkubectl jsonpath 示例 仅获取某个资源的名称 bash 1 2 # 语法 kubectl -n \u0026lt;my_namespace\u0026gt; get deploy/\u0026lt;my_deployment\u0026gt; -o jsonpath=\u0026#39;{.metadata.name}\u0026#39; 获取一个 deployment的 信息\nbash 1 2 $ kubectl get deploy/traefik -o jsonpath=\u0026#39;{.metadata.name}\u0026#39; traefik 通常使用 json path 不会获取一个资源的信息，而是获取所有资源的信息，例如获取所有 Pod 的 name\nbash 1 2 $ kubectl get pod -A -o jsonpath=\u0026#34;{.items[*][\u0026#39;metadata.name\u0026#39;]}\u0026#34; echo-hello-world-task-run-1-pod-ksdtm echo-hello-world-task-run-pod-4sx9n traefik-679bf6459c-sz9jv calico-kube-controllers-577f77cb5c-kwhph calico-node-59d5x calico-node-82zgm coredns-6b9bb479b9-wnc8n minio spin-clouddriver-88df48858-dfzkg spin-deck-5dc8f847b8-m4tbk spin-echo-69868fd866-nxn8g spin-front50-54fb4b6d67-jfkds spin-gate-7b6f4d4566-gdjkd spin-orca-7765fb5c96-9gmvr spin-redis-8485df6b88-bgrgm spin-rosco-6d77f8cb-bf2nj tekton-pipelines-controller-5cdb46974f-8rjbx tekton-pipelines-webhook-6479d769ff-756gq Tips：jsonpath 的输出是以字符串方式输出，不会携带换行之类的\n@ 的用法 @ 表示当前对象，例如 kubectl get pods 这获取的是一个 list 列表，那么 @ 就代表这个 list，例如\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 $ kubectl get pods -o=jsonpath=\u0026#39;{@}\u0026#39;|jq { \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;items\u0026#34;: [ { \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;Pod\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;annotations\u0026#34;: { \u0026#34;cni.projectcalico.org/containerID\u0026#34;: \u0026#34;aedb0d3f11b2572d82a7ccb456cec393f88de2c8befa4d19e69a577bb8c0e20f\u0026#34;, \u0026#34;cni.projectcalico.org/podIP\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;cni.projectcalico.org/podIPs\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;kubectl.kubernetes.io/last-applied-configuration\u0026#34;: \u0026#34;{\\\u0026#34;apiVersion\\\u0026#34;:\\\u0026#34;tekton.dev/v1beta1\\\u0026#34;,\\\u0026#34;kind\\\u0026#34;:\\\u0026#34;Task\\\u0026#34;,\\\u0026#34;metadata\\\u0026#34;:{\\\u0026#34;annotations\\\u0026#34;:{},\\\u0026#34;name\\\u0026#34;:\\\u0026#34;echo-hello-world\\\u0026#34;,\\\u0026#34;namespace\\\u0026#34;:\\\u0026#34;default\\\u0026#34;},\\\u0026#34;spec\\\u0026#34;:{\\\u0026#34;steps\\\u0026#34;:[{\\\u0026#34;args\\\u0026#34;:[\\\u0026#34;Hello World\\\u0026#34;],\\\u0026#34;command\\\u0026#34;:[\\\u0026#34;echo\\\u0026#34;],\\\u0026#34;image\\\u0026#34;:\\\u0026#34;busybox\\\u0026#34;,\\\u0026#34;name\\\u0026#34;:\\\u0026#34;echo\\\u0026#34;}]}}\\n\u0026#34;, \u0026#34;pipeline.tekton.dev/release\u0026#34;: \u0026#34;v0.19.0\u0026#34;, \u0026#34;tekton.dev/ready\u0026#34;: \u0026#34;READY\u0026#34; }, \u0026#34;creationTimestamp\u0026#34;: \u0026#34;2023-06-26T15:05:54Z\u0026#34;, \u0026#34;labels\u0026#34;: { \u0026#34;app.kubernetes.io/managed-by\u0026#34;: \u0026#34;tekton-pipelines\u0026#34;, \u0026#34;tekton.dev/task\u0026#34;: \u0026#34;echo-hello-world\u0026#34;, \u0026#34;tekton.dev/taskRun\u0026#34;: \u0026#34;echo-hello-world-task-run-1\u0026#34; }, \u0026#34;managedFields\u0026#34;: [ { \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;fieldsType\u0026#34;: \u0026#34;FieldsV1\u0026#34;, \u0026#34;fieldsV1\u0026#34;: { \u0026#34;f:metadata\u0026#34;: { \u0026#34;f:annotations\u0026#34;: { \u0026#34;f:cni.projectcalico.org/containerID\u0026#34;: {}, \u0026#34;f:cni.projectcalico.org/podIP\u0026#34;: {}, \u0026#34;f:cni.projectcalico.org/podIPs\u0026#34;: {} } } }, \u0026#34;manager\u0026#34;: \u0026#34;calico\u0026#34;, \u0026#34;operation\u0026#34;: \u0026#34;Update\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2023-06-26T15:05:55Z\u0026#34; }, { \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;fieldsType\u0026#34;: \u0026#34;FieldsV1\u0026#34;, \u0026#34;fieldsV1\u0026#34;: { \u0026#34;f:metadata\u0026#34;: { \u0026#34;f:annotations\u0026#34;: { \u0026#34;.\u0026#34;: {}, \u0026#34;f:kubectl.kubernetes.io/last-applied-configuration\u0026#34;: {}, \u0026#34;f:pipeline.tekton.dev/release\u0026#34;: {}, \u0026#34;f:tekton.dev/ready\u0026#34;: {} ... \u0026#34;nodeName\u0026#34;: \u0026#34;node01\u0026#34;, \u0026#34;preemptionPolicy\u0026#34;: \u0026#34;PreemptLowerPriority\u0026#34;, \u0026#34;priority\u0026#34;: 0, \u0026#34;restartPolicy\u0026#34;: \u0026#34;Never\u0026#34;, \u0026#34;schedulerName\u0026#34;: \u0026#34;default-scheduler\u0026#34;, \u0026#34;securityContext\u0026#34;: {}, \u0026#34;serviceAccount\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;serviceAccountName\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;terminationGracePeriodSeconds\u0026#34;: 30, \u0026#34;tolerations\u0026#34;: [ { \u0026#34;effect\u0026#34;: \u0026#34;NoExecute\u0026#34;, \u0026#34;key\u0026#34;: \u0026#34;node.kubernetes.io/not-ready\u0026#34;, \u0026#34;operator\u0026#34;: \u0026#34;Exists\u0026#34;, \u0026#34;tolerationSeconds\u0026#34;: 300 }, { \u0026#34;effect\u0026#34;: \u0026#34;NoExecute\u0026#34;, \u0026#34;key\u0026#34;: \u0026#34;node.kubernetes.io/unreachable\u0026#34;, \u0026#34;operator\u0026#34;: \u0026#34;Exists\u0026#34;, \u0026#34;tolerationSeconds\u0026#34;: 300 } . 和 [] 的用法 . 和 [] 是子操作符，用于获取到列表的元素，返回值也是 list，例如获取 Pod 列表中第一个 Pod，下面是一个 [] 的使用示例，可以获取某个元素\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 kubectl get pods -o=jsonpath=\u0026#39;{.items[0]}\u0026#39; | jq { \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;Pod\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;annotations\u0026#34;: { \u0026#34;cni.projectcalico.org/containerID\u0026#34;: \u0026#34;aedb0d3f11b2572d82a7ccb456cec393f88de2c8befa4d19e69a577bb8c0e20f\u0026#34;, ... }, \u0026#34;status\u0026#34;: { \u0026#34;conditions\u0026#34;: [ { \u0026#34;lastProbeTime\u0026#34;: null, ... \u0026#34;hostIP\u0026#34;: \u0026#34;10.0.0.5\u0026#34;, \u0026#34;initContainerStatuses\u0026#34;: [ { \u0026#34;containerID\u0026#34;: \u0026#34;docker://8129d155a9e9f204a22947ae3268513a1bf4d1ce98012120ab30cfd0eca04564\u0026#34;, \u0026#34;image\u0026#34;: \u0026#34;sha256:5d54c55f19bc6fdda7629a4f2015255ec1bed2750a81909817bede45a4d360b5\u0026#34;, \u0026#34;imageID\u0026#34;: \u0026#34;docker-pullable://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/entrypoint@sha256:67fceb87f3f76baefcfdb35fd04d0ebfc8d91117dccb7f3194056d6727bac636\u0026#34;, \u0026#34;lastState\u0026#34;: {}, \u0026#34;name\u0026#34;: \u0026#34;place-tools\u0026#34;, \u0026#34;ready\u0026#34;: true, \u0026#34;restartCount\u0026#34;: 0, \u0026#34;state\u0026#34;: { \u0026#34;terminated\u0026#34;: { \u0026#34;containerID\u0026#34;: \u0026#34;docker://8129d155a9e9f204a22947ae3268513a1bf4d1ce98012120ab30cfd0eca04564\u0026#34;, \u0026#34;exitCode\u0026#34;: 0, \u0026#34;finishedAt\u0026#34;: \u0026#34;2023-06-26T15:05:55Z\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;Completed\u0026#34;, \u0026#34;startedAt\u0026#34;: \u0026#34;2023-06-26T15:05:55Z\u0026#34; } } } ], \u0026#34;phase\u0026#34;: \u0026#34;Succeeded\u0026#34;, \u0026#34;podIP\u0026#34;: \u0026#34;10.244.196.131\u0026#34;, \u0026#34;podIPs\u0026#34;: [ { \u0026#34;ip\u0026#34;: \u0026#34;10.244.196.131\u0026#34; } ], \u0026#34;qosClass\u0026#34;: \u0026#34;BestEffort\u0026#34;, \u0026#34;startTime\u0026#34;: \u0026#34;2023-06-26T15:05:54Z\u0026#34; } } . 是 [] 的子操作符，可以获取某一个元素下的某个值，与 json 中语法相同，例如获取 Pod 列表中==第一个 Pod 的名称==\nbash 1 2 $ kubectl get pods -o=jsonpath=\u0026#39;{.items[0].metadata.name}\u0026#39; echo-hello-world-task-run-1-pod-ksdtm 下标操作符 : 下标操作符 “:” 可以视为一个切片，获取列表中某一些元素，语法为 [start:end :step]\n例如获取前三个元素的名称\nbash 1 kubectl get pods -o=jsonpath=\u0026#39;{.items[0:2].metadata.name}\u0026#39; 获取最后两个元素\nbash\u0026#39; $ kubectl get pods -n spinnaker -o jsonpath=\u0026#39;{range .items[*]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;\rspin-clouddriver-88df48858-dfzkg\rspin-deck-5dc8f847b8-m4tbk\rspin-echo-69868fd866-nxn8g\rspin-front50-54fb4b6d67-jfkds\rspin-gate-7b6f4d4566-gdjkd\rspin-orca-7765fb5c96-9gmvr\rspin-redis-8485df6b88-bgrgm\rspin-rosco-6d77f8cb-bf2nj\r$ kubectl get pods -n spinnaker -o jsonpath=\u0026#39;{range .items[-2:]}{.metadata.name}{\u0026#34;\\n\u0026#34;}{end}\u0026#39;\rspin-redis-8485df6b88-bgrgm\rspin-rosco-6d77f8cb-bf2nj 过滤表达式 如果设置了 limit 参数则打印其名称\nbash 1 kubectl get pods -n spinnaker -o jsonpath=\u0026#39;{.items[?(@.spec.containers[*].resources.limits.memory != \u0026#34;\u0026#34;)].metadata.name}\u0026#39; 价格大于 10 的 元素的 name\nbash 1 \u0026#34;$.store.book[?(@.price \u0026gt; 10)].name\u0026#34; json path 支持下列过滤操作符参考 [2]\nOperator Description == 等于。字符串值必须用单引号（而不是双引号）括起来：[?(@.color=='red')]。注意：数字与字符串比较的工作方式因播放引擎而异。在 TestEngine 中，1 不等于 “1”。在 ReadyAPI 1.9 及更早版本中，1 等于 “1”。 != 不等于，字符串值必须用单引号括起来：[?(@.color!='red')]。 \u0026gt; 大于 \u0026gt;= 大于或等于 \u0026lt; 小于 \u0026lt;= 小于或等于 =~ 匹配 JavaScript 正则表达式。例如，[?(@.description =~ /cat.*/i)] 匹配描述以 cat 开头的项目（不区分大小写）。注意：如果使用 ReadyAPI 1.1 作为播放引擎，则不支持。 ! 用于否定过滤器：[?(!@.isbn)] 匹配不具有 isbn 属性的项目。注意：如果使用 ReadyAPI 1.1 作为播放引擎，则不支持。 \u0026amp;\u0026amp; 逻辑与 AND，用于组合多个过滤表达式: [?(@.category=='fiction' \u0026amp;\u0026amp; @.price \u0026lt; 10)] || 逻辑或 OR Logical OR, 用于组合多个过滤表达式： `[?(@.category==\u0026lsquo;fiction\u0026rsquo; in 检查左侧值是否存在于右侧列表中。类似于 SQL IN 运算符。字符串比较区分大小写。 [?(@.size in ['M', 'L'])] [?('S' in @.sizes)] 注意：仅由 TestEngine 播放引擎支持。 nin 与 in 相反。检查左侧值是否不存在于右侧列表中。字符串比较区分大小写。 [?(@.size nin ['M', 'L'])] [?('S' nin @.sizes)] 注意：仅由 TestEngine 播放引擎支持。 contains 检查字符串是否包含指定的子字符串（区分大小写），或者数组是否包含指定的元素。\n[?(@.name contains 'Alex')] [?(@.numbers contains 7)] [?('ABCDEF' contains @.character)] 注意：仅由 TestEngine 播放引擎支持。 size 检查数组或字符串是否具有指定的长度。 [?(@.name size 4)] 注意：仅由 TestEngine 播放引擎支持。 empty true 匹配空数组或字符串。 [?(@.name empty true)] 注意：仅由 TestEngine 播放引擎支持。 empty false 匹配非空数组或字符串。 [?(@.name empty false)] 注意：仅由 TestEngine 播放引擎支持。 正则表达式 kubectl jsonpath 不支持正则表达式，如果需要使用正则表达式可以使用 jq 替换\n例如获取 spin 开头的 Pod 是否配置了 resources\nbash 1 kubectl get pods -n spinnaker -o json | jq -r \u0026#39;.items[] | select(.metadata.name | test(\u0026#34;spin-\u0026#34;)).spec.resources\u0026#39; example\nbash 1 2 3 4 5 6 7 8 9 $ kubectl get pods -n spinnaker -o json | jq -r \u0026#39;.items[] | select(.metadata.name | test(\u0026#34;spin-\u0026#34;)).spec.resources\u0026#39; null null null null null null null null kubectl example 示例 获取 Pod 为 web 的 Pod name bash 1 kubectl get pods -o jsonpath = \u0026#39;{.items[?(@.metadata.labels.name==\u0026#34;web\u0026#34;)].metadata.name}\u0026#39; 过滤一个元素 过滤 Node 地址模式\nbash 1 kubectl get nodes -o jsonpath=\u0026#39;{.items[*].status.addresses[?(@.type==\u0026#34;InternalIP\u0026#34;)]}\u0026#39; 过滤元素并只打印想要的属性\nbash 1 kubectl get nodes -o jsonpath=\u0026#39;{.items[*].status.addresses[?(@.type==\u0026#34;InternalIP\u0026#34;)].address}\u0026#39; 从数组中获取每个元素的单个字段 bash 1 kubectl get pods -o jsonpath={$.items[*].status.hostIP} 换行 jsonpath 在获取元素后，是一个单行字符串，如果需要换行操作可以使用下面示例\nbash 1 2 3 4 $ kubectl get pods -o jsonpath=\u0026#39;{range .items[*]}{.status.hostIP}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; 10.0.0.5 10.0.0.5 10.0.0.5 循环并获得多个元素 如果想获取两个元素，可以使用下面示例\nbash 1 2 3 4 $ kubectl get pods -o jsonpath={range .items[*]}{.status.hostIP}{\u0026#34;\\t\u0026#34;}{.status.phase}{\u0026#34;\\n\u0026#34;}{end} 10.154.196.228\tRunning 10.154.202.136\tRunning 10.154.201.54\tRunning 递归 如果想获取一个元素下所有相同名称的字段，可以使用下面示例\n递归获取所有 name 字段\nbash 1 2 3 $ kubectl get pod -A -o jsonpath=\u0026#39;{..name}\u0026#39; step-echo place-tools echo-hello-world-task-run-1-pod-ksdtm echo-hello-world-task-run-1 tekton-internal-workspace tekton-internal-home tekton-internal-results tekton-internal-tools tekton-internal-downward tekton-creds-init-home-tlvg8 default-token-6762j step-echo HOME tekton-internal-tools tekton-internal-downward tekton-creds-init-home-tlvg8 tekton-internal-workspace tekton-i.. ... 巧用递归简化语句，如果一个元素 (元素名称) 是独有的，那么可以使用递归直接获取到这个元素的值，例如，过去所有 containers\nbash 1 2 3 4 5 6 $ kubectl get pods -A -o=jsonpath=\u0026#39;{range .items[*]}{\u0026#34;pod: \u0026#34;}{.metadata.name} {\u0026#34;\\n\u0026#34;}{range ..containers[*]}{\u0026#34;\\tcontainer: \u0026#34;}{.name}{\u0026#34;\\n\\timage: \u0026#34;}{.image}{\u0026#34;\\n\u0026#34;}{end}{end}\u0026#39; pod: echo-hello-world-task-run-1-pod-ksdtm container: step-echo image: busybox pod: echo-hello-world-task-run-pod-4sx9n container: step-echo 获取每个 Pod limit 值 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 kubectl get pod -A -o jsonpath=\u0026#39;{range $.items[*]}{\u0026#34;Pod: \u0026#34;}{.metadata.name}{\u0026#34;\\n\u0026#34;} {\u0026#34; limit_mem: \u0026#34;}{.spec.containers[*].resources.limits.memory}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; Pod: echo-hello-world-task-run-1-pod-ksdtm limit_mem: Pod: echo-hello-world-task-run-pod-4sx9n limit_mem: Pod: traefik-679bf6459c-sz9jv limit_mem: Pod: calico-kube-controllers-577f77cb5c-kwhph limit_mem: Pod: calico-node-59d5x limit_mem: Pod: calico-node-82zgm limit_mem: Pod: coredns-6b9bb479b9-wnc8n limit_mem: 170Mi Pod: minio limit_mem: Pod: spin-clouddriver-88df48858-dfzkg limit_mem: Pod: spin-deck-7fbf94d8bc-k7zrk limit_mem: 200Mi Pod: spin-echo-69868fd866-nxn8g limit_mem: Pod: spin-front50-54fb4b6d67-jfkds limit_mem: Pod: spin-gate-6d7dbf74b9-4l89r limit_mem: 600Mi Pod: spin-orca-7765fb5c96-9gmvr limit_mem: Pod: spin-redis-8485df6b88-bgrgm limit_mem: Pod: spin-rosco-6d77f8cb-bf2nj limit_mem: Pod: tekton-pipelines-controller-5cdb46974f-8rjbx limit_mem: Pod: tekton-pipelines-webhook-6479d769ff-756gq limit_mem: 500Mi 获取容器的 IP 获取单独一个 Pod\nbash 1 kubectl get pod nginx-67d5fc57d8-jkfjp -n quota-example -o jsonpath=\u0026#39;{.status.podIPs[].ip}{\u0026#34;\\n\u0026#34;}\u0026#39; 循环获取\nbash 1 2 3 4 kubectl get pod -o jsonpath=\u0026#39;{range $.items[*]}{.status.podIPs[].ip}{\u0026#34;\\n\u0026#34;}{end}\u0026#39; 10.244.196.131 10.244.196.129 10.244.196.186 获取所有的 Container ID 和 Pod IP bash 1 kubectl get pods --all-namespaces -o=jsonpath=\u0026#39;{range .items[*]}[{.status.containerStatuses[0].containerID}, {.status.podIP}]{\u0026#34;\\n\u0026#34;}{end}\u0026#39; 获取所有的容器名称和镜像名称 bash 1 kubectl get pods -n kube-system -o=jsonpath=\u0026#39;{range .items[*]}[{.metadata.name},{.status.containerStatuses[0].image}]{\u0026#34;\\n\u0026#34;}{end}\u0026#39; 获取所有状态条件中的类型 bash 1 kubectl get pod cm-test-pod -o jsonpath=\u0026#39;{.status.conditions[*].type}\u0026#39; 获取 Pod 的 apiversion bash 1 kubectl get pod cm-test-pod -o jsonpath=\u0026#39;{.apiVersion}\u0026#39; 从第一个状态条件开始到最后一个结束，每隔2个获取一次 bash 1 kubectl get pod cm-test-pod -o jsonpath=\u0026#39;{.status.conditions[0:3:2].type}\u0026#39; Reference ​[1] jsonpath\n​[2] JSONPath Syntax\n​[3] k8s学习-kubectl命令行 jsonpath的使用\n","permalink":"https://www.161616.top/kubectl-jsonpath/","summary":"工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 kubernetes集群工具 kubect 提供了一种强大的数据提取的模式，jsonpath，相对于 yaml 来说，jsonpath 拥有高度的自制提取功能，以及一些更便于提取字段的模式，使得过去 kubernetes 资源信息时更便捷，在本文中将解开 jsonpath 的神秘面纱。\n什么是jsonpath JSONPath 是一种用于查询 JSON 数据结构中特定元素的查询语言。它类似于 XPath 用于 XML 数据的查询。JSONPath 允许您以一种简单而灵活的方式从 JSON 对象中提取数据，而不需要编写复杂的代码来解析 JSON 结构。\nJSONPath 使用路径表达式来指定您要检索的 JSON 数据的位置。这些路径表达式类似于文件系统中的路径，但用于导航 JSON 结构。以下是一些常见的 JSONPath 表达式示例：\n$：表示 JSON 根对象。 $.store：表示从根对象中获取名为 \u0026ldquo;store\u0026rdquo; 的属性。 $.store.book：表示从根对象中获取 \u0026ldquo;store\u0026rdquo; 属性中的 \u0026ldquo;book\u0026rdquo; 属性。 $.store.book[0]：表示获取 \u0026ldquo;store\u0026rdquo; 属性中的 \u0026ldquo;book\u0026rdquo; 属性的第一个元素。 $.store.book[?(@.price \u0026lt; 10)]：表示选择 \u0026ldquo;store\u0026rdquo; 属性中的 \u0026ldquo;book\u0026rdquo; 属性中价格小于 10 的所有元素。 Function Description Example Result text the plain text kind is {.","title":"探索kubectl - 巧用jsonpath提取有用数据"},{"content":"s3cmd 是为了管理 Linux 服务器上的 S3 存储桶而创建的。 但我们也在 Windows 服务器上使用这个工具。 本文将帮助您在 Windows 系统中设置 s3cmd\nRequirment s3cmd 系统要求： s3cmd 需要 Python 2.7 或更高版本才能运行，还需要安装GPG。\n步骤1：安装 Python 从 python 官方网站下载并安装 python 2.7 或更高版本并安装。安装python后，将将其加到 PATH 环境变量。\n步骤 2： 在 Windows 上安装 GPG Gpg4win (GNU Privacy Guard for Windows) 是一款用于数字加密 (file, email) 的免费软件，可以使用以下链接下载并安装它。\n步骤3：配置 s3cmd 下载最新的 s3cmd 源代码 从s3cmd 官方页面 并解压；\n提取源代码后，使用以下命令设置 s3 环境。 它会询问您的 对象存储的 AccessKey 和 SecretKey，即 GPG 命令的路径\nbat 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 C:s3cmd\u0026gt; python s3cmd --configure Enter new values or accept defaults in brackets with Enter. Refer to user manual for detailed description of all options. Access key and Secret key are your identifiers for Amazon S3 Access Key: XXXXXXXXXXXXXXXXXXXX Secret Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Encryption password is used to protect your files from reading by unauthorized persons while in transfer to S3 Encryption password: XXXXXXXXX Path to GPG program: C:\\Program Files (x86)\\GNU\\GnuPG\\gpg2.exe When using secure HTTPS protocol all communication with Amazon S3 servers is protected from 3rd party eavesdropping. This method is slower than plain HTTP and can\u0026#39;t be used if you\u0026#39;re behind a proxy Use HTTPS protocol [No]: Yes New settings: Access Key: XXXXXXXXXXXXXXXXXXXX Secret Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Encryption password: XXXXXXXXX Path to GPG program: C:Program Files (x86)GNUGnuPGgpg2.exe Use HTTPS protocol: True HTTP Proxy server name: HTTP Proxy server port: 0 Test access with supplied credentials? [Y/n] Y Please wait, attempting to list all buckets... Success. Your access key and secret key worked fine :-) Now verifying that encryption works... Success. Encryption and decryption worked fine :-) Save settings? [y/N] Y Configuration saved to \u0026#39;C:\\Users\\Administrator\\Application Data\\s3cmd.ini\u0026#39; 步骤4：验证 使用以下命令来验证 s3cmd 配置\nbash 1 \u0026gt; python c:\\s3cmd\\s3cmd ls ","permalink":"https://www.161616.top/ch05-4-s3cmd-in-windows/","summary":"s3cmd 是为了管理 Linux 服务器上的 S3 存储桶而创建的。 但我们也在 Windows 服务器上使用这个工具。 本文将帮助您在 Windows 系统中设置 s3cmd\nRequirment s3cmd 系统要求： s3cmd 需要 Python 2.7 或更高版本才能运行，还需要安装GPG。\n步骤1：安装 Python 从 python 官方网站下载并安装 python 2.7 或更高版本并安装。安装python后，将将其加到 PATH 环境变量。\n步骤 2： 在 Windows 上安装 GPG Gpg4win (GNU Privacy Guard for Windows) 是一款用于数字加密 (file, email) 的免费软件，可以使用以下链接下载并安装它。\n步骤3：配置 s3cmd 下载最新的 s3cmd 源代码 从s3cmd 官方页面 并解压；\n提取源代码后，使用以下命令设置 s3 环境。 它会询问您的 对象存储的 AccessKey 和 SecretKey，即 GPG 命令的路径\nbat 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 C:s3cmd\u0026gt; python s3cmd --configure Enter new values or accept defaults in brackets with Enter.","title":"Ceph对象存储 - windows上安装s3cmd"},{"content":"s3cmd 是一个 Amazon S3 工具，可以用于创建 s3 bucket、向对象存储中上传，检索和管理数据，在下文将如何在 Linux 上如何安装和使用 “s3cmd” 工具。\n在 Linux 上安装 s3cmd s3cmd 在 Ubuntu/Debian, Fedora/CentOS/RHEL 这类发行版上的默认软件包存储库中都是可用的，只需在执行对应发行版的安装命令即可安装。\nCentOS/RHEL/Fedora bash 1 2 3 4 # centos 8 $ sudo dnf install s3cmd # centos 7 $ sudo yum install s3cmd Ubuntu/Debian bash 1 sudo apt-get install s3cmd 安装最新版本 通常包管理仓库中的版本比较旧，或者使用的 Linux 没有包管理来获取最新版本的 s3cmd，那么可以使用源代码在系统上安装最新版本的 s3cmd，下载地址可以参考附录1 [1]\n下面以 2.2 版本进行安装\nbash 1 2 $ wget https://sourceforge.net/projects/s3tools/files/s3cmd/2.2.0/s3cmd-2.2.0.tar.gz $ tar xzf s3cmd-2.2.0.tar.gz 使用以下命令和源文件安装\nbash 1 2 $ cd s3cmd-2.2.0 $ sudo python setup.py install 配置 s3cmd s3cmd 并不仅仅可以管理 AWS s3，也可以管理任意的 S3 对象存储，为了配置 s3cmd 我们需要 Access Key 和 Secret Key 您的 S3 来访问 S3 对象存储，通常 AWS S3 的 Access Key 和 Secret Key 需要到 Amazon security_credential 页面获取 (这里涉及到 AWS 中的用户管理)\n使用下列命令配置 s3cmd\nbash 1 s3cmd --configure Note：通常这个配置是交互类型的，很多值在自维护的 S3 对象存储中不需要配置，可以一路回车即可\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 Enter new values or accept defaults in brackets with Enter. Refer to user manual for detailed description of all options. Access key and Secret key are your identifiers for Amazon S3 Access Key: xxxxxxxxxxxxxxxxxxxxxx Secret Key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Encryption password is used to protect your files from reading by unauthorized persons while in transfer to S3 Encryption password: xxxxxxxxxx Path to GPG program [/usr/bin/gpg]: When using secure HTTPS protocol all communication with Amazon S3 servers is protected from 3rd party eavesdropping. This method is slower than plain HTTP and can\u0026#39;t be used if you\u0026#39;re behind a proxy Use HTTPS protocol [No]: Yes New settings: Access Key: xxxxxxxxxxxxxxxxxxxxxx Secret Key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Encryption password: xxxxxxxxxx Path to GPG program: /usr/bin/gpg Use HTTPS protocol: True HTTP Proxy server name: HTTP Proxy server port: 0 Test access with supplied credentials? [Y/n] Y Please wait, attempting to list all buckets... Success. Your access key and secret key worked fine :-) Now verifying that encryption works... Success. Encryption and decryption worked fine :-) Save settings? [y/N] y Configuration saved to \u0026#39;/root/.s3cfg\u0026#39; 最终生成的文件在目录 /root/.s3cfg 下\n通常需要关注的参数只有几个\ntext 1 2 3 4 5 6 [default] access_key = \u0026lt;ACCESS KEY FROM PORTAL\u0026gt; host_base = s3-api.us-geo.objectstorage.softlayer.net # 这个要注意，在ceph中使用的是下面格式，表示列出的 host/bucket host_bucket = s3-api.us-geo.objectstorage.softlayer.net/%(bucket) secret_key = \u0026lt;SECRET KEY LISTED IN PORTAL\u0026gt; https://gist.github.com/greyhoundforty/a4a9d80a942d22a8a7bf838f7abbcab2\ns3cmd examples 说明 命令 列出 bucket 文件 s3cmd ls 创建存储桶 s3cmd mb s3://tecadmin 上传文件到 bucket s3cmd put file.txt s3://tecadmin/ 上传目录到 bucket s3cmd put -r backup s3://tecadmin/ 需要注意斜杠才表示目录 下载文件 s3cmd get s3://tecadmin/file.txt 从 bucket 删除文件 s3cmd del s3://tecadmin/file.txt 删除一个目录 s3cmd del s3://tastethelinux/Script 删除 bucket s3cmd rb s3://tastethelinux 拷贝 bucket 文件到另一个 bucket s3cmd cp s3://tastethelinux/tla.txt s3://tastethelinux-example 移动 bucket 文件到另一个 bucket s3cmd mv s3://tastethelinux/tla.txt s3://tastethelinux-example/tla_new.txt 查看存储使用量 s3cmd du s3://tastethelinux/ \u0026ndash;human-readable 获取 bucket 信息 s3cmd info s3://tastethelinux 继续上次中断的文件 s3cmd \u0026ndash;continue get s3://tastethelinux/tastethelinux.tar.gz 尝试运行但不上传 s3cmd \u0026ndash;dry-run 排除规则 —exclude / —include shell 风格通配符 s3cmd sync \u0026ndash;dry-run \u0026ndash;exclude \u0026lsquo;*.txt\u0026rsquo; 排除规则 —rexclude / —rinclude 正则表达式 s3cmd sync \u0026ndash;dry-run \u0026ndash;exclude \u0026lsquo;*.(txt|jpg)\u0026rsquo; 同步 s3cmd sync ./ s3://s3tools-demo/some/path/ 需要注意的是，s3cmd sync 首先检查 目的 已存在的文件的列表和详细信息，与==本地文件进行比较==，然后仅上传远程不存在或具有不同大小或 md5 校验和的文件。如果您运行了上述所有示例，您将从同步中获得与以下输出类似的输出：\nbash 1 2 3 $ s3cmd sync ./ s3://s3tools-demo/some/path/ dir2/file2-1.log -\u0026gt; s3://s3tools-demo/some/path/dir2/file2-1.log [1 of 2] dir2/file2-2.txt -\u0026gt; s3://s3tools-demo/some/path/dir2/file2-2.txt [2 of 2] Reference [1] s3cmd Files\n","permalink":"https://www.161616.top/ch05-3-s3cmd/","summary":"s3cmd 是一个 Amazon S3 工具，可以用于创建 s3 bucket、向对象存储中上传，检索和管理数据，在下文将如何在 Linux 上如何安装和使用 “s3cmd” 工具。\n在 Linux 上安装 s3cmd s3cmd 在 Ubuntu/Debian, Fedora/CentOS/RHEL 这类发行版上的默认软件包存储库中都是可用的，只需在执行对应发行版的安装命令即可安装。\nCentOS/RHEL/Fedora bash 1 2 3 4 # centos 8 $ sudo dnf install s3cmd # centos 7 $ sudo yum install s3cmd Ubuntu/Debian bash 1 sudo apt-get install s3cmd 安装最新版本 通常包管理仓库中的版本比较旧，或者使用的 Linux 没有包管理来获取最新版本的 s3cmd，那么可以使用源代码在系统上安装最新版本的 s3cmd，下载地址可以参考附录1 [1]\n下面以 2.2 版本进行安装\nbash 1 2 $ wget https://sourceforge.net/projects/s3tools/files/s3cmd/2.2.0/s3cmd-2.2.0.tar.gz $ tar xzf s3cmd-2.2.0.tar.gz 使用以下命令和源文件安装","title":"Ceph对象存储 - 使用s3cmd管理对象存储"},{"content":"什么是 default backend default backend 是 ingress-nginx 中的一个服务，主要用于处理 nginx controller 无法识别的而请求的服务\n主要提供了两个接口\n/healthz that returns 200 /that returns 404 如何改default backend 状态码 需求：修改 default backend 状态码 404 为 403\n原理：nginx-controller 启动时指定了一个 default backend 容器，如下所示\nbash 1 2 3 4 5 6 7 8 9 10 11 12 containers: - args: - /nginx-ingress-controller # 这里指的是 default-backend 的名称 {namespace_name}/{service_name} - --default-backend-service=$(POD_NAMESPACE)/ingress-nginx-ext-defaultbackend - --publish-service=$(POD_NAMESPACE)/ingress-nginx-ext-controller - --election-id=ingress-controller-leader - --ingress-class=nginx-yewu-ext - --configmap=$(POD_NAMESPACE)/ingress-nginx-ext-controller - --validating-webhook=:8443 - --validating-webhook-certificate=/usr/local/certificates/cert - --validating-webhook-key=/usr/local/certificates/key 通常情况下在通过定义配置文件方式改变是不容易做的，ingress-nginx 提供了一种自定义方式 “custom-error-pages“ 可以完成 ，完成后该 defaultBackend 支持使用 X-code方式自定义任意的错误页即错误码。\n变更影响范围：\n如果使用 helm 通过 upgrade 方式要确保配置只有default-backend 可以直接修改deployment ingress-nginx 在更新时会等待所有连接处理完成才会终止结束，可能 termnating 30分钟甚至以上 上面方式均可以更新 default-backend 方式 都不会对nginx controller 服务挂的域名的业务\ndefault backend 状态码重置 本实例只针对 helm 安装的 ingress-nginx 进行改变\nnginx 官方有提供 Customized default backend 方式，通过更改镜像，和注入 configmap 可以把对应的错误页面更改掉\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 controller: config: custom-http-errors: \u0026#34;404,503\u0026#34; defaultBackend: enabled: true image: registry: registry.k8s.io image: ingress-nginx/custom-error-pages tag: v1.0.2@sha256:b2259cf6bfda813548a64bded551b1854cb600c4f095738b49b4c5cdf8ab9d21 extraVolumes: - name: custom-error-pages configMap: name: custom-error-pages items: - key: \u0026#34;404\u0026#34; path: \u0026#34;404.html\u0026#34; - key: \u0026#34;503\u0026#34; path: \u0026#34;503.html\u0026#34; extraVolumeMounts: - name: custom-error-pages mountPath: /www configMap\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # Custom error page configMap --- apiVersion: v1 kind: ConfigMap metadata: name: custom-error-pages data: 404: | \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;PAGE NOT FOUND\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt;PAGE NOT FOUND\u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 503: | \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;CUSTOM SERVICE UNAVAILABLE\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt;CUSTOM SERVICE UNAVAILABLE\u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 如果想把 404 改成 403 需要自己修改 custom-default-backend 的源码\nReference [1] Default backend\n[2] Custom Errors\n","permalink":"https://www.161616.top/ingress-nginx-default-backend-status-code/","summary":"什么是 default backend default backend 是 ingress-nginx 中的一个服务，主要用于处理 nginx controller 无法识别的而请求的服务\n主要提供了两个接口\n/healthz that returns 200 /that returns 404 如何改default backend 状态码 需求：修改 default backend 状态码 404 为 403\n原理：nginx-controller 启动时指定了一个 default backend 容器，如下所示\nbash 1 2 3 4 5 6 7 8 9 10 11 12 containers: - args: - /nginx-ingress-controller # 这里指的是 default-backend 的名称 {namespace_name}/{service_name} - --default-backend-service=$(POD_NAMESPACE)/ingress-nginx-ext-defaultbackend - --publish-service=$(POD_NAMESPACE)/ingress-nginx-ext-controller - --election-id=ingress-controller-leader - --ingress-class=nginx-yewu-ext - --configmap=$(POD_NAMESPACE)/ingress-nginx-ext-controller - --validating-webhook=:8443 - --validating-webhook-certificate=/usr/local/certificates/cert - --validating-webhook-key=/usr/local/certificates/key 通常情况下在通过定义配置文件方式改变是不容易做的，ingress-nginx 提供了一种自定义方式 “custom-error-pages“ 可以完成 ，完成后该 defaultBackend 支持使用 X-code方式自定义任意的错误页即错误码。","title":"修改ingress-nginx中default backend默认状态码"},{"content":"CEPH RGW 支持 Bucket 的 S3 策略语言，但又不完全类似于 S3 的策略，因为 S3 中策略是基于 AWS 的，某些属性在 CEPH 中并不存在，下面就解开 RGW 关于桶策略的配置。\nBucket Policy (桶策略，下文中统称为 BP) 是对象存储中的管理权限和对象存储访问的机制。\nPolicy Language 的组成 BP 的格式采用了 JSON 语言，也就是 PL 是基于 JSON 的一种策略语言，他的格式主要为几个元素\njson 1 2 3 4 5 6 7 8 9 { \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [{ \u0026#34;Effect\u0026#34;: ..., \u0026#34;Principal\u0026#34;: ..., \u0026#34;Action\u0026#34;: ..., \u0026#34;Resource\u0026#34;: ... }] } 该结构由 ==一个== Version (表示当前版本) 和 ==一个或多个== Statement 数组组成，这些数组定义了希望应用的策略。每个语句数组中都有Effect, Principal, Action, Resource 和可选的 Condition 元素。\nEffect Effect 部分定义是一个动作，表示是否 Allow 或 Deny 指定资源的访问\njson 1 \u0026#34;Effect\u0026#34;:\u0026#34;Allow\u0026#34; Principal Principal 部分定义了策略应用的 “用户” 或 “实体” (entity) 或 “服务” 等，这里是按照 aws 中子源固定语法组成，当然在 CEPH 中不存在这些资源，那么相对的也是一种固定格式\nAWS 将用户分为了三类：\nAWS 账户 (AWS ACCOUNT) IAM 用户 (IAM USER) 匿名用户 (anonymous) 语法如下：\njson 1 2 3 4 5 \u0026#34;Principal\u0026#34;: { \u0026#34;AWS\u0026#34;: [ \u0026#34;arn:aws:iam:::user/a0000000-000a-0000-0000-00d0ff0f0000\u0026#34; ] } AWS Account 对于该类 “实体”，采用了下面的语法\njson 1 \u0026#34;AWS\u0026#34;:\u0026#34;account-ARN\u0026#34; 示例\njson 1 \u0026#34;Principal\u0026#34;:{\u0026#34;AWS\u0026#34;:\u0026#34;arn:aws:iam::AccountIDWithoutHyphens:root\u0026#34;} 或\njson 1 \u0026#34;Principal\u0026#34;:{\u0026#34;AWS\u0026#34;:[\u0026#34;arn:aws:iam::AccountID1WithoutHyphens:root\u0026#34;,\u0026#34;arn:aws:iam::AccountID2WithoutHyphens:root\u0026#34;]} IAM USER AWS IAM (AWS Identity and Access Management) ,是 AWS 中用户管理的一种方式，指定 “WHO”, “CAN ACCESS”, “WAHT” (AWS 中的服务和资源、集中管理精细权限)\n对于该类 “实体”，采用了下面的语法\njson 1 \u0026#34;Principal\u0026#34;:{\u0026#34;AWS\u0026#34;:\u0026#34;arn:aws:iam::account-number-without-hyphens:user/username\u0026#34;} 匿名用户 匿名用户就是指对 ”每个人都授予的权限“，可以使用 通配符 (\u0026quot;*\u0026quot;) ，这类权限的配置对象是 ”存储桶中的所有对象均可公开访问“\njson 1 \u0026#34;Principal\u0026#34;:\u0026#34;*\u0026#34; 或者\njson 1 \u0026#34;Principal\u0026#34;:{\u0026#34;AWS\u0026#34;:\u0026#34;*\u0026#34;} 上面两类用户实际上权限分散的还是不一样，主要区别如下：\n\u0026quot;Principal\u0026quot;: \u0026quot;*\u0026quot; 并且 Effect: Allow 那么将允许任何人访问该资源。 如果 Effect: Allow 并且 \u0026quot;Principal\u0026quot; : { \u0026quot;AWS\u0026quot; : \u0026quot;*\u0026quot; }，允许 AWS 账户中的 IAM User, Root User.. 访问该资源（通常 不涉及到 CEPH） Action Action 部分定义了 ”对策略授予 (删除) 的权限”。这些操作包括 list bucket 等的功能；需要注意的是，这里 AWS S3 与 CEPH RGW 中定义的权限又不相同，对于 CEPH 中 Action 的权限集合，可以参考 [1]\nResource Action 部分定义了 “应用于对象存储资源” 例如存储桶和对象，这里的资源类型也是一种基于 aws 资源的固定格式，而在 CEPH 中不存在的部分直接为空，例如：\nBucket resources：\u0026quot;arn:aws:s3:::[bucket]\u0026quot; 应用所存储同种所有对象或部分对象：\u0026quot;arn:aws:s3:::[bucket]/[object]\u0026quot; 在上面的策略语言中，将 [bucket] 替换为存储桶的标签，将 [object] 替换为指定所有对象的通配符值 (*) 或对象的路径和名称。\n例如下面几个示例\n将策略应用到所有对象：\njson 1 2 3 \u0026#34;Resource\u0026#34;: [ \u0026#34;arn:aws:s3:::example-bucket/*\u0026#34; ] 指定目录中的所有对象：\njson 1 2 3 \u0026#34;Resource\u0026#34;: [ \u0026#34;arn:aws:s3:::example-bucket/folder/*\u0026#34; ] 特殊对象\njson 1 2 3 \u0026#34;Resource\u0026#34;: [ \u0026#34;arn:aws:s3:::example-bucket/example-file.ext\u0026#34; ] 桶策略示例 允许任何人查看和下载 bucket 中的对象 json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 { \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [{ \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Principal\u0026#34;: \u0026#34;*\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;s3:GetObject\u0026#34;, \u0026#34;s3:ListBucket\u0026#34; ], \u0026#34;Resource\u0026#34;: [ \u0026#34;arn:aws:s3:::bucket-example/*\u0026#34; ] }] } 授予指定帐户只能对指定目录的访问权限 json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 { \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Principal\u0026#34;: { \u0026#34;AWS\u0026#34;: \u0026#34;arn:aws:iam:::user/a0000000-000a-0000-0000-00d0ff0f0000\u0026#34; }, \u0026#34;Action\u0026#34;: [ \u0026#34;s3:ListBucket\u0026#34; ], \u0026#34;Resource\u0026#34;: [ \u0026#34;arn:aws:s3:::example-bucket\u0026#34; ] }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Principal\u0026#34;: { \u0026#34;AWS\u0026#34;: \u0026#34;arn:aws:iam:::user/a0000000-000a-0000-0000-00d0ff0f0000\u0026#34; }, \u0026#34;Action\u0026#34;: [ \u0026#34;s3:GetObject\u0026#34; ], \u0026#34;Resource\u0026#34;: [ \u0026#34;arn:aws:s3:::example-bucket/test/*\u0026#34; ] } ] } 允许特定 IP 的访问 上面在 “策略语言” 中没有提到一个可选参数 “Condition”，这在 CEPH 中也是支持的，通过使用 “Condition” 您可以选择允许或拒绝来自指定 IP 地址或范围的流量。\n下面的示例仅允许来自指定 IP 地址的所有流量：\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Principal\u0026#34;: \u0026#34;*\u0026#34;, \u0026#34;Action\u0026#34;: \u0026#34;s3:*\u0026#34;, \u0026#34;Resource\u0026#34;: \u0026#34;arn:aws:s3:::example-bucket/*\u0026#34;, \u0026#34;Condition\u0026#34;: { \u0026#34;IpAddress\u0026#34;: { \u0026#34;aws:SourceIp\u0026#34;: \u0026#34;192.0.2.1/32\u0026#34; } } } ] } 目前 CEPH 支持的 Condition 字段的值为，参考与 [1]\naws:CurrentTime aws:EpochTime aws:PrincipalType aws:Referer aws:SecureTransport aws:SourceIp aws:UserAgent aws:username 应用桶策略 桶策略的应用只能通过 s3cmd 命令执行，radosgw-admin 命令并不可以应用，所以要想应用前，需要准备好对应的对象存储相关的配置\n语法\nbash 1 s3cmd setpolicy [policy-file] s3://[bucket-label] 例如，将文件 “policy.json” 中定义的策略应用到名为 “example-bucket” 的存储桶中\nbash 1 s3cmd setpolicy policy.json s3://example-bucket Reference [1] LIMITATIONS\n[2] Guides - Define Access and Permissions using Bucket Policies\n","permalink":"https://www.161616.top/ch05-2-bucket-policy/","summary":"CEPH RGW 支持 Bucket 的 S3 策略语言，但又不完全类似于 S3 的策略，因为 S3 中策略是基于 AWS 的，某些属性在 CEPH 中并不存在，下面就解开 RGW 关于桶策略的配置。\nBucket Policy (桶策略，下文中统称为 BP) 是对象存储中的管理权限和对象存储访问的机制。\nPolicy Language 的组成 BP 的格式采用了 JSON 语言，也就是 PL 是基于 JSON 的一种策略语言，他的格式主要为几个元素\njson 1 2 3 4 5 6 7 8 9 { \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [{ \u0026#34;Effect\u0026#34;: ..., \u0026#34;Principal\u0026#34;: ..., \u0026#34;Action\u0026#34;: ..., \u0026#34;Resource\u0026#34;: ... }] } 该结构由 ==一个== Version (表示当前版本) 和 ==一个或多个== Statement 数组组成，这些数组定义了希望应用的策略。每个语句数组中都有Effect, Principal, Action, Resource 和可选的 Condition 元素。","title":"Ceph对象存储 - 桶策略 Bucket Policy"},{"content":"我们可以看到，kube-proxy 有一个 \u0026ndash;cluster-cidr 的参数，我们就来解开这个参数究竟有没有用\nbash 1 2 $ kube-proxy -h|grep cidr --cluster-cidr string The CIDR range of pods in the cluster. When configured, traffic sent to a Service cluster IP from outside this range will be masqueraded and traffic sent from pods to an external LoadBalancer IP will be directed to the respective cluster IP instead 可以看到，参数说明是说，如果配置，那么从外部发往 Service Cluster IP 的流量将被伪装，从 Pod 发往外部 LB 将被直接发往对应的 cluster IP。但实际上做了什么并不知道，那么就从源码解决这个问题。\n首先我们知道，参数是作为 kube-proxy server 的参数，位于 cmd/kube-proxy 下，而对应的逻辑则位于 pkg/kube-proxy 下，参数很明显，就是 clusterCIDR，那么我们就寻找这个参数的调用即可。\n在 API KubeProxyConfiguration 中我们找到的对应的 ClusterCIDR ，在这里的注释又变为 ”用于桥接集群外部流量“。这里涉及到关于 kube-proxy 的两个模式 “LocalMode” 和 “ProxyMode“。\nLocalMode：表示是来自节点本地流量的模式，包含 ClusterCIDR, NodeCIDR ProxyMode：就是 kube-proxy 最常用的模式，包含 iptables, IPVS, user namespace, kernelspace 而参数 \u0026ndash;cluster-cidr 是作为选择使用的 “本地网络检测器” (Local Network Detector)，这里起到的作用就是 “将集群外部的流量伪装成 service VIP” ，从代码中我们可以看到 Detector 将决定了你使用的是什么网络，无论是 LocalMode 还是 ProxyMode。\n在代码 cmd/kube-proxy/app/server_others.go 中可以看到是如何选择的 LocalMode 方式，可以看出在存在三种模式：\n没有配置 \u0026ndash;cluster-cidr 则会返回一个 NoOpLocalDetector； 在配置了 \u0026ndash;cluster-cidr ，则将会使用 CIDR 的本地模式； 如果 \u0026ndash;cluster-cidr 没有配置，但配置了 LocalModeNodeCIDR，则会设置为 CNI 为该 Node 配置的 POD CIDR 的地址 (使用参数 \u0026ndash;proxy-mode 指定的模式，如果为空，那么会检测对应操作系统默认 Linux 为 iptables，如果内核开启 IPVS 那么则使用 IPVS，windows 默认为 kernelspace) go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func getLocalDetector(mode proxyconfigapi.LocalMode, config *proxyconfigapi.KubeProxyConfiguration, ipt utiliptables.Interface, nodeInfo *v1.Node) (proxyutiliptables.LocalTrafficDetector, error) { switch mode { case proxyconfigapi.LocalModeClusterCIDR: if len(strings.TrimSpace(config.ClusterCIDR)) == 0 { klog.Warning(\u0026#34;detect-local-mode set to ClusterCIDR, but no cluster CIDR defined\u0026#34;) break } return proxyutiliptables.NewDetectLocalByCIDR(config.ClusterCIDR, ipt) case proxyconfigapi.LocalModeNodeCIDR: if len(strings.TrimSpace(nodeInfo.Spec.PodCIDR)) == 0 { klog.Warning(\u0026#34;detect-local-mode set to NodeCIDR, but no PodCIDR defined at node\u0026#34;) break } return proxyutiliptables.NewDetectLocalByCIDR(nodeInfo.Spec.PodCIDR, ipt) } klog.V(0).Info(\u0026#34;detect-local-mode: \u0026#34;, string(mode), \u0026#34; , defaulting to no-op detect-local\u0026#34;) return proxyutiliptables.NewNoOpLocalDetector(), nil } 这里我们以 IPVS 为例，如果开启了 localDetector 在 这个 ipvs proxier 中做了什么? 在代码 pkg/proxy/ipvs/proxier.go 可以看到\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 if !proxier.ipsetList[kubeClusterIPSet].isEmpty() { args = append(args[:0], \u0026#34;-A\u0026#34;, string(kubeServicesChain), \u0026#34;-m\u0026#34;, \u0026#34;comment\u0026#34;, \u0026#34;--comment\u0026#34;, proxier.ipsetList[kubeClusterIPSet].getComment(), \u0026#34;-m\u0026#34;, \u0026#34;set\u0026#34;, \u0026#34;--match-set\u0026#34;, proxier.ipsetList[kubeClusterIPSet].Name, ) if proxier.masqueradeAll { writeLine(proxier.natRules, append(args, \u0026#34;dst,dst\u0026#34;, \u0026#34;-j\u0026#34;, string(KubeMarkMasqChain))...) } else if proxier.localDetector.IsImplemented() { // This masquerades off-cluster traffic to a service VIP. The idea // is that you can establish a static route for your Service range, // routing to any node, and that node will bridge into the Service // for you. Since that might bounce off-node, we masquerade here. // If/when we support \u0026#34;Local\u0026#34; policy for VIPs, we should update this. writeLine(proxier.natRules, proxier.localDetector.JumpIfNotLocal(append(args, \u0026#34;dst,dst\u0026#34;), string(KubeMarkMasqChain))...) } else { // Masquerade all OUTPUT traffic coming from a service ip. // The kube dummy interface has all service VIPs assigned which // results in the service VIP being picked as the source IP to reach // a VIP. This leads to a connection from VIP:\u0026lt;random port\u0026gt; to // VIP:\u0026lt;service port\u0026gt;. // Always masquerading OUTPUT (node-originating) traffic with a VIP // source ip and service port destination fixes the outgoing connections. writeLine(proxier.natRules, append(args, \u0026#34;src,dst\u0026#34;, \u0026#34;-j\u0026#34;, string(KubeMarkMasqChain))...) } } 可以看到“不管使用了什么模式，都会更新一条 iptables 规则” 这就代表了使用了什么模式，而这个则被称之为 LocalTrafficDetector，也就是本地流量的检测，那我们看一下这个做了什么。\n在使用 IPVS 的日志中，可以看到这样一条规则，这个是来自集群外部的 IP 去访问集群 CLUSTER IP (KUBE-CLUSTER-IP，即集群内所有 service IP) 时, 将非集群 IP 地址，转换为集群内的 IP 地址 (做源地址转换)\nbash 1 [DetectLocalByCIDR (10.244.0.0/16)] Jump Not Local: [-A KUBE-SERVICES -m comment --comment \u0026#34;Kubernetes service cluster ip + port for masquerade purpose\u0026#34; -m set --match-set KUBE-CLUSTER-IP dst,dst ! -s 10.244.0.0/16 -j KUBE-MARK-MASQ] 而这个步骤分布在所有模式下 (iptables\u0026amp;ipvs)，这里还是没说到两个概念 LocalMode 和 ProxyMode，实际上这两个模式的区别为：\nLocalMode：集群 IP 伪装采用 ClusterCIDR 还是 NodeCIDR，ClusterCIDR 是使用集群 Pod IP 的地址段 (IP Range)，而 LocalCIDR 只仅仅使用被分配给该 kubernetes node 上的 Pod 做地址伪装 ProxyMode：和 LocalMode 没有任何关系，是 kube-proxy 在运行时使用什么为集群 service 做代理，例如 iptables, ipvs ，而在这些模式下将采用什么 LocalMode 为集群外部地址作伪装，大概分为三种类型： 为来自集群外部地址 (cluster-off)：所有非 Pod 地址的请求执行跳转 (KUBE-POSTROUTING) 没有操作 ：在非 iptables/ipvs 模式下，不做伪装 masqueradeAll：为所有访问 cluster ip 的地址做伪装 ClusterCIDR 原理 kube-proxy 为 kube node 上生成一些 NAT 规则，如下所示\nbash 1 2 3 4 5 6 7 8 9 10 11 -A KUBE-FIREWALL -j KUBE-MARK-DROP -A KUBE-LOAD-BALANCER -j KUBE-MARK-MASQ -A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000 -A KUBE-NODE-PORT -p tcp -m comment --comment \u0026#34;Kubernetes nodeport TCP port for masquerade purpose\u0026#34; -m set --match-set KUBE-NODE-PORT-TCP dst -j KUBE-MARK-MASQ -A KUBE-POSTROUTING -m comment --comment \u0026#34;Kubernetes endpoints dst ip:port, source ip for solving hairpin purpose\u0026#34; -m set --match-set KUBE-LOOP-BACK dst,dst,src -j MASQUERADE -A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN -A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0 -A KUBE-POSTROUTING -m comment --comment \u0026#34;kubernetes service traffic requiring SNAT\u0026#34; -j MASQUERADE -A KUBE-SERVICES ! -s 10.244.0.0/16 -m comment --comment \u0026#34;Kubernetes service cluster ip + port for masquerade purpose\u0026#34; -m set --match-set KUBE-CLUSTER-IP dst,dst -j KUBE-MARK-MASQ -A KUBE-SERVICES -m addrtype --dst-type LOCAL -j KUBE-NODE-PORT -A KUBE-SERVICES -m set --match-set KUBE-CLUSTER-IP dst,dst -j ACCEPT 可以看到这里做了几个链，在 KUBE-SERVICES 链中指明了非来自 ClusterCIDR 的 IP 都做一个，并且访问的目的地址是 KUBE-CLUSTER-IP (ipset 里配置的地址) 那么将跳转到 KUBE-MARK-MASQ 链做一个 --set-xmark 0x4000/0x4000 ，而在 KUBE-POSTROUTING 中对没有被标记 0x4000/0x4000 的操作不做处理\n具体来说，-A KUBE-NODE-PORT -p tcp -m comment --comment \u0026quot;Kubernetes nodeport TCP port for masquerade purpose\u0026quot; -m set --match-set KUBE-NODE-PORT-TCP dst -j KUBE-MARK-MASQ 做了如下操作：\n-A KUBE-SERVICES：将这条规则附加到名为KUBE-SERVICES的iptables链。 ! -s 10.244.0.0/16：排除源IP地址为10.244.0.0/16的流量（即来自Kubernetes服务集群IP的流量）。 -m comment --comment \u0026quot;Kubernetes service cluster ip + port for masquerade purpose\u0026quot;：添加一条注释，说明这个规则的用途。 -m set --match-set KUBE-CLUSTER-IP dst,dst：使用IP集合KUBE-CLUSTER-IP来匹配目标IP地址和目标端口。 -j KUBE-MARK-MASQ：如果流量匹配了前面的条件，将流量传递到名为KUBE-MARK-MASQ的目标。 iptables -j RETURN 是用于iptables规则中的一个目标动作，它不是用于拒绝或接受数据包的动作，而是用于从当前规则链中返回（返回到调用链）的动作。\n具体来说，当规则链中的数据包被标记为 RETURN 时，它们将不再受到当前链中后续规则的影响，而会立即返回到调用链，以便继续进行后续规则的处理。这通常用于某些高级设置，例如在自定义规则链中执行特定的操作后返回到主要的防火墙链。\n从代码中可以看到，对应执行 jump 的操作的链就是 KUBE-MARK-MASQ\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 } else if proxier.localDetector.IsImplemented() { // This masquerades off-cluster traffic to a service VIP. The idea // is that you can establish a static route for your Service range, // routing to any node, and that node will bridge into the Service // for you. Since that might bounce off-node, we masquerade here. // If/when we support \u0026#34;Local\u0026#34; policy for VIPs, we should update this. writeLine(proxier.natRules, proxier.localDetector.JumpIfNotLocal(append(args, \u0026#34;dst,dst\u0026#34;), string(KubeMarkMasqChain))...)\t// KubeMarkMasqChain is the mark-for-masquerade chain KubeMarkMasqChain utiliptables.Chain = \u0026#34;KUBE-MARK-MASQ\u0026#34; // 具体拼接的就是 -j 链名的操作 func (d *detectLocalByCIDR) JumpIfNotLocal(args []string, toChain string) []string { line := append(args, \u0026#34;!\u0026#34;, \u0026#34;-s\u0026#34;, d.cidr, \u0026#34;-j\u0026#34;, toChain) klog.V(4).Info(\u0026#34;[DetectLocalByCIDR (\u0026#34;, d.cidr, \u0026#34;)]\u0026#34;, \u0026#34; Jump Not Local: \u0026#34;, line) return line } 继续往下 KUBE-POSTROUTING 可以看到对应伪装是一个动态的源地址改造，而 RETURN 则不是被标记的请求\ngo 1 2 3 4 5 6 Chain KUBE-POSTROUTING (1 references) target prot opt source destination MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 /* Kubernetes endpoints dst ip:port, source ip for solving hairpin purpose */ match-set KUBE-LOOP-BACK dst,dst,src RETURN all -- 0.0.0.0/0 0.0.0.0/0 mark match ! 0x4000/0x4000 MARK all -- 0.0.0.0/0 0.0.0.0/0 MARK xor 0x4000 MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service traffic requiring SNAT */ 这整体就是 ClusterCIDR 在 kube-proxy 中的应用，换句话说还需要关注一个 LocalCIDR\n","permalink":"https://www.161616.top/ch26-kube-proxy-clustercidr/","summary":"我们可以看到，kube-proxy 有一个 \u0026ndash;cluster-cidr 的参数，我们就来解开这个参数究竟有没有用\nbash 1 2 $ kube-proxy -h|grep cidr --cluster-cidr string The CIDR range of pods in the cluster. When configured, traffic sent to a Service cluster IP from outside this range will be masqueraded and traffic sent from pods to an external LoadBalancer IP will be directed to the respective cluster IP instead 可以看到，参数说明是说，如果配置，那么从外部发往 Service Cluster IP 的流量将被伪装，从 Pod 发往外部 LB 将被直接发往对应的 cluster IP。但实际上做了什么并不知道，那么就从源码解决这个问题。\n首先我们知道，参数是作为 kube-proxy server 的参数，位于 cmd/kube-proxy 下，而对应的逻辑则位于 pkg/kube-proxy 下，参数很明显，就是 clusterCIDR，那么我们就寻找这个参数的调用即可。","title":"kube-proxy参数ClusterCIDR做什么"},{"content":"测试上传/下载对象 存取故据时，客户端必须首先连接至RAD05集群上某存储地，而后根据对像名称由相关的中CRUSH规则完成数据对象寻址。于是为了测试集群的数据存储功能，首先创建一个用于测试的存储池mypool，并设定其PG数量为16个。\nsh 1 ceph osd pool create mypool 16 16 而后，即可将测试文件上传至存储池中。例如下面的rados put命令将/etc/hosts\nrados\nlspool 显示存储池\nrmpool 删除存储池\nmkpool 创建存储池\nrados mkpool mypool 32 32\nsh 1 2 rados mkpool {name} {pgnum} {pgpnum} rados mkpool test 32 32 sh 1 2 $ ceph osd pool create testpool 32 32 pool \u0026#39;testpool\u0026#39; created 列出存储池\ntext 1 2 3 4 5 6 7 8 9 $ ceph osd pool ls mypool rbdpool testpool $ rados lspools mypool rbdpool testpool 而后即可将测试文件上传到存储池中，例如将rados put命令将/etc/issue文件上传至testpool存储池，对象名称仍然较保留文件名issue，而rados ls可以列出指定存储池中的数据对象\nsh 1 2 3 4 rados put issue /etc/issue --pool=testpool $ rados ls --pool=testpool # --pool 指定放入那个存储池中去 issue 而ceph osd map可查看获取到存储池中数据对象的具体位置信息（数据和元数据怎么映射存储的)\nsh 1 2 3 4 ceph osd map testpool issue $ ceph osd map mypool passwd osdmap e36 pool \u0026#39;mypool\u0026#39; (1) object \u0026#39;passwd\u0026#39; -\u0026gt; pg 1.27292a34 (1.14) -\u0026gt; up ([0,3,2], p0) acting ([0,3,2], p0) mypool存储池中的对象passwd被放在pg上1.27292a34 1为存储池编号.后面的编号可以理解为pg的位图。是pg的编号；up ([0,3,2], p0)正常可访问编号0、3、2，副本型存储池，crush算法计算得到，0为主osd。活动集acting ([0,3,2], p0)，此组pg(pg 1.27292a34 (1.14))之下所有的osd([0,3,2])都处于正常活动状态。\n删除数据对象 text 1 ceph osd pool rm testpool --yes-i-really-really-mean-it 删除存储池命令存在数据丢失的风险，Ceph于是默认禁止此类操作。管理员需要在ceph.conf配置文件中启用支持删除存储池的操作后，方可使用如下命令删除存储池。\nbash 1 rados rm issue --pool=mypool ceph集群的访问接口 Ceph块设备接口 Ceph块设备，也称为RADOS块设备（简称RBD），是一种基于RADOS存储系统支持超配，（thin-provisioned）、可伸缩的条带化数据存储系统，它通过librbd库与OSD进行交互。RBD为KVM等虚拟化技术和云OS（如OpenStack和CloudStack）提供高可用和无限扩展性的存储后端，这些系统以来与libvirt和QEMU实用程序与RBD进行集。\n在集群部署完成以后，就具有了RBD接口，RBD接口关键是在客户端的配置。服务端本身可以直接使用。只需创建出存储池，在存储池中就可以创建块设备。块设备主要表现为存储池当中的镜像或映像文件（image）。\n客户端基于librbd库即可将RADOS存储集群用作块设备，不过，用于rbd的存储池需要实现启用rbd功能并进行初始化。例如，创建一个名为rbddata的存储池，在启动rbd功能后对其进行初始化\n对于rbdpool而言，创建完成后并不能直接使用，因为三种应用程序需要单独进行启用。相关存储池的应用才可以。\nsh 1 ceph osd pool create rbpool 64 ## 指明pg数量 sh 1 2 3 4 5 # 默认情况下是裸池 $ ceph osd pool application enable rbdpool rbd enabled application \u0026#39;rbd\u0026#39; on pool \u0026#39;rbdpool\u0026#39; osd pool application enable \u0026lt;poolname\u0026gt; \u0026lt;app\u0026gt; {--yes-i-really-mean-it} enable use of an application \u0026lt;app\u0026gt; [cephfs,rbd,rgw] on pool \u0026lt;poolname\u0026gt; sh 1 2 rbd pool init -p rbddata 不过，rbd存储池并不能直接用于块设备，而是需要事先在其中按需创建映像（image），以及可用映像、创建快照、将映像回滚到快照和查看快照等管理操作。\n创建名为img1的映像\nsh 1 rbd create rbdpool/img --size 1G sh 1 2 3 $ rbd ls -p rbdpool img img1 显示映像的相关信息，rbd info\nsh 1 2 3 4 5 6 7 8 9 10 11 $ rbd info rbdpool/img rbd image \u0026#39;img\u0026#39;: size 1 GiB in 256 objects order 22 (4 MiB objects) id: 38bb6b8b4567 block_name_prefix: rbd_data.38bb6b8b4567 format: 2 features: layering, exclusive-lock, object-map, fast-diff, deep-flatten op_features: flags: create_timestamp: Fri Jun 14 17:08:48 2019 在客户端主机上，用户通过内核级的rbd驱动识别相关设备，即可对其进行分区、创建文件系统并挂载使用。\nRank 层级 MDS MDS在哪台服务器 上 Pool 两个存储池，存储池都位于同一个ceph集群之上，所以看到的空间大小是一样的。\n检查集群状态 命令：ceph-s\n输出信息：\n集群ID 集群运行状况 监视器地图版本号和监视器仲裁的状态 OSD map版本号和OSD的状态 归置组map版本 归置组和存储池数量 所存储数据理论上的数量和所存储对象的数量 所存储数据的总量 获取集群的即时状态 ceph pg stat ceph osd pool stat ceph df ceph df detail ceph df\n输出两端内容：GLOBAL和POOLS\nGLOBAL：存储量概览 POOLS：存储池列表和每个存储池的理论用量，但出不反应副本、克隆数据或快照 GLOBAL段\nsize 集群的整体存储容量 AVAIL 集群中可以使用的可用空间容量 RAW USED 已用的原始存储量 % RAW USED：已用的原始存储量百分比，将此数字与 full ratio和near full ratio搭配使用，可确保您不会用完集群的容量。 检查OSD和Mon的状态 可通过执行以下命令来检查OSD，以确保它们已启动里正在运行\nceph osd stat ceph osd dump 还可以根据OSD在CRUSH map中的位置查看OSD ceph osd tree Ceph将列显CRUSH树及主机它的OSD、OSD是否已启动及其权重 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ ceph osd tree ID CLASS WEIGHT TYPE NAME STATUS REWEIGHT PRI-AFF -1 0.09775 root default -3 0.03897 host stor01 0 hdd 0.01949 osd.0 up 1.00000 1.00000 7 hdd 0.01949 osd.7 up 1.00000 1.00000 -5 0.01959 host stor02 1 hdd 0.00980 osd.1 up 1.00000 1.00000 6 hdd 0.00980 osd.6 up 1.00000 1.00000 -7 0.01959 host stor03 2 hdd 0.00980 osd.2 up 1.00000 1.00000 5 hdd 0.00980 osd.5 up 1.00000 1.00000 -9 0.01959 host stor04 3 hdd 0.00980 osd.3 up 1.00000 1.00000 4 hdd 0.00980 osd.4 up 1.00000 1.00000 集群中存在多个Mon主机时，应该在启动集群之后读取或写入数据之前检查Mon的种裁状态：事实上，管理员也应该定期检查这种仲裁结果。\n显示监视器映射：ceph mon stat命令或者ceph mon dump text 1 2 $ ceph mon stat e3: 3 mons at {stor01=10.0.0.4:6789/0,stor02=10.0.0.5:6789/0,stor03=10.0.0.6:6789/0}, election epoch 20, leader 0 stor01, quorum 0,1,2 stor01,stor02,stor03 显示伸裁状态：ceph quorum status 使用管理套接字 每一个socket文件能够用来直接通过它管理对应的sock背后的守护进程。\nCeph的管理套接字接口常用于查询守护进程。\n套接字默认保存于/var/run/ceph目录 此接口的使用不能以远程方式进程 命令的使用格式\nsh 1 ceph --admin-daemon /var/run/ceph/{socket-name} 获取使用帮助：\ntext 1 ceph --admin-daemon /var/run/ceph/{socket-name} 停止或重启Ceph集群 停止 告知Ceph集群不要将osd标记为out，命令ceph osd set noout 按如下顺序停止守护进程和节点 存储客户端 网关，例如NFS Ganesha或对象网关 元数据服务器 Ceph OSD Ceph Manager Ceph Monitor 启动 以与停止过程相反的顺序启动节点 Ceph Monitor Ceph Manager Ceph OSD 元数据服务器 网关，例如NFS Ganesha或对象网关 存储客户端 删除noout标志，命令ceph osd unset noout Ceph的配置文件 配置文件结构 ceph配置文件使用ini语法格式 ceph在启动时会依次查找多个不同位置的配置文件，如后找的配置文件与前面发生冲突，会覆盖此前的配置信息 注释可通过\u0026quot;#\u0026quot;,\u0026quot;;\u0026quot; 配置文件主要有以下几个配置项所组成 [global]:全局配置,影响ceph存储集群中的所有守护进程 [osd]: 影响Ceph存储集群中的所有ceph-osd守护进程并覆盖全局中的相同设置 [mon]: 影响ceph存储集群中的所有ceph-mon守护进程并覆盖全局中的相同设置 [client]: 影响所有客户端，例如，挂载ceph块设备，ceph对象网关等 每一个独立的配置项是对所有选项生效的，如[mon]，如有需要对单独的选项进行配置可以使用[mon.id]加上id进行标识。\n您可以通过输入由.分隔的类型来指定守护程序的特定实例的配置，您可以指定该实例。 并通过实例ID\nceph osd守护进程的实例id总是数字，但它可能是ceph monitors的字母数字\n例如[mon.a]、[mon.b]、[mon.0]等 按顺序包含的默认ceph配置文件位置\n$CEPH_CONF环境变量指定的文件路径路径\n-c /path/ceph.conf 使用-c的命令行选项传递给ceph各应用程序或守护进程的命令行选项\n/etc/ceph/ceph.conf\n~/.ceph/config\n./ceph.conf 用户当前工作目录\n在配置文件配置时，还可以使用元变量来引用配置文件中的其他信息或引用ceph集群中的元数据信息做变量替换的。称作元参数或元变量\n常用的元参数\ncluster: 当前Ceph集群的名称 $type: 当前服务的类型名称，可能会展开为OSD或mon $id: 进程的标识符，例如对osd.0来说，其标识符为0 $host：守护进程所在的主机的主机名 $name: 其值为$type.$id 进程的运行时配置\n在进程的运行当中，设定osd、mon、mgr等工作特性。\n要查看运行时配置，请登录Ceph节点并执行：\ntext 1 ceph daemon {daemon-type}.{id} config show 获取帮助信息\nsh 1 ceph daemon {daemon-type}.{id} help 在运行时获取特定配置设置\nsh 1 2 3 4 5 ceph daemon {daemon-type}.{id} config get {parameter} # 例如： ceph daemon osd.0 config get public_addr 在运行时设置特定配置\n设置运行时配置有两种常用方法：\n使用Ceph mmonitor ceph tell {daemon-type}.{daemon id or *} injectargs --{name} {value} [--{name}} {value}] 例如：ceph tell osd.0 injectargs '--debug-osd 0/5' 使用 administration socket ceph daemon {daemon-type}.{id} set {name} {type} 例如：ceph osd.0 config set debug_osd 0/5 ","permalink":"https://www.161616.top/ch11-1-ceph-common-cmd/","summary":"测试上传/下载对象 存取故据时，客户端必须首先连接至RAD05集群上某存储地，而后根据对像名称由相关的中CRUSH规则完成数据对象寻址。于是为了测试集群的数据存储功能，首先创建一个用于测试的存储池mypool，并设定其PG数量为16个。\nsh 1 ceph osd pool create mypool 16 16 而后，即可将测试文件上传至存储池中。例如下面的rados put命令将/etc/hosts\nrados\nlspool 显示存储池\nrmpool 删除存储池\nmkpool 创建存储池\nrados mkpool mypool 32 32\nsh 1 2 rados mkpool {name} {pgnum} {pgpnum} rados mkpool test 32 32 sh 1 2 $ ceph osd pool create testpool 32 32 pool \u0026#39;testpool\u0026#39; created 列出存储池\ntext 1 2 3 4 5 6 7 8 9 $ ceph osd pool ls mypool rbdpool testpool $ rados lspools mypool rbdpool testpool 而后即可将测试文件上传到存储池中，例如将rados put命令将/etc/issue文件上传至testpool存储池，对象名称仍然较保留文件名issue，而rados ls可以列出指定存储池中的数据对象","title":"ceph常用命令"},{"content":"Rebalance 当 Ceph 集群在扩容/缩容后，Ceph会更新 Cluster map, 在更新时会更新 Cluster map 也会更新 “对象的放置” CRUSH 会平均但随机的将对象放置在现有的 OSD 之上，在 Rebalancing 时，只有少量数据进行移动而不是全部数据进行移动，直到达到 OSD 与 对象 之间的平衡，这个过程就叫做 Ceph 的 Rebalance。\n需要注意的是，当集群中的 OSD 数量越多，那么在做 Rebalance 时所移动的就越少。例如，在具有 50 个 OSD 的集群中，在添加 OSD 时可能会移动 1/50th 或 2% 的数据。\n如下图所示，当前集群有两个 OSD，当在集群中添加一个 OSD，使其数量达到3时，这个时候会触发 Rebalance，所移动的数量为 OSD1 上的 PG3 与 OSD2 上的 PG 6和9\n图：Ceph Rebalancing 示意图 Source：https://access.redhat.com/documentation/zh-cn/red_hat_ceph_storage/4/html/architecture_guide/ceph-rebalancing-and-recovery_arch\nBalancer 执行 Rebalance 的模块时 Balancer，其可以优化 OSD 上的放置组 (PG) ，以实现平衡分配。\n可以通过命令查看 balancer 的状态\nbash 1 ceph balancer status https://docs.ceph.com/en/latest/rados/operations/balancer/\nBackfill Ceph 回填 (Backfill) 指的是每当删除 OSD 时，Ceph 都会使用 “Backfill” 和 “recovery” 来重新 rebalance 存储集群。这样做是为了根据PG 策略保留数据的多个副本。这两个操作都会占用系统资源，因此当 Ceph 存储集群处于负载状态时，Ceph 的性能将会下降，因为 Ceph 将资源转移到 “回填” 和 “恢复” 过程。\n有时为了在删除 OSD 时保持 Ceph 存储可接受的性能，需要先降低 “Backfill” 和 “recovery” 操作的优先级。降低优先级的代价是，较长时间内的数据副本较少，这将会导致数据面临风险。\n回填和恢复的发生是发生在 OSD/节点 故障或新增时被触发，如果所有的回填同时发生，会对OSD带来很大的负载，这个现象叫做 ”雷群效应“ (\u0026ldquo;thundering herd\u0026rdquo; effect)\nConfigration backfill paramter 回填的参数通常位于 OSD 参数下，在 Ceph OSD 中关于 backfill [1] 的参数如下：\n参数 类型 默认值 说明 osd_max_backfills uint 1 允许回填到单个 OSD 或从单个 OSD 回填的最大数量。请注意，这对于读和写操作是分开应用的。 osd_backfill_scan_min int 64 每次回填扫描的最小对象数 osd_backfill_scan_max int 512 每次回填扫描的最大对象数 osd_backfill_retry_interval float 30.0 重试回填请求之前等待的秒数。 查看当前参数\n查看配置之前需要确定 OSD 所在的节点，例如 OSD.1 可以通过 ceph osd tree 获取所有 OSD 列表\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $ ceph osd tree ID CLASS WEIGHT TYPE NAME STATUS REWEIGHT PRI-AFF -1 13.09845 root default -3 4.36615 host PMX1 0 nvme 0.72769 osd.0 up 1.00000 1.00000 1 nvme 0.72769 osd.1 up 1.00000 1.00000 2 nvme 0.72769 osd.2 up 1.00000 1.00000 3 nvme 0.72769 osd.3 up 1.00000 1.00000 4 nvme 0.72769 osd.4 up 1.00000 1.00000 5 nvme 0.72769 osd.5 up 1.00000 1.00000 -5 4.36615 host PMX2 6 nvme 0.72769 osd.6 up 1.00000 1.00000 7 nvme 0.72769 osd.7 up 1.00000 1.00000 8 nvme 0.72769 osd.8 up 1.00000 1.00000 9 nvme 0.72769 osd.9 up 1.00000 1.00000 10 nvme 0.72769 osd.10 up 1.00000 1.00000 11 nvme 0.72769 osd.11 up 1.00000 1.00000 -7 4.36615 host PMX3 12 nvme 0.72769 osd.12 up 1.00000 1.00000 13 nvme 0.72769 osd.13 up 1.00000 1.00000 14 nvme 0.72769 osd.14 up 1.00000 1.00000 15 nvme 0.72769 osd.15 up 1.00000 1.00000 16 nvme 0.72769 osd.16 up 1.00000 1.00000 17 nvme 0.72769 osd.17 up 1.00000 1.00000 在拿到 OSD 坐在节点可以通过下面命令查看对应的 OSD 配置\nbash 1 2 3 4 5 6 7 8 $ ceph daemon osd.1 config get osd_max_backfills { \u0026#34;osd_max_backfills\u0026#34;: \u0026#34;1\u0026#34; } $ ceph daemon osd.1 config get osd_recovery_max_active { \u0026#34;osd_recovery_max_active\u0026#34;: \u0026#34;0\u0026#34; } 接下来可以根据 OSD 类型(SSD, HDD, nvme) 的不同，来相应的调整，例如 NVMes 比 HDD 更好的性能，那么可以设置大的回填\nbash 1 2 ceph config show osd.0 osd_recovery_max_active ceph config set osd osd_max_backfills 16 Recovery 如果 Ceph OSD 守护进程崩溃并重新上线，通常这个OSD会与 PG 中包含更新版本对象的其他 Ceph OSD 守护进程不同步。发生这种情况时，Ceph OSD 守护进程会进入恢复模式 (Recovery)，并寻求获取最新的数据副本并使其映射恢复到最新状态。根据 Ceph OSD daemon 关闭的时间长短，OSD 的对象和 PG 可能会明显过时。此外，如果一个故障域（机架）发生故障，多个 Ceph OSD 守护进程可能会同时恢复在线状态。这会使恢复过程耗时且占用资源。\n为了维持操作性能，Ceph 在执行恢复时限制恢复请求数量、线程和对象块大小，这使得 Ceph 在降级状态下也能良好运行。\n恢复的参数通常位于 OSD 参数下，在 Ceph OSD 中关于 recovery [2] 的参数如下：\n参数 类型 默认值 说明 osd_recovery_delay_start float 0.0 peer互连完成后，Ceph 将延迟指定的秒数，然后再开始恢复 RADOS 对象。 osd_recovery_max_active uint 0 每个 OSD 一次的活动恢复请求数。更多请求将加速恢复，但请求会增加集群的负载 osd_recovery_max_active_hdd uint 3 如果主设备是旋转设备（HDD），则每个 OSD 一次的活动恢复请求数。 osd_recovery_max_active_ssd uint 10 如果主设备是非旋转设备（即 SSD），则每个 OSD 一次的活动恢复请求数。 osd_recovery_max_chunk size 8Mi 恢复操作可以携带的数据块的最大总大小，需要注意单位。 osd_recovery_max_single_start uint 1 当 OSD (daemon)恢复时，每个 OSD 新启动的恢复操作的最大数量。 osd_recovery_sleep float 0.0 在下一次“恢复”或“回填”操作之前休眠的时间（以秒为单位）。增加此值将减慢恢复操作，而客户端操作受影响较小。 osd_recovery_sleep_hdd float 0.1 HDD 下次恢复或回填操作之前的睡眠时间（以秒为单位）。 osd_recovery_sleep_ssd float 0.0 SSD 下一次恢复或回填操作之前的睡眠时间（以秒为单位）。 osd_recovery_sleep_hybrid float 0.025 当 OSD 数据位于 HDD 上并且 OSD 日志/WAL+DB 位于 SSD 上时，在下一次恢复或回填操作之前休眠的时间（以秒为单位）。 osd_recovery_priority uint 5 为恢复工作队列设置的默认优先级。与 Pool 无关 Ceph backfill 和 recovery 也可以在 Ceph dashboard 中进行配置\n异步恢复 在 Nautilus 版本之前 “恢复” 动作是同步的，同步最显著的一个特征就是 “同步时会阻止对 RADOS 对象的写入，直到恢复为止”。\n回填操作与恢复操作有些不同，回填会临时分配不同的活动集(Active set, PG的一个属性)，并回填活动集之外的 OSD 来允许继续写入\n而为了避免 “同步恢复” 的问题 Ceph 提供了一种可以异步恢复的配置，当异步恢复发生时，对活动集成员可继续写入，有关于更多的异步说明，可以参考 Ceph 文档 asynchronous recovery 部分\nReference [1] backfilling\n[2] recovery\n[3] ASYNCHRONOUS RECOVERY\n[4] Advantages and Disadvantages of SAN\n","permalink":"https://www.161616.top/ch6-1-ceph-rebalance/","summary":"Rebalance 当 Ceph 集群在扩容/缩容后，Ceph会更新 Cluster map, 在更新时会更新 Cluster map 也会更新 “对象的放置” CRUSH 会平均但随机的将对象放置在现有的 OSD 之上，在 Rebalancing 时，只有少量数据进行移动而不是全部数据进行移动，直到达到 OSD 与 对象 之间的平衡，这个过程就叫做 Ceph 的 Rebalance。\n需要注意的是，当集群中的 OSD 数量越多，那么在做 Rebalance 时所移动的就越少。例如，在具有 50 个 OSD 的集群中，在添加 OSD 时可能会移动 1/50th 或 2% 的数据。\n如下图所示，当前集群有两个 OSD，当在集群中添加一个 OSD，使其数量达到3时，这个时候会触发 Rebalance，所移动的数量为 OSD1 上的 PG3 与 OSD2 上的 PG 6和9\n图：Ceph Rebalancing 示意图 Source：https://access.redhat.com/documentation/zh-cn/red_hat_ceph_storage/4/html/architecture_guide/ceph-rebalancing-and-recovery_arch\nBalancer 执行 Rebalance 的模块时 Balancer，其可以优化 OSD 上的放置组 (PG) ，以实现平衡分配。\n可以通过命令查看 balancer 的状态\nbash 1 ceph balancer status https://docs.","title":"Ceph重新平衡 - Rebalance"},{"content":"Overview 阅读完本文，您当了解\nKubernetes 卷 CephFS 在 kubernetes 中的挂载 Kubelet VolumeManager 本文只是个人理解，如果有大佬觉得不是这样的可以留言一起讨论，参考源码版本为 1.18.20，与高版本相差不大\nVolumeManager VolumeManager VM 是在 kubelet 启动时被初始化的一个异步进程，主要是维护 “Pod\u0026quot; 卷的两个状态，”desiredStateOfWorld“ 和 ”actualStateOfWorld“； 这两个状态用于将节点上的卷 “协调” 到所需的状态。\nVM 实际上包含三个 “异步进程” (goroutine)，其中有一个 reconciler 就是用于协调与挂载的，下面就来阐述 VM 的挂载过程。\nVM中的重要组件 actualStateOfWorld mountedPod desiredStateOfWorld VolumeToMount podToMount VM的组成 VM 的代码位于，由图可以看出，主要包含三个重要部分：\nreconciler：协调器 populator：填充器 cache：包含 ”desiredStateOfWorld“ 和 ”actualStateOfWorld“ 图：VM的目录组成 在代码结构上，volumeManager 如下所示\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 // volumeManager implements the VolumeManager interface type volumeManager struct { // DesiredStateOfWorldPopulator 用来与 API 服务器通信以获取 PV 和 PVC 对象的 API 客户端 kubeClient clientset.Interface // VolumePluginMgr 是用于访问 VolumePlugin 插件的 VolumePlugin 管理器。它必须预初始化。 volumePluginMgr *volume.VolumePluginMgr // desiredStateOfWorld 是一个数据结构，包含根据 VM 所需的状态：即应附加哪些卷以及 \u0026#34;哪些pod” 正在引用这些卷。 // 使用 kubelet pod manager 根据 world populator 的所需状态填充数据结构。 desiredStateOfWorld cache.DesiredStateOfWorld // 与 desiredStateOfWorld 相似，是实际状态：即哪些卷被 attacted 到该 Node 以及 volume 被 mounted 到哪些 pod。 // 成功完成 reconciler attach,detach, mount, 和 unmount 操作后，将填充数据结构。 actualStateOfWorld cache.ActualStateOfWorld // operationExecutor 用于启动异步 attach,detach, mount, 和 unmount 操作。 operationExecutor operationexecutor.OperationExecutor // reconciler reconciler 运行异步周期性循环，通过使用操作执行器触发 attach,detach, mount, 和 unmount操作 // 来协调 desiredStateOfWorld 与 actualStateOfWorld。 reconciler reconciler.Reconciler // desiredStateOfWorldPopulator 运行异步周期性循环以使用 kubelet Pod Manager 填充desiredStateOfWorld。 desiredStateOfWorldPopulator populator.DesiredStateOfWorldPopulator // csiMigratedPluginManager keeps track of CSI migration status of plugins csiMigratedPluginManager csimigration.PluginManager // intreeToCSITranslator translates in-tree volume specs to CSI intreeToCSITranslator csimigration.InTreeToCSITranslator } VM的初始化 入口：“volumeManager”(vm) 的 初始化 操作发生在 kubelet Run 时被作为一个异步进程启动。 VM 初始化： 如代码1所示，VM在初始化阶段创建了两个 cache 对象 “desiredStateOfWorld”（dsw）和“actualStateOfWorld”（asw）以及一个 “operationExecutor”，用于启动异步的线程操作 attach,detach, mount, 和 unmount 如代码2所示：VM在初始化阶段还创建了 “desiredStateOfWorldPopulator” (dswp) 与 “reconciler” “reconciler” 通过使用上面的 “operationExecutor” 触发 attach,detach, mount 和 unmount来协调 dsw 与 asw 代码1\ngo 1 2 3 4 5 6 7 8 9 10 11 12 vm := \u0026amp;volumeManager{ kubeClient: kubeClient, volumePluginMgr: volumePluginMgr, desiredStateOfWorld: cache.NewDesiredStateOfWorld(volumePluginMgr), actualStateOfWorld: cache.NewActualStateOfWorld(nodeName, volumePluginMgr), operationExecutor: operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( kubeClient, volumePluginMgr, recorder, checkNodeCapabilitiesBeforeMount, blockVolumePathHandler)), } 代码2：\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 vm.intreeToCSITranslator = intreeToCSITranslator vm.csiMigratedPluginManager = csiMigratedPluginManager vm.desiredStateOfWorldPopulator = populator.NewDesiredStateOfWorldPopulator( kubeClient, desiredStateOfWorldPopulatorLoopSleepPeriod, desiredStateOfWorldPopulatorGetPodStatusRetryDuration, podManager, podStatusProvider, vm.desiredStateOfWorld, vm.actualStateOfWorld, kubeContainerRuntime, keepTerminatedPodVolumes, csiMigratedPluginManager, intreeToCSITranslator) vm.reconciler = reconciler.NewReconciler( kubeClient, controllerAttachDetachEnabled, reconcilerLoopSleepPeriod, waitForAttachTimeout, nodeName, vm.desiredStateOfWorld, vm.actualStateOfWorld, vm.desiredStateOfWorldPopulator.HasAddedPods, vm.operationExecutor, mounter, hostutil, volumePluginMgr, kubeletPodsDir) VM 的 Run VM 是在 Kubelet 启动时作为异步线程启动，如代码1所示\n如下面代码2所示，VM 在运行时会启动 三个 异步线程\n第一个调用是 第二个是调用 “dswp” 填充其的“ Run ”，这里主要做的操作是从 API 拿到 Pod 列表，根据对应条件来决定 attach,detach, mount, 和 unmount 第二个调用的是，reconciler 来协调应该安装的卷是否已安装以及应该卸载的卷是否已卸载。 第三个调用的是，volumePluginMgr，启用 CSI informer 代码1\ntext 1 2 // Start volume manager go kl.volumeManager.Run(kl.sourcesReady, wait.NeverStop) 代码2\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func (vm *volumeManager) Run(sourcesReady config.SourcesReady, stopCh \u0026lt;-chan struct{}) { defer runtime.HandleCrash() go vm.desiredStateOfWorldPopulator.Run(sourcesReady, stopCh) klog.V(2).Infof(\u0026#34;The desired_state_of_world populator starts\u0026#34;) klog.Infof(\u0026#34;Starting Kubelet Volume Manager\u0026#34;) go vm.reconciler.Run(stopCh) metrics.Register(vm.actualStateOfWorld, vm.desiredStateOfWorld, vm.volumePluginMgr) if vm.kubeClient != nil { // start informer for CSIDriver vm.volumePluginMgr.Run(stopCh) } \u0026lt;-stopCh klog.Infof(\u0026#34;Shutting down Kubelet Volume Manager\u0026#34;) } VM 的调用流程 desiredStateOfWorldPopulator DesiredStateOfWorldPopulator 是一个周期 Loop，会定期循环遍历 Active Pod 列表，并确保每个 Pod 都处于所需状态（如果有卷，World state）。它还会验证 World cache 中处于所需状态的 pod 是否仍然存在，如果不存在，则会将其删除。\ndesiredStateOfWorldPopulator 结构包含两个方法，ReprocessPod 和 HasAddedPods；ReprocessPod 负责将 processedPods 中指定 pod 的值设置为false，强制重新处理它。这是在 Pod 更新时启用重新挂载卷所必需的。而 HasAddedPods 返回 填充器 是否已循环遍历 Active Pod 列表并将它们添加到 world cache 的所需状态。\n在期待填充器 desiredStateOfWorldPopulator 启动时，会运行一个 populatorLoop，这里主要负责运行两个函数，\nfindAndAddNewPods 负责迭代所有 pod，如果它们不存在添加到 desired state of world (desiredStateOfWorld) findAndRemoveDeletedPods 负责迭代 desiredStateOfWorld 下的所有 Pod，如果它们不再存在则将其删除 reconciler reconciler Run 的过程是通过一个 Loop 函数 reconciliationLoopFunc 完成的，正如下列 代码 所示\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 func (rc *reconciler) Run(stopCh \u0026lt;-chan struct{}) { wait.Until(rc.reconciliationLoopFunc(), rc.loopSleepDuration, stopCh) } func (rc *reconciler) reconciliationLoopFunc() func() { return func() { rc.reconcile() // Sync the state with the reality once after all existing pods are added to the desired state from all sources. // Otherwise, the reconstruct process may clean up pods\u0026#39; volumes that are still in use because // desired state of world does not contain a complete list of pods. if rc.populatorHasAddedPods() \u0026amp;\u0026amp; !rc.StatesHasBeenSynced() { klog.Infof(\u0026#34;Reconciler: start to sync state\u0026#34;) rc.sync() } } } func (rc *reconciler) reconcile() { // Unmounts are triggered before mounts so that a volume that was // referenced by a pod that was deleted and is now referenced by another // pod is unmounted from the first pod before being mounted to the new // pod. // 卸载会在挂载之前触发，以便已删除的 Pod 引用的卷现在被另一个 Pod 引用， // 然后再挂载到新 Pod 之前从第一个 Pod 中卸载。 rc.unmountVolumes() // Next we mount required volumes. This function could also trigger // attach if kubelet is responsible for attaching volumes. // If underlying PVC was resized while in-use then this function also handles volume // resizing. // 接下来我们安装所需的卷。如果 kubelet 负责附加卷， // 则此函数还可以触发附加。如果底层 PVC 在使用时调整了大小，则此函数还可以处理卷大小调整。 rc.mountAttachVolumes() // Ensure devices that should be detached/unmounted are detached/unmounted. // 确保应 detached/unmounted 的设备已完成 detached/unmounted。 rc.unmountDetachDevices() } Reconciler 是挂载部分最重要的角色，用于协调应该安装的卷是否已安装以及应该卸载的卷是否已卸载；对应的，实际上执行的为三个函数：“unmountVolumes”、“mountAttachVolumes” 和 “unmountDetachDevices”。\nmountAttachVolumes 首先，“mountAttachVolumes” 会调用 “dsw” (desiredStateOfWorld) 的函数 “GetVolumesToMount” 来检索所有 “volumesToMount” 并迭代它们，这里主要是为了确保 “volumes” 应完成了 “attached/mounted”\n接下来这个循环做的工作是，对于每个 Volume 和 Pod，都会检查该 Volume 或 Pod 是否存在于 “asw” 的 “attachedVolumes” 中。如果 Volume 不存在，则“asw”返回 “newVolumeNotAttachedError ”，否则它检查指定的 pod 是否存在并根据状态返回结果。这里存在 三个状态，返回也是根据这个状态返回。这里主要为了得到挂载路径和是否挂载\nVolumeMounted：表示 Volume 已挂载到 pod 的本地路径中\nVolumeMountUncertain：表示 Volume 可能会也可能不会安装在 Pod 的本地路径中\nVolumeNotMounted：表示 Volume 还未挂载到 pod 的本地路径中\n当“ asw” 返回 “ newVolumeNotAttachedError ” 时，“reconciler” 会检查 “controllerAttachDetachEnabled” 是否启用，或 “volumeToMount” 没有实现了对应插件，这里面如果其中任何一个为 true，“reconciler” 将调用 “operationExecutor” 来执行操作“ ，走到这里代表了 Volume 没有被 attach，或者没有实现 attacher，例如 cephfs 没有实现 attacher；或者是 kubelet 禁用了 attach [1] （默认是开启状态），将进入 “ VerifyControllerAttachedVolume ”\n在此期间，“operationExecutor” 生成一个名为 “verifyControllerAttachedVolumeFunc” 的函数来实际实现。在此函数中，如果 “volumeToMount” 的 “PluginIsAttachable” 为 false（没有实现），则假设其已经实现并标记 attached，标记出错时进行重试（这是一个函数用于后面的调用，这里只是定义）\n如果还没有将 Node attached 到 Volume 节点列表状态中，则返回错误进行重试（这是一个函数用于后面的调用，这里只是定义）\n上面两个步骤是为了组装这个操作，返回的是操作的内容，包含执行的函数，完成的hook等，最后运行这个函数并返回\n这是步骤3的另外一个分支，即 kubelet 启用了 ADController，并且实现了对应的 attcher，那么将执行附加操作\n拼接对象 执行函数 ”AttachVolume“ AttachVolume 如上面步骤一样，拼接出最后的执行的动作，进行执行操作（将 node 附加到 volume 之上） 步骤5 表示 3, 4 条件均不满足，也就是 Attached，目前状态为 ”未挂载“ 或者 ”已挂载“，将执行这个步骤，未挂载的进行挂载，已挂载的进行 remount\n在该分支中（也就是 步骤5 执行的）执行的是名为 “GenerateMountVolumeFunc“ 的函数，在此函数中，会获取 Plugin ，并通过 Plugin 创建出一个 volumeMounter，在通过 Plugin 获取一个 deviceMouter（能够挂载块设备的）；当然我们这里挂载的是 ”cephfs“ 所以没有 ”deviceMouter“ 这里不被执行。\n如果 ”deviceMounter“ 定义了，那么则执行这个 plugin 的 \u0026ldquo;MountDevice\u0026rdquo; 函数 如果没有定义，那么执行 volumeMounter 的 SetUp 进行挂载（因为不是块设备） 执行 SetUp 函数，通常 NFS, CephFS, HostPath，都实现了这个函数，那么就会通过这个函数挂载到 Node 对应的目录\n最后通过 Overlay2 文件系统附加到容器里\nReference ​[1] command-line-tools-reference kubelet\n​[2] What happens when volumeManager in the kubelet starts?\n","permalink":"https://www.161616.top/ch29-volumemanager/","summary":"Overview 阅读完本文，您当了解\nKubernetes 卷 CephFS 在 kubernetes 中的挂载 Kubelet VolumeManager 本文只是个人理解，如果有大佬觉得不是这样的可以留言一起讨论，参考源码版本为 1.18.20，与高版本相差不大\nVolumeManager VolumeManager VM 是在 kubelet 启动时被初始化的一个异步进程，主要是维护 “Pod\u0026quot; 卷的两个状态，”desiredStateOfWorld“ 和 ”actualStateOfWorld“； 这两个状态用于将节点上的卷 “协调” 到所需的状态。\nVM 实际上包含三个 “异步进程” (goroutine)，其中有一个 reconciler 就是用于协调与挂载的，下面就来阐述 VM 的挂载过程。\nVM中的重要组件 actualStateOfWorld mountedPod desiredStateOfWorld VolumeToMount podToMount VM的组成 VM 的代码位于，由图可以看出，主要包含三个重要部分：\nreconciler：协调器 populator：填充器 cache：包含 ”desiredStateOfWorld“ 和 ”actualStateOfWorld“ 图：VM的目录组成 在代码结构上，volumeManager 如下所示\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 // volumeManager implements the VolumeManager interface type volumeManager struct { // DesiredStateOfWorldPopulator 用来与 API 服务器通信以获取 PV 和 PVC 对象的 API 客户端 kubeClient clientset.","title":"深入理解kubelet - VolumeManager源码解析"},{"content":"背景 目前的 Kubernetes 集群资源面板是基于集群的使用资源，因为是多集群，业务同时运行字啊不同集群上，如果通过 label 来划分业务使用的资源情况，这个才是真的和每个集群的每个业务使用的资源有关。\n对于这种场景的划分，Kubernetes 中有一个专门的名词是 Pod 的拓扑域；基于这些需要做的事情就如下步骤\n首先确定node label可以搜集到，如果不存在需要收集 当收集到node label 时，需要根据对应的 label 将一个域中的 根据域（label）做变量注入到 对应的查询语句中以生成图表 收集 node label 在使用 kube-prometheus-stack 中收集 kubernetes 的 node label 需要手动启动参数 - --metric-labels-allowlist=nodes=[*] 才可以收集到 node label，手动给Node 打上标签，test 拓扑域 为 aaaa bbbb两个\n在 helm 中直接增加如下：\nbash 1 - nodes=[*] 查看 label\nbash 1 2 3 4 $ kubectl get node --show-labels NAME STATUS ROLES AGE VERSION LABELS node01 Ready \u0026lt;none\u0026gt; 13d v1.16.10 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node01,kubernetes.io/os=linux,test=bbbb node01 Ready \u0026lt;none\u0026gt; 13d v1.16.10 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node01,kubernetes.io/os=linux,test=aaaa 开启后，会在 kube_node_labels 收取到 node 的 label，其他 node label 没什么用，使用 relabeing 删除掉\nyaml 1 2 regex: label_(beta_kubernetes_io_arch|beta_kubernetes_io_os|kubernetes_io_arch|kubernetes_io_os|kubernetes_io_hostname) action: labeldrop 使用标签分组 因为标签 只存在于 kube_node_label 指标中，需要利用这个指标对每种类型 Pod 分组，首先找到指标的相同标签，可以看到每个 Pod 都会存在一个 node=\u0026ldquo;xxxxxxx\u0026rdquo; 这与 kube_node_labels 上的相符合。\nbash 1 2 3 kube_pod_container_resource_requests{container=\u0026#34;alertmanager\u0026#34;, endpoint=\u0026#34;http\u0026#34;, instance=\u0026#34;10.0.130.150:8080\u0026#34;, job=\u0026#34;kube-state-metrics\u0026#34;, namespace=\u0026#34;default\u0026#34;, node=\u0026#34;node003\u0026#34;, pod=\u0026#34;alertmanager-kube-prometheus-stack-alertmanager-0\u0026#34;, resource=\u0026#34;memory\u0026#34;, service=\u0026#34;kube-prometheus-stack-kube-state-metrics\u0026#34;, uid=\u0026#34;12787588-b05e-466a-9f1a-661a05e8b634\u0026#34;, unit=\u0026#34;byte\u0026#34;} kube_node_labels{container=\u0026#34;kube-state-metrics\u0026#34;, endpoint=\u0026#34;http\u0026#34;, instance=\u0026#34;10.0.130.150:8080\u0026#34;, job=\u0026#34;kube-state-metrics\u0026#34;, label_test=\u0026#34;aaaa\u0026#34;, namespace=\u0026#34;default\u0026#34;, node=\u0026#34;node195\u0026#34;, pod=\u0026#34;kube-prometheus-stack-kube-state-metrics-7fb89968cb-jrrdp\u0026#34;, service=\u0026#34;kube-prometheus-stack-kube-state-metrics\u0026#34;} 然后使用 promQL 的 join 表达式，对应决定拓扑域的 label 注入到每个 Pod 的标签中\nbash 1 2 kube_pod_info{node=xxx} * on(node) group_left(test) kube_node_labels{test=\u0026#34;aaa\u0026#34;} 上面的表达式中，选择了 kube_pod_info 指标，并且使用 node=xxx 进行过滤。然后使用 on(node) 语句指定在 node 标签上进行 join 操作，同时使用 group_left(test) 告诉 Prometheus 把 kube_node_labels 中的 test 标签添加到左侧的指标（即 kube_pod_info）中。\n通过这种方式，我们就可以把 kube_node_labels 中的 test=aaa 标签附加到所有 kube_pod_info 上，且它们都是在相同的节点中。具体效果如下图\n图1：node label指标 grafana 使用 label 做变量 有了决定拓扑域的标签，还需要吧这个 label 作为 grafana 的变量\n图2：grafana变量配置 这个里可以用 promql 的 by 进行分组\nbash 1 2 count(kube_pod_info{} * on(node) group_left(label_test) kube_node_labels{}) by (label_test) 如图1所示，这时就知道每个拓扑域中的 Pod 使用了多少资源，以及可以是用 聚合函数 算出每个拓扑域总资源等信息\n基于 kube_node_labels 作为变量进行输出面板\n图3：grafana定义变量 图4：promql ","permalink":"https://www.161616.top/kubernetes-node-label-dashboard/","summary":"背景 目前的 Kubernetes 集群资源面板是基于集群的使用资源，因为是多集群，业务同时运行字啊不同集群上，如果通过 label 来划分业务使用的资源情况，这个才是真的和每个集群的每个业务使用的资源有关。\n对于这种场景的划分，Kubernetes 中有一个专门的名词是 Pod 的拓扑域；基于这些需要做的事情就如下步骤\n首先确定node label可以搜集到，如果不存在需要收集 当收集到node label 时，需要根据对应的 label 将一个域中的 根据域（label）做变量注入到 对应的查询语句中以生成图表 收集 node label 在使用 kube-prometheus-stack 中收集 kubernetes 的 node label 需要手动启动参数 - --metric-labels-allowlist=nodes=[*] 才可以收集到 node label，手动给Node 打上标签，test 拓扑域 为 aaaa bbbb两个\n在 helm 中直接增加如下：\nbash 1 - nodes=[*] 查看 label\nbash 1 2 3 4 $ kubectl get node --show-labels NAME STATUS ROLES AGE VERSION LABELS node01 Ready \u0026lt;none\u0026gt; 13d v1.16.10 beta.","title":"记录kubernetes node label的面板实施"},{"content":"存储选择需要考虑的问题：不同的文件访问方式? 在关注存储之前，需要关注下面一些问题：\n“应用” 访问数据的方式是什么？\n一次读取 或 分块读取 一个连续的“流”传输最好的方式是什么 有序的 或 随机的 “数据的类型是什么”？\n数据库，Text，视频/音频，图像\u0026hellip; 静态 / 固定 / 动态 是否需要数据共享？\n由应用共享 / 由存储共享 读 / 写 共享方面关注的问题？\nNarrow (只需要更新部分内容，这可以共享特定部分内容，这将不是一个广泛共享) / Broad 安全和访问控制：\n应用什么级别的的安全性？ 访问性会影响存储的选择：\nLocal / Network 介质：光纤，以太网，SAS，SATA，PCIe\u0026hellip; 有了这些问题，就可以引入存储的类型，以便选择最佳的存储（Balance performance and cost ）\nDAS Direct Attached Storage (DAS) 直接附加存储是指，直接连接到服务器存储系统，通俗来讲就是直接连接磁盘，服务器与存储系统之间“没有经过网络设备” (如交换机等)，服务器与存储直接由专用的“连接技术”进行连接，如 SCSI, 但现在更常见的是 “eSATA”, “SAS”, 或 “光纤通道”。\n图：DAS结构图 Source：https://www.pcmag.com/encyclopedia/term/direct-attached-storage\n图：DAS接口类型 Source：https://ramsaihan.wordpress.com/2017/10/16/the-sas-sata-scsi-and-ata-in-storage-and-peripheral-communication/\n外部连接 直连存储也可以通过连接电缆从服务器连接到存储设备，但服务器中必须存在 SAS、以太网或 FC 控制器，只有该服务器可以使用外部磁盘空间。因此直连存储也可以作为是服务器的扩展\nSAS 作为连接介质价格低廉，但距离仅限于几米（最大 5 或 10 米，具体取决于制造商）；光纤通道的传输距离可达数公里，因此也可用作灾备系统。\n许多 DAS 系统都有一个内部 RAID 控制器，这些 RAID 将作为一个逻辑磁盘呈现给服务器。这样，用小的物理磁盘也可以生产出大的逻辑硬盘。RAID 控制器的另一个优点是与单个磁盘相比，RAID 5 和 RAID 10 中的 I/O 吞吐量有所增加。\n图：外部的DAS Source：https://www.storitback.de/service/direct-attached-storage.html\nDAS的优缺点 优点：\n高可用 更好的数据安全与容错 存储容量扩展更经济 消除网络设置的复杂性 易于管理 缺点：\n有限的分享 利用率差 仅允许有限的使用者(服务器) 单设备有限的接口 表现形式 在操作系统方面表现为一个裸磁盘(块设备)，如 /dev/sda 在硬件方向表现为一个硬盘设备，如：SSD, HDD, M.2\u0026hellip; NAS Network Attached Storage (NAS) 网络附加存储是连接到网络的专用文件服务器。NAS 使用以太网和 TCP/IP 等协议，使得 NAS 能够摆脱 SCSI 技术的限制。在表现方面 NAS 是作为一个“网络节点”，也会存在一些 NAS 产品（例如 Network Appliance Filer 和 Auspex 服务器）作为存储设备。\n图：NAS的表现方式 Source：https://dreamlog.tistory.com/565\n图：NAS 与 DAS 的对比 Source：https://www.pcmag.com/encyclopedia/term/direct-attached-storage\nNAS 的优缺点 优点：\n对端口没有限制 高度的可扩展性和灵活性 便于安装和维护 多协议 缺点：\n大规模场景下性能会下降（性能取决于协议） 面向文件（文件系统） 因依赖网络，传输速率比 DAS 慢 NAS 由于“共享文件系统”，在安全性的地方会存在弊端 备份和恢复期间会造成网络拥塞 表现形式 在操作系统层面表现为“存储中的一个目录”，如 /data 协议类型：NFS, CIFS 典型产品： NFS Samba GlusterFS CephFS 公有云：EFS(AWS), CFS(tencent), NAS(Aliyun) SAN Storage Area Network (SAN) ，是一种附加远程存储的架构，使其看起来像是本地的，多用于数据中心的存储解决方案，SAN 将存储作为了可通过网络访问的单独设备，该方案融合了 DAS 和 NAS 的灵活性，也增加了配置的复杂性，通常使用与 DAS 类型的协议(SCSI, ISCSI, Fiber Channel) 连接。\n图：SAN存储结构图 Source：https://www.pcmag.com/encyclopedia/term/direct-attached-storage\nSAN 的优缺点 优点：\n可以存储大量数据 中心化管理 高度容错 支持动态扩展 快速高效的备份和恢复 缺点：\n硬件成本高(FC 交换机, FC 网络接口, HBA)，价格昂贵 因客户端是多个共享，安全性不好 维护困难 SAN 不适合数据密集型传输，更适用与低流量 依赖高速网络 表现形式 在操作系统方面表现为一个裸磁盘(块设备)，如 /dev/sda 在硬件方向表现为一个磁盘阵列服务器 DAS vs NAS vs SAN 三种存储类型在架构图上表示如下图所示：\n图：存储类型的架构对比 Source：https://dreamlog.tistory.com/565\n对比 DAS, NAS 和 SAN：\n类型 DAS NAS SAN 组成 主机 (PC/Server)+存储设备（硬盘） 主机 (PC/Server) 文件系统服务 存储设备 磁盘阵列服务器光纤交换机 存储设备 传输介质 电缆(IDE, SATA..) 以太网 光纤 + 光纤交换机 + 电缆 (IDE, SATA) 类型 块设备 文件系统 块设备 应用规模 小规模 中 大 传输速度 介质通道速度 LAN + 介质通道速度双冲因素 通道速度 NAS vs SAN 在通常情况下 NAS与 SAN 常常会进行对比，主要区别在于 SAN 是通道附加的，而 NAS 是网络附加的。\n传输协议方面，SAN 与 NAS 技术相似但又不同。SAN 传统上采用低级网络协议来传输磁盘块 NAS 设备通常通过 TCP/IP 传输文件\n设备表现方面：NAS 是对数据文件进行操作的单个存储设备，而 SAN 是对磁盘块进行操作的多个设备的本地网络。\n网络依赖方面：SAN 通常使用 iSCSI 和光纤通道互连。NAS 通常建立以太网和 TCP/IP 连接。\n性能方面：\n由于文件系统层速度较慢，NAS 通常具有较低的吞吐量和较高的延迟，但高速网络可以弥补 NAS 的性能损失。 SAN 是适用于事务数据库和电子商务网站等高流量环境的高性能设备。FC 或 NVMe 等技术有助于有效地消除此类请求。 扩展性方面：NAS可以是 DAS构成的主机，也可以是一个网络磁盘阵列服务器，通常扩展性不是很高；SAN 。其网络架构允许管理员在纵向或横向配置中扩展性能和容量\n传输协议方面：\nNAS 通过以太网交换机的网线直接连接到以太网。NAS 可以使用多种协议连接到服务器，包括 NFS、SMB/CIFS 和 HTTP。 SAN 使用 SCSI 协议或 SAN 驱动设备进行通信。网络是通过使用 SAS /SATA 连接类型或通过将层映射到其他协议来形成的，例如 FC 协议 (FCP) 协议映射 SCSI over FC 或 iSCSI映射 SCSI over TCP/IP 。 Block Storage 块存储是一种允许对低级存储设备进行抽象的技术，主要优点是提供低延迟操作。块设备视通常为常规磁盘（底层可以是 DAS 或 SAN），在操作系统层面会将其检测为原始磁盘。然后，对其进行格式化以在其上创建文件系统（ext4、XFS、NTFS\u0026hellip;）并开始将其用作可以存储数据的常规设备。\n块设备由集群作为较小块的集合进行管理（称为块或简称为块，因此得名）。这些块中的每一个都可以存储在由多台机器组成的存储集群中并存储在唯一的地址下。\n块存储由 1 和 0 组成；没有用于跟踪和可视化数据的文件系统或元数据；操作系统必须处理所有块的读/写。此选项的优点是吞吐量性能、低延迟和高 IOPS。通常，块最适合云平台基础设施（管理程序）和数据库，因为它具有高性能的趋势。\nFIle System 与块存储不同，基于文件的存储（NAS、文件系统）隐藏了与块存储相关的大部分复杂性。NAS 只是在网络上显示为驱动器号或目录，文件系统在存储和管理文件方面表现出色，而块存储缺乏更高级别的数据结构因为它能够轻松地通过网络共享文件，并且具有扩展能力。\nObject Storage 对象存储是在云计算行业为了存储大量非结构化数据的需求而创建的。数据不使用文件路径，而对象及其元数据的访问是使用标准 HTTP API 完成的\nReference ​[1] 스토리지 구성 DAS, NAS, SAN, IP-SAN\n​[2] NAS vs SAN storage: Sự khác biệt và các ứng dụng phù hợp\n​[3] direct attached storage\n​[4] Advantages and Disadvantages of SAN\n","permalink":"https://www.161616.top/acquaintance-stroage/","summary":"存储选择需要考虑的问题：不同的文件访问方式? 在关注存储之前，需要关注下面一些问题：\n“应用” 访问数据的方式是什么？\n一次读取 或 分块读取 一个连续的“流”传输最好的方式是什么 有序的 或 随机的 “数据的类型是什么”？\n数据库，Text，视频/音频，图像\u0026hellip; 静态 / 固定 / 动态 是否需要数据共享？\n由应用共享 / 由存储共享 读 / 写 共享方面关注的问题？\nNarrow (只需要更新部分内容，这可以共享特定部分内容，这将不是一个广泛共享) / Broad 安全和访问控制：\n应用什么级别的的安全性？ 访问性会影响存储的选择：\nLocal / Network 介质：光纤，以太网，SAS，SATA，PCIe\u0026hellip; 有了这些问题，就可以引入存储的类型，以便选择最佳的存储（Balance performance and cost ）\nDAS Direct Attached Storage (DAS) 直接附加存储是指，直接连接到服务器存储系统，通俗来讲就是直接连接磁盘，服务器与存储系统之间“没有经过网络设备” (如交换机等)，服务器与存储直接由专用的“连接技术”进行连接，如 SCSI, 但现在更常见的是 “eSATA”, “SAS”, 或 “光纤通道”。\n图：DAS结构图 Source：https://www.pcmag.com/encyclopedia/term/direct-attached-storage\n图：DAS接口类型 Source：https://ramsaihan.wordpress.com/2017/10/16/the-sas-sata-scsi-and-ata-in-storage-and-peripheral-communication/\n外部连接 直连存储也可以通过连接电缆从服务器连接到存储设备，但服务器中必须存在 SAS、以太网或 FC 控制器，只有该服务器可以使用外部磁盘空间。因此直连存储也可以作为是服务器的扩展\nSAS 作为连接介质价格低廉，但距离仅限于几米（最大 5 或 10 米，具体取决于制造商）；光纤通道的传输距离可达数公里，因此也可用作灾备系统。","title":"存储概念 - 存储类型对比"},{"content":"在配置好 github 仓库后，需要将对应的信息填写在 picgo 中，可以按照如下进行配置\n仓库名：xxxx/xxx 无需写 github.com/xxx/xxx\n分支名：直接填写分支名即可\nToken：在 github 上面配置的仓库 token\n设定存储路径：这里填写 github 仓库上传到的路径\n设置自定义域名：https://cdn.jsdelivr.net/gh/\u0026lt;github_username\u0026gt;@\u0026lt;branch_name\u0026gt;/\u0026lt;repo_name\u0026gt;/\u0026lt;path\u0026gt;\n例如：https://cdn.jsdelivr.net/gh/cylonchau/blogs@img/img/image-20241129232645456.png\n","permalink":"https://www.161616.top/github-and-picgo-img-beg/","summary":"在配置好 github 仓库后，需要将对应的信息填写在 picgo 中，可以按照如下进行配置\n仓库名：xxxx/xxx 无需写 github.com/xxx/xxx\n分支名：直接填写分支名即可\nToken：在 github 上面配置的仓库 token\n设定存储路径：这里填写 github 仓库上传到的路径\n设置自定义域名：https://cdn.jsdelivr.net/gh/\u0026lt;github_username\u0026gt;@\u0026lt;branch_name\u0026gt;/\u0026lt;repo_name\u0026gt;/\u0026lt;path\u0026gt;\n例如：https://cdn.jsdelivr.net/gh/cylonchau/blogs@img/img/image-20241129232645456.png","title":"picgo + github 给typora做图床"},{"content":" 本文是Ceph集群部署系列第1章 使用cephadm纯离线安装Ceph集群 使用cephadm纯离线安装Ceph集群 2 Ceph集群安装 - ceph-deploy Ceph集群安装 - ceph-deploy下线rgw 开篇常例 - 概述 Ceph 是一个广泛使用的开源存储平台。 它提供高性能、可靠性和可扩展性。 Ceph 分布式存储系统提供了对象存储、块存储和文件级存储。 Ceph 旨在提供无单点故障的分布式存储系统。\n在本教程中，将通过 ceph-adm 方式在 CentOS 7 上安装和构建 Ceph 集群。该实验的 Ceph 集群需要以下 Ceph 组件：\nCeph OSD (ceph-osd) - 处理数据存储、数据复制和恢复；通常一个Ceph集群至少需要两台 OSD 服务器 。 Ceph Monitor (ceph-mon) - 监视集群状态、OSD 映射和 CRUSH 映射，我们在这里与 cephadm 或 OSD 公用一个节点 Ceph 元数据服务器 (ceph-mds) - 这是使用 CephFS 所需的组件。 有了上面的条件，我们实验环境所需要的节点如下：\n三台服务器节点，CentOS 7 注：CentOS 7 可安装最高级别的 ceph 版本就是 O 版\n本教程中的服务器将使用以下主机名和 IP 地址：\n主机名 IP地址 作用 cephadmin 10.0.0.20 作为 ceph 管理节点，以管理与部署 ceph 集群 osd01 10.0.0.21 osd02 10.0.0.22 any any 作为 Ceph Client 的角色 注：所有 OSD 节点都需要两个分区，一个根（/）分区和一个空分区，稍后用作 Ceph 数据存储。\nREQUIREMENTS 使用 cephadm 安装 ceph 集群，所需要的先决条件如下:\n必要条件：\nPython 3，因为 cephadm 是一个 python3 脚本，所以需要每个节点都需要安装 python3 Systemd Podman or Docker：cephadm 安装的集群是一种以 “容器方式” 运行在对应的 ceph node 之上 LVM2：ceph OSD 是通过 LVM 来使用的，所以需要在每个 OSD 节点之上安装 LVM2 非必要条件：\nchrony or NTP：ceph 强依赖每个节点之上的时间 Internet 域名解析：ceph 集群对于 ceph node 来说是通过 hostname.random_str 识别的的 集群组件端口占用\n服务 端口 Monitor 3300, 6789 Metadata Server/Ceph manager 6800~7300 OSD daemon 6800~7300 Grafana 3000 Node-exporter 9100 Object gateway 7480 Ceph Dashboard 8443 Step 1 配置节点 此步骤，将配置所有 3 个节点，为安装 Ceph 集群做好准备。 建议在所有节点上按照并运行以下所有命令。 并确保所有节点上都安装了 ssh-server。\n创建ceph用户(可选) bash 1 2 useradd -d /home/cephuser -m cephuser echo 1|passwd cephuser --stdin 创建新用户后，我们需要为“cephuser” 配置 sudo。 他必须能够以 root 身份运行命令并无需密码即可获得 root 权限。\n运行以下命令为用户创建 sudoers 文件并使用 sed 编辑 /etc/sudoers 文件。\nbash 1 2 3 echo \u0026#34;cephuser ALL = (root) NOPASSWD:ALL\u0026#34; | sudo tee /etc/sudoers.d/cephuser chmod 0440 /etc/sudoers.d/cephuser sed -i s\u0026#39;/Defaults requiretty/#Defaults requiretty\u0026#39;/g /etc/sudoers Note：上述 通过 ceph-deploy 需要配置，cephadm 中没有强制\n安装配置 NTP 服务(可选) 因为分布式存储需要依赖时间，所以需要对所有 OSD 节点的时间保持一致，这里时间同步的软件可以随意选择，\nbash 1 2 3 4 5 yum install -y ntp ntpdate ntp-doc ntpdate 0.us.pool.ntp.org hwclock --systohc systemctl enable ntpd.service systemctl start ntpd.service 可以不准备，随意启动一个服务即可，否则安装会出现如下提示\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ cephadm bootstrap --mon-ip cephadmin Verifying podman|docker is present... Verifying lvm2 is present... Verifying time synchronization is in place... No time sync service is running; checked for [\u0026#39;chrony.service\u0026#39;, \u0026#39;chronyd.service\u0026#39;, \u0026#39;systemd-timesyncd.service\u0026#39;, \u0026#39;ntpd.service\u0026#39;, \u0026#39;ntp.service\u0026#39;, \u0026#39;ntpsec.service\u0026#39;, \u0026#39;openntpd.service\u0026#39;] Installing packages [\u0026#39;chrony\u0026#39;]... Enabling unit chronyd.service No time sync service is running; checked for [\u0026#39;chrony.service\u0026#39;, \u0026#39;chronyd.service\u0026#39;, \u0026#39;systemd-timesyncd.service\u0026#39;, \u0026#39;ntpd.service\u0026#39;, \u0026#39;ntp.service\u0026#39;, \u0026#39;ntpsec.service\u0026#39;, \u0026#39;openntpd.service\u0026#39;] Repeating the final host check... docker (/usr/bin/docker) is present systemctl is present lvcreate is present Unit chronyd.service is enabled and running Host looks OK Cluster fsid: 19c90bda-2fb8-11ee-9128-000c293e5d57 Address: cephadmin is not a valid IP address Verifying IP cephadmin port 3300 ... Address: cephadmin is not a valid IP address Verifying IP cephadmin port 6789 ... Address: cephadmin is not a valid IP address Cannot infer CIDR network for mon IP `cephadmin` : \u0026#39;cephadmin\u0026#39; does not appear to be an IPv4 or IPv6 address Cannot infer CIDR network for mon IP `cephadmin` : \u0026#39;cephadmin\u0026#39; does not appear to be an IPv4 or IPv6 address ERROR: Cannot infer CIDR network. Pass --skip-mon-network to configure it later 关闭 SELInux 在所有 Ceph Node 节点上关闭 SELInux，可以根据下面命令使用 sed 操作\nbash 1 sed -i \u0026#39;s/SELINUX=enforcing/SELINUX=disabled/g\u0026#39; /etc/selinux/config 配置 Hosts 文件 这里主机名可以根据自己选择进行，如果你有 DNS 服务，那么也可以通过注册在 DNS 内的服务进行\nbash 1 2 3 4 5 $ tee \u0026gt;\u0026gt; /etc/hosts \u0026lt;\u0026lt; EOF 10.0.0.20 ceph-octopus-cephadm 10.0.0.21 ceph-octopus-01 10.0.0.22 ceph-octopus-02 EOF 安装依赖 bash 1 2 3 4 # centos 7 yum install -y python3 lvm2 docker-ce # centos 8 dnf install -y python3 lvm2 podman Step2 下载 cephadm 并 修改 cephadm 镜像地址 获取 cephadm 脚本 步骤参考了 ceph 官方安装手册 [1] ，需要注意的是 cephadm 脚本也是需要按照版本来的\nbash 1 2 curl --silent --remote-name --location https://github.com/ceph/ceph/raw/octopus/src/cephadm/cephadm chmod +x cephadm 这个步骤主要是为了使 cephadm 可以正常的拉去 ceph 镜像，你可以通过 docker load 方式导入到 ceph node 之上，但是 ceph 镜像必须通过私有镜像进行拉取（存在 reposig 认证）其他 ceph 组件（prometheus, node-exporter..）可以通过 docker load 导入\ncephadm 最上面几行写明了要拉去镜像的镜像仓库地址，可以在有互联网机器上下载好，push 到私有镜像仓库中，如果没有私有镜像仓库，可以 run 一个 docker registry ，这个步骤是强制的；其他组件是可以通过 docker load 方式获得\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ head -20 cephadm #!/usr/bin/python3 # Default container images ----------------------------------------------------- DEFAULT_IMAGE = \u0026#39;quay.io/ceph/ceph:v15\u0026#39; DEFAULT_IMAGE_IS_MASTER = False DEFAULT_PROMETHEUS_IMAGE = \u0026#39;quay.io/prometheus/prometheus:v2.18.1\u0026#39; DEFAULT_NODE_EXPORTER_IMAGE = \u0026#39;quay.io/prometheus/node-exporter:v0.18.1\u0026#39; DEFAULT_ALERT_MANAGER_IMAGE = \u0026#39;quay.io/prometheus/alertmanager:v0.20.0\u0026#39; DEFAULT_GRAFANA_IMAGE = \u0026#39;quay.io/ceph/ceph-grafana:6.7.4\u0026#39; # ------------------------------------------------------------------------------ LATEST_STABLE_RELEASE = \u0026#39;octopus\u0026#39; DATA_DIR = \u0026#39;/var/lib/ceph\u0026#39; LOG_DIR = \u0026#39;/var/log/ceph\u0026#39; LOCK_DIR = \u0026#39;/run/cephadm\u0026#39; LOGROTATE_DIR = \u0026#39;/etc/logrotate.d\u0026#39; UNIT_DIR = \u0026#39;/etc/systemd/system\u0026#39; LOG_DIR_MODE = 0o770 DATA_DIR_MODE = 0o700 CONTAINER_INIT=False Run docker registry 拉去 docker registry\nbash 1 docker pull registry 镜像保存路径放置在当前工作目录中\nNote: 如果你没有独立的私有镜像仓库，那么请保留 docker registry，直到你不对 ceph 集群进行扩展\n执行下面命令，运行 docker registry\nbash 1 2 3 4 5 6 7 8 docker run \\ --detach \\ --name registry \\ --hostname registry \\ --volume $(pwd)/registry:/var/lib/registry/docker/registry \\ --publish 5000:5000 \\ --restart unless-stopped \\ registry:latest 在所有 ceph node 之上执行下面命令，需要自行替换 registry_host 部分\n现象：https://xxx:5000/v2/: http: server gave HTTP response to HTTPS client\nbash 1 2 3 4 5 tee /etc/docker/daemon.json \u0026lt;\u0026lt; EOF { \u0026#34;insecure-registries\u0026#34;: [\u0026#34;registry_host:5000\u0026#34;] } EOF Step 3 引导一个新集群 在上面步骤都完成后，可以直接去引导一个新集群了\n可以选择性执行下面步骤\n这里是安装 ceph 客户端时需要用到的，例如 ceph-common, ceph-fuse 都会用到这些\nbash 1 $ ./cephadm add-repo --release octopus cephadm 命令能够：\n引导一个新集群 使用 ceph cli 启动容器化的 shell 用于调试容器化的 ceph daemon O 版的安装命令是通过 github 下载，要注意的是，每个版本号的 cephadm 命令不通用\nbash 1 2 3 4 5 # curl --silent --remote-name --location https://github.com/ceph/ceph/raw/pacific/src/cephadm/cephadm # chmod +x cephadm # ./cephadm add-repo --release octopus # install命令旨在将 cephadm 安装到环境变量中 # ./cephadm install 开始引导一个新的 ceph 集群 创建 Ceph 集群的第一步是在 Ceph 集群的管理几点上执行命令 cephadm bootstrap，这个命令的行为会创建 Ceph 集群中的第一个 \u0026ldquo;monitor\u0026rdquo; 守护进程，这需要提供一个 “IP地址” 而不可以是 “域名”。\n这里将 ceph monitor 部署在管理节点上了，以节省 Node 数量\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 $ cephadm bootstrap --mon-ip 10.0.0.20 Verifying podman|docker is present... Verifying lvm2 is present... Verifying time synchronization is in place... Unit chronyd.service is enabled and running Repeating the final host check... docker (/usr/bin/docker) is present systemctl is present lvcreate is present Unit chronyd.service is enabled and running Host looks OK Cluster fsid: 420ccab4-2fb8-11ee-9f5c-000c293e5d57 Verifying IP 10.0.0.20 port 3300 ... Verifying IP 10.0.0.20 port 6789 ... Mon IP `10.0.0.20` is in CIDR network `10.0.0.0/24` Mon IP `10.0.0.20` is in CIDR network `10.0.0.0/24` Internal network (--cluster-network) has not been provided, OSD replication will default to the public_network Pulling container image quay.io/ceph/ceph:v17... Non-zero exit code 1 from /usr/bin/docker pull quay.io/ceph/ceph:v17 /usr/bin/docker: stderr Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? ERROR: Failed command: /usr/bin/docker pull quay.io/ceph/ceph:v17 [root@cephadmin ~]# systemctl start docker [root@cephadmin ~]# cephadm bootstrap --mon-ip 10.0.0.20 Verifying podman|docker is present... Verifying lvm2 is present... Verifying time synchronization is in place... Unit chronyd.service is enabled and running Repeating the final host check... docker (/usr/bin/docker) is present systemctl is present lvcreate is present Unit chronyd.service is enabled and running Host looks OK Cluster fsid: 4d128cbe-2fb8-11ee-8326-000c293e5d57 Verifying IP 10.0.0.20 port 3300 ... Verifying IP 10.0.0.20 port 6789 ... Mon IP `10.0.0.20` is in CIDR network `10.0.0.0/24` Mon IP `10.0.0.20` is in CIDR network `10.0.0.0/24` Internal network (--cluster-network) has not been provided, OSD replication will default to the public_network Pulling container image quay.io/ceph/ceph:v17... Ceph version: ceph version 17.2.6 (d7ff0d10654d2280e08f1ab989c7cdf3064446a5) quincy (stable) Extracting ceph user uid/gid from container image... Creating initial keys... Creating initial monmap... Creating mon... Waiting for mon to start... Waiting for mon... mon is available Assimilating anything we can from ceph.conf... Generating new minimal ceph.conf... Restarting the monitor... Setting mon public_network to 10.0.0.0/24 Wrote config to /etc/ceph/ceph.conf Wrote keyring to /etc/ceph/ceph.client.admin.keyring Creating mgr... Verifying port 9283 ... Waiting for mgr to start... Waiting for mgr... mgr not available, waiting (1/15)... mgr not available, waiting (2/15)... mgr not available, waiting (3/15)... mgr not available, waiting (4/15)... mgr is available Enabling cephadm module... Waiting for the mgr to restart... Waiting for mgr epoch 5... mgr epoch 5 is available Setting orchestrator backend to cephadm... Generating ssh key... Wrote public SSH key to /etc/ceph/ceph.pub Adding key to root@localhost authorized_keys... Adding host cephadmin... Deploying mon service with default placement... Deploying mgr service with default placement... Deploying crash service with default placement... Deploying prometheus service with default placement... Deploying grafana service with default placement... Deploying node-exporter service with default placement... Deploying alertmanager service with default placement... Enabling the dashboard module... Waiting for the mgr to restart... Waiting for mgr epoch 9... mgr epoch 9 is available Generating a dashboard self-signed certificate... Creating initial admin user... Fetching dashboard port number... Ceph Dashboard is now available at: URL: https://cephadmin:8443/ User: admin Password: eqlf3jh1i1 Enabling client.admin keyring and conf on hosts with \u0026#34;admin\u0026#34; label Saving cluster configuration to /var/lib/ceph/4d128cbe-2fb8-11ee-8326-000c293e5d57/config directory Enabling autotune for osd_memory_target You can access the Ceph CLI as following in case of multi-cluster or non-default config: sudo /usr/local/bin/cephadm shell --fsid 4d128cbe-2fb8-11ee-8326-000c293e5d57 -c /etc/ceph/ceph.conf -k /etc/ceph/ceph.client.admin.keyring Or, if you are only running a single cluster on this host: sudo /usr/local/bin/cephadm shell Please consider enabling telemetry to help improve Ceph: ceph telemetry on For more information see: https://docs.ceph.com/docs/master/mgr/telemetry/ Bootstrap complete. 成功后会看到 ceph dashboard 的界面，默认密码会输出到控制台，第一次登陆会要求修改默认密码\n在安装将生成一个最小的 ceph.conf 仅适用于引导阶段的配置文件，通过进入 mon 容器查看\nbash 1 $ docker exec -it ceph-350494de-d23f-11ea-be85-525400d32681-mon.mon0 cat /etc/ceph/ceph.conf # 向 ceph 集群导入 osd node 向每个 node 导入 ssh key，下面的操作是通过进入管理容器执行的\nbash 1 ssh-copy-id -f -i /etc/ceph/ceph.pub root@*\u0026lt;new-host\u0026gt;* 添加一个主机到 ceph 集群\nbash 1 ceph orch host add *newhost* 部署一个新的 mon，你可以给新加入的主机打上标签\nbash 1 2 # ceph orch host label add *\u0026lt;hostname\u0026gt;* mon ceph orch apply mon *\u0026lt;number-of-monitors\u0026gt;* 向集群部署新的组件\nbash 1 2 ceph orch apply mon *\u0026lt;number-of-monitors\u0026gt;* ceph orch apply mon *\u0026lt;host1,host2,host3,...\u0026gt;* 部署 osd damon 在新的主机之上\nbash 1 2 ceph orch daemon add osd *\u0026lt;host\u0026gt;*:*\u0026lt;device-path\u0026gt;* ceph orch daemon add osd host1:/dev/sdb 这是可以列出正在管理的服务器 cephadm 使用 host ls 命令：\nbash 1 ceph orch host ls 到此，如果你只使用 RDB 块存储，这里已经部署完成了，如果需要选择使用 文件存储 CephFS，或者对象存储 RGW，可以在另外部署相应的组件，部署的组件是根据按需使用进行部署\nosd device 命令也可以列出对应的设备\nbash 1 ceph orch device ls 在 Ceph 中一切存储的基础都是基于 RADOS 集群\nTroubleshooting TypeError: init() missing 2 required positional arguments: \u0026lsquo;hostname\u0026rsquo; and \u0026lsquo;addr\u0026rsquo; 现象：实际上输入了 hostname 和 addr 也是出现这个问题\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ ceph orch host add ceph-octopus-01 Error EINVAL: Traceback (most recent call last): File \u0026#34;/usr/share/ceph/mgr/mgr_module.py\u0026#34;, line 1756, in _handle_command return self.handle_command(inbuf, cmd) File \u0026#34;/usr/share/ceph/mgr/orchestrator/_interface.py\u0026#34;, line 171, in handle_command return dispatch[cmd[\u0026#39;prefix\u0026#39;]].call(self, cmd, inbuf) File \u0026#34;/usr/share/ceph/mgr/mgr_module.py\u0026#34;, line 462, in call return self.func(mgr, **kwargs) File \u0026#34;/usr/share/ceph/mgr/orchestrator/_interface.py\u0026#34;, line 107, in \u0026lt;lambda\u0026gt; wrapper_copy = lambda *l_args, **l_kwargs: wrapper(*l_args, **l_kwargs) # noqa: E731 File \u0026#34;/usr/share/ceph/mgr/orchestrator/_interface.py\u0026#34;, line 96, in wrapper return func(*args, **kwargs) File \u0026#34;/usr/share/ceph/mgr/orchestrator/module.py\u0026#34;, line 356, in _add_host return self._apply_misc([s], False, Format.plain) File \u0026#34;/usr/share/ceph/mgr/orchestrator/module.py\u0026#34;, line 1092, in _apply_misc raise_if_exception(completion) File \u0026#34;/usr/share/ceph/mgr/orchestrator/_interface.py\u0026#34;, line 225, in raise_if_exception e = pickle.loads(c.serialized_exception) TypeError: __init__() missing 2 required positional arguments: \u0026#39;hostname\u0026#39; and \u0026#39;addr\u0026#39; 首先先将公钥分发到对应的 CEPH NODE 之上\nbash 1 2 3 4 5 6 7 8 9 10 11 12 export CEPH_HOSTNAME=root@ceph-octopus-01 # 获取公钥 ceph cephadm get-pub-key \u0026gt; /etc/ceph/ceph.pub # 分发公钥到对应 ceph node ssh-copy-id -f -i /etc/ceph/ceph.pub ${CEPH_HOSTNAME} # 尝试使用私钥是否可以连接到 ceph node ceph cephadm get-ssh-config \u0026gt; ssh_config ceph config-key get mgr/cephadm/ssh_identity_key \u0026gt; ~/cephadm_private_key chmod 0600 ~/cephadm_private_key ssh -F ssh_config -i ~/cephadm_private_key ${CEPH_HOSTNAME} 我解决的方式：实际上版本不对，更新版本就恢复了\nReference [1] install-cephadm\n[2] Object Request Broker Architecture\n[3] Cooperation for Open Systems Interconnection Networking in Europe\n","permalink":"https://www.161616.top/ch02-1-install-ceph-with-cephadm-part1/","summary":"本文是Ceph集群部署系列第1章 使用cephadm纯离线安装Ceph集群 使用cephadm纯离线安装Ceph集群 2 Ceph集群安装 - ceph-deploy Ceph集群安装 - ceph-deploy下线rgw 开篇常例 - 概述 Ceph 是一个广泛使用的开源存储平台。 它提供高性能、可靠性和可扩展性。 Ceph 分布式存储系统提供了对象存储、块存储和文件级存储。 Ceph 旨在提供无单点故障的分布式存储系统。\n在本教程中，将通过 ceph-adm 方式在 CentOS 7 上安装和构建 Ceph 集群。该实验的 Ceph 集群需要以下 Ceph 组件：\nCeph OSD (ceph-osd) - 处理数据存储、数据复制和恢复；通常一个Ceph集群至少需要两台 OSD 服务器 。 Ceph Monitor (ceph-mon) - 监视集群状态、OSD 映射和 CRUSH 映射，我们在这里与 cephadm 或 OSD 公用一个节点 Ceph 元数据服务器 (ceph-mds) - 这是使用 CephFS 所需的组件。 有了上面的条件，我们实验环境所需要的节点如下：\n三台服务器节点，CentOS 7 注：CentOS 7 可安装最高级别的 ceph 版本就是 O 版","title":"使用cephadm纯离线安装Ceph集群"},{"content":" 本文是Ceph集群部署系列第2章 使用cephadm纯离线安装Ceph集群 使用cephadm纯离线安装Ceph集群 2 Ceph集群安装 - ceph-deploy Ceph集群安装 - ceph-deploy下线rgw 离线安装 - ceph-mds ceph-mds (metadata server daemon) 是 cephfs 功能中所需要的组件，是用于收集和管理文件系统名称空间，协调和共享 OSD 集群的组件。\ncephadm 部署集群所有组件都打包在 ceph 镜像内，只需要修改一遍就可以全局离线安装了 要部署 cephfs 就需要有一个或多个 ceph-mds\n使用 cephadm 部署 mds bash 1 2 ceph orch apply mds *\u0026lt;fs-name\u0026gt;* --placement=\u0026#34;*\u0026lt;num-daemons\u0026gt;* [*\u0026lt;host1\u0026gt;* ...]\u0026#34; ceph orch apply mds kubernetes01 --placement=\u0026#34;hostname01,hostname02,hostname03\u0026#34; 删除 mds bash 1 2 ceph config set mon mon_allow_pool_delete true ceph fs volume rm kubernetes01 --yes-i-really-mean-it 离线安装 - Ceph Object Gateway Ceph 从 0.8 版本起，RGW 就是使用在 CivetWeb （C++ web server ）取代了 Apache FastCGI；Ceph Object Gateway 不支持 SSL，必须使用代理服务器，分配HTTPS请求到CivetWeb\n使用 cephadm 部署 radosgw，这时的 radosgw 是作为一组守护进程在集群上运行，而这是用于管理 “单集群” 部署或 多集群部署 中的特定 realm 和 zone\n在使用 cephadm 需要注意的是，radowgw 的配置 是通过 monitor 配置数据库，而不是使用 ceph.conf 或命令行\n部署 radosgw 可以通过 orch 命令来执行\nbash 1 2 ceph orch apply rgw \u0026lt;realm_name\u0026gt; \u0026lt;zone_name\u0026gt; [\u0026lt;subcluster\u0026gt;] [\u0026lt;port:int\u0026gt;] [--ssl] [\u0026lt;placement\u0026gt;] [-- Update the number of RGW instances for the given zone dry-run] [plain|json|json-pretty|yaml] [--unmanaged] 先给对应的 ceph node 打标签\nbash 1 ceph orch host label add {HOSTNAME} rgwceph 应用\nbash 1 2 # 可以尝试运行 ceph orch apply rgw default ph73svr097119 --placement=\u0026#34;1 ph73svr097120\u0026#34; --dry-run 部署时候需如果需要要指定对应 Realm 和 zone，如果不存在，cephadm会自动创建，并部署 rgw daemon；也可以手动指定创建\nbash 1 2 3 4 5 6 7 radosgw-admin realm create --rgw-realm=\u0026lt;realm-name\u0026gt; --default radosgw-admin zonegroup create --rgw-zonegroup=\u0026lt;zonegroup-name\u0026gt; --master --default radosgw-admin zone create --rgw-zonegroup=\u0026lt;zonegroup-name\u0026gt; --rgw-zone=\u0026lt;zone-name\u0026gt; --master --default radosgw-admin period update --rgw-realm=\u0026lt;realm-name\u0026gt; --commit 也可以通过 placement 规格文件进行部署\nyaml 1 2 3 4 5 6 service_type: prometheus placement: hosts: - host1 - host2 - host3 这个规格文件转换为命令后如下面所示\nbash 1 orch apply prometheus --placement=\u0026#34;host1 host2 host3\u0026#34; 离线安装 - ceph-common 如果有网络情况下只需要执行下面两个命令就可以安装\nbash 1 2 cephadm add-repo --release octopus cephadm install ceph-common 离线环境对安装 ceph-common 难度很大，大致步骤如下：\n梳理 依赖包 下载 ceph 到本地 搭建本地yum源 梳理依赖包 需要一个有网络的机器，去下载这些依赖包，如果装过 ceph-common，那么就需要手动卸载后重新下载，如果没有可以使用下面命令操作，这样会将安装包都下载到本地\nbash 1 yum install leveldb --downloadonly --downloaddir=./ 通常ceph-common 依赖得安装包如下所示\nbash 1 2 3 4 5 6 lttng-ust-2.4.1-4.el7.x86_64.rpm userspace-rcu-0.7.16-1.el7.x86_64.rpm liboath-2.6.2-1.el7.x86_64.rpm leveldb-1.12.0-11.el7.x86_64.rpm libbabeltrace-1.2.4-3.el7.x86_64.rpm python36-prettytable-0.7.2-19.el7.noarch.rpm 下载 ceph 安装包到本地 reposync 可以吧公共源得所有包都下载到本地，然后将这些作为本地yum源安装就可\nbash 1 reposync -n repoid=Ceph-noarch repoid=Ceph-source -p ~/ceph/yumrepo/ 搭建本地yum源 text 1 2 3 4 5 6 7 tee /etc/yum.repo.d/local.repo \u0026lt;\u0026lt;EOF [local] name = Ceph Local baseurl=file:///root/yumlocalrepo/ gpgcheck=0 enabled=1 EOF 然后将 下载得依赖包塞入 对应目录 /root/yumlocalrepo\n每次yum源目录更新后都需要执行下面两个命令\nbash 1 2 createrepo /root/yumlocalrepo yum makecache 完成上面步骤后就可以使用 yum 去安装 ceph-common\n","permalink":"https://www.161616.top/ch02-2-install-ceph-with-cephadm-part2/","summary":"本文是Ceph集群部署系列第2章 使用cephadm纯离线安装Ceph集群 使用cephadm纯离线安装Ceph集群 2 Ceph集群安装 - ceph-deploy Ceph集群安装 - ceph-deploy下线rgw 离线安装 - ceph-mds ceph-mds (metadata server daemon) 是 cephfs 功能中所需要的组件，是用于收集和管理文件系统名称空间，协调和共享 OSD 集群的组件。\ncephadm 部署集群所有组件都打包在 ceph 镜像内，只需要修改一遍就可以全局离线安装了 要部署 cephfs 就需要有一个或多个 ceph-mds\n使用 cephadm 部署 mds bash 1 2 ceph orch apply mds *\u0026lt;fs-name\u0026gt;* --placement=\u0026#34;*\u0026lt;num-daemons\u0026gt;* [*\u0026lt;host1\u0026gt;* ...]\u0026#34; ceph orch apply mds kubernetes01 --placement=\u0026#34;hostname01,hostname02,hostname03\u0026#34; 删除 mds bash 1 2 ceph config set mon mon_allow_pool_delete true ceph fs volume rm kubernetes01 --yes-i-really-mean-it 离线安装 - Ceph Object Gateway Ceph 从 0.","title":"使用cephadm纯离线安装Ceph集群2"},{"content":"背景 在云原生环境中，特别是基于 Kubernetes，集群中的 “服务” 在与外部交互时，例如，一个外部的第三方 Web 服务/API 等，而监控这些不同的 endpoint 诊断服务可用性的一个关键点，这里将阐述基于 Kube-prometheus-stacks 如果做到可以监控外部 IP/URL，例如，HTTP/TCP/ICMP 等。\nblackbox_exporter 是 Prometheus 官方维护的 exporter之一，是提供一种用于检测 HTTP/S、DNS、TCP 和 ICMP 端点的可用性。\n基于 kube-prometheus-stack 安装 blackbox 本文使用了 helm 安装的 prometheus-community/prometheus-blackbox-exporter ，在安装前，需要自行修改要启动的 prober，与是否开启默认的 servicemonitor\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 secretConfig: false config: modules: ping: prober: icmp timeout: 5s icmp: preferred_ip_protocol: \u0026#34;ip4\u0026#34; http_2xx: prober: http timeout: 5s http: valid_http_versions: [\u0026#34;HTTP/1.1\u0026#34;, \u0026#34;HTTP/2.0\u0026#34;] follow_redirects: true preferred_ip_protocol: \u0026#34;ip4\u0026#34; 安装\nbash 1 helm install prometheus-blackbox-exporter -n monitoring . -f values.yaml 配置 servicemonitor blackbox-exporter 实现了多种探针，因此可以传递多个 endpoint 进行探测，下列是 ServiceMonitor 实现的检测外部IP/URL，使用了 icmp 探针，也就是说是在 blackbox-exporter 中配置的模块，探针通过抓取 Kubernetes service 下挂的 endpoint，通过访问 blackbox service 的 /probe 抓取暴露的指标 metrics\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: blackbox-exporter-probe spec: endpoints: - interval: 1m path: /probe scrapeTimeout: 10s params: module: [tcp_prober] relabelings: - sourceLabels: [__address__] targetLabel: __param_target - targetLabel: __address__ replacement: black-prometheus-blackbox-exporter:9115 # is the name:port of the blackbox exporter service - sourceLabels: [__param_target] targetLabel: instance - action: labelmap regex: __meta_kubernetes_service_label_(.+) # specify to monitor kubernets services jobLabel: blackbox-exporter selector: matchLabels: app.kubernetes.io/action: probe # monitor the services only with this label 这个 ServiceMonitor 会通过标签匹配对应的 Kubernets service，标签为 app.kubernetes.io/action: probe所有 namespace 中的存在 这个 Label 的 service。 如果有需要，可以通过指定抓取的 namespace，使用namespaceSelector\n创建一个外部 service 这里使用了 Kubernetes 外部 service 方式将外部IP引入到内部\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 apiVersion: v1 kind: Service metadata: labels: app.kubernetes.io/action: probe name: icmp-prober spec: clusterIP: None ports: - name: db protocol: TCP port: 9100 targetPort: 9100 --- apiVersion: v1 kind: Endpoints metadata: labels: app.kubernetes.io/action: probe name: rahasak # name is same as service name subsets: - addresses: - ip: 10.0.0.4 - ip: 10.0.0.5 ports: - name: db protocol: TCP port: 9100 创建 servicemonitor 创建一个 ServiceMonitor 来探测每个 endpoint，使用了 icmp 探针来抓取 带有标签 app.kubernetes.io/action: probe 的Kubernets service\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: blackbox-exporter spec: endpoints: - interval: 1m path: /probe scrapeTimeout: 10s params: module: [ping] relabelings: - sourceLabels: [__address__] targetLabel: __param_target - targetLabel: __address__ # blackbox_service_name.namespace:port replacement: prometheus-blackbox-prometheus-blackbox-exporter:9115 - sourceLabels: [__param_target] targetLabel: instance - action: labelmap regex: __meta_kubernetes_endpoints_label_(.+) # specify to monitor kubernetes endpoints jobLabel: blackbox-exporter selector: matchLabels: app.kubernetes.io/action: probe # monitor endpoints only with the given label Notes blackbox_exporter 如果想要使用 icmp 探针，必须拥有 root 权限，直接修改 runAsGroup: 0 即可\n对于 直接使用 servicemonitor 中的 endpoint 只能识别出一个 IP，原因是，balckbox 暴漏的指标需要带参数访问，下列格式\nbash 1 \u0026#34;http://10.104.202.8:9115/probe?module=ping\u0026amp;target=10.0.0.5\u0026#34; 而传入多个 target 没有用，只会返回第一个 target 的指标，所以说会出现多个 endpoint 只会出现一个，同样的问题，如果 service 设置为 ClusterIP，也会只有一个指标，必须 type: None 才可以，这就是只有多个 endpoint 才会在拉去指标时请求多个 balckbox exporter 的 API\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: blackbox-probe-tcp namespace: default labels: release: kube-prometheus-stacks spec: endpoints: - port: http path: /probe interval: 5s scrapeTimeout: 5s params: module: - ping target: - 10.0.0.4 - 10.0.0.5 relabelings: - action: replace regex: (.*) replacement: $1 sourceLabels: - __meta_kubernetes_service_label_cluster targetLabel: cluster - action: replace regex: (.*) replacement: $1 sourceLabels: - __param_module targetLabel: module - action: labelmap regex: (.*) replacement: $1 sourceLabels: [__param_target] targetLabel: __address__ selector: matchLabels: app.kubernetes.io/instance: prometheus-blackbox 目前暂未找到有效的解决方法，但是在清单配置多个 endpoint 就出现多条 serivcemonitor 记录，持续更进该问题，可能可以通过 additionalScrapeConfigs 可以解决该问题\nReference ​[1] Monitor Kubernets Services/Endpoints with Prometheus Blackbox Exporter ​ [2] Prometheus Operator + Blackbox exporter\n","permalink":"https://www.161616.top/blackbox_exporter-in-k8s/","summary":"背景 在云原生环境中，特别是基于 Kubernetes，集群中的 “服务” 在与外部交互时，例如，一个外部的第三方 Web 服务/API 等，而监控这些不同的 endpoint 诊断服务可用性的一个关键点，这里将阐述基于 Kube-prometheus-stacks 如果做到可以监控外部 IP/URL，例如，HTTP/TCP/ICMP 等。\nblackbox_exporter 是 Prometheus 官方维护的 exporter之一，是提供一种用于检测 HTTP/S、DNS、TCP 和 ICMP 端点的可用性。\n基于 kube-prometheus-stack 安装 blackbox 本文使用了 helm 安装的 prometheus-community/prometheus-blackbox-exporter ，在安装前，需要自行修改要启动的 prober，与是否开启默认的 servicemonitor\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 secretConfig: false config: modules: ping: prober: icmp timeout: 5s icmp: preferred_ip_protocol: \u0026#34;ip4\u0026#34; http_2xx: prober: http timeout: 5s http: valid_http_versions: [\u0026#34;HTTP/1.1\u0026#34;, \u0026#34;HTTP/2.","title":"在 Kubernetes 集群中使用 blackbox exporter监控外部IP"},{"content":"Prerequisites 具有一个 Kubernetes 集群 以部署 Spinnaker 可运行 Docker 的环境 (1 vCPU, 3.75 GB) 或者是 Ubuntu，用以安装 Halyard (用于 spinnaker 的服务) 对象存储 (MinIO)，用于持久化 Spinnaker 的数据 对象存储的 Bucket 的访问账号 安装执行步骤 安装 Halyard 可以直接使用 Docker 方式安装，这个没什么必要性，就是管理工具而已，参考附录1 [1]\n首先创建映射目录\nbash 1 2 mkdir ~/.hal -pv mkdir ~/.kubeconfig -pv 然后执行 docker run 运行容器\nbash 1 2 3 4 5 6 7 docker run -d -p 8084:8084 -p 9000:9000 \\ --name halyard --rm \\ -v ~/.hal:/home/spinnaker/.hal \\ -v ~/.kubeconfig:/home/spinnaker/.kube \\ -v /usr/local/bin:/usr/local/sbin \\ -v /etc/kubernetes/auth/admin.conf:/home/spinnaker/.kube/config \\ us-docker.pkg.dev/spinnaker-community/docker/halyard:stable 因为 docker 环境不能重启服务，需要修改配置文件，这里可以在外部创建一个配置文件来映射进去，这里后面会说到 GCS 的问题\nbash 1 $ docker run -it --rm us-docker.pkg.dev/spinnaker-community/docker/halyard:stable cat /opt/halyard/config/halyard.yml \u0026gt; /tmp/halyard.yml 修改 spinnaker 部分的配置，将 enabled: true ，改为 enabled: false\nyaml 1 2 3 4 5 6 7 8 9 10 spinnaker: artifacts: debian: https://us-apt.pkg.dev/projects/spinnaker-community docker: us-docker.pkg.dev/spinnaker-community/docker config: input: gcs: enabled: false writerEnabled: false bucket: halconfig 然后将输出的配置文件保存到宿主机，而后映射到容器内即可\nbash 1 2 3 4 5 6 7 8 docker run -d -p 8084:8084 -p 9000:9000 \\ --name halyard --rm \\ -v ~/.hal:/home/spinnaker/.hal \\ -v ~/.kubeconfig:/home/spinnaker/.kube \\ -v /usr/local/bin:/usr/local/sbin \\ -v /etc/kubernetes/auth/admin.conf:/home/spinnaker/.kube/config \\ -v /tmp/halyard.yml:/opt/halyard/config/halyard.yml \\ us-docker.pkg.dev/spinnaker-community/docker/halyard:stable 安装kubectl 这部分在上一章中注明了挂载 kubectl 的路径\n使用 Docker 运行的 Halyard 可以直接挂在 kubectl 到 容器内就可以了，halyard 默认的路径在 /usr/local/bin 只要避免和这个路径冲突就可以了。\nhalyard 中附带的 kubectl 不一定与你的集群版本一致\n最后进入容器就可以管理 spinnaker 了\nbash 1 docker exec -it halyard bash 生成一个 Halyard config [3] 离线安装时，需要生成一个 Halyard config 文件，默认在 ~/.hal/config\nbash 1 hal config version edit --version local:1.19.2 Notes:\n如果是选择离线安装或者 Local 方式安装，那么 local 关键字必须加 如果主机没有网，此时需要指定参数 --no-validate 来控制关闭验证，验证通常要联网 对于执行大部分的 hal 命令都会在 ~/.hal/config 生成配置文件 选择云供应商 这里可以指选择一个 Kubernetes 集群将其添加到 Halyard config 中\n注意，这里需要时 Kubectl 可以正常请求集群，即需要 kubectl 与 kubeconfig\n但在选择 Kubernetes 作为 Halyard 的 provider 之前，可以在 Kubernetes 中创建一个新的 service 以在 Halyard 中使用。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # Run in Halyard container CONTEXT=$(kubectl config current-context) kubectl apply --context $CONTEXT \\ -f https://spinnaker.io/downloads/kubernetes/service-account.yml TOKEN=$(kubectl get secret --context $CONTEXT \\ $(kubectl get serviceaccount spinnaker-service-account \\ --context $CONTEXT \\ -n spinnaker \\ -o jsonpath=\u0026#39;{.secrets[0].name}\u0026#39;) \\ -n spinnaker \\ -o jsonpath=\u0026#39;{.data.token}\u0026#39; | base64 --decode) kubectl config set-credentials ${CONTEXT}-token-user --token $TOKEN kubectl config set-context $CONTEXT --user ${CONTEXT}-token-user 启用 kubernetes，并配置使用的 kubernetes 用户\nbash 1 2 3 4 5 6 7 # Run in Halyard container hal config provider kubernetes enable ACCOUNT=\u0026#34;my-k8s-account\u0026#34; hal config provider kubernetes account add ${ACCOUNT} \\ --context ${CONTEXT} Halyard 有几种部署 Spinnaker 服务的选项，例如Local, git 和 Distributed，这里使用 Distributed 模式，将 Spinnaker 以分布式方式部署到 Kubernetes内。\nbash 1 hal config deploy edit --type distributed --account-name $ACCOUNT 配置S3存储 Spinnaker 需要外部存储，例如 S3 对象存储置，为此，我使用 Minio 作为外部存储服务。这里使用 docker 进行部署，作为学习，也可以使用 minIO 官方提供的 minIO-dev [4]，可以快速在 Kubernetes 集群上部署一个单实例的 minIO\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # System MINIO_ROOT_USER=$(\u0026lt; /dev/urandom tr -dc a-z | head -c${1:-4}) MINIO_ROOT_PASSWORD=$(\u0026lt; /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-8}) MINIO_PORT=\u0026#34;9010\u0026#34; # Start the container docker run -it -d --rm -v ~/.minio-data/:/data --name minio-4-spinnaker -p ${MINIO_PORT}:${MINIO_PORT} \\ -e MINIO_ROOT_USER=${MINIO_ROOT_USER} -e MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD} \\ minio/minio server /data --address :${MINIO_PORT} # This information is used in next {.1} echo \u0026#34; MINIO_ROOT_USER=${MINIO_ROOT_USER} MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD} ENDPOINT=http://$(docker inspect -f \u0026#39;{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}\u0026#39; minio-4-spinnaker):${MINIO_PORT} \u0026#34; 如果需要开启，那么需要在目录 ~/.hal/default/profiles/front50-local.yml 创建文件\nbash 1 2 3 spinnaker: s3: versioning: false 然后使用以下命令进行配置到 halyard config 文件\nbash 1 2 3 4 5 6 7 8 9 ENDPOINT=http://10.0.2.4:9000 MINIO_ACCESS_KEY=minio MINIO_SECRET_KEY=minio123 echo $MINIO_SECRET_KEY | hal config storage s3 edit --endpoint $ENDPOINT \\ --access-key-id $MINIO_ACCESS_KEY \\ --secret-access-key hal config storage edit --type s3 生成一个BOM文件 找一台有外网的主机，执行下列命令\nbash 1 hal version bom 1.19.2 -q -o yaml Note: 如果开启了 gcs.enabled: true 需要重新启动一个容器，因为这个步骤需要联网查询\nbash 1 2 3 4 5 $ DOCKERID=`docker run -d --rm \\ -v ~/.hal:/home/spinnaker/.hal \\ us-docker.pkg.dev/spinnaker-community/docker/halyard:stable` $ docker exec -it ${DOCKERID} hal version bom 1.19.2 -q -o yaml \u0026amp;\u0026amp; docker stop ${DOCKERID} 将输出的文件保存在 halyard 容器内 ~/.hal/.boms/bom/{version}.yml ，将 {version} 替换为你安装的版本，例如这里为 1.19.2\n其次，要在本地执行此操作的容器内，需要在 halconfig 目录下有对应的 BOM 清单，清单格式如下：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ tree ~/.hal/.boms/ /root/.hal/.boms/ ├── bom │ └── 1.19.2.yml ├── clouddriver │ └── clouddriver.yml ├── deck │ └── settings.js ├── echo │ └── echo.yml ├── fiat │ └── fiat.yml ├── front50 │ └── front50.yml ├── gate │ └── gate.yml ├── igor │ └── igor.yml ├── kayenta │ └── kayenta.yml ├── orca │ └── orca.yml └── rosco └── rosco.yml 这些文件夹需要自行创建，并且里面的配置文件也需要自行创建，如果不知道格式如何，可以参考 Spinnaker github 仓库上，每一个上面的文件夹都是一个项目仓库，而这些仓库的根目录都存在一个 halconfig 目录，此时需要你将对应的文件保存到对应目录下，例如，clouddriver 文件夹需要选择 github.com/spinnaker/clouddriver 项目，而配置文件需要选择 {service_name}.yml 为命名的，例如 clouddriver 就需要选择 clouddriver.yml，这个需要自行下载。\n可以使用下列脚本进行生成这些配置文件（需要上网）\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 #!/bin/bash #################################################################################### # Install Spinnaker scripts for CentOS # #################################################################################### set -e START_TIME=`date +%s` export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin export ROOT=$(cd $(dirname $0); pwd) export BASE_URL=\u0026#34;https://raw.githubusercontent.com/spinnaker\u0026#34; export DECK_FILE_NAME=\u0026#34;halconfig/settings.js\u0026#34; export FILE_PREFIX=\u0026#34;halconfig\u0026#34; usage(){ cat \u0026lt;\u0026lt;EOF Usage: $CMD \u0026lt;bom_file\u0026gt; \u0026lt;output_dir\u0026gt; $CMD ~/.hal/.boms/bom/1.20.0.yml ~/.hal/.boms EOF } function install_json_tools(){ which jq \u0026amp;\u0026gt; /dev/null || sudo yum install jq -y which yq || ( wget https://github.com/mikefarah/yq/releases/download/v4.16.2/yq_linux_amd64 \\ \u0026amp;\u0026amp; chmod +x yq_linux_amd64 \\ \u0026amp;\u0026amp; mv yq_linux_amd64 /usr/local/bin/yq ) } function remove_json_tools(){ rm -f a/usr/local/bin/yq rpm -e jq --force } function pull_packer(){ ##check paramter## if [[ ${#} -ne 1 ]]; then echo -e \u0026#34;\\033[32m Paramter amount error. \\033[0m\u0026#34; \u0026amp;\u0026amp; exit ${MALFORMEDPARAMTER} fi export SPIN_TMP_DIR=${ROOT}/spin_installer [ -d ${SPIN_TMP_DIR} ] || mkdir -pv ${SPIN_TMP_DIR} cd ${SPIN_TMP_DIR} git init # 配置远程仓库地址 git remote add origin https://github.com/spinnaker/rosco # 启用 sparse checkout git config core.sparsecheckout true # 指定要克隆的目录 echo \u0026#34;halconfig/packer\u0026#34; \u0026gt;\u0026gt; .git/info/sparse-checkout # 拉取远程仓库的内容 git pull origin ${1} tar zcf packer.tar.gz -C ./halconfig packer mv packer.tar.gz ${BOM_PATH_I}/ # clean work dir cd ${ROOT} \u0026amp;\u0026amp; rm -fr ${SPIN_TMP_DIR} } function gererate_bom(){ yq eval -o json ${BOM_FILE_NAME} | jq \u0026#39;.services\u0026#39; | jq \u0026#39;del(.defaultArtifact ,.[\u0026#34;monitoring-third-party\u0026#34;], .[\u0026#34;monitoring-daemon\u0026#34;])\u0026#39; | jq -r \u0026#39;to_entries[] | \u0026#34;\\(.key)=\\(.value)\u0026#34;\u0026#39; | while IFS=\u0026#34;=\u0026#34; read -r key value; do VERSION=\u0026#34;version-`echo $value | jq \u0026#39;.version\u0026#39;|awk -F \u0026#39;=\u0026#39; \u0026#39;{print $1}\u0026#39; | awk -F \u0026#39;-\u0026#39; \u0026#39;{print $1}\u0026#39;| tr -d \u0026#39;\u0026#34;\u0026#39;`\u0026#34; export BOM_PATH_I=${BOM_PATH}/${key} [ -d ${BOM_PATH_I} ] || mkdir -pv ${BOM_PATH_I}; chmod 777 ${BOM_PATH_I} case ${key} in \u0026#34;deck\u0026#34;) curl \u0026#34;${BASE_URL}/${key}/${VERSION}/${FILE_PREFIX}/settings.js\u0026#34; -o ${BOM_PATH_I}/settings.js ;; \u0026#34;rosco\u0026#34;) curl \u0026#34;${BASE_URL}/${key}/${VERSION}/${FILE_PREFIX}/${key}.yml\u0026#34; -o ${BOM_PATH_I}/${key}.yml curl \u0026#34;${BASE_URL}/${key}/${VERSION}/${FILE_PREFIX}/images.yml\u0026#34; -o ${BOM_PATH_I}/images.yml pull_packer ${VERSION} ;; *) curl \u0026#34;${BASE_URL}/${key}/${VERSION}/${FILE_PREFIX}/${key}.yml\u0026#34; -o ${BOM_PATH_I}/${key}.yml esac done chmod 777 ${BOM_PATH} -R } function MAIN(){ ##check user## if [[ $UID != 0 ]];then echo -e \u0026#34;\\033[41;05m Sorry, this script must be run as root! \\033[0m\u0026#34; exit ${ILLEGALUSER} fi ##check paramter## if [[ ${#} -lt 2 ]]; then usage \u0026amp;\u0026amp; exit ${MALFORMEDPARAMTER} fi export BOM_FILE_NAME=$1; shift export BOM_PATH=$1; shift ##cheking command line## install_json_tools ##processing bom## gererate_bom END_TIME=`date +%s` EXECUTING_TIME=`expr $END_TIME - $START_TIME` echo -e \u0026#34;\\033[42;30m Time had spent $EXECUTING_TIME seconds. \\033[0m\u0026#34; echo -e \u0026#34;\\033[40;34m ######################################################### \\033[0m\u0026#34; echo -e \u0026#39;\\n\u0026#39; } MAIN $1 $2 在执行脚本时需要在容器运行的宿主机进行，如果这台主机没有网络，那么可以在其他机器执行\n额外下载一个 packer.tar.gz 这里 进入文件夹 rosco/master rosco 有一个文件夹叫packer，这将其移至文件夹并解压缩为 packer.tar.gz bash 1 2 3 mv ~/.hal/.boms/rosco/master/packer.tar.gz ~/.hal/.boms/rosco cd ~/.hal/.boms/rosco tar xvf packer.tar.gz 为BOM服务配置local关键字 对于离线安装，我们需要为 BOM 中的每个服务使用的镜像名称都增加一个 local: 前缀，这是官方的固定格式 [5]\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 dependencies: consul: version: 0.7.5 redis: version: 2:2.8.4-2 vault: version: 0.7.0 services: clouddriver: commit: 024b9220a1322f80ed732de9f58aec2768e93d1b version: local:6.4.3-20191210131345 ... 配置镜像获取源 这里可以选择 直接 docker 导入镜像到每个 Kubernetes worker 节点上，也可以选择配置私有镜像仓库。\n如果需要使用私有镜像，那么需要修改 VERSION.yml 中的dockerRegistry 选项，将其修改为你自己的镜像仓库\nyaml 1 2 3 4 5 6 artifactSources: debianRepository: https://dl.bintray.com/spinnaker-releases/debians #dockerRegistry: gcr.io/spinnaker-marketplace dockerRegistry: private-docker-registry/repository-name gitPrefix: https://github.com/spinnaker googleImageProject: marketplace-spinnaker-release 或者使用 docker save 通过命令导出镜像为 tar.gz 文件，然后导入到所有的 Kubernetes 的工作节点上\n部署 可以直接执行 hal deploy 命令可以进行部署，更新，删除等操作\nbash 1 2 hal deploy apply # 部署 hal deploy clean # 一键清理已经部署的服务 在默认部署情况下，igor 与 fiat 是不开启的，如果你配置了授权与CI的配置，那么会部署上这两个服务sss\n这里会生成 Kubernetes 的资源，而手动创建的资源会存在 S3 对象存储中\nTroubleshooting Could not load \u0026ldquo;versions.yml\u0026rdquo; from config bucket: xx [2] 这是因为默认情况下从GCS读取配置文件，可以通过修改配置文件 /opt/spinanker/config/halyard-local.yml 关闭 gcs 功能（或 /opt/halyard/config/halyard.yml ）\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 server: port: 8064 ... spinnaker: artifacts: debian: https://us-apt.pkg.dev/projects/spinnaker-community docker: us-docker.pkg.dev/spinnaker-community/docker config: input: gcs: enabled: true writerEnabled: false bucket: halconfig Notes: 修改完成后需要重启进程，并且修改时需要使用root用户进入容器内\ntext 1 2 docker exec -it 4f3c037d2e3c bash hal shutdown Unable to retrieve profile \u0026ldquo;clouddriver.yml\u0026rdquo; bash 1 2 Validation in Global: ! ERROR Unable to retrieve profile \u0026#34;clouddriver.yml\u0026#34;: connect timed out 解决：BOM需要按照固定格式，创建对应每个配置文件的清单\nUnable to retrieve profile \u0026ldquo;versions.yml\u0026rdquo; bash 1 2 Validation in Global: ! ERROR Unable to retrieve profile \u0026#34;versions.yml\u0026#34;: connect timed out 解决：关闭 GCS 即可\nNo persistent storage type was configured bash 1 2 Validation in Global: ! ERROR No persistent storage type was configured. 解决：hal config storage s3 edit\nError retirveing contentes of archive packer.tar.gz bash 1 2 Validation in Global: ! ERROR Error retirveing contentes of archive packer.tar.gz 解决：拷贝对应服务的 github 仓库中的 packer 文件夹\nNo profile reader exists to read bash 1 2 3 ! ERROR No profile reader exists to read \u0026#39;6.7.1-20200319123809\u0026#39;. Consider setting \u0026#39;spinnaker.config.input.gcs.enabled: true\u0026#39; in /opt/spinnaker/config/halyard.yml 解决：因为 bom 文件中镜像没有设置 local\nAccess to XMLHttpRequest at \u0026lsquo;xxx\u0026rsquo; has been blocked by CORS policy 如下图所示：\n官方给出的检查方法是“排查 gate 服务的可用性” [8]，但检查 gate 日志没有问题，service ip 请求也是通的\nbash 1 2 3 4 5 2023-07-19 15:01:05.150 INFO 1 --- [applications-10] c.n.s.g.s.internal.ClouddriverService : ---\u0026gt; HTTP GET http://spin-clouddriver.spinnaker:7002/applications?restricted=false\u0026amp;expand=true 2023-07-19 15:01:05.186 INFO 1 --- [applications-10] c.n.s.g.s.internal.ClouddriverService : \u0026lt;--- HTTP 200 http://spin-clouddriver.spinnaker:7002/applications?restricted=false\u0026amp;expand=true (31ms) 2023-07-19 15:01:05.227 INFO 1 --- [-applications-9] c.n.s.g.s.internal.Front50Service : \u0026lt;--- HTTP 200 http://spin-front50.spinnaker:8080/v2/applications?restricted=false (83ms) 2023-07-19 15:01:08.343 INFO 1 --- [TaskScheduler-6] c.n.s.gate.plugins.deck.DeckPluginCache : Refreshing plugin cache 2023-07-19 15:01:08.343 INFO 1 --- [TaskScheduler-6] c.n.s.gate.plugins.deck.DeckPluginCache : Cached 0 deck plugins 请求 service ip + port\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $ curl 10.111.192.125:8084 -vv * About to connect() to 10.111.192.125 port 8084 (#0) * Trying 10.111.192.125... * Connected to 10.111.192.125 (10.111.192.125) port 8084 (#0) \u0026gt; GET / HTTP/1.1 \u0026gt; User-Agent: curl/7.29.0 \u0026gt; Host: 10.111.192.125:8084 \u0026gt; Accept: */* \u0026lt; HTTP/1.1 302 \u0026lt; Access-Control-Allow-Credentials: true \u0026lt; Access-Control-Allow-Origin: * \u0026lt; Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT, PATCH \u0026lt; Access-Control-Max-Age: 3600 \u0026lt; Access-Control-Allow-Headers: x-requested-with, content-type, authorization, X-RateLimit-App, X-Spinnaker-Priority \u0026lt; Access-Control-Expose-Headers: X-AUTH-REDIRECT-URL \u0026lt; X-SPINNAKER-REQUEST-ID: 6b96a924-9bcb-496c-9389-12cc6834aff7 \u0026lt; X-Content-Type-Options: nosniff \u0026lt; X-XSS-Protection: 1; mode=block \u0026lt; Cache-Control: no-cache, no-store, max-age=0, must-revalidate \u0026lt; Pragma: no-cache \u0026lt; Expires: 0 \u0026lt; X-Frame-Options: DENY \u0026lt; Location: http://spin-deck.spinnaker:9000 \u0026lt; Content-Length: 0 \u0026lt; Date: Wed, 19 Jul 2023 15:01:28 GMT \u0026lt; * Connection #0 to host 10.111.192.125 left intact 浏览器访问 gate url 也是正常的\n访问首页提示如下错误，但是单独访问 gate 页面没有问题\n报错如下：\ntext 1 Access to XMLHttpRequest at \u0026#39;http://gate.spinnaker.fuck:30080/credentials?expand=true\u0026#39; from origin \u0026#39;http://deck.spinnaker.fuck:30080\u0026#39; has been blocked by CORS policy: Response to preflight request doesn\u0026#39;t pass access control check: The value of the \u0026#39;Access-Control-Allow-Origin\u0026#39; header in the response must not be the wildcard \u0026#39;*\u0026#39; when the request\u0026#39;s credentials mode is \u0026#39;include\u0026#39;. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.Access to XMLHttpRequest at \u0026#39;http://gate.spinnaker.fuck:30080/credentials?expand=true\u0026#39; from origin \u0026#39;http://deck.spinnaker.fuck:30080\u0026#39; has been blocked by CORS policy: Response to preflight request doesn\u0026#39;t pass access control check: The value of the \u0026#39;Access-Control-Allow-Origin\u0026#39; header in the response must not be the wildcard \u0026#39;*\u0026#39; when the request\u0026#39;s credentials mode is \u0026#39;include\u0026#39;. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute. 图：spinnaker首页报错 - 跨源资源共享问题 解决：如果使用 Halyard 部署 Spinnaker，则可以使用以下设置创建文件 ~/.hal/default/profiles/gate-local.yml\nyaml 1 2 cors: allowedOriginsPattern: {you gate url} Reference [1] Install Halyard on Docker\n[2] ERROR Could not load \u0026ldquo;versions.yml\u0026rdquo; from config bucket: 403 #3920\n[3] Spinnaker: How to bring custom boms into spinnaker pod to be able to deploy it with hal?\n[4] MinIO Object Storage for Kubernetes\n[5] BOMs and Configuration on your Filesystem\n[6] ! ERROR No persistent storage type was configured. #5875\n[7] Install in Air Gaped Environment\n[8] I can’t load the Applications screen\n[9] use k8s cluster private, how to access? not use localhost! #4689\n","permalink":"https://www.161616.top/offline-installtation/","summary":"Prerequisites 具有一个 Kubernetes 集群 以部署 Spinnaker 可运行 Docker 的环境 (1 vCPU, 3.75 GB) 或者是 Ubuntu，用以安装 Halyard (用于 spinnaker 的服务) 对象存储 (MinIO)，用于持久化 Spinnaker 的数据 对象存储的 Bucket 的访问账号 安装执行步骤 安装 Halyard 可以直接使用 Docker 方式安装，这个没什么必要性，就是管理工具而已，参考附录1 [1]\n首先创建映射目录\nbash 1 2 mkdir ~/.hal -pv mkdir ~/.kubeconfig -pv 然后执行 docker run 运行容器\nbash 1 2 3 4 5 6 7 docker run -d -p 8084:8084 -p 9000:9000 \\ --name halyard --rm \\ -v ~/.hal:/home/spinnaker/.hal \\ -v ~/.","title":"无互联网环境下安装Spinnaker - Offline Install Spinnaker"},{"content":"背景 Prometheus 是目前云原生架构中监控解决方案中的基石，而对于 “metrics”，“traces” 和 “logs” 是组成云原生架构中“可观测性”的一个基础，当在扩展 Prometheus，那么 Prometheus 提供的基础架构是无法满足需求的（高可用性和可扩展性）， 而高可用性与可扩展性是满足不断增长基础设施的一个基本条件。而 Prometheus 本身并没有提供“弹性”的集群配置，也就是说，多个副本的 Prometheus 实例，对于分布在每个 Pod 上的数据也会不一致，这时也需要保证指标的归档问题。\n并且在一定的集群规模下，问题的出现远远大于 Prometheus 本身的能力，例如：\n如何经济且搞笑的存储历史数据（TB, PB）？如何快速的查询历史数据？ 如何合并 Promehtues 多个实例收集来的副本数据？ 以及多集群间的监控？ 由于 TSDB 的块同步，Prometheus 严重依赖内存，使得 Prometheus 监控项的扩展将导致集群中的CPU/MEM 的使用加大 .. 解决 Thanos 是一款可以使 Prometheus 获得 ”长期存储“，并具体有”高可用性“ 的 Prometheus 的功能扩展，“Thanos” 源自希腊语“ Athanasios”，英文意思是”不朽“。这也正是 ”Thanos“ 提供的功能：”无限制的对象存储“，并与原生 Prometheus API 高度兼容，你可以理解为 Thanos API 就是 Prometheus API。\nCortexmetrics 与 Thanos 类似，是用通过将 Prometheus 实例的”存储“和”查询“等功能分离到独立的组件中，实现水平扩展。它使用对象存储来持久化历史指标，块存储（TSDB）是他的存储后端；此外，Cortex 还提供了多租户与多租户隔离等功能\n联邦集群，联邦集群是 Prometheus 官方提供的一个概念，使用了联邦将允许 Prometheus 从另一个 Prometheus 中抓取选定的指标。可以使用的一些模型如下：\n分层联邦：大规模的集群中，Prometheus 部署模型如一个”树形“，高级别的从多个低级实例中抓取指标，并存储聚合 跨服务联邦：Prometheus 从另一个 Prometheus 只抓取指定的数据 图：Prometheus 联邦 Source：https://www.improbable.io/blog/thanos-prometheus-at-scale\n但在这种架构中，仍然还是每个查询只能针对单个 Prometheus 服务器完成。另外 Thanos 可以查询与聚合来自多个 Prometheus 实例的数据，这些数据就类似与联邦中的 ”叶“，这些数据的来源可以单实例也可以是多实例。\n在这种架构中，本质上并不是 ”高可用性“ 的，实际上存在潜在故障点，并且数据的查询是通过唯一入口（API）进行查询，并且需要配置复杂的抓取规则才可以使规则不重复。\nThanos 架构 Thanos 遵循了 KISS (Keep it simple) 原则，thanos 由多个组件组成，每个组件负责不同的功能，\n在与 Prometheus 交互方向，Thanos 使用下列两种方式（组件）：\nSidecar：\n连接到 Prometheus，读取数据或者将数据上传到对象存储中 也可以部署为传统架构，这里不能完全理解为是 Kubernetes 中的 Sidecar Receiver：从 Prometheus 接收数据，暴露或上传到云端\n扩展 Prometheus 的功能：\nStore/Store Gateway：提供存储在对象存储中的历史数据的查询功能，历史 Chunk 会存储在对象存储中\n支持基于 “时间/标签” 的分区 Compactor：对于存储在对象存储中的数据进行压缩，通过将其合并为更大的快，以便提高查询效率\nRuler/Rule：类似与 Alertmanager 的功能，他提供了告警功能\nQuerier/Query：实现了 Prometheus API，他可以从 Sidecar 与 对象存储中查询全局查询\nQuery Frontend：实现了 Prometheus API 并将请求代理至 Query 组件；并且支持缓存功能（Redis/Memcached），缓存其查询结果\nThanos 不是一种 “节省指标存储” 的方案，而是一种提供更大时间间隔，更高可用性的的查询方案，使用Thanos 不会减少磁盘空间，反而会增加磁盘空间。\n通常 Thanos 会按照维度划分为三个级别：raw, 5m, 1h，这是根据时间划分，raw 是从 Prometheus 拿到的原始数据; 5m 压缩为5分钟的快；1h 压缩为 1h的块 [1] 。不过这些没有在官方找到，引用的其他文章\n指标的查询过程 PromQL 查询请求到组件 Querier 它解释查询并进入预过滤器 查询根据标签和时间范围要求 扇出 (fan-out) 其对 stores 或 prometheuses 的请求 store 决定 是否从s3中拉去数据 query 会缓存数据到内存中 Query 发送和接收请求 收集所有响应后，如果启用合并功能，会合并并删除重复数据 最后返回该时间序列 图：Thanos查询生命周期 Source：https://banzaicloud.com/blog/multi-cluster-monitoring/\n基于时间的分片 默认 Thanos 的 Store gateway 会查询对象中的所有存储，根据查询时间返回数据，这显然不行，如果此时有大量数据（基于PB级别），此时可以根据时间分片（水平扩展），Store API 可以使用 最大时间 和 最小时间 来缩短查询的时间，\n基于标签的分片 基于标签的分片与基于时间的分片类似，这里是使用labels，而 Label 是采集与 Prometheus 的外部 Label ，并基于 Thanos 组件显式重新标记，要记住，Thanos 就是 Prometheus 扩展，功能用法与 Prometheus 是相同的的，例如下列 Thanos relabeling 的操作\nyaml 1 2 3 4 - action: keep regex: \u0026#34;eu.*\u0026#34; source_labels: - region 这些表示了只保留了以 eu.* 开头的 region label\n重复副本的删除 Thanos 对于 Prometheus 的 HA，也就是采集多个 Prometheus 实例的指标，此时 每个实例会产生相同的指标，这种模式来实现的“高可用性”，那么这种架构产生的重复副本就需要 Thanos 来处理了，例如如下所示：up{job=\u0026quot;prometheus\u0026quot;,env=\u0026quot;2\u0026quot;} 的指标， 通过重复数据删除，结果是：\ntext 1 2 up{job=\u0026#34;prometheus\u0026#34;,env=\u0026#34;2\u0026#34;,cluster=\u0026#34;1\u0026#34;} 1 up{job=\u0026#34;prometheus\u0026#34;,env=\u0026#34;2\u0026#34;,cluster=\u0026#34;2\u0026#34;} 1 那么如果不删除重复标签，可能结果就很多，是用过 replica 标签来区分副本数量\ntext 1 2 3 4 up{job=\u0026#34;prometheus\u0026#34;,env=\u0026#34;2\u0026#34;,cluster=\u0026#34;1\u0026#34;,replica=\u0026#34;A\u0026#34;} 1 up{job=\u0026#34;prometheus\u0026#34;,env=\u0026#34;2\u0026#34;,cluster=\u0026#34;1\u0026#34;,replica=\u0026#34;B\u0026#34;} 1 up{job=\u0026#34;prometheus\u0026#34;,env=\u0026#34;2\u0026#34;,cluster=\u0026#34;2\u0026#34;,replica=\u0026#34;A\u0026#34;} 1 up{job=\u0026#34;prometheus\u0026#34;,env=\u0026#34;2\u0026#34;,cluster=\u0026#34;2\u0026#34;,replica=\u0026#34;B\u0026#34;} 1 全局视图查询 如图所示，query 是一种无状态的可水平扩展的 Querier 组件，他提供了基于 Prometheus API ，可以相应基于 PromQL 的查询，而中间数据的相应是由 Store 或者\n图：Thanos query组件 Source：https://banzaicloud.com/blog/multi-cluster-monitoring/\n高基数 基数 (cardinality) 通俗来说是一个集合中的元素数量 [1] 基数的来源通常为：\nlabel 的数量 series(指标) 的数量 时间：label 或者 series 随时间而流失或增加，通常是增加 那么这么看来高基数就是，label, series, 时间这三个集合的笛卡尔积，那么高基数的情况就很正常了。\n而高基数带来的则是 Prometheus 资源使用，以及监控的性能。下图是 Grafana Lab 提到的一张图，很好的阐述了高基数这个问题\n图：Prometheus中的基数 Source：https://grafana.com/blog/2022/02/15/what-are-cardinality-spikes-and-why-do-they-matter\n如图所示：一个指标 server_responses 他的 label 存在两个 status_code 与 environment ，这代表了一个集合，那他的 label value 是 1~5xx，这个指标的笛卡尔积就是10。\n那么此时存在一个问题，如何能定位 基数高不高，Grafana Lab 给出了下面的数据 [1]，但是我不清楚具体的来源或者如何得到的这些值。也就是 label:value\n低基数：1: 5 标准基数：1: 80 高基数：1: 10000 为什么指标会指数级增长 在以 Kubernetes 为基础的架构中，随着抽象级别的提高（通常为Pod, Label, 以及更多抽象的拓扑），指标的时间序列也越来越多。因为在这种基础架构中，在传统架构中运行的一个应用的单个裸机，被许多运行分散在许多不同节点上的许多不同微服务的 Pod 所取代。在这些抽象层中的每一个都需要一个标签，以便可以唯一地标识它们，并且这些组件中的每一个都会生成自己的指标，从而创建其独特的时间序列集。\n此外，在 Kubernetes 中的工作负载的短暂性最终也会创建更多的时间序列。例如 JAVA的 http_request_duration_seconds_bucket 指标，它会每次 pod 更改状态时生成一个新的时间序列，比如从“状态200\u0026quot; 或者 “状态 404” 在到 “每个URL” 再到 “每个请求的时间”，这样大量短时间请求，对一个 Pod 状态可能会生成大量指标。\n这是就要考虑到 Prometheus 兼容的格式，而非传统监控的监控指标的格式问题，就例如上面的例子，通过对 URI，请求时长，请求状态码几个维度去监控，那么此时的 exporter 导出的数据势必是非常杂乱的，而这种可能相同的指标就会放大到无穷。\n在这种环境中的 Label，就是两组集合的笛卡尔积的选择，就是次优标签 sub-optimal labels ，对付这类高基数的指标，控制基数，以及如何避免使用这类错误，就是解决高基数的根本。\n高基数是一个非常重要的问题 高基数的问题，带来的就是基于 Prometheus 的监控带来的是更多的可观测性，反之，随着时间序列的基数增加，那么为了维持某几个特别的指标的观测性，就必须要付出更多的硬件资源，以及影响本身监控系统的性能。比较明显的表现，就是监控的相应下降，极大的拖慢了整个系统的运行速度（包含仪表盘，promQL等）。还会延长系统故障排除时的MTTR (Mean Time to Repair)。\nNotes: 其实这里还有一类型错误，就是这会导致时间序列的乱序，怎么说呢，就是当指标无线放大时，在某一个点 scrap 的指标存储时间，大于了抓取周期，导致新指标存储早于旧指标，这种很容易出现在例如 Prometheus 的从内存到存储的那个点。\n如何控制控制指标的高基数增长 指标的无序扩张（高基数）是不可避免对监控系统产生非常大的影响（存储和性能），而为此引出了一个如何优化不断增长的指标就是控制高基数增长的关键部分，下面将从几个维度来阐释控制“高基数”问题的步骤\n第一步：高基数指标是否有价值？ 在任何优化方法的第一步都是去了解哪些指标给系统带来负面影响（这里指高基数），并且还需要确定这些指标中哪些指标是有价值的；所谓的有价值既，在仪表板、告警中是否有被使用。\n基于这些信息，我将根据基数问题与监控指标的价值分为四个象限：\n高价值，低成本：闲置、陈旧、很长时间没有新数据的 低价值，低成本：基本上没有什么影响，但是需要去考虑优化 低价值，高成本：可以考虑删除掉 Label 和 metric 高价值，高成本：你的指标是否过细化，是否需要重新设计 Label 或者聚合数据；或这类指标是否适合使用 Prometheus 这类时间序列 第二步：如何确定高基数指标 确定高基数指标包含3种方式\nPrometheus WEB UI 分析，2.14 版本之后 PromQL 分析 Prometheus API 分析 通常情况下，WEB UI 就可以满足需求了，通过路径 Prometheus UI -\u0026gt; Status -\u0026gt; TSDB Status -\u0026gt; Head Cardinality Stats。\n图：Prometheus WEB UI TOP 10 series 由上图可见，在这里体现的高基数问题的指标，通常都是以 bucket 结尾的指标，而这些指标通常包含2个维度，会无线拉长成为高基数指标。如下面指标所示，通常由 le （标识每个 bucket 的上限，这可以确保可以定位到在一个时间范围内相应的请求指标有哪些）\ntext 1 2 3 4 5 apiserver_request_duration_seconds_bucket{component=\u0026#34;apiserver\u0026#34;, endpoint=\u0026#34;https\u0026#34;, group=\u0026#34;admissionregistration.k8s.io\u0026#34;, instance=\u0026#34;10.0.0.4:6443\u0026#34;, job=\u0026#34;apiserver\u0026#34;, le=\u0026#34;+Inf\u0026#34;, namespace=\u0026#34;default\u0026#34;, resource=\u0026#34;mutatingwebhookconfigurations\u0026#34;, scope=\u0026#34;cluster\u0026#34;, service=\u0026#34;kubernetes\u0026#34;, verb=\u0026#34;DELETE\u0026#34;, version=\u0026#34;v1\u0026#34;}\t19 apiserver_request_duration_seconds_bucket{component=\u0026#34;apiserver\u0026#34;, endpoint=\u0026#34;https\u0026#34;, group=\u0026#34;admissionregistration.k8s.io\u0026#34;, instance=\u0026#34;10.0.0.4:6443\u0026#34;, job=\u0026#34;apiserver\u0026#34;, le=\u0026#34;0.05\u0026#34;, namespace=\u0026#34;default\u0026#34;, resource=\u0026#34;mutatingwebhookconfigurations\u0026#34;, scope=\u0026#34;cluster\u0026#34;, service=\u0026#34;kubernetes\u0026#34;, verb=\u0026#34;POST\u0026#34;, version=\u0026#34;v1\u0026#34;} apiserver_request_duration_seconds_bucket{component=\u0026#34;apiserver\u0026#34;, endpoint=\u0026#34;https\u0026#34;, group=\u0026#34;admissionregistration.k8s.io\u0026#34;, instance=\u0026#34;10.0.0.4:6443\u0026#34;, job=\u0026#34;apiserver\u0026#34;, le=\u0026#34;0.25\u0026#34;, namespace=\u0026#34;default\u0026#34;, resource=\u0026#34;mutatingwebhookconfigurations\u0026#34;, scope=\u0026#34;cluster\u0026#34;, service=\u0026#34;kubernetes\u0026#34;, verb=\u0026#34;PATCH\u0026#34;, version=\u0026#34;v1\u0026#34;} 例如下面是生产环境中的一个高基数TOP10\nName Count http_server_requests_seconds_bucket 2017537 lettuce_command_firstreponse_seconds_bucket 755056 lettuce_command_completion_seconds_bucket 755056 http_server_requests_seconds 555575 nginx_ingress_controller_request_duration_seconds_bucket 475440 node_ipvs_backend_connections_inactive 148796 node_ipvs_backend_connections_active 148796 apiserver_request_duration_seconds_bucket 27896 etcd_request_duration_seconds_bucket1 22763 至此可以看到实际上 http_server_requests_seconds_bucket 这一个指标占据了 prometheus 总指标的50%+\n而其他的一些分析，可以很有效的定位到你需要优化的标签\nTop 10 label names with high memory usage Top 10 series count by label value pairs 通过 promQL 定位 job 查询 top 10 的 series topk(10, count by (__name__)({__name__=~\u0026quot;.+\u0026quot;})) sum(scrape_series_added) by (job) 通过 job Label 分析 series 增长 sum(scrape_samples_scraped) by (job) 通过 job Label 分析 series 总量 可以通过指标属于哪个 job\n第三步：发现那些指标没有在使用 Grafana Mimirtool 是一个开源的命令行工具， 它可以识别 Mimir、Prometheus 或 Prometheus 的存储中未在Dashboard、Alert 或 recording 中使用的指标。通过 Mimirtool 可以快速发现未使用的指标，并且做出操作\n优化监控指标 优化监控指标来解决高基数问题主要从以下维度进行\n增加采集间隔 Prometheus 的默认值为 scrape_interval: 15s，或 DPM (Data points Per minute) 4个，但是如果查询语句为 scrape_samples_scraped[1m] 那么可以考虑将这个 job 的 scrape_interval 增加为1m，这样15~60 可以减少近75%的存储成本。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 kube-state-metrics: namespaceOverride: \u0026#34;\u0026#34; rbac: create: true releaseLabel: true prometheus: monitor: enabled: true ## Scrape interval. If not set, the Prometheus default scrape interval is used. ## interval: \u0026#34;\u0026#34; 优化 histogram histrogram 是 Prometheus中一种更具有更复杂类型的监控指标，通常用于决定数据的精度，典型的例子就是上面提到的 http_server_requests_seconds_bucket 中的 le ，此时假设 le 代表请求毫秒，那么我们只需要决定你所需要的精度是哪些？例如，如果仅仅需要 1ms, 5ms, 10ms，那么指标 le 标签就控制为3，这样结合 URI 指标，那么这个 histogram 是有限的\nyaml 1 2 3 4 5 6 7 8 9 # drop all metric series ending with _bucket and where le=\u0026#34;0.1xxx\u0026#34; - source_labels: [__name__, le] separator: _ regex: \u0026#34;.+_bucket_(0.1+)\u0026#34; action: \u0026#34;drop\u0026#34; # Object labels: __name__: http_server_requests_seconds_bucket le: 0.114421 这里可以通过 promlabs 来测试你的规则是否是成功的 [2]\n删除不需要的标签 对于一些指标，删除了未使用的标签后，反而会使这个指标变得没有意义，并且使这个指标变得序列重复，这个时候可以完整删除这个指标\n例如在下面的示例中，第一个示例可以安全地删除 ip 标签，因为其余系列都是唯一的。但在第二个示例中，如果删除 ip 标签将产生重复的时间序列，Prometheus 将删除这些时间序列。my_metric_total在此示例中，Prometheus 将接收具有相同时间戳的值 1、3 和 7，并将丢弃其中的 2 个数据点。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # You can drop ip label, remaining series are still unique my_metric_total{env=“dev”, ip=“1.1.1.1\u0026#34;} 12 my_metric_total{env=“tst”, ip=“1.1.1.1\u0026#34;} 14 my_metric_total{env=“prd”, ip=“1.1.1.1\u0026#34;} 18 #Remaining values after dropping ip label my_metric_total{env=“dev”} 12 my_metric_total{env=“tst”} 14 my_metric_total{env=“prd”} 18 # You can not drop ip label, remaining series are not unique my_metric_total{env=“dev”, ip=“1.1.1.1\u0026#34;} 1 my_metric_total{env=“dev”, ip=“3.3.3.3\u0026#34;} 3 my_metric_total{env=“dev”, ip=“5.5.5.5\u0026#34;} 7 #Remaining values after dropping ip label are not unique my_metric_total{env=“dev”} 1 my_metric_total{env=“dev”} 3 my_metric_total{env=“dev”} 7 如果无法控制删除标签将导致重复序列，通过 Prometheus sum、avg、min、max等函数可以保留聚合数据，同时删除单个系列。在下面的示例中，我们使用 sum 函数来存储聚合指标，从而允许我们删除单个时间序列。\ntext 1 2 3 4 5 6 7 8 9 # sum by env my_metric_total{env=\u0026#34;dev\u0026#34;, ip=\u0026#34;1.1.1.1\u0026#34;} 1 my_metric_total{env=\u0026#34;dev\u0026#34;, ip=\u0026#34;3.3.3.3\u0026#34;} 3 my_metric_total{env=\u0026#34;dev\u0026#34;, ip=\u0026#34;5.5.5.5\u0026#34;} 7 # Recording rule sum by(env) (my_metric_total{}) my_metric_total{env=\u0026#34;dev\u0026#34;} 11 使用聚合组 例如对于 *_seconds_bucket 类的指标, 通常需要的是一些高纬度的指标，那么这些指标可以通过 recording rules 进行记录和存储\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 groups: - interval: 3m name: kube-apiserver-availability.rules rules: - expr: \u0026gt;- avg_over_time(code_verb:apiserver_request_total:increase1h[30d]) * 24 * 30 record: code_verb:apiserver_request_total:increase30d - expr: \u0026gt;- sum by (cluster, code, verb) (increase(apiserver_request_total{job=\u0026#34;apiserver\u0026#34;,verb=~\u0026#34;LIST|GET|POST|PUT|PATCH|DELETE\u0026#34;,code=~\u0026#34;2..\u0026#34;}[1h])) record: code_verb:apiserver_request_total:increase1h - expr: \u0026gt;- sum by (cluster, code, verb) (increase(apiserver_request_total{job=\u0026#34;apiserver\u0026#34;,verb=~\u0026#34;LIST|GET|POST|PUT|PATCH|DELETE\u0026#34;,code=~\u0026#34;5..\u0026#34;}[1h])) record: code_verb:apiserver_request_total:increase1h 最后 drop 掉指标\nyaml 1 2 3 4 write_relabel_configs: - source_labels: [__name__] regex: \u0026#34;apiserver_request_duration_seconds_bucket\u0026#34; action: drop recording rules 是允许预先将经常计算的表达式的结果保存为一组新的时间序列的，这种情况下查询的成本会比每次直接查询原始的表达式要快许多，并且在聚合后，可以将原来的指标删掉\nReference [1] Multi cluster monitoring with Thanos\n[2] How to manage high cardinality metrics in Prometheus and Kubernetes\n[3] 精简Prometheus指标减少资源占用\n[4] What are cardinality spikes and why do they matter?\n[5] Containing your Cardinality\n","permalink":"https://www.161616.top/using-thanos-improve-prometheus.md/","summary":"背景 Prometheus 是目前云原生架构中监控解决方案中的基石，而对于 “metrics”，“traces” 和 “logs” 是组成云原生架构中“可观测性”的一个基础，当在扩展 Prometheus，那么 Prometheus 提供的基础架构是无法满足需求的（高可用性和可扩展性）， 而高可用性与可扩展性是满足不断增长基础设施的一个基本条件。而 Prometheus 本身并没有提供“弹性”的集群配置，也就是说，多个副本的 Prometheus 实例，对于分布在每个 Pod 上的数据也会不一致，这时也需要保证指标的归档问题。\n并且在一定的集群规模下，问题的出现远远大于 Prometheus 本身的能力，例如：\n如何经济且搞笑的存储历史数据（TB, PB）？如何快速的查询历史数据？ 如何合并 Promehtues 多个实例收集来的副本数据？ 以及多集群间的监控？ 由于 TSDB 的块同步，Prometheus 严重依赖内存，使得 Prometheus 监控项的扩展将导致集群中的CPU/MEM 的使用加大 .. 解决 Thanos 是一款可以使 Prometheus 获得 ”长期存储“，并具体有”高可用性“ 的 Prometheus 的功能扩展，“Thanos” 源自希腊语“ Athanasios”，英文意思是”不朽“。这也正是 ”Thanos“ 提供的功能：”无限制的对象存储“，并与原生 Prometheus API 高度兼容，你可以理解为 Thanos API 就是 Prometheus API。\nCortexmetrics 与 Thanos 类似，是用通过将 Prometheus 实例的”存储“和”查询“等功能分离到独立的组件中，实现水平扩展。它使用对象存储来持久化历史指标，块存储（TSDB）是他的存储后端；此外，Cortex 还提供了多租户与多租户隔离等功能\n联邦集群，联邦集群是 Prometheus 官方提供的一个概念，使用了联邦将允许 Prometheus 从另一个 Prometheus 中抓取选定的指标。可以使用的一些模型如下：\n分层联邦：大规模的集群中，Prometheus 部署模型如一个”树形“，高级别的从多个低级实例中抓取指标，并存储聚合 跨服务联邦：Prometheus 从另一个 Prometheus 只抓取指定的数据 图：Prometheus 联邦 Source：https://www.","title":"使用Thanos强化Prometheus"},{"content":"Spinnaker的认证 spinnaker 中提供了认证 ( Authentication) 的机制流为 Deck \u0026lt;=\u0026gt; Gate \u0026lt;=\u0026gt; Identity Provider\nDeck 是 spinnaker 的 WEB UI (由 apache server服务的一组静态文件) Gate 是 API Gateway，所有的进入 Spinnaker 的流量都会通过 Gate 处理，这里完成 authentication 和 authorization。 Identity Provider：用于用户身份认证的外部服务或系统，例如 LDAP, OAuth 2.0(行业标准鉴权协议), SAML, X.509 等。 更多 spinnaker Authentication 工作流可以参考\nSpinnaker 认证配置 启动配置\nbash 1 hal config security authn oauth2 enable --no-validate 使用 hal 命令配置 redirect URI\nbash 1 hal config security authn oauth2 edit --pre-established-redirect-uri https://my-real-gate-address.com:8084/login 或者手动修改配置配置文件\nyaml 1 2 3 4 5 security: authn: oauth2: client: preEstablishedRedirectUri: https://my-real-gate-address.com:8084/login 这里 /login 后缀是 spinnaker 强制要求的\nBe sure to include the /login suffix at the end of the of your preEstablishedRedirectUri! [2]\n用户映射配置\n用户映射是将 spinnaker 用户字段映射为你的 Identity Provider 的用户的字段，例如如果你的 Identity Provider 字段如下所示\njson 1 2 3 4 5 6 { \u0026#34;user\u0026#34;: \u0026#34;fmercury\u0026#34;, \u0026#34;mail\u0026#34;: \u0026#34;fmercury@queen.com\u0026#34;, \u0026#34;fName\u0026#34;: \u0026#34;Freddie\u0026#34;, \u0026#34;lName\u0026#34;: \u0026#34;Mercury\u0026#34; } 那么你的用户映射应该配置如下\nyaml 1 2 3 4 5 userInfoMapping: email: mail firstName: fName lastName: lName username: user 一个完整的 keycloak 配置如下\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 oauth2: enabled: true client: clientId: {your keycloak client id} clientSecret: {your keycloak client secret} # accessTokenUri 内部可以访问即可 accessTokenUri: http://{your keycloak url}/realms/{your realm name}/protocol/openid-connect/token # userAuthorizationUri 是需要内外都可以访问才行 userAuthorizationUri: http://{your keycloak url}/realms/{your realm name}/protocol/openid-connect/auth scope: profile,email,roles # 取消了该选项 # 必须外网跳转地址 # preEstablishedRedirectUri: http://{your keycloak url}/realms/test/protocol/openid-connect/logout?redirect_uri=http://{your_spinnaker_gate_domain}/login # preEstablishedRedirectUri: http://{your_spinnaker_gate_domain}/login resource: # 只需要内部访问 userInfoUri: http://{your keycloak url}/realms/{your realm name}/protocol/openid-connect/userinfo userInfoMapping: email: email firstName: given_name lastName: family_name username: preferred_username # roles: group # 该选项无法识别 provider: OTHER 上门提到内部访问为 spinnaker 服务间调用；外部访问为，用户浏览器可以跳转到的地址\n使用命令行配置上面的信息\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 keycloak_url=keycloak.example.com realm_name=example gate_external_url=gate-api.example.com hal config security authn oauth2 edit --client-id {client_name} --no-validate hal config security authn oauth2 edit --client-secret {token} --no-validate hal config security authn oauth2 edit --user-authorization-uri https://${keycloak_url}/auth/realms/${realm_name}/protocol/openid-connect/auth --no-validate hal config security authn oauth2 edit --access-token-uri https://${keycloak_url}/auth/realms/${realm_name}/protocol/openid-connect/token --no-validate hal config security authn oauth2 edit --pre-established-redirect-uri https://${keycloak_url}/auth/realms/${realm_name}/protocol/openid-connect/logout?redirect_uri=http://${gate_external_url}/login --no-validate hal config security authn oauth2 edit --scope roles,email,profile --no-validate hal config security authn oauth2 edit --user-info-mapping-email email --no-validate hal config security authn oauth2 edit --user-info-mapping-first-name given_name --no-validate hal config security authn oauth2 edit --user-info-mapping-last-name lastName --no-validate hal config security authn oauth2 edit --user-info-mapping-username preferred_username --no-validate hal config security authn oauth2 edit --provider Other --no-validate hal config security authn oauth2 edit --scope roles,email,profile --no-validate tip 最终配置完后，存在访问拿去不到user，最终改为统一的域名，域名必须保证运行spinnaker的节点可以解析到 keycloak配置 创建一个 keycloak client，名字为 Spinnaker。\n图：Client for Spinnaker Source：https://akuity.io/blog/argo-cd-architectures-explained/ 选择 openid-connect 作为客户端协议\n图：Realm configuration Root URL should be our Spinnaker URL. When we click save, we will get a lot of other options to configure. Then we have to configure a Valid Redirect URI. This is the URI that Keycloak redirects users when the authentication is completed successfully. This should be our Spinnaker gate public URI.\n下面配置他的跳转 URL，ROOT URL 是 Spinnaker URL, Valid Redirect URI 是在完成认证后，为用户重定向的 URI，通常配置为 \u0026lsquo;/*\u0026rsquo;\n图：Client Valid Redirect URI Source：https://akuity.io/blog/argo-cd-architectures-explained/ 最后配置 Access Type 为 “confidential”，选择 “Credentials” 并配配置为 “client id and secret” ，这里的 secret 将用于上面 spinnaker 中的配置。\n图：Client credentials tab Source：https://akuity.io/blog/argo-cd-architectures-explained/ 拷贝 “secret” 部分的“密钥”配置到 spinnaker中。\nyaml 1 2 3 4 5 oauth2: enabled: true client: clientId: {your keycloak client id} clientSecret: {your keycloak client secret} Reference [1] Authentication Architecture\n[2] OAuth 2.0\n[3] OAuth 2.0 Configuration\n[4] OAuth 2.0 Configuration\n","permalink":"https://www.161616.top/spinnaker-auth-with-keycloak/","summary":"Spinnaker的认证 spinnaker 中提供了认证 ( Authentication) 的机制流为 Deck \u0026lt;=\u0026gt; Gate \u0026lt;=\u0026gt; Identity Provider\nDeck 是 spinnaker 的 WEB UI (由 apache server服务的一组静态文件) Gate 是 API Gateway，所有的进入 Spinnaker 的流量都会通过 Gate 处理，这里完成 authentication 和 authorization。 Identity Provider：用于用户身份认证的外部服务或系统，例如 LDAP, OAuth 2.0(行业标准鉴权协议), SAML, X.509 等。 更多 spinnaker Authentication 工作流可以参考\nSpinnaker 认证配置 启动配置\nbash 1 hal config security authn oauth2 enable --no-validate 使用 hal 命令配置 redirect URI\nbash 1 hal config security authn oauth2 edit --pre-established-redirect-uri https://my-real-gate-address.","title":"Spinnaker接入keycloak认证"},{"content":"开始前的实验环境 Resources controller worker-1 worker-2 OS CentOS 7.9 CentOS 7.9 CentOS 7.9 Storage 20GB 20GB 20GB vCPU 2 2 2 RAM 4GB 4GB 4GB NIC 10.0.0.4 10.0.0.4 10.0.0.4 Kubernetes Version 1.19.10 1.19.10 1.19.10 选择匹配 Kubernetes 版本的 Calico 版本 通常情况下，查看 Calico 所支持的 Kubernetes 版本，可以通过路径 Install Calico ==\u0026gt; Kubernetes ==\u0026gt; System requirements 可以找到自己的 Kubernetes 集群所支持的 Calico 版本。\n例如在实验环境中，Kubernetes 1.19 版本所支持的版本有 Calico 3.20，这个时候直接 apply 这个版本提供的资源清单即可\n如何开启纯 BGP 模式 默认情况下下，Calico 使用的是 full mesh 和 IPIP， 如果想通过在部署时就修改关闭 IPIP 模式，可以通过修改资源清单中的环境变量来关闭 CALICO_IPV4POOL_IPIP: Never。\n如果需要在安装时配置Pod 的 CIDR，需要修改 CALICO_IPV4POOL_CIDR\n如果你需要切换 CNI 如果你的集群不是空的，而是存在很多 Pod 的集群，请注意，这个时候你的 flannel 或者其他 CNI 生成的网络接口是不会被销毁的，Pod 的 IP也是旧 CNI 生成的网段，此时 Calico 会按照原有的 IP 进行维护路由，可能会存在访问不了的情况，这时候不要随意切换 CNI\n如何检查 Calico 使用的是什么模式 在使用默认的资源清单安装完 Calico 后，实际上此时会表现为 BGP + IPIP 隧道模式，同节点 Pod 使用直连方式，跨节点 Pod 通讯使用 tunnel 隧道完成，表现形式为 ip addr 会看到 tunl0 设备\n如果是纯 BGP 模式，那么表现形式为 route -n 看到的路由跨节点的都应该是 eth0 这样子的，如下所示\nbash 1 2 3 4 5 6 7 8 9 10 $ route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 10.0.0.2 0.0.0.0 UG 0 0 0 eth0 10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 10.244.196.128 0.0.0.0 255.255.255.255 UH 0 0 0 calia5f3c234a97 10.244.196.128 0.0.0.0 255.255.255.192 U 0 0 0 * 10.244.214.0 10.0.0.4 255.255.255.192 UG 0 0 0 eth0 169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 当然此时一定是不存在 tunl0 设备的。\nReference ​[1] BGP configuration\n​[2] BGP peer\n​[3] IP pool\n​[4] System requirements\n","permalink":"https://www.161616.top/calico-cni-deplyment/","summary":"开始前的实验环境 Resources controller worker-1 worker-2 OS CentOS 7.9 CentOS 7.9 CentOS 7.9 Storage 20GB 20GB 20GB vCPU 2 2 2 RAM 4GB 4GB 4GB NIC 10.0.0.4 10.0.0.4 10.0.0.4 Kubernetes Version 1.19.10 1.19.10 1.19.10 选择匹配 Kubernetes 版本的 Calico 版本 通常情况下，查看 Calico 所支持的 Kubernetes 版本，可以通过路径 Install Calico ==\u0026gt; Kubernetes ==\u0026gt; System requirements 可以找到自己的 Kubernetes 集群所支持的 Calico 版本。\n例如在实验环境中，Kubernetes 1.19 版本所支持的版本有 Calico 3.20，这个时候直接 apply 这个版本提供的资源清单即可\n如何开启纯 BGP 模式 默认情况下下，Calico 使用的是 full mesh 和 IPIP， 如果想通过在部署时就修改关闭 IPIP 模式，可以通过修改资源清单中的环境变量来关闭 CALICO_IPV4POOL_IPIP: Never。","title":"在Kubernetes集群上安装 Calico cni 的注意事项"},{"content":"spinnaker 的模板是通过 spin 命令行去创建的，spin 是 spinnaker 的套件，可以使用 spin 获取到存在的pipeline与 template，还可以创建一个新的模板或者替换就得；cli获取到的数据是 json 类型\n配置spin bash 1 2 3 4 5 6 7 mkdir ~/spin # Inside the dir, setup ‘config’ file using the sample # https://github.com/spinnaker/spin/blob/master/config/example.yaml #Sample ~/.spin/config gate: endpoint: http://demospin.net:9000/gateauth: enabled: false spin 配置文件 spin 默认从 ~/spin/config 读取配置，可以通过 \u0026ndash;config 指定 spin 的配置文件。下面是一个官方给的一个 spin config 的 example，在配置时只需要选择一个认证方式即可 iap/x.509/oauth2 这个选择的是 oauth2\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 # NOTE: Copy this file to ~/.spin/config gate: endpoint: https://my-spinnaker-gate:8084 retryTimeout: 300 auth: enabled: true x509: # See https://www.spinnaker.io/setup/security/ssl/ and # https://www.spinnaker.io/setup/security/authentication/x509/ for guides on creating # the key and cert files. certPath: \u0026#34;~/.spin/certpath\u0026#34; keyPath: \u0026#34;~/.spin/keypath\u0026#34; # This should point to an _unencrypted_ keyfile. # Pipe to start a multi-line string. This is necessary to import the b64 cert/key value. cert: | -----BEGIN CERTIFICATE----- BLAHBLAHBLAHBLAHBLAHBLAH== -----END RSA PRIVATE KEY----- # Pipe to start a multi-line string. This is necessary to import the b64 cert/key value. key: | -----BEGIN RSA PRIVATE KEY----- BLAHBLAHBLAHBLAHBLAHBLAH== -----END RSA PRIVATE KEY----- oauth2: # The following is an example for Google\u0026#39;s OAuth2 endpoints. # The values for these are specific to your OAuth2 provider. authUrl: https://accounts.google.com/o/oauth2/auth tokenUrl: https://accounts.google.com/o/oauth2/token # See https://spinnaker.io/setup/security/authentication/oauth/#oauth-20-providers # for examples acquiring clientId/clientSecret. clientId: clientSecret: scopes: - scope1 - scope2 # To set a cached token, follow the following format: # note that these yaml keys must match the golang struct tags exactly because of yaml.UnmarshalStrict cachedToken: access_token: \u0026lt;token\u0026gt; token_type: bearer refresh_token: \u0026lt;token\u0026gt; 这里使用了 oauth2，下面是一个完整的配置示例\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 auth: enabled: true ignoreCertErrors: false ignoreRedirects: false oauth2: authUrl: http://{your spinnaker web address}/realms/test/protocol/openid-connect/auth cachedToken: access_token: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI3VU0wRUtWRGYwMzJaSFh5MjVDeXhTSGM2b3AySUpod3B0VkhsQnZZSXVvIn0.eyJleHAiOjE2OTAzNTMzNTYsImlhdCI6MTY5MDM1MzA1NiwianRpIjoiOTAzN2ZhNDItYzc1ZC00ODY3LThhZDYtZDkxYWY0ZTRhZTAyIiwiaXNzIjoiaHR0cDovL3NlaXlhLXNzby5ic3Nydi5jb20vcmVhbG1zL3Rlc3QiLCJzdWIiOiJjODgzOGY0Ni1iZWNhLTRmODctYWNjMC0yMTEzYWM2Y2E0NzAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJzZWl5YS1zcGluLXRlc3QiLCJzZXNzaW9uX3N0YXRlIjoiNzNkNjQ2MGUtY2E0OC00YTYwLWE2MWUtZTUzMTBjNTZmMmQzIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vc2VpeWEtc3Bpbi1hcGkuYnNzcnYuY29tIiwiaHR0cDovL3NlaXlhLXNwaW4td2ViLmJzc3J2LmNvbSJdLCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwic2lkIjoiNzNkNjQ2MGUtY2E0OC00YTYwLWE2MWUtZTUzMTBjNTZmMmQzIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoic2VpeWEgc2VpeWEiLCJncm91cHMiOltdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0IiwiZ2l2ZW5fbmFtZSI6InNlaXlhIiwiZmFtaWx5X25hbWUiOiJzZWl5YSIsImVtYWlsIjoic2VpeWFAYnNzcnYuY29tIn0.HCr2D0LpRq0nM4_JGoSjl3gmuQZZf-SAyNN01gQj1mIJoTjR2qFd_GuCy3UMgdNCacnEApR-tLmu_7vgti4GDmvv0Fgxl2k2QF8YrDyX89QB2M-72EcqWrHeOSCjeWTagF-ewh_ENfhWPle9iUszM8XLt-rOLr9Rf4RpBRDDMh25MyVKFfy6sezKk9x0oN9IA0MoUWXsdL5nbewu_KCXmF-Jh7raoxXadWs3N9SmWa5RC6FsYFsZEhbJ5vou-EEHzdsBlWwKj_skd7IuPalRAd3MzHqr9HMcZmyzAxC349ozipOGo5vY258wHs8cOQnasJSWYqQani0xNoE3-1L5PA expiry: \u0026#34;0001-01-01T00:00:00Z\u0026#34; refresh_token: eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI3YmI2NmI3OC1lYzQ0LTQ2YzEtYTMxYy1kNGJkZTZkMjI2ZjEifQ.eyJleHAiOjE2OTAzNTQ4NTYsImlhdCI6MTY5MDM1MzA1NiwianRpIjoiZjM0Y2JkMDUtNWFiZC00ODNjLTkyOTEtODZlZTIxNGJkNTkwIiwiaXNzIjoiaHR0cDovL3NlaXlhLXNzby5ic3Nydi5jb20vcmVhbG1zL3Rlc3QiLCJhdWQiOiJodHRwOi8vc2VpeWEtc3NvLmJzc3J2LmNvbS9yZWFsbXMvdGVzdCIsInN1YiI6ImM4ODM4ZjQ2LWJlY2EtNGY4Ny1hY2MwLTIxMTNhYzZjYTQ3MCIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJzZWl5YS1zcGluLXRlc3QiLCJzZXNzaW9uX3N0YXRlIjoiNzNkNjQ2MGUtY2E0OC00YTYwLWE2MWUtZTUzMTBjNTZmMmQzIiwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsInNpZCI6IjczZDY0NjBlLWNhNDgtNGE2MC1hNjFlLWU1MzEwYzU2ZjJkMyJ9.bWN0Zencpbwj3fc4SSbc6ApMmx9OBBtevpDFG0974-4 token_type: bearer clientId: \u0026#34;{your spinnaker client id}\u0026#34; clientSecret: 61VpMJND1V3A4OXp1afZSidaxIG5hvgt scopes: - openid - email - profile tokenUrl: http://{your spinnaker web address}/realms/test/protocol/openid-connect/token gate: endpoint: http://{your spinnaker web address} retryTimeout: 300 Token 是有时效性的，默认情况下生成的token只有 300 秒\ntoken的获取 当提示下面错误时，表示 token 已过期\nbash 1 2 3 Caching oauth2 token. Could not reach Gate, please ensure it is running. Failing. Get \u0026#34;http://{your spinnaker web address}/realms/test/protocol/openid-connect/auth?client_id=spin-test\u0026amp;redirect_uri=http://{your spinnaker web address}/login\u0026amp;response_type=code\u0026amp;scope=profile%20email%20role\u0026amp;state=sh5ZiP\u0026#34;: stopped after 10 redirects 图：获取access_token参数图 上图表示通过 spinnaker api 获取登录 token 的一个参数，这里 API 是 “keycloak” 的 realm，也是 spinnaker 中健全认证 [1] 的一部分。\n配置 Pineline Get the pipeline. bash 1 2 spin pipeline get --name \u0026lt;pipelineName\u0026gt; --application \u0026lt;appName\u0026gt; spin pipeline get \u0026lt;pipelineName\u0026gt; -a \u0026lt;appName\u0026gt; 从文件 save 一个 pipeline.\nbash 1 spin pipeline get --name \u0026lt;pipelineName\u0026gt; --application \u0026lt;appName\u0026gt; | tee new_template.txt 列出一个 pipeline.\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 $ spin pipeline list -a example_pipeline Caching oauth2 token. [ { \u0026#34;application\u0026#34;: \u0026#34;example_pipeline\u0026#34;, \u0026#34;expectedArtifacts\u0026#34;: [ { \u0026#34;defaultArtifact\u0026#34;: { \u0026#34;customKind\u0026#34;: true, \u0026#34;id\u0026#34;: \u0026#34;e37a6a0a-4d93-4977-bc1c-d2931e29156a\u0026#34; }, \u0026#34;displayName\u0026#34;: \u0026#34;testdepoloy\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;a8085035-93a0-45d7-985c-64615d8f200d\u0026#34;, \u0026#34;matchArtifact\u0026#34;: { \u0026#34;artifactAccount\u0026#34;: \u0026#34;testdepoloy\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;dc72329f-e894-496f-af96-ce1c528340fc\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;https://git.com/Example/test/-/raw/master/nginxtest2.yaml\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;gitlab/file\u0026#34; }, \u0026#34;useDefaultArtifact\u0026#34;: false, \u0026#34;usePriorArtifact\u0026#34;: false } ], \u0026#34;id\u0026#34;: \u0026#34;1638c12f-aab6-4c5e-8886-ed52fc4c8bbb\u0026#34;, \u0026#34;index\u0026#34;: 0, \u0026#34;keepWaitingPipelines\u0026#34;: true, \u0026#34;lastModifiedBy\u0026#34;: \u0026#34;anonymous\u0026#34;, \u0026#34;limitConcurrent\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;deploy\u0026#34;, \u0026#34;spelEvaluator\u0026#34;: \u0026#34;v4\u0026#34;, \u0026#34;stages\u0026#34;: [], \u0026#34;triggers\u0026#34;: [], \u0026#34;updateTs\u0026#34;: \u0026#34;1690187227000\u0026#34; } ] Create a pipeline 使用的 schema json 1 \u0026#34;schema\u0026#34;: \u0026#34;v2\u0026#34;, 声明变量 在 “模板” 中配置的 “variable” 可以作为之实例化时填写的内容到对应的 pipeline 中。\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 \u0026#34;variables\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;\u0026lt;type\u0026gt;\u0026#34;, \u0026#34;defaultValue\u0026#34;: \u0026lt;defaultValue\u0026gt;, \u0026#34;description\u0026#34;: \u0026#34;\u0026lt;some description\u0026gt;\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;\u0026lt;name of this variable\u0026gt;\u0026#34; }, { \u0026#34;type\u0026#34;: \u0026#34;\u0026lt;type\u0026gt;\u0026#34;, \u0026#34;defaultValue\u0026#34;: \u0026lt;defaultValue\u0026gt;, \u0026#34;description\u0026#34;: \u0026#34;\u0026lt;some description\u0026gt;\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;\u0026lt;name of this variable\u0026gt;\u0026#34; } ] 在上下文中使用变量 例如 使用 ${templateVariables.${variableName}} , 例如我们在 pipeline 使用一个配置 \u0026quot;waitTime\u0026quot;: \u0026lt;time\u0026gt; ，他在参数化的模板中的使用如下\njson 1 \u0026#34;waitTime\u0026#34;: \u0026#34;${ templateVariables.timeToWait }\u0026#34;, 最终一个完整模板为\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 { \u0026#34;schema\u0026#34;: \u0026#34;v2\u0026#34;, # Reference to the MPTv2 schema \u0026#34;variables\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;int\u0026#34;, \u0026#34;defaultValue\u0026#34;: 42, \u0026#34;description\u0026#34;: \u0026#34;The time a wait stage shall pauseth\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;timeToWait\u0026#34; # This is the name that\u0026#39;s referenced in the SpEL expression later } ], \u0026#34;id\u0026#34;: \u0026#34;newSpelTemplate\u0026#34;, # Main identifier to reference this template from instance \u0026#34;protect\u0026#34;: false, \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;Variable Wait\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;A demonstrative Wait Pipeline.\u0026#34;, \u0026#34;owner\u0026#34;: \u0026#34;example@example.com\u0026#34;, \u0026#34;scopes\u0026#34;: [\u0026#34;global\u0026#34;] }, \u0026#34;pipeline\u0026#34;: { # A \u0026#34;normal\u0026#34; pipeline definition. \u0026#34;lastModifiedBy\u0026#34;: \u0026#34;anonymous\u0026#34;, \u0026#34;updateTs\u0026#34;: \u0026#34;0\u0026#34;, \u0026#34;parameterConfig\u0026#34;: [], \u0026#34;limitConcurrent\u0026#34;: true, \u0026#34;keepWaitingPipelines\u0026#34;: false, \u0026#34;description\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;triggers\u0026#34;: [], \u0026#34;notifications\u0026#34;: [], \u0026#34;stages\u0026#34;: [ { \u0026#34;waitTime\u0026#34;: \u0026#34;${ templateVariables.timeToWait }\u0026#34;, # Templated field. \u0026#34;name\u0026#34;: \u0026#34;My Wait Stage\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;wait\u0026#34;, \u0026#34;refId\u0026#34;: \u0026#34;wait1\u0026#34;, \u0026#34;requisiteStageRefIds\u0026#34;: [] } ] } } Save a pipeline bash 1 spin pipeline-templates save --file my_template.txt Reference [1] OAuth 2.0\n[2] Create a Pipeline Template\n","permalink":"https://www.161616.top/spinnaker-template/","summary":"spinnaker 的模板是通过 spin 命令行去创建的，spin 是 spinnaker 的套件，可以使用 spin 获取到存在的pipeline与 template，还可以创建一个新的模板或者替换就得；cli获取到的数据是 json 类型\n配置spin bash 1 2 3 4 5 6 7 mkdir ~/spin # Inside the dir, setup ‘config’ file using the sample # https://github.com/spinnaker/spin/blob/master/config/example.yaml #Sample ~/.spin/config gate: endpoint: http://demospin.net:9000/gateauth: enabled: false spin 配置文件 spin 默认从 ~/spin/config 读取配置，可以通过 \u0026ndash;config 指定 spin 的配置文件。下面是一个官方给的一个 spin config 的 example，在配置时只需要选择一个认证方式即可 iap/x.509/oauth2 这个选择的是 oauth2\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 # NOTE: Copy this file to ~/.","title":"Spinnaker template"},{"content":" 原作者 Javier Martínez\n背景 在学习 Kubernetes 调度时，有两个重要的概念，\u0026ldquo;request \u0026ldquo;与 \u0026ldquo;limit\u0026rdquo;，而对应的资源就是“内存” 与 “CPU” ，而这两个决定了 Pod 将如何调度；\u0026ldquo;request \u0026ldquo;与 \u0026ldquo;limit\u0026rdquo; 也是整个调度系统中的基数因子。\n什么是 request 和 limit 在 Kubernetes 中，Limit 是容器可以使用的最大资源量，这表示 “容器” 的内存或 CPU 的使用，永远不会超过 Limit 配置的值。\n而另一方面，Request 则是为 “容器” 保留的最低资源保障；换句话来说，Request 则是在调度时，容器被允许所需的配置。\n图：Kubernetes 中Limit 和 Request 图示 Source：https://sysdig.com/blog/kubernetes-limits-requests/ 如何配置 request 和 limit 下列清单是 Deployment 的部署清单，他将部署一个 redis 与 一个 busybox\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 kind: Deployment apiVersion: extensions/v1beta1 … template: spec: containers: - name: redis image: redis:5.0.3-alpine resources: limits: memory: 600Mi cpu: 1 requests: memory: 300Mi cpu: 500m - name: busybox image: busybox:1.28 resources: limits: memory: 200Mi cpu: 300m requests: memory: 100Mi cpu: 100m 假设现有集群，具有 4 核CPU 和 16GB RAM 节点。此时可以提取出的信息如下：\nPod Request 是 400 MiB 内存和 600 毫核 CPU (Redis+busybox)。而调度需要一具有足够可用可分配空间的Node\n来调度 Pod。\nredis的 CPU 为 512，busybox 容器的 CPU 份额为 102，Kubernetes 为每个核心分配 1024 个份额，因此 redis：1024 * 0.5 个核心 ≅ 512 与 busybox：1024 * 0.1 个核心 ≅ 102\n如果 Redis 容器尝试分配超过 600MB 的内存，则它会被 OOM 终止\n如果 Redis 每 100 毫秒超过超过 100 毫秒时的 CPU，（Node有 4 个核，可用时间为每 100 毫秒 400 毫秒时），Redis 将受到 CPU 限制，从而导致性能下降**。**\n如果 Busybox 容器试图分配超过 200MB 的内存，它将被 OOM 终止\n如果Busybox尝试每 100 毫秒使用超过 30 毫秒的 CPU\nRequest 通过上面示例，可以下定义了，Kuberentes 将 Request 定义为容器的 最小资源量。\n当一个 Pod 被调度时，kube-scheduler 将检查 Kubernetes 请求，以便将该 Pod 分配到最佳节点，该节点至少可以满足 Pod 中所有容器的数量。如果 Request 的数量高于可用资源，则 Pod 将不会被调度并保持在 Pending 状态。\n例如下列例子\nyaml 1 2 3 4 resources: requests: cpu: 0.1 memory: 4Mi 使用请求：\n将 Pod 分配给 Node 时，满足 Pod 中容器指示的 Request 。\n在运行时，指示的 Request 将保证为该 Pod 中的容器的最小 Request 。\n图：如何最佳的分配资源 Source：https://sysdig.com/blog/kubernetes-limits-requests/ Limit Limit 在 Kubernetes 为定义容器可以使用的最大资源量。这代表容器永远不会超过 Limit 配置的 内存 或 CPU 。\nyaml 1 2 3 4 resources: limits: cpu: 0.5 memory: 100Mi 在调度时，如果没有配置 Request，默认 Kubernetes 将设置 requests = limits。 调度后，运行时，kubelet 检查 Pod 中的容器是否消耗了比 Limit 中配置的更多的资源。 CPU 和 内存的特性 CPU 是一种 “可压缩资源”，这意味着它可以被拉伸以满足所有需求。如果进程申请了太多 CPU，其中一些将被限制。\n可以使用 millicores (m) 来表示比1核心更小的数量 CPU 最小量为 1m 内存是一种 “不可压缩的” 资源，这意味着内存资源不能像 CPU 资源那样被拉伸。如果一个进程没有足够的内存来工作，这个进程就会被 OOM。\n内存资源在 Kubernetes 中的单位是以字节为单位，可以使用大写的 E、P、T、G、M、k 来表示 Exabyte、Petabyte、Terabyte、Gigabyte、Megabyte 和 kilobyte，例如 4G, 500M；也可以使用 Ei、Pi、Ti，例如 500Mi\n**G 和 Gi ** 的区别：**G 和 Gi **区别主要在计算方式上，G是按照 2 的 n 次方进行计算，例如 1KB = $2^{10}$，而 Gi 计算方式是按照 10 的 n 次方，例如 1Mi = $10^3$\nNote：不要使用小写的 “m” ，这代表 Millibytes\nResource/LimitQuota - 基于名称空间的资源限制 ResourceQuotas 在 Kubernetes 集群中提供了基于名称空间的资源隔离，我们可以将资源隔离到不同的名称空间中，也称为租户；例如可以 为整个命名空间设置内存或 CPU 限制，确保名称空间内的业务不能从使用更多的系统资源。\n下列是一个 ResourceQuotas 的配置\nyaml 1 2 3 4 5 6 7 8 9 10 apiVersion: v1 kind: ResourceQuota metadata: name: mem-cpu-demo spec: hard: requests.cpu: 2 requests.memory: 1Gi limits.cpu: 3 limits.memory: 2Gi requests.cpu：名称空间中所有 Request 的最低 CPU 数量 requests.memory：名称空间中所有 Request 的 最低内存数量 limits.cpu：名称空间中所有 Limit 最大 CPU 数量 limits.memory：名称空间中所有 Limit 最大内存量 ResourceQuotas 可限制名称空间的资源总量，如果我们想给里名称空间里的 Pod 配置限制可以使用 “LimitRange”\n下列是一个 LimitRanges 的配置\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: v1 kind: LimitRange metadata: name: cpu-resource-constraint spec: limits: - default: cpu: 500m defaultRequest: cpu: 500m min: cpu: 100m max: cpu: \u0026#34;1\u0026#34; type: Container default: 如果未指定，创建的容器将具有此值。 min：创建的容器不能有小于此的限制或请求。 max: 创建的容器不能有比这更大的限制或请求。 Note: 在默认情况下，即使未设置 LimitRanges，Pod 中的所有容器也会有效地请求 100m 的 CPU。\n总结 Request 和 Limit 是在 Kubernetes 集群中控制成本的关键配置 只有巧用 Request 和 Limit 才可以为集群提供最佳配额 专用的 容器不应该设置 Request 和 Limit，这将导致容器无法正常允许，甚至被驱逐 只有对 Request 和 Limit 进行精细的设置才可以使 Kubernetes 集群最佳化，否则 “弊大于利” ","permalink":"https://www.161616.top/kubernetes-limit-request/","summary":"原作者 Javier Martínez\n背景 在学习 Kubernetes 调度时，有两个重要的概念，\u0026ldquo;request \u0026ldquo;与 \u0026ldquo;limit\u0026rdquo;，而对应的资源就是“内存” 与 “CPU” ，而这两个决定了 Pod 将如何调度；\u0026ldquo;request \u0026ldquo;与 \u0026ldquo;limit\u0026rdquo; 也是整个调度系统中的基数因子。\n什么是 request 和 limit 在 Kubernetes 中，Limit 是容器可以使用的最大资源量，这表示 “容器” 的内存或 CPU 的使用，永远不会超过 Limit 配置的值。\n而另一方面，Request 则是为 “容器” 保留的最低资源保障；换句话来说，Request 则是在调度时，容器被允许所需的配置。\n图：Kubernetes 中Limit 和 Request 图示 Source：https://sysdig.com/blog/kubernetes-limits-requests/ 如何配置 request 和 limit 下列清单是 Deployment 的部署清单，他将部署一个 redis 与 一个 busybox\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 kind: Deployment apiVersion: extensions/v1beta1 … template: spec: containers: - name: redis image: redis:5.","title":"Kubernetes中的资源限制 - Request\u0026Limit"},{"content":"背景 对于整个 Kubernetes 集群来说，随着业务不断地打磨，新增指标，那么对于 Prometheus 特性来说，那么内存 与 存储的使用势必是增加。这是对于存储压力是很重的，通常情况下，使用 Prometheus，都会是用于 Kubernetes 集群中，而 应用于 Kubernetes 集中的存储势必是 PVC 之类的网络存储。\n这种场景中，我将尝试拆解如何分析和配置 Prometheus 以显著的减少其资源使用并解决高基数问题\n高基数 基数 (cardinality) 通俗来说是一个集合中的元素数量 [1] 基数的来源通常为：\nlabel 的数量 series(指标) 的数量 时间：label 或者 series 随时间而流失或增加，通常是增加 那么这么看来高基数就是，label, series, 时间这三个集合的笛卡尔积，那么高基数的情况就很正常了。\n而高基数带来的则是 Prometheus 资源使用，以及监控的性能。下图是 Grafana Lab 提到的一张图，很好的阐述了高基数这个问题\n图：Prometheus中的基数 Source：https://grafana.com/blog/2022/02/15/what-are-cardinality-spikes-and-why-do-they-matter\n如图所示：一个指标 server_responses 他的 label 存在两个 status_code 与 environment ，这代表了一个集合，那他的 label value 是 1~5xx，这个指标的笛卡尔积就是10。\n那么此时存在一个问题，如何能定位 基数高不高，Grafana Lab 给出了下面的数据 [1]，但是我不清楚具体的来源或者如何得到的这些值。也就是 label:value\n低基数：1: 5 标准基数：1: 80 高基数：1: 10000 为什么指标会指数级增长 在以 Kubernetes 为基础的架构中，随着抽象级别的提高（通常为Pod, Label, 以及更多抽象的拓扑），指标的时间序列也越来越多。因为在这种基础架构中，在传统架构中运行的一个应用的单个裸机，被许多运行分散在许多不同节点上的许多不同微服务的 Pod 所取代。在这些抽象层中的每一个都需要一个标签，以便可以唯一地标识它们，并且这些组件中的每一个都会生成自己的指标，从而创建其独特的时间序列集。\n此外，在 Kubernetes 中的工作负载的短暂性最终也会创建更多的时间序列。例如 JAVA的 http_request_duration_seconds_bucket 指标，它会每次 pod 更改状态时生成一个新的时间序列，比如从“状态200\u0026quot; 或者 “状态 404” 在到 “每个URL” 再到 “每个请求的时间”，这样大量短时间请求，对一个 Pod 状态可能会生成大量指标。\n这是就要考虑到 Prometheus 兼容的格式，而非传统监控的监控指标的格式问题，就例如上面的例子，通过对 URI，请求时长，请求状态码几个维度去监控，那么此时的 exporter 导出的数据势必是非常杂乱的，而这种可能相同的指标就会放大到无穷。\n在这种环境中的 Label，就是两组集合的笛卡尔积的选择，就是次优标签 sub-optimal labels ，对付这类高基数的指标，控制基数，以及如何避免使用这类错误，就是解决高基数的根本。\n高基数是一个非常重要的问题 高基数的问题，带来的就是基于 Prometheus 的监控带来的是更多的可观测性，反之，随着时间序列的基数增加，那么为了维持某几个特别的指标的观测性，就必须要付出更多的硬件资源，以及影响本身监控系统的性能。比较明显的表现，就是监控的相应下降，极大的拖慢了整个系统的运行速度（包含仪表盘，promQL等）。还会延长系统故障排除时的MTTR (Mean Time to Repair)。\nNotes: 其实这里还有一类型错误，就是这会导致时间序列的乱序，怎么说呢，就是当指标无线放大时，在某一个点 scrap 的指标存储时间，大于了抓取周期，导致新指标存储早于旧指标，这种很容易出现在例如 Prometheus 的从内存到存储的那个点。\n如何控制控制指标的高基数增长 指标的无序扩张（高基数）是不可避免对监控系统产生非常大的影响（存储和性能），而为此引出了一个如何优化不断增长的指标就是控制高基数增长的关键部分，下面将从几个维度来阐释控制“高基数”问题的步骤\n第一步：高基数指标是否有价值？ 在任何优化方法的第一步都是去了解哪些指标给系统带来负面影响（这里指高基数），并且还需要确定这些指标中哪些指标是有价值的；所谓的有价值既，在仪表板、告警中是否有被使用。\n基于这些信息，我将根据基数问题与监控指标的价值分为四个象限：\n高价值，低成本：闲置、陈旧、很长时间没有新数据的 低价值，低成本：基本上没有什么影响，但是需要去考虑优化 低价值，高成本：可以考虑删除掉 Label 和 metric 高价值，高成本：你的指标是否过细化，是否需要重新设计 Label 或者聚合数据；或这类指标是否适合使用 Prometheus 这类时间序列 第二步：如何确定高基数指标 确定高基数指标包含3种方式\nPrometheus WEB UI 分析，2.14 版本之后 PromQL 分析 Prometheus API 分析 通常情况下，WEB UI 就可以满足需求了，通过路径 Prometheus UI -\u0026gt; Status -\u0026gt; TSDB Status -\u0026gt; Head Cardinality Stats。\n图：Prometheus WEB UI TOP 10 series 查看上图可见，http_server_requests_seconds_bucket 的信息，就这个 bucket 指标占总指标的 25% 左右，在加上其他的 几个 bucket，10个基本上占用了60%；在这里体现的高基数问题的指标，通常都是以 bucket 结尾的指标，而这些指标通常包含2个维度，会无线拉长成为高基数指标。如下面指标所示，通常由 le （标识每个 bucket 的上限，这可以确保可以定位到在一个时间范围内相应的请求指标有哪些）\ntext 1 2 3 4 5 6 7 8 9 10 11 12 http_server_requests_seconds_bucket{application=\u0026#34;map-common-api\u0026#34;,exception=\u0026#34;None\u0026#34;,method=\u0026#34;POST\u0026#34;,outcome=\u0026#34;SUCCESS\u0026#34;,status=\u0026#34;200\u0026#34;,uri=\u0026#34;/api/v1/map\u0026#34;,le=\u0026#34;+Inf\u0026#34;,} 71.0 http_server_requests_seconds_bucket{application=\u0026#34;map-common-api\u0026#34;,exception=\u0026#34;None\u0026#34;,method=\u0026#34;POST\u0026#34;,outcome=\u0026#34;SUCCESS\u0026#34;,status=\u0026#34;200\u0026#34;,uri=\u0026#34;/api/v1/maplist\u0026#34;,le=\u0026#34;0.001\u0026#34;,} 0.0 http_server_requests_seconds_bucket{application=\u0026#34;map-common-api\u0026#34;,exception=\u0026#34;None\u0026#34;,method=\u0026#34;POST\u0026#34;,outcome=\u0026#34;SUCCESS\u0026#34;,status=\u0026#34;200\u0026#34;,uri=\u0026#34;/api/v1/maplist\u0026#34;,le=\u0026#34;0.001048576\u0026#34;,} 0.0 http_server_requests_seconds_bucket{application=\u0026#34;map-common-api\u0026#34;,exception=\u0026#34;None\u0026#34;,method=\u0026#34;POST\u0026#34;,outcome=\u0026#34;SUCCESS\u0026#34;,status=\u0026#34;200\u0026#34;,uri=\u0026#34;/api/v1/maplist\u0026#34;,le=\u0026#34;0.001398101\u0026#34;,} 0.0 http_server_requests_seconds_bucket{application=\u0026#34;map-common-api\u0026#34;,exception=\u0026#34;None\u0026#34;,method=\u0026#34;POST\u0026#34;,outcome=\u0026#34;SUCCESS\u0026#34;,status=\u0026#34;200\u0026#34;,uri=\u0026#34;/api/v1/maplist\u0026#34;,le=\u0026#34;0.001747626\u0026#34;,} 0.0 http_server_requests_seconds_bucket{application=\u0026#34;map-common-api\u0026#34;,exception=\u0026#34;None\u0026#34;,method=\u0026#34;POST\u0026#34;,outcome=\u0026#34;SUCCESS\u0026#34;,status=\u0026#34;200\u0026#34;,uri=\u0026#34;/api/v1/maplist\u0026#34;,le=\u0026#34;0.002097151\u0026#34;,} 0.0 http_server_requests_seconds_bucket{application=\u0026#34;map-common-api\u0026#34;,exception=\u0026#34;None\u0026#34;,method=\u0026#34;POST\u0026#34;,outcome=\u0026#34;SUCCESS\u0026#34;,status=\u0026#34;200\u0026#34;,uri=\u0026#34;/api/v1/maplist\u0026#34;,le=\u0026#34;0.002446676\u0026#34;,} 0.0 http_server_requests_seconds_bucket{application=\u0026#34;map-common-api\u0026#34;,exception=\u0026#34;None\u0026#34;,method=\u0026#34;POST\u0026#34;,outcome=\u0026#34;SUCCESS\u0026#34;,status=\u0026#34;200\u0026#34;,uri=\u0026#34;/api/v1/maplist\u0026#34;,le=\u0026#34;0.002796201\u0026#34;,} 0.0 http_server_requests_seconds_bucket{application=\u0026#34;map-common-api\u0026#34;,exception=\u0026#34;None\u0026#34;,method=\u0026#34;POST\u0026#34;,outcome=\u0026#34;SUCCESS\u0026#34;,status=\u0026#34;200\u0026#34;,uri=\u0026#34;/api/v1/maplist\u0026#34;,le=\u0026#34;0.003145726\u0026#34;,} 0.0 http_server_requests_seconds_bucket{application=\u0026#34;map-common-api\u0026#34;,exception=\u0026#34;None\u0026#34;,method=\u0026#34;POST\u0026#34;,outcome=\u0026#34;SUCCESS\u0026#34;,status=\u0026#34;200\u0026#34;,uri=\u0026#34;/api/v1/maplist\u0026#34;,le=\u0026#34;0.003495251\u0026#34;,} 0.0 http_server_requests_seconds_bucket{application=\u0026#34;map-common-api\u0026#34;,exception=\u0026#34;None\u0026#34;,method=\u0026#34;POST\u0026#34;,outcome=\u0026#34;SUCCESS\u0026#34;,status=\u0026#34;200\u0026#34;,uri=\u0026#34;/api/v1/maplist\u0026#34;,le=\u0026#34;0.003844776\u0026#34;,} 0.0 http_server_requests_seconds_bucket{application=\u0026#34;map-common-api\u0026#34;,exception=\u0026#34;None\u0026#34;,method=\u0026#34;POST\u0026#34;,outcome=\u0026#34;SUCCESS\u0026#34;,status=\u0026#34;200\u0026#34;,uri=\u0026#34;/api/v1/maplist\u0026#34;,le=\u0026#34;0.004194304\u0026#34;,} 0.0 例如下面是生产环境中的一个高基数TOP10\nDC01 Top 10 series count by metric names\nName Count http_server_requests_seconds_bucket 1282158 lettuce_command_completion_seconds_bucket 782680 lettuce_command_firstresponse_seconds_bucket 782680 nginx_ingress_controller_response_size_bucket 99840 nginx_ingress_controller_request_duration_seconds_bucket 99840 http_server_requests_seconds 92695 nginx_ingress_controller_request_size_bucket 91520 nginx_ingress_controller_response_duration_seconds_bucket 74580 nginx_ingress_controller_bytes_sent_bucket 66560 node_ipvs_backend_weight 51173 DC02 Top 10 series count by metric names\nName Count http_server_requests_seconds_bucket 1073571 lettuce_command_firstresponse_seconds_bucket 755650 lettuce_command_completion_seconds_bucket 755650 http_server_requests_seconds 77775 nginx_ingress_controller_request_duration_seconds_bucket 67440 nginx_ingress_controller_response_size_bucket 67440 nginx_ingress_controller_request_size_bucket 61820 nginx_ingress_controller_response_duration_seconds_bucket 53052 node_ipvs_backend_connections_inactive 48796 node_ipvs_backend_connections_active 48796 至此可以看到实际上 http_server_requests_seconds_bucket 这一个指标占据了 prometheus 总指标的50%+；这种指标就会存在多个维度的扩张，URI, outcome, status,uri,le；假设我们有 100个 接口，\n在什么都不做的情况下，就多出了100个 series 如果状态码是存在变数的，假设为5，此时series 为500 在基于 outcome 的数量，此时一个指标的基数已经为1000 那么 le 将无限放大这个 metric 到 无穷 这样就可以定位，影响到 prometheus 存储于性能最大点在哪里，那么此时就需要考虑 le=\u0026ldquo;0.001048576\u0026rdquo; 和 le=\u0026ldquo;0.001\u0026rdquo; 有什么区别，以及此类的指标是否有必要存在？\n基于我们现有的监控报表来看 结合业务日志将访问信息的监控从 Prometheus 提到 ELK 这种日志层面监控 再通过 将 kube-apiserver 的 bucket 以及负载均衡在线/不在线Pod 这两个 指标清空，基本上可以满足70%左右的监控项删减 通常会删掉 le 这个标签，而不是 http_server_requests_seconds_bucket 的数据，但是也需要考虑，http_server_requests_seconds_bucket 指标是否有用到，如果没有用到， 又是高基数，那么可以删除 而其他的一些分析，可以很有效的定位到你需要优化的标签\nTop 10 label names with high memory usage Top 10 series count by label value pairs 通过 promQL 定位 job 查询 top 10 的 series topk(10, count by (__name__)({__name__=~\u0026quot;.+\u0026quot;})) sum(scrape_series_added) by (job) 通过 job Label 分析 series 增长 sum(scrape_samples_scraped) by (job) 通过 job Label 分析 series 总量 可以通过指标属于哪个 job\n第三步：发现那些指标没有在使用 Grafana Mimirtool 是一个开源的命令行工具， 它可以识别 Mimir、Prometheus 或 Prometheus 的存储中未在Dashboard、Alert 或 recording 中使用的指标。通过 Mimirtool 可以快速发现未使用的指标，并且做出操作\n优化监控指标 优化监控指标来解决高基数问题主要从以下维度进行\n增加采集间隔 Prometheus 的默认值为 scrape_interval: 15s，或 DPM (Data points Per minute) 4个，但是如果查询语句为 scrape_samples_scraped[1m] 那么可以考虑将这个 job 的 scrape_interval 增加为1m，这样15~60 可以减少近75%的存储成本。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 kube-state-metrics: namespaceOverride: \u0026#34;\u0026#34; rbac: create: true releaseLabel: true prometheus: monitor: enabled: true ## Scrape interval. If not set, the Prometheus default scrape interval is used. ## interval: \u0026#34;\u0026#34; 优化 histogram histrogram 是 Prometheus中一种更具有更复杂类型的监控指标，通常用于决定数据的精度，典型的例子就是上面提到的 http_server_requests_seconds_bucket 中的 le ，此时假设 le 代表请求毫秒，那么我们只需要决定你所需要的精度是哪些？例如，如果仅仅需要 1ms, 5ms, 10ms，那么指标 le 标签就控制为3，这样结合 URI 指标，那么这个 histogram 是有限的\nyaml 1 2 3 4 5 6 7 8 9 # drop all metric series ending with _bucket and where le=\u0026#34;0.1xxx\u0026#34; - source_labels: [__name__, le] separator: _ regex: \u0026#34;.+_bucket_(0.1+)\u0026#34; action: \u0026#34;drop\u0026#34; # Object labels: __name__: http_server_requests_seconds_bucket le: 0.114421 这里可以通过 promlabs 来测试你的规则是否是成功的 [2]\n删除不需要的标签 对于一些指标，删除了未使用的标签后，反而会使这个指标变得没有意义，并且使这个指标变得序列重复，这个时候可以完整删除这个指标\n例如在下面的示例中，第一个示例可以安全地删除 ip 标签，因为其余系列都是唯一的。但在第二个示例中，如果删除 ip 标签将产生重复的时间序列，Prometheus 将删除这些时间序列。my_metric_total在此示例中，Prometheus 将接收具有相同时间戳的值 1、3 和 7，并将丢弃其中的 2 个数据点。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # You can drop ip label, remaining series are still unique my_metric_total{env=“dev”, ip=“1.1.1.1\u0026#34;} 12 my_metric_total{env=“tst”, ip=“1.1.1.1\u0026#34;} 14 my_metric_total{env=“prd”, ip=“1.1.1.1\u0026#34;} 18 #Remaining values after dropping ip label my_metric_total{env=“dev”} 12 my_metric_total{env=“tst”} 14 my_metric_total{env=“prd”} 18 # You can not drop ip label, remaining series are not unique my_metric_total{env=“dev”, ip=“1.1.1.1\u0026#34;} 1 my_metric_total{env=“dev”, ip=“3.3.3.3\u0026#34;} 3 my_metric_total{env=“dev”, ip=“5.5.5.5\u0026#34;} 7 #Remaining values after dropping ip label are not unique my_metric_total{env=“dev”} 1 my_metric_total{env=“dev”} 3 my_metric_total{env=“dev”} 7 如果无法控制删除标签将导致重复序列，通过 Prometheus sum、avg、min、max等函数可以保留聚合数据，同时删除单个系列。在下面的示例中，我们使用 sum 函数来存储聚合指标，从而允许我们删除单个时间序列。\ntext 1 2 3 4 5 6 7 8 9 # sum by env my_metric_total{env=\u0026#34;dev\u0026#34;, ip=\u0026#34;1.1.1.1\u0026#34;} 1 my_metric_total{env=\u0026#34;dev\u0026#34;, ip=\u0026#34;3.3.3.3\u0026#34;} 3 my_metric_total{env=\u0026#34;dev\u0026#34;, ip=\u0026#34;5.5.5.5\u0026#34;} 7 # Recording rule sum by(env) (my_metric_total{}) my_metric_total{env=\u0026#34;dev\u0026#34;} 11 使用聚合组 例如对于 *_seconds_bucket 类的指标, 通常需要的是一些高纬度的指标，那么这些指标可以通过 recording rules 进行记录和存储\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 groups: - interval: 3m name: kube-apiserver-availability.rules rules: - expr: \u0026gt;- avg_over_time(code_verb:apiserver_request_total:increase1h[30d]) * 24 * 30 record: code_verb:apiserver_request_total:increase30d - expr: \u0026gt;- sum by (cluster, code, verb) (increase(apiserver_request_total{job=\u0026#34;apiserver\u0026#34;,verb=~\u0026#34;LIST|GET|POST|PUT|PATCH|DELETE\u0026#34;,code=~\u0026#34;2..\u0026#34;}[1h])) record: code_verb:apiserver_request_total:increase1h - expr: \u0026gt;- sum by (cluster, code, verb) (increase(apiserver_request_total{job=\u0026#34;apiserver\u0026#34;,verb=~\u0026#34;LIST|GET|POST|PUT|PATCH|DELETE\u0026#34;,code=~\u0026#34;5..\u0026#34;}[1h])) record: code_verb:apiserver_request_total:increase1h 最后 drop 掉指标\nyaml 1 2 3 4 write_relabel_configs: - source_labels: [__name__] regex: \u0026#34;apiserver_request_duration_seconds_bucket\u0026#34; action: drop recording rules 是允许预先将经常计算的表达式的结果保存为一组新的时间序列的，这种情况下查询的成本会比每次直接查询原始的表达式要快许多，并且在聚合后，可以将原来的指标删掉\n优化后每个块的大小\n优化后的每个块的大小\nReference ​[1] What are cardinality spikes and why do they matter?\n​[2] How to manage high cardinality metrics in Prometheus and Kubernetes\n​[3] 精简Prometheus指标减少资源占用\n​[4] What are cardinality spikes and why do they matter?\n​[5] Containing your Cardinality\n","permalink":"https://www.161616.top/impove-prometheus-performance/","summary":"背景 对于整个 Kubernetes 集群来说，随着业务不断地打磨，新增指标，那么对于 Prometheus 特性来说，那么内存 与 存储的使用势必是增加。这是对于存储压力是很重的，通常情况下，使用 Prometheus，都会是用于 Kubernetes 集群中，而 应用于 Kubernetes 集中的存储势必是 PVC 之类的网络存储。\n这种场景中，我将尝试拆解如何分析和配置 Prometheus 以显著的减少其资源使用并解决高基数问题\n高基数 基数 (cardinality) 通俗来说是一个集合中的元素数量 [1] 基数的来源通常为：\nlabel 的数量 series(指标) 的数量 时间：label 或者 series 随时间而流失或增加，通常是增加 那么这么看来高基数就是，label, series, 时间这三个集合的笛卡尔积，那么高基数的情况就很正常了。\n而高基数带来的则是 Prometheus 资源使用，以及监控的性能。下图是 Grafana Lab 提到的一张图，很好的阐述了高基数这个问题\n图：Prometheus中的基数 Source：https://grafana.com/blog/2022/02/15/what-are-cardinality-spikes-and-why-do-they-matter\n如图所示：一个指标 server_responses 他的 label 存在两个 status_code 与 environment ，这代表了一个集合，那他的 label value 是 1~5xx，这个指标的笛卡尔积就是10。\n那么此时存在一个问题，如何能定位 基数高不高，Grafana Lab 给出了下面的数据 [1]，但是我不清楚具体的来源或者如何得到的这些值。也就是 label:value\n低基数：1: 5 标准基数：1: 80 高基数：1: 10000 为什么指标会指数级增长 在以 Kubernetes 为基础的架构中，随着抽象级别的提高（通常为Pod, Label, 以及更多抽象的拓扑），指标的时间序列也越来越多。因为在这种基础架构中，在传统架构中运行的一个应用的单个裸机，被许多运行分散在许多不同节点上的许多不同微服务的 Pod 所取代。在这些抽象层中的每一个都需要一个标签，以便可以唯一地标识它们，并且这些组件中的每一个都会生成自己的指标，从而创建其独特的时间序列集。","title":"我在Prometheus监控中高基数问题中的优化之路"},{"content":"场景 在配置代理后，GET 请求的变量全部失效，配置如下\ntext 1 2 3 location /fw { proxy_pass http://127.0.0.1:2952; } 我的需求是，/fw/ 的都发往 2952端口，但实际情况是404，原因为“在没有指定 URI 的情况下，在1.12版本后会传递原有的URI” 这时会导致一个404错误，因为我的后端接口本身就是 /fw/xxx/ 会出现重复\n接下来做了一个变量传递\ntext 1 2 3 location ~* /fw/(?\u0026lt;section\u0026gt;.*) { proxy_pass http://127.0.0.1:2952/fw/$section; } 这时存在一个问题，就是 GET 请求的变量无法传递过去\n解决 nginx 官方给出一个样例，说明了，存在某种情况下，nginx 不会确定请求 URI 中的部分参数\n使用正则表达式时 在 localtion 名称内 例如，在这个场景下，proxy_pass 就会忽略原有的请求的URI，而将拼接后的请求转发\ntext 1 2 3 4 location /name/ { rewrite /name/([^/]+) /users?name=$1 break; proxy_pass http://127.0.0.1; } 那么这服务我遇到的问题，nginx官方给出了使用方式\n当在 proxy_pass 中需要变量，可以使用 $request_uri;\n另外也可以使用 $is_args$args 参数 来保证原有的请求参数被传递\ntext 1 2 3 location ~* /fw/(?\u0026lt;section\u0026gt;.*) { proxy_pass http://127.0.0.1:2952/fw/$section$is_args$args; } $is_args\n“?” if a request line has arguments, or an empty string otherwise\n$args\narguments in the request line\nReference\nAlphabetical index of variables\n","permalink":"https://www.161616.top/nginx-proxy_pass/","summary":"场景 在配置代理后，GET 请求的变量全部失效，配置如下\ntext 1 2 3 location /fw { proxy_pass http://127.0.0.1:2952; } 我的需求是，/fw/ 的都发往 2952端口，但实际情况是404，原因为“在没有指定 URI 的情况下，在1.12版本后会传递原有的URI” 这时会导致一个404错误，因为我的后端接口本身就是 /fw/xxx/ 会出现重复\n接下来做了一个变量传递\ntext 1 2 3 location ~* /fw/(?\u0026lt;section\u0026gt;.*) { proxy_pass http://127.0.0.1:2952/fw/$section; } 这时存在一个问题，就是 GET 请求的变量无法传递过去\n解决 nginx 官方给出一个样例，说明了，存在某种情况下，nginx 不会确定请求 URI 中的部分参数\n使用正则表达式时 在 localtion 名称内 例如，在这个场景下，proxy_pass 就会忽略原有的请求的URI，而将拼接后的请求转发\ntext 1 2 3 4 location /name/ { rewrite /name/([^/]+) /users?name=$1 break; proxy_pass http://127.0.0.1; } 那么这服务我遇到的问题，nginx官方给出了使用方式\n当在 proxy_pass 中需要变量，可以使用 $request_uri;\n另外也可以使用 $is_args$args 参数 来保证原有的请求参数被传递","title":"踩坑nginx proxy_pass GET 参数传递"},{"content":"什么是容器中的多进程管理 在容器中的主进程 (main running process) 是指 Dockerfile中 ENTRYPOINT 或 CMD 指定运行的命令，通常情况下一个进程（服务）为一个容器；也存在一种场景，就是主进程会fork多个子进程，例如nginx，不过这种多进程通常为nginx主进程进行管理。而一些场景下，我们的业务本身就需要多个启用独立的多个进程。\n在Docker官方提到了在容器中运行多个服务的方式，官方提出，应该避免这种情况\nbut to get the most benefit out of Docker, avoid one container being responsible for multiple aspects of your overall application.\n但也给出了如何管理多进程的一种思路，\nUse a wrapper script Use Bash job controls Use a process manager 下面就通过官方给出的这三种方式阐述容器中的多进程管理\nUse a wrapper script 对于使用脚本来管理多进程来说，本质上是可以实现多进程的启动，但是你没法去监控(管理)多个进程的运行时，例如 Nginx + PHP 模式， PHP或nginx全部挂掉，只要脚本还在运行，那么这个容器的生命周期还是处于Running\nUse Bash job controls 这种模式是利用了Bash的后台模式进行短暂的切换进程，但有些镜像不提供Bash这时应该怎么办\nUse a process manager 进程管理器，通常情况下大家想到的就是顶顶大名的 supervisor 和 systemd，但这两个程序运行的环境十分苛刻，例如 supervisor 是Python开发的程序，运行需要依赖 Python；而 systemd 的运行条件更为苛刻，例如需要额外运行dbus-damon进行注册到dbus总线之上，这种进程管理器可能运行的进程比我们要管理的进程都要多。在这种场景下，有一个部署简单，配置简单，无依赖的轻量级容器多进程管理器 s6-overlay\ns6-overlay s6-overlay 一组脚本，只需要简单解压就可以使现有的 Docker 镜像通过将 s6 用作容器的 pid 1 和服务的来管理多个进程。\ns6-overlay 包含两个组件，s6-overlay-noarch.tar.xz 与 s6-overlay-x86_64.tar.xz\nnoarch 包含了一些脚本，是s6运行的所必须有的一个组件，他包含了 /init 作为 pid 为1 的进程 x86 是作为 x86系统下运行 s6 所需要的 所有二进制文件 编写服务启动脚本 需要在 /etc/s6-overlay/s6-rc.d/ 与 /etc/services.d/ 中配置你要启动的app，例如\nbash 1 /etc/services.d/nginx/run run则代表启动的命令\nbash 1 2 #!/command/execlineb -P nginx -g \u0026#34;daemon off;\u0026#34; 除上述提到的内容外，还需一个 type 来指明 启动的模式\nlongrun 运行为daemon模式被s6进行管理 oneshot 类似一个脚本，但通过s6-rc进行管理，类似于初始化任务 所以你需要在 /etc/s6-overlay/s6-rc.d/myapp/type 中定义其 type 文件，这个文件内填写这两种类型的文字即可\n到这里完成了一个基本的进程的配置，例如还有 finish 脚本，当在失败时执行的\nS6 init 的阶段 s6官方对init阶段省略了用户不需要关心的一个阶段后，为 3 个阶段\n初始化阶段 (initialization)，这里是内核启动的第一个用户态进程，该阶段作为init唯一的持久进程 巡航阶段 (cruising)，这个阶段init负责启动与维护其他进程，比如运行s6系列，init 的职责是清除孤儿进程并监督进程，同时允许管理员添加或删除服务，例如上面的 longrun 与 oneshot 类的服务，都是在这个阶段被启动 关闭阶段 (shutdown)，在此阶段结束时，所有进程都将被终止 发送 TERM 信号 到遗留的 longrun 服务，如果需要将等待结束后退出 有序的关闭用户 s6-rc 运行 finalization 脚本 向进程发送 TERM signal，最终不会留下任何的进程 sleep一阵，允许驻留的进程退出完 发送 KILL 信号，退出所有进程，这时容器退出 S6的安装 S6的安装很简单，步骤只需要如下几步：\n只需要下载对应的两个tar包 将 init 作为pid为1的进程 准备 installiation阶段 和 finalization 阶段的脚本 复制到对应路径内就可以正常启动了 finalization 通常使用场景为：当你的程序在退出时存在一些特定的结束命令的场景，官方给出的通常是用于进程结束后的清理动作\nNote that in general, finish scripts should only be used for local cleanups after a daemon dies. If a service is so important that the container needs to stop when it dies, we really recommend running it as the CMD.\n下面是一个完整的使用了 s6 的多进程容器的 Dockerfile\ndocker 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 FROM nginx:1.20 AS runner WORKDIR /uranus ARG S6_OVERLAY_VERSION=3.1.5.0 ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-x86_64.tar.xz /tmp RUN apt update \u0026amp;\u0026amp; apt install xz-utils procps iproute2 -y \u0026amp;\u0026amp; \\ tar -Jxpf /tmp/s6-overlay-x86_64.tar.xz -C / \u0026amp;\u0026amp; \\ tar -Jxpf /tmp/s6-overlay-noarch.tar.xz -C / \u0026amp;\u0026amp; \\ rm -f /tmp/s6-overlay-x86_64.tar.xz \u0026amp;\u0026amp; \\ rm -f /tmp/s6-overlay-noarch.tar.xz ENTRYPOINT [\u0026#34;/init\u0026#34;] RUN mkdir /etc/services.d/ COPY --from=builder /uranus/_output/firewalld-gateway ./bin/ COPY --from=builder /uranus/firewalld-gateway.toml . COPY --from=builder /uranus/dist /var/run/nginx/ COPY --from=builder /uranus/uranus.nginx.conf /etc/nginx/conf.d/ COPY --from=builder /uranus/s6/ /etc/s6-overlay/s6-rc.d/ COPY --from=builder /uranus/s6/ /etc/services.d/ ENV PATH \u0026#34;$PATH:/uranus/bin\u0026#34; RUN firewalld-gateway --sql-driver=sqlite --migration \u0026amp;\u0026amp; \\ rm -f /etc/nginx/conf.d/default.conf \u0026amp;\u0026amp; \\ echo \u0026#34;longrun\u0026#34; \u0026gt; /etc/s6-overlay/s6-rc.d/nginx/type \u0026amp;\u0026amp; \\ echo \u0026#34;longrun\u0026#34; \u0026gt; /etc/s6-overlay/s6-rc.d/uranus/type \u0026amp;\u0026amp; \\ mkdir -pv /etc/s6-overlay/s6-rc.d/uranus/contents.d \u0026amp;\u0026amp; \\ mkdir -pv /etc/s6-overlay/s6-rc.d/nginx/contents.d #CMD [ \u0026#34; /command/s6-svscan\u0026#34;, \u0026#34;/etc/services.d\u0026#34; ] VOLUME [\u0026#34;/uranus\u0026#34; ] EXPOSE 2953/tcp 在容器中进程内可以看出对应进程图 s6init 作为所有进程的父进程管理着supervise，之后管理者你需要管理的进程；如果进程异常，他会不断地拉起对应的进程，当然，如果是启动参数错误问题，那么永远不会被拉起，当然容器是出于 Running，这时就需要自行做服务检测\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $ pstree s6-svscan-+-s6-supervise---s6-linux-init-s |-s6-supervise---s6-ipcserverd |-3*[s6-supervise] |-s6-supervise---firewalld-gatew---5*[{firewalld-gatew}] `-s6-supervise---nginx---4*[nginx] $ ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 May18 ? 00:00:00 /package/admin/s6/command/s6-svscan -d4 -- /run/service root 16 1 0 May18 ? 00:00:00 s6-supervise s6-linux-init-shutdownd root 18 16 0 May18 ? 00:00:00 /package/admin/s6-linux-init/command/s6-linux-init-shutdownd -c /run/s6/basedir -g 3000 -C -B root 25 1 0 May18 ? 00:00:00 s6-supervise s6rc-oneshot-runner root 26 1 0 May18 ? 00:00:00 s6-supervise s6rc-fdholder root 27 1 0 May18 ? 00:00:00 s6-supervise uranus root 28 1 0 May18 ? 00:00:00 s6-supervise nginx root 34 25 0 May18 ? 00:00:00 /package/admin/s6/command/s6-ipcserverd -1 -- /package/admin/s6/command/s6-ipcserver-access -v0 -E -l0 -i data/rules -- /package/admin/s6/command/s6-sudod -t 30000 -- /package/admin/s6-rc/command/s6-rc-oneshot-run -l . root 69 1 0 May18 ? 00:00:00 s6-supervise uranus root 70 1 0 May18 ? 00:00:00 s6-supervise nginx root 71 69 1 May18 ? 00:36:40 /uranus/bin/firewalld-gateway -v 5 --sql-driver=sqlite --config=/uranus/firewalld-gateway.toml root 72 70 0 May18 ? 00:00:00 nginx: master process nginx -g daemon off; nginx 74 72 0 May18 ? 00:00:00 nginx: worker process nginx 75 72 0 May18 ? 00:00:00 nginx: worker process nginx 76 72 0 May18 ? 00:00:00 nginx: worker process nginx 77 72 0 May18 ? 00:00:00 nginx: worker process root 83 0 1 04:17 pts/0 00:00:00 bash root 90 83 0 04:18 pts/0 00:00:00 ps -ef Reference How to run s6-svscan as process 1\nUsage\n","permalink":"https://www.161616.top/multi-process-management/","summary":"什么是容器中的多进程管理 在容器中的主进程 (main running process) 是指 Dockerfile中 ENTRYPOINT 或 CMD 指定运行的命令，通常情况下一个进程（服务）为一个容器；也存在一种场景，就是主进程会fork多个子进程，例如nginx，不过这种多进程通常为nginx主进程进行管理。而一些场景下，我们的业务本身就需要多个启用独立的多个进程。\n在Docker官方提到了在容器中运行多个服务的方式，官方提出，应该避免这种情况\nbut to get the most benefit out of Docker, avoid one container being responsible for multiple aspects of your overall application.\n但也给出了如何管理多进程的一种思路，\nUse a wrapper script Use Bash job controls Use a process manager 下面就通过官方给出的这三种方式阐述容器中的多进程管理\nUse a wrapper script 对于使用脚本来管理多进程来说，本质上是可以实现多进程的启动，但是你没法去监控(管理)多个进程的运行时，例如 Nginx + PHP 模式， PHP或nginx全部挂掉，只要脚本还在运行，那么这个容器的生命周期还是处于Running\nUse Bash job controls 这种模式是利用了Bash的后台模式进行短暂的切换进程，但有些镜像不提供Bash这时应该怎么办\nUse a process manager 进程管理器，通常情况下大家想到的就是顶顶大名的 supervisor 和 systemd，但这两个程序运行的环境十分苛刻，例如 supervisor 是Python开发的程序，运行需要依赖 Python；而 systemd 的运行条件更为苛刻，例如需要额外运行dbus-damon进行注册到dbus总线之上，这种进程管理器可能运行的进程比我们要管理的进程都要多。在这种场景下，有一个部署简单，配置简单，无依赖的轻量级容器多进程管理器 s6-overlay","title":"Docker中的多进程管理 s6-overlay"},{"content":"基本概念 从版本 2.4 开始，如果包包含.config.yaml ，可以使用包配置，包配置可以使用配置文件来设置包中资源通用的值，例如 API 凭证、连接详细信息、限制和阈值。这些值在运行时可供操作和传感器使用。\n包配置和 Action 参数之间的区别在于，配置通常包含包中所有资源通用的值，并且很少更改。动作参数是随每个动作调用动态提供的，并且可能会发生变化 - 例如，它们可能来自映射某些输入事件的规则。\n包配置遵循基础架构即代码方法，并存储在特殊目录中的 YAML 格式文件中（默认情况下 /opt/stackstorm/configs）。每个包都为此配置文件定义自己的架构。\n配置 Schema 配置文件的结构是一个 YAML 格式的文件，它定义了该包的配置文件。该配置由包作者自行编写，包含有关每个可用配置项的信息，例如名称, Secret等）。该文件已命名 config.schema.yaml 并位于包目录 /opt/stackstorm/packs/\u0026lt;mypack\u0026gt; 的根目录中。\n这是一个示例包配置文件：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 --- api_key: description: \u0026#34;API key\u0026#34; type: \u0026#34;string\u0026#34; required: true api_secret: description: \u0026#34;API secret\u0026#34; type: \u0026#34;string\u0026#34; secret: true required: true region: description: \u0026#34;API region to use\u0026#34; type: \u0026#34;string\u0026#34; required: true default: \u0026#34;us-east-1\u0026#34; private_key_path: description: \u0026#34;Path to the private key file to use\u0026#34; type: \u0026#34;string\u0026#34; required: false 在该示例中，配置文件由 4 项 配置组成 (api_key, api_secret, region, private_key_path)\n注，api_secret 被标注为 secret，这意味着如果使用动态值，则该值将加密存储在数据存储中。\n除了上面所示的“平面”配置之外，模式还支持嵌套对象。例如：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 --- consumer_key: description: \u0026#34;Your consumer key.\u0026#34; type: \u0026#34;string\u0026#34; required: true secret: true consumer_secret: description: \u0026#34;Your consumer secret.\u0026#34; type: \u0026#34;string\u0026#34; required: true secret: true access_token: description: \u0026#34;Your access token.\u0026#34; type: \u0026#34;string\u0026#34; required: true secret: true access_token_secret: description: \u0026#34;Your access token secret.\u0026#34; type: \u0026#34;string\u0026#34; required: true secret: true sensor: description: \u0026#34;Sensor specific settings.\u0026#34; type: \u0026#34;object\u0026#34; required: false additionalProperties: false properties: device_uuids: type: \u0026#34;array\u0026#34; description: \u0026#34;A list of device UIDs to poll metrics for.\u0026#34; items: type: \u0026#34;string\u0026#34; required: false 在该示例中，配置文件可以包含一个 sensor 项目，该项目是具有单个 device_uuids 属性的对象。\n配置文件 配置文件是 YAML 格式的文件，该文件可以包含 “静态” 或 “动态” 值。配置文件已命名并位于 /opt/stackstorm/configs/\u0026lt;pack name\u0026gt;.yaml 目录中。文件所有权应该是 st2:st2\n例如，对于名为 libcloud 的包 配置文件位于 /opt/stackstorm/configs/libcloud.yaml。\nyaml 1 2 3 4 5 --- api_key: \u0026#34;some_api_key\u0026#34; api_secret: \u0026#34;{{st2kv.user.api_secret}}\u0026#34; # user-scoped configuration value which is also a secret as declared in config schema region: \u0026#34;us-west-1\u0026#34; private_key_path: \u0026#34;{{st2kv.system.private_key_path}}\u0026#34; # global datastore value 配置文件不会在 run-time时动态读取，必须先进行注册，然后将值加载到 StackStorm DB 中。它们的注册方式与其他资源相同，通过运行 st2ctl reload / st2-register-content 脚本来注册。对于config，您需要使用 \u0026ndash;register-configs flag 运行此脚本：\nbash 1 2 3 sudo st2ctl reload --register-configs # Or sudo st2-register-content --register-configs 在使用上述命令加载和注册 config 时，将根据 shema 验证配置文件中的静态值。如果 schema 不存在，则不执行验证。\n注：仅验证配置中的静态值。动态值（使用 Jinja 表示法引用数据存储中的值的值）在运行时解析，因此无法在 register/load 阶段验证它们。\n静态配置值 静态配置值是从配置文件加载并按原样使用的值。\n动态配置值 动态配置值提供了额外的灵活性，并包括对用户范围的数据存储值的支持。当您想要根据调用操作的用户使用不同的配置值时，这非常有用。\n动态配置值是包含 Jinja 模板表达式的值。该模板表达式在运行时进行计算，并解析为 数据存储区 值的名称（Keys）。然后，该数据存储值将用作配置值。\n注：目前只有字符串（字符串类型）支持动态配置值。\n在config中，动态配置值的引用如下：\nyaml 1 2 3 --- api_secret: \u0026#34;{{st2kv.user.api_secret}}\u0026#34; # user-scoped configuration value which is also a secret as declared in config schema private_key_path: \u0026#34;{{st2kv.system.private_key_path}}\u0026#34; # global datastore value api_secret 是一个用户范围的动态配置值，这意味着 user 部分将被触发操作执行的用户的用户名替换。\n动态配置值存储在“数据存储”中，并使用 CLI 或 API 进行配置。\n如果某个值在配置 schema 中被标记为加密，则需要将其加密存储在数据存储中。设置该值时， 应使用 \u0026ndash;encrypt 标志，如下所示：\nbash 1 st2 key set api_secret \u0026#34;my super secret api secret\u0026#34; --scope=user --encrypt 在上面的示例中，private_key_path 常规动态配置值，这意味着 private_key_path 将从数据存储中加载与此键对应的数据存储项。在这种情况下，使用命令行将该值设置如下：\nyaml 1 st2 key set private_key_path \u0026#34;/home/myuser/.ssh/my_private_rsa_key\u0026#34; 配置的加载和配置动态值解析 配置文件在注册时加载。动态值在运行时解析。对于传感器，这是传感器容器为传感器实例生成子进程的时间，对于 Action，这是执行 Action 的时间。\n解析和加载用户范围的配置值时，触发操作 Action 的经过身份验证的用户将用作解析值时的上下文。\n在解析和加载用户范围配置值时，使用认证后的用户触发操作 Action 的来作为上下文来解析该值。\n命令行配置动态值 可以使用 st2 key 命令集与其他数据存储项相同的方式操作动态配置值，动态配置值包含“用户范围的动态配置值” 与 “常规动态配置值”\n配置常规动态配置值 常规动态配置值可以由管理员或任何用户配置：\nbash 1 2 3 4 st2 key set \u0026lt;key name\u0026gt; \u0026lt;key value\u0026gt; # For example st2 key set private_key_path \u0026#34;/home/myuser/.ssh/my_private_rsa_key\u0026#34; 要查看配置值可以使用命令 st2 key get\nbash 1 2 3 4 st2 key get \u0026lt;key name\u0026gt; # For example st2 key get private_key_path 注意：默认情况下 Secret 类型的值将被屏蔽。\n配置用户范围的动态配置值 动态配置值可以由每个用户自己配置，也可以由管理员为任何可用的系统用户配置：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 st2 key set --scope=user [--encrypt] \u0026lt;key name\u0026gt; \u0026lt;key value\u0026gt; # For example (authenticated as \u0026#34;user1\u0026#34;) st2 key set --scope=user default_region \u0026#34;us-west-1\u0026#34; st2 key set --scope=user --encrypt api_secret user1_api_secret # For example (authenticated as \u0026#34;user2\u0026#34;) st2 key set --scope=user default_region \u0026#34;us-east-1\u0026#34; st2 key set --scope=user --encrypt api_secret user2_api_secret # For example (authenticated as administrator, setting a value for \u0026#34;user1\u0026#34; and \u0026#34;user2\u0026#34;) st2 key set --scope=user --user=user1 default_region \u0026#34;us-west-1\u0026#34; st2 key set --scope=user --user=user2 default_region \u0026#34;us-east-1\u0026#34; 查看值命令时用户只能看到他们自己的值，管理员可以看到所有值，默认情况下秘密被屏蔽\n配置的使用 配置可以在 Packs 中的 Python 脚本中全局调用，但需要注意的是，Action 与 Sensor 的 Python 脚本调用有些微差异。\n例如我们有一个 pack，名为 config_example，那么配置文件应定义为 config_example.yaml\nbash 1 2 3 4 5 --- if_host: \u0026#39;10.0.0.26\u0026#39; if_port: 8086 if_username: \u0026#39;ifadm\u0026#39; if_password: \u0026#39;xxxxxxx\u0026#39; 在 /opt/stackstorm/pack/{packs_name}/actions/ 目录下的 Python 脚本可使用如下方法调用。\npy 1 2 3 4 host=self.config[\u0026#39;if_host\u0026#39;] port=self.config[\u0026#39;if_port\u0026#39;] username=self.config[\u0026#39;if_username\u0026#39;] password=self.config[\u0026#39;if_password\u0026#39;] 在 /opt/stackstorm/pack/crontab/sensor/ 中的 Python 脚本，则必需使用下列方法调用。\nbash 1 2 3 4 host=self._config[\u0026#39;if_host\u0026#39;] port=self._config[\u0026#39;if_port\u0026#39;] username=self._config[\u0026#39;if_username\u0026#39;] password=self._config[\u0026#39;if_password\u0026#39;] Reference ​[1] Pack Configuration\n","permalink":"https://www.161616.top/stackstorm-pack-configuaration/","summary":"基本概念 从版本 2.4 开始，如果包包含.config.yaml ，可以使用包配置，包配置可以使用配置文件来设置包中资源通用的值，例如 API 凭证、连接详细信息、限制和阈值。这些值在运行时可供操作和传感器使用。\n包配置和 Action 参数之间的区别在于，配置通常包含包中所有资源通用的值，并且很少更改。动作参数是随每个动作调用动态提供的，并且可能会发生变化 - 例如，它们可能来自映射某些输入事件的规则。\n包配置遵循基础架构即代码方法，并存储在特殊目录中的 YAML 格式文件中（默认情况下 /opt/stackstorm/configs）。每个包都为此配置文件定义自己的架构。\n配置 Schema 配置文件的结构是一个 YAML 格式的文件，它定义了该包的配置文件。该配置由包作者自行编写，包含有关每个可用配置项的信息，例如名称, Secret等）。该文件已命名 config.schema.yaml 并位于包目录 /opt/stackstorm/packs/\u0026lt;mypack\u0026gt; 的根目录中。\n这是一个示例包配置文件：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 --- api_key: description: \u0026#34;API key\u0026#34; type: \u0026#34;string\u0026#34; required: true api_secret: description: \u0026#34;API secret\u0026#34; type: \u0026#34;string\u0026#34; secret: true required: true region: description: \u0026#34;API region to use\u0026#34; type: \u0026#34;string\u0026#34; required: true default: \u0026#34;us-east-1\u0026#34; private_key_path: description: \u0026#34;Path to the private key file to use\u0026#34; type: \u0026#34;string\u0026#34; required: false 在该示例中，配置文件由 4 项 配置组成 (api_key, api_secret, region, private_key_path)","title":"StackStorm自动化 - 包配置"},{"content":"vue项目部署在裸机Linux上运行正常，部署在docker中nginx出现下列错误\ntext 1 Nginx \u0026#34;rewrite or internal redirection cycle while internally redirecting to \u0026#34;/index.html\u0026#34; 表现在用户界面 500 Internal Server Error\n原因：nginx配置路径不对，改成正确的后恢复\n","permalink":"https://www.161616.top/ngx-in-docker-500/","summary":"vue项目部署在裸机Linux上运行正常，部署在docker中nginx出现下列错误\ntext 1 Nginx \u0026#34;rewrite or internal redirection cycle while internally redirecting to \u0026#34;/index.html\u0026#34; 表现在用户界面 500 Internal Server Error\n原因：nginx配置路径不对，改成正确的后恢复","title":"解决nginx在docker中报错 [rewrite or internal redirection cycle while internally redirecting to \"/index.html]"},{"content":"Deployment Recommended Cluster Architecture - rancher Hardware recommendations - etcd A simple shell script for backing up etcd v2 and v3 datastores Considerations for large clusters - kubernetes cluster Operating etcd clusters for Kubernetes Recommended performance and scalability practices Binary deploy script pure shell Managing Kubernetes Traffic with F5 NGINX Eraser - Cleaning up Images from Kubernetes Nodes 对于不同规模的 Kubernetes 集群所需要的 etcd 规模推荐 datree: allowing you to scan your k8s configs during development Performance etcd: getting 30% more write/s 蚂蚁集团万级规模 K8s 集群 etcd 高可用建设之路 各组件参数配置调优 万级K8s集群背后etcd稳定性及性能优化实践 K8s 集群稳定性：LIST 请求源码分析、性能评估与大规模基础服务部署调优 Comparing comparing Kubernetes ingress controller Troubleshooting 一次Etcd集群宕机引发的思考 Stern: allows you to tail multiple pods on Kubernetes Diagnosis Kubernetes 自动化诊断工具：k8sgpt-operator ktop: displays useful metrics information about kubernetes cluster Dashboard KDash - A fast and simple dashboard for Kubernetes Security Kubernetes 加固指南 Popeye 扫描实时 Kubernetes 集群并报告已部署资源和配置的潜在问题 Test kube-monkey It randomly deletes Kubernetes (k8s) pods in the cluster encouraging and validating the development of failure-resilient services. Study KWOK (Kubernetes With Out Kubelet) Backup etcd 备份与恢复工具 ","permalink":"https://www.161616.top/awesome-kubernetes/","summary":"Deployment Recommended Cluster Architecture - rancher Hardware recommendations - etcd A simple shell script for backing up etcd v2 and v3 datastores Considerations for large clusters - kubernetes cluster Operating etcd clusters for Kubernetes Recommended performance and scalability practices Binary deploy script pure shell Managing Kubernetes Traffic with F5 NGINX Eraser - Cleaning up Images from Kubernetes Nodes 对于不同规模的 Kubernetes 集群所需要的 etcd 规模推荐 datree: allowing you to scan your k8s configs during development Performance etcd: getting 30% more write/s 蚂蚁集团万级规模 K8s 集群 etcd 高可用建设之路 各组件参数配置调优 万级K8s集群背后etcd稳定性及性能优化实践 K8s 集群稳定性：LIST 请求源码分析、性能评估与大规模基础服务部署调优 Comparing comparing Kubernetes ingress controller Troubleshooting 一次Etcd集群宕机引发的思考 Stern: allows you to tail multiple pods on Kubernetes Diagnosis Kubernetes 自动化诊断工具：k8sgpt-operator ktop: displays useful metrics information about kubernetes cluster Dashboard KDash - A fast and simple dashboard for Kubernetes Security Kubernetes 加固指南 Popeye 扫描实时 Kubernetes 集群并报告已部署资源和配置的潜在问题 Test kube-monkey It randomly deletes Kubernetes (k8s) pods in the cluster encouraging and validating the development of failure-resilient services.","title":"Awesome kubernetes"},{"content":"为什么去除polkit验证 在2021年询问过firewalld项目组，firewalld在dbus通讯时，会进行两部认证 policy kit 和 UID checking，正是因为这种情况，使得firewalld不能够通过TCP/IP连接，如果你需要连接，因为存在 UID checking ，这时会因为没有UID会报错。\nfirewalld needs to do some authorization on the dbus request. It currently tries two ways, in order of preference:\npolicy kit UID checking Neither of these are available over a TCP/IP dbus connection. [1]\n如何去除polkit 首选需要确定你的firewalld版本，例如Centos7系列，那么你的 firewalld 版本为 0.6.3，那么你需要修改的包为 python-firewall-0.6.3, 在 debian11 上 firewalld版本默认为 0.9.3，那么需要关注的版本为：python3-firewall_0.9.3\n在确定版本后直接从github仓库进行拉去修改就可以\nbash 1 2 git fetch v0.9.3 git checkout v0.9.3 对于 python-firewall-0.6.3 来说。直接注释掉 slip.dbus.polkit.require_auth 就可以了\npython 1 2 3 @slip.dbus.polkit.require_auth(config.dbus.PK_ACTION_POLICIES_INFO) @dbus_service_method(config.dbus.DBUS_INTERFACE_POLICIES, in_signature=\u0026#39;\u0026#39;, out_signature=\u0026#39;as\u0026#39;) 而对于新一些版本的场景下，可以在github上看到，他们移除了对 python-slip 的依赖，这将对去除polkit认证的步骤则有些许调整 [2]:\n首先需要完全移除修饰器 polkit.require_auth\n完全移除修饰器 @slip.dbus.polkit.enable_proxy\n由于对 slip.dbus 去除，那么需要注释掉 import slip.dbus*\n此时修改firewall server，去除基于slip的mainloop，改为旧版本形式\npython 1 2 3 4 5 6 mainloop = GLib.MainLoop() slip.dbus.service.set_mainloop(mainloop) mainloop.run() # 修改为 mainloop = GLib.MainLoop() mainloop.run() 为了兼容命令 firewall-cmd ，还需要将 FirewallClient 中使用 slip.dbus.xxx 的内容修改为 dbus.xxx\npython 1 2 3 4 5 6 7 8 9 try: self.bus = slip.dbus.SystemBus() # 修改为 self.bus = dbus.SystemBus() except dbus.exceptions.DBusException as e: raise FirewallError(errors.DBUS_ERROR, e.get_dbus_message()) else: print(\u0026#34;Not using slip.dbus\u0026#34;) 最后一步，根据你的发行版本进行打包安装即可\nTips：Redhat系列官方提供了rpm打包文件直接用就可以\ndebian control文件为：\ntext 1 2 3 4 5 6 7 Package: python3-firewall Version: 0.9.3 Architecture: amd64 Description: python3-firewall Maintainer: Cylon Chau \u0026lt;cylonchau@outlook.com\u0026gt; Section: comm Homepage: https://github.com/cylonchau/firewalld debian构建目录为根据原生包目录格式进行后见即可\ndbus配置 debian 系列 dbus 配置与 redhat 系列有略微差别，配置文件目录为\n主配置文件需要自行控制权限，必须为：/etc/dbus-1/system.d/org.freedesktop.PackageKit.conf 其他配置可以根据redhat 系列进行调整即可 最后感谢firewalld团队，issue回复超级快，如果你需要像使用阿里云安全组使用firewalld管理你公司的大量linux 防火墙，可以试试我的项目 github.com/cylonchau/firewalld-gateway，这是一个firewlld控制器，可以管理大量的firewalld主机\nReference ​[1] firewalld issue 851\n​[2] firewalld issue 793 drop dependency python-slip\n","permalink":"https://www.161616.top/firewalld-without-polkit/","summary":"为什么去除polkit验证 在2021年询问过firewalld项目组，firewalld在dbus通讯时，会进行两部认证 policy kit 和 UID checking，正是因为这种情况，使得firewalld不能够通过TCP/IP连接，如果你需要连接，因为存在 UID checking ，这时会因为没有UID会报错。\nfirewalld needs to do some authorization on the dbus request. It currently tries two ways, in order of preference:\npolicy kit UID checking Neither of these are available over a TCP/IP dbus connection. [1]\n如何去除polkit 首选需要确定你的firewalld版本，例如Centos7系列，那么你的 firewalld 版本为 0.6.3，那么你需要修改的包为 python-firewall-0.6.3, 在 debian11 上 firewalld版本默认为 0.9.3，那么需要关注的版本为：python3-firewall_0.9.3\n在确定版本后直接从github仓库进行拉去修改就可以\nbash 1 2 git fetch v0.9.3 git checkout v0.9.3 对于 python-firewall-0.6.3 来说。直接注释掉 slip.dbus.polkit.require_auth 就可以了","title":"firewalld去除polkit验证"},{"content":"如果不小心提交github提交错了，而 --amend 也不能修改提交者的信息，可以通过尝试下面的方式\nCheckout\nbash 1 git checkout --orphan \u0026lt;latest_branch\u0026gt; Add all the files\nbash 1 git add -A Commit the changes\nbash 1 git commit -am \u0026#34;commit message\u0026#34; Delete the branch\nbash 1 git branch -D main Rename the current branch to main\nbash 1 git branch -m main Finally, force update your repository\nbash 1 git push -f origin main 缺点是：所有该分支的提交记录都将被删除\nReference\nhow to delete all commit history in github? ","permalink":"https://www.161616.top/delete-github-commit/","summary":"如果不小心提交github提交错了，而 --amend 也不能修改提交者的信息，可以通过尝试下面的方式\nCheckout\nbash 1 git checkout --orphan \u0026lt;latest_branch\u0026gt; Add all the files\nbash 1 git add -A Commit the changes\nbash 1 git commit -am \u0026#34;commit message\u0026#34; Delete the branch\nbash 1 git branch -D main Rename the current branch to main\nbash 1 git branch -m main Finally, force update your repository\nbash 1 git push -f origin main 缺点是：所有该分支的提交记录都将被删除\nReference\nhow to delete all commit history in github?","title":"删除github上面的历史提交记录"},{"content":"deb 概述 deb包（.deb）是 Debian 和基于 Debian衍生操作系统（如Ubuntu）中使用的一种软件包的格式。deb是一种基于 Unix ar [3] (Unix archiver) 的归档文件。其中包含二进制文件、配置文件和其他软件所需的资源。deb包可用于安装、升级和卸载软件包。通常，Debian操作系统的用户使用apt（Advanced Package Tool）等软件包管理器工具来管理deb包。通过这些工具，用户可以轻松下载、安装和管理软件包，而无需手动编译、安装和解决软件包之间的依赖关系。\ndeb VS rpm 包的归档格式不同：deb是基于 ar 的归档模式，而RPM是基于 cpio 的归档模式 包的结构不同：deb包要求必须包含一个 DEBIAN 目录；而RPM不需要以来额外的目录结构 包的依赖机制不同： Deb使用epoch，而RPM使用build number：在Deb中，epoch是一个可选的字段，它允许呈现基准日期之前的先前版本。而在RPM中，build number表示软件包编译的次数。因此，在Deb中，为了解决版本控制问题，epoch是非常重要的，而在RPM中，则更关注build number。 Deb使用逆向依赖关系，而RPM使用依赖关系：在Deb中，依赖项是从包本身向外扩展，在解决依赖问题时可以通过逆向依赖关系进行。而在RPM中，则更喜欢使用依赖关系直接指向其他包。 Deb允许代理软件包，而RPM则不允许代理软件包：Deb中，软件包可以使用另一种软件包的代理来提供功能。在RPM中，软件包需要直接引用相关的软件包。这意味着在Deb中，对于版本控制，可以用另一种代理软件包来解决问题，而在RPM中必须直接引用包。 Deb允许多重依赖关系，RPM则不允许：Deb允许使用多个依赖项列表，以便包与不同版本的库兼容。在RPM中，需要在每个包中定义依赖项和其版本，不能使用多重依赖。 deb包的分析 deb包的结构 deb 最重要的是 控制文件 Control ，该文件记录了deb包与其安装的程序的信息。\n在deb包内部包含一组模拟 Linux 文件系统的文件夹，例如 /usr, /usr/bin, /opt等等。 放置在其中一个目录中的文件将在安装期间复制到实际文件系统中的相同位置。 因此，例如将二进制文件放入 \u0026lt;.deb\u0026gt;/usr/local/bin/binaryfile 将被安装到 /usr/local/bin/binaryfile.\n对于deb 包的命名是遵循着一个特定的格式：\ntext 1 \u0026lt;name\u0026gt;_\u0026lt;version\u0026gt;-\u0026lt;revision\u0026gt;_\u0026lt;architecture\u0026gt;.deb \u0026lt;name\u0026gt; 构建的deb包名称，如nginx \u0026lt;version\u0026gt; 程序的版本号 ，如1.20 \u0026lt;revision\u0026gt; 当前 deb 包的版本号 \u0026lt;architecture\u0026gt; 表示构建出的包的操作系统架构，如，amd64、i386 如果你构建一个nginx-1.20的arm操作系统下的，那么deb包名格式则为 nginx_1.20-1_arm64.deb\ncontrol文件 [2] Deb软件包（.deb文件）中的 控制文件 (control) 包含有关软件包的 源码元数据 和 二进制元数据 ，用于描述例如软件包的名称、版本、描述、作者、许可证和依赖关系等信息。在创建和打包Deb软件包时，必须创建和编辑control文件以包含关于软件包的所有必要信息。\n控制文件 (control) 由一个或多节组成，各节之间用空行分隔。解析器可以接受仅由空格和制表符组成的行作为节分隔符，但控制文件应该使用空行。一些控制文件只允许一个节；其他人允许多个，在这种情况下，每个节通常指的是不同的包，控制文件中节的顺序很重要。\n而 Control 文件存在两种，一种为 源代码包控制文件 (Source package control files)，这种类型的控制文件主要用于定义源代码在构建时的一些元数据信息，例如需要构建一个nginx.deb，那么源代码控制文件就是用于描述下载下来的源代码相关的数据，这种文件被放置在目录 debian/control；\n另一种为二进制包控制文件 (Binary package control files)，这部分是控制在nginx编译后制作成deb包的控制文件，这种控制文件被放置在 DEBIAN/control。\n另外还存在一种 源代码控制文件 ( Debian source control files ) ，包含完整的源代码和软件包元数据信息。它们通常存储在软件包的源代码存储库中，供开发人员和维护人员使用。包含在源代码控制文件中的信息包括软件包名称、版本、维护人员、许可证、依赖关系、构建和安装指令集等。\n例如下面为 control 文件常见的字段类型：\n字段 说明 Package \u0026lt;软件包的名称\u0026gt; Priority \u0026lt;软件包的优先级\u0026gt; Section \u0026lt;软件包的分类\u0026gt; Version \u0026lt;软件包的版本\u0026gt; Depends \u0026lt;软件包的依赖关系\u0026gt; Architecture \u0026lt;软件包的体系结构\u0026gt; Maintainer \u0026lt;软件包的维护者\u0026gt; Description \u0026lt;软件包的描述\u0026gt; Rules-Requires-Root [1] Rules-Requires-Root是Debian软件包中一个可选的控制文件，定义了软件包在安装和运行时是否需要超级用户权限；No/Yes Standards-Version: deb包 控制文件 中的一个元素，用于指定软件包所遵循的标准和规范的版本号。该字段的值应该是一个数字，例如：“3.9.8”, “2.3.0.0” 其中，上面给出的通常是必须指定的字段，如一个control 文件必须包含 Package 、Priority、Section、Version、Architecture、Maintainer 和Description 。由于等 deb软件包通常都安装在系统上，在 control 文件中指定软件包的依赖关系非常重要。这可通过Depends字段完成。\n例如下面是一个 control 文件的示例\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 源代码控制文件 Source: nginx Maintainer: root \u0026lt;root@debian-template\u0026gt; Section: comm Priority: optional Homepage: https://nginx.org # 二进制控制文件 Package: nginx Version: 1.22.1 Architecture: amd64 Standards-Version: 1.0.0 Build-Depends: debhelper-compat (= 13) Description: Nginx HTTP server Nginx (\u0026#34;engine x\u0026#34;) is a web server created by Igor Sysoev. It is known for its high performance, stability, and low resource consumption. rules文件 [5] rules文件的规范 rules文件在deb中被成为 主要构建脚本 (Main building script)，这个文件必须是一个可执行的Makefile。它包含了特定于包的源代码（如果需要）和构建一个或多个二进制包的指令。\ndebian/rules文件 必须以 #!/usr/bin/make -f 开头，这个声明是为了通过调用其名称而不是显式调用make来调用它。也就是说，调用 make -f debian/rules args... 或 ./debian/rules args... 必须产生相同的行为。\n在没有使用其他方法的充分理由的情况下，实现Debian软件包构建过程的推荐方式是使用dh工具。这包括debian/rules构建脚本的内容。dh是Debian中最常见的封装辅助工具。使用它通常可以节省遵守本文档中规则的工作，因为dh将自动实现许多规则而无需明确的指示。\nrules 文件内置了一些阶段，这标志着整个源码包构建的过程；如 debian/rules 必须由：clean、binary、binary-arch、binary-indep、build、build-arch 和 build-indep，如果通过 dpkg-buildpackage 构建时，这些阶段则是调用的阶段。\n需要注意的一点是，由于交互式 debian/rules 脚本无法自动编译该软件包，也使其他人难以复制相同的二进制软件包，因此所有必需的阶段都必须是非交互式的。它还遵循这些阶段所依赖的任何阶段也必须是非交互式的。\nrules文件的构建过程 通俗来说，rules文件是一个将源码包转换为二进制包的一个构建模式，首先，binarey二进制包写入解压后的源码包的父目录。其次，所需的目标可以写入 /tmp、/var/tmp 和 TMPDIR 环境变量指定的目录，但不得依赖于其中任何一个的内容。\n这个限制旨在防止源代码包构建创建和依赖于其外部状态，从而影响多个独立的重建过程。特别地，所需的目标不能尝试写入 HOME 目录。\nrules文件的过程，也是阶段：\nbuild (required)：该阶段应该执行软件包的所有配置和编译操作，例如 make，这个阶段执行前，会先运行 clean 阶段 build-indep 和 build-arch (required) build-arch (required)：该阶段必须执行生成所有与架构（architecture）有关的二进制包，例如 make intall 好的 x86 的二进制文件 build-indep (required)： 该阶段执行生成所有与 架构（architecture）无关的二进制包操作，所需的所有配置和编译操作。build 阶段应该依赖于这些阶段，或者采取与调用这些阶段相同的操作， build-arch 和 build-indep 不能执行任何可能需要 root 权限的操作。 通俗来讲，这两个阶段会被用于操作构建完的事情，对于rpmbuild，这里操作就是%files的操作了。 binary (required), binary-arch (required), binary-indep (required)： binary：该阶段必须是用户从此源代码包生成二进制包所需的全部内容。它分为两个部分：binary-arch用于生成特定架构的二进制包，binary-indep用于生成其他类型的二进制包 binary-* 阶段都应该依赖于 build 阶段或适当的 build-arch 或 build-indep 阶段，以便在没有构建该软件包时构建它。然后使用 dpkg-gencontrol 创建其控制文件以及dpkg-deb 构建成一个二进制包，并将它们放置在顶级目录的父目录中（即你执行构建的目录的上级），以创建相应的二进制包。 binary-arch 和 binary-indep 两个阶段必须要存在。如果其中一个阶段无事可做（如果源代码仅生成单个二进制包，无论是否架构相关，始终是如此），它仍必须存在并始终成功。 根据Rules-Requires-Root字段的值的配置，binary阶段可能需要作为root用户调用。 clean (required)：该阶段将撤消 build 和 binary 阶段可能产生的所有效果，除了它应该保留在上次运行 binary 阶段时在父目录中创建的任何输出文件。 patch (optional)：此阶段执行所需的任何其他操作，以使源代码包准备好进行编辑（解包其他上游归档文件、应用补丁等） 这些阶段执行构建的工作目录，与deb包的工作目录不同，他的工作目录为当前目录作为包的根目录来使用，有一点需要注意的是，这里的架构的觉得为 dpkg-architecture 命令决定的，在不同架构机器上构建则为不同的架构的包\n工作目录 在构建deb软件包（.deb文件）时，通常需要将软件包的文件和目录安排在特定的文件目录中，这个特定目录就是打包时的工作目录，通过上面的解释，已知，deb包分为两种 源代码包 和 二进制包，而两中类型的工作目录不相同。\n例如，我们需要构建一个源码软件包，假设为要为一个nginx制作 deb包，那么他的工作目录则为 debian，而control 文件 和 rule 文件则是必须的，封包的内容目录 则为控制文件中 Package 配置的名字，这里配置的为 nginx，那么这个deb包封包时寻找的内容则为 debian/nginx\n在例如，我们需要构建一个二进制软件包，那么他的工作目录为 DEBIAN，control文件与 changelog 则是必须的，而封包的内容目录 当前目录，也就是 DEBIAN，制作时的控制文件这些不会被封入包中\n那么完整的制作流程为：\n在源代码目录下创建一个名为nginx的子目录 mkdir debian 在nginx目录下创建名为 DEBIAN 的子目录，包含 control 文件，在DEBIAN目录中包含control文件是必须的，因为它是软件包元数据的中心存储库。 例如我们编译完的内容，nginx 的配置文件在 /etc/下，那么这个 etc 则在 nginx 子目录下 在工作目录下创建 rules 文件，这里面写明了构建的命令，例如： build：这个部分用于执行编译和构建软件的命令。 clean：这个部分用于清除编译输出，以便你可以重新构建软件包。 install：这个部分指定了如何将软件安装到目标系统中。 binary：这个部分用于在 Debian 软件包中打包二进制文件。 其中该结构中还包含一些钩子脚本文件 debian/p* 而这个 p* 文件的命名是固定的。下面是一些常见的 p* 文件名： debian/package.install：定义软件包中需要安装的文件和目录。 debian/package.manpages：将软件包中的 man 页面添加到 man 手册中。 debian/package.docs：将软件包中的文档添加到系统上的 /usr/share/doc 目录。 debian/package.preinst：在软件包安装前执行的脚本。 debian/package.postinst：在软件包安装后执行的脚本。 debian/package.prerm：在软件包卸载前执行的脚本。 debian/package.postrm：在软件包卸载后执行的脚本。 实战：从0构建一个 deb 包 准备条件 上面介绍的为deb包的一些基础，作为实战的一些理论知识，下面将以nginx为代表将其制作为deb包；通过前面知识了解到，deb包分为 源代码包 和 二进制包，那么nginx明显为源代码包，我们就需要按照 源代码包 =》二进制包 这种顺序依次定义，而 debian 提供了一系列的命令可以帮助生成这些文件，我们只需要改写具体的逻辑就可以完成包的制作，如 dh_make , dh_install 等命令。\n对于 deb 包的标准，debian 官方有一些列的教程，称为 Debian Policy Manual [4] ，可以通过这个手册，整个deb包中所需的一些理论知识，控制文件，维护脚本，包间的依赖关系，共享库等进行详细的说明\ndh工具 dh [6]（Debhelper）工具是Debian中自带的一个构建deb包的工具集。dh工具包含了一系列的脚本，用于从打包的源代码中自动化生成debian目录下的文件。这些脚本是 debian/rules 文件中的一部分，它们负责处理诸如构建、安装、打包、删除等任务。\ndh工具包括多个命令及其选项，更多的 dh 工具可以参考官方\n命令 说明 dh_make 创建一个基本的在deb包打包时的工作框架 dh_auto_configure 自动运行 configure 脚本以生成 Makefile dh_install 将文件复制到正确的deb包的构建目录中，一些常见选项：\n-s 或 --sourcedir=: 指定源代码目录；\n-X 或 --exclude: 排除在安装中不必要的文件；\n-n 或 --noscripts: 在安装软件包时不运行脚本；\n-Z: 表示安装的文件不要在规则中注册； dh_builddeb 以 .deb 文件格式构建软件包 dh_clean 清理debian目录和生成的文件，以确保它们不会在构建时对结果造成影响。 dh_gencontrol 生产和安装control文件 dh_installsystemd 安装systemd单元文件 dh_make dh_make 命令是一个用于快速创建 deb 包的命令行工具。它可以根据用户提供的基本信息和软件包源代码自动创建 deb 包的基础结构，包括 debian/ 目录和相关文件，如 control、rules 以及 changelog 等文件。DH 表示 Debian 包含 debhelper 建议的目录和文件结构。\n在默认情况下，dh_make 命令是交互式的，它会提示你输入一些必要的软件包信息，如软件包名称、版本号、作者、软件许可证等。如果你想非交互方式运行 dh_make 命令，并指定软件包信息，则可以使用 -s 选项。\nbash 1 $ dh_make -s --createorig -f ../{some_package-1.2.3}.tar.gz -p {some_package_1.2.3-1}.deb 在上述命令中，-s 选项表示以非交互方式运行 dh_make 命令，并将软件包的基本信息作为参数指定。--createorig 选项表示我们想要创建一个原始的源代码包，-f 选项指定软件包源代码的文件名和路径，-p 选项指定要创建的 Debian 软件包的名称和版本号。\n而上述命令会提示输入一些基本的详细信息，以便在 debian/control 控制文件中为软件包设置元数据。dh_make 会询问输入软件包的名称、版本、描述、组件、维护人员等信息，是否以这些参数作为构建，如果不需要这部确认可以使用 -y 默认同意\n下面是通过 dh_make 构建出的在构建deb包的工作目录，这种方式下可以很明了的理解deb包的结构\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ tree debian/ debian/ ├── changelog ├── control ├── copyright ├── manpage.1.ex ├── manpage.sgml.ex ├── manpage.xml.ex ├── nginx.cron.d.ex ├── nginx.doc-base.EX ├── nginx-docs.docs ├── postinst.ex ├── postrm.ex ├── preinst.ex ├── prerm.ex ├── README.Debian ├── README.source ├── rules ├── salsa-ci.yml.ex ├── source │ └── format └── watch.ex 到这里，我们就完成了一个源码包的前提条件的构建，只需要稍微修改控制文件即可，进行后续的二进制包的构建。\n这里最终的 控制文件 如下所示：\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 源代码控制文件 Source: nginx Maintainer: root \u0026lt;root@debian-template\u0026gt; Section: comm Priority: optional Homepage: https://nginx.org # 二进制控制文件 Package: nginx-d Version: 1.22.1 Architecture: amd64 Standards-Version: 1.0.0 Build-Depends: debhelper-compat (= 13) Description: Nginx HTTP server Nginx (\u0026#34;engine x\u0026#34;) is a web server created by Igor Sysoev. It is known for its high performance, stability, and low resource consumption. 准备rules文件 通过上面的rules文件部分介绍，了解到 rules 文件就是一个 Makefile，并且包含三个阶段\nclean \u0026ndash; 删除所有build与binary阶段产生的内容\nbuild \u0026ndash; 编译与构建的步骤\nbinary \u0026ndash; binary阶段将创建deb包的root目录，这个目录被包含在 debian 目录下，而目录名则为 control 文件中配置的 Package 名字，即 debian/\u0026lt;source package name\u0026gt;\n这些阶段都通过 dpkg-buildpackage 在构建时被执行，一个rules文件的模板如下\nmakefile 1 2 3 4 5 6 7 8 9 10 11 12 #!/usr/bin/make -f clean: @# Do nothing build: @# Do nothing binary: @# Do nothing dh_gencontrol dh_builddeb 那这个 nginx 的 rules 文件则为下述所示：\nmake 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 #!/usr/bin/make -f # 列出您要編譯的檔案和目錄 export DH_VERBOSE=1 PACKAGE=nginx UPSTREAM_VERSION=1.22.1 # 設置打包的參數 export DEB_BUILD_MAINT_OPTIONS = hardening=+all export BUILDDIR_WORK = $(CURDIR)/debian/$(PACKAGE) export BUILDDIR=$(CURDIR)/debian/tmp # 定義建立目錄，它會在${CURDIR}/debian/$PACKAGE下創建 debian/$PACKAGE:: %: # build阶段，将完成软件的编译 build: tar zxf nginx-$(UPSTREAM_VERSION).tar.gz # 配置 Nginx 以使用必要的模塊和參數 cd nginx-$(UPSTREAM_VERSION) \u0026amp;\u0026amp; \\ ./configure \\ --conf-path=/etc/nginx/nginx.conf \\ --pid-path=/var/run/nginx \\ --sbin-path=/usr/sbin/nginx \\ --http-log-path=/var/log/nginx/error.log \\ --error-log-path=/var/log/nginx/access.log \\ --http-client-body-temp-path=/etc/nginx/ \\ --http-proxy-temp-path=/var/run/nginx/ \\ --http-fastcgi-temp-path=/var/run/nginx/ \\ --http-uwsgi-temp-path=/var/run/nginx/ \\ --http-scgi-temp-path=/var/run/nginx/ \\ --pid-path=/var/run/nginx/nginx.pid \\ --with-http_ssl_module \\ --without-http_gzip_module cd nginx-$(UPSTREAM_VERSION) \u0026amp;\u0026amp; make # 这里存在一个问题，即 prefix 与 DESTDIR 不要同时写，make install 被安装在哪里 # 如果配置了prefix 那么则被安装在 prefix/DESTDIR 变成了双重目录 # 这里不配置 prefix 只配置DESTDIR则会被安装在 deb 包在构建时的工作目录，这里制定了$(BUILDDIR) # 将被放置在 $(BUILDDIR) cd nginx-$(UPSTREAM_VERSION) \u0026amp;\u0026amp; make install DESTDIR=$(BUILDDIR) # 清理 rm -rf nginx-$(UPSTREAM_VERSION) clean: rm -rf nginx-$(UPSTREAM_VERSION) rm -rf $(BUILDDIR) rm -rf $(BUILDDIR_WORK) build-indep: # 构建前测试目录 dh_testdir build-arch: dh_testdir binary-indep: dh_testdir # 测试是否有root权限 dh_testroot # 在 binary 阶段中，执行的安装操作，创建 /etc/nginx目录 # 这些命令执行的是 install -d # 为什么使用 dh tool 而不用install，因为dh tool是rules file的一部分，也就是他默认的工作目录是 # 构建deb包的工作目录，这里必须是相对目录 # 而对于第一个的 \u0026#34;/\u0026#34; 与 最后一个 \u0026#34;/\u0026#34; 都不要写，会被自动除虫上 dh_installdirs etc/nginx dh_installdirs var/log/nginx dh_installdirs var/run/nginx dh_install etc/nginx/* etc/nginx dh_install usr/sbin/* usr/sbin # 这里创建了二进制包的目录结构 install -d $(BUILDDIR_WORK)/DEBIAN # dh_gencontrol可以根据源码包的配置来生成二进制包的control文件 # 底层封装的是 dh_gencontrol -O --file --package=nginx 为二进制包生成 dh_gencontrol -O --file --package=$(PACKAGE) # dh_installchangelogs 可以根据源码包中的changelog来生成二进制包的changelog # changelog 和 control file是二进制包必须的文件 # dh_gencontrol -O --file --package=nginx # 底层执行的为在 deb 包根目录的/usr/share/doc/nginx 中生成changelog # dh_installchangelogs # install -d debian/nginx/usr/share/doc/nginx # install -p -m0644 debian/changelog debian/nginx/usr/share/doc/nginx/changelog.Debian dh_installchangelogs # 这里将修复链接路径及压缩，如果你的一些软连接需要修复时可以使用 dh_compress binary: binary-indep # dh_builddeb命令将会构建一个二进制包 # dh_builddeb封装的时dpkg-deb命令 # dpkg-deb --build debian/nginx .. dh_builddeb .PHONY: clean binary-indep binary-arch binary install build 执行构建 通过执行 dpkg-buildpackage -b 来构建二进制源码包\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 install -d debian/nginx/usr/sbin cp --reflink=auto -a debian/tmp/usr/sbin/nginx debian/nginx/usr/sbin/ install -d /root/nginx/debian/nginx/DEBIAN dh_gencontrol -O --file --package=nginx dpkg-gencontrol -pnginx -ldebian/changelog -Tdebian/nginx.substvars -Pdebian/nginx dpkg-gencontrol: warning: unknown information field \u0026#39;Version\u0026#39; in input data in package\u0026#39;s section of control info file dpkg-gencontrol: warning: unknown information field \u0026#39;Standards-Version\u0026#39; in input data in package\u0026#39;s section of control info file chmod 0644 -- debian/nginx/DEBIAN/control chown 0:0 -- debian/nginx/DEBIAN/control dh_installchangelogs install -d debian/nginx/usr/share/doc/nginx install -p -m0644 debian/changelog debian/nginx/usr/share/doc/nginx/changelog.Debian dh_compress cd debian/nginx chmod a-x usr/share/doc/nginx/changelog.Debian gzip -9nf usr/share/doc/nginx/changelog.Debian cd \u0026#39;/root/nginx\u0026#39; dh_builddeb dpkg-deb --build debian/nginx .. dpkg-deb: building package \u0026#39;nginx\u0026#39; in \u0026#39;../nginx_1.22.1-1_amd64.deb\u0026#39;. dpkg-genbuildinfo --build=binary dpkg-genchanges --build=binary \u0026gt;../nginx_1.22.1-1_amd64.changes dpkg-genchanges: warning: unknown information field \u0026#39;Version\u0026#39; in input data in package\u0026#39;s section of control info file dpkg-genchanges: warning: unknown information field \u0026#39;Standards-Version\u0026#39; in input data in package\u0026#39;s section of control info file dpkg-genchanges: warning: unknown information field \u0026#39;Build-Depends\u0026#39; in input data in package\u0026#39;s section of control info file dpkg-genchanges: info: binary-only upload (no source code included) dpkg-source --after-build . dpkg-source: warning: unknown information field \u0026#39;Version\u0026#39; in input data in package\u0026#39;s section of control info file dpkg-source: warning: unknown information field \u0026#39;Standards-Version\u0026#39; in input data in package\u0026#39;s section of control info file dpkg-source: warning: unknown information field \u0026#39;Build-Depends\u0026#39; in input data in package\u0026#39;s section of control info file dpkg-buildpackage: info: binary-only upload (no source included) 构建完成后，生成的deb包在当前执行命令的父目录中，而生成的文件名遵守了 \u0026lt;name\u0026gt;_\u0026lt;version\u0026gt;-\u0026lt;revision\u0026gt;_\u0026lt;architecture\u0026gt;.deb 这种格式\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 $ dpkg -c ../nginx_1.22.1-1_amd64.deb ./ ./etc/ ./etc/nginx/ ./etc/nginx/fastcgi.conf ./etc/nginx/fastcgi.conf.default ./etc/nginx/fastcgi_params ./etc/nginx/fastcgi_params.default ./etc/nginx/koi-utf ./etc/nginx/koi-win ./etc/nginx/mime.types ./etc/nginx/mime.types.default ./etc/nginx/nginx.conf ./etc/nginx/nginx.conf.default ./etc/nginx/scgi_params ./etc/nginx/scgi_params.default ./etc/nginx/uwsgi_params ./etc/nginx/uwsgi_params.default ./etc/nginx/win-utf ./usr/ ./usr/sbin/ ./usr/sbin/nginx ./usr/share/ ./usr/share/doc/ ./usr/share/doc/nginx/ ./usr/share/doc/nginx/changelog.Debian.gz ./var/ ./var/log/ ./var/log/nginx/ ./var/run/ ./var/run/nginx/ Troubleshooting no upstream tarball found text 1 2 dpkg-source: error: can\u0026#39;t build with source format \u0026#39;3.0 (quilt)\u0026#39;: no upstream tarball found at ../nginx_1.22.1.orig.tar.{bz2,gz,lzma,xz} dpkg-buildpackage: error: dpkg-source -b . subprocess returned exit status 2 解决：需要和你的构建目录 为上级即 debian 的上级\nnon-native package version does not contain a revision text 1 dpkg-source: error: can\u0026#39;t build with source format \u0026#39;3.0 (quilt)\u0026#39;: non-native package version does not contain a revision 解决：在changelog中定义正确的修订号\nDebian 软件包版本号由三个部分组成：Major.Minor.Revision，它们通常定义在软件包的 debian/changelog 文件中。\n在 debian/changelog 文件中，每个软件包版本都会记录下来，包括软件包的版本号、构建日期和构建者的姓名和电子邮件等信息，形成一个时间线。每个 Debian 软件包版本号应该遵循 X.Y.Z-r (或 X.Y.Z~rN) 的格式，其中 X.Y.Z 是软件包的版本号，r (或 N) 是软件包的修订号。\n用于版本号的changelog文件通常位于软件包源代码的 debian/ 目录中。您可以打开 debian/changelog 文件，找到最近的条目，将其中的版本号中的修订号修改为所需的值，并保存文件。在对 Debian 软件包进行重新打包之前，请确保更新了 debian/changelog 文件，以便反映新版本号的修改。\n需要注意的是，当您更改 debian/changelog 文件中的版本号时，请确保更新 debian/control 文件中的软件包版本依赖信息，以便与新的软件包版本号匹配。 这是为了确保软件包依赖关系与实际的软件包版本相匹配，避免在安装和使用软件包时出现问题。\nis not a valid version text 1 dpkg-genchanges: error: is not a valid version 解决：这个问题是因为制定了 -v 重写了 version 配置，而没有指定具体的版本号\n\u0026ldquo;yes\u0026rdquo; is invalid; use \u0026ldquo;binary-targets\u0026rdquo; instead bash 1 dpkg-buildpackage: error: Rules-Requires-Root field keyword \u0026#34;yes\u0026#34; is invalid; use \u0026#34;binary-targets\u0026#34; instead 解决：control 文件的 Rules-Requires-Root 没有yes选项 [8]\nCompatibility levels before 5 are no longer supported (level 1 requested) bash 1 dh: error: Compatibility levels before 5 are no longer supported (level 1 requested) 这个错误提示是由于 Debian Policy 从 3.9.0 开始不再支持 compat level 4 以及更低级别的设置，需要使用符合 Debian Policy 3.9.0 或更高版本的规范定义包。\n如果您在 debian/control 文件中设置了 compatibility level 较低的版本（如 3, 4），则会遇到这个错误。您需要将这个 compatibility level 提升至 5 或以上版本，即在 debian/control 文件中加入以下设置：\nunwanted binary file: debian/debian.deb bash 1 2 3 dpkg-source: error: unwanted binary file: debian/debian.deb dpkg-source: error: detected 1 unwanted binary file (add it in debian/source/include-binaries to allow its inclusion). dpkg-buildpackage: error: dpkg-source -b . subprocess returned exit status 255 解决：删除上级目录中多余文件即可\ncannot represent change to .Xauthority: binary file contents changed bash 1 2 3 dpkg-source: error: cannot represent change to .Xauthority: binary file contents changed dpkg-source: error: add .Xauthority in debian/source/include-binaries if you want to store the modified binary in the debian tarball dpkg-source: error: unrepresentable changes to source 解决：这是因为我直接在家目录操作的，家目录存在一些其他文件，如家目录下的隐藏文件\nReference [1] rules-requires-root\n[2] Control files and their fields\n[3] ar (Unix)\n[4] Debian Policy Manual\n[5] Main building script\n[6] debhelper(7)\n[7] Tutorial 2: building a binary package using dpkg-buildpackage\n[8] 5.6.31. Rules-Requires-Root\n","permalink":"https://www.161616.top/deb-package/","summary":"deb 概述 deb包（.deb）是 Debian 和基于 Debian衍生操作系统（如Ubuntu）中使用的一种软件包的格式。deb是一种基于 Unix ar [3] (Unix archiver) 的归档文件。其中包含二进制文件、配置文件和其他软件所需的资源。deb包可用于安装、升级和卸载软件包。通常，Debian操作系统的用户使用apt（Advanced Package Tool）等软件包管理器工具来管理deb包。通过这些工具，用户可以轻松下载、安装和管理软件包，而无需手动编译、安装和解决软件包之间的依赖关系。\ndeb VS rpm 包的归档格式不同：deb是基于 ar 的归档模式，而RPM是基于 cpio 的归档模式 包的结构不同：deb包要求必须包含一个 DEBIAN 目录；而RPM不需要以来额外的目录结构 包的依赖机制不同： Deb使用epoch，而RPM使用build number：在Deb中，epoch是一个可选的字段，它允许呈现基准日期之前的先前版本。而在RPM中，build number表示软件包编译的次数。因此，在Deb中，为了解决版本控制问题，epoch是非常重要的，而在RPM中，则更关注build number。 Deb使用逆向依赖关系，而RPM使用依赖关系：在Deb中，依赖项是从包本身向外扩展，在解决依赖问题时可以通过逆向依赖关系进行。而在RPM中，则更喜欢使用依赖关系直接指向其他包。 Deb允许代理软件包，而RPM则不允许代理软件包：Deb中，软件包可以使用另一种软件包的代理来提供功能。在RPM中，软件包需要直接引用相关的软件包。这意味着在Deb中，对于版本控制，可以用另一种代理软件包来解决问题，而在RPM中必须直接引用包。 Deb允许多重依赖关系，RPM则不允许：Deb允许使用多个依赖项列表，以便包与不同版本的库兼容。在RPM中，需要在每个包中定义依赖项和其版本，不能使用多重依赖。 deb包的分析 deb包的结构 deb 最重要的是 控制文件 Control ，该文件记录了deb包与其安装的程序的信息。\n在deb包内部包含一组模拟 Linux 文件系统的文件夹，例如 /usr, /usr/bin, /opt等等。 放置在其中一个目录中的文件将在安装期间复制到实际文件系统中的相同位置。 因此，例如将二进制文件放入 \u0026lt;.deb\u0026gt;/usr/local/bin/binaryfile 将被安装到 /usr/local/bin/binaryfile.\n对于deb 包的命名是遵循着一个特定的格式：\ntext 1 \u0026lt;name\u0026gt;_\u0026lt;version\u0026gt;-\u0026lt;revision\u0026gt;_\u0026lt;architecture\u0026gt;.deb \u0026lt;name\u0026gt; 构建的deb包名称，如nginx \u0026lt;version\u0026gt; 程序的版本号 ，如1.20 \u0026lt;revision\u0026gt; 当前 deb 包的版本号 \u0026lt;architecture\u0026gt; 表示构建出的包的操作系统架构，如，amd64、i386 如果你构建一个nginx-1.20的arm操作系统下的，那么deb包名格式则为 nginx_1.20-1_arm64.deb\ncontrol文件 [2] Deb软件包（.","title":"Unix归档模式Unix ar - 深入剖析与构建deb包"},{"content":" telnet：busybox-extras net-tools: net-tools tcpdump: tcpdump wget: wget dig nslookup: bind-tools curl: curl nmap: nmap wget ifconfig nc traceroute.. : busybox ssh: openssh-client ss iptables: iproute2 ethtool: ethtool yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 FROM alpine MAINTAINER RUN sed -i \u0026#39;s@http://dl-cdn.alpinelinux.org/@https://mirrors.aliyun.com/@g\u0026#39; /etc/apk/repositories RUN apk add --no-cache --virtual .persistent-deps \\ curl \\ tcpdump \\ iproute2 \\ bind-tools \\ ethtool \\ busybox-extras \\ libressl \\ openssh-client \\ busybox CMD [ \u0026#34;tail\u0026#34;, \u0026#34;-f\u0026#34; ] ","permalink":"https://www.161616.top/alpine-network-tools/","summary":" telnet：busybox-extras net-tools: net-tools tcpdump: tcpdump wget: wget dig nslookup: bind-tools curl: curl nmap: nmap wget ifconfig nc traceroute.. : busybox ssh: openssh-client ss iptables: iproute2 ethtool: ethtool yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 FROM alpine MAINTAINER RUN sed -i \u0026#39;s@http://dl-cdn.alpinelinux.org/@https://mirrors.aliyun.com/@g\u0026#39; /etc/apk/repositories RUN apk add --no-cache --virtual .persistent-deps \\ curl \\ tcpdump \\ iproute2 \\ bind-tools \\ ethtool \\ busybox-extras \\ libressl \\ openssh-client \\ busybox CMD [ \u0026#34;tail\u0026#34;, \u0026#34;-f\u0026#34; ] ","title":"alpine安装网络工具"},{"content":"D-Bus 是 Linux 系统中的一种通信机制，用于在进程之间进行通信。D-Bus 配置文件则是一种用于配置 D-Bus 的文件，其中包含有关系统总线 (system bus)，会话总线 (session bus) 和各种系统服务的详细信息。\n本文将解析 D-Bus 配置文件，侧重点则为权限的配置\n配置文件的基本结构 D-Bus 配置文件使用 XML 格式进行编写，具有以下基本结构：\nxml 1 2 3 4 5 6 7 8 9 10 11 12 \u0026lt;!DOCTYPE busconfig PUBLIC \u0026#34;-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN\u0026#34; \u0026#34;http://www.freedesktop.org/standards/D-Bus/1.0/busconfig.dtd\u0026#34;\u0026gt; \u0026lt;busconfig\u0026gt; \u0026lt;policy group=\u0026#34;wheel\u0026#34;\u0026gt; \u0026lt;!-- policy rules go here --\u0026gt; \u0026lt;/policy\u0026gt; \u0026lt;policy context=\u0026#34;default\u0026#34;\u0026gt; \u0026lt;!-- policy rules go here --\u0026gt; \u0026lt;/policy\u0026gt; \u0026lt;include filename=\u0026#34;other-config.xml\u0026#34;/\u0026gt; \u0026lt;listen\u0026gt;unix:path=/var/run/D-Bus/system_bus_socket\u0026lt;/listen\u0026gt; \u0026lt;/busconfig\u0026gt; 什么是D-Bus Policy？ D-Bus Policy是D-Bus配置文件中最重要的字段之一，用于定义D-Bus服务的访问控制策略。D-Bus Policy包含了一组规则，用于限制D-Bus服务的使用者对D-Bus服务的访问，确保D-Bus服务的安全性。\n这些规则可以限制对D-Bus的连接，以及对D-Bus服务的读写访问、接收和发送消息等操作，以助于保护D-Bus服务免受未经授权的访问和攻击。\nD-Bus Policy 是由 dbus-daemon 进程执行的，dbus-daemon 进程是DBus 消息总线系统的核心组件。D-Bus Policy 则在系统启动时加载并编译 dbus-policy 文件。此文件定义了系统中所有服务和对象的访问控制。\n配置D-Bus Policy \u0026lt;policy\u0026gt; 元素定义了要应用于总线连接的特定一组安全策略，也就是这里的核心配置， \u0026lt;policy\u0026gt; 段的主要的子配置包含两个 allow 与 deny\n例如，下面是一个D-Bus Policy配置文件的示例：\nxml 1 2 3 4 5 6 7 8 9 10 \u0026lt;!DOCTYPE busconfig PUBLIC \u0026#34;-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN\u0026#34; \u0026#34;http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\u0026#34;\u0026gt; \u0026lt;busconfig\u0026gt; \u0026lt;policy\u0026gt; \u0026lt;allow own=\u0026#34;org.example.myapp\u0026#34;/\u0026gt; \u0026lt;allow own=\u0026#34;org.example.myapp.service\u0026#34;/\u0026gt; \u0026lt;allow send_destination=\u0026#34;org.example.myapp.service\u0026#34;/\u0026gt; \u0026lt;/policy\u0026gt; \u0026lt;/busconfig\u0026gt; 其中 own 用于允许其他DBus进程注册特定的DBus服务名称。own则是DBus服务的名称，它指明了DBus服务注册的名称，而这两条 own 允许了 org.example.myapp 和 org.example.myapp.service 的名字被注册。\n对于 own 存在四个回复标记\nRequestNameReplyPrimaryOwner: 表示名称请求成功，您已成为主要所有者。 RequestNameReplyInQueue: 表示名称请求被放置在名称队列中等待获取，因为另一个所有者正在使用该名称。请求没有立即成功，但您可以等待并在所有者释放名称所有权后获取名称所有权。 RequestNameReplyExists: 表示名称请求失败，因为该名称已被另一个所有者占用，不能再次被分配给您。 RequestNameReplyAlreadyOwner: 表示名称请求被拒绝，因为您已经是该名称的所有者。无需再次请求。 send_destination 指DBus进程发送消息的目的地DBus进程的名称，它指明了DBus进程消息通信的目标进程；而这条配置则表示 允许向 org.example.myapp.service 发送消息。这里 org.example.myapp.service 可以理解为是一个服务\npolicy的优先级 在dbus policy 不同的上下文属性具有不同的优先级，优先级从高到低为：\n“at_console” 属性的优先级最高，表示进程是否在控制台上运行。如果该属性为1，则表示进程在控制台上运行，否则表示不在控制台上。 其次是 “user” 属性，它用于指定DBus进程所属的用户账户。 最后是 “group” 属性，它用于指定DBus进程所属的用户组。 默认策略 系统总线 ( system bus ) 对于发送方法调用拥有的总 bus 默认策略为拒绝，对于接收消息、发送信号 (signal) 和为每个 没有 NO_REPLY 标志的方法调用发送单个成功或错误回复具有默认允许策略。不允许发送多个预期数量的回复。\n\u0026lt;policy\u0026gt; 包含四个属性，通常user, group 只系统的 id 可以看到的用户\ncontext：(default|mandatory) at_console：\u0026quot;(true|false)\u0026quot; user：\u0026ldquo;username or userid\u0026rdquo; group：\u0026ldquo;group name or gid\u0026rdquo; 权限配置 \u0026lt;deny\u0026gt; 配置需要出现在 \u0026lt;policy\u0026gt; 最上方，表示禁止某些操作，\u0026lt;allow\u0026gt; 是对上面的 \u0026lt;deny\u0026gt; 语句进行修饰。\n\u0026lt;deny\u0026gt; 确定是否拒绝与特定条件匹配的请求。如果匹配，则拒绝该操作（如果下面的 \u0026lt;allow\u0026gt; 允许，则允许），例如下列的配置\nxml 1 2 3 4 5 6 \u0026lt;policy context=\u0026#34;default\u0026#34;\u0026gt; \u0026lt;deny receive_path=\u0026#34;/org/fedoraproject/FirewallD1\u0026#34; /\u0026gt; \u0026lt;allow user=\u0026#34;root\u0026#34; /\u0026gt; \u0026lt;allow own=\u0026#34;com.github.cylonchau.Uranus\u0026#34; /\u0026gt; \u0026lt;allow receive_sender=\u0026#34;com.github.cylonchau.Uranus\u0026#34; receive_path=\u0026#34;/org/fedoraproject/FirewallD1\u0026#34; /\u0026gt; \u0026lt;/policy\u0026gt; 例如拒绝所有请求 /org/fedoraproject/FirewallD1 的请求，只允许服务 com.github.cylonchau.Uranus 发送请求 /org/fedoraproject/FirewallD1 的请求\n具有一个或多个 send_* 系列属性的规则在连接尝试发送消息时按顺序检查。最后匹配消息的规则确定是否可以发送它。\n而通常，已知的会话总线允许发送任何消息。通常，已知的系统总线允许发送任何信号，选择性地向dbus-daemon发送方法调用，并恰好回答先前已发送的每个方法调用（成功或错误）。\n具有一个或多个 receive_* 系列属性或仅带有 eavesdrop 属性且没有其他属性的规则将为每个消息的 receiver 进行检查（如果消息是广播或连接正在窃听，则可能有多个接收者）。最后一个匹配消息的规则确定是否可以接收它。\nsend_destination 和 receive_sender 规则表示消息不能被发送到或从给定名称的 “所有者” （服务）接收，而不是不能向该名称发送。即，如果连接拥有服务A、B、C，并且向A发送被拒绝，则向B或C发送也将无法工作。作为特例，send_destination=\u0026quot;*\u0026quot; 匹配任何消息（无论是否指定了目标），而 receive_sender=\u0026quot;*\u0026quot; 匹配任何消息。\nReference dbus-daemon\n","permalink":"https://www.161616.top/dbus-security-policy/","summary":"D-Bus 是 Linux 系统中的一种通信机制，用于在进程之间进行通信。D-Bus 配置文件则是一种用于配置 D-Bus 的文件，其中包含有关系统总线 (system bus)，会话总线 (session bus) 和各种系统服务的详细信息。\n本文将解析 D-Bus 配置文件，侧重点则为权限的配置\n配置文件的基本结构 D-Bus 配置文件使用 XML 格式进行编写，具有以下基本结构：\nxml 1 2 3 4 5 6 7 8 9 10 11 12 \u0026lt;!DOCTYPE busconfig PUBLIC \u0026#34;-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN\u0026#34; \u0026#34;http://www.freedesktop.org/standards/D-Bus/1.0/busconfig.dtd\u0026#34;\u0026gt; \u0026lt;busconfig\u0026gt; \u0026lt;policy group=\u0026#34;wheel\u0026#34;\u0026gt; \u0026lt;!-- policy rules go here --\u0026gt; \u0026lt;/policy\u0026gt; \u0026lt;policy context=\u0026#34;default\u0026#34;\u0026gt; \u0026lt;!-- policy rules go here --\u0026gt; \u0026lt;/policy\u0026gt; \u0026lt;include filename=\u0026#34;other-config.xml\u0026#34;/\u0026gt; \u0026lt;listen\u0026gt;unix:path=/var/run/D-Bus/system_bus_socket\u0026lt;/listen\u0026gt; \u0026lt;/busconfig\u0026gt; 什么是D-Bus Policy？ D-Bus Policy是D-Bus配置文件中最重要的字段之一，用于定义D-Bus服务的访问控制策略。D-Bus Policy包含了一组规则，用于限制D-Bus服务的使用者对D-Bus服务的访问，确保D-Bus服务的安全性。","title":"Linux Dbus中的ACL策略"},{"content":"创建型模式 工厂模式 概念说明 工厂模式 (factory pattern) 是在父类中提供一个创建对象的方法，是用于创建不同类型的对象，而无需指定对象的真实的类\n工厂模式的特点：\n对客户端隐藏对象创建的复杂逻辑 可以通过修改工厂类来创建对象而不影响客户端代码 提供创建对象的单一来源。 单个工厂类用以各组件保持一致性。 允许子类创建对象类型 图：工厂设计模式的示意图 Source：https://www.techcrashcourse.com/2015/10/factory-design-pattern.html\n图片说明： Owl, Eagle, Sparrow 类都必须实现 Brid 接口， 该接口声明了一个名为 fly() 的方法。 每个类都将以不同的方式实现该方法。而使用工厂模式后的代码机构则为图所示，当 Owl, Eagle, Sparrow 实现了共同的接口，就可以将其对象传递给客户代码， 而无需提供额外数据。\n而 “调用工厂方法的代码” 称为 “客户端代码”，这样可以做到 “不需要了解不同子类返回实际对象之间的差别”。客户端代码将所有 Brid Sanctuary 视为抽象的 Brid ，这样 ”客户端代码“ 知道所有鸟类对象都提供 fly() 方法， 但是并不关心其实现方式。\n代码实现 brid.go\ngo 1 2 3 4 5 package main type Brid interface { Fly() } Owl.go\ngo 1 2 3 4 5 type Owl struct {} func (g *Owl) Fly() { fmt.Println(\u0026#34;猫头鹰在飞\u0026#34;) } Eagle.go\ngo 1 2 3 4 5 type Eagle struct {} func (e *Eagle) Fly() { fmt.Println(\u0026#34;鹰在飞\u0026#34;) } Sparrow.go\ngo 1 2 3 4 5 type Sparrow struct {} func (e *Sparrow) Fly() { fmt.Println(\u0026#34;麻雀在飞\u0026#34;) } 创建一个工厂类 BridSanctuary.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 func BridSanctuary(type string) Brid { if type == \u0026#34;Owl\u0026#34;{ return newOwl() } if type == \u0026#34;Eagle\u0026#34;{ return newEagle() } if type == \u0026#34;Sparrow\u0026#34;{ return newSparrow() } } 客户端使用时通过工厂类获得不同的对象\ngo 1 2 sparrow := BridSanctuary(\u0026#34;sparrow\u0026#34;) eagle := BridSanctuary(\u0026#34;eagle\u0026#34;) 工厂模式特点\n客户端代码使用工厂模式提供的创建对象，而不是直接使用 new 运算符创建对象。 调用工厂对象并指定需要什么类型的对象。 工厂模式在不暴露对象创建的复杂逻辑的情况下可以创建各种对象。 工厂方法在将其类型转换为公共接口后，根据客户端代码的请求返回新创建的对象。 客户端通过 Interface 与对象进行交互，但并不知道具体类的类型 抽象工厂 概念说明 抽象工厂模式 (Abstract Factory)，是提供了一个 接口 或 抽象类创建一系列相关或依赖的对象，而不需要指定的具体类。抽象工厂模式，是工厂模式的超集，换句话说，抽象工厂是工厂的工厂，或者是工厂的wapper\n图：抽象工厂设计模式的示意图 Source：https://www.techcrashcourse.com/2015/10/abstract-factory-design-pattern.html\n代码实现 brid.go\ngo 1 2 3 type Brid interface { fly() } Owl.go\ngo 1 2 3 4 5 6 7 8 9 type Owl struct{} func (g *Owl) fly() { fmt.Println(\u0026#34;猫头鹰在飞\u0026#34;) } func newOwl() Brid { return \u0026amp;Owl{} } Eagle.go\ngo 1 2 3 4 5 6 7 8 9 type Eagle struct{} func (e *Eagle) fly() { fmt.Println(\u0026#34;鹰在飞\u0026#34;) } func newEagle() Brid { return \u0026amp;Eagle{} } Sparrow.go\ngo 1 2 3 4 5 6 7 8 9 type Sparrow struct{} func (e *Sparrow) fly() { fmt.Println(\u0026#34;麻雀在飞\u0026#34;) } func newSparrow() Brid { return \u0026amp;Sparrow{} } Animal.go\ngo 1 2 3 type Animal interface { run() } Horse.go\ngo 1 2 3 4 5 6 7 8 9 type Horse struct{} func (a *Horse) run() { fmt.Println(\u0026#34;马在跑\u0026#34;) } func newHorse() Animal { return \u0026amp;Horse{} } Lion.go\ngo 1 2 3 4 5 6 7 8 9 type Lion struct {} func (a *Lion) run() { fmt.Println(\u0026#34;狮子在跑\u0026#34;) } func newLion() Animal { return \u0026amp;Lion{} } 接下来创建 动物园的抽象工厂模式\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Zoo interface { GetBrid(t string) Brid GetAnimal(t string) Animal } // 抽象工厂的初始化 func FactoryCreator(t string) Zoo { if t == \u0026#34;EZooFactory\u0026#34; { return new(EZooFactory) } if t == \u0026#34;SZooFactory\u0026#34; { return new(EZooFactory) } return nil } 实现两个动物园，EZoo 和 SZoo\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type EZooFactory struct{} func (e *EZooFactory) GetBrid(t string) Brid { if t == \u0026#34;Eagle\u0026#34; { return newEagle() } if t == \u0026#34;Sparrow\u0026#34; { return newSparrow() } return nil } func (c *EZooFactory) GetAnimal(t string) Animal { if t == \u0026#34;Lion\u0026#34; { return newLion() } return nil } SZoo\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type SZooFactory struct{} func (e *SZooFactory) GetBrid(t string) Brid { if t == \u0026#34;Owl\u0026#34; { return newOwl() } return nil } func (c *SZooFactory) GetAnimal(t string) Animal { if t == \u0026#34;Horse\u0026#34; { return newHorse() } return nil } 客户端代码，通过creator可以定义了创建不同动物园的类，而每个动物园有其独特的动物。换句话来说，客户端代码通过抽象接口，将工厂模式与实体柔和为一体，使客户端创建出的工厂类可以是任何动物的变体\ngo 1 2 3 4 5 6 func main() { e := FactoryCreator(\u0026#34;EZooFactory\u0026#34;) s := FactoryCreator(\u0026#34;SZooFactory\u0026#34;) e.GetBrid(\u0026#34;owl\u0026#34;) e.GetAnimal(\u0026#34;Horse\u0026#34;) } 抽象工厂模式的特点：\n抽象工厂设计模式目的是为了创建一系列相关的对象，从而不依赖具体的每一个子类 客户端代码并不知道使用了那个子类的工厂，首先工厂，然后调用具体方法获得具体的类 抽象工厂是工厂模式的超集 工厂模式与抽象工厂模式比较 Factory Method Abstract Factory 当包含一个工厂类时，只能产生一种类型的对象 包含一个工厂类时，能够产生一系列的多种不同类型的对象 工厂类通过create方法实现接口 使用interface为工厂创建抽象类 子类决定了返回什么样的对象 每一种专门的工厂用于创建一种类型的对象，因此也被称为 “factory of factories” 一个工厂产生一种对象 一个广义的工厂，包含一或多个工厂，每个工厂可产生一种类型的对象 建造者模式 建造者 (Builder) 是一种使用相同的代码逐步构建复杂对象的方式，\n假设一个复杂对象， 在对其进行构造时需要对诸多成员变量和嵌套对象进行繁琐的初始化工作。 这种场景的初始化代码通常是包含众多参数的构造函数中。例如，盖房子 House对象。 建造一栋简单的房屋， 首先需要建造四面墙和地板， 安装房门和一套窗户， 然后再建造一个屋顶。 但是如果想要一栋更大更明亮的房屋，还需要其他设施 （例如暖气，排水系统，供电系统，冷气系统）。\n在这种场景下，建造者模式就应景而生，Builder 建议将对象构造代码从产品类中抽取出来， 并将其放在一个名为 Builder 的独立对象中。\n图：Builder示意图/center\u003e Source：https://www.techcrashcourse.com/2015/10/abstract-factory-design-pattern.html\n在这种情况下， 通过创建多个不同的Builder， 用不同方式实现一组相同的创建步骤。 然后你就可以在创建过程中使用这些Builder （例如按顺序调用多个构造步骤） 来生成不同类型的对象。\n代码实现 Builder interface\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type IBuilder interface { setWindowType() setDoorType() setNumFloor() getHouse() House } func getBuilder(builderType string) IBuilder { if builderType == \u0026#34;normal\u0026#34; { return newNormalBuilder() } if builderType == \u0026#34;igloo\u0026#34; { return newIglooBuilder() } return nil } 一般房屋\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package main type NormalBuilder struct { windowType string doorType string floor int } func newNormalBuilder() *NormalBuilder { return \u0026amp;NormalBuilder{} } func (b *NormalBuilder) setWindowType() { b.windowType = \u0026#34;Wooden Window\u0026#34; } func (b *NormalBuilder) setDoorType() { b.doorType = \u0026#34;Wooden Door\u0026#34; } func (b *NormalBuilder) setNumFloor() { b.floor = 2 } func (b *NormalBuilder) getHouse() House { return House{ doorType: b.doorType, windowType: b.windowType, floor: b.floor, } } 冰屋\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 type IglooBuilder struct { windowType string doorType string floor int } func newIglooBuilder() *IglooBuilder { return \u0026amp;IglooBuilder{} } func (b *IglooBuilder) setWindowType() { b.windowType = \u0026#34;Snow Window\u0026#34; } func (b *IglooBuilder) setDoorType() { b.doorType = \u0026#34;Snow Door\u0026#34; } func (b *IglooBuilder) setNumFloor() { b.floor = 1 } func (b *IglooBuilder) getHouse() House { return House{ doorType: b.doorType, windowType: b.windowType, floor: b.floor, } } 房屋属性\ngo 1 2 3 4 5 6 7 package main type House struct { windowType string doorType string floor int } 工人\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type Worker struct { builder IBuilder } func newWorker(b IBuilder) *Worker { return \u0026amp;Worker{ builder: b, } } func (d *Worker) setBuilder(b IBuilder) { d.builder = b } func (d *Worker) buildHouse() House { d.builder.setDoorType() d.builder.setWindowType() d.builder.setNumFloor() return d.builder.getHouse() } 客户端代码实现，即建造房屋\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func main() { // 定义房屋的类型 normalBuilder := getBuilder(\u0026#34;normal\u0026#34;) iglooBuilder := getBuilder(\u0026#34;igloo\u0026#34;) // 请工人盖房子 worker := newWorker(normalBuilder) normalHouse := worker.buildHouse() // 设置工人需要施工的方向，即要盖什么类型的房子 fmt.Printf(\u0026#34;Normal House Door Type: %s\\n\u0026#34;, normalHouse.doorType) fmt.Printf(\u0026#34;Normal House Window Type: %s\\n\u0026#34;, normalHouse.windowType) fmt.Printf(\u0026#34;Normal House Num Floor: %d\\n\u0026#34;, normalHouse.floor) worker.setBuilder(iglooBuilder) iglooHouse := worker.buildHouse() fmt.Printf(\u0026#34;\\nIgloo House Door Type: %s\\n\u0026#34;, iglooHouse.doorType) fmt.Printf(\u0026#34;Igloo House Window Type: %s\\n\u0026#34;, iglooHouse.windowType) fmt.Printf(\u0026#34;Igloo House Num Floor: %d\\n\u0026#34;, iglooHouse.floor) } prototype 模式概念 prototype 这种设计模式，提供的是一种复制现有对象的模式，在这种模式下不需要重复的构建对象。\nprototype 的特点有：\n客户端代码克隆对象时，客户端并不知道它获得的对象类型，而需要指定其对象类型 提高了系统性能；对于资源密集型操作的对象，可以通过克隆来减少创建成本 隐藏了客户端代码创建新示例的复杂性 该类型主要的体系就是在clone() 上 选择 prototype的场景\n当创建对象很复杂时，或需要大量的资源操作时 当想对客户端隐藏创建逻辑时 当一个类有多种状态，可以通过先全部创建，用时克隆来提高效率 代码的实现 brid.go\ngo 1 2 3 4 type Brid interface { fly() Clone() Brid } Owl.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Owl struct{ Name string } func (g *Owl) fly() { fmt.Printf(\u0026#34;猫头鹰 %s 在飞\\n\u0026#34;, g.Name) } func (g *Owl) clone() Brid { return \u0026amp;Owl{Name: g.Name} } func newOwl() Brid { return \u0026amp;Owl{} } Eagle.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Eagle struct{ Name string } func (e *Eagle) fly() { fmt.Printf(\u0026#34;鹰 %s 在飞\\n\u0026#34;, e.Name) } func (g *Eagle) clone() Brid { return \u0026amp;Eagle{Name: g.Name} } func newEagle() Brid { return \u0026amp;Eagle{} } Sparrow.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Sparrow struct{ Name string } func (e *Sparrow) fly() { fmt.Printf(\u0026#34;麻雀 %s 在飞\\n\u0026#34;, e.Name) } func (g *Sparrow) clone() Brid { return \u0026amp;Sparrow{Name: g.Name} } func newSparrow() Brid { return \u0026amp;Sparrow{} } prototype 类，使用 BridsFactory 对象来创建 Parrot、Sparrow 和 Eagle 等类的对象。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type BridsFactory struct { children []Brid name string } func (b *BridsFactory) print(indentation string) { fmt.Println(indentation + b.name) for _, i := range f.children { i.fly() } } func (b *BridsFactory) clone() BridsFactory { clonBrid := \u0026amp;Brids{name: b.name + \u0026#34;_clone\u0026#34;} var tempChildren []Brid for _, i := range f.children { copy := i.clone() tempChildren = append(tempChildren, copy) } clonBrid.children = tempChildren return clonBrid } 客户端代码\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main() { brid1 := \u0026amp;Owl{name: \u0026#34;jackchan\u0026#34;} brid2 := \u0026amp;Eagle{name: \u0026#34;calenlee\u0026#34;} brid3 := \u0026amp;Sparrow{name: \u0026#34;jerrywong\u0026#34;} brids1 := \u0026amp;BridsFactory{ children: []Brid{brid1, brid2, brid3}, name: \u0026#34;owls\u0026#34;, } cloneBrids := brids1.clone() fmt.Println(\u0026#34;\\nPrinting hierarchy for clone Brids\u0026#34;) cloneBrids.print(\u0026#34; \u0026#34;) } 单例 概念说明 单例 (Singleton) 模式主要特点是确保一个类只能创建一个对象，其提供了一种只能创建一个对象的方法。\n单例模式的特点:\n确保一个类只能创建的一个实例 通常情况下构造函数是私有的，以防止通过new来创建多个实例 类似静态的构建函数的方法，它将提供了一种调用私有构建函数来创建出对象并保存在静态字段中，后续所有的构建调用都会返回唯一的这个静态对象 代码实现 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type single struct {} var singleInstance *single // 定义一个静态字段 func getInstance() *single { if singleInstance == nil { // 当这个“静态变量”为空时，则创建这个对象，已确保只能被初始化一次 if singleInstance == nil { fmt.Println(\u0026#34;Creating single instance now.\u0026#34;) singleInstance = \u0026amp;single{} } else { fmt.Println(\u0026#34;Single instance already created.\u0026#34;) } } else { fmt.Println(\u0026#34;Single instance already created.\u0026#34;) } return singleInstance } 客户端代码\ngo 1 2 3 4 5 6 func main() { for i := 0; i \u0026lt; 30; i++ { go getInstance() } } 结构模式 适配器 适配器设计模式 (Adapter)，是一种结构设计模式，主要是使具有不兼容接口的对象可以进行协作；通俗来讲，是将一个类的接口转换为客户端期望的另一个接口，可以使不兼容的两个类通过 适配器，完成与现有类的交互。\nAdapter 将作为一个 “代理人” 的职责\n适配器模式特点：\n使不兼容的类可以通过Adapter进行交互 提升了现有系统的可用性（一个类可以使多个类使用） 很常见的一个例子，数据使一个结构体，而客户端需要 json, 那么 Adapter 就可以是 json.Marshual\n适配器模式的组成 Target Interface：客户端期望的数据格式，即客户端使用的类型 Adapter: 接收来自客户端的调用，假设服务端类型是 Adaptee，他会将客户端的调用转换为Adaptee 类型 Adaptee Interface: 实际的类型，客户端想与其交互，由于类型不相同无法直接请求 Client: 客户端使用 Target Interface 与 Adapter 交互 代码示例 例如我们有一个Windows电脑来作为 Adaptee Interface，电脑上有各类的接口，例如USB，这两种类型是不同的，这时就需要 Adapter\nclient.go\ngo 1 2 3 4 5 6 type Client struct {} func (c *Client) InsertLightningConnectorIntoComputer(com Computer) { fmt.Println(\u0026#34;Client inserts Lightning connector into computer.\u0026#34;) com.InsertIntoLightningPort() } Client interface 为Computer配置一个雷电接口\ngo 1 2 3 type Computer interface { InsertIntoLightningPort() } Mac PC 实现了Computer接口，这样客户端与服务端可以直接通讯\ngo 1 2 3 4 5 type Mac struct {} func (m *Mac) InsertIntoLightningPort() { fmt.Println(\u0026#34;Lightning connector is plugged into mac machine.\u0026#34;) } windows PC 没有实现 Computer 接口，\ngo 1 2 3 4 5 type Windows struct{} func (w *Windows) insertIntoUSBPort() { fmt.Println(\u0026#34;USB connector is plugged into windows machine.\u0026#34;) } 要使 windows 也可以 使用 Lightning，那么需要适配器\ngo 1 2 3 4 5 6 7 8 type WindowsAdapter struct { windowMachine *Windows } func (w *WindowsAdapter) InsertIntoLightningPort() { fmt.Println(\u0026#34;Adapter converts Lightning signal to USB.\u0026#34;) w.windowMachine.insertIntoUSBPort() } 客户端代码\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package main func main() { client := \u0026amp;Client{} mac := \u0026amp;Mac{} client.InsertLightningConnectorIntoComputer(mac) windowsMachine := \u0026amp;Windows{} windowsMachineAdapter := \u0026amp;WindowsAdapter{ windowMachine: windowsMachine, } client.InsertLightningConnectorIntoComputer(windowsMachineAdapter) } Bridge 桥接 (Bridge) 允许将大类拆分为两个独立的层次结构（abstraction 和 implementation），桥接模式提供一个接口，来充当两个层次间的桥梁\n桥接模式的特点：\n","permalink":"https://www.161616.top/design-patterns/","summary":"创建型模式 工厂模式 概念说明 工厂模式 (factory pattern) 是在父类中提供一个创建对象的方法，是用于创建不同类型的对象，而无需指定对象的真实的类\n工厂模式的特点：\n对客户端隐藏对象创建的复杂逻辑 可以通过修改工厂类来创建对象而不影响客户端代码 提供创建对象的单一来源。 单个工厂类用以各组件保持一致性。 允许子类创建对象类型 图：工厂设计模式的示意图 Source：https://www.techcrashcourse.com/2015/10/factory-design-pattern.html\n图片说明： Owl, Eagle, Sparrow 类都必须实现 Brid 接口， 该接口声明了一个名为 fly() 的方法。 每个类都将以不同的方式实现该方法。而使用工厂模式后的代码机构则为图所示，当 Owl, Eagle, Sparrow 实现了共同的接口，就可以将其对象传递给客户代码， 而无需提供额外数据。\n而 “调用工厂方法的代码” 称为 “客户端代码”，这样可以做到 “不需要了解不同子类返回实际对象之间的差别”。客户端代码将所有 Brid Sanctuary 视为抽象的 Brid ，这样 ”客户端代码“ 知道所有鸟类对象都提供 fly() 方法， 但是并不关心其实现方式。\n代码实现 brid.go\ngo 1 2 3 4 5 package main type Brid interface { Fly() } Owl.go\ngo 1 2 3 4 5 type Owl struct {} func (g *Owl) Fly() { fmt.","title":"Go设计模式"},{"content":" 本文是关于Kubernetes service解析的第5章 深入理解Kubernetes service - 你真的理解service吗? 深入理解Kubernetes service - EndpointSlices做了什么？ 深入理解Kubernetes service - kube-proxy架构分析 深入理解Kubernetes service - 如何扩展现有的kube-proxy架构 kube-proxy如何保证规则的一致性 所有关于Kubernetes service 部分代码上传至仓库 github.com/cylonchau/kube-haproxy\n前景 这里谈 kube-proxy 如何保证规则的一致性以及提升 kube-proxy 性能点的地方，这也是 kubernetes 使用稳定性的一部分。\nkube-proxy 如何做到的CRUD kube-proxy 实际上与其他内置 controller 架构是相同的，实际上也属于一个 controller ，但它属于一个 service, endpoints 的可读可写的控制器，node的读控制器。对于CRUD方面，kube-proxy，在设计上分为 增/改 两方面。正如下面代码所示 pkg/proxy/ipvs/proxier.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func (proxier *Proxier) OnServiceAdd(service *v1.Service) { proxier.OnServiceUpdate(nil, service) } // OnServiceUpdate is called whenever modification of an existing service object is observed. func (proxier *Proxier) OnServiceUpdate(oldService, service *v1.Service) { if proxier.serviceChanges.Update(oldService, service) \u0026amp;\u0026amp; proxier.isInitialized() { proxier.Sync() } } // OnServiceDelete is called whenever deletion of an existing service object is observed. func (proxier *Proxier) OnServiceDelete(service *v1.Service) { proxier.OnServiceUpdate(service, nil) } 可以看到代码最终调用的都是 OnServiceUpdate，最终 调用的是 proxier.Sync()。对于 Sync()，这里会调用在 Proxier 初始化时注入的那个函数，而 Sync() 本质上是 一个异步有限的函数管理器，这里将实现两个方面，一是，定时去触发执行这个函数；二是满足规则去触发这个函数；而 Sync() 属于条件2\n对于注入的函数，则是 proxier.syncProxyRules ，由下列代码可以看到\ngo 1 proxier.syncRunner = async.NewBoundedFrequencyRunner(\u0026#34;sync-runner\u0026#34;, proxier.syncProxyRules, minSyncPeriod, syncPeriod, burstSyncs) 这样就是说，kube-proxy 通过 syncProxyRules 实现了整个 service 与 endpoint 的增删改查\n性能提升点1 由上面有限的函数管理器 runner，可以作为性能提升点，而该runner初始化时提供了minSyncPeriod, syncPeriod 两个函数，这两个函数代表的意思为，minSyncPeriod是runner允许你在最小多少时间内可以调用，如果你的集群规模大，那么则可以适当配置该参数小写，因为service的频繁更改会被这个参数限制。\n如何通过一个函数做CRUD 对于增改，存在三个资源，ClusterIP, NodePort, Ingress,当这些资源被触发时，会同步这个service与endpoint，如代码所示 syncProxyRules\ngo 1 2 3 4 5 6 7 8 9 if err := proxier.syncService(svcNameString, serv, true, bindedAddresses); err == nil { activeIPVSServices[serv.String()] = true activeBindAddrs[serv.Address.String()] = true if err := proxier.syncEndpoint(svcName, svcInfo.OnlyNodeLocalEndpoints(), serv); err != nil { klog.Errorf(\u0026#34;Failed to sync endpoint for service: %v, err: %v\u0026#34;, serv, err) } } else { klog.Errorf(\u0026#34;Failed to sync service: %v, err: %v\u0026#34;, serv, err) } 由上面代码可知，所有的 service 与 endpoint 的更新，都会触发 Sync()，而 Sync() 执行的是 syncProxyRules() ，当service有变动时，就会通过 syncService/syncEndpoint 进行同步\n而对于删除动作来说，kube-proxy 提供了 cleanLegacyService 函数在变动做完时，进行清理遗留的service规则，如下列代码所示。\ngo 1 proxier.cleanLegacyService(activeIPVSServices, currentIPVSServices, legacyBindAddrs) 并且通过两个数组来维护两个 activeIPVSServices , 与 currentIPVSServices 为主，来维护删除的数据\nCRUD实际实现 上面了解到了删除与添加的逻辑，下面分析这些是如何进行的\n当添加被触发时，会触发 proxier.syncService() ，首先会进行本机上ipvs规则是否存在这个规则，存在则更改，而后返回一个 error, 这个 error 取决于是否更新 endpoints，如下列代码所示\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 func (proxier *Proxier) syncService(svcName string, vs *utilipvs.VirtualServer, bindAddr bool, bindedAddresses sets.String) error { appliedVirtualServer, _ := proxier.ipvs.GetVirtualServer(vs) if appliedVirtualServer == nil || !appliedVirtualServer.Equal(vs) { if appliedVirtualServer == nil { // IPVS service is not found, create a new service klog.V(3).Infof(\u0026#34;Adding new service %q %s:%d/%s\u0026#34;, svcName, vs.Address, vs.Port, vs.Protocol) if err := proxier.ipvs.AddVirtualServer(vs); err != nil { klog.Errorf(\u0026#34;Failed to add IPVS service %q: %v\u0026#34;, svcName, err) return err } } else { // IPVS service was changed, update the existing one // During updates, service VIP will not go down klog.V(3).Infof(\u0026#34;IPVS service %s was changed\u0026#34;, svcName) if err := proxier.ipvs.UpdateVirtualServer(vs); err != nil { klog.Errorf(\u0026#34;Failed to update IPVS service, err:%v\u0026#34;, err) return err } } } // bind service address to dummy interface if bindAddr { // always attempt to bind if bindedAddresses is nil, // otherwise check if it\u0026#39;s already binded and return early if bindedAddresses != nil \u0026amp;\u0026amp; bindedAddresses.Has(vs.Address.String()) { return nil } klog.V(4).Infof(\u0026#34;Bind addr %s\u0026#34;, vs.Address.String()) _, err := proxier.netlinkHandle.EnsureAddressBind(vs.Address.String(), DefaultDummyDevice) if err != nil { klog.Errorf(\u0026#34;Failed to bind service address to dummy device %q: %v\u0026#34;, svcName, err) return err } } return nil } 接下来通过后会 触发 proxier.syncEndpoint() 这里传入了当前的 service, 这里是为了与 IPVS 概念相吻合，如IPVS 中存在 RealServers/VirtualServers，首先会拿到本机这个VirtualServer下的所有RealServer，而后进行添加，而后对比 传入的 Endpoints 列表 与 本机这个VirtualServer下的所有RealServer，不相等的则被删除；删除的动作是一个异步操作。由 gracefuldeleteManager 维护\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 func (proxier *Proxier) syncEndpoint(svcPortName proxy.ServicePortName, onlyNodeLocalEndpoints bool, vs *utilipvs.VirtualServer) error { appliedVirtualServer, err := proxier.ipvs.GetVirtualServer(vs) if err != nil || appliedVirtualServer == nil { klog.Errorf(\u0026#34;Failed to get IPVS service, error: %v\u0026#34;, err) return err } // curEndpoints represents IPVS destinations listed from current system. curEndpoints := sets.NewString() // newEndpoints represents Endpoints watched from API Server. newEndpoints := sets.NewString() curDests, err := proxier.ipvs.GetRealServers(appliedVirtualServer) if err != nil { klog.Errorf(\u0026#34;Failed to list IPVS destinations, error: %v\u0026#34;, err) return err } for _, des := range curDests { curEndpoints.Insert(des.String()) } endpoints := proxier.endpointsMap[svcPortName] // Service Topology will not be enabled in the following cases: // 1. externalTrafficPolicy=Local (mutually exclusive with service topology). // 2. ServiceTopology is not enabled. // 3. EndpointSlice is not enabled (service topology depends on endpoint slice // to get topology information). if !onlyNodeLocalEndpoints \u0026amp;\u0026amp; utilfeature.DefaultFeatureGate.Enabled(features.ServiceTopology) \u0026amp;\u0026amp; utilfeature.DefaultFeatureGate.Enabled(features.EndpointSliceProxying) { endpoints = proxy.FilterTopologyEndpoint(proxier.nodeLabels, proxier.serviceMap[svcPortName].TopologyKeys(), endpoints) } for _, epInfo := range endpoints { if onlyNodeLocalEndpoints \u0026amp;\u0026amp; !epInfo.GetIsLocal() { continue } newEndpoints.Insert(epInfo.String()) } // Create new endpoints for _, ep := range newEndpoints.List() { ip, port, err := net.SplitHostPort(ep) if err != nil { klog.Errorf(\u0026#34;Failed to parse endpoint: %v, error: %v\u0026#34;, ep, err) continue } portNum, err := strconv.Atoi(port) if err != nil { klog.Errorf(\u0026#34;Failed to parse endpoint port %s, error: %v\u0026#34;, port, err) continue } newDest := \u0026amp;utilipvs.RealServer{ Address: net.ParseIP(ip), Port: uint16(portNum), Weight: 1, } if curEndpoints.Has(ep) { // check if newEndpoint is in gracefulDelete list, if true, delete this ep immediately uniqueRS := GetUniqueRSName(vs, newDest) if !proxier.gracefuldeleteManager.InTerminationList(uniqueRS) { continue } klog.V(5).Infof(\u0026#34;new ep %q is in graceful delete list\u0026#34;, uniqueRS) err := proxier.gracefuldeleteManager.MoveRSOutofGracefulDeleteList(uniqueRS) if err != nil { klog.Errorf(\u0026#34;Failed to delete endpoint: %v in gracefulDeleteQueue, error: %v\u0026#34;, ep, err) continue } } err = proxier.ipvs.AddRealServer(appliedVirtualServer, newDest) if err != nil { klog.Errorf(\u0026#34;Failed to add destination: %v, error: %v\u0026#34;, newDest, err) continue } } // Delete old endpoints for _, ep := range curEndpoints.Difference(newEndpoints).UnsortedList() { // if curEndpoint is in gracefulDelete, skip uniqueRS := vs.String() + \u0026#34;/\u0026#34; + ep if proxier.gracefuldeleteManager.InTerminationList(uniqueRS) { continue } ip, port, err := net.SplitHostPort(ep) if err != nil { klog.Errorf(\u0026#34;Failed to parse endpoint: %v, error: %v\u0026#34;, ep, err) continue } portNum, err := strconv.Atoi(port) if err != nil { klog.Errorf(\u0026#34;Failed to parse endpoint port %s, error: %v\u0026#34;, port, err) continue } delDest := \u0026amp;utilipvs.RealServer{ Address: net.ParseIP(ip), Port: uint16(portNum), } klog.V(5).Infof(\u0026#34;Using graceful delete to delete: %v\u0026#34;, uniqueRS) err = proxier.gracefuldeleteManager.GracefulDeleteRS(appliedVirtualServer, delDest) if err != nil { klog.Errorf(\u0026#34;Failed to delete destination: %v, error: %v\u0026#34;, uniqueRS, err) continue } } return nil } 删除 service 将删除所有的 RealServer，这点上面提到过，kube-proxy 通过 cleanLegacyService 进行删除，如下列代码所示\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func (proxier *Proxier) cleanLegacyService(activeServices map[string]bool, currentServices map[string]*utilipvs.VirtualServer, legacyBindAddrs map[string]bool) { isIPv6 := utilnet.IsIPv6(proxier.nodeIP) for cs := range currentServices { svc := currentServices[cs] if proxier.isIPInExcludeCIDRs(svc.Address) { continue } if utilnet.IsIPv6(svc.Address) != isIPv6 { // Not our family continue } if _, ok := activeServices[cs]; !ok { klog.V(4).Infof(\u0026#34;Delete service %s\u0026#34;, svc.String()) if err := proxier.ipvs.DeleteVirtualServer(svc); err != nil { klog.Errorf(\u0026#34;Failed to delete service %s, error: %v\u0026#34;, svc.String(), err) } addr := svc.Address.String() if _, ok := legacyBindAddrs[addr]; ok { klog.V(4).Infof(\u0026#34;Unbinding address %s\u0026#34;, addr) if err := proxier.netlinkHandle.UnbindAddress(addr, DefaultDummyDevice); err != nil { klog.Errorf(\u0026#34;Failed to unbind service addr %s from dummy interface %s: %v\u0026#34;, addr, DefaultDummyDevice, err) } else { // In case we delete a multi-port service, avoid trying to unbind multiple times delete(legacyBindAddrs, addr) } } } } } 性能提升点2 由上面的讲解可知，CRUD动作是每一个事件 syncProxyRules 被触发时都会进行执行，而删除动作存在多组循环（如构建维护的两个列表；进行循环删除）即每一次 Endpoints 变动也会触发 大量的 Service 的循环，从而检测 是否由遗留的Service资源，而这个操作保留到kubernetes 1.26版本\n假设你的集群节点是5000个，service资源是两万个，那么当你更新一个Service资源循环的次数，会至少循环多达数万次（Service, EndpointSpilt, currentServices, NodeIP, Ingress）其中无用的为currentServices，因为这个只有在删除Service本身才会有效（如果存在20000个service，其中currentServices在构建与对比的过程超过4万次）\n","permalink":"https://www.161616.top/ch24-kube-proxy-performance/","summary":"本文是关于Kubernetes service解析的第5章 深入理解Kubernetes service - 你真的理解service吗? 深入理解Kubernetes service - EndpointSlices做了什么？ 深入理解Kubernetes service - kube-proxy架构分析 深入理解Kubernetes service - 如何扩展现有的kube-proxy架构 kube-proxy如何保证规则的一致性 所有关于Kubernetes service 部分代码上传至仓库 github.com/cylonchau/kube-haproxy\n前景 这里谈 kube-proxy 如何保证规则的一致性以及提升 kube-proxy 性能点的地方，这也是 kubernetes 使用稳定性的一部分。\nkube-proxy 如何做到的CRUD kube-proxy 实际上与其他内置 controller 架构是相同的，实际上也属于一个 controller ，但它属于一个 service, endpoints 的可读可写的控制器，node的读控制器。对于CRUD方面，kube-proxy，在设计上分为 增/改 两方面。正如下面代码所示 pkg/proxy/ipvs/proxier.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func (proxier *Proxier) OnServiceAdd(service *v1.Service) { proxier.OnServiceUpdate(nil, service) } // OnServiceUpdate is called whenever modification of an existing service object is observed.","title":"kube-proxy如何保证规则的一致性"},{"content":"概述 在之前有一个系列提到了扩展proxier，但可能细心的同学注意到，作为一个外部的LB，市场上存在一些开源的为kubernetes集群提供的LB，这不是舍近求远吗？而 Google工程师 Adam Dunstan 的 文章 [1] 对比了这些LB的区别（中文翻译备份 [2] ），例如：\nMetalLB：最流行的 负载均衡控制器 PureLB：新产品 (文章作者 Adam Dunstan 参与了 PureLB的开发工作) OpenELB：相对较新的产品，最初该LB仅关注路由方向 文章提出了一个LB实现的基本目标为：必要的简单网络组件，与可扩展的集群操作\n启动受控的集群service/应用的外部访问 外部资源的预配置 易于整合自动化的工作流程（CI/CD） 那么这些LB与 kube-proxy 甚至于 IPVS/IPTables 有什么区别呢？\n这些LB的核心是为集群service提供一个外部IP，而service功能本身还是由 kube-proxy,IPVS 提供，在这点 MetalLB 介绍中提到了这个问题\nIn layer 2 mode, all traffic for a service IP goes to one node. From there, kube-proxy spreads the traffic to all the service’s pods. [3]\nAfter the packets arrive at the node, kube-proxy is responsible for the final hop of traffic routing, to get the packets to one specific pod in the service. [4]\n通过kubernetes service 资源方向表示，是为EXTERNAL-IP部分分配一个IP地址，而从来不是说内部的LB\nbash 1 2 3 $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 192.168.0.1 \u0026lt;none\u0026gt; 443/TCP 196d 关于MetalLb的使用可以参考视频：Set up MetalLB Load Balancing for Bare Metal Kubernetes\n正如注解所说，工作与L2的模式下，流量会到达一个Node,接下来通过kube-proxy广播至所有的Pod，这种模式下是否可以做到HA，还有待测试。\n接下来提到 Proixer，Proixer是对于集群（内）正常工作的一个保证，最基础的一点则是Kubernetes service 需要每个Pod可以访问到，所以 kube-proxy 则完全不同于 kubernetes LB\n总结 Kubernetes LB是Kubernetes的扩展功能，主要特点体现在下列方面：\nLB 是要作用是为service提供一个外部IP 通常情况下，LB支持的都是 L2, L3 网络，而非传统的L4, L7 LB kube-proxy并不可以被这类LB所替代，因为这类LB的端点是到service 目前开源的 kube-proxy repleacement 应该只有 eBPF Reference ​[1] Kubernetes Ingress 架构说明\n​[2] 「译文」比较开源 k8s LoadBalancer-MetalLB vs PureLB vs OpenELB\n​[3] METALLB IN LAYER 2 MODE\n​[4] METALLB IN BGP MODE\n","permalink":"https://www.161616.top/kubernetes-lb/","summary":"概述 在之前有一个系列提到了扩展proxier，但可能细心的同学注意到，作为一个外部的LB，市场上存在一些开源的为kubernetes集群提供的LB，这不是舍近求远吗？而 Google工程师 Adam Dunstan 的 文章 [1] 对比了这些LB的区别（中文翻译备份 [2] ），例如：\nMetalLB：最流行的 负载均衡控制器 PureLB：新产品 (文章作者 Adam Dunstan 参与了 PureLB的开发工作) OpenELB：相对较新的产品，最初该LB仅关注路由方向 文章提出了一个LB实现的基本目标为：必要的简单网络组件，与可扩展的集群操作\n启动受控的集群service/应用的外部访问 外部资源的预配置 易于整合自动化的工作流程（CI/CD） 那么这些LB与 kube-proxy 甚至于 IPVS/IPTables 有什么区别呢？\n这些LB的核心是为集群service提供一个外部IP，而service功能本身还是由 kube-proxy,IPVS 提供，在这点 MetalLB 介绍中提到了这个问题\nIn layer 2 mode, all traffic for a service IP goes to one node. From there, kube-proxy spreads the traffic to all the service’s pods. [3]\nAfter the packets arrive at the node, kube-proxy is responsible for the final hop of traffic routing, to get the packets to one specific pod in the service.","title":"扫盲Kubernetes负载均衡 - 从Ingress聊到LB"},{"content":"前言 MIUI13 石锤了内置反诈APP后，我的是MIUI12, 接到公安的私人电话，系统直接弹出国家反诈的弹窗，关键我是印度版的Rom，一身冷汗，估计当局审查是通过系统组件更新了，直接装Pixel Experience，以后换设备永远不换最新的，让网友们踩坑吧\n注：隐私是一种权利，电信诈骗请问 骗子怎么知道我的金融信息，怎么知道我的出入境信息。上海公安10亿信息泄露是怎么情况，当公权力无法保证用户隐私时，请不要实名制，参考韩国。隐私权参考欧洲\n操作 进入fastboot(power button + volume button up)，然后使用数据线连接至PC(windows),然后下载MiFlash 首次弹出时需要安装驱动，以便PC可以识别到手机\n给手机安装TWRP [1]，通过搜索找到你的手机型号 例如 Redmi Note5。(可以去小米ROM网上对照下你的手机代号时什么例如 Note7 Pro 代号为 紫罗兰 violet)\n在下载时TWRP网站上会提示你先安装 Play Stroe(这是包含了adb fastboot等工具的工具包，有的话可以不装) 安装步骤可以参考 [2]\n选择 Wipe – Advance Wipe – 选上 System, Data, Dalvik, Cache 四个擦除\n下载 firmware 与 PixelExperience\n去 https://download.pixelexperience.org/ 下载 PixelExperience 找到自己的手机型号，参考1 去 https://xiaomifirmwareupdater.com/firmware/ 下载 fireware 找到自己的手机型号，参考1 注：建议直接搜代号如violet，搜型号太多不好找 向手机复制 firmware [3] 和固件\nfw_violet_miui_VIOLET_9.9.3_79d3ccd33b_9.0.zip PixelExperience_violet-10.0-20191021-1744-BETA-OFFICIAL.zip 复制命令参考 [10] 按先后顺序安装后，重启就安装好google pixel experience了 enjoy 🤞\n拷贝命令 bash 1 adb push xxx.zip /sdcard couldn\u0026rsquo;t create file: Required key not available：没有写入权限，换个目录写就行\n对于Win10中进入Fastboot下Bootloader Interface自动断开问题 管理员运行\nbat 1 2 3 4 5 6 7 @echooff reg add \u0026#34;HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\usbflags\\18D1D00D0100\u0026#34; /v \u0026#34;osvc\u0026#34; /t REG_BINARY /d \u0026#34;0000\u0026#34; /f reg add \u0026#34;HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\usbflags\\18D1D00D0100\u0026#34; /v \u0026#34;SkipContainerIdQuery\u0026#34; /t REG_BINARY /d \u0026#34;01000000\u0026#34; /f reg add \u0026#34;HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\usbflags\\18D1D00D0100\u0026#34; /v \u0026#34;SkipBOSDescriptorQuery\u0026#34; /t REG_BINARY /d \u0026#34;01000000\u0026#34; /f pause twrp operation not permitted redmi 在 https://github.com/Szaki/XiaomiADBFastbootTools 下载对应工具与 jdk 11，安装后运行工具即可，点击 wipe cache and userdata 后就有写入权限了\nReference ​[1] twrp\n​[2] 小米手机刷 TWRP 方法（解决卡米问题）\n​[3] 安卓手机在Win10中进入Fastboot下Adb Bootloader Interface自动断开\n​[4] Twrp Error Operation not Permitted 2021 Fix\n​[5] 红米 Note 7 Pro 刷 Pixel Experience\n","permalink":"https://www.161616.top/xiaomi-install-pixelexperience/","summary":"前言 MIUI13 石锤了内置反诈APP后，我的是MIUI12, 接到公安的私人电话，系统直接弹出国家反诈的弹窗，关键我是印度版的Rom，一身冷汗，估计当局审查是通过系统组件更新了，直接装Pixel Experience，以后换设备永远不换最新的，让网友们踩坑吧\n注：隐私是一种权利，电信诈骗请问 骗子怎么知道我的金融信息，怎么知道我的出入境信息。上海公安10亿信息泄露是怎么情况，当公权力无法保证用户隐私时，请不要实名制，参考韩国。隐私权参考欧洲\n操作 进入fastboot(power button + volume button up)，然后使用数据线连接至PC(windows),然后下载MiFlash 首次弹出时需要安装驱动，以便PC可以识别到手机\n给手机安装TWRP [1]，通过搜索找到你的手机型号 例如 Redmi Note5。(可以去小米ROM网上对照下你的手机代号时什么例如 Note7 Pro 代号为 紫罗兰 violet)\n在下载时TWRP网站上会提示你先安装 Play Stroe(这是包含了adb fastboot等工具的工具包，有的话可以不装) 安装步骤可以参考 [2]\n选择 Wipe – Advance Wipe – 选上 System, Data, Dalvik, Cache 四个擦除\n下载 firmware 与 PixelExperience\n去 https://download.pixelexperience.org/ 下载 PixelExperience 找到自己的手机型号，参考1 去 https://xiaomifirmwareupdater.com/firmware/ 下载 fireware 找到自己的手机型号，参考1 注：建议直接搜代号如violet，搜型号太多不好找 向手机复制 firmware [3] 和固件\nfw_violet_miui_VIOLET_9.9.3_79d3ccd33b_9.0.zip PixelExperience_violet-10.0-20191021-1744-BETA-OFFICIAL.zip 复制命令参考 [10] 按先后顺序安装后，重启就安装好google pixel experience了 enjoy 🤞","title":"红米手机安装 Pixel Experience"},{"content":" 本文是关于Kubernetes service解析的第2章 深入理解Kubernetes service - 你真的理解service吗? 深入理解Kubernetes service - EndpointSlices做了什么？ 深入理解Kubernetes service - kube-proxy架构分析 深入理解Kubernetes service - 如何扩展现有的kube-proxy架构 kube-proxy如何保证规则的一致性 所有关于Kubernetes service 部分代码上传至仓库 github.com/cylonchau/kube-haproxy\nEndpoint Endpoints 就是 service 中后端的server，通常来说 endpoint 与 service是关联的，例如下面的一个endpoints 资源。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: v1 kind: Endpoints metadata: name: nginx subsets: - addresses: - ip: 172.17.0.2 - ip: 172.17.0.3 ports: - port: 80 name: \u0026#34;111\u0026#34; # 多个端口需要用name - port: 88 name: \u0026#34;222\u0026#34; 而 Endpoints 资源是由控制平面的 Endpoints controller 进行管理的，主要用于将外部server引入至集群内时使用的，例如Kube-apiserver 在集群外的地址，以及external service所需要创建的。\n我们看到 Endpoints controller 代码中，在对 该 informer 监听的包含 service 与 Pod，位于 NewEndpointController()\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 // NewEndpointController returns a new *EndpointController. func NewEndpointController(podInformer coreinformers.PodInformer, serviceInformer coreinformers.ServiceInformer, endpointsInformer coreinformers.EndpointsInformer, client clientset.Interface, endpointUpdatesBatchPeriod time.Duration) *EndpointController { broadcaster := record.NewBroadcaster() broadcaster.StartStructuredLogging(0) broadcaster.StartRecordingToSink(\u0026amp;v1core.EventSinkImpl{Interface: client.CoreV1().Events(\u0026#34;\u0026#34;)}) recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: \u0026#34;endpoint-controller\u0026#34;}) if client != nil \u0026amp;\u0026amp; client.CoreV1().RESTClient().GetRateLimiter() != nil { ratelimiter.RegisterMetricAndTrackRateLimiterUsage(\u0026#34;endpoint_controller\u0026#34;, client.CoreV1().RESTClient().GetRateLimiter()) } e := \u0026amp;EndpointController{ client: client, queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), \u0026#34;endpoint\u0026#34;), workerLoopPeriod: time.Second, } serviceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: e.onServiceUpdate, UpdateFunc: func(old, cur interface{}) { e.onServiceUpdate(cur) }, DeleteFunc: e.onServiceDelete, }) e.serviceLister = serviceInformer.Lister() e.servicesSynced = serviceInformer.Informer().HasSynced podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: e.addPod, UpdateFunc: e.updatePod, DeleteFunc: e.deletePod, }) e.podLister = podInformer.Lister() e.podsSynced = podInformer.Informer().HasSynced endpointsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ DeleteFunc: e.onEndpointsDelete, }) e.endpointsLister = endpointsInformer.Lister() e.endpointsSynced = endpointsInformer.Informer().HasSynced .... } EndpointSlices [1] EndpointSlices 是提供为集群内用于替换 Endpoints 资源的一种灵活并具有扩展性的一种资源，由控制平面的 EndpointSlices Controller 来创建和管理的，默认情况下 EndpointSlices Controller 创建和管理的EndpointSlices 资源将不大于100个Endpoints；可以通过 kube-controller-manager 的参数 --max-endpoints-per-slice 设置，该参数最大为1000 [2]\n通常情况下无需自行创建该资源，因为在创建 service 资源时 通常是通过 label 来匹配到对应的 backend server\n下面是一个完整的 EndpointSlices 资源清单\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 addressType: IPv4 apiVersion: discovery.k8s.io/v1beta1 #注意版本 1.21后是 v1 endpoints: - addresses: - 192.168.1.241 conditions: ready: true targetRef: kind: Pod name: netbox-ff6dd9445-kxr4s namespace: default resourceVersion: \u0026#34;1994535\u0026#34; topology: kubernetes.io/hostname: master-machine - addresses: - 192.168.1.242 conditions: ready: true targetRef: kind: Pod name: netbox-ff6dd9445-566tj namespace: default topology: kubernetes.io/hostname: master-machine kind: EndpointSlice metadata: annotations: endpoints.kubernetes.io/last-change-trigger-time: \u0026#34;2023-02-24T22:40:20+08:00\u0026#34; labels: endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io kubernetes.io/service-name: netbox name: netbox-l489z namespace: default ports: - name: \u0026#34;\u0026#34; port: 80 protocol: TCP 在代码中 EndpointSlices 资源是这么呈现的，可以看到主要的就是包含一组 Endpoints 资源\ngo 1 2 3 4 5 6 7 type EndpointSlice struct { metav1.TypeMeta metav1.ObjectMeta AddressType AddressType Endpoints []Endpoint Ports []EndpointPort } EndpointSlices 在 kube-proxy中的应用 Google 工程师 Rob Scott 在2020年一文 [3] 中提到了 EndpointSlices 的作用，从kubernetes 1.19 开始EndpointSlices 默认被开启，而开启后的kube-proxy将使用 EndpointSlices 读取集群内的service的 Endpoints，而这个最大的变化就是『拓扑感知路由』(Topology Aware Routing)\nRob Scott 在文中提到 EndpointSlice API 是为了提升 Endpoints API 的限制，例如，etcd的存储大小，以及pod规模变动时最大产生的超过22TB数据的问题\n而这些问题可以通过文中变化图来说明，开启功能后会将所有匹配到的 Endpoint，划分为多个EndpointSlices，而在大规模集群环境场景下，每次的变更只需要修改其中一个 EndpointSlices 即可，这将带给 kube-proxy 提供超Endpoint模式 10倍的性能\n图：Kubernetes EndpointSlices Source：https://kubernetes.io/blog/2020/09/02/scaling-kubernetes-networking-with-endpointslices\nNotes：该文中没有提到的一点是：”EndpointSlices资源解决的是集群内的service节点问题，如你使用了endpoint类资源，那么不会触发到EndpointSlices的资源，这部分在 kube-proxy 源码中可以很清晰的看到\n下面的 kube-proxy 日志可以看到获取 server是通过 Endpoints 还是 EndpointSlices\ntext 1 2 3 4 5 endpointslicecache.go:322] Setting endpoints for \u0026#34;default/netbox\u0026#34; to [192.168.1.241:80 192.168.1.242:80] 10008 proxier.go:1057] Syncing ipvs Proxier rules 10008 iptables.go:343] running iptables-save [-t filter] 10008 iptables.go:343] running iptables-save [-t nat] 10008 ipset.go:173] Successfully add entry: 192.168.1.241,tcp:80,192.168.1.241 to ip set: KUBE-LOOP-BACK 总结 Endpoints 与 EndpointSlices 均是为service提供端点的 Service规模越大，那么Endpoints中的 Pod 数量越大，传输的 EndPoints 对象就越大。集群中 Pod 更改的频率越高，也意味着传输在网络中发生的频率就越高 Endpoints 对象在大规模集群场景下存在下列问题： 增加网络流量 超大规模的 service 理论上会无法存储 该 Endpoints 处理Endpoints资源的 worker 会消耗更多的计算资源 隐性增加对控制平面的影响，service的可扩展性将降低 Endpointslices 解决了： 部分更新，更少的网络流量 Worker 处理 Endpoints 更新所需的资源更少 减少对控制平面的影响，提升的性能和 service 规模 Reference\n​[1] EndpointSlices\n​[2] EndpointSlice API\n​[3] Scaling Kubernetes Networking With EndpointSlices\n​[4] Scalability Limitations of the Endpoints API\n","permalink":"https://www.161616.top/ch18-endpointslices/","summary":"本文是关于Kubernetes service解析的第2章 深入理解Kubernetes service - 你真的理解service吗? 深入理解Kubernetes service - EndpointSlices做了什么？ 深入理解Kubernetes service - kube-proxy架构分析 深入理解Kubernetes service - 如何扩展现有的kube-proxy架构 kube-proxy如何保证规则的一致性 所有关于Kubernetes service 部分代码上传至仓库 github.com/cylonchau/kube-haproxy\nEndpoint Endpoints 就是 service 中后端的server，通常来说 endpoint 与 service是关联的，例如下面的一个endpoints 资源。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: v1 kind: Endpoints metadata: name: nginx subsets: - addresses: - ip: 172.17.0.2 - ip: 172.17.0.3 ports: - port: 80 name: \u0026#34;111\u0026#34; # 多个端口需要用name - port: 88 name: \u0026#34;222\u0026#34; 而 Endpoints 资源是由控制平面的 Endpoints controller 进行管理的，主要用于将外部server引入至集群内时使用的，例如Kube-apiserver 在集群外的地址，以及external service所需要创建的。","title":"深入理解Kubernetes service - EndpointSlices做了什么？"},{"content":" 本文是关于Kubernetes service解析的第4章 深入理解Kubernetes service - 你真的理解service吗? 深入理解Kubernetes service - EndpointSlices做了什么？ 深入理解Kubernetes service - kube-proxy架构分析 深入理解Kubernetes service - 如何扩展现有的kube-proxy架构 kube-proxy如何保证规则的一致性 所有关于Kubernetes service 部分代码上传至仓库 github.com/cylonchau/kube-haproxy\nOverview 在前两部分中，学习了一些 service,于kube-proxy在设计架构，但存在扩展问题将引入了一些问题：\n为什么需要了解这部分内容呢？ 与传统架构有什么区别呢？ 于eBPF 的 cilium又有什么区别呢？ 既然eBPF可以做到，那为什么要这部分内容呢？ 接下来的内容将围绕这四个问题展开来讲，而不是代码的讲解，代码可以看置顶\nIPVS与iptables在kubernetes中应用时的问题 对于在使用了kubernetes用户以及了解 kube-proxy 架构后，知道当集群规模过大时，service必将增多，而一个service未必是一条iptables/ipvs规则，对于kubernetes这种分布式架构来说，集群规模越大，集群状态就越不可控，尤其时kube-proxy。\n为什么单指kube-proxy呢？想想可以知道，pod的故障 或 node 的故障对于kubernetes集群来说却不是致命的，因为 这些资源集群中存在 避免方案，例如Pod的驱逐。而kube-proxy或iptables/IPVS问题将导致服务的不可控 『抖动』例如规则生成的快慢和Pod就绪的快慢不一致，部分节点不存在 service 此时服务必然抖动。\n再例如 iptables/IPVS 排查的难度对于普通运维工程师或开发工程师的技术水平有很高的要求，网上随处可见分析该类问题的帖子：\nkube-proxy源码分析与问题定位\n案例分析：怎么解决海量IPVS规则带来的网络延时抖动问题？\nipvs 连接复用引发的系列问题\nInvestigating Causes of Jitter in Container Networking\nContainerNative network LoadBalancer IPVS jitter\n对于上述问题，相信遇到了很难定位处理，虽然现在已fixed，并有eBPF技术的加入减少了此类问题的发生，但是eBPF实际同理于IPVS 都是需要对Linux内核有一定了解后才可以，这也就是为什么需要了解这部分\n如果需要自定义proxier为什么会解决这个问题 这里就是放大到kubernetes意外的传统架构中，当直接部署于Linux系统上使用nginx等传统LB时就很少有人提到这些问题了，而这些问题存在一个关键字「Container」；而引发这个问题的则是 service。去除 service 的功能，传统架构于Kubernetes架构部署的应用则是相同的，只是区分了名称空间。\n抓入关键的核心之后就做接下来的事情了，我称之为「shed kube-proxy, fetch service」；即把service提取到集群外部的LB之上，例如F5, nginx等。\n这里会存在一个疑问：「这个不是ingress吗？」，这个问题会在下一章讲到 proxier与ingress有什么区别?\n软件的设计 既然拿到了核心问题就该定义软件工作模式，这里将软件架构设计为三种：\nonly fetch：任然需要 kube-proxy 组件，通过定义 contoller 将流量引入，即不过service，这种场景不会破坏现有的集群架构，从而去除service的功能，如果需要service功能配置外部service即可 SK (similar kube-proxy)：通过效仿kube-proxy + ipvs架构，将LB于proxier部署在每个worker节点上，让浏览都走本地 replacement kube-proxy：完全取代kube-proxy 这于cilium类似了，但不同的是，proxier 可以于 kube-controller-manager；kube-scheduler 作为控制平面为集群提供 service 功能，而无需为所有worker节点都部署一个 kube-proxy 或 cilium 这种架构 最后一个问题 此时可以引入最后一个问题了：「既然eBPF可以做到，那为什么要这部分内容呢？」。\n答：其一简单，每个运维人员无需额外知识都可以对 service 问题进行排错，简便了运维复杂度。另外这一部分其实是对于完整企业生态来讲，统一的流量转发平台是所必须的，有了这个就不需要单独的 service 功能了\n实践：基于haproxy的proxier 在扩展proxier需要对 kube-proxy 有一定的了解，并且，kube-proxy 在可扩展性来说做的也是相当不错的，我们只需要实现一个 proxier.go 就可以基本上完成了对 kube-proxy ；而 proxier.go 的核心方法只需要三个函数即可（==这里是根据iptables/ipvs的设计进行的，也可以整合为一个方法==）\n除了这三个函数外，其他的函数全都是 kube-proxy 已经实现好的通用的，这里直接使用或者按照其他内置proxier的方法即可\n满足条件 haproxy工作与proxier相同的节点，可以是集群内也可以是集群外，整个集群只需要一个 实现方法：syncProxyRules(), syncService(), syncEndpoint() 查看当前的service\nbash 1 2 3 4 $ kubectl get endpointslices NAME ADDRESSTYPE PORTS ENDPOINTS AGE kubernetes IPv4 6443 10.0.0.4 195d netbox-l489z IPv4 80 192.168.1.241,192.168.1.242 2d1h 查看service 配置\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: v1 kind: Service metadata: name: netbox spec: clusterIP: 192.168.129.5 ports: - port: 88 protocol: TCP targetPort: 80 selector: app: nginx sessionAffinity: None type: ClusterIP status: loadBalancer: {} 通过 proxier 生成了对应的 backend 与 frontend，这样就可以通过 haproxy 作为一个外部LB来跨过 service 与 IPVS/IPTables，通过这种情况下，我们可以将集群拉出一个平面至传统架构上，而又不影响集群的功能\n在这种场景下需要注意的是：\nOF模式下，我们需要 kube-proxy 组件，而使用 kube-proxy 组件 所有模式下，haproxy worker和kubernetes nodes需处于一个网络平面 非OF模式下需要自行修改 kube-apiserver 源代码（主要是使kubernetes service分配机制） Proxier与Ingress的区别 肯定有人会问，kubernetes提供了Ingress功能不是和这个一样吗？\n答：对比一个LB是Proxier还是Ingress最好的区别就是“舍去kube-proxy”可以工作正常吗？\n而kubernetes官方也提供说明，Ingress的后端是service，service的后端则是IPVS/IPTables，而IPVS的后端才是Pod；相对于Proxier LB，他的后端则直接是Pod，跨越了Service。\nKubernetes Ingress 架构说明 [1] Traefik Ingress 架构说明 [2] APISIX Ingress 架构说明 [3] 而相对的本文的学习思路，haproxy官方提供了对应的解决方案 [4] ；而由此，可以灵活的为Kubernetes提供更多的LB方案\nReference\n​[1] Kubernetes Ingress 架构说明\n​[2] Traefik Ingress 架构说明\n​[3] APISIX Ingress 架构说明\n​[4] External haproxy\n","permalink":"https://www.161616.top/ch23-extend-kube-proxy/","summary":"本文是关于Kubernetes service解析的第4章 深入理解Kubernetes service - 你真的理解service吗? 深入理解Kubernetes service - EndpointSlices做了什么？ 深入理解Kubernetes service - kube-proxy架构分析 深入理解Kubernetes service - 如何扩展现有的kube-proxy架构 kube-proxy如何保证规则的一致性 所有关于Kubernetes service 部分代码上传至仓库 github.com/cylonchau/kube-haproxy\nOverview 在前两部分中，学习了一些 service,于kube-proxy在设计架构，但存在扩展问题将引入了一些问题：\n为什么需要了解这部分内容呢？ 与传统架构有什么区别呢？ 于eBPF 的 cilium又有什么区别呢？ 既然eBPF可以做到，那为什么要这部分内容呢？ 接下来的内容将围绕这四个问题展开来讲，而不是代码的讲解，代码可以看置顶\nIPVS与iptables在kubernetes中应用时的问题 对于在使用了kubernetes用户以及了解 kube-proxy 架构后，知道当集群规模过大时，service必将增多，而一个service未必是一条iptables/ipvs规则，对于kubernetes这种分布式架构来说，集群规模越大，集群状态就越不可控，尤其时kube-proxy。\n为什么单指kube-proxy呢？想想可以知道，pod的故障 或 node 的故障对于kubernetes集群来说却不是致命的，因为 这些资源集群中存在 避免方案，例如Pod的驱逐。而kube-proxy或iptables/IPVS问题将导致服务的不可控 『抖动』例如规则生成的快慢和Pod就绪的快慢不一致，部分节点不存在 service 此时服务必然抖动。\n再例如 iptables/IPVS 排查的难度对于普通运维工程师或开发工程师的技术水平有很高的要求，网上随处可见分析该类问题的帖子：\nkube-proxy源码分析与问题定位\n案例分析：怎么解决海量IPVS规则带来的网络延时抖动问题？\nipvs 连接复用引发的系列问题\nInvestigating Causes of Jitter in Container Networking\nContainerNative network LoadBalancer IPVS jitter\n对于上述问题，相信遇到了很难定位处理，虽然现在已fixed，并有eBPF技术的加入减少了此类问题的发生，但是eBPF实际同理于IPVS 都是需要对Linux内核有一定了解后才可以，这也就是为什么需要了解这部分\n如果需要自定义proxier为什么会解决这个问题 这里就是放大到kubernetes意外的传统架构中，当直接部署于Linux系统上使用nginx等传统LB时就很少有人提到这些问题了，而这些问题存在一个关键字「Container」；而引发这个问题的则是 service。去除 service 的功能，传统架构于Kubernetes架构部署的应用则是相同的，只是区分了名称空间。","title":"深入理解Kubernetes service - 如何扩展现有的kube-proxy架构？"},{"content":" 本文是关于Kubernetes service解析的第3章 深入理解Kubernetes service - 你真的理解service吗? 深入理解Kubernetes service - EndpointSlices做了什么？ 深入理解Kubernetes service - kube-proxy架构分析 深入理解Kubernetes service - 如何扩展现有的kube-proxy架构 kube-proxy如何保证规则的一致性 所有关于Kubernetes service 部分代码上传至仓库 github.com/cylonchau/kube-haproxy\n前提概述 kubernetes集群中运行在每个Worker节点上的组件 kube-proxy，本文讲解的是如何快速的了解 kube-proxy 的软件架构，而不是流程的分析，专注于 proxy 层面的设计讲解，而不会贴大量的代码\nkube-proxy软件设计 kube-proxy 在设计上分为三个模块 server 于 proxy：\nserver: 是一个常驻进程用于处理service的事件 proxy: 是 kube-proxy 的工作核心，实际上的角色是一个 service controller，通过监听 node, service, endpoint 而生成规则 proxier: 是实现service的组件，例如iptables, ipvs\u0026hellip;. 如何快速读懂kube-proxy源码 要想快速读懂 kube-proxy 源码就需要对 kube-proxy 设计有深刻的了解，例如需要看 kube-proxy 的实现，我们就可以看 proxy的部分，下列是 proxy 部分的目录结构\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $ tree -L 1 . ├── BUILD ├── OWNERS ├── apis ├── config ├── healthcheck ├── iptables ├── ipvs ├── metaproxier ├── metrics ├── userspace ├── util ├── winuserspace ├── winkernel ├── doc.go ├── endpoints.go ├── endpoints_test.go ├── endpointslicecache.go ├── endpointslicecache_test.go ├── service.go ├── service_test.go ├── topology.go ├── topology_test.go └── types.go 目录 ipvs, iptables, 就是所知的 kube-proxy 提供的两种 load balancer 目录 apis, 则是kube-proxy 配置文件资源类型的定义，--config=/etc/kubernetes/kube-proxy-config.yaml 所指定问题的shema 目录config: 定义了每种资源的handler需要实现什么 service.go, endpoints.go：是controller的实现 type.go: 是每个资源的interface定义，例如： Provider: 规定了每个proxier需要实现什么 ServicePort: service 控制器需要实现什么 Endpoint: service 控制器需要实现什么 上述是整个 proxy 的一级结构\nservice controller service控制器换句话说，就是工作内容类似于kubernetes集群中的pod控制器那些，所作的工作就是监听对应资源做出相应事件处理，而这个处理被定义为 handler\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 type Provider interface { config.EndpointsHandler config.EndpointSliceHandler config.ServiceHandler config.NodeHandler // Sync immediately synchronizes the Provider\u0026#39;s current state to proxy rules. Sync() // SyncLoop runs periodic work. // This is expected to run as a goroutine or as the main loop of the app. // It does not return. SyncLoop() } 由上面代码可以看到，每一个Provider 即 proxier （用于实现的LB的控制器）都需要包含对应资源的事件处理函数于 一个 Sync() 和 SyncLoop()，所以这里将总结为 controller 而不是用于这里给到的术语\n同理，其他类型的 controller 则是相同与 service controller\n深入理解proxier 这里将以 ipvs 为例，如图所示，这将是一个 proxier 的实现，而 proxier.go 则是真实的 proxier 实现\n图：Kubernetes API 请求的请求处理步骤图（详细） 而 ipvs 的 proxier 则是如下定义的\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 type Proxier struct { // endpointsChanges and serviceChanges contains all changes to endpoints and // services that happened since last syncProxyRules call. For a single object, // changes are accumulated, i.e. previous is state from before all of them, // current is state after applying all of those. endpointsChanges *proxy.EndpointChangeTracker serviceChanges *proxy.ServiceChangeTracker mu sync.Mutex // protects the following fields serviceMap proxy.ServiceMap endpointsMap proxy.EndpointsMap portsMap map[utilproxy.LocalPort]utilproxy.Closeable nodeLabels map[string]string // endpointsSynced, endpointSlicesSynced, and servicesSynced are set to true when // corresponding objects are synced after startup. This is used to avoid updating // ipvs rules with some partial data after kube-proxy restart. endpointsSynced bool endpointSlicesSynced bool servicesSynced bool initialized int32 syncRunner *async.BoundedFrequencyRunner // governs calls to syncProxyRules // These are effectively const and do not need the mutex to be held. syncPeriod time.Duration minSyncPeriod time.Duration // Values are CIDR\u0026#39;s to exclude when cleaning up IPVS rules. excludeCIDRs []*net.IPNet // Set to true to set sysctls arp_ignore and arp_announce strictARP bool iptables utiliptables.Interface ipvs utilipvs.Interface ipset utilipset.Interface exec utilexec.Interface masqueradeAll bool masqueradeMark string localDetector proxyutiliptables.LocalTrafficDetector hostname string nodeIP net.IP portMapper utilproxy.PortOpener recorder record.EventRecorder serviceHealthServer healthcheck.ServiceHealthServer healthzServer healthcheck.ProxierHealthUpdater ipvsScheduler string // Added as a member to the struct to allow injection for testing. ipGetter IPGetter // The following buffers are used to reuse memory and avoid allocations // that are significantly impacting performance. iptablesData *bytes.Buffer filterChainsData *bytes.Buffer natChains *bytes.Buffer filterChains *bytes.Buffer natRules *bytes.Buffer filterRules *bytes.Buffer // Added as a member to the struct to allow injection for testing. netlinkHandle NetLinkHandle // ipsetList is the list of ipsets that ipvs proxier used. ipsetList map[string]*IPSet // Values are as a parameter to select the interfaces which nodeport works. nodePortAddresses []string // networkInterfacer defines an interface for several net library functions. // Inject for test purpose. networkInterfacer utilproxy.NetworkInterfacer gracefuldeleteManager *GracefulTerminationManager } 再看这个结构体的 structure，发现他实现了上述提到的 Handler 和 Sync()\n可以看到 Sync() 的实现是调用 runner.Run()\ngo 1 2 3 4 5 6 7 func (proxier *Proxier) Sync() { if proxier.healthzServer != nil { proxier.healthzServer.QueuedUpdate() } metrics.SyncProxyRulesLastQueuedTimestamp.SetToCurrentTime() proxier.syncRunner.Run() } 而 handler 中的任意事件的触发则是调用 Sync()\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // handler 不同的事件均指向 On{rs}Update() 函数 func (proxier *Proxier) OnEndpointsAdd(endpoints *v1.Endpoints) { proxier.OnEndpointsUpdate(nil, endpoints) } // OnEndpointsDelete is called whenever deletion of an existing endpoints object is observed. func (proxier *Proxier) OnEndpointsDelete(endpoints *v1.Endpoints) { proxier.OnEndpointsUpdate(endpoints, nil) } ... // 而 update 调用的则是 Sync() func (proxier *Proxier) OnEndpointsUpdate(oldEndpoints, endpoints *v1.Endpoints) { if proxier.endpointsChanges.Update(oldEndpoints, endpoints) \u0026amp;\u0026amp; proxier.isInitialized() { proxier.Sync() } } 到了这里，明了了 runner 才是这个 proxier 的核心，被定义于 proxier 结构图的 syncRunner 在初始化时被注入了函数 syncProxyRules()\ngo 1 2 3 4 5 6 7 8 func NewProxier(ipt utiliptables.Interface, ... proxier.syncRunner = async.NewBoundedFrequencyRunner(\u0026#34;sync-runner\u0026#34;, proxier.syncProxyRules, minSyncPeriod, syncPeriod, burstSyncs) proxier.gracefuldeleteManager.Run() return proxier, nil } syncProxyRules() 而这个 syncProxyRules() 则是完成了整个 ipvs 以及 service 的生命周期\n对于了解kubernetes架构的同学来说，kube-proxy 完成的功能就是 ipvs 的规则管理，那么换句话说就是干预 ipvs 规则的生命周期，也就是分析函数 syncProxyRules() 是如何干预这些规则的。\nsyncProxyRules() 将动作分为两部分，一是对 ipvs 资源的增改，二是对资源的销毁；引入完概念后，就开始进行分析吧。\n600多行的代码看起来很困难，那就拆分成步骤进行分析\nstep1 前期准备工作 为什么这么叫第一部分呢？看下列代码就知道，做的工作和 ipvs rules 没多大关系\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 func (proxier *Proxier) syncProxyRules() { // 互斥锁 proxier.mu.Lock() defer proxier.mu.Unlock() // don\u0026#39;t sync rules till we\u0026#39;ve received services and endpoints // 在等待接收完信息前，不同步收到的 services和endpoints资源 if !proxier.isInitialized() { klog.V(2).Info(\u0026#34;Not syncing ipvs rules until Services and Endpoints have been received from master\u0026#34;) return } // Keep track of how long syncs take. // 记录同步耗时 start := time.Now() defer func() { metrics.SyncProxyRulesLatency.Observe(metrics.SinceInSeconds(start)) klog.V(4).Infof(\u0026#34;syncProxyRules took %v\u0026#34;, time.Since(start)) }() // 获取本地多个IP地址 localAddrs, err := utilproxy.GetLocalAddrs() if err != nil { klog.Errorf(\u0026#34;Failed to get local addresses during proxy sync: %v, assuming external IPs are not local\u0026#34;, err) } else if len(localAddrs) == 0 { klog.Warning(\u0026#34;No local addresses found, assuming all external IPs are not local\u0026#34;) } localAddrSet := utilnet.IPSet{} localAddrSet.Insert(localAddrs...) // We assume that if this was called, we really want to sync them, // even if nothing changed in the meantime. In other words, callers are // responsible for detecting no-op changes and not calling this function. // 这两步骤正如注释所讲，如果在资源修改的前提下需要同步，也就是update操作包含了增改 serviceUpdateResult := proxy.UpdateServiceMap(proxier.serviceMap, proxier.serviceChanges) endpointUpdateResult := proxier.endpointsMap.Update(proxier.endpointsChanges) // 陈腐的UDP信息处理 staleServices := serviceUpdateResult.UDPStaleClusterIP // merge stale services gathered from updateEndpointsMap for _, svcPortName := range endpointUpdateResult.StaleServiceNames { if svcInfo, ok := proxier.serviceMap[svcPortName]; ok \u0026amp;\u0026amp; svcInfo != nil \u0026amp;\u0026amp; conntrack.IsClearConntrackNeeded(svcInfo.Protocol()) { klog.V(2).Infof(\u0026#34;Stale %s service %v -\u0026gt; %s\u0026#34;, strings.ToLower(string(svcInfo.Protocol())), svcPortName, svcInfo.ClusterIP().String()) staleServices.Insert(svcInfo.ClusterIP().String()) for _, extIP := range svcInfo.ExternalIPStrings() { staleServices.Insert(extIP) } } } step2：构建IPVS规则 首先会经历一些预处理的操作 这部分掠过了 L1042-L1140\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 klog.V(3).Infof(\u0026#34;Syncing ipvs Proxier rules\u0026#34;) // Begin install iptables // Reset all buffers used later. // This is to avoid memory reallocations and thus improve performance. proxier.natChains.Reset() proxier.natRules.Reset() proxier.filterChains.Reset() proxier.filterRules.Reset() // Write table headers. writeLine(proxier.filterChains, \u0026#34;*filter\u0026#34;) writeLine(proxier.natChains, \u0026#34;*nat\u0026#34;) proxier.createAndLinkeKubeChain() // make sure dummy interface exists in the system where ipvs Proxier will bind service address on it _, err = proxier.netlinkHandle.EnsureDummyDevice(DefaultDummyDevice) if err != nil { klog.Errorf(\u0026#34;Failed to create dummy interface: %s, error: %v\u0026#34;, DefaultDummyDevice, err) return } // make sure ip sets exists in the system. for _, set := range proxier.ipsetList { if err := ensureIPSet(set); err != nil { return } set.resetEntries() } // Accumulate the set of local ports that we will be holding open once this update is complete replacementPortsMap := map[utilproxy.LocalPort]utilproxy.Closeable{} // activeIPVSServices represents IPVS service successfully created in this round of sync activeIPVSServices := map[string]bool{} // currentIPVSServices represent IPVS services listed from the system currentIPVSServices := make(map[string]*utilipvs.VirtualServer) // activeBindAddrs represents ip address successfully bind to DefaultDummyDevice in this round of sync activeBindAddrs := map[string]bool{} bindedAddresses, err := proxier.ipGetter.BindedIPs() if err != nil { klog.Errorf(\u0026#34;error listing addresses binded to dummy interface, error: %v\u0026#34;, err) } // 检查是否是nodeport类型 hasNodePort := false for _, svc := range proxier.serviceMap { svcInfo, ok := svc.(*serviceInfo) if ok \u0026amp;\u0026amp; svcInfo.NodePort() != 0 { hasNodePort = true break } } // Both nodeAddresses and nodeIPs can be reused for all nodePort services // and only need to be computed if we have at least one nodePort service. var ( // List of node addresses to listen on if a nodePort is set. nodeAddresses []string // List of node IP addresses to be used as IPVS services if nodePort is set. nodeIPs []net.IP ) if hasNodePort { nodeAddrSet, err := utilproxy.GetNodeAddresses(proxier.nodePortAddresses, proxier.networkInterfacer) if err != nil { klog.Errorf(\u0026#34;Failed to get node ip address matching nodeport cidr: %v\u0026#34;, err) } else { nodeAddresses = nodeAddrSet.List() for _, address := range nodeAddresses { if utilproxy.IsZeroCIDR(address) { nodeIPs, err = proxier.ipGetter.NodeIPs() if err != nil { klog.Errorf(\u0026#34;Failed to list all node IPs from host, err: %v\u0026#34;, err) } break } nodeIPs = append(nodeIPs, net.ParseIP(address)) } } } 接下来是整个构建ipvs规则的关键部分，大概200-300行代码，通过循环 serviceMap 拿到每一个 service 的信息，然后在通过 循环 endpointsMap[svcName] 得到每个 service下所属的 endpoint ，然后构建 ipvs 的规则\nL1141-L1542 这里也包含了 nodeport, clusterIP, ingress等不同的service类型\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 // Build IPVS rules for each service. for svcName, svc := range proxier.serviceMap { svcInfo, ok := svc.(*serviceInfo) if !ok { klog.Errorf(\u0026#34;Failed to cast serviceInfo %q\u0026#34;, svcName.String()) continue } isIPv6 := utilnet.IsIPv6(svcInfo.ClusterIP()) protocol := strings.ToLower(string(svcInfo.Protocol())) // Precompute svcNameString; with many services the many calls // to ServicePortName.String() show up in CPU profiles. svcNameString := svcName.String() // 循环endpoint // Handle traffic that loops back to the originator with SNAT. for _, e := range proxier.endpointsMap[svcName] { ep, ok := e.(*proxy.BaseEndpointInfo) if !ok { klog.Errorf(\u0026#34;Failed to cast BaseEndpointInfo %q\u0026#34;, e.String()) continue } if !ep.IsLocal { continue } epIP := ep.IP() epPort, err := ep.Port() // Error parsing this endpoint has been logged. Skip to next endpoint. if epIP == \u0026#34;\u0026#34; || err != nil { continue } entry := \u0026amp;utilipset.Entry{ IP: epIP, Port: epPort, Protocol: protocol, IP2: epIP, SetType: utilipset.HashIPPortIP, } if valid := proxier.ipsetList[kubeLoopBackIPSet].validateEntry(entry); !valid { klog.Errorf(\u0026#34;%s\u0026#34;, fmt.Sprintf(EntryInvalidErr, entry, proxier.ipsetList[kubeLoopBackIPSet].Name)) continue } proxier.ipsetList[kubeLoopBackIPSet].activeEntries.Insert(entry.String()) } // Capture the clusterIP. // ipset call entry := \u0026amp;utilipset.Entry{ IP: svcInfo.ClusterIP().String(), Port: svcInfo.Port(), Protocol: protocol, SetType: utilipset.HashIPPort, } // add service Cluster IP:Port to kubeServiceAccess ip set for the purpose of solving hairpin. // proxier.kubeServiceAccessSet.activeEntries.Insert(entry.String()) if valid := proxier.ipsetList[kubeClusterIPSet].validateEntry(entry); !valid { klog.Errorf(\u0026#34;%s\u0026#34;, fmt.Sprintf(EntryInvalidErr, entry, proxier.ipsetList[kubeClusterIPSet].Name)) continue } proxier.ipsetList[kubeClusterIPSet].activeEntries.Insert(entry.String()) // ipvs call serv := \u0026amp;utilipvs.VirtualServer{ Address: svcInfo.ClusterIP(), Port: uint16(svcInfo.Port()), Protocol: string(svcInfo.Protocol()), Scheduler: proxier.ipvsScheduler, } // Set session affinity flag and timeout for IPVS service if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP { serv.Flags |= utilipvs.FlagPersistent serv.Timeout = uint32(svcInfo.StickyMaxAgeSeconds()) } // We need to bind ClusterIP to dummy interface, so set `bindAddr` parameter to `true` in syncService() if err := proxier.syncService(svcNameString, serv, true, bindedAddresses); err == nil { activeIPVSServices[serv.String()] = true activeBindAddrs[serv.Address.String()] = true // ExternalTrafficPolicy only works for NodePort and external LB traffic, does not affect ClusterIP // So we still need clusterIP rules in onlyNodeLocalEndpoints mode. if err := proxier.syncEndpoint(svcName, false, serv); err != nil { klog.Errorf(\u0026#34;Failed to sync endpoint for service: %v, err: %v\u0026#34;, serv, err) } } else { klog.Errorf(\u0026#34;Failed to sync service: %v, err: %v\u0026#34;, serv, err) } // Capture externalIPs. for _, externalIP := range svcInfo.ExternalIPStrings() { // If the \u0026#34;external\u0026#34; IP happens to be an IP that is local to this // machine, hold the local port open so no other process can open it // (because the socket might open but it would never work). if (svcInfo.Protocol() != v1.ProtocolSCTP) \u0026amp;\u0026amp; localAddrSet.Has(net.ParseIP(externalIP)) { // We do not start listening on SCTP ports, according to our agreement in the SCTP support KEP lp := utilproxy.LocalPort{ Description: \u0026#34;externalIP for \u0026#34; + svcNameString, IP: externalIP, Port: svcInfo.Port(), Protocol: protocol, } if proxier.portsMap[lp] != nil { klog.V(4).Infof(\u0026#34;Port %s was open before and is still needed\u0026#34;, lp.String()) replacementPortsMap[lp] = proxier.portsMap[lp] } else { socket, err := proxier.portMapper.OpenLocalPort(\u0026amp;lp, isIPv6) if err != nil { msg := fmt.Sprintf(\u0026#34;can\u0026#39;t open %s, skipping this externalIP: %v\u0026#34;, lp.String(), err) proxier.recorder.Eventf( \u0026amp;v1.ObjectReference{ Kind: \u0026#34;Node\u0026#34;, Name: proxier.hostname, UID: types.UID(proxier.hostname), Namespace: \u0026#34;\u0026#34;, }, v1.EventTypeWarning, err.Error(), msg) klog.Error(msg) continue } replacementPortsMap[lp] = socket } } // We\u0026#39;re holding the port, so it\u0026#39;s OK to install IPVS rules. // ipset call entry := \u0026amp;utilipset.Entry{ IP: externalIP, Port: svcInfo.Port(), Protocol: protocol, SetType: utilipset.HashIPPort, } if utilfeature.DefaultFeatureGate.Enabled(features.ExternalPolicyForExternalIP) \u0026amp;\u0026amp; svcInfo.OnlyNodeLocalEndpoints() { if valid := proxier.ipsetList[kubeExternalIPLocalSet].validateEntry(entry); !valid { klog.Errorf(\u0026#34;%s\u0026#34;, fmt.Sprintf(EntryInvalidErr, entry, proxier.ipsetList[kubeExternalIPLocalSet].Name)) continue } proxier.ipsetList[kubeExternalIPLocalSet].activeEntries.Insert(entry.String()) } else { // We have to SNAT packets to external IPs. if valid := proxier.ipsetList[kubeExternalIPSet].validateEntry(entry); !valid { klog.Errorf(\u0026#34;%s\u0026#34;, fmt.Sprintf(EntryInvalidErr, entry, proxier.ipsetList[kubeExternalIPSet].Name)) continue } proxier.ipsetList[kubeExternalIPSet].activeEntries.Insert(entry.String()) } // ipvs call serv := \u0026amp;utilipvs.VirtualServer{ Address: net.ParseIP(externalIP), Port: uint16(svcInfo.Port()), Protocol: string(svcInfo.Protocol()), Scheduler: proxier.ipvsScheduler, } if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP { serv.Flags |= utilipvs.FlagPersistent serv.Timeout = uint32(svcInfo.StickyMaxAgeSeconds()) } if err := proxier.syncService(svcNameString, serv, true, bindedAddresses); err == nil { activeIPVSServices[serv.String()] = true activeBindAddrs[serv.Address.String()] = true onlyNodeLocalEndpoints := false if utilfeature.DefaultFeatureGate.Enabled(features.ExternalPolicyForExternalIP) { onlyNodeLocalEndpoints = svcInfo.OnlyNodeLocalEndpoints() } if err := proxier.syncEndpoint(svcName, onlyNodeLocalEndpoints, serv); err != nil { klog.Errorf(\u0026#34;Failed to sync endpoint for service: %v, err: %v\u0026#34;, serv, err) } } else { klog.Errorf(\u0026#34;Failed to sync service: %v, err: %v\u0026#34;, serv, err) } } // Capture load-balancer ingress. for _, ingress := range svcInfo.LoadBalancerIPStrings() { if ingress != \u0026#34;\u0026#34; { // ipset call entry = \u0026amp;utilipset.Entry{ IP: ingress, Port: svcInfo.Port(), Protocol: protocol, SetType: utilipset.HashIPPort, } // add service load balancer ingressIP:Port to kubeServiceAccess ip set for the purpose of solving hairpin. // proxier.kubeServiceAccessSet.activeEntries.Insert(entry.String()) // If we are proxying globally, we need to masquerade in case we cross nodes. // If we are proxying only locally, we can retain the source IP. if valid := proxier.ipsetList[kubeLoadBalancerSet].validateEntry(entry); !valid { klog.Errorf(\u0026#34;%s\u0026#34;, fmt.Sprintf(EntryInvalidErr, entry, proxier.ipsetList[kubeLoadBalancerSet].Name)) continue } proxier.ipsetList[kubeLoadBalancerSet].activeEntries.Insert(entry.String()) // insert loadbalancer entry to lbIngressLocalSet if service externaltrafficpolicy=local if svcInfo.OnlyNodeLocalEndpoints() { if valid := proxier.ipsetList[kubeLoadBalancerLocalSet].validateEntry(entry); !valid { klog.Errorf(\u0026#34;%s\u0026#34;, fmt.Sprintf(EntryInvalidErr, entry, proxier.ipsetList[kubeLoadBalancerLocalSet].Name)) continue } proxier.ipsetList[kubeLoadBalancerLocalSet].activeEntries.Insert(entry.String()) } if len(svcInfo.LoadBalancerSourceRanges()) != 0 { // The service firewall rules are created based on ServiceSpec.loadBalancerSourceRanges field. // This currently works for loadbalancers that preserves source ips. // For loadbalancers which direct traffic to service NodePort, the firewall rules will not apply. if valid := proxier.ipsetList[kubeLoadbalancerFWSet].validateEntry(entry); !valid { klog.Errorf(\u0026#34;%s\u0026#34;, fmt.Sprintf(EntryInvalidErr, entry, proxier.ipsetList[kubeLoadbalancerFWSet].Name)) continue } proxier.ipsetList[kubeLoadbalancerFWSet].activeEntries.Insert(entry.String()) allowFromNode := false for _, src := range svcInfo.LoadBalancerSourceRanges() { // ipset call entry = \u0026amp;utilipset.Entry{ IP: ingress, Port: svcInfo.Port(), Protocol: protocol, Net: src, SetType: utilipset.HashIPPortNet, } // enumerate all white list source cidr if valid := proxier.ipsetList[kubeLoadBalancerSourceCIDRSet].validateEntry(entry); !valid { klog.Errorf(\u0026#34;%s\u0026#34;, fmt.Sprintf(EntryInvalidErr, entry, proxier.ipsetList[kubeLoadBalancerSourceCIDRSet].Name)) continue } proxier.ipsetList[kubeLoadBalancerSourceCIDRSet].activeEntries.Insert(entry.String()) // ignore error because it has been validated _, cidr, _ := net.ParseCIDR(src) if cidr.Contains(proxier.nodeIP) { allowFromNode = true } } // generally, ip route rule was added to intercept request to loadbalancer vip from the // loadbalancer\u0026#39;s backend hosts. In this case, request will not hit the loadbalancer but loop back directly. // Need to add the following rule to allow request on host. if allowFromNode { entry = \u0026amp;utilipset.Entry{ IP: ingress, Port: svcInfo.Port(), Protocol: protocol, IP2: ingress, SetType: utilipset.HashIPPortIP, } // enumerate all white list source ip if valid := proxier.ipsetList[kubeLoadBalancerSourceIPSet].validateEntry(entry); !valid { klog.Errorf(\u0026#34;%s\u0026#34;, fmt.Sprintf(EntryInvalidErr, entry, proxier.ipsetList[kubeLoadBalancerSourceIPSet].Name)) continue } proxier.ipsetList[kubeLoadBalancerSourceIPSet].activeEntries.Insert(entry.String()) } } // ipvs call serv := \u0026amp;utilipvs.VirtualServer{ Address: net.ParseIP(ingress), Port: uint16(svcInfo.Port()), Protocol: string(svcInfo.Protocol()), Scheduler: proxier.ipvsScheduler, } if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP { serv.Flags |= utilipvs.FlagPersistent serv.Timeout = uint32(svcInfo.StickyMaxAgeSeconds()) } if err := proxier.syncService(svcNameString, serv, true, bindedAddresses); err == nil { activeIPVSServices[serv.String()] = true activeBindAddrs[serv.Address.String()] = true if err := proxier.syncEndpoint(svcName, svcInfo.OnlyNodeLocalEndpoints(), serv); err != nil { klog.Errorf(\u0026#34;Failed to sync endpoint for service: %v, err: %v\u0026#34;, serv, err) } } else { klog.Errorf(\u0026#34;Failed to sync service: %v, err: %v\u0026#34;, serv, err) } } } if svcInfo.NodePort() != 0 { if len(nodeAddresses) == 0 || len(nodeIPs) == 0 { // Skip nodePort configuration since an error occurred when // computing nodeAddresses or nodeIPs. continue } var lps []utilproxy.LocalPort for _, address := range nodeAddresses { lp := utilproxy.LocalPort{ Description: \u0026#34;nodePort for \u0026#34; + svcNameString, IP: address, Port: svcInfo.NodePort(), Protocol: protocol, } if utilproxy.IsZeroCIDR(address) { // Empty IP address means all lp.IP = \u0026#34;\u0026#34; lps = append(lps, lp) // If we encounter a zero CIDR, then there is no point in processing the rest of the addresses. break } lps = append(lps, lp) } // For ports on node IPs, open the actual port and hold it. for _, lp := range lps { if proxier.portsMap[lp] != nil { klog.V(4).Infof(\u0026#34;Port %s was open before and is still needed\u0026#34;, lp.String()) replacementPortsMap[lp] = proxier.portsMap[lp] // We do not start listening on SCTP ports, according to our agreement in the // SCTP support KEP } else if svcInfo.Protocol() != v1.ProtocolSCTP { socket, err := proxier.portMapper.OpenLocalPort(\u0026amp;lp, isIPv6) if err != nil { klog.Errorf(\u0026#34;can\u0026#39;t open %s, skipping this nodePort: %v\u0026#34;, lp.String(), err) continue } if lp.Protocol == \u0026#34;udp\u0026#34; { conntrack.ClearEntriesForPort(proxier.exec, lp.Port, isIPv6, v1.ProtocolUDP) } replacementPortsMap[lp] = socket } // We\u0026#39;re holding the port, so it\u0026#39;s OK to install ipvs rules. } // Nodeports need SNAT, unless they\u0026#39;re local. // ipset call var ( nodePortSet *IPSet entries []*utilipset.Entry ) switch protocol { case \u0026#34;tcp\u0026#34;: nodePortSet = proxier.ipsetList[kubeNodePortSetTCP] entries = []*utilipset.Entry{{ // No need to provide ip info Port: svcInfo.NodePort(), Protocol: protocol, SetType: utilipset.BitmapPort, }} case \u0026#34;udp\u0026#34;: nodePortSet = proxier.ipsetList[kubeNodePortSetUDP] entries = []*utilipset.Entry{{ // No need to provide ip info Port: svcInfo.NodePort(), Protocol: protocol, SetType: utilipset.BitmapPort, }} case \u0026#34;sctp\u0026#34;: nodePortSet = proxier.ipsetList[kubeNodePortSetSCTP] // Since hash ip:port is used for SCTP, all the nodeIPs to be used in the SCTP ipset entries. entries = []*utilipset.Entry{} for _, nodeIP := range nodeIPs { entries = append(entries, \u0026amp;utilipset.Entry{ IP: nodeIP.String(), Port: svcInfo.NodePort(), Protocol: protocol, SetType: utilipset.HashIPPort, }) } default: // It should never hit klog.Errorf(\u0026#34;Unsupported protocol type: %s\u0026#34;, protocol) } if nodePortSet != nil { entryInvalidErr := false for _, entry := range entries { if valid := nodePortSet.validateEntry(entry); !valid { klog.Errorf(\u0026#34;%s\u0026#34;, fmt.Sprintf(EntryInvalidErr, entry, nodePortSet.Name)) entryInvalidErr = true break } nodePortSet.activeEntries.Insert(entry.String()) } if entryInvalidErr { continue } } // Add externaltrafficpolicy=local type nodeport entry if svcInfo.OnlyNodeLocalEndpoints() { var nodePortLocalSet *IPSet switch protocol { case \u0026#34;tcp\u0026#34;: nodePortLocalSet = proxier.ipsetList[kubeNodePortLocalSetTCP] case \u0026#34;udp\u0026#34;: nodePortLocalSet = proxier.ipsetList[kubeNodePortLocalSetUDP] case \u0026#34;sctp\u0026#34;: nodePortLocalSet = proxier.ipsetList[kubeNodePortLocalSetSCTP] default: // It should never hit klog.Errorf(\u0026#34;Unsupported protocol type: %s\u0026#34;, protocol) } if nodePortLocalSet != nil { entryInvalidErr := false for _, entry := range entries { if valid := nodePortLocalSet.validateEntry(entry); !valid { klog.Errorf(\u0026#34;%s\u0026#34;, fmt.Sprintf(EntryInvalidErr, entry, nodePortLocalSet.Name)) entryInvalidErr = true break } nodePortLocalSet.activeEntries.Insert(entry.String()) } if entryInvalidErr { continue } } } // Build ipvs kernel routes for each node ip address for _, nodeIP := range nodeIPs { // ipvs call serv := \u0026amp;utilipvs.VirtualServer{ Address: nodeIP, Port: uint16(svcInfo.NodePort()), Protocol: string(svcInfo.Protocol()), Scheduler: proxier.ipvsScheduler, } if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP { serv.Flags |= utilipvs.FlagPersistent serv.Timeout = uint32(svcInfo.StickyMaxAgeSeconds()) } // There is no need to bind Node IP to dummy interface, so set parameter `bindAddr` to `false`. if err := proxier.syncService(svcNameString, serv, false, bindedAddresses); err == nil { activeIPVSServices[serv.String()] = true if err := proxier.syncEndpoint(svcName, svcInfo.OnlyNodeLocalEndpoints(), serv); err != nil { klog.Errorf(\u0026#34;Failed to sync endpoint for service: %v, err: %v\u0026#34;, serv, err) } } else { klog.Errorf(\u0026#34;Failed to sync service: %v, err: %v\u0026#34;, serv, err) } } } } 其中有两个非常重要的函数 syncService() 于 syncEndpoint() 这两个定义了同步的过程\nsyncService() 函数表示了增加或删除一个service，如果存在则修改，如果存在则添加\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 func (proxier *Proxier) syncService(svcName string, vs *utilipvs.VirtualServer, bindAddr bool, bindedAddresses sets.String) error { appliedVirtualServer, _ := proxier.ipvs.GetVirtualServer(vs) if appliedVirtualServer == nil || !appliedVirtualServer.Equal(vs) { if appliedVirtualServer == nil { // IPVS service is not found, create a new service klog.V(3).Infof(\u0026#34;Adding new service %q %s:%d/%s\u0026#34;, svcName, vs.Address, vs.Port, vs.Protocol) if err := proxier.ipvs.AddVirtualServer(vs); err != nil { klog.Errorf(\u0026#34;Failed to add IPVS service %q: %v\u0026#34;, svcName, err) return err } } else { // IPVS service was changed, update the existing one // During updates, service VIP will not go down klog.V(3).Infof(\u0026#34;IPVS service %s was changed\u0026#34;, svcName) if err := proxier.ipvs.UpdateVirtualServer(vs); err != nil { klog.Errorf(\u0026#34;Failed to update IPVS service, err:%v\u0026#34;, err) return err } } } // bind service address to dummy interface if bindAddr { // always attempt to bind if bindedAddresses is nil, // otherwise check if it\u0026#39;s already binded and return early if bindedAddresses != nil \u0026amp;\u0026amp; bindedAddresses.Has(vs.Address.String()) { return nil } klog.V(4).Infof(\u0026#34;Bind addr %s\u0026#34;, vs.Address.String()) _, err := proxier.netlinkHandle.EnsureAddressBind(vs.Address.String(), DefaultDummyDevice) if err != nil { klog.Errorf(\u0026#34;Failed to bind service address to dummy device %q: %v\u0026#34;, svcName, err) return err } } return nil } 同理 syncEndpoint() 也是相同的操作\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 func (proxier *Proxier) syncEndpoint(svcPortName proxy.ServicePortName, onlyNodeLocalEndpoints bool, vs *utilipvs.VirtualServer) error { appliedVirtualServer, err := proxier.ipvs.GetVirtualServer(vs) if err != nil || appliedVirtualServer == nil { klog.Errorf(\u0026#34;Failed to get IPVS service, error: %v\u0026#34;, err) return err } // curEndpoints represents IPVS destinations listed from current system. curEndpoints := sets.NewString() // newEndpoints represents Endpoints watched from API Server. newEndpoints := sets.NewString() curDests, err := proxier.ipvs.GetRealServers(appliedVirtualServer) if err != nil { klog.Errorf(\u0026#34;Failed to list IPVS destinations, error: %v\u0026#34;, err) return err } for _, des := range curDests { curEndpoints.Insert(des.String()) } endpoints := proxier.endpointsMap[svcPortName] // Service Topology will not be enabled in the following cases: // 1. externalTrafficPolicy=Local (mutually exclusive with service topology). // 2. ServiceTopology is not enabled. // 3. EndpointSlice is not enabled (service topology depends on endpoint slice // to get topology information). if !onlyNodeLocalEndpoints \u0026amp;\u0026amp; utilfeature.DefaultFeatureGate.Enabled(features.ServiceTopology) \u0026amp;\u0026amp; utilfeature.DefaultFeatureGate.Enabled(features.EndpointSliceProxying) { endpoints = proxy.FilterTopologyEndpoint(proxier.nodeLabels, proxier.serviceMap[svcPortName].TopologyKeys(), endpoints) } for _, epInfo := range endpoints { if onlyNodeLocalEndpoints \u0026amp;\u0026amp; !epInfo.GetIsLocal() { continue } newEndpoints.Insert(epInfo.String()) } // Create new endpoints for _, ep := range newEndpoints.List() { ip, port, err := net.SplitHostPort(ep) if err != nil { klog.Errorf(\u0026#34;Failed to parse endpoint: %v, error: %v\u0026#34;, ep, err) continue } portNum, err := strconv.Atoi(port) if err != nil { klog.Errorf(\u0026#34;Failed to parse endpoint port %s, error: %v\u0026#34;, port, err) continue } newDest := \u0026amp;utilipvs.RealServer{ Address: net.ParseIP(ip), Port: uint16(portNum), Weight: 1, } if curEndpoints.Has(ep) { // check if newEndpoint is in gracefulDelete list, if true, delete this ep immediately uniqueRS := GetUniqueRSName(vs, newDest) if !proxier.gracefuldeleteManager.InTerminationList(uniqueRS) { continue } klog.V(5).Infof(\u0026#34;new ep %q is in graceful delete list\u0026#34;, uniqueRS) err := proxier.gracefuldeleteManager.MoveRSOutofGracefulDeleteList(uniqueRS) if err != nil { klog.Errorf(\u0026#34;Failed to delete endpoint: %v in gracefulDeleteQueue, error: %v\u0026#34;, ep, err) continue } } err = proxier.ipvs.AddRealServer(appliedVirtualServer, newDest) if err != nil { klog.Errorf(\u0026#34;Failed to add destination: %v, error: %v\u0026#34;, newDest, err) continue } } // Delete old endpoints for _, ep := range curEndpoints.Difference(newEndpoints).UnsortedList() { // if curEndpoint is in gracefulDelete, skip uniqueRS := vs.String() + \u0026#34;/\u0026#34; + ep if proxier.gracefuldeleteManager.InTerminationList(uniqueRS) { continue } ip, port, err := net.SplitHostPort(ep) if err != nil { klog.Errorf(\u0026#34;Failed to parse endpoint: %v, error: %v\u0026#34;, ep, err) continue } portNum, err := strconv.Atoi(port) if err != nil { klog.Errorf(\u0026#34;Failed to parse endpoint port %s, error: %v\u0026#34;, port, err) continue } delDest := \u0026amp;utilipvs.RealServer{ Address: net.ParseIP(ip), Port: uint16(portNum), } klog.V(5).Infof(\u0026#34;Using graceful delete to delete: %v\u0026#34;, uniqueRS) err = proxier.gracefuldeleteManager.GracefulDeleteRS(appliedVirtualServer, delDest) if err != nil { klog.Errorf(\u0026#34;Failed to delete destination: %v, error: %v\u0026#34;, uniqueRS, err) continue } } return nil } 接下来就是同步规则的步骤了，L1544-L1621\nstep 3：规则的删除 粗略翻到这里可能有一个疑问？没有提到删除，删除时包含在 syncXX() 函数中的\n例如在 syncEndpoint() 中会看是否在 终止列表中，如果在跳过，如果不在加入\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 if curEndpoints.Has(ep) { // check if newEndpoint is in gracefulDelete list, if true, delete this ep immediately uniqueRS := GetUniqueRSName(vs, newDest) if !proxier.gracefuldeleteManager.InTerminationList(uniqueRS) { continue } klog.V(5).Infof(\u0026#34;new ep %q is in graceful delete list\u0026#34;, uniqueRS) err := proxier.gracefuldeleteManager.MoveRSOutofGracefulDeleteList(uniqueRS) if err != nil { klog.Errorf(\u0026#34;Failed to delete endpoint: %v in gracefulDeleteQueue, error: %v\u0026#34;, ep, err) continue } } 而 gracefuldeleteManager 是一个 一直运行的协程，在初始化 proxier 时被 Run()\ngo 1 2 3 4 // Run start a goroutine to try to delete rs in the graceful delete rsList with an interval 1 minute func (m *GracefulTerminationManager) Run() { go wait.Until(m.tryDeleteRs, rsCheckDeleteInterval, wait.NeverStop) } proxier.go\ngo 1 2 3 proxier.syncRunner = async.NewBoundedFrequencyRunner(\u0026#34;sync-runner\u0026#34;, proxier.syncProxyRules, minSyncPeriod, syncPeriod, burstSyncs) proxier.gracefuldeleteManager.Run() return proxier, nil 总结 到这里已经清楚的掌握了 kube-proxy 的架构，接下来的会为扩展kubernetes中service架构，以及手撸一个 kube-proxy做准备；本系列第三部分：如何扩展现有的kube-proxy架构\n文中的知识都是个人根据理解整理的，如有不对的地方欢迎指出，感谢各位大佬\nReference\n​[1] dual-stack service\n","permalink":"https://www.161616.top/ch19-kube-proxy-code/","summary":"本文是关于Kubernetes service解析的第3章 深入理解Kubernetes service - 你真的理解service吗? 深入理解Kubernetes service - EndpointSlices做了什么？ 深入理解Kubernetes service - kube-proxy架构分析 深入理解Kubernetes service - 如何扩展现有的kube-proxy架构 kube-proxy如何保证规则的一致性 所有关于Kubernetes service 部分代码上传至仓库 github.com/cylonchau/kube-haproxy\n前提概述 kubernetes集群中运行在每个Worker节点上的组件 kube-proxy，本文讲解的是如何快速的了解 kube-proxy 的软件架构，而不是流程的分析，专注于 proxy 层面的设计讲解，而不会贴大量的代码\nkube-proxy软件设计 kube-proxy 在设计上分为三个模块 server 于 proxy：\nserver: 是一个常驻进程用于处理service的事件 proxy: 是 kube-proxy 的工作核心，实际上的角色是一个 service controller，通过监听 node, service, endpoint 而生成规则 proxier: 是实现service的组件，例如iptables, ipvs\u0026hellip;. 如何快速读懂kube-proxy源码 要想快速读懂 kube-proxy 源码就需要对 kube-proxy 设计有深刻的了解，例如需要看 kube-proxy 的实现，我们就可以看 proxy的部分，下列是 proxy 部分的目录结构\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $ tree -L 1 .","title":"深入理解Kubernetes service - kube-proxy架构分析"},{"content":" 本文是关于Kubernetes service解析的第1章 深入理解Kubernetes service - 你真的理解service吗? 深入理解Kubernetes service - EndpointSlices做了什么？ 深入理解Kubernetes service - kube-proxy架构分析 深入理解Kubernetes service - 如何扩展现有的kube-proxy架构 kube-proxy如何保证规则的一致性 所有关于Kubernetes service 部分代码上传至仓库 github.com/cylonchau/kube-haproxy\n前景 对于了解kubernetes架构时，已知的是 service 是kubernetes在设计时为了避免Pod在频繁创建和销毁时IP变更问题，从而给集群内服务（一组Pod）提供访问的一个入口。而Pod在这里的角色是 『后端』( backend ) ，而 service 的角色是 『前端』( frontend )。本文将阐述service的生命周期\n为什么需要了解这部分内容呢 对于 without kube-proxy来说，这部分是最重要的部分，因为service的生成不是kube-proxy来完成的，而这部分也就是service ip定义的核心。\n控制器 service的资源创建很奇妙，继不属于 controller-manager 组件，也不属于 kube-proxy 组件，而是存在于 apiserver 中的一个被成为控制器的组件；而这个控制器又区别于准入控制器。更准确来说，准入控制器是位于kubeapiserver中的组件，而 控制器 则是存在于单独的一个包，这里包含了很多kubernetes集群的公共组件的功能，其中就有service。这也就是在操作kubernetes时 当 controller-manager 于 kube-proxy 未工作时，也可以准确的为service分配IP。\n首先在构建出apiserver时，也就是代码 cmd/kube-apiserver/app/server.go\ngo 1 2 3 4 serviceIPRange, apiServerServiceIP, err := master.ServiceIPRange(s.PrimaryServiceClusterIPRange) if err != nil { return nil, nil, nil, nil, err } master.ServiceIPRange 承接了为service分配IP的功能，这部分逻辑就很简单了\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func ServiceIPRange(passedServiceClusterIPRange net.IPNet) (net.IPNet, net.IP, error) { serviceClusterIPRange := passedServiceClusterIPRange if passedServiceClusterIPRange.IP == nil { klog.Warningf(\u0026#34;No CIDR for service cluster IPs specified. Default value which was %s is deprecated and will be removed in future releases. Please specify it using --service-cluster-ip-range on kube-apiserver.\u0026#34;, kubeoptions.DefaultServiceIPCIDR.String()) serviceClusterIPRange = kubeoptions.DefaultServiceIPCIDR } size := integer.Int64Min(utilnet.RangeSize(\u0026amp;serviceClusterIPRange), 1\u0026lt;\u0026lt;16) if size \u0026lt; 8 { return net.IPNet{}, net.IP{}, fmt.Errorf(\u0026#34;the service cluster IP range must be at least %d IP addresses\u0026#34;, 8) } // Select the first valid IP from ServiceClusterIPRange to use as the GenericAPIServer service IP. apiServerServiceIP, err := utilnet.GetIndexedIP(\u0026amp;serviceClusterIPRange, 1) if err != nil { return net.IPNet{}, net.IP{}, err } klog.V(4).Infof(\u0026#34;Setting service IP to %q (read-write).\u0026#34;, apiServerServiceIP) return serviceClusterIPRange, apiServerServiceIP, nil } 而后kube-apiserver为service分为两类\napiserver 地址在集群内的service，在代码中表示为 APIServerServiceIP Service，--service-cluster-ip-range 配置指定的ip，通过『逗号』分割可以为两个 有了对 service 更好的理解后，接下来开始本系列第二节深入理解Kubernetes service - kube-proxy软件架构分析\nReference\n​[1] dual-stack service\n","permalink":"https://www.161616.top/ch17-service-controller/","summary":"本文是关于Kubernetes service解析的第1章 深入理解Kubernetes service - 你真的理解service吗? 深入理解Kubernetes service - EndpointSlices做了什么？ 深入理解Kubernetes service - kube-proxy架构分析 深入理解Kubernetes service - 如何扩展现有的kube-proxy架构 kube-proxy如何保证规则的一致性 所有关于Kubernetes service 部分代码上传至仓库 github.com/cylonchau/kube-haproxy\n前景 对于了解kubernetes架构时，已知的是 service 是kubernetes在设计时为了避免Pod在频繁创建和销毁时IP变更问题，从而给集群内服务（一组Pod）提供访问的一个入口。而Pod在这里的角色是 『后端』( backend ) ，而 service 的角色是 『前端』( frontend )。本文将阐述service的生命周期\n为什么需要了解这部分内容呢 对于 without kube-proxy来说，这部分是最重要的部分，因为service的生成不是kube-proxy来完成的，而这部分也就是service ip定义的核心。\n控制器 service的资源创建很奇妙，继不属于 controller-manager 组件，也不属于 kube-proxy 组件，而是存在于 apiserver 中的一个被成为控制器的组件；而这个控制器又区别于准入控制器。更准确来说，准入控制器是位于kubeapiserver中的组件，而 控制器 则是存在于单独的一个包，这里包含了很多kubernetes集群的公共组件的功能，其中就有service。这也就是在操作kubernetes时 当 controller-manager 于 kube-proxy 未工作时，也可以准确的为service分配IP。\n首先在构建出apiserver时，也就是代码 cmd/kube-apiserver/app/server.go\ngo 1 2 3 4 serviceIPRange, apiServerServiceIP, err := master.ServiceIPRange(s.PrimaryServiceClusterIPRange) if err !","title":"深入理解Kubernetes service - 你真的理解service吗？"},{"content":"haproxy作为一个『代理软件』如果当工作与 HTTP 模式下，所有经由haproxy的的连接的请求和响应都取决于 frondend 中配置的 『http_connection_mode』 即 haproxy 中 frontend 与 backend 的组合，而haproxy 支持 3 种连接模式：\nKAL keep alive: frontend 中配置为 http-keep-alive ; 这是默认模式，这也是http中的keepalive 表示所有请求和响应都得到处理，连接保持打开状态，但在响应和新请求之间处于空闲状态。 SCL server close : frontend 中配置为 http-server-close ; 接收到响应结束后，面向服务器的连接关闭，但面向客户端的连接保持打开状态 CLO close: frontend 中配置为 httpclose ；连接在响应结束后关闭，并在两个方向上附加 \u0026ldquo;Connection: close\u0026rdquo; 。 下列矩阵表示的是通过 frondend 与 backend 之间两端的代理模式，这个模式是对称的\ntext 1 2 3 4 5 6 7 | KAL | SCL | CLO ----+-----+-----+---- KAL | KAL | SCL | CLO ----+-----+-----+---- mode SCL | SCL | SCL | CLO ----+-----+-----+---- CLO | CLO | CLO | CLO 对于http选项的说明 选项 说明 forwardfor 这个选项同时存在于backend 与 frontend端，但backend中的优先级超过frontend 如果同时设置了这个参数，那么 backend段的子参数将优先与 frontend 一端 httpchk 启用http协议检查来检测server的健康状态，默认情况下状态检查是仅建立一个tcp连接 httpclose 这个选项代表了haproxy 对于http协议持久连接方便的配置 Reference：configuration.txt\n","permalink":"https://www.161616.top/haproxy-http-connection-mode/","summary":"haproxy作为一个『代理软件』如果当工作与 HTTP 模式下，所有经由haproxy的的连接的请求和响应都取决于 frondend 中配置的 『http_connection_mode』 即 haproxy 中 frontend 与 backend 的组合，而haproxy 支持 3 种连接模式：\nKAL keep alive: frontend 中配置为 http-keep-alive ; 这是默认模式，这也是http中的keepalive 表示所有请求和响应都得到处理，连接保持打开状态，但在响应和新请求之间处于空闲状态。 SCL server close : frontend 中配置为 http-server-close ; 接收到响应结束后，面向服务器的连接关闭，但面向客户端的连接保持打开状态 CLO close: frontend 中配置为 httpclose ；连接在响应结束后关闭，并在两个方向上附加 \u0026ldquo;Connection: close\u0026rdquo; 。 下列矩阵表示的是通过 frondend 与 backend 之间两端的代理模式，这个模式是对称的\ntext 1 2 3 4 5 6 7 | KAL | SCL | CLO ----+-----+-----+---- KAL | KAL | SCL | CLO ----+-----+-----+---- mode SCL | SCL | SCL | CLO ----+-----+-----+---- CLO | CLO | CLO | CLO 对于http选项的说明 选项 说明 forwardfor 这个选项同时存在于backend 与 frontend端，但backend中的优先级超过frontend 如果同时设置了这个参数，那么 backend段的子参数将优先与 frontend 一端 httpchk 启用http协议检查来检测server的健康状态，默认情况下状态检查是仅建立一个tcp连接 httpclose 这个选项代表了haproxy 对于http协议持久连接方便的配置 Reference：configuration.","title":"haproxy 中 http 代理的连接模式"},{"content":"Windows git \u0026ldquo;warning: LF will be replaced by CRLF\u0026rdquo; [1] bash 1 git config --global core.autocrlf false Disable Credential Manager bash 1 2 3 4 5 git config --global credential.modalprompt false git credential-manager remove -force git credential-manager uninstall --force Multi account management [2] step1: clean globle setting\nbash 1 2 git config --global --unset user.name git config --global --unset user.email step2： change config file only ssh\nbash Do not Pop-ups authtication [3] This question is the git shell prompt input user and password in an openssh popup on windows plateform\nbash 1 git config --global core.askPass \u0026#34;\u0026#34; Reference ​[1] Windows git \u0026ldquo;warning: LF will be replaced by CRLF\u0026rdquo;, is that warning tail backward?\n​[2] window下git多账户管理\n​[3] Git shell prompts for password in an OpenSSH popup window\n","permalink":"https://www.161616.top/awesome-git-configration-in-windows/","summary":"Windows git \u0026ldquo;warning: LF will be replaced by CRLF\u0026rdquo; [1] bash 1 git config --global core.autocrlf false Disable Credential Manager bash 1 2 3 4 5 git config --global credential.modalprompt false git credential-manager remove -force git credential-manager uninstall --force Multi account management [2] step1: clean globle setting\nbash 1 2 git config --global --unset user.name git config --global --unset user.email step2： change config file only ssh\nbash Do not Pop-ups authtication [3] This question is the git shell prompt input user and password in an openssh popup on windows plateform","title":"git在windows上常用配置"},{"content":"类型断言 类型断言 type assertion 并不是真正的将 interface 类型转换为另一种确定的类型，只是提供了对 interface 类型的值的访问，通常情况下，这是常见的需求\n类型断言通过 语法 x.(T) ，这将会确定 x 变量中存储的值是否属于 T 类型，通常场景有两种：\n如果 T 不是 interface 类型，而是一个具体的类型，那么这次断言将断言 x 的 动态类型是否与 T 相同 如果 T 是 interface 类型，这次断言 x 的动态类型是否实现了 T go 1 2 3 4 5 6 7 8 9 10 11 12 var x interface{} = \u0026#34;foo\u0026#34; var s string = x.(string) fmt.Println(s) // \u0026#34;foo\u0026#34; s, ok := x.(string) fmt.Println(s, ok) // \u0026#34;foo true\u0026#34; n, ok := x.(int) fmt.Println(n, ok) // \u0026#34;0 false\u0026#34; n = x.(int) // ILLEGAL Note：在断言时，x 的类型必须为 interface{}\n那么怎么理解 T=interface 和 T != interface 这两句话呢\nT != interface 则是一个正常的断言，即 x (interface) 是否等于 T (really type)，这里 x 必须为 interface，T 则可以为任意类型而不是变量 T=interface 时 不能说是一个断言，而是一个对 interface 的断言，此时 x 必须为 interface，T 也必须为 interface 如下面代码所示：\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type s interface {} // 一个interface类型的变量a var a interface{} a = a a = 1 // 一个int 类型的变量b var b = 20 b = b x, ok := a.(s) // 1 true 因为a实现了interface x, ok := b.(s) // false b 不是interface不能断言 x, ok := a.(int) // 1 true 因为a的值为int x := a.(string) // 当一个返回参数时将触发panic 类型转换 类型转换 type switch 是指类型断言的应用场景，是通过对一个interface类型的变量进行多次断言，以匹配到真实的数据类型\ngo 1 2 3 4 5 6 7 8 9 10 11 12 var x interface{} = \u0026#34;foo\u0026#34; switch v := x.(type) { case nil: fmt.Println(\u0026#34;x is nil\u0026#34;) // here v has type interface{} case int: fmt.Println(\u0026#34;x is\u0026#34;, v) // here v has type int case bool, string: fmt.Println(\u0026#34;x is bool or string\u0026#34;) // here v has type interface{} default: fmt.Println(\u0026#34;type unknown\u0026#34;) // here v has type interface{} } ","permalink":"https://www.161616.top/go-type-assertion/","summary":"类型断言 类型断言 type assertion 并不是真正的将 interface 类型转换为另一种确定的类型，只是提供了对 interface 类型的值的访问，通常情况下，这是常见的需求\n类型断言通过 语法 x.(T) ，这将会确定 x 变量中存储的值是否属于 T 类型，通常场景有两种：\n如果 T 不是 interface 类型，而是一个具体的类型，那么这次断言将断言 x 的 动态类型是否与 T 相同 如果 T 是 interface 类型，这次断言 x 的动态类型是否实现了 T go 1 2 3 4 5 6 7 8 9 10 11 12 var x interface{} = \u0026#34;foo\u0026#34; var s string = x.(string) fmt.Println(s) // \u0026#34;foo\u0026#34; s, ok := x.(string) fmt.Println(s, ok) // \u0026#34;foo true\u0026#34; n, ok := x.","title":"Go中的类型断言与类型转换"},{"content":"方法1：dial 使用 net.DialTimeout 去检查端口的技巧：\n在通过Dial检查端口占用时，需要知道网络中常见的报错状态，而不是 err != nil 都为可用\nConnection reset by peer connection reset by peer 这种错误情况下有以下几种场景：\n基于包过滤的防火墙给予 RST；对于此情况，基于网络模型来说处于网络层与传输层之间的netfilter，如果是防火墙拒绝那么未到应用层无法确认端口 对端应用资源限制而reset，通常为负载过高；对于此场景是已到达应用层 客户端关闭了连接，而服务器还在给客户端发送数据；对于端口检查来说不会到这步 由上面可知，这种错误一定为占用\nConnection timed out Connection timed out 这种场景根本就dial不成功，go中给出了一个专门的事件 opErr.Timeout() 来说明这个错误，故此错误将不能确认端口是否占用\nConnection refused Connection refused 这种场景催在两种情况\n对于 local 场景来说，这将表示端口未监听 对于远端场景来说，这种基本上表示 client 发往 remote ，remote不能接受 host:port 这个连接 通常对于存在两种情况，但多数为端口为监听\nMisconfiguration, such as where a user has mistyped the port number, or is using stale information about what port the service they require is running on. A service error, such as where the service that should be listening on a port has crashed or is otherwise unavailable. 所以此状态可以用于判断端口的状态，而对于端口检测通常为 local，所以可以用作判断依据\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func checkPortIsAvailable(protocol string, port int) { timeoutSecs := 3 addr, err := GetInterfaceIpv4Addr(\u0026#34;eth0\u0026#34;) conn, err := net.DialTimeout(protocol, net.JoinHostPort(addr, strconv.Itoa(port)), time.Duration(timeoutSecs)*time.Second) for { if err != nil { opErr, ok := err.(*net.OpError) if ok \u0026amp;\u0026amp; strings.Contains(opErr.Err.Error(), \u0026#34;refused\u0026#34;) { break } else if opErr.Timeout() { continue } else { continue } } if conn != nil { defer conn.Close() continue } } } 方法2：golib 库 github.com/antelman107/net-wait-go 可以用于等待端口直到状态为open，通过这种方法也可以很好的检测端口是否占用\n","permalink":"https://www.161616.top/goskill-port-is-available/","summary":"方法1：dial 使用 net.DialTimeout 去检查端口的技巧：\n在通过Dial检查端口占用时，需要知道网络中常见的报错状态，而不是 err != nil 都为可用\nConnection reset by peer connection reset by peer 这种错误情况下有以下几种场景：\n基于包过滤的防火墙给予 RST；对于此情况，基于网络模型来说处于网络层与传输层之间的netfilter，如果是防火墙拒绝那么未到应用层无法确认端口 对端应用资源限制而reset，通常为负载过高；对于此场景是已到达应用层 客户端关闭了连接，而服务器还在给客户端发送数据；对于端口检查来说不会到这步 由上面可知，这种错误一定为占用\nConnection timed out Connection timed out 这种场景根本就dial不成功，go中给出了一个专门的事件 opErr.Timeout() 来说明这个错误，故此错误将不能确认端口是否占用\nConnection refused Connection refused 这种场景催在两种情况\n对于 local 场景来说，这将表示端口未监听 对于远端场景来说，这种基本上表示 client 发往 remote ，remote不能接受 host:port 这个连接 通常对于存在两种情况，但多数为端口为监听\nMisconfiguration, such as where a user has mistyped the port number, or is using stale information about what port the service they require is running on.","title":"如何使用go语言来检查端口可用性"},{"content":"haproxy1 VS haproxy2 haproxy2由 2019-06-16 被发布，对于与haproxy1版本来说，haproxy 2.0 增加了对云原生的支持，这使得haproxy 2.0 更适用于云原生环境，对比于 haproxy1.0 在2001年发布来，到 1.9.16 在 2020/07/31 最后一次更新也代表haproxy1.0的结束维护\n为什么选择haproxy2.0 haproxy2.0的核心功能就是集成了云原生架构的支持。包含L7重试, Prometheus metrics, 流量镜像 (traffic shadowing), 多语言可扩展性, gRPC 。haproxy2.0 还增加 基于haproxy2.0 的 Kubernetes Ingress Controller 和强大的 HAProxy Data Plane API，这提供了用于配置和管理 HAProxy 的 REST API\n安装haproxy2.0 对于 Ubuntu/Debian 来说，社区版haproxy提供了更友好的安装方式，用户直接添加对应仓库可以直接安装最新版本的haproxy Debian/Ubuntu HAProxy packages\n对于 CentOS/Fedora 来说，只有Fedora 仓库提供了较为新版的haproxy，通常来在这类平台的Linux都是通过编译安装haproxy\n下载haproxy2.6源码 [ haproxy下载 ]\n安装依赖包\nbash 1 yum install gcc pcre-devel openssl-devel tar make -y 编译程序\nbash 1 2 3 4 5 6 7 8 9 tar xf haproxy-2.6.7.tar.gz \u0026amp;\u0026amp; cd haproxy-2.6.7/ # 查看编译参数 # 直接使用make可以查看编译参数，这是makefile中配置的 make # 编译参数 make TARGET=/app/haproxy USE_ZLIB=1 USE_OPENSSL=1 USE_PCRE=1 make install 默认安装的路径在 /usr/local/ 下\n官方提供的一份 haproxy2.0 配置文件 HAProxy 2.0 configuration\nReference ​[1] How to install HAProxy load balancer on CentOS\n​[2] HAProxy 2.0 and Beyond\nTroubeshooting The configuration file is not declared in the HAPROXY_CFGFILES environment variable, cannot start. bash 1 2 3 4 5 6 7 8 9 10 11 $ haproxy -f haproxy.cfg [NOTICE] (3143) : New program \u0026#39;api\u0026#39; (3144) forked [NOTICE] (3143) : New worker (3145) forked [NOTICE] (3143) : Loading success. time=\u0026#34;2022-12-15T18:43:44+08:00\u0026#34; level=fatal msg=\u0026#34;The configuration file is not declared in the HAPROXY_CFGFILES environment variable, cannot start.\u0026#34; [NOTICE] (3143) : haproxy version is 2.6.7-c55bfdb [NOTICE] (3143) : path to executable is /usr/local/sbin/haproxy [ALERT] (3143) : Current program \u0026#39;api\u0026#39; (3144) exited with code 1 (Exit) [ALERT] (3143) : exit-on-failure: killing every processes with SIGTERM [ALERT] (3143) : Current worker (3145) exited with code 143 (Terminated) [WARNING] (3143) : All workers exited. Exiting... (1) 原因：指定的配置文件必须带有路径 haproxy -f haproxy.cfg 这种是错误的，-f 参数属性为\n如果为目录，则是这个目录下所有的 .cfg 结尾的文件 如果是目录，./\u0026lt;filename\u0026gt; 与 filename 都提示这个报错，必须绝对路径 no users configured bash 1 2 3 4 5 6 haproxy -f /root/haproxy.cfg [NOTICE] (3193) : New program \u0026#39;api\u0026#39; (3194) forked [NOTICE] (3193) : New worker (3195) forked [NOTICE] (3193) : Loading success. time=\u0026#34;2022-12-15T18:45:49+08:00\u0026#34; level=fatal msg=\u0026#34;Error initiating users: no users configured in /root/haproxy.cfg, error: section missing\u0026#34; [NOTICE] (3193) : haproxy version is 2.6.7-c55bfdb 原因：data plane api 程序必须有运行的用户和用户组在配置文件中，官方手册中给出的配置不全 [1] ，对于data plane api部分配置可以参考 [2]\nset gid: operation not permitted bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # haproxy -f /root/haproxy.cfg [NOTICE] (3701) : haproxy version is 2.6.7-c55bfdb [NOTICE] (3701) : path to executable is /usr/local/sbin/haproxy [WARNING] (3701) : config : missing timeouts for frontend \u0026#39;myfrontend\u0026#39;. | While not properly invalid, you will certainly encounter various problems | with such a configuration. To fix this, please ensure that all following | timeouts are set to a non-zero value: \u0026#39;client\u0026#39;, \u0026#39;connect\u0026#39;, \u0026#39;server\u0026#39;. [WARNING] (3701) : config : missing timeouts for backend \u0026#39;web_servers\u0026#39;. | While not properly invalid, you will certainly encounter various problems | with such a configuration. To fix this, please ensure that all following | timeouts are set to a non-zero value: \u0026#39;client\u0026#39;, \u0026#39;connect\u0026#39;, \u0026#39;server\u0026#39;. [NOTICE] (3701) : New program \u0026#39;api\u0026#39; (3702) forked [NOTICE] (3701) : New worker (3703) forked [NOTICE] (3701) : Loading success. set gid: operation not permitted [NOTICE] (3701) : haproxy version is 2.6.7-c55bfdb [NOTICE] (3701) : path to executable is /usr/local/sbin/haproxy [ALERT] (3701) : Current program \u0026#39;api\u0026#39; (3702) exited with code 1 (Exit) [ALERT] (3701) : exit-on-failure: killing every processes with SIGTERM [ALERT] (3701) : Current worker (3703) exited with code 143 (Terminated) [WARNING] (3701) : All workers exited. Exiting... (1) Reference ​[1] HAProxy Community\n​[2] configuration examples\n​[3] SSSD and LDAP\n​[4] Chapter 10. Migrating authentication from nslcd to SSSD\n​[5] OpenLDAP Client 2.4.23: TLS negotiation failure\n​[6] Chapter 10. Migrating authentication from nslcd to SSSD\n​[7] Configure SSSD\n​[8] Configure OpenLDAP SSSD client on CentOS 6/7\n","permalink":"https://www.161616.top/haproxy2/","summary":"haproxy1 VS haproxy2 haproxy2由 2019-06-16 被发布，对于与haproxy1版本来说，haproxy 2.0 增加了对云原生的支持，这使得haproxy 2.0 更适用于云原生环境，对比于 haproxy1.0 在2001年发布来，到 1.9.16 在 2020/07/31 最后一次更新也代表haproxy1.0的结束维护\n为什么选择haproxy2.0 haproxy2.0的核心功能就是集成了云原生架构的支持。包含L7重试, Prometheus metrics, 流量镜像 (traffic shadowing), 多语言可扩展性, gRPC 。haproxy2.0 还增加 基于haproxy2.0 的 Kubernetes Ingress Controller 和强大的 HAProxy Data Plane API，这提供了用于配置和管理 HAProxy 的 REST API\n安装haproxy2.0 对于 Ubuntu/Debian 来说，社区版haproxy提供了更友好的安装方式，用户直接添加对应仓库可以直接安装最新版本的haproxy Debian/Ubuntu HAProxy packages\n对于 CentOS/Fedora 来说，只有Fedora 仓库提供了较为新版的haproxy，通常来在这类平台的Linux都是通过编译安装haproxy\n下载haproxy2.6源码 [ haproxy下载 ]\n安装依赖包\nbash 1 yum install gcc pcre-devel openssl-devel tar make -y 编译程序\nbash 1 2 3 4 5 6 7 8 9 tar xf haproxy-2.","title":"haproxy v1 与 haproxy v2"},{"content":" 工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 perf [1] perf 是基于内核子系统的Linux的性能计数器，也被称为 perf_events，它提供了为所有事件进行性能分析的框架，perf 由两部分组成：\n内核系统调用，用于提供对这些性能数据的访问 用户空间工具，用于提供收集，显示分析这些性能数据的用户空间程序 由于 perf 是内核的一部分，但要想使用 perf 还需要安装另外一部分，通常情况下安装的版本是Linux内核版本，如操作系统内核版本为 5.10 那么安装 linux-tool 后则为 5.10\nbash 1 2 3 4 $ apt-get install linux-perf $ perf --version perf version 5.10.149 各系统下的包名与安装\nUbuntu/Debian: linux-perf | linux-tools ；apt-get install linux-perf CentOS/Fedora: perf ；yum install -y perf list - 列出可用事件描述符 使用 perf 子命令 list 可以列出所有的 perf 可测量事件\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 perf list List of pre-defined events (to be used in -e): branch-instructions OR branches [Hardware event] branch-misses [Hardware event] cache-misses [Hardware event] cache-references [Hardware event] cpu-cycles OR cycles [Hardware event] instructions [Hardware event] stalled-cycles-backend OR idle-cycles-backend [Hardware event] stalled-cycles-frontend OR idle-cycles-frontend [Hardware event] alignment-faults [Software event] bpf-output [Software event] context-switches OR cs [Software event] cpu-clock [Software event] cpu-migrations OR migrations [Software event] dummy [Software event] emulation-faults [Software event] major-faults [Software event] minor-faults [Software event] page-faults OR faults [Software event] task-clock [Software event] duration_time [Tool event] L1-dcache-load-misses [Hardware cache event] L1-dcache-loads [Hardware cache event] L1-dcache-prefetches [Hardware cache event] L1-icache-load-misses [Hardware cache event] L1-icache-loads [Hardware cache event] branch-load-misses [Hardware cache event] branch-loads [Hardware cache event] dTLB-load-misses [Hardware cache event] dTLB-loads [Hardware cache event] iTLB-load-misses [Hardware cache event] iTLB-loads [Hardware cache event] list 子命令后还可以加过滤器以查看对应类型的事件，示例：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 # 列出TCP相关事件 $ perf list tcp List of pre-defined events (to be used in -e): syscalls:sys_enter_getcpu [Tracepoint event] syscalls:sys_exit_getcpu [Tracepoint event] tcp:tcp_destroy_sock [Tracepoint event] tcp:tcp_probe [Tracepoint event] tcp:tcp_rcv_space_adjust [Tracepoint event] tcp:tcp_receive_reset [Tracepoint event] tcp:tcp_retransmit_skb [Tracepoint event] tcp:tcp_retransmit_synack [Tracepoint event] tcp:tcp_send_reset [Tracepoint event] # 列出bpf相关事件 $ perf list bpf List of pre-defined events (to be used in -e): bpf-output [Software event] bpf_test_run:bpf_test_finish [Tracepoint event] bpf_trace:bpf_trace_printk [Tracepoint event] syscalls:sys_enter_bpf [Tracepoint event] syscalls:sys_exit_bpf [Tracepoint event] # 列出硬件相关事件 $ perf list hardware List of pre-defined events (to be used in -e): branch-instructions OR branches [Hardware event] branch-misses [Hardware event] cache-misses [Hardware event] cache-references [Hardware event] cpu-cycles OR cycles [Hardware event] instructions [Hardware event] stalled-cycles-backend OR idle-cycles-backend [Hardware event] stalled-cycles-frontend OR idle-cycles-frontend [Hardware event] top - 查看系统实时信息 perf 的 top子命令可以查看CPU的实时信息\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ perf top 23.69% [kernel] [k] mpt_put_msg_frame 14.27% [kernel] [k] read_tsc 13.34% [kernel] [k] asm_sysvec_apic_timer_interrupt 11.72% [kernel] [k] vmware_sched_clock 5.79% perf_5.10 [.] 0x00000000002901f4 5.11% [kernel] [k] delay_tsc 4.87% [kernel] [k] native_read_msr 4.57% perf_5.10 [.] 0x0000000000284c2d 3.13% perf_5.10 [.] 0x0000000000284c1a 3.09% [kernel] [k] native_write_msr 2.07% [kernel] [k] s_show 1.99% [kernel] [k] mpt_interrupt 1.82% perf_5.10 [.] 0x0000000000284d46 1.69% [kernel] [k] asm_sysvec_call_function_single 0.86% [kernel] [k] __es_tree_search.isra.0 0.83% [kernel] [k] security_task_free 0.78% [vdso] [.] 0x0000000000000698 0.38% perf_5.10 [.] 0x0000000000284d57 上面的信息的展示类似于 top 命令，从左右到信息为：\n第一列：与CPU使用率百分比占用的相关函数 第二列：那个库或者进程使用的这个函数 第三列：[k] 表示内核空间， [.] 表示用户空间 第四列：符号或函数的名称 默认情况下 perf top 监控的是所有CPU，也可以使用子选项，例如下表（一些常用的命令参数）\nOption describe -a 监控所有CPU包含空闲值 -c 收集 -C 收集指定CPU的样本，后接CPU核心编号 -d 后接数字，将延迟几秒刷新 -e 指定特殊的事件，事件通过 perf list 查看 -F 控制采样的频率 -p 指定PID的进程的事件信息 -g 启用 显示调用图记录 -i 不继承模式，子任务将不继承计数器 -t 指定线程ID的事件信息 -u 指定user的事件信息 更多选项可以使用 perf top -h\nstat - CPU相关统计 使用 perf 子命令 stat 可以对指定命令的CPU性能统计\nbash 1 perf stat \u0026lt;commond\u0026gt; 查看指定命令的CPU计数器统计信息\nbash 1 2 3 perf stat \u0026lt;command\u0026gt; # 如果需要更详细信息可以跟 -d 选项 perf stat -d \u0026lt;command\u0026gt; 示例\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $ perf stat curl baidu.com Performance counter stats for \u0026#39;curl baidu.com\u0026#39;: 31.09 msec task-clock # 0.012 CPUs utilized 27 context-switches # 0.868 K/sec 1 cpu-migrations # 0.032 K/sec 577 page-faults # 0.019 M/sec 41,691,320 cycles # 1.341 GHz (17.63%) 0 stalled-cycles-frontend 0 stalled-cycles-backend # 0.00% backend cycles idle 0 instructions # 0.00 insn per cycle (82.37%) \u0026lt;not counted\u0026gt; branches (0.00%) \u0026lt;not counted\u0026gt; branch-misses (0.00%) 2.646439514 seconds time elapsed 0.026403000 seconds user 0.013201000 seconds sys Some events weren\u0026#39;t counted. Try disabling the NMI watchdog: echo 0 \u0026gt; /proc/sys/kernel/nmi_watchdog perf stat ... echo 1 \u0026gt; /proc/sys/kernel/nmi_watchdog 查看指定PID的CPU计数器统计信息\n统计命令将会直到 ctrl - c 结束\nbash 1 perf stat -p \u0026lt;PID\u0026gt; 示例：例如统计一个进程的CPU使用情况\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 perf stat -p 477 ^C Performance counter stats for process id \u0026#39;477\u0026#39;: 146.96 msec task-clock # 0.034 CPUs utilized 88 context-switches # 0.599 K/sec 8 cpu-migrations # 0.054 K/sec 4,991 page-faults # 0.034 M/sec 153,052,247 cycles # 1.041 GHz (36.77%) 0 stalled-cycles-frontend (50.31%) 0 stalled-cycles-backend # 0.00% backend cycles idle (57.94%) 0 instructions # 0.00 insn per cycle (63.23%) 0 branches # 0.000 K/sec (49.69%) 0 branch-misses # 0.00% of all branches (42.06%) 4.336415211 seconds time elapsed 只统计缓存信息\nbash 1 perf stat -e LLC-loads,LLC-load-misses,LLC-stores,LLC-prefetches LLC last-level cache 是指内存分层结构中主内存之前的最后一级 LLC-loads：命中的指标 LLC-load-misses：未命中指标，显示这个周期内尚未处理的比率 LLC-stores LLC-prefetches：事件发生在的 L2 硬件预取中 示例：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 # 使用原始 PMC 计数器，例如，计算未暂停的核心周期： # 使用原始PMC计数器，例如，计数未改变的核心周期: perf stat -e r003c -a sleep 5 # 统计系统范围内每秒的系统调用： perf stat -e cycles -e cpu/event=0x0e,umask=0x01,inv,cmask=0x01/ -a sleep 5 # 统计系统范围内每秒的系统调用： perf stat -e raw_syscalls:sys_enter -I 1000 -a # 按类型计算指定PID的系统调用，直到Ctrl-C结束 perf stat -e \u0026#39;syscalls:sys_enter_*\u0026#39; -p \u0026lt;PID\u0026gt; # 按类型统计整个系统范围内的系统调用，持续 5 秒： perf stat -e \u0026#39;syscalls:sys_enter_*\u0026#39; -a sleep 5 # 按类型计数整个系统的系统调用，持续5秒: perf stat -e \u0026#39;syscalls:sys_enter_*\u0026#39; -a sleep 5 # 记录指定PID进程的调度器事件直到Ctrl-C结束 perf stat -e \u0026#39;sched:*\u0026#39; -p PID # 记录指定PID进程的调度器事件，持续10s perf stat -e \u0026#39;sched:*\u0026#39; -p PID sleep 10 # 记录整个系统内的ext4事件，持续10s perf stat -e \u0026#39;ext4:*\u0026#39; -a sleep 10 # 统计整个系统的块设备 I/O 事件，持续10s perf stat -e \u0026#39;block:*\u0026#39; -a sleep 10 # 统计所有 vmscan 事件，每秒打印一份报告： perf stat -e \u0026#39;vmscan:*\u0026#39; -a -I 1000 record - 将CPU事件记录到文件 导出事件记录到文件\nperf的子命令 record 是可以将事件记录到 perf.data，例如要CPU周期事件，可以使用record子命令并通过 tag -e 来指定事件名称\nbash 1 2 # 通过perf list 可以看出 CPU周期事件为 cpu-cycles OR cycles perf record -e cycles sleep 10 通过查看文件的记录\n结果将保存到 perf.data 文件中，如果需要查看 perf.data 需要使用子命令 report 查看，report 子命令默认查找当前目录下的 perf.data 文件，如果需要指定特定目录的需要使用tag -i\nbash 1 perf report -i ./perf.data 修改样本文件输出的结果格式\nreport 子命令也可以改变要显示的结果样式，例如想输出为标准输出，可以使用 --stdio\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ perf report --stdio # To display the perf.data header info, please use --header/--header-only options. # # # Total Lost Samples: 0 # # Samples: 18 of event \u0026#39;cycles\u0026#39; # Event count (approx.): 440 # # Overhead Command Shared Object Symbol # ........ ....... ................. .................... # 90.91% sleep [kernel.kallsyms] [k] native_write_msr 9.09% perf_5. [kernel.kallsyms] [k] native_write_msr # # (Tip: Order by the overhead of source file name and line number: perf report -s srcline) # 如果想显示事件的编号以及对特定列排序可以使用下面域名\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ perf report -n --sort comm,symbol --stdio # To display the perf.data header info, please use --header/--header-only options. # # # Total Lost Samples: 0 # # Samples: 18 of event \u0026#39;cycles\u0026#39; # Event count (approx.): 440 # # Overhead Samples Command Symbol IPC [IPC Coverage] # ........ ............ ....... .................... .................... # 90.91% 9 sleep [k] native_write_msr - - 9.09% 9 perf_5. [k] native_write_msr - - # # (Tip: Show current config key-value pairs: perf config --list) # script - trace做了什么 perf 子命令 script 可以trace perf.data 中所有的事件；例如上面的 perf.data 最终两个事件展开为\nperf script 子命令也是作为一个后期处理数据的一个命令\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ perf script perf_5.10 2730 28762.537401: 1 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) perf_5.10 2730 28762.537540: 1 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) perf_5.10 2730 28762.537670: 1 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) perf_5.10 2730 28762.537798: 2 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) perf_5.10 2730 28762.537901: 3 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) perf_5.10 2730 28762.538003: 4 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) perf_5.10 2730 28762.538105: 6 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) perf_5.10 2730 28762.538207: 9 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) perf_5.10 2730 28762.538320: 13 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) sleep 2730 28772.542839: 26 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) sleep 2730 28772.543041: 26 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) sleep 2730 28772.543233: 26 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) sleep 2730 28772.543421: 30 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) sleep 2730 28772.543558: 35 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) sleep 2730 28772.543683: 41 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) sleep 2730 28772.543806: 53 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) sleep 2730 28772.543929: 70 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) sleep 2730 28772.544065: 93 cycles: ffffffffbd46b466 native_write_msr+0x6 ([kernel.kallsyms]) 输出显示文件的头信息，例如跟踪何时开始、持续了多长时间、CPU信息以及获取数据的命令。 事件列表在头信息之后。\n显示trace的头信息\n使用tag --header 可以显示文件的头信息，例如跟何时开始trace、持续的事件、CPU信息以及获取数据的命令。 事件列表在头信息之后。事件头信息是由 # ======== 包含著的信息\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 # ======== # captured on : Tue Dec 6 04:44:38 2022 # header version : 1 # data offset : 256 # data size : 11528 # feat offset : 11784 # hostname : debian-template # os release : 5.10.0-16-amd64 # perf version : 5.10.149 # arch : x86_64 # nrcpus online : 2 # nrcpus avail : 2 # cpudesc : AMD Ryzen 7 5800U with Radeon Graphics # cpuid : AuthenticAMD,25,80,0 # total memory : 1996352 kB # cmdline : /usr/bin/perf_5.10 record -e cycles sleep 10 # event : name = cycles, , id = { 471, 472 }, size = 120, { sample_period, sample_freq } = 2250, sample_type = IP|TID|TIME|PERIOD, read_forma\u0026gt; # CPU_TOPOLOGY info available, use -I to display # NUMA_TOPOLOGY info available, use -I to display # pmu mappings: software = 1, power = 9, uprobe = 7, cpu = 4, breakpoint = 5, tracepoint = 2, kprobe = 6, msr = 8 # CACHE info available, use -I to display # time of first sample : 28762.537401 # time of last sample : 28772.544065 # sample duration : 10006.663 ms # MEM_TOPOLOGY info available, use -I to display # bpf_prog_info 3: bpf_prog_47dd357395126b0c addr 0xffffffffc00eb59c size 309 # bpf_prog_info 4: bpf_prog_6deef7357e7b4530 addr 0xffffffffc00f2168 size 54 # bpf_prog_info 5: bpf_prog_6deef7357e7b4530 addr 0xffffffffc00f40e0 size 54 # bpf_prog_info 6: bpf_prog_b73cbcf8b8c71a5b addr 0xffffffffc02591c8 size 307 # bpf_prog_info 7: bpf_prog_6deef7357e7b4530 addr 0xffffffffc025b584 size 54 # bpf_prog_info 8: bpf_prog_6deef7357e7b4530 addr 0xffffffffc025db10 size 54 # bpf_prog_info 9: bpf_prog_ee0e253c78993a24 addr 0xffffffffc0534640 size 255 # bpf_prog_info 10: bpf_prog_ce28cc67158d681f addr 0xffffffffc04947f0 size 447 # bpf_prog_info 11: bpf_prog_6deef7357e7b4530 addr 0xffffffffc052fe4c size 54 # bpf_prog_info 12: bpf_prog_6deef7357e7b4530 addr 0xffffffffc0531224 size 54 # cpu pmu capabilities: max_precise=0 # missing features: TRACING_DATA BRANCH_STACK GROUP_DESC AUXTRACE STAT CLOCKID DIR_FORMAT COMPRESSED CLOCK_DATA # ======== 导出16进制的原生数据\n导出原生数据是ASIIC格式事件信息\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $ perf script -D 0x100 [0x50]: event: 1 . . ... raw event: size 80 bytes . 0000: 01 00 00 00 01 00 50 00 ff ff ff ff 00 00 00 00 ......P......... . 0010: 00 00 40 bd ff ff ff ff f7 1d c0 00 00 00 00 00 ..@............. . 0020: 00 00 40 bd ff ff ff ff 5b 6b 65 72 6e 65 6c 2e ..@.....[kernel. . 0030: 6b 61 6c 6c 73 79 6d 73 5d 5f 74 65 78 74 00 00 kallsyms]_text.. . 0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0 0x100 [0x50]: PERF_RECORD_MMAP -1/0: [0xffffffffbd400000(0xc01df7) @ 0xffffffffbd400000]: x [kernel.kallsyms]_text 0x150 [0x78]: event: 1 . . ... raw event: size 120 bytes . 0000: 01 00 00 00 01 00 78 00 ff ff ff ff 00 00 00 00 ......x......... . 0010: 00 10 0a c0 ff ff ff ff 00 00 04 00 00 00 00 00 ................ . 0020: 00 00 00 00 00 00 00 00 2f 6c 69 62 2f 6d 6f 64 ......../lib/mod . 0030: 75 6c 65 73 2f 35 2e 31 30 2e 30 2d 31 36 2d 61 ules/5.10.0-16-a . 0040: 6d 64 36 34 2f 6b 65 72 6e 65 6c 2f 64 72 69 76 md64/kernel/driv . 0050: 65 72 73 2f 73 63 73 69 2f 73 63 73 69 5f 6d 6f ers/scsi/scsi_mo . 0060: 64 2e 6b 6f 00 00 00 00 00 00 00 00 00 00 00 00 d.ko............ . 0070: 00 00 00 00 00 00 00 00 trace - 更高性能的strace的替代品 trace是linux 3.7 增加的功能，可以用作strace命令的替代品，因为不需要用户-内核空间切换，所以性能将更快\nbash 1 perf trace \u0026lt;command\u0026gt; 也可以使用tag -e 来指定仅对指定事件trace\nbash 1 perf trace -e read,write \u0026lt;command\u0026gt; 可以看到对应事件将只有 read 与 write\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ perf trace -e read,write ls 0.000 (1\tnginx-1.22.0\tnginx_1.22.0.orig.tar.gz\tperf.data 1.c\tnginx_1.22.0-1.debian.tar.xz nginx-1.22.0.tar.gz\tperf.data.old deb-multimedia-keyring_2016.8.1_all.deb nginx_1.22.0-1.dsc\tpaping_1.5.5_x86-64_linux.tar.gz 0.058 ms): ls/3126 read(fd: 3, buf: 0x7ffcd2548068, count: 832) = 832 0.132 ( 0.034 ms): ls/3126 read(fd: 3, buf: 0x7ffcd2548048, count: 832) = 832 0.232 ( 0.033 ms): ls/3126 read(fd: 3, buf: 0x7ffcd2548028, count: 832) = 832 0.324 ( 0.032 ms): ls/3126 read(fd: 3, buf: 0x7ffcd2548008, count: 832) = 832 0.416 ( 0.032 ms): ls/3126 read(fd: 3, buf: 0x7ffcd2547fc8, count: 832) = 832 0.828 ( 0.048 ms): ls/3126 read(fd: 3, buf: 0x55c4bb555500, count: 1024) = 361 0.907 ( 0.028 ms): ls/3126 read(fd: 3, buf: 0x55c4bb555500, count: 1024) = 0 1.127 ( 0.060 ms): ls/3126 write(fd: 1, buf: 0x55c4bb555500, count: 65) = 65 1.219 ( 0.385 ms): ls/3126 write(fd: 1, buf: 0x55c4bb555500, count: 75) = 75 1.638 ( 0.049 ms): ls/3126 write(fd: 1, buf: 0x55c4bb555500, count: 100) = 100 probe - 动态追踪 perf probe子命令是可以动态的在linux内核中自定义追踪事件（追踪点），追踪点的运行时可以在被放置任何任何地方，并且每次通过该追踪点时，都可以记录其值。\n如何使用 perf probe？\n查看可探测的函数\nperf probe -F 可以找到可用的追踪点，如果模糊查找可以使用filter\nbash 1 perf probe -F -–filter dev*xmit* probe参数\nOption describe -L 显示源代码 -x 可执行文件的名称或路径 -l 列出所有probe探测事件 -k 指定vmlinux文件 -a **`\u0026lt;[EVENT=]FUNC[@SRC][+OFF EVENT 事件名称 FUNC 函数名 +OFF 函数入口的偏移量 %return 探针位置为函数返回处 SRC 源代码路径 RL 相对函数入口处的行号 AL 在文件内的绝对行号 ARG: 探测参数（局部变量名 或 kprobe-tracer 参数格式。 text 1 2 perf probe -x tst --add \u0026#39;out=func%return $retval\u0026#39; perf record -g -e probe_tst:out -aR ./tst 一些使用示例\nbash 1 2 3 4 5 6 7 8 9 10 11 # 添加一个追踪点到linux内核函数tcp_sendmsg()至入口 perf probe --add tcp_sendmsg # 删除linux内核tcp_sendmsg()函数上的追踪点 perf probe -d tcp_sendmsg # 列出现有的追踪点 perf probe -l # 添加一个追踪点到linux内核函数tcp_sendmsg()返回部分 perf probe \u0026#39;tcp_sendmsg%return\u0026#39; 通过probe检测内核函数 probe使用示例说明\n内核函数：tcp_sendmsg() 在内核函数上 tcp_sendmsg 添加一个事件\nbash 1 perf probe --add tcp_sendmsg 此时会存在一个追踪点，通过 perf probe -l 可以查看\ntrace此追踪点5s，记录堆栈信息\nbash 1 perf record -e probe:tcp_sendmsg -a -g -- sleep 5 通过 report 子命令可以查看对应信息\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 $ perf report --stdio -i perf.data # To display the perf.data header info, please use --header/--header-only options. # # # Total Lost Samples: 0 # # Samples: 13 of event \u0026#39;probe:tcp_sendmsg\u0026#39; # Event count (approx.): 13 # # Children Self Command Shared Object Symbol # ........ ........ ....... ................ .................................. # 100.00% 100.00% sshd [kernel.vmlinux] [k] tcp_sendmsg | |--92.31%--0 | getnetbyaddr_r@@GLIBC_2.2.5 | entry_SYSCALL_64_after_hwframe | do_syscall_64 | ksys_write | vfs_write | new_sync_write | sock_write_iter | sock_sendmsg | tcp_sendmsg | --7.69%--0x1b81475c085 getnetbyaddr_r@@GLIBC_2.2.5 entry_SYSCALL_64_after_hwframe do_syscall_64 ksys_write vfs_write new_sync_write sock_write_iter sock_sendmsg tcp_sendmsg 100.00% 0.00% sshd libc-2.31.so [.] getnetbyaddr_r@@GLIBC_2.2.5 | ---getnetbyaddr_r@@GLIBC_2.2.5 entry_SYSCALL_64_after_hwframe 删除对应跟踪点\nbash 1 perf probe -d \u0026lt;probe_name\u0026gt; 也可以通过内核函数的变量进行检查\n查看内核函数参数，可以看到存在三个参数 size int类型, msg 结构体指针，sk 结构体指针\nbash 1 2 3 4 5 6 $ perf probe -V tcp_sendmsg Available variables at tcp_sendmsg @\u0026lt;tcp_sendmsg+0\u0026gt; size_t size struct msghdr* msg struct sock* sk 使用 size 变量作为 tcp_sendmsg 探测点的探测器\nbash 1 perf probe --add \u0026#39;tcp_sendmsg size\u0026#39; 通过probe检测用户空间程序 [2] 准备一段代码，即每次循环，打印该值并打印该值+5\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; int func(int xxx) { int zzz = xxx; printf(\u0026#34;zzz: %d\\n\u0026#34;, zzz); return zzz+5; } int main(int argc, char* argv[]) { int i=0; for( i=0; i\u0026lt;10; i++) printf(\u0026#34;yyy: %d\\n\u0026#34;, func(argc + i)); return 0; } 这里使用环境为 debian11，内核 5.10，需要注意的是，在新内核中版本中传参的命令与老内核有少许差别\n运行编译后的程序可以看到结果\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ gcc -g -o tst tst.c \u0026amp;\u0026amp; ./tst zzz: 1 yyy: 6 zzz: 2 yyy: 7 zzz: 3 yyy: 8 zzz: 4 yyy: 9 zzz: 5 yyy: 10 zzz: 6 yyy: 11 zzz: 7 yyy: 12 zzz: 8 yyy: 13 zzz: 9 yyy: 14 zzz: 10 yyy: 15 这里编译时使用了 -g 选项，-g 是一个编译选项，即在源代码编译的过程中起作用，让gcc把更多调试信息（也就包括符号信息）收集起来并将存放到最终的可执行文件\n接下来为程序创建一个追踪事件，\nbash 1 2 3 4 5 6 7 8 perf probe -x tst --add \u0026#39;out=func%return $retval\u0026#39; # 格式将严格遵循 \u0026lt;[EVENT=]FUNC[@SRC][+OFF|%return|:RL|;PT]|SRC:AL|SRC;PT [[NAME=]ARG ...]\u0026gt; # out=func%return %retval # EVENT=FUNC%return ARG # EVENT 探测事件名 # FUNC 函数名 # %return 在函数return处放置探针 # ARG 参数 此时可以执行这个程序，让probe可以追踪到数据\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $ perf record -g -e probe_tst:out__return -aR ./tst Lowering default frequency rate to 2750. Please consider tweaking /proc/sys/kernel/perf_event_max_sample_rate. zzz: 1 yyy: 6 zzz: 2 yyy: 7 zzz: 3 yyy: 8 zzz: 4 yyy: 9 zzz: 5 yyy: 10 zzz: 6 yyy: 11 zzz: 7 yyy: 12 zzz: 8 yyy: 13 zzz: 9 yyy: 14 zzz: 10 yyy: 15 [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.163 MB perf.data (10 samples) ] 执行的结果保存在 perf.data 中\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $ perf report --stdio # To display the perf.data header info, please use --header/--header-only options. # # # Total Lost Samples: 0 # # Samples: 10 of event \u0026#39;probe_tst:out__return\u0026#39; # Event count (approx.): 10 # # Children Self Command Shared Object Symbol # ........ ........ ....... ................ ...................... # 100.00% 100.00% tst tst [.] main | ---0x5541d68949564100 cancel_handler main 100.00% 0.00% tst [unknown] [.] 0x5541d68949564100 | ---0x5541d68949564100 cancel_handler main 100.00% 0.00% tst libc-2.31.so [.] cancel_handler | ---cancel_handler main 使用 script 子命令查看这个程序的trace记录，可以看到\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 perf script tst 2272 [000] 28276.947273: probe_tst:out__return: (55dc41f11135 \u0026lt;- 55dc41f11192) arg1=0x6 55dc41f11192 main+0x2e (/root/tst) 7f67337bed0a cancel_handler+0x3a (/usr/lib/x86_64-linux-gnu/libc-2.31.so) 5541d68949564100 [unknown] ([unknown]) tst 2272 [000] 28276.947282: probe_tst:out__return: (55dc41f11135 \u0026lt;- 55dc41f11192) arg1=0x7 55dc41f11192 main+0x2e (/root/tst) 7f67337bed0a cancel_handler+0x3a (/usr/lib/x86_64-linux-gnu/libc-2.31.so) 5541d68949564100 [unknown] ([unknown]) tst 2272 [000] 28276.947288: probe_tst:out__return: (55dc41f11135 \u0026lt;- 55dc41f11192) arg1=0x8 55dc41f11192 main+0x2e (/root/tst) 7f67337bed0a cancel_handler+0x3a (/usr/lib/x86_64-linux-gnu/libc-2.31.so) 5541d68949564100 [unknown] ([unknown]) tst 2272 [000] 28276.947294: probe_tst:out__return: (55dc41f11135 \u0026lt;- 55dc41f11192) arg1=0x9 55dc41f11192 main+0x2e (/root/tst) 7f67337bed0a cancel_handler+0x3a (/usr/lib/x86_64-linux-gnu/libc-2.31.so) 5541d68949564100 [unknown] ([unknown]) tst 2272 [000] 28276.947300: probe_tst:out__return: (55dc41f11135 \u0026lt;- 55dc41f11192) arg1=0xa 55dc41f11192 main+0x2e (/root/tst) 7f67337bed0a cancel_handler+0x3a (/usr/lib/x86_64-linux-gnu/libc-2.31.so) 5541d68949564100 [unknown] ([unknown]) tst 2272 [000] 28276.947306: probe_tst:out__return: (55dc41f11135 \u0026lt;- 55dc41f11192) arg1=0xb 55dc41f11192 main+0x2e (/root/tst) 7f67337bed0a cancel_handler+0x3a (/usr/lib/x86_64-linux-gnu/libc-2.31.so) 5541d68949564100 [unknown] ([unknown]) tst 2272 [000] 28276.947311: probe_tst:out__return: (55dc41f11135 \u0026lt;- 55dc41f11192) arg1=0xc 55dc41f11192 main+0x2e (/root/tst) 7f67337bed0a cancel_handler+0x3a (/usr/lib/x86_64-linux-gnu/libc-2.31.so) 5541d68949564100 [unknown] ([unknown]) tst 2272 [000] 28276.947317: probe_tst:out__return: (55dc41f11135 \u0026lt;- 55dc41f11192) arg1=0xd 55dc41f11192 main+0x2e (/root/tst) 7f67337bed0a cancel_handler+0x3a (/usr/lib/x86_64-linux-gnu/libc-2.31.so) 5541d68949564100 [unknown] ([unknown]) tst 2272 [000] 28276.947322: probe_tst:out__return: (55dc41f11135 \u0026lt;- 55dc41f11192) arg1=0xe # 14 55dc41f11192 main+0x2e (/root/tst) 7f67337bed0a cancel_handler+0x3a (/usr/lib/x86_64-linux-gnu/libc-2.31.so) 5541d68949564100 [unknown] ([unknown]) tst 2272 [000] 28276.947328: probe_tst:out__return: (55dc41f11135 \u0026lt;- 55dc41f11192) arg1=0xf # 15 55dc41f11192 main+0x2e (/root/tst) 7f67337bed0a cancel_handler+0x3a (/usr/lib/x86_64-linux-gnu/libc-2.31.so) 5541d68949564100 [unknown] ([unknown]) 可以通过上面看到，这里制作的探针在用户空间程序 tst.func 函数中，当他返回时，记录了要返回的值作为arg1，也就是每行返回的 0xf 这类16进制值，也可以看到每次命中该探针的部分\nTroubleshooting Uhhuh. NMI received for unknown reason bash 1 2 3 4 5 6 7 8 Message from syslogd@phab1 at Dec 26 19:16:16 ... kernel:Uhhuh. NMI received for unknown reason 30 on CPU 0. Message from syslogd@phab1 at Dec 26 19:16:16 ... kernel:Do you have a strange power saving mode enabled? Message from syslogd@phab1 at Dec 26 19:16:16 ... kernel:Dazed and confused, but trying to continue 上述问题通常发生于虚拟化环境\nSolve solution: disable c-state in bios\nFailed to find the path for kernel: Invalid ELF file bash 1 2 Failed to find the path for kernel: Invalid ELF file Error: Failed to show vars. 这个错误通常使用 perf probe -V 时出现，这里需要内核支持 debug symbols，即使用公开发行版需要安装对应内核包\nRHEL/CentOS：安装 kernel-debuginfo-common 与 kernel-debuginfo package Debian/Ubuntu：安装 linux-image-\u0026lt;kernel_version\u0026gt;-amd64-dbg debian下保持需要至少5G空间 [3] Failed to find source file path bash 1 2 3 $ perf probe -L tcp_sendmsg Failed to find source file path. Error: Failed to show lines. 思路：可以通过 strace 命令看看为什么报错\n原因：perf probe -L 将显示对应内核探测点的源代码，此时perf会寻找构建的内核目录，而操作系统发行版供应商对于系统都是通过包管理，包括内核，并未提供这些源码，此状态为正确的，如果非要解决，可以自行编译内核。\ndmesg dmesg 是来自内核的一个环形缓冲区，而通过 dmesg 命令可以看到来自该缓冲区的消息，而该消息也被称为 ”driver message“ 或 ”display message“\n示例1：对dmesg输出着色 bash 1 $ dmesg -L 示例2：dmesg输出消息增加时间 bash 1 $ dmesg -T 示例3：过滤相关级别信息 可以通过 --level 来进行过滤出不同级别的日志，可用级别有\nemerg, alert, crit, err, warn, notice, info debug bash 1 2 3 $ dmesg --level=err $ dmesg --level=warn 示例4：过滤相关事件信息 dmesg 可以通过参数指定 --facility 来指定对应事件的日志，可用的设施有：\nkern user mail daemon auth lpr news bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $ dmesg --facility=daemon [ 1.793879] systemd[1]: Inserted module \u0026#39;autofs4\u0026#39; [ 1.807871] systemd[1]: systemd 247.3-7 running in system mode. (+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +ZSTD +SECCOMP +BLKID +ELFUTILS +KMOD +IDN2 -IDN +PCRE2 default-hierarchy=unified) [ 1.807925] systemd[1]: Detected virtualization vmware. [ 1.807927] systemd[1]: Detected architecture x86-64. [ 1.808612] systemd[1]: Set hostname to \u0026lt;debian-template\u0026gt;. [ 1.881058] systemd[1]: Queued start job for default target Graphical Interface. [ 1.882132] systemd[1]: Created slice system-getty.slice. [ 1.882337] systemd[1]: Created slice system-modprobe.slice. [ 1.882724] systemd[1]: Created slice system-systemd\\x2dfsck.slice. [ 1.882869] systemd[1]: Created slice User and Session Slice. [ 1.882902] systemd[1]: Started Dispatch Password Requests to Console Directory Watch. [ 1.882920] systemd[1]: Started Forward Password Requests to Wall Directory Watch. [ 1.883019] systemd[1]: Set up automount Arbitrary Executable File Formats File System Automount Point. [ 1.883036] systemd[1]: Reached target Local Encrypted Volumes. [ 1.883049] systemd[1]: Reached target Paths. [ 1.883054] systemd[1]: Reached target Remote File Systems. [ 1.883058] systemd[1]: Reached target Slices. [ 1.883080] systemd[1]: Reached target Swap. [ 1.883152] systemd[1]: Listening on Syslog Socket. [ 1.883193] systemd[1]: Listening on fsck to fsckd communication Socket. [ 1.883218] systemd[1]: Listening on initctl Compatibility Named Pipe. [ 1.883285] systemd[1]: Listening on Journal Audit Socket. [ 1.883322] systemd[1]: Listening on Journal Socket (/dev/log). [ 1.883364] systemd[1]: Listening on Journal Socket. [ 1.883422] systemd[1]: Listening on udev Control Socket. [ 1.883456] systemd[1]: Listening on udev Kernel Socket. [ 1.883961] systemd[1]: Mounting Huge Pages File System... 示例5：实时打印dmesg日志 bash 1 $ dmesg --follow 示例6：显示dmesg原生信息 bash 1 $ dmesg -r 示例7：dmesg信息重定向到syslog dmesg本身只是一个用户空间命令，而 ”driver message“ 是内存中一个缓冲器，在Linux标识为 /dev/kmsg，而这个缓冲区是存在与内存中，如果需要将其重定向到syslog，可以通过参数 -S 实现，-s 则是设置这个环形buffer的大小\n示例8：过滤硬件设备相关信息 bash 1 2 3 4 5 6 7 8 9 # usb设备 $ dmesg | grep -i usb # 还可以通过grep查看其它硬件设备相关信息 $ dmesg | grep -i dma $ dmesg | grep -i scsi $ dmesg | grep -i acpi $ dmesg | grep -i memory $ dmesg | grep -i tty $ dmesg | grep sda 示例9：清空buffer bash 1 2 3 4 # 直接清空 dmesg -C # 读取并清空 dmesg -c vmstat vmstat 命令是Linux虚拟内存统计信息的命令，带来的是与进程有关的信息，如processes, memory, paging, block IO\n没有任何参数的vmstat\nbash 1 2 3 4 $ vmstat procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 1 0 0 1246808 24308 507656 0 0 5 0 60 130 0 0 100 0 0 vmstat输出包含的字段\nProcs – r: 等待运行的数量 Procs – b: 忙碌进程的数量 Memory – swpd: 已使用的虚拟内存 Memory – free: 空闲的虚拟内存 Memory – buff: 用作buffer的内存 Memory – cache: 用作cache的内存 Swap – si: 从磁盘交换至内存 (for every second) Swap – so: 内存交换到磁盘 (for every second) IO – bi (Blocks in). i.e 从设备接受到的块(for every second) IO – bo (Blocks out). i.e 发送到设备的块 (for every second) System – in (Interrupts per second) 每秒的中断 System – cs (Context switches) 上下文切换 CPU – us, sy, id, wa, st: CPU user time, system time, idle time, wait time 示例1：显示活动和非活动内存 bash 1 2 3 4 $ vmstat -a procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free inact active si so bi bo in cs us sy id wa st 1 0 0 1247060 294352 314540 0 0 5 0 60 130 0 0 100 0 0 示例2：显示启动系统后所有fork系统调用 显示所有 fork、vfork 和 clone 系统调用计数\nbash 1 2 $ vmstat -f 2889 forks 示例3：动态展示 展示结果将以 x 秒刷新，直到 crtl - c 退出\nbash 1 $ vmstat 2 也可以接俩个参数，一个是刷新时间，一个是刷新多少次，例如，2秒刷新一次，一共刷新10次，完成后退出命令\nbash 1 $ vmstat 2 10 示例4：打印时间 bash 1 2 3 4 5 $ vmstat -t 1 2 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- -----timestamp----- r b swpd free buff cache si so bi bo in cs us sy id wa st PST 1 0 0 1247424 24396 507656 0 0 5 0 60 130 0 0 100 0 0 2022-12-10 08:10:03 1 0 0 1247416 24396 507656 0 0 0 8 130 277 0 0 100 0 0 2022-12-10 08:10:04 示例5：显示slab相关信息 slab是一种内存管理机制，目的是为了更有效的分配内存对象\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 vmstat -m Cache Num Total Size Pages nf_conntrack 102 102 320 51 ovl_inode 94 94 688 47 ext4_groupinfo_1k 120 120 136 60 fuse_request 0 0 152 53 fuse_inode 0 0 832 39 ext4_groupinfo_4k 112 112 144 56 ext4_fc_dentry_update 0 0 80 51 ext4_inode_cache 13554 13554 1184 27 ext4_system_zone 204 204 40 102 ext4_io_end 128 128 64 64 ext4_extent_status 3060 3060 40 102 jbd2_journal_handle 146 146 56 73 ... 场景6：输出格式为表格形式 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $ vmstat -s 1996352 K total memory 216852 K used memory 314636 K active memory 294352 K inactive memory 1247416 K free memory 24428 K buffer memory 507656 K swap cache 0 K total swap 0 K used swap 0 K free swap 3635 non-nice user cpu ticks 2 nice user cpu ticks 8204 system cpu ticks 9759423 idle cpu ticks 310 IO-wait cpu ticks 0 IRQ cpu ticks 320 softirq cpu ticks 0 stolen cpu ticks 498371 pages paged in 45598 pages paged out 0 pages swapped in 0 pages swapped out 5866212 interrupts 12709713 CPU context switches 1670639441 boot time 2922 forks 场景7：磁盘相关信息 bash 1 2 3 4 5 $ vmstat -d 1 20 disk- ------------reads------------ ------------writes----------- -----IO------ total merged sectors ms total merged sectors ms cur sec sda 7835 2737 996742 2134 5719 2598 91620 6219 0 9 sda 7835 2737 996742 2134 5747 2598 91908 6230 0 9 场景8：输出格式增加宽度 bash 1 2 3 4 5 6 vmstat -w 1 3 --procs-- -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu-------- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 0 1247440 24468 507656 0 0 5 0 60 130 0 0 100 0 0 0 0 0 1247440 24468 507656 0 0 0 0 135 265 0 0 100 0 0 0 0 0 1247440 24468 507656 0 0 0 0 126 262 0 0 100 0 0 场景9：输出单位格式化 bash 1 2 vmstat -S k vmstat: -S requires k, K, m or M (default is KiB) mpstat mpstat是统计CPU相关信息的命令\n各系统下的包名与安装\nUbuntu/Debian/Mint：apt install sysstat -y RHEL/CentOS/Fedora：yum install -y sysstat bash 1 2 3 4 $ mpstat 08:22:07 AM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle 08:22:07 AM all 0.04 0.00 0.09 0.00 0.00 0.00 0.00 0.00 0.00 99.87 看懂 mpstat 输出结果\nCPU：处理器编号. all为所有CPU在一段时间内的平均统计信息. %usr：在用户级别（应用程序）执行时的CPU平均使用率。 %nice：具有良好级别的用户级别（应用程序）执行时的CPU平均使用率。 %sys：显示在内核级别执行时发生的CPU使用率。 这里不包括耗时的硬/软中断服务 %iowait：CPU 或 CPU 处于空闲状态期间系统有未完成的磁盘 I/O 请求。 %irq：一个或多个 CPU 在中断时，硬中断所花费的时间的百分比。 %soft： 一个或多个CPU用于中断时，软中断所花费的时间百分比。 %steal： 一个或多个虚拟CPU当在虚拟机管理器服务与另一个虚拟处理器时非自愿等待时间所花费的百分比 %guest：一个或多个CPU在运行一个虚拟处理器使用时间的百分比 %idle : 一个或多个CPU处于idle状态，并且系统没有尚未完成的磁盘 I/O 请求 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 显示所有信息 mpstat -A # 按照独立核心展示 mpstat -P ALL # 使用编号指定单独的CPU编号 mpstat -P 1 # 第一个参数表示刷新时间，第二个参数表示刷新次数 mpstat 2 10 # CPU利用率 mpstat -u %usr # CPU中断信息 mpstat -I { SUM | CPU | SCPU | ALL } pidstat pidstat 是 sysstat 包的一部分，可以统计单个进程并生成报告。用以通过 PID 来评估资源的使用率\n各系统下的包名与安装\nUbuntu/Debian/Mint：apt install sysstat -y RHEL/CentOS/Fedora：yum install -y sysstat bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 # 显示所有进程 pidstat -p ALL # 查看特定进程 pidstat -p 514 # 根据进程名称来查看 pidstat -C \u0026#34;mysql\u0026#34; # 指定实时刷新 pidstat -p 23493 1 # 显示指定进程的I/O统计信息 pidstat -p \u0026lt;pid\u0026gt; -d # 显示指定进程的活动分页统计信息 pidstat -p \u0026lt;pid\u0026gt; -r # 显示结果时加上进程程序所在路径、参数等信息 pidstat -C java -l # 第一个参数表示刷新时间，第二个参数表示刷新次数 pidstat 2 5 # 显示进程的子进程信息 # -T: CHILD, or TASKS, or ALL. pidstat -p 1 -T CHILD # 显示为依赖进程树格式 pidstat -t -C \u0026#34;ssh\u0026#34; # 展示一个水平线上的性能 # option “r” page faults and memory utilization # option “d” I/O statistics # option “u” CPU utilization # 展示结果将按照 r d u 依次输出 pidstat -rud iostat iostat 是 sysstat 包的一部分，可以通过命令来统计CPU使用率, I/O, （设备，分区）, 网络文件系统等\n各系统下的包名与安装\nUbuntu/Debian/Mint：apt install sysstat -y RHEL/CentOS/Fedora：yum install -y sysstat iostat命令参数\n-c: 显示CPU使用率 -d: 显示设备使用率 -k: 以kb为单位统计（每秒） -m: 以mb为单位统计（每秒） -x: 展示一些扩展的统计文件 iostat结果为两部分，avg-cpu 与 Device，均是指自开机以来的统计\navg-cpu部分：\n%user：在用户级别（应用程序）执行时的CPU平均使用率。 %nice：具有良好级别的用户级别（应用程序）执行时的CPU平均使用率。 %system：显示在内核级别执行时发生的CPU使用率。 这里不包括耗时的硬/软中断服务 %iowait：CPU 或 CPU 处于空闲状态期间系统有未完成的磁盘 I/O 请求。 %steal： 一个或多个虚拟CPU当在虚拟机管理器服务与另一个虚拟处理器时非自愿等待时间所花费的百分比 %idle : 一个或多个CPU处于idle状态，并且系统没有尚未完成的磁盘 I/O 请求 Device部分：\ntps - 表示每秒发送给设备的传输次数。 Blk_read/s (kB_read/s, MB_read/s) - 表示每秒从设备读取的数据量，以块（KB、MB）表示。 Blk_wrtn/s (kB_read/s, MB_read/s) - 表示每秒写入设备的数据量，以块（KB、MB）表示。 Blk_read (kB_read, MB_read) - 读取块总数（KB、MB） Blk_wrtn (kB_read, MB_read)- 写入块总数（KB、MB） bash 1 2 3 4 5 6 7 8 $ iostat Linux 5.10.0-16-amd64 (debian-template) 12/11/2022 _x86_64_\t(2 CPU) avg-cpu: %user %nice %system %iowait %steal %idle 0.04 0.00 0.09 0.00 0.00 99.87 Device tps kB_read/s kB_wrtn/s kB_dscd/s kB_read kB_wrtn kB_dscd sda 0.26 8.22 1.88 0.00 578135 131934 0 使用示例\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # 显示CPU使用率 iostat -c # 显示CPU使用率 按照周期刷新 sec iostat -c N # 设备使用率 iostat -d # 以人类可读方式展示 iostat -h # 显示一些扩展选项 iostat -x # 以kb为单位展示 iostat -k # 以mb为单位展示 iostat -m # 显示设备与分区的使用率 iostat -p # 显示指定设备的使用率 iostat -p \u0026lt;device_name\u0026gt; # 忽略非活跃设备 iostat -z htop htop可以理解为linux中的与windows任务管理器相同的产品，与top不同的是，htop是一个支持交互式的top命令\n各系统下的包名与安装\nUbuntu/Debian/Mint：apt install htop -y RHEL/CentOS/Fedora：yum install -y htop CPU和内存使用状态 htop上部屏幕，为CPU和存储使用详情\n显示的颜色 默认模式\n蓝色：低优先级进程（nice\u0026gt; 0） 绿色：正常（用户）流程 红色：内核时间（内核，iowait，irqs \u0026hellip;） 橙色：有效时间（窃取时间+访客时间） 详细模式\n蓝色：低优先级线程（nice\u0026gt; 0） 绿色：正常（用户）流程 红色：系统进程 橙色：IRQ时间 洋红色：IRQ时间较慢 灰色：IO等待时间 青色：偷时间 青色：访客时间 内存计量器更简单：\n绿色：已用内存页 蓝色：缓冲页 橙色：缓存页面 可以通过f1查看帮助对于颜色的说明\nldd sar sar System Activity Report的简写，可以用于收集、报告或保存系统活动的统计信息，如 Linux 系统中的 CPU 利用率、内存使用情况、I/O 设备使用情况。 sar 命令显示自系统启动以来的平均统计信息。它在输出中生成报告，也可以保存在文件中。\n各系统下的包名与安装\nUbuntu/Debian/Mint：apt install sysstat -y RHEL/CentOS/Fedora：yum install -y sysstat Notes：sar是服务，需要开启收集才可以查询到，配置 /etc/default/sysstat 修改为 ENABLED=\u0026quot;false\u0026quot; 然后重启服务 systemctl restart sysstat.service\nsar语法\nbash 1 $ sar [option] [interval] [count] 更多可以参考 [5]\nioping ioping是一款磁盘延迟监控工具\nUbuntu/Debian/Mint：apt install -y ioping RHEL/CentOS/Fedora：yum install -y ioping Option describe -c count ping的次数 -i interval 每次请求的间隔 -t time 最大有效的请求事件，太慢的请求将被忽略 -s size 请求大小 -S wsize -o offset 在 file/device 开始的偏移量 -w deadline 在多少时间后停止 -p period 打印每秒请求的原生统计数据 -A 使用异步IO (syscalls io_submit(2), io_submit(2), -B 批量模式，以安静的原始数据方式统计最终的数据 -C 使用 cached I/O 在posix_fadvise(2)读取之前 和写入 fdatasync(2)之后，来抑制缓存失效。 -D 使用direct I/O (O_DIRECT in open(2)). -L 使用序列操作而不是随机操作，这相当于设置了默认大小，例如 -s 256k 与这个是相同的 -R 磁盘查找速度测试，这个选项将以人类可读模式输出每个请求\n设置默认间隔为0, -i=0停止测量将在3秒后停止 -w=3设置工作集大小为64m -S=64m -W 写而不是读。目录目标安全。写入 I/O 为不支持或在某种级别缓存非缓存读取从而提供更可靠的结果。对于file/device来说是危险的：这将会粉碎数据。 -Y 使用同步IO (O_SYNC in open(2)). -y 使用数据同步IO (O_DSYNC in open(2)). -k 重用临时工作目录文件 \u0026ldquo;ioping.tmp\u0026rdquo; （仅对于目标目录生效） -q Suppress periodical human-readable output. 使用示例\nRAW STATISTICS\nbash 1 2 $ ioping -p 100 -c 200 -i 0 -q . 100 16282962 6141 25155128 130599 162830 328499 37909 101 17115660 上面输出的结果意思为：\n统计请求的计数 运行时间 usec 微秒 每秒请求 (iops) 传输速率 (bytes/sec) 最小请求时间 (usec) 平均请求时间 (usec) 最大请求时间 (usec) 请求时间偏差 (usec) 总请求 （包含很慢和很快的） 总共运行时间 (usec) bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 使用默认值和当前目录 测试磁盘 I/O 延迟，ctrl - c 中断。 ioping . # 测量/tmp设备的延迟，总计使用10个请求，每个请求1MB ioping -c 10 -s 1M /tmp # 测量 设备 /dev/sda 的查找速度 ioping -R /dev/sda # 测试设备磁盘序列速度 ioping -RL /dev/sda # 获取磁盘序列的速度（每秒多少字节） ioping -RLB . | awk \u0026#39;{print $4}\u0026#39; vnstat vnstat是 Linux 中用于监控网络参数的命令，通常查看带宽消耗或一些流入或流出的流量，与网络接口上的流量。\n各系统下的包名与安装\nUbuntu/Debian/Mint：apt install -y vnstat RHEL/CentOS/Fedora：epel yum install -y vnstat vnstat是一个守护进程，如果需要记录需要启动这个服务才可以，而不是一个单独的命令\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 以小时显示流量 vnstat -h # 以天显示流量 vnstat -d # 以月为单位展示 vnstat -m # 计算接口多长时间内的流量（这个是实时的，可以不用启动服务） vnstat -tr 10 # 10 sec # 指定一个接口 vnstat -i eth0 # 指定输出格式 vnstat --json vnstat --xml ifstat ifstat是Linux下网络接口统计的命令\nUbuntu/Debian/Mint：apt install -y ifstat RHEL/CentOS/Fedora：yum install -y ifstat bash 1 2 3 4 5 6 7 8 9 10 11 # 指定接口名 ifstat eth0 # 查看全部接口 ifstat -a # 清除网络接口的数据 ifstat -z \u0026lt;interface_name\u0026gt; # 展示x秒内网络数据的平均值 ifstat -t 10 iptraf iptraf是Linux 中交互式的网络监控命令，通过交互式实现展示\n各系统下的包名与安装\nUbuntu/Debian/Mint：apt install -y iptraf-ng RHEL/CentOS/Fedora：yum install -y iptraf-ng iftop 各系统下的包名与安装\nUbuntu/Debian/Mint：apt install -y iftop RHEL/CentOS/Fedora：epel yum install -y iftop bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # 指定端口的带宽统计 iftop -i enp0s8 # 隐藏顶部的流量刻度栏 iftop -b # 不使用域名解析 iftop -n -i enp0s8 # 直接输出为文字，而不是交互式 iftop -t # 显示指定子网的流量 iftop -F 192.168.2.0/24 # 根据source addr排序 iftop -o source # 根据destnation addr排序 iftop -o destination # 显示使用的带宽 iftop -B -i enp0s8 arpwatch arpwatch是Linux上用于监视ARP记录的\n各系统下的包名与安装\nUbuntu/Debian/Mint：apt install -y arpwatch RHEL/CentOS/Fedora：epel yum install -y arpwatch Option describe -d debug模式 -f 设置用于存储 ethernet/ip address 的文件，默认在 /var/arpwatch/arp.dat -i 指定默认接口 -n 指定本地网络 -u 指定用户或用户组 -Q The flags prevents arpwatch from sending reports by mail -z 设置忽略的 IP范围，IP和掩码用 \u0026ldquo;/\u0026rdquo; 化为，如 -z 192.168.10.0/255.255.255.0 bash 1 2 # 指定一个接口，命令并没有输出，当有新IP或MAC被改变时，会保存到/var/log/messages arpwatch -i eth0 Reference [1] perf Examples\n[2] User-space introspection with Linux perf\n[3] Getting Debugging Symbols\n[4] Linux Perf Tools Tips\n[5] 20 sar command examples in Linux\n[6] A Guide to the htop command in Linux\n","permalink":"https://www.161616.top/performance-command/","summary":"工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 perf [1] perf 是基于内核子系统的Linux的性能计数器，也被称为 perf_events，它提供了为所有事件进行性能分析的框架，perf 由两部分组成：\n内核系统调用，用于提供对这些性能数据的访问 用户空间工具，用于提供收集，显示分析这些性能数据的用户空间程序 由于 perf 是内核的一部分，但要想使用 perf 还需要安装另外一部分，通常情况下安装的版本是Linux内核版本，如操作系统内核版本为 5.10 那么安装 linux-tool 后则为 5.10\nbash 1 2 3 4 $ apt-get install linux-perf $ perf --version perf version 5.10.149 各系统下的包名与安装\nUbuntu/Debian: linux-perf | linux-tools ；apt-get install linux-perf CentOS/Fedora: perf ；yum install -y perf list - 列出可用事件描述符 使用 perf 子命令 list 可以列出所有的 perf 可测量事件","title":"长期总结 - Linux性能分析命令"},{"content":"搜索linux 内核 image\nbash 1 apt-cache search linux-image 然后安装对应image\nbash 1 sudo apt install linux-image-\u0026lt;flavour\u0026gt; 安装完成后可以看到对应的image\nbash 1 2 3 4 $ dpkg -l|grep linux-image ri linux-image-5.10.0-16-amd64 5.10.127-2 amd64 Linux 5.10 for 64-bit PCs (signed) ii linux-image-5.10.0-16-amd64-dbg 5.10.127-2 amd64 Debug symbols for linux-image-5.10.0-16-amd64 ii linux-image-amd64 5.10.127-2 amd64 Linux for 64-bit PCs (meta-package) 可以通过命令查看拥有的内核启动项\nbash 1 grep -e \u0026#34;menuentry \u0026#34; -e submenu -e linux /boot/grub/grub.cfg 需要修改至新内核可以修改 /etc/default/grub 下的 GRUB_DEFAULT=\n这里要填的值为上面命令查询出的，例如 menuentry 'Debian GNU/Linux, with Linux 5.10.0-16-amd64'\nbash 1 GRUB_DEFAULT=\u0026#34;1\u0026gt;Debian GNU/Linux, with Linux 5.10.0-12-amd64\u0026#34; 然后执行 update-grub\nReference HowToUpgradeKernel\nHow to set default kernel in Debian?\n","permalink":"https://www.161616.top/debian-update-kernel/","summary":"搜索linux 内核 image\nbash 1 apt-cache search linux-image 然后安装对应image\nbash 1 sudo apt install linux-image-\u0026lt;flavour\u0026gt; 安装完成后可以看到对应的image\nbash 1 2 3 4 $ dpkg -l|grep linux-image ri linux-image-5.10.0-16-amd64 5.10.127-2 amd64 Linux 5.10 for 64-bit PCs (signed) ii linux-image-5.10.0-16-amd64-dbg 5.10.127-2 amd64 Debug symbols for linux-image-5.10.0-16-amd64 ii linux-image-amd64 5.10.127-2 amd64 Linux for 64-bit PCs (meta-package) 可以通过命令查看拥有的内核启动项\nbash 1 grep -e \u0026#34;menuentry \u0026#34; -e submenu -e linux /boot/grub/grub.cfg 需要修改至新内核可以修改 /etc/default/grub 下的 GRUB_DEFAULT=\n这里要填的值为上面命令查询出的，例如 menuentry 'Debian GNU/Linux, with Linux 5.","title":"debian11更新内核版本"},{"content":"服务质量 Quality of Service (QoS)，在Kubernetes是用于解决资源抢占，延迟等方向的一种技术，是服务于调度与抢占之间的条件。\nQoS 级别 QoS 与 资源限制紧密相关，正如下属展示，是一个Pod资源限制部分的配置\nyaml 1 2 3 4 5 6 7 resources: limits: cpu: 200m memory: 1G requests: cpu: 500m memory: 1G 而Kubernetes 将Pod QoS 根据 CPU 与 内存的配置，将QoS分为三个等级：\nGuaranteed：确保的，只设置 limits 或者 requests 与 limits 为相同时则为该等级 Burstable：可突发的，只设置 requests 或 requests 低于 limits 的场景 Best-effort： 默认值，如果不设置则为这个等级 为什么要关心Pod QoS级别 在Kubernetes中，将资源分为两类：可压缩性资源 “CPU”，不可压缩性资源 “内存”。当可压缩性资源用尽时，不会被终止与驱逐，而不可压缩性资源用尽时，即Pod内存不足，此时会被OOMKiller杀掉，也就是被驱逐等操作，而了解Pod 的QoS级别可以有效避免关键Pod被驱逐。\n图：Pod QoS分类 Source：https://doc.kaas.thalesdigital.io/docs/BestPractices/QOS\n有上图可知，BestEffort 级别的 Pod 能够使用节点上所有资源，浙江导致其他 Pod 出现资源问题。所以这类 Pod 优先级最低，如果系统没有内存，将首先被杀死。\nPod是如何被驱逐的 当节点的计算资源不足时，kubelet 会发起驱逐，这个操作是为了避免系统OOM事件，而QoS的等级决定了驱逐的优先级，没有限制资源的 BestEffort 类型的Pod最先被驱逐，接下来资源使用率低于 Requests 的 Guaranteed 与 Burstable 将不会被其他Pod的资源使用量而驱逐，其次对于此类Pod而言，如果Pod使用了比配置（Requests）更多的资源时，会根据这两个级别Pod的优先级进行驱逐。 BestEffort 与 **Burstable **将按照先优先级，后资源使用率顺序进行驱逐\n对于磁盘压力来讲，驱逐顺序根据 BestEffort ==》Burstable ==》Guaranteed 进行驱逐\n如何查看Pod的QoS等级 Pod资源清单中 Status 字段代表Pod QoS等级\nbash 1 kubectl get pod \u0026lt;pod_name\u0026gt; -o jsonpath=\u0026#39;{.status.qosClass}\u0026#39; 如何配置QoS默认级别 如果不想对每个Pod都配置资源限制，Kubernetes提供了一个API LimitRange 可以指定默认的QoS，为Pod提供默认的资源限制，然后准入控制器会增加默认的资源限制 k8s.io/kubernetes/plugin/pkg/admission/limitranger，正如官方给出的实例一样\nNotes：准入控制器与Pod控制器概念不同，准入控制器是 kube-apiserver 请求时的hander chain\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: v1 kind: LimitRange metadata: name: cpu-resource-constraint spec: limits: - default: # this section defines default limits cpu: 500m defaultRequest: # this section defines default requests cpu: 500m max: # max and min define the limit range cpu: \u0026#34;1\u0026#34; min: cpu: 100m 准入控制器会进行检查，而 LimitRange 也是一个标准，限制所有Pod的资源限制标准（Request 与 limits ）必须小于等于 LimitRange 配置的格式，例如下列配置将不会被准入\nyaml 1 2 3 4 5 6 7 8 9 10 11 apiVersion: v1 kind: Pod metadata: name: example-conflict-with-limitrange-cpu spec: containers: - name: demo image: registry.k8s.io/pause:2.0 resources: requests: cpu: 700m 由于该Pod没有配置 Limits ，不符合规范，该Pod不会被调度，错误如下\nbash 1 Pod \u0026#34;example-conflict-with-limitrange-cpu\u0026#34; is invalid: spec.containers[0].resources.requests: Invalid value: \u0026#34;700m\u0026#34;: must be less than or equal to cpu limit 如果同时设置 request 和 limit ，即使大于LimitRange 的配置，新的 Pod 也会被成功调度：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: v1 kind: Pod metadata: name: example-no-conflict-with-limitrange-cpu spec: containers: - name: demo image: registry.k8s.io/pause:2.0 resources: requests: cpu: 700m limits: cpu: 700m ","permalink":"https://www.161616.top/kubernetes-pod-qos/","summary":"服务质量 Quality of Service (QoS)，在Kubernetes是用于解决资源抢占，延迟等方向的一种技术，是服务于调度与抢占之间的条件。\nQoS 级别 QoS 与 资源限制紧密相关，正如下属展示，是一个Pod资源限制部分的配置\nyaml 1 2 3 4 5 6 7 resources: limits: cpu: 200m memory: 1G requests: cpu: 500m memory: 1G 而Kubernetes 将Pod QoS 根据 CPU 与 内存的配置，将QoS分为三个等级：\nGuaranteed：确保的，只设置 limits 或者 requests 与 limits 为相同时则为该等级 Burstable：可突发的，只设置 requests 或 requests 低于 limits 的场景 Best-effort： 默认值，如果不设置则为这个等级 为什么要关心Pod QoS级别 在Kubernetes中，将资源分为两类：可压缩性资源 “CPU”，不可压缩性资源 “内存”。当可压缩性资源用尽时，不会被终止与驱逐，而不可压缩性资源用尽时，即Pod内存不足，此时会被OOMKiller杀掉，也就是被驱逐等操作，而了解Pod 的QoS级别可以有效避免关键Pod被驱逐。\n图：Pod QoS分类 Source：https://doc.kaas.thalesdigital.io/docs/BestPractices/QOS\n有上图可知，BestEffort 级别的 Pod 能够使用节点上所有资源，浙江导致其他 Pod 出现资源问题。所以这类 Pod 优先级最低，如果系统没有内存，将首先被杀死。\nPod是如何被驱逐的 当节点的计算资源不足时，kubelet 会发起驱逐，这个操作是为了避免系统OOM事件，而QoS的等级决定了驱逐的优先级，没有限制资源的 BestEffort 类型的Pod最先被驱逐，接下来资源使用率低于 Requests 的 Guaranteed 与 Burstable 将不会被其他Pod的资源使用量而驱逐，其次对于此类Pod而言，如果Pod使用了比配置（Requests）更多的资源时，会根据这两个级别Pod的优先级进行驱逐。 BestEffort 与 **Burstable **将按照先优先级，后资源使用率顺序进行驱逐","title":"理解Kubernetes驱逐核心 - Pod QoS"},{"content":"驱逐 (eviction) 是指终止在Node上运行的Pod，保证workload的可用性，对于使用Kubernetes，了解驱逐机制是很有必要性的，因为通常情况下，Pod被驱逐是需要解决驱逐背后导致的问题，而想要快速定位就需要对驱逐机制进行了解。\nPod被驱逐原因 Kubernetes官方给出了下属Pod被驱逐的原因：\n抢占驱逐 (Preemption and Eviction) [1] 节点压力驱逐 (Node-pressure) [2] 污点驱逐 (Taints) [3] 使用API发起驱逐 (API-initiated) [4] 排出Node上的Pod (drain) [5] 被 controller-manager 驱逐 抢占和优先级 抢占是指当节点资源不足以运行新添加的Pod时，kube-scheduler 会检查低优先级Pod而后驱逐掉这些Pod以将资源分配给优先级高的Pod。这个过程称为 “抢占” 例如这个实例是 kube-proxy 被驱逐的场景\n节点压力驱逐 节点压力驱逐是指，Pod所在节点的资源，如CPU, 内存, inode等，这些资源被分为可压缩资源CPU (compressible resources) 与不可压缩资源 (incompressible resources) 磁盘IO, 内存等，当不可压缩资源不足时，Pod会被驱逐。对于此类问题的驱逐 是每个计算节点的 kubelet 通过捕获 cAdvisor 指标来监控节点的资源使用情况。\n被 controller-manager 驱逐 kube-controller-manager 会定期检查节点的状态，如节点处于 NotReady 超过一定时间，或Pod部署长时间失败，这些Pod由控制平面 controller-manager 创建新的Pod已替换存在问题的Pod\n通过API发起驱逐 Kubernetes为用户提供了驱逐的API，用户可以通过调用API来实现自定义的驱逐。\n对于 1.22 以上版本，可以通过API policy/v1 进行驱逐\nbash 1 2 3 4 5 6 7 8 9 10 11 curl -v \\ -H \u0026#39;Content-type: application/json\u0026#39; \\ https://your-cluster-api-endpoint.example/api/v1/namespaces/default/pods/quux/eviction -d \u0026#39;\\ { \u0026#34;apiVersion\u0026#34;: \u0026#34;policy/v1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;Eviction\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;quux\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;default\u0026#34; } }\u0026#39; 例如，要驱逐Pod netbox-85865d5556-hfg6v，可以通过下述命令\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # 1.22+ $ curl -v \u0026#39;https://10.0.0.4:6443/api/v1/namespaces/default/pods/netbox-85865d5556-hfg6v/eviction\u0026#39; \\ --header \u0026#39;Content-Type: application/json\u0026#39; \\ --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt \\ --key /etc/kubernetes/pki/apiserver-kubelet-client.key \\ --cacert /etc/kubernetes/pki/ca.crt \\ -d \u0026#39;{ \u0026#34;apiVersion\u0026#34;: \u0026#34;policy/v1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;Eviction\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;netbox-85865d5556-hfg6v\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;default\u0026#34; } }\u0026#39; # 1.22- curl -v \u0026#39;https://10.0.0.4:6443/api/v1/namespaces/default/pods/netbox-85865d5556-hfg6v/eviction\u0026#39; \\ --header \u0026#39;Content-Type: application/json\u0026#39; \\ --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt \\ --key /etc/kubernetes/pki/apiserver-kubelet-client.key \\ --cacert /etc/kubernetes/pki/ca.crt \\ -d \u0026#39;{ \u0026#34;apiVersion\u0026#34;: \u0026#34;policy/v1beta1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;Eviction\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;netbox-85865d5556-hfg6v\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;default\u0026#34; } }\u0026#39; 可以看到结果，旧Pod被驱逐，而新Pod被创建，在这里实验环境节点较少，所以体现为没有更换节点\nbash 1 2 3 4 5 $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES netbox-85865d5556-hfg6v 1/1 Terminating 0 101d 192.168.1.213 master-machine \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; netbox-85865d5556-vlgr4 1/1 Running 0 101d 192.168.0.4 node01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; netbox-85865d5556-z6vqx 1/1 Running 0 11s 192.168.1.220 master-machine \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 通过API驱逐返回状态 200 OK|201 Success：允许驱逐，Eviction 类似于向Pod URL发送 DELETE 请求 429 Too Many Requests：由于API限速可能会看到该相应，另外也为配置原因，不允许驱逐 poddisruptionbudget (PDB是一种保护机制，将总是确保一定数量或百分比的Pod 被自愿驱逐) 500 Internal Server Error：不允许驱逐，存在错误配置，如多个PDB引用一个 Pod 排出Node上的Pod drain 是kubernetes 1.5+之后提供给用户维护命令，通过这个命令 (kubectl drain \u0026lt;node_name\u0026gt;) 可以驱逐该节点上运行的所有Pod，已用来对节点主机进行操作（如内核升级，重启）\nNotes：kubectl drain \u0026lt;node_name\u0026gt; 一次只能接一个nodename [6]\n污点驱逐 污点通常与容忍度同时使用，拥有污点的node，Pod将不会被调度至该节点，而容忍度将允许一定的污点来调度 pod。\n在Kubernetes 1.18+后，允许基于污点的驱逐机制，即kubelet在某些情况下会自动添加节点从而进行驱逐：\nKubernetes内置了一些污点，此时 Controller 会自动污染节点：\nnode.kubernetes.io/not-ready: Node故障。对应 NodeCondition 的Ready = False。 node.kubernetes.io/unreachable：Node控制器无法访问节点。对应 NodeCondition Ready= Unknown。 node.kubernetes.io/memory-pressure：Node内存压力。 node.kubernetes.io/disk-pressure：Node磁盘压力。 node.kubernetes.io/pid-pressure：Node有PID压力。 node.kubernetes.io/network-unavailable：Node网络不可用。 node.kubernetes.io/unschedulable：Node不可调度。 【转】实例：Pod被驱逐故障排除过程 [7] 设想一个场景：，有三个工作节点的Kubernetes 集群，版本为 v1.19.0。发现在 worker 1 上运行的一些 pod 被驱逐了\n图：Pod被驱逐的日志 Source：https://www.containiq.com/post/kubernetes-pod-evictions\n从上图可以看出有很多pod被驱逐了，报错信息也很清楚。由于节点上存储资源不足，导致kubelet触发驱逐过程。\n方法1：启用auto-scaler 向集群添加工作节点，要么部署cluster-autoscaler以根据配置的条件自动扩缩容。\n只增加worker的本地存储空间，这涉及到虚拟机的扩容，会导致worker节点暂时不可用。\n方法2：保护关键Pod 在资源清单中指定资源请求和限制，配置QoS (Quality of Service)。当kubelet触发驱逐时，将至少保证这些 pod 不受影响。\n这种施在一定程度上保证了一些关键Pod的可用性。如果节点出现问题时 Pod 没有被驱逐，这将需要执行更多步骤来查找故障。\n运行命令 kubectl get pods 结果显示很多 pod 处于 evicted 状态。检查结果将保存在节点的kubelet日志中。查找对应日志使用 cat /var/paas/sys/log/kubernetes/kubelet.log | grep -i Evicted -C3。\n检查思路 查看Pod容忍度 当Pod故障无法连接或节点无法响应时，可以使用 tolerationSeconds 配置对应时长长短\nyaml 1 2 3 4 5 tolerations: - key: \u0026#34;node.kubernetes.io/unreachable\u0026#34; operator: \u0026#34;Exists\u0026#34; effect: \u0026#34;NoExecute\u0026#34; tolerationSeconds: 6000 查看防止 Pod 驱逐的条件 如果集群中的节点数小于50，并且故障节点数超过总节点数的55%，则暂停 Pod 驱逐。在这种情况下，Kubernetes 将尝试驱逐故障节点的工作负载（运行在kubernetes中的APP）。\n下属json描述了一个健康的节点\njson 1 2 3 4 5 6 7 8 9 10 \u0026#34;conditions\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;Ready\u0026#34;, \u0026#34;status\u0026#34;: \u0026#34;True\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;KubeletReady\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;kubelet is posting ready status\u0026#34;, \u0026#34;lastHearbeatTime\u0026#34;: \u0026#34;2019-06-05T18:38:35Z\u0026#34;, \u0026#34;lastTransitionTime\u0026#34;: \u0026#34;2019-06-05T11:41:27Z\u0026#34; } ] 如果就绪条件为 Unknown 或 False 的时间超过了 pod-eviction-timeout，node controller 将对分配给该节点上的所有 Pod 执行 API-initiated 类型驱逐。\n检查Pod的已分配资源 Pod会根据节点的资源使用情况被逐出。被逐出的Pod将会根据分配给Pod的节点资源进行调度。管理驱逐”和“调度”的条件由不同的规则组成。这种结果会导致，被逐出的容器可能会被重新安排到原始节点。因此，要合理分配资源给每个容器。\n检查Pod 是否定期失败 Pod 可以被驱逐多次。即如果在 Pod 被驱逐并调度到新节点后该节点中的 Pod 也被驱逐，则该 Pod 将再次被驱逐。\n如果驱逐动作是由 kube-controller-manager 触发的，则保留处于 Terminating 状态的 Pod 。在节点恢复后，Pod将被 自动销毁。如果节点已经被删除或者其他原因无法恢复，可以强制删除Pod。\n如果是由 kubelet 触发的驱逐，Pod 状态将保留为 Evicted 状态。仅用于后期故障定位，可直接删除。\n删除被逐出的 Pod 命令为：\nbash 1 kubectl get pods | grep Evicted | awk ‘{print $1}’ | xargs kubectl delete pod Notes：\n被Kubernetes驱逐的Pod，不会被自动重新创建 pod。如果要重新创建Pod，需要使用replicationcontroller、replicaset和 deployment 机制，这也是上述提到的Kubernetes的工作负载。 Pod控制器是协调一组Pod始终为理想状态的控制器，所以会删除后重建，也是Kubernetes 声明式API的特点 如何监控被驱逐的Pod 使用Prometheus bash 1 kube_pod_status_reason{reason=\u0026#34;Evicted\u0026#34;} \u0026gt; 0 使用 ContainIQ ContainIQ 是为Kubernetes设计的可观测性工具，其中包含Kubernetes 事件仪表板，这就包括 Pod 驱逐事件\nReference ​[1] Scheduling, Preemption and Eviction ​[2] Node-pressure Eviction ​[3] Taints and Tolerations ​[4] API-initiated Eviction ​[5] Safely Drain a Node ​[6] Draining multiple nodes in parallel ​[7] Kubernetes Pod Evictions | Troubleshooting and Examples ​[8] kubernetes pod evicted\n","permalink":"https://www.161616.top/kubernetes-eviction/","summary":"驱逐 (eviction) 是指终止在Node上运行的Pod，保证workload的可用性，对于使用Kubernetes，了解驱逐机制是很有必要性的，因为通常情况下，Pod被驱逐是需要解决驱逐背后导致的问题，而想要快速定位就需要对驱逐机制进行了解。\nPod被驱逐原因 Kubernetes官方给出了下属Pod被驱逐的原因：\n抢占驱逐 (Preemption and Eviction) [1] 节点压力驱逐 (Node-pressure) [2] 污点驱逐 (Taints) [3] 使用API发起驱逐 (API-initiated) [4] 排出Node上的Pod (drain) [5] 被 controller-manager 驱逐 抢占和优先级 抢占是指当节点资源不足以运行新添加的Pod时，kube-scheduler 会检查低优先级Pod而后驱逐掉这些Pod以将资源分配给优先级高的Pod。这个过程称为 “抢占” 例如这个实例是 kube-proxy 被驱逐的场景\n节点压力驱逐 节点压力驱逐是指，Pod所在节点的资源，如CPU, 内存, inode等，这些资源被分为可压缩资源CPU (compressible resources) 与不可压缩资源 (incompressible resources) 磁盘IO, 内存等，当不可压缩资源不足时，Pod会被驱逐。对于此类问题的驱逐 是每个计算节点的 kubelet 通过捕获 cAdvisor 指标来监控节点的资源使用情况。\n被 controller-manager 驱逐 kube-controller-manager 会定期检查节点的状态，如节点处于 NotReady 超过一定时间，或Pod部署长时间失败，这些Pod由控制平面 controller-manager 创建新的Pod已替换存在问题的Pod\n通过API发起驱逐 Kubernetes为用户提供了驱逐的API，用户可以通过调用API来实现自定义的驱逐。\n对于 1.22 以上版本，可以通过API policy/v1 进行驱逐\nbash 1 2 3 4 5 6 7 8 9 10 11 curl -v \\ -H \u0026#39;Content-type: application/json\u0026#39; \\ https://your-cluster-api-endpoint.","title":"kubernetes概念 - 理解Kubernetes的驱逐机制"},{"content":" 本文是关于Kubernetes 4A解析的第4章 深入理解Kubernetes 4A - Authentication源码解析 深入理解Kubernetes 4A - Authorization源码解析 深入理解Kubernetes 4A - Admission Control源码解析 深入理解Kubernetes 4A - Audit源码解析 TLS Everywhere - 解密kubernetes集群的安全认证 所有关于Kubernetes 4A部分代码上传至仓库 github.com/cylonchau/hello-k8s-4A\nOverview 审计是信息系统中非常重要的一部分，Kubernetes 1.11中也增加了审计 (Auditing) 功能，通过审计功能获得 deployment, ns,等资源操作的事件。\nobjective：\n从设计角度了解Auditing在kubernets中是如何实现的 了解kubernetes auditing webhook 完成实验，通过webhook来收集审计日志 如有错别字或理解错误地方请多多担待，代码是以1.24进行整理，实验是以1.19环境进行，差别不大。\nKubernetes Auditing 根据Kubernetes官方描述审计在kubernetes中是有控制平面 kube-apiserver 中产生的一个事件，记录了集群中所操作的资源，审计围绕下列几个维度来记录事件的：\n发生了什么 发生的事件 谁触发的 发生动作的对象 在哪里检查到动作的 从哪触发的 处理行为是什么 审计生命周期开始于组件 kube-apiserver 准入控制阶段，在每个阶段内都会产生审计事件并经过预处理后写入后端，目前后端包含webhook与日志文件。\n审计日志功能增加了 kube-apiserver 的内存消耗，因为会为每个请求存储了审计所需的上下文。内存的消耗取决于审计日志配置 [1]。\n审计事件设计 审计的schema不同于资源API的设计，没有 metav1.ObjectMeta 属性，Event是一个事件的结构体，Policy是事件配置，属于kubernetes资源，在代码 k8s.io/apiserver/pkg/apis/audit/types.go 可以看到\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Event struct { metav1.TypeMeta `json:\u0026#34;,inline\u0026#34;` Level Level `json:\u0026#34;level\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=level,casttype=Level\u0026#34; AuditID types.UID `json:\u0026#34;auditID\u0026#34; protobuf:\u0026#34;bytes,2,opt,name=auditID,casttype=k8s.io/apimachinery/pkg/types.UID\u0026#34;` Stage Stage `json:\u0026#34;stage\u0026#34; protobuf:\u0026#34;bytes,3,opt,name=stage,casttype=Stage\u0026#34;` RequestURI string `json:\u0026#34;requestURI\u0026#34; protobuf:\u0026#34;bytes,4,opt,name=requestURI\u0026#34;` Verb string `json:\u0026#34;verb\u0026#34; protobuf:\u0026#34;bytes,5,opt,name=verb\u0026#34;` User authnv1.UserInfo `json:\u0026#34;user\u0026#34; protobuf:\u0026#34;bytes,6,opt,name=user\u0026#34;` ImpersonatedUser *authnv1.UserInfo `json:\u0026#34;impersonatedUser,omitempty\u0026#34; protobuf:\u0026#34;bytes,7,opt,name=impersonatedUser\u0026#34;` SourceIPs []string `json:\u0026#34;sourceIPs,omitempty\u0026#34; protobuf:\u0026#34;bytes,8,rep,name=sourceIPs\u0026#34;` UserAgent string `json:\u0026#34;userAgent,omitempty\u0026#34; protobuf:\u0026#34;bytes,16,opt,name=userAgent\u0026#34;` ObjectRef *ObjectReference `json:\u0026#34;objectRef,omitempty\u0026#34; protobuf:\u0026#34;bytes,9,opt,name=objectRef\u0026#34;` // +optional ResponseStatus *metav1.Status `json:\u0026#34;responseStatus,omitempty\u0026#34; protobuf:\u0026#34;bytes,10,opt,name=responseStatus\u0026#34;` ... } 对于记录的认证事件来说，会根据请求阶段记录审计的阶段，主要分为下属集中情况，每个请求会记录其中一个验证阶段，如代码所示 [1]\ngo 1 2 3 4 5 6 7 8 9 10 11 const ( // 这个阶段是audit handler收到请求后立即生成事件的阶段，然后委托handler chain处理。 StageRequestReceived Stage = \u0026#34;RequestReceived\u0026#34; // 这个阶段阶段仅对长时间运行的请求如 watch // 将在发送响应标头后，响应正文之前生成的阶段 StageResponseStarted Stage = \u0026#34;ResponseStarted\u0026#34; // 这个阶段是发送相应体后的事件。 StageResponseComplete Stage = \u0026#34;ResponseComplete\u0026#34; // 如果程序出现panic，则触发这个阶段 StagePanic Stage = \u0026#34;Panic\u0026#34; ) 审计工作流程 审计真正工作的地方在 k8s.io/apiserver/pkg/endpoints/filters/audit.go.WithAudit 函数，下面对与官方文档说明与这个实际代码进行结合\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 func WithAudit(handler http.Handler, sink audit.Sink, policy audit.PolicyRuleEvaluator, longRunningCheck request.LongRunningRequestCheck) http.Handler { // sink是一个backend（webhook 或 日志），policy则是自定义的事件配置 // 如果两者之一未配置，则不会使用审计功能 if sink == nil || policy == nil { return handler } return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { // 通过给定的配置与请求构建出一个事件 context // 这里可以看到 auditContext, err := evaluatePolicyAndCreateAuditEvent(req, policy) if err != nil { utilruntime.HandleError(fmt.Errorf(\u0026#34;failed to create audit event: %v\u0026#34;, err)) responsewriters.InternalError(w, req, errors.New(\u0026#34;failed to create audit event\u0026#34;)) return } // 下面代码可以看出是对事件context进行构建，与拿到来自请求Context ev := auditContext.Event if ev == nil || req.Context() == nil { handler.ServeHTTP(w, req) return } req = req.WithContext(audit.WithAuditContext(req.Context(), auditContext)) ctx := req.Context() omitStages := auditContext.RequestAuditConfig.OmitStages // 这里到StageRequestReceived阶段，如果是收到请求阶段则通过注入的后端进行处理 ev.Stage = auditinternal.StageRequestReceived if processed := processAuditEvent(ctx, sink, ev, omitStages); !processed { audit.ApiserverAuditDroppedCounter.WithContext(ctx).Inc() responsewriters.InternalError(w, req, errors.New(\u0026#34;failed to store audit event\u0026#34;)) return } // 拦截watch类长请求的状态码 var longRunningSink audit.Sink if longRunningCheck != nil { ri, _ := request.RequestInfoFrom(ctx) if longRunningCheck(req, ri) { longRunningSink = sink } } respWriter := decorateResponseWriter(ctx, w, ev, longRunningSink, omitStages) // send audit event when we leave this func, either via a panic or cleanly. In the case of long // running requests, this will be the second audit event. // 在离开函数前会处理 ResponseStarted、ResponseComplete、Panic这三个阶段 defer func() { if r := recover(); r != nil { defer panic(r) // 当前发生panic的请求 ev.Stage = auditinternal.StagePanic ev.ResponseStatus = \u0026amp;metav1.Status{ Code: http.StatusInternalServerError, Status: metav1.StatusFailure, Reason: metav1.StatusReasonInternalError, Message: fmt.Sprintf(\u0026#34;APIServer panic\u0026#39;d: %v\u0026#34;, r), } processAuditEvent(ctx, sink, ev, omitStages) return } // if no StageResponseStarted event was sent b/c neither a status code nor a body was sent, fake it here // But Audit-Id http header will only be sent when http.ResponseWriter.WriteHeader is called. fakedSuccessStatus := \u0026amp;metav1.Status{ Code: http.StatusOK, Status: metav1.StatusSuccess, Message: \u0026#34;Connection closed early\u0026#34;, } if ev.ResponseStatus == nil \u0026amp;\u0026amp; longRunningSink != nil { ev.ResponseStatus = fakedSuccessStatus ev.Stage = auditinternal.StageResponseStarted processAuditEvent(ctx, longRunningSink, ev, omitStages) } // ResponseStarted 在响应头发送后，响应体发送前的事件。watch会触发他 ev.Stage = auditinternal.StageResponseComplete if ev.ResponseStatus == nil { // 没有相应状态 正是上面构造的fakedSuccessStatus ev.ResponseStatus = fakedSuccessStatus } // 将事件发送到后端 processAuditEvent(ctx, sink, ev, omitStages) }() handler.ServeHTTP(respWriter, req) }) } 在评估请求时，会调用 GetAuthorizerAttributes(ctx) ，这里通过授权记录然后来通过给定的审计配置来\n当在将事件发送到后端时，使用 processAuditEvent() 函数，最终修改时间后会转交至后端函数，例如webhook，会请求后端配置的webhook url的客户端，最终被执行 return sink.ProcessEvents(ev)\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (b *backend) processEvents(ev ...*auditinternal.Event) error { var list auditinternal.EventList for _, e := range ev { list.Items = append(list.Items, *e) } return b.w.WithExponentialBackoff(context.Background(), func() rest.Result { trace := utiltrace.New(\u0026#34;Call Audit Events webhook\u0026#34;, utiltrace.Field{\u0026#34;name\u0026#34;, b.name}, utiltrace.Field{\u0026#34;event-count\u0026#34;, len(list.Items)}) // Only log audit webhook traces that exceed a 25ms per object limit plus a 50ms request overhead allowance. The high per object limit used here is primarily to allow enough time for the serialization/deserialization of audit events, which contain nested request and response objects plus additional event fields. defer trace.LogIfLong(time.Duration(50+25*len(list.Items)) * time.Millisecond) return b.w.RestClient.Post().Body(\u0026amp;list).Do(context.TODO()) }).Error() } 在 k8s.io/apiserver/pkg/endpoints/filters/audit.go.evaluatePolicyAndCreateAuditEvent 会评估请求的级别和规则，而 k8s.io/apiserver/pkg/audit/policy/checker.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func (p *policyRuleEvaluator) EvaluatePolicyRule(attrs authorizer.Attributes) auditinternal.RequestAuditConfigWithLevel { for _, rule := range p.Rules { // 评估则是评估用户与用户组，verb，ns,非API资源 /metrics /healthz if ruleMatches(\u0026amp;rule, attrs) { // 通过后，则将这条规则与配置返回 return auditinternal.RequestAuditConfigWithLevel{ Level: rule.Level, RequestAuditConfig: auditinternal.RequestAuditConfig{ OmitStages: rule.OmitStages, OmitManagedFields: isOmitManagedFields(\u0026amp;rule, p.OmitManagedFields), }, } } } // 如果条件都不满足，则构建一个 return auditinternal.RequestAuditConfigWithLevel{ Level: DefaultAuditLevel, RequestAuditConfig: auditinternal.RequestAuditConfig{ OmitStages: p.OmitStages, OmitManagedFields: p.OmitManagedFields, }, } } k8s.io/apiserver/pkg/audit/request.go.NewEventFromRequest 创建出审计事件对象被上面 evaluatePolicyAndCreateAuditEvent 返回\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 // evaluatePolicyAndCreateAuditEvent is responsible for evaluating the audit // policy configuration applicable to the request and create a new audit // event that will be written to the API audit log. // - error if anything bad happened func evaluatePolicyAndCreateAuditEvent(req *http.Request, policy audit.PolicyRuleEvaluator) (*audit.AuditContext, error) { ctx := req.Context() attribs, err := GetAuthorizerAttributes(ctx) if err != nil { return nil, fmt.Errorf(\u0026#34;failed to GetAuthorizerAttributes: %v\u0026#34;, err) } ls := policy.EvaluatePolicyRule(attribs) audit.ObservePolicyLevel(ctx, ls.Level) if ls.Level == auditinternal.LevelNone { // Don\u0026#39;t audit. return \u0026amp;audit.AuditContext{ RequestAuditConfig: ls.RequestAuditConfig, }, nil } requestReceivedTimestamp, ok := request.ReceivedTimestampFrom(ctx) if !ok { requestReceivedTimestamp = time.Now() } ev, err := audit.NewEventFromRequest(req, requestReceivedTimestamp, ls.Level, attribs) if err != nil { return nil, fmt.Errorf(\u0026#34;failed to complete audit event from request: %v\u0026#34;, err) } return \u0026amp;audit.AuditContext{ RequestAuditConfig: ls.RequestAuditConfig, Event: ev, }, nil } 到这里，已经清楚的了解到，Kubernetes审计工作与什么位置了，而对于Kubernetes准入给出的登录（Authentication），授权 (Authorization) 与 准入控制 (Admission control) 三个阶段来说，Audition 位于授权之后，正如下图所示，而这个真正的流程在kubernetes中有个属于叫 handler chain 整个链条中，准入与审计只是其中一部分。\n图：Kubernetes 4A 的 handler chain\n由图再结合代码可以看出，所有的客户端访问API都需要经过完整经由整个链条，而 Auditing 事件的构建是需要获取经由验证过的用户等资源构建出的事件，首次发生的为 StageRequestReceived ，这将在收到请求后执行，而由代码又可知，因为在最终结束掉整个请求时会执行 WithAudit 函数，这就为 StageResponseComplete 与 StageResponseStarted 这两个阶段被执行，而这个将发生在被注册的 handler 完成后，也就是 Admission control 后因为 AC 是在每个真实REST中被执行。TODO\n审计策略级别 [2] 审计策略级别是控制审计记录将记录那些对象的数据内容，当事件被处理时，会按照配置的审计规则进行比较。而使用该功能需要 kube-apiserver 开启参数 --audit-policy-file 指定对应的配置，如果未指定则默认不记录任何事件，可供定义的级别有四个，被定义在 k8s.io/apiserver/pkg/apis/audit/v1/types.go 中\ngo 1 2 3 4 5 6 7 8 9 10 11 12 const ( // LevelNone disables auditing LevelNone Level = \u0026#34;None\u0026#34; // LevelMetadata provides the basic level of auditing. LevelMetadata Level = \u0026#34;Metadata\u0026#34; // LevelRequest provides Metadata level of auditing, and additionally // logs the request object (does not apply for non-resource requests). LevelRequest Level = \u0026#34;Request\u0026#34; // LevelRequestResponse provides Request level of auditing, and additionally // logs the response object (does not apply for non-resource requests). LevelRequestResponse Level = \u0026#34;RequestResponse\u0026#34; ) None： 不记录符合该规则的事件 Metadata：只记录请求元数据（如User, timestamp, resources, verb），不记录请求和响应体。 Request：记录事件元数据和请求体，不记录响应体。 RequestResponse： 记录事件元数据，请求和响应体 下面是Kubernetes官网给出的 Policy 的配置 [2]\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 apiVersion: audit.k8s.io/v1 # This is required. kind: Policy # omitStages 代表忽略该阶段所有请求事件 # RequestReceived 这里配置的指在RequestReceived阶段忽略所有请求事件 omitStages: - \u0026#34;RequestReceived\u0026#34; rules: # 记录将以RequestResponse级别的格式记录pod更改 - level: RequestResponse resources: - group: \u0026#34;\u0026#34; # 这里资源的配置必须与RBAC配置的一致，pods将不支持pods/log这类子资源 resources: [\u0026#34;pods\u0026#34;] # 如果需要配置子资源按照下列方式 - level: Metadata resources: - group: \u0026#34;\u0026#34; resources: [\u0026#34;pods/log\u0026#34;, \u0026#34;pods/status\u0026#34;] # 不记录的资源为controller-leader的configmaps资源的请求 - level: None resources: - group: \u0026#34;\u0026#34; resources: [\u0026#34;configmaps\u0026#34;] resourceNames: [\u0026#34;controller-leader\u0026#34;] # 不记录用户为 \u0026#34;system:kube-proxy\u0026#34; 发起的对 endpoints与services资源的watch请求事件 - level: None users: [\u0026#34;system:kube-proxy\u0026#34;] verbs: [\u0026#34;watch\u0026#34;] resources: - group: \u0026#34;\u0026#34; # core API group resources: [\u0026#34;endpoints\u0026#34;, \u0026#34;services\u0026#34;] # 每个登录成功的用户，都会被追加一个用户组为 \u0026#34;system:authenticated\u0026#34; # 下述规则为不记录包含非资源类型的URL的已认证请求 - level: None userGroups: [\u0026#34;system:authenticated\u0026#34;] nonResourceURLs: - \u0026#34;/api*\u0026#34; # Wildcard matching. - \u0026#34;/version\u0026#34; # 记录kube-system名称空间configmap更改事件的请求体与元数据 - level: Request resources: - group: \u0026#34;\u0026#34; # core API group resources: [\u0026#34;configmaps\u0026#34;] # This rule only applies to resources in the \u0026#34;kube-system\u0026#34; namespace. # The empty string \u0026#34;\u0026#34; can be used to select non-namespaced resources. namespaces: [\u0026#34;kube-system\u0026#34;] # 事件将记录所有名称空间内对于configmap与secret资源改变的元数据 - level: Metadata resources: - group: \u0026#34;\u0026#34; # core API group resources: [\u0026#34;secrets\u0026#34;, \u0026#34;configmaps\u0026#34;] # 记录对group为core与extensions下的资源类型请求的 请求体与元数据（request级别） - level: Request resources: - group: \u0026#34;\u0026#34; # core API group - group: \u0026#34;extensions\u0026#34; # Version of group should NOT be included. # 这种属于泛规则，会记录所有上述其他之外的所有类型请求的元数据 # 类似于授权，小权限在前，* 最后 - level: Metadata # Long-running requests like watches that fall under this rule will not # generate an audit event in RequestReceived. omitStages: - \u0026#34;RequestReceived\u0026#34; Backend [3] kubernetes目前为Auditing 提供了两个后端，日志方式与webhook方式，kubernetes审计事件会遵循 audit.k8s.io 结构写入到后端。\n日志模式配置 启用日志模式只需要配置几个参数 [4]\n--audit-log-path 写入审计事件的日志路径。这个是必须配置的否则默认输出到STDOUT --audit-log-maxage 审计日志文件保留的最大天数 --audit-log-maxbackup 审计日志保留的的最大数量 --audit-log-maxsize 审计日志文件最大大小（单位M）大于会切割 例如配置\nbash 1 2 3 --audit-policy-file=/etc/kubernetes/audit-policy.yaml \\ --audit-log-path=/var/log/kubernetes/audit.log \\ --audit-log-maxsize=20M webhook [5] webhook是指审计事件将由 kube-apiserver 发送到webhook服务中记录，开启webhook只需要配置 --audit-webhook-config-file 与 --audit-policy-file 两个参数，而其他的则是对该模式的辅助\n--audit-webhook-config-file ：webhook的配置文件，格式是kubeconfig类型，所有的信息不是kubernetes api配置，而是webhook相关信息\n--audit-webhook-initial-backoff ：第一次失败后重试事件，随后仍失败后将以指数方式退避重试\n--audit-webhook-mode ：发送至webhook的模式。 batch, blocking, blocking-strict 。\n例如配置\nyaml 1 2 3 --audit-policy-file=/etc/kubernetes/audit-policy.yaml \\ --audit-webhook-config-file=/etc/kubernetes/auth/audit-webhook.yaml \\ --audit-webhook-mode=batch \\ 对于initialBackoff 的退避重试则如代码所示 k8s.io/apiserver/pkg/server/options/audit.go\ngo 1 2 3 4 5 6 7 8 9 func (o *AuditWebhookOptions) newUntruncatedBackend(customDial utilnet.DialFunc) (audit.Backend, error) { groupVersion, _ := schema.ParseGroupVersion(o.GroupVersionString) webhook, err := pluginwebhook.NewBackend(o.ConfigFile, groupVersion, webhook.DefaultRetryBackoffWithInitialDelay(o.InitialBackoff), customDial) if err != nil { return nil, fmt.Errorf(\u0026#34;initializing audit webhook: %v\u0026#34;, err) } webhook = o.BatchOptions.wrapBackend(webhook) return webhook, nil } 在函数 k8s.io/apiserver/pkg/util/webhook/webhook.go.DefaultRetryBackoffWithInitialDelay 中看到 通过 wait.Backoff 进行的\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 return wait.Backoff{ // 时间间隔，用于调用 Step 方法时返回的时间间隔 Duration: initialBackoffDelay, // 用于计算下次的时间间隔，不能为负数 // Factor 大于 0 时，Backoff 在计算下次的时间间隔时都会根据 // Duration * Factor，Factor * Duration 不能大于 Cap Factor: 1.5, // 抖动，Jitter \u0026gt; 0 时，每次迭代的时间间隔都会额外加上 0 - Duration * Jitter 的随机时间, // 并且抖动出的时间不会设置为 Duration，而且不受 Caps 的限制 Jitter: 0.2, // 进行指数回退(*Factor) 操作的次数 // 当 Factor * Duration \u0026gt; Cap 时 Steps 会被设置为 0, Duration 设置为 Cap // 也就是说后续的迭代时间间隔都会返回 Duration Steps: 5, // 还有一个cap（Cap time.Duration），是最大的时间间隔 } 批处理 日志后端与webhook后端都支持批处理模式，默认值为webhook默认开启batch，而log则被禁用\n--audit-log-mode/--audit-webhook-mode ：参数通过将webhook替换为log则为对应的 batch 模式的参数，可以通过 kube-apiserver --help|grep \u0026quot;audit\u0026quot;|grep batch 查看 batch 默认值，缓冲事件进行异步批量处理 --audit-webhook-batch-buffer-size：批处理之前要缓冲的事件数。如果传入事件的溢出，则被丢弃。 --audit-webhook-batch-max-size：定义每一批中的最大事件数 --audit-webhook-batch-max-wait：批处理队列未满时等待的事件，到时强制写入一次 --audit-webhook-batch-throttle-qps：定义每秒最大平均批次 --audit-webhook-batch-throttle-burst：如果之前还没使用throttle-qps之前，发送的最大批数，通常情况下为第一次启动时生效的参数 blocking 阻止 apiserver 处理每个单独事件 blocking-strict：与 blocking 相同，但当 RequestReceived 阶段的审计日志记录失败时，对 kube-apiserver 的整个请求都将失败 参数调整 适当的调整参数与策略可以有效适应 kuber-apiserver 的负载，如在记录日志时应只记录所需的事件，而不是所有的事件，这样可以避免 APIServer不必要开销，例如：\n每个请求存在多个阶段，而审计时其实不关心响应等信息，可以只记录 RequestReceived 的 metadata 级别。 \u0026ldquo;pods/log\u0026rdquo;, \u0026ldquo;pods/status\u0026rdquo; 在记录时应该区分子资源类型，而不要直接写 pods 或 pods/* kubernetes系统组件内的事件如果没有特殊要求可以不记录 对于资源类型，如configmap的请求其实没必要记录 审计记录应严格按照外部用户记录，而不是所有请求 如何适配APIServer的负载能力，正如官方给的示例一样，如果 kube-apiserver 每秒收到100个请求，而记录事件为 ResponseStarted 和ResponseComplete 阶段，此时会记录的条数约 200/s ，如果batch缓冲区为100，那么需要配置的参数至少2Qps/s。再假设后端处理能力为5秒，那么缓冲区需要配置的大小至少为5秒的事件，即1000条evnet，10个batch。正如下图所示：\n图：审计批处理参数调优结构图 Source：https://www.cnblogs.com/zhangmingcheng/p/16539514.html\n而kube-apiserver提供了两个Prometheus指标可以用于监控审计子系统的状态\napiserver_audit_event_total 审计事件的总数 apiserver_audit_error_total 由于错误而被丢弃的审计事件总数，例如panic类型事件 实验：Audit Webhook 编写一个webhook，用于处理接收到的日志，这里直接打印\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 func serveAudit(w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil { httpError(w, err) return } var eventList audit.EventList err = json.Unmarshal(b, \u0026amp;eventList) if err != nil { klog.V(3).Info(\u0026#34;Json convert err: \u0026#34;, err) httpError(w, err) return } for _, event := range eventList.Items { // here is your logic fmt.Printf(\u0026#34;审计ID %s: 用户\u0026lt;%s\u0026gt;, 请求对象\u0026lt;%s\u0026gt;, 操作\u0026lt;%s\u0026gt;, 请求阶段\u0026lt;%s\u0026gt;\\n\u0026#34;, event.AuditID, event.User.UID, event.RequestURI, event.Verb, event.Stage, ) } w.WriteHeader(http.StatusOK) } 当使用命令执行查看Pod的操作时，会看到webhook收到的下述审计日志\n操作命令\nbash 1 for n in `seq 1 100`; do kubectl get pod --user=admin; done 审计日志\ntext 1 2 3 4 5 6 审计ID c0313416-f950-4361-9823-7c4792b143fd: 用户\u0026lt;admin\u0026gt;, 请求对象\u0026lt;/api/v1/namespaces/default/pods?limit=500\u0026gt;, 操作\u0026lt;list \u0026gt;, 请求阶段\u0026lt;ResponseComplete\u0026gt; 审计ID db2390c1-83cf-42e7-b589-70cd04003d0e: 用户\u0026lt;admin\u0026gt;, 请求对象\u0026lt;/api/v1/namespaces/default/pods?limit=500\u0026gt;, 操作\u0026lt;list \u0026gt;, 请求阶段\u0026lt;ResponseComplete\u0026gt; 审计ID a8fc2ff9-d0c5-4263-901c-b5974fd58026: 用户\u0026lt;admin\u0026gt;, 请求对象\u0026lt;/api/v1/namespaces/default/pods?limit=500\u0026gt;, 操作\u0026lt;list \u0026gt;, 请求阶段\u0026lt;ResponseComplete\u0026gt; 总结 kubernetes通过插件的方式提供了提供了一个IT系统 4A模型为集群提供了安全保障，与传统的4A (Authentication, Authorization, Accounting, Auditing) 不同的是，对于 Accounting 与 Authentication 在kubernetes中设计来说 Kubernetes没有用户的实现而是一个抽象，这使得Kubernetes可以更灵活使用任意的用户系统完成登录（OID, X.509, webhook, proxy, SA\u0026hellip;.），而对于授权来说，Kubernetes 通过多种授权模型(RBAC, ABAC, Node, Webhook)，为集群提供了灵活的权限；而不同的是，通过 Admission Control 可以为集群提供更多的安全策略，例如镜像策略，通过三方提供的控制器来自定义更多的安全策略，如OPA。而这种设计为Kubernetes集群提供了一种更灵活的安全。\nReference ​[1] Auditing\n​[2] Audit policy\n​[3] Audit backends\n​[4] Log backend\n​[5] Webhook backend\n​[6] Event batching\n​[7] Parameter tuning\n​[8] Privilege Management Infrastructure\n​[9] Kubernetes 审计（Auditing）功能详解\n​[10] kubernetes 审计日志功能\n","permalink":"https://www.161616.top/ch34-auditing/","summary":"本文是关于Kubernetes 4A解析的第4章 深入理解Kubernetes 4A - Authentication源码解析 深入理解Kubernetes 4A - Authorization源码解析 深入理解Kubernetes 4A - Admission Control源码解析 深入理解Kubernetes 4A - Audit源码解析 TLS Everywhere - 解密kubernetes集群的安全认证 所有关于Kubernetes 4A部分代码上传至仓库 github.com/cylonchau/hello-k8s-4A\nOverview 审计是信息系统中非常重要的一部分，Kubernetes 1.11中也增加了审计 (Auditing) 功能，通过审计功能获得 deployment, ns,等资源操作的事件。\nobjective：\n从设计角度了解Auditing在kubernets中是如何实现的 了解kubernetes auditing webhook 完成实验，通过webhook来收集审计日志 如有错别字或理解错误地方请多多担待，代码是以1.24进行整理，实验是以1.19环境进行，差别不大。\nKubernetes Auditing 根据Kubernetes官方描述审计在kubernetes中是有控制平面 kube-apiserver 中产生的一个事件，记录了集群中所操作的资源，审计围绕下列几个维度来记录事件的：\n发生了什么 发生的事件 谁触发的 发生动作的对象 在哪里检查到动作的 从哪触发的 处理行为是什么 审计生命周期开始于组件 kube-apiserver 准入控制阶段，在每个阶段内都会产生审计事件并经过预处理后写入后端，目前后端包含webhook与日志文件。\n审计日志功能增加了 kube-apiserver 的内存消耗，因为会为每个请求存储了审计所需的上下文。内存的消耗取决于审计日志配置 [1]。\n审计事件设计 审计的schema不同于资源API的设计，没有 metav1.ObjectMeta 属性，Event是一个事件的结构体，Policy是事件配置，属于kubernetes资源，在代码 k8s.io/apiserver/pkg/apis/audit/types.go 可以看到\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Event struct { metav1.","title":"深入理解Kubernetes 4A - Audit源码解析"},{"content":" 本文是关于Kubernetes 4A解析的第2章 深入理解Kubernetes 4A - Authentication源码解析 深入理解Kubernetes 4A - Authorization源码解析 深入理解Kubernetes 4A - Admission Control源码解析 深入理解Kubernetes 4A - Audit源码解析 TLS Everywhere - 解密kubernetes集群的安全认证 所有关于Kubernetes 4A部分代码上传至仓库 github.com/cylonchau/hello-k8s-4A Overview 在 Kubernetes 中，当一个访问请求通过了登录阶段（Authentication），必须还需要请求拥有该对象的访问权限，而授权部分也是Kubernetes API 访问控制中的第二个部分 Authorization .\nAuthorization 在 Kubernetes中是以评估发起请求的用户，根据其身份特性评估这次请求是被 ”拒绝“ 还是 “允许”，同访问控制三部曲中其他两个插件 (Authentication, Adminssion Control) 一样，Authorization 也可以同时配置多个，当收到用户的请求时，会依次检查这个阶段配置的所有模块，如果任何一个模块对该请求授予权限（拒绝或允许），那么该阶段会直接返回，当所有模块都没有该用户所属的权限时，默认是拒绝，在Kubernetes中，被该插件拒绝的用户显示为HTTP 403。\n如有错别字或理解错误地方请多多担待，代码是以1.24进行整理，实验是以1.19环境进行，差别不大\nobjective：\n了解kubernetes Authorization机制 了解授权系统的设计 完成实验，使用 OPA 作为 Kubernetes 外部用户，权限认证模型 RBAC 的替代品 Kubernetes是如何对用户授权的 kubernetes对用户授权需要遵守的shema必须拥有下列属性，代码位于pkg\\apis\\authorization\\types.go\ngo 1 2 3 4 5 6 7 8 9 type SubjectAccessReview struct { // API必须实现的部分 metav1.TypeMeta metav1.ObjectMeta // 请求需要遵守的属性 Spec SubjectAccessReviewSpec // 请求被授权的状态 Status SubjectAccessReviewStatus } 这里可以看到数据模型是\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type SubjectAccessReviewSpec struct { // ResourceAttributes describes information for a resource access request ResourceAttributes *ResourceAttributes // NonResourceAttributes describes information for a non-resource access request NonResourceAttributes *NonResourceAttributes // 请求的用户，必填 // 如果只传递 User，而没有Group，那么权限必须与用户对应，例如rolebinding/clusterrolebing // 如果传递了User与Group，那么rolebinding/clusterrolebing权限最大为Group，最小为User User string // Groups是用户所属组，可以有多个 Groups []string // Extra corresponds to the user.Info.GetExtra() method from the authenticator. Since that is input to the authorizer // 这里通常对于验证和授权阶段，没有特别的需求 Extra map[string]ExtraValue // UID 请求用户的UID，通常来说与User相同，Authentication中也是这么做的 UID string } 由此可得知，在授权部分，kubernetes要求请求必须存在\n用户类属性：user，group ，extra 由 Authentication 提供的用户信息 请求类属性： API资源： curl $API_SERVER_URL/api/v1/namespaces 请求路径： 非API资源格式的路径，/api，/healthz verb：HTTP请求方法，GET，POST.. 资源类属性： 访问的资源的名称或ID，如Pod名 要访问的名称空间 资源所属组，Kubernetes资源有GVR组成 那么，SubjectAccessReview.Spec 为要审查的对象，SubjectAccessReview.Status 为审查结果，通常在每个请求到来时，入库前必定被审查\nKubernetes中的授权模式 知道授权的对象，就需要知道如何对该对象进行授权，Kubernetes authorizer 提供了下列授权模式\npkg/kubeapiserver/authorizer/modes/modes.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const ( // ModeAlwaysAllow is the mode to set all requests as authorized ModeAlwaysAllow string = \u0026#34;AlwaysAllow\u0026#34; // ModeAlwaysDeny is the mode to set no requests as authorized ModeAlwaysDeny string = \u0026#34;AlwaysDeny\u0026#34; // ModeABAC is the mode to use Attribute Based Access Control to authorize ModeABAC string = \u0026#34;ABAC\u0026#34; // ModeWebhook is the mode to make an external webhook call to authorize ModeWebhook string = \u0026#34;Webhook\u0026#34; // ModeRBAC is the mode to use Role Based Access Control to authorize ModeRBAC string = \u0026#34;RBAC\u0026#34; // ModeNode is an authorization mode that authorizes API requests made by kubelets. ModeNode string = \u0026#34;Node\u0026#34; ) 可以看出，大致遵循模式进行授权\nModeABAC (Attribute-based access control)：是一种将属性分组，而后属性组分配给用户的模型，通常情况下这种模型很少使用 ModeRBAC (Role Based Access Control) ：是kubernetes主流的授权模型，是将用户分组，将属性分配给用户组的一种模型 ModeNode：对kubelet授权的方式 ModeWebhook：用户注入给Kubernetes 授权插件进行回调的一种授权模式 Kubernetes 授权生命周期 在启动 kube-apiserver 是都会初始化被注入一个 Authorizer 而这个被上面模式进行实现，如 RBACAuthorizer , WebhookAuthorizer k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go\ngo 1 2 3 type Authorizer interface { Authorize(ctx context.Context, a Attributes) (authorized Decision, reason string, err error) } 在 Run 中会创建一个CreateServerChain，这里面可以看到对应注册进来的 Authorizer k8s.io\\kubernetes\\cmd\\kube-apiserver\\app\\server.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // Run runs the specified APIServer. This should never exit. func Run(completeOptions completedServerRunOptions, stopCh \u0026lt;-chan struct{}) error { // To help debugging, immediately log version klog.Infof(\u0026#34;Version: %+v\u0026#34;, version.Get()) klog.InfoS(\u0026#34;Golang settings\u0026#34;, \u0026#34;GOGC\u0026#34;, os.Getenv(\u0026#34;GOGC\u0026#34;), \u0026#34;GOMAXPROCS\u0026#34;, os.Getenv(\u0026#34;GOMAXPROCS\u0026#34;), \u0026#34;GOTRACEBACK\u0026#34;, os.Getenv(\u0026#34;GOTRACEBACK\u0026#34;)) server, err := CreateServerChain(completeOptions) if err != nil { return err } prepared, err := server.PrepareRun() if err != nil { return err } return prepared.Run(stopCh) } 可以看到在创建这个 Authorizer 时会调用一个 BuildAuthorizer 构建这个 Authorizer k8s.io/kubernetes/cmd/kube-apiserver/app/server.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func buildGenericConfig( s *options.ServerRunOptions, proxyTransport *http.Transport, ) ( genericConfig *genericapiserver.Config, versionedInformers clientgoinformers.SharedInformerFactory, serviceResolver aggregatorapiserver.ServiceResolver, pluginInitializers []admission.PluginInitializer, admissionPostStartHook genericapiserver.PostStartHookFunc, storageFactory *serverstorage.DefaultStorageFactory, lastErr error, ) { ... genericConfig.Authorization.Authorizer, genericConfig.RuleResolver, err = BuildAuthorizer(s, genericConfig.EgressSelector, versionedInformers) if err != nil { lastErr = fmt.Errorf(\u0026#34;invalid authorization config: %v\u0026#34;, err) return } ... } 在代码 BuildAuthorizer 中构建了这个 Authorizer 其中可以看到 s 为 kube-apiserver 对于授权阶段的参数，例如参数，使用哪些模式 --authorization-mode，使用的webhook的配置 --authentication-token-webhook-config-file 等，通过传入的参数来决定这些\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // BuildAuthorizer constructs the authorizer func BuildAuthorizer(s *options.ServerRunOptions, EgressSelector *egressselector.EgressSelector, versionedInformers clientgoinformers.SharedInformerFactory) (authorizer.Authorizer, authorizer.RuleResolver, error) { // 这里构建出 authorizer.Config authorizationConfig := s.Authorization.ToAuthorizationConfig(versionedInformers) if EgressSelector != nil { egressDialer, err := EgressSelector.Lookup(egressselector.ControlPlane.AsNetworkContext()) if err != nil { return nil, nil, err } authorizationConfig.CustomDial = egressDialer } // 然后返回你开启的每一个webhook的模式的 authorizer return authorizationConfig.New() } 而对应这部分的数据结构如下所示 k8s.io/pkg/kubeapiserver/options/authorization.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 // BuiltInAuthorizationOptions contains all build-in authorization options for API Server type BuiltInAuthorizationOptions struct { Modes []string PolicyFile string WebhookConfigFile string WebhookVersion string WebhookCacheAuthorizedTTL time.Duration WebhookCacheUnauthorizedTTL time.Duration // WebhookRetryBackoff specifies the backoff parameters for the authorization webhook retry logic. // This allows us to configure the sleep time at each iteration and the maximum number of retries allowed // before we fail the webhook call in order to limit the fan out that ensues when the system is degraded. WebhookRetryBackoff *wait.Backoff } 例如在客户端部分，如果需要授权，都会使用该操作，可以在代码 k8s.io/pkg/registry/authorization/subjectaccessreview/rest.go 中可以看到REST中会 authorizer.Authorize 去验证是否有权限操作\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { subjectAccessReview, ok := obj.(*authorizationapi.SubjectAccessReview) if !ok { return nil, apierrors.NewBadRequest(fmt.Sprintf(\u0026#34;not a SubjectAccessReview: %#v\u0026#34;, obj)) } if errs := authorizationvalidation.ValidateSubjectAccessReview(subjectAccessReview); len(errs) \u0026gt; 0 { return nil, apierrors.NewInvalid(authorizationapi.Kind(subjectAccessReview.Kind), \u0026#34;\u0026#34;, errs) } if createValidation != nil { if err := createValidation(ctx, obj.DeepCopyObject()); err != nil { return nil, err } } authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(subjectAccessReview.Spec) decision, reason, evaluationErr := r.authorizer.Authorize(ctx, authorizationAttributes) subjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{ Allowed: (decision == authorizer.DecisionAllow), Denied: (decision == authorizer.DecisionDeny), Reason: reason, } if evaluationErr != nil { subjectAccessReview.Status.EvaluationError = evaluationErr.Error() } return subjectAccessReview, nil } authorizer.Authorize 会被实现在每一个该阶段的模式下，在 withAuthentication 构建了一个授权的 http.Handler 函数\nk8s.io/apiserver/pkg/endpoints/filters/authorization.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func WithAuthorization(handler http.Handler, a authorizer.Authorizer, s runtime.NegotiatedSerializer) http.Handler { if a == nil { klog.Warning(\u0026#34;Authorization is disabled\u0026#34;) return handler } return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx := req.Context() attributes, err := GetAuthorizerAttributes(ctx) if err != nil { responsewriters.InternalError(w, req, err) return } // 这里调用了authorizer.Authorizer传入的authorizer来进行鉴权 authorized, reason, err := a.Authorize(ctx, attributes) // an authorizer like RBAC could encounter evaluation errors and still allow the request, so authorizer decision is checked before error here. if authorized == authorizer.DecisionAllow { audit.AddAuditAnnotations(ctx, decisionAnnotationKey, decisionAllow, reasonAnnotationKey, reason) handler.ServeHTTP(w, req) return } if err != nil { audit.AddAuditAnnotation(ctx, reasonAnnotationKey, reasonError) responsewriters.InternalError(w, req, err) return } klog.V(4).InfoS(\u0026#34;Forbidden\u0026#34;, \u0026#34;URI\u0026#34;, req.RequestURI, \u0026#34;Reason\u0026#34;, reason) audit.AddAuditAnnotations(ctx, decisionAnnotationKey, decisionForbid, reasonAnnotationKey, reason) responsewriters.Forbidden(ctx, attributes, w, req, reason, s) }) } 接下来在 createAggregatorConfig 调用了 BuildHandlerChainWithStorageVersionPrecondition 而又调用了\ncmd/kube-apiserver/app/aggregator.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func createAggregatorConfig( kubeAPIServerConfig genericapiserver.Config, commandOptions *options.ServerRunOptions, externalInformers kubeexternalinformers.SharedInformerFactory, serviceResolver aggregatorapiserver.ServiceResolver, proxyTransport *http.Transport, pluginInitializers []admission.PluginInitializer, ) (*aggregatorapiserver.Config, error) { // make a shallow copy to let us twiddle a few things // most of the config actually remains the same. We only need to mess with a couple items related to the particulars of the aggregator genericConfig := kubeAPIServerConfig genericConfig.PostStartHooks = map[string]genericapiserver.PostStartHookConfigEntry{} genericConfig.RESTOptionsGetter = nil // prevent generic API server from installing the OpenAPI handler. Aggregator server // has its own customized OpenAPI handler. genericConfig.SkipOpenAPIInstallation = true if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StorageVersionAPI) \u0026amp;\u0026amp; utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerIdentity) { // Add StorageVersionPrecondition handler to aggregator-apiserver. // The handler will block write requests to built-in resources until the // target resources\u0026#39; storage versions are up-to-date. genericConfig.BuildHandlerChainFunc = genericapiserver.BuildHandlerChainWithStorageVersionPrecondition } 而 k8s.io/apiserver/pkg/server/config.go 返回这个函数 BuildHandlerChainWithStorageVersionPrecondition\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 handlerChainBuilder := func(handler http.Handler) http.Handler { return c.BuildHandlerChainFunc(handler, c.Config) } apiServerHandler := NewAPIServerHandler(name, c.Serializer, handlerChainBuilder, delegationTarget.UnprotectedHandler()) s := \u0026amp;GenericAPIServer{ discoveryAddresses: c.DiscoveryAddresses, LoopbackClientConfig: c.LoopbackClientConfig, legacyAPIGroupPrefixes: c.LegacyAPIGroupPrefixes, admissionControl: c.AdmissionControl, Serializer: c.Serializer, AuditBackend: c.AuditBackend, Authorizer: c.Authorization.Authorizer, delegationTarget: delegationTarget, EquivalentResourceRegistry: c.EquivalentResourceRegistry, HandlerChainWaitGroup: c.HandlerChainWaitGroup, Handler: apiServerHandler, listedPathProvider: apiServerHandler, 只要知道哪里调用了 handlerChainBuilder 就知道了鉴权步骤在哪里了，可以看到 handlerChainBuilder 被传入了 apiServerHandler，而后被作为参数返回给 listedPathProvider: \u0026amp;GenericAPIServer{}\nlistedPathProvider在 k8s.io/apiserver/pkg/server/genericapiserver.go\ngo 1 2 3 func (s *GenericAPIServer) ListedPaths() []string { return s.listedPathProvider.ListedPaths() } ListedPaths() 又在代码 k8s.io/apiserver/pkg/server/routes/index.go 中被 构建成这个http服务\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // ListedPaths returns the paths that should be shown under / func (a *APIServerHandler) ListedPaths() []string { var handledPaths []string // Extract the paths handled using restful.WebService for _, ws := range a.GoRestfulContainer.RegisteredWebServices() { handledPaths = append(handledPaths, ws.RootPath()) } handledPaths = append(handledPaths, a.NonGoRestfulMux.ListedPaths()...) sort.Strings(handledPaths) return handledPaths } // ServeHTTP serves the available paths. func (i IndexLister) ServeHTTP(w http.ResponseWriter, r *http.Request) { responsewriters.WriteRawJSON(i.StatusCode, metav1.RootPaths{Paths: i.PathProvider.ListedPaths()}, w) } 至此，可以知道，每次请求时，我们在配置 kube-apiserver 配置的授权插件 .authorizer.Authorize ，而这个参数会被带至 subjectAccessReview 向下传递，其中 User,Group,Extra,UID 为 authentication 部分提供\nAuthorization webhook Authorization webhook 位于 k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go，是通过 kube-apiserver 注入进来的配置，就是上面讲到的如果提供了配置就会加入这种类型的 Authorization 插件来认证。当配置此类型的授权插件，Authorize 会被调用，通过向注入的 URL 发起 REST 请求进行授权，请求对象是 v1beta1.SubjectAccessReview\n下面是请求的实例\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { \u0026#34;apiVersion\u0026#34;: \u0026#34;authorization.k8s.io/v1beta1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;SubjectAccessReview\u0026#34;, \u0026#34;spec\u0026#34;: { \u0026#34;resourceAttributes\u0026#34;: { \u0026#34;namespace\u0026#34;: \u0026#34;kittensandponies\u0026#34;, \u0026#34;verb\u0026#34;: \u0026#34;GET\u0026#34;, \u0026#34;group\u0026#34;: \u0026#34;group3\u0026#34;, \u0026#34;resource\u0026#34;: \u0026#34;pods\u0026#34; }, \u0026#34;user\u0026#34;: \u0026#34;jane\u0026#34;, \u0026#34;group\u0026#34;: [ \u0026#34;group1\u0026#34;, \u0026#34;group2\u0026#34; ] } } webhook 返回的格式\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 如果允许这个用户访问则返回这个格式 { \u0026#34;apiVersion\u0026#34;: \u0026#34;authorization.k8s.io/v1beta1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;SubjectAccessReview\u0026#34;, \u0026#34;status\u0026#34;: { \u0026#34;allowed\u0026#34;: true } } // 如果拒绝这个用户访问则返回这个格式 { \u0026#34;apiVersion\u0026#34;: \u0026#34;authorization.k8s.io/v1beta1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;SubjectAccessReview\u0026#34;, \u0026#34;status\u0026#34;: { \u0026#34;allowed\u0026#34;: false, \u0026#34;reason\u0026#34;: \u0026#34;user does not have read access to the namespace\u0026#34; } } 对于webhook来讲，只要接受请求保持上面格式，而返回格式为下属格式，就可以很好的将Kubernetes 权限体系接入到三方系统中，例如 open policy agent。\n同样 webhook 也提供了 Authorize 函数，如同上面一样会被注入到每个handler中被执行\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 func (w *WebhookAuthorizer) Authorize(ctx context.Context, attr authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { r := \u0026amp;authorizationv1.SubjectAccessReview{} if user := attr.GetUser(); user != nil { r.Spec = authorizationv1.SubjectAccessReviewSpec{ User: user.GetName(), UID: user.GetUID(), Groups: user.GetGroups(), Extra: convertToSARExtra(user.GetExtra()), } } if attr.IsResourceRequest() { r.Spec.ResourceAttributes = \u0026amp;authorizationv1.ResourceAttributes{ Namespace: attr.GetNamespace(), Verb: attr.GetVerb(), Group: attr.GetAPIGroup(), Version: attr.GetAPIVersion(), Resource: attr.GetResource(), Subresource: attr.GetSubresource(), Name: attr.GetName(), } } else { r.Spec.NonResourceAttributes = \u0026amp;authorizationv1.NonResourceAttributes{ Path: attr.GetPath(), Verb: attr.GetVerb(), } } key, err := json.Marshal(r.Spec) if err != nil { return w.decisionOnError, \u0026#34;\u0026#34;, err } if entry, ok := w.responseCache.Get(string(key)); ok { r.Status = entry.(authorizationv1.SubjectAccessReviewStatus) } else { var result *authorizationv1.SubjectAccessReview // WithExponentialBackoff will return SAR create error (sarErr) if any. if err := webhook.WithExponentialBackoff(ctx, w.retryBackoff, func() error { var sarErr error var statusCode int start := time.Now() result, statusCode, sarErr = w.subjectAccessReview.Create(ctx, r, metav1.CreateOptions{}) latency := time.Since(start) if statusCode != 0 { w.metrics.RecordRequestTotal(ctx, strconv.Itoa(statusCode)) w.metrics.RecordRequestLatency(ctx, strconv.Itoa(statusCode), latency.Seconds()) return sarErr } if sarErr != nil { w.metrics.RecordRequestTotal(ctx, \u0026#34;\u0026lt;error\u0026gt;\u0026#34;) w.metrics.RecordRequestLatency(ctx, \u0026#34;\u0026lt;error\u0026gt;\u0026#34;, latency.Seconds()) } return sarErr }, webhook.DefaultShouldRetry); err != nil { klog.Errorf(\u0026#34;Failed to make webhook authorizer request: %v\u0026#34;, err) return w.decisionOnError, \u0026#34;\u0026#34;, err } r.Status = result.Status if shouldCache(attr) { if r.Status.Allowed { w.responseCache.Add(string(key), r.Status, w.authorizedTTL) } else { w.responseCache.Add(string(key), r.Status, w.unauthorizedTTL) } } } switch { case r.Status.Denied \u0026amp;\u0026amp; r.Status.Allowed: return authorizer.DecisionDeny, r.Status.Reason, fmt.Errorf(\u0026#34;webhook subject access review returned both allow and deny response\u0026#34;) case r.Status.Denied: return authorizer.DecisionDeny, r.Status.Reason, nil case r.Status.Allowed: return authorizer.DecisionAllow, r.Status.Reason, nil default: return authorizer.DecisionNoOpinion, r.Status.Reason, nil } } 执行 webhook.Authorize() 会执行 w.subjectAccessReview.Create() 在这里可以看到会发起一个POST请求将 v1beta1.SubjectAccessReview 传入给webhook\nk8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go.Create\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 func (t *subjectAccessReviewV1Client) Create(ctx context.Context, subjectAccessReview *authorizationv1.SubjectAccessReview, opts metav1.CreateOptions) (result *authorizationv1.SubjectAccessReview, statusCode int, err error) { result = \u0026amp;authorizationv1.SubjectAccessReview{} restResult := t.client.Post(). Resource(\u0026#34;subjectaccessreviews\u0026#34;). VersionedParams(\u0026amp;opts, scheme.ParameterCodec). Body(subjectAccessReview). Do(ctx) restResult.StatusCode(\u0026amp;statusCode) err = restResult.Into(result) return } 实验：基于OPA的RBAC模型 通过上面阐述，大致了解到kubernetes认证框架中的用户的分类以及认证的策略由哪些，实验的目的也是为了阐述一个结果，就是使用OIDC/webhook 是比其他方式更好的保护，管理kubernetes集群。首先在安全上，假设网络环境是不安全的，那么任意node节点遗漏 bootstrap token文件，就意味着拥有了集群中最高权限；其次在管理上，越大的团队，人数越多，不可能每个用户都提供单独的证书或者token，要知道传统教程中讲到token在kubernetes集群中是永久有效的，除非你删除了这个secret/sa；而Kubernetes提供的插件就很好的解决了这些问题。\n实验环境 一个kubernetes集群 了解OPA相关技术 实验大致分为以下几个步骤：\n建立一个HTTP服务器用于返回给kubernetes Authorization服务 查询用户操作是否有权限 实验开始 编写webhook Authorization 这里做的就是接收 subjectAccessReview ，将授权结果赋予 subjectAccessReview.Status.Allowed ，true/false，然后返回 subjectAccessReview 即可\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func serveAuthorization(w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil { httpError(w, err) return } klog.V(4).Info(\u0026#34;Receied: \u0026#34;, string(b)) var subjectAccessReview authoV1.SubjectAccessReview err = json.Unmarshal(b, \u0026amp;subjectAccessReview) if err != nil { klog.V(3).Info(\u0026#34;Json convert err: \u0026#34;, err) httpError(w, err) return } subjectAccessReview.Status.Allowed = rbac.RBACChek(\u0026amp;subjectAccessReview) b, err = json.Marshal(subjectAccessReview) if err != nil { klog.V(3).Info(\u0026#34;Json convert err: \u0026#34;, err) httpError(w, err) return } w.Write(b) klog.V(3).Info(\u0026#34;Returning: \u0026#34;, string(b)) } 编写rego 这里简单配置了两个权限，admin 组拥有所有操作权限，不包含 watch ，而 conf 组只能 list，在访问控制三部曲中，已授权的会增加一个组，例如 system:authenticated 代表被 Authentication 授予通过的用户，所以 Groups 为一个数组格式，这里检查为两个数组的交集 \u0026gt; 1，则肯定代表这个用户拥有该组的权限。\n实验中 rego 部分可以在 playground 中测试\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var module = `package k8s import future.keywords.in default allow = false admin_verbs := {\u0026#34;create\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;delete\u0026#34;, \u0026#34;update\u0026#34;} admin_groups := {\u0026#34;admin\u0026#34;} conf_groups := {\u0026#34;conf\u0026#34;} conf_verbs := {\u0026#34;list\u0026#34;} allow { groups := {v | v := input.spec.groups[_]} count(admin_groups \u0026amp; groups) \u0026gt; 0 input.spec.resourceAttributes.verb in admin_verbs } allow { groups := {v | v := input.spec.groups[_]} count(conf_groups \u0026amp; groups) \u0026gt; 0 input.spec.resourceAttributes.verb in conf_verbs } ` 下面编写 RBACChek 函数，由于go1.16提供了embed功能，就可以直接将 rego embed go中，最后result.Allowed() 如果 input 通过评估则为 true ，反之亦然\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func RBACChek(req *authoV1.SubjectAccessReview) bool { fmt.Printf(\u0026#34;\\n%+v\\n\u0026#34;, req) query, err := rego.New( // query是要检查的模块，data是固定格式，这与playground中不一样，需要.allow // k8s是package rego.Query(\u0026#34;data.k8s.allow\u0026#34;), rego.Module(\u0026#34;k8s.allow\u0026#34;, module), ).PrepareForEval(context.TODO()) if err != nil { klog.V(4).Info(err) return false } result, err := query.Eval(context.TODO(), rego.EvalInput(req)) if err != nil { klog.V(4).Info(\u0026#34;evaluation error:\u0026#34;, err) return false } else if len(result) == 0 { klog.V(4).Info(\u0026#34;undefined result\u0026#34;, err) return false } return result.Allowed() } 配置kube-apiserver Authorization webhook 与其他 webhook 一样，启用的方法也是修改 kube-apiserver 参数，并指定 kubeconfig 类型的配置文件，其中对于 Kubernetes 集群来说 kubeconfig 是 kubernetes 客户端访问的信息，而 webhook 这里的 kubeconfig 配置文件要填写的则是 webhook的信息，其中 user,cluster,contexts 属性均为 webhook的配置信息 [1]。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: v1 kind: Config clusters: - cluster: server: http://10.0.0.1:88/authorization insecure-skip-tls-verify: true name: authorizator users: - name: webhook-authorizator current-context: webhook-authorizator@authorizator contexts: - context: cluster: authorizator user: webhook-authorizator name: webhook-authorizator@authorizator 修改 kube-apiserver 参数\nyaml 1 2 3 4 5 6 7 --authorization-webhook-config-file=/etc/kubernetes/auth/authorization-webhook.conf \\ # 1s 是为了在测试时减少等待的时间，否则缓存太长不会走webhook --authorization-webhook-cache-authorized-ttl=1s \\ --authorization-webhook-cache-unauthorized-ttl=1s \\ # api版本建议还是指定下，因为v1与v1beta1的 subjectAccessReview 内容不同rego因为格式问题会为空从而false # 代码中schema v1与v1beta1相同，测试时收到的请求的格式不一样，没找到原因 TODO --authorization-webhook-version=v1 \\ 验证结果 准备三个外部用户，admin,admin1,searchUser，admin,admin1 为 admin 组，拥有所有权限，searchUser 为 conf 组，仅能 list 操作\nyaml 1 2 3 4 5 6 7 8 9 - name: admin user: token: admin@111 - name: admin1 user: token: admin1@111 - name: searchUser user: token: searchUser@111 测试用户 searchUser ，可以看到只能list操作\nbash 1 2 3 4 5 6 7 8 $ kubectl get pod --user=searchUser NAME READY STATUS RESTARTS AGE netbox-85865d5556-hfg6v 1/1 Running 0 96d netbox-85865d5556-vlgr4 1/1 Running 0 96d pod 0/1 CrashLoopBackOff 95 22h $ kubectl delete pod pod --user=searchUser Error from server (Forbidden): pods \u0026#34;pod\u0026#34; is forbidden: User \u0026#34;searchUser\u0026#34; cannot delete resource \u0026#34;pods\u0026#34; in API group \u0026#34;\u0026#34; in the namespace \u0026#34;default\u0026#34; 测试用户 admin，可以看出可以进行写与查看的操作\nbash 1 2 3 4 5 6 7 $ kubectl delete pod pod --user=admin pod \u0026#34;pod\u0026#34; deleted $ kubectl get pod --user=admin NAME READY STATUS RESTARTS AGE netbox-85865d5556-hfg6v 1/1 Running 0 96d netbox-85865d5556-vlgr4 1/1 Running 0 96d 总结 kubernetes 提供了 Authentication,Authorization,Adminsion Control,Audit 几种webhook，可以自行在Kubernetes之上实现一个4A的标准，Authorization部分提供了一个并行与，但脱离Kubernetes的授权系统，使得外部用户可以很灵活的被授权，而不是手动管理多个clusterrolebinding,rolebingding 之类的资源。\n实验中使用了OPA，这里是将rego静态文件embed入go中，在正常情况下OPA给出的架构如下图所示，存在一个 OPA Service，来进行验证，而实验中是直接嵌入到go中，OPA本身也提供了 HTTP Service，可以直接编译运行为 HTTP服务 [2]。 TODO\n图：OPA 架构 Source：https://www.openpolicyagent.org/docs/latest/\nOPA本身提供了 Gatekeeper ，可以作为Kubernetes 资源使用，官方示例是作为为一个kubernetes准入网关，也提供了ingress浏览的验证 [3]\nNotes：实验中还需要注意的一点则是，如果RBAC与webhook同时验证时，需要合理的规划权限，例如集群组件的账户，coreDNS，flannel等，也会被拒绝（在OPA设置的 default allow = false ）。\nReference ​[1] Webhook Mode\n​[2] HTTP APIs\n​[3] What is OPA Gatekeeper?\n​[4] 用 Goalng 开发 OPA 策略\n​[5] 初探 Open Policy Agent 實作 RBAC (Role-based access control) 權限控管\n","permalink":"https://www.161616.top/ch32-authorization/","summary":"本文是关于Kubernetes 4A解析的第2章 深入理解Kubernetes 4A - Authentication源码解析 深入理解Kubernetes 4A - Authorization源码解析 深入理解Kubernetes 4A - Admission Control源码解析 深入理解Kubernetes 4A - Audit源码解析 TLS Everywhere - 解密kubernetes集群的安全认证 所有关于Kubernetes 4A部分代码上传至仓库 github.com/cylonchau/hello-k8s-4A Overview 在 Kubernetes 中，当一个访问请求通过了登录阶段（Authentication），必须还需要请求拥有该对象的访问权限，而授权部分也是Kubernetes API 访问控制中的第二个部分 Authorization .\nAuthorization 在 Kubernetes中是以评估发起请求的用户，根据其身份特性评估这次请求是被 ”拒绝“ 还是 “允许”，同访问控制三部曲中其他两个插件 (Authentication, Adminssion Control) 一样，Authorization 也可以同时配置多个，当收到用户的请求时，会依次检查这个阶段配置的所有模块，如果任何一个模块对该请求授予权限（拒绝或允许），那么该阶段会直接返回，当所有模块都没有该用户所属的权限时，默认是拒绝，在Kubernetes中，被该插件拒绝的用户显示为HTTP 403。\n如有错别字或理解错误地方请多多担待，代码是以1.24进行整理，实验是以1.19环境进行，差别不大\nobjective：\n了解kubernetes Authorization机制 了解授权系统的设计 完成实验，使用 OPA 作为 Kubernetes 外部用户，权限认证模型 RBAC 的替代品 Kubernetes是如何对用户授权的 kubernetes对用户授权需要遵守的shema必须拥有下列属性，代码位于pkg\\apis\\authorization\\types.go\ngo 1 2 3 4 5 6 7 8 9 type SubjectAccessReview struct { // API必须实现的部分 metav1.","title":"深入理解Kubernetes 4A - Authorization源码解析"},{"content":" 本文是关于Kubernetes 4A解析的第1章 深入理解Kubernetes 4A - Authentication源码解析 深入理解Kubernetes 4A - Authorization源码解析 深入理解Kubernetes 4A - Admission Control源码解析 深入理解Kubernetes 4A - Audit源码解析 TLS Everywhere - 解密kubernetes集群的安全认证 所有关于Kubernetes 4A部分代码上传至仓库 github.com/cylonchau/hello-k8s-4A\nOverview 本章主要简单阐述kubernetes 认证相关原理，最后以实验来阐述kubernetes用户系统的思路\nobjective：\n了解kubernetes 各种认证机制的原理 了解kubernetes 用户的概念 了解kubernetes authentication webhook 完成实验，如何将其他用户系统接入到kubernetes中的一个思路 如有错别字或理解错误地方请多多担待，代码是以1.24进行整理，实验是以1.19环境进行，差别不大。\nKubernetes 认证 在Kubernetes apiserver对于认证部分所描述的，对于所有用户访问Kubernetes API（通过任何客户端，客户端库，kubectl 等）时都会经历 验证 (Authentication) , 授权 (Authorization), 和准入控制 (Admission control) 三个阶段来完成对 “用户” 进行授权，整个流程正如下图所示\n图：Kubernetes API 请求的请求处理步骤图 Source：https://www.armosec.io/blog/kubernetes-admission-controller/\n其中在大多数教程中，在对这三个阶段所做的工作大致上为：\nAuthentication 阶段所指用于确认请求访问Kubernetes API 用户是否为合法用户，拒绝为401\nAuthorization 阶段所指的将是这个用户是否有对操作的资源的权限，拒绝为403\nAdmission control 阶段所指控制对请求资源进行控制，通俗来说，就是一票否决权，即使前两个步骤完成\n到这里了解到了Kubernetes API实际上做的工作就是 “人类用户” 与 “kubernetes service account\u0026quot; [2]；那么就引出了一个重要概念就是 “用户” 在Kubernetes中是什么，以及用户在认证中的也是本章节的中心。\n在Kubernetes官方手册中给出了 ”用户“ 的概念，Kubernetes集群中存在的用户包括 ”普通用户“ 与 “service account” 但是 Kubernetes 没有普通用户的管理方式，只是将使用集群的证书CA签署的有效证书的用户都被视为合法用户 [3]\n那么对于使得Kubernetes集群有一个真正的用户系统，就可以根据上面给出的概念将Kubernetes用户分为 ”外部用户“ 与 ”内部用户“。如何理解外部与内部用户呢？实际上就是有Kubernetes管理的用户，即在kubernetes定义用户的数据模型这种为 “内部用户” ，正如 service account；反之，非Kubernetes托管的用户则为 ”外部用户“ 这中概念也更好的对kubernetes用户的阐述。\n对于外部用户来说，实际上Kubernetes给出了多种用户概念 [3]，例如：\n拥有kubernetes集群证书的用户 拥有Kubernetes集群token的用户（--token-auth-file 指定的静态token） 用户来自外部用户系统，例如 OpenID，LDAP，QQ connect, google identity platform 等 向外部用户授权集群访问的示例 场景1：通过证书请求k8s 该场景中kubernetes将使用证书中的cn作为用户，ou作为组，如果对应 rolebinding/clusterrolebinding 给予该用户权限，那么请求为合法\nbash 1 2 3 4 $ curl https://hostname:6443/api/v1/pods \\ --cert ./client.pem \\ --key ./client-key.pem \\ --cacert ./ca.pem 接下来浅析下在代码中做的事情\n确认用户是 apiserver 在 Authentication 阶段 做的事情，而对应代码在 pkg/kubeapiserver/authenticator 下，整个文件就是构建了一系列的认证器，而x.509证书指是其中一个\ngo 1 2 3 4 5 6 7 8 9 10 11 12 // 创建一个认证器，返回请求或一个k8s认证机制的标准错误 func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, error) { ... // X509 methods // 可以看到这里就是将x509证书解析为user if config.ClientCAContentProvider != nil { certAuth := x509.NewDynamic(config.ClientCAContentProvider.VerifyOptions, x509.CommonNameUserConversion) authenticators = append(authenticators, certAuth) } ... 接下来看实现原理，NewDynamic函数位于代码 k8s.io/apiserver/pkg/authentication/request/x509/x509.go\n通过代码可以看出，是通过一个验证函数与用户来解析为一个 Authenticator\ngo 1 2 3 4 5 // NewDynamic returns a request.Authenticator that verifies client certificates using the provided // VerifyOptionFunc (which may be dynamic), and converts valid certificate chains into user.Info using the provided UserConversion func NewDynamic(verifyOptionsFn VerifyOptionFunc, user UserConversion) *Authenticator { return \u0026amp;Authenticator{verifyOptionsFn, user} } 验证函数为 CAContentProvider 的方法，而x509部分实现为 k8s.io/apiserver/pkg/server/dynamiccertificates/dynamic_cafile_content.go.VerifyOptions；可以看出返回是一个 x509.VerifyOptions + 与认证的状态\ngo 1 2 3 4 5 6 7 8 9 // VerifyOptions provides verifyoptions compatible with authenticators func (c *DynamicFileCAContent) VerifyOptions() (x509.VerifyOptions, bool) { uncastObj := c.caBundle.Load() if uncastObj == nil { return x509.VerifyOptions{}, false } return uncastObj.(*caBundleAndVerifier).verifyOptions, true } 而用户的获取则位于 k8s.io/apiserver/pkg/authentication/request/x509/x509.go；可以看出，用户正是拿的证书的CN，而组则是为证书的OU\ngo 1 2 3 4 5 6 7 8 9 10 11 12 // CommonNameUserConversion builds user info from a certificate chain using the subject\u0026#39;s CommonName var CommonNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (*authenticator.Response, bool, error) { if len(chain[0].Subject.CommonName) == 0 { return nil, false, nil } return \u0026amp;authenticator.Response{ User: \u0026amp;user.DefaultInfo{ Name: chain[0].Subject.CommonName, Groups: chain[0].Subject.Organization, }, }, true, nil }) 由于授权不在本章范围内，直接忽略至入库阶段，入库阶段由 RESTStorageProvider 实现 这里，每一个Provider都提供了 Authenticator 这里包含了已经允许的请求，将会被对应的REST客户端写入到库中\ngo 1 2 3 4 5 6 7 8 9 type RESTStorageProvider struct { Authenticator authenticator.Request APIAudiences authenticator.Audiences } // RESTStorageProvider is a factory type for REST storage. type RESTStorageProvider interface { GroupName() string NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, error) } 场景2：通过token 该场景中，当 kube-apiserver 开启了 --enable-bootstrap-token-auth 时，就可以使用 Bootstrap Token 进行认证，通常如下列命令，在请求头中增加 Authorization: Bearer \u0026lt;token\u0026gt; 标识\nbash 1 2 3 $ curl https://hostname:6443/api/v1/pods \\ --cacert ${CACERT} \\ --header \u0026#34;Authorization: Bearer \u0026lt;token\u0026gt;\u0026#34; \\ 接下来浅析下在代码中做的事情\n可以看到，在代码 pkg/kubeapiserver/authenticator.New() 中当 kube-apiserver 指定了参数 --token-auth-file=/etc/kubernetes/token.csv\u0026quot; 这种认证会被激活\ngo 1 2 3 4 5 6 7 if len(config.TokenAuthFile) \u0026gt; 0 { tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile) if err != nil { return nil, nil, err } tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth)) } 此时打开 token.csv 查看下token长什么样\nbash 1 2 $ cat /etc/kubernetes/token.csv 12ba4f.d82a57a4433b2359,\u0026#34;system:bootstrapper\u0026#34;,10001,\u0026#34;system:bootstrappers\u0026#34; 这里回到代码 k8s.io/apiserver/pkg/authentication/token/tokenfile/tokenfile.go.NewCSV ，这里可以看出，就是读取 --token-auth-file= 参数指定的tokenfile，然后解析为用户，record[1] 作为用户名，record[2] 作为UID\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 // NewCSV returns a TokenAuthenticator, populated from a CSV file. // The CSV file must contain records in the format \u0026#34;token,username,useruid\u0026#34; func NewCSV(path string) (*TokenAuthenticator, error) { file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() recordNum := 0 tokens := make(map[string]*user.DefaultInfo) reader := csv.NewReader(file) reader.FieldsPerRecord = -1 for { record, err := reader.Read() if err == io.EOF { break } if err != nil { return nil, err } if len(record) \u0026lt; 3 { return nil, fmt.Errorf(\u0026#34;token file \u0026#39;%s\u0026#39; must have at least 3 columns (token, user name, user uid), found %d\u0026#34;, path, len(record)) } recordNum++ if record[0] == \u0026#34;\u0026#34; { klog.Warningf(\u0026#34;empty token has been found in token file \u0026#39;%s\u0026#39;, record number \u0026#39;%d\u0026#39;\u0026#34;, path, recordNum) continue } obj := \u0026amp;user.DefaultInfo{ Name: record[1], UID: record[2], } if _, exist := tokens[record[0]]; exist { klog.Warningf(\u0026#34;duplicate token has been found in token file \u0026#39;%s\u0026#39;, record number \u0026#39;%d\u0026#39;\u0026#34;, path, recordNum) } tokens[record[0]] = obj if len(record) \u0026gt;= 4 { obj.Groups = strings.Split(record[3], \u0026#34;,\u0026#34;) } } return \u0026amp;TokenAuthenticator{ tokens: tokens, }, nil } 而token file中配置的格式正是以逗号分隔的一组字符串，\ngo 1 2 3 4 5 6 type DefaultInfo struct { Name string UID string Groups []string Extra map[string][]string } 这种用户最常见的方式就是 kubelet 通常会以此类用户向控制平面进行身份认证，例如下列配置\nbash 1 2 3 4 5 6 7 KUBELET_ARGS=\u0026#34;--v=0 \\ --logtostderr=true \\ --config=/etc/kubernetes/kubelet-config.yaml \\ --kubeconfig=/etc/kubernetes/auth/kubelet.conf \\ --network-plugin=cni \\ --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1 \\ --bootstrap-kubeconfig=/etc/kubernetes/auth/bootstrap.conf\u0026#34; /etc/kubernetes/auth/bootstrap.conf 内容，这里就用到了 kube-apiserver 配置的 --token-auth-file= 用户名，组必须为 system:bootstrappers\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: v1 clusters: - cluster: certificate-authority-data: ...... server: https://10.0.0.4:6443 name: kubernetes contexts: - context: cluster: kubernetes user: system:bootstrapper name: system:bootstrapper@kubernetes current-context: system:bootstrapper@kubernetes kind: Config preferences: {} users: - name: system:bootstrapper 而通常在二进制部署时会出现的问题，例如下列错误\ntext 1 Unable to register node \u0026#34;hostname\u0026#34; with API server: nodes is forbidden: User \u0026#34;system:anonymous\u0026#34; cannot create resource \u0026#34;nodes\u0026#34; in API group \u0026#34;\u0026#34; at the cluster scope 而通常解决方法是执行下列命令，这里就是将 kubelet 与 kube-apiserver 通讯时的用户授权，因为kubernetes官方给出的条件是，用户组必须为 system:bootstrappers [4]\nbash 1 $ kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers 生成的clusterrolebinding 如下\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: creationTimestamp: \u0026#34;2022-08-14T22:26:51Z\u0026#34; managedFields: - apiVersion: rbac.authorization.k8s.io/v1 fieldsType: FieldsV1 ... time: \u0026#34;2022-08-14T22:26:51Z\u0026#34; name: kubelet-bootstrap resourceVersion: \u0026#34;158\u0026#34; selfLink: /apis/rbac.authorization.k8s.io/v1/clusterrolebindings/kubelet-bootstrap uid: b4d70f4f-4ae0-468f-86b7-55e9351e4719 roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:node-bootstrapper subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: system:bootstrappers 上述就是 bootstrap token，翻译后就是引导token，因为其做的工作就是将节点载入Kubernetes系统过程提供认证机制的用户。\nNotes：这种用户不存在与kubernetes内，可以算属于一个外部用户，但认证机制中存在并绑定了最高权限，也可以用来做其他访问时的认证\n场景3：serviceaccount serviceaccount通常为API自动创建的，但在用户中，实际上认证存在两个方向，一个是 --service-account-key-file 这个参数可以指定多个，指定对应的证书文件公钥或私钥，用以办法sa的token\n首先会根据指定的公钥或私钥文件生成token\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 if len(config.ServiceAccountKeyFiles) \u0026gt; 0 { serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.APIAudiences, config.ServiceAccountTokenGetter) if err != nil { return nil, nil, err } tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth) } if len(config.ServiceAccountIssuers) \u0026gt; 0 { serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuers, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter) if err != nil { return nil, nil, err } tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth) } 对于 --service-account-key-file 他生成的用户都是 “kubernetes/serviceaccount” , 而对于 --service-account-issuer 只是对sa颁发者提供了一个称号标识是谁，而不是统一的 “kubernetes/serviceaccount” ，这里可以从代码中看到，两者是完全相同的，只是称号不同罢了\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // newLegacyServiceAccountAuthenticator returns an authenticator.Token or an error func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) { allPublicKeys := []interface{}{} for _, keyfile := range keyfiles { publicKeys, err := keyutil.PublicKeysFromFile(keyfile) if err != nil { return nil, err } allPublicKeys = append(allPublicKeys, publicKeys...) } // 唯一的区别 这里使用了常量 serviceaccount.LegacyIssuer tokenAuthenticator := serviceaccount.JWTTokenAuthenticator([]string{serviceaccount.LegacyIssuer}, allPublicKeys, apiAudiences, serviceaccount.NewLegacyValidator(lookup, serviceAccountGetter)) return tokenAuthenticator, nil } // newServiceAccountAuthenticator returns an authenticator.Token or an error func newServiceAccountAuthenticator(issuers []string, keyfiles []string, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) { allPublicKeys := []interface{}{} for _, keyfile := range keyfiles { publicKeys, err := keyutil.PublicKeysFromFile(keyfile) if err != nil { return nil, err } allPublicKeys = append(allPublicKeys, publicKeys...) } // 唯一的区别 这里根据kube-apiserver提供的称号指定名称 tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(issuers, allPublicKeys, apiAudiences, serviceaccount.NewValidator(serviceAccountGetter)) return tokenAuthenticator, nil } 最后根据ServiceAccounts，Secrets等值签发一个token，也就是通过下列命令获取的值\ngo 1 $ kubectl get secret multus-token-v6bfg -n kube-system -o jsonpath={\u0026#34;.data.token\u0026#34;} 场景4：openid OpenID Connect是 OAuth2 风格，允许用户授权三方网站访问他们存储在另外的服务提供者上的信息，而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容，下面是一张kubernetes 使用 OID 认证的逻辑图\n图：Kubernetes OID认证 Source：https://developer.okta.com/blog/2021/11/08/k8s-api-server-oidc\n场景5：webhook webhook是kubernetes提供自定义认证的其中一种，主要是用于认证 “不记名 token“ 的钩子，“不记名 token“ 将 由身份验证服务创建。当用户对kubernetes访问时，会触发准入控制，当对kubernetes集群注册了 authenticaion webhook时，将会使用该webhook提供的方式进行身份验证时，此时会为您生成一个 token 。\n如代码 pkg/kubeapiserver/authenticator.New() 中所示 newWebhookTokenAuthenticator 会通过提供的config (--authentication-token-webhook-config-file) 来创建出一个 WebhookTokenAuthenticator\ngo 1 2 3 4 5 6 7 8 if len(config.WebhookTokenAuthnConfigFile) \u0026gt; 0 { webhookTokenAuth, err := newWebhookTokenAuthenticator(config) if err != nil { return nil, nil, err } tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth) } 下图是kubernetes 中 WebhookToken 验证的工作原理\n图：kubernetes WebhookToken验证原理 Source：https://learnk8s.io/kubernetes-custom-authentication\n最后由token中的authHandler，循环所有的Handlers在运行 AuthenticateToken 去进行获取用户的信息\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func (authHandler *unionAuthTokenHandler) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) { var errlist []error for _, currAuthRequestHandler := range authHandler.Handlers { info, ok, err := currAuthRequestHandler.AuthenticateToken(ctx, token) if err != nil { if authHandler.FailOnError { return info, ok, err } errlist = append(errlist, err) continue } if ok { return info, ok, err } } return nil, false, utilerrors.NewAggregate(errlist) } 而webhook插件也实现了这个方法 AuthenticateToken ,这里会通过POST请求，调用注入的webhook，该请求携带一个JSON 格式的 TokenReview 对象，其中包含要验证的令牌\ngo 1 2 3 4 5 6 7 8 9 func (w *WebhookTokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) { .... start := time.Now() result, statusCode, tokenReviewErr = w.tokenReview.Create(ctx, r, metav1.CreateOptions{}) latency := time.Since(start) ... } webhook token认证服务要返回用户的身份信息，就是上面token部分提到的数据结构（webhook来决定接受还是拒绝该用户）\ngo 1 2 3 4 5 6 type DefaultInfo struct { Name string UID string Groups []string Extra map[string][]string } 场景6：代理认证 实验：基于LDAP的身份认证 通过上面阐述，大致了解到kubernetes认证框架中的用户的分类以及认证的策略由哪些，实验的目的也是为了阐述一个结果，就是使用OIDC/webhook 是比其他方式更好的保护，管理kubernetes集群。首先在安全上，假设网络环境是不安全的，那么任意node节点遗漏 bootstrap token文件，就意味着拥有了集群中最高权限；其次在管理上，越大的团队，人数越多，不可能每个用户都提供单独的证书或者token，要知道传统教程中讲到token在kubernetes集群中是永久有效的，除非你删除了这个secret/sa；而Kubernetes提供的插件就很好的解决了这些问题。\n实验环境 一个kubernetes集群 一个openldap服务，建议可以是集群外部的，因为webhook不像SSSD有缓存机制，并且集群不可用，那么认证不可用，当认证不可用时会导致集群不可用，这样事故影响的范围可以得到控制，也叫最小化半径 了解ldap相关技术，并了解go ldap客户端 实验大致分为以下几个步骤：\n建立一个HTTP服务器用于返回给kubernetes Authenticaion服务 查询ldap该用户是否合法 查询用户是否合法 查询用户所属组是否拥有权限 实验开始 初始化用户数据 首先准备openldap初始化数据，创建三个 posixGroup 组，与5个用户 admin, admin1, admin11, searchUser, syncUser 密码均为111，组与用户关联使用的 memberUid\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 cat \u0026lt;\u0026lt; EOF | ldapdelete -r -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: dc=test,dc=com objectClass: top objectClass: organizationalUnit objectClass: extensibleObject description: US Organization ou: people dn: ou=tvb,dc=test,dc=com objectClass: organizationalUnit description: Television Broadcasts Limited ou: tvb dn: cn=admin,ou=tvb,dc=test,dc=com objectClass: posixGroup gidNumber: 10000 cn: admin dn: cn=conf,ou=tvb,dc=test,dc=com objectClass: posixGroup gidNumber: 10001 cn: conf dn: cn=dir,ou=tvb,dc=test,dc=com objectClass: posixGroup gidNumber: 10002 cn: dir dn: uid=syncUser,ou=tvb,dc=test,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: posixAccount objectClass: shadowAccount objectClass: pwdPolicy pwdAttribute: userPassword uid: syncUser cn: syncUser uidNumber: 10006 gidNumber: 10002 homeDirectory: /home/syncUser loginShell: /bin/bash sn: syncUser givenName: syncUser memberOf: cn=confGroup,ou=tvb,dc=test,dc=com dn: uid=searchUser,ou=tvb,dc=test,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: posixAccount objectClass: shadowAccount objectClass: pwdPolicy pwdAttribute: userPassword uid: searchUser cn: searchUser uidNumber: 10005 gidNumber: 10001 homeDirectory: /home/searchUser loginShell: /bin/bash sn: searchUser givenName: searchUser memberOf: cn=dirGroup,ou=tvb,dc=test,dc=com dn: uid=admin1,ou=tvb,dc=test,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: posixAccount objectClass: shadowAccount objectClass: pwdPolicy pwdAttribute: userPassword uid: admin1 sn: admin1 cn: admin uidNumber: 10010 gidNumber: 10000 homeDirectory: /home/admin loginShell: /bin/bash givenName: admin memberOf: cn=adminGroup,ou=tvb,dc=test,dc=com dn: uid=admin11,ou=tvb,dc=test,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: posixAccount objectClass: shadowAccount objectClass: pwdPolicy sn: admin11 pwdAttribute: userPassword uid: admin11 cn: admin11 uidNumber: 10011 gidNumber: 10000 homeDirectory: /home/admin loginShell: /bin/bash givenName: admin11 memberOf: cn=adminGroup,ou=tvb,dc=test,dc=com dn: uid=admin,ou=tvb,dc=test,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: posixAccount objectClass: shadowAccount objectClass: pwdPolicy pwdAttribute: userPassword uid: admin cn: admin uidNumber: 10009 gidNumber: 10000 homeDirectory: /home/admin loginShell: /bin/bash sn: admin givenName: admin memberOf: cn=adminGroup,ou=tvb,dc=test,dc=com EOF 接下来需要确定如何为认证成功的用户，上面讲到对于kubernetes中用户格式为 v1.UserInfo 的格式，即要获得用户，即用户组，假设需要查找的用户为，admin，那么在openldap中查询filter如下：\nbash 1 \u0026#34;(|(\u0026amp;(objectClass=posixAccount)(uid=admin))(\u0026amp;(objectClass=posixGroup)(memberUid=admin)))\u0026#34; 上面语句意思是，找到 objectClass=posixAccount 并且 uid=admin 或者 objectClass=posixGroup 并且 memberUid=admin 的条目信息，这里使用 ”|“ 与 ”\u0026amp;“ 是为了要拿到这两个结果。\n编写webhook查询用户部分 这里由于openldap配置密码保存格式不是明文的，如果直接使用 ”=“ 来验证是查询不到内容的，故直接多用了一次登录来验证用户是否合法\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 func ldapSearch(username, password string) (*v1.UserInfo, error) { ldapconn, err := ldap.DialURL(ldapURL) if err != nil { klog.V(3).Info(err) return nil, err } defer ldapconn.Close() // Authenticate as LDAP admin user err = ldapconn.Bind(\u0026#34;uid=searchUser,ou=tvb,dc=test,dc=com\u0026#34;, \u0026#34;111\u0026#34;) if err != nil { klog.V(3).Info(err) return nil, err } // Execute LDAP Search request result, err := ldapconn.Search(ldap.NewSearchRequest( \u0026#34;ou=tvb,dc=test,dc=com\u0026#34;, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf(\u0026#34;(\u0026amp;(objectClass=posixGroup)(memberUid=%s))\u0026#34;, username), // Filter nil, nil, )) if err != nil { klog.V(3).Info(err) return nil, err } userResult, err := ldapconn.Search(ldap.NewSearchRequest( \u0026#34;ou=tvb,dc=test,dc=com\u0026#34;, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf(\u0026#34;(\u0026amp;(objectClass=posixAccount)(uid=%s))\u0026#34;, username), // Filter nil, nil, )) if err != nil { klog.V(3).Info(err) return nil, err } if len(result.Entries) == 0 { klog.V(3).Info(\u0026#34;User does not exist\u0026#34;) return nil, errors.New(\u0026#34;User does not exist\u0026#34;) } else { // 验证用户名密码是否正确 if err := ldapconn.Bind(userResult.Entries[0].DN, password); err != nil { e := fmt.Sprintf(\u0026#34;Failed to auth. %s\\n\u0026#34;, err) klog.V(3).Info(e) return nil, errors.New(e) } else { klog.V(3).Info(fmt.Sprintf(\u0026#34;User %s Authenticated successfuly!\u0026#34;, username)) } // 拼接为kubernetes authentication 的用户格式 user := new(v1.UserInfo) for _, v := range result.Entries { attrubute := v.GetAttributeValue(\u0026#34;objectClass\u0026#34;) if strings.Contains(attrubute, \u0026#34;posixGroup\u0026#34;) { user.Groups = append(user.Groups, v.GetAttributeValue(\u0026#34;cn\u0026#34;)) } } u := userResult.Entries[0].GetAttributeValue(\u0026#34;uid\u0026#34;) user.UID = u user.Username = u return user, nil } } 编写HTTP部分 这里有几个需要注意的部分，即用户或者理解为要认证的token的定义，此处使用了 ”username@password“ 格式作为用户的辨别，即登录kubernetes时需要直接输入 ”username@password“ 来作为登录的凭据。\n第二个部分为返回值，返回给Kubernetes的格式必须为 api/authentication/v1.TokenReview 格式，Status.Authenticated 表示用户身份验证结果，如果该用户合法，则设置 tokenReview.Status.Authenticated = true 反之亦然。如果验证成功还需要 Status.User 这就是在ldapSearch\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 func serve(w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil { httpError(w, err) return } klog.V(4).Info(\u0026#34;Receiving: %s\\n\u0026#34;, string(b)) var tokenReview v1.TokenReview err = json.Unmarshal(b, \u0026amp;tokenReview) if err != nil { klog.V(3).Info(\u0026#34;Json convert err: \u0026#34;, err) httpError(w, err) return } // 提取用户名与密码 s := strings.SplitN(tokenReview.Spec.Token, \u0026#34;@\u0026#34;, 2) if len(s) != 2 { klog.V(3).Info(fmt.Errorf(\u0026#34;badly formatted token: %s\u0026#34;, tokenReview.Spec.Token)) httpError(w, fmt.Errorf(\u0026#34;badly formatted token: %s\u0026#34;, tokenReview.Spec.Token)) return } username, password := s[0], s[1] // 查询ldap，验证用户是否合法 userInfo, err := ldapSearch(username, password) if err != nil { // 这里不打印日志的原因是 ldapSearch 中打印过了 return } // 设置返回的tokenReview if userInfo == nil { tokenReview.Status.Authenticated = false } else { tokenReview.Status.Authenticated = true tokenReview.Status.User = *userInfo } b, err = json.Marshal(tokenReview) if err != nil { klog.V(3).Info(\u0026#34;Json convert err: \u0026#34;, err) httpError(w, err) return } w.Write(b) klog.V(3).Info(\u0026#34;Returning: \u0026#34;, string(b)) } func httpError(w http.ResponseWriter, err error) { err = fmt.Errorf(\u0026#34;Error: %v\u0026#34;, err) w.WriteHeader(http.StatusInternalServerError) // 500 fmt.Fprintln(w, err) klog.V(4).Info(\u0026#34;httpcode 500: \u0026#34;, err) } 下面是完整的代码\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;errors\u0026#34; \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;github.com/go-ldap/ldap\u0026#34; \u0026#34;k8s.io/api/authentication/v1\u0026#34; \u0026#34;k8s.io/klog/v2\u0026#34; ) var ldapURL string func main() { klog.InitFlags(nil) flag.Parse() http.HandleFunc(\u0026#34;/authenticate\u0026#34;, serve) klog.V(4).Info(\u0026#34;Listening on port 443 waiting for requests...\u0026#34;) klog.V(4).Info(http.ListenAndServe(\u0026#34;:443\u0026#34;, nil)) ldapURL = \u0026#34;ldap://10.0.0.10:389\u0026#34; ldapSearch(\u0026#34;admin\u0026#34;, \u0026#34;1111\u0026#34;) } func serve(w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil { httpError(w, err) return } klog.V(4).Info(\u0026#34;Receiving: %s\\n\u0026#34;, string(b)) var tokenReview v1.TokenReview err = json.Unmarshal(b, \u0026amp;tokenReview) if err != nil { klog.V(3).Info(\u0026#34;Json convert err: \u0026#34;, err) httpError(w, err) return } // 提取用户名与密码 s := strings.SplitN(tokenReview.Spec.Token, \u0026#34;@\u0026#34;, 2) if len(s) != 2 { klog.V(3).Info(fmt.Errorf(\u0026#34;badly formatted token: %s\u0026#34;, tokenReview.Spec.Token)) httpError(w, fmt.Errorf(\u0026#34;badly formatted token: %s\u0026#34;, tokenReview.Spec.Token)) return } username, password := s[0], s[1] // 查询ldap，验证用户是否合法 userInfo, err := ldapSearch(username, password) if err != nil { // 这里不打印日志的原因是 ldapSearch 中打印过了 return } // 设置返回的tokenReview if userInfo == nil { tokenReview.Status.Authenticated = false } else { tokenReview.Status.Authenticated = true tokenReview.Status.User = *userInfo } b, err = json.Marshal(tokenReview) if err != nil { klog.V(3).Info(\u0026#34;Json convert err: \u0026#34;, err) httpError(w, err) return } w.Write(b) klog.V(3).Info(\u0026#34;Returning: \u0026#34;, string(b)) } func httpError(w http.ResponseWriter, err error) { err = fmt.Errorf(\u0026#34;Error: %v\u0026#34;, err) w.WriteHeader(http.StatusInternalServerError) // 500 fmt.Fprintln(w, err) klog.V(4).Info(\u0026#34;httpcode 500: \u0026#34;, err) } func ldapSearch(username, password string) (*v1.UserInfo, error) { ldapconn, err := ldap.DialURL(ldapURL) if err != nil { klog.V(3).Info(err) return nil, err } defer ldapconn.Close() // Authenticate as LDAP admin user err = ldapconn.Bind(\u0026#34;cn=admin,dc=test,dc=com\u0026#34;, \u0026#34;111\u0026#34;) if err != nil { klog.V(3).Info(err) return nil, err } // Execute LDAP Search request result, err := ldapconn.Search(ldap.NewSearchRequest( \u0026#34;ou=tvb,dc=test,dc=com\u0026#34;, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf(\u0026#34;(\u0026amp;(objectClass=posixGroup)(memberUid=%s))\u0026#34;, username), // Filter nil, nil, )) if err != nil { klog.V(3).Info(err) return nil, err } userResult, err := ldapconn.Search(ldap.NewSearchRequest( \u0026#34;ou=tvb,dc=test,dc=com\u0026#34;, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf(\u0026#34;(\u0026amp;(objectClass=posixAccount)(uid=%s))\u0026#34;, username), // Filter nil, nil, )) if err != nil { klog.V(3).Info(err) return nil, err } if len(result.Entries) == 0 { klog.V(3).Info(\u0026#34;User does not exist\u0026#34;) return nil, errors.New(\u0026#34;User does not exist\u0026#34;) } else { // 验证用户名密码是否正确 if err := ldapconn.Bind(userResult.Entries[0].DN, password); err != nil { e := fmt.Sprintf(\u0026#34;Failed to auth. %s\\n\u0026#34;, err) klog.V(3).Info(e) return nil, errors.New(e) } else { klog.V(3).Info(fmt.Sprintf(\u0026#34;User %s Authenticated successfuly!\u0026#34;, username)) } // 拼接为kubernetes authentication 的用户格式 user := new(v1.UserInfo) for _, v := range result.Entries { attrubute := v.GetAttributeValue(\u0026#34;objectClass\u0026#34;) if strings.Contains(attrubute, \u0026#34;posixGroup\u0026#34;) { user.Groups = append(user.Groups, v.GetAttributeValue(\u0026#34;cn\u0026#34;)) } } u := userResult.Entries[0].GetAttributeValue(\u0026#34;uid\u0026#34;) user.UID = u user.Username = u return user, nil } } 部署webhook kubernetes官方手册中指出，启用webhook认证的标记是在 kube-apiserver 指定参数 --authentication-token-webhook-config-file 。而这个配置文件是一个 kubeconfig 类型的文件格式 [5]\n下列是部署在kubernetes集群外部的配置\n创建一个给 kube-apiserver 使用的配置文件 /etc/kubernetes/auth/authentication-webhook.conf\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: v1 kind: Config clusters: - cluster: server: http://10.0.0.1:88/authenticate name: authenticator users: - name: webhook-authenticator current-context: webhook-authenticator@authenticator contexts: - context: cluster: authenticator user: webhook-authenticator name: webhook-authenticator@authenticator 修改 kube-apiserver 参数\nbash 1 2 3 4 5 6 # 指向对应的配置文件 --authentication-token-webhook-config-file=/etc/kubernetes/auth/authentication-webhook.conf # 这个是token缓存时间，指的是用户在访问API时验证通过后在一定时间内无需在请求webhook进行认证了 --authentication-token-webhook-cache-ttl=30m # 版本指定为API使用哪个版本？authentication.k8s.io/v1或v1beta1 --authentication-token-webhook-version=v1 启动服务后，创建一个 kubeconfig 中的用户用于验证结果\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 apiVersion: v1 clusters: - cluster: certificate-authority-data: server: https://10.0.0.4:6443 name: kubernetes contexts: - context: cluster: kubernetes user: k8s-admin name: k8s-admin@kubernetes current-context: k8s-admin@kubernetes kind: Config preferences: {} users: - name: admin user: token: admin@111 验证结果 当密码不正确时，使用用户admin请求集群\nbash 1 2 $ kubectl get pods --user=admin error: You must be logged in to the server (Unauthorized) 当密码正确时，使用用户admin请求集群\nbash 1 2 $ kubectl get pods --user=admin Error from server (Forbidden): pods is forbidden: User \u0026#34;admin\u0026#34; cannot list resource \u0026#34;pods\u0026#34; in API group \u0026#34;\u0026#34; in the namespace \u0026#34;default\u0026#34; 可以看到admin用户是一个不存在与集群中的用户，并且提示没有权限操作对应资源，此时将admin用户与集群中的cluster-admin绑定，测试结果\nbash 1 2 3 $ kubectl create clusterrolebinding admin \\ --clusterrole=cluster-admin \\ --group=admin 此时再尝试使用admin用户访问集群\nbash 1 2 3 4 $ kubectl get pods --user=admin NAME READY STATUS RESTARTS AGE netbox-85865d5556-hfg6v 1/1 Running 0 91d netbox-85865d5556-vlgr4 1/1 Running 0 91d 总结 kubernetes authentication 插件提供的功能可以注入一个认证系统，这样可以完美解决了kubernetes中用户的问题，而这些用户并不存在与kubernetes中，并且也无需为多个用户准备大量serviceaccount或者证书，也可以完成鉴权操作。首先返回值标准如下所示，如果kubernetes集群有对在其他用户系统中获得的 Groups 并建立了 clusterrolebinding 或 rolebinding 那么这个组的所有用户都将有这些权限。管理员只需要维护与公司用户系统中组同样多的 clusterrole 与 clusterrolebinding 即可\ngo 1 2 3 4 5 6 type DefaultInfo struct { Name string UID string Groups []string Extra map[string][]string } 对于如何将 kubernetes 与其他平台进行融合可以参考 文章\nNotes：Kubernetes原生就支持OID，完全不用自己开发webhook从而实现接入其他系统，这里展示的只是一个思路\nReference ​[1] Implementing a custom Kubernetes authentication method\n​[2] Controlling Access to the Kubernetes API\n​[3] Users in Kubernetes\n​[4] bootstrap tokens\n​[5] Webhook Token Authentication\n","permalink":"https://www.161616.top/ch31-authentication/","summary":"本文是关于Kubernetes 4A解析的第1章 深入理解Kubernetes 4A - Authentication源码解析 深入理解Kubernetes 4A - Authorization源码解析 深入理解Kubernetes 4A - Admission Control源码解析 深入理解Kubernetes 4A - Audit源码解析 TLS Everywhere - 解密kubernetes集群的安全认证 所有关于Kubernetes 4A部分代码上传至仓库 github.com/cylonchau/hello-k8s-4A\nOverview 本章主要简单阐述kubernetes 认证相关原理，最后以实验来阐述kubernetes用户系统的思路\nobjective：\n了解kubernetes 各种认证机制的原理 了解kubernetes 用户的概念 了解kubernetes authentication webhook 完成实验，如何将其他用户系统接入到kubernetes中的一个思路 如有错别字或理解错误地方请多多担待，代码是以1.24进行整理，实验是以1.19环境进行，差别不大。\nKubernetes 认证 在Kubernetes apiserver对于认证部分所描述的，对于所有用户访问Kubernetes API（通过任何客户端，客户端库，kubectl 等）时都会经历 验证 (Authentication) , 授权 (Authorization), 和准入控制 (Admission control) 三个阶段来完成对 “用户” 进行授权，整个流程正如下图所示\n图：Kubernetes API 请求的请求处理步骤图 Source：https://www.armosec.io/blog/kubernetes-admission-controller/\n其中在大多数教程中，在对这三个阶段所做的工作大致上为：\nAuthentication 阶段所指用于确认请求访问Kubernetes API 用户是否为合法用户，拒绝为401\nAuthorization 阶段所指的将是这个用户是否有对操作的资源的权限，拒绝为403\nAdmission control 阶段所指控制对请求资源进行控制，通俗来说，就是一票否决权，即使前两个步骤完成","title":"深入理解Kubernetes 4A - Authentication源码解析"},{"content":" openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 Overview SSSD (System Security Services Daemon) 是一套用于远程身份验证的套件服务，为使用SSSD服务的客户端提供了远程访问身份认证服务来获取权限，其后端包括AD, LDAP等，本文将围绕下列方向来阐述SSSD：\n为什么需要SSSD，以及使用SSSD来解决什么 使用SSSD的好处 SSSD服务工作原理及架构 如何在Linux上配置SSSD+LDAP 为什么需要SSSD SSSD设计主要是为了传统使用身份认证服务，例如PAM+NSS架构中存在的一些问题：\nPAM+NSS扩展性差，并配置较为复杂，尽管提供了 authconfig ，通常在大多数教程中以及不同的系统中配置都不相同 PAM+NSS不是真正意义上的离线身份认证，如果当 nslcd 或者 slapd 等服务异常时，无法完成用户认证 以及越来越多的后端，例如LDAP, AD, IPA, IdM,Kerberos等无法做到很好的适配 SSSD就是为了解决上述的问题，对于Linux平台中，SSSD拥有比传统PAM+NSS更好的优势：\n符合现代Linux基础架构设计需求，可以适配更多的后端，并降低了操作配置的复杂性 增加了缓存功能，有效的减少了对于后端服务器的负载 因为有了缓存功能，实现了真正的离线认证功能，即使后端服务异常，例如LDAP服务down 了解SSSD架构 了解SSSD架构，其实就是了解前两章的内容，要做到真正的多后端，真脱机，那么服务就有多个组件组成：\nMonitor：所有SSSD的父进程，即用于管理 Providers 与 Responders Providers：用于感知验证后端的模块，后端就是提供目录树的一端 Responders：为Linux提供与后端交互的功能，这部分通常为 NSS PAM sudo等 图：SSSD架构图 Source：https://sssd.io/docs/architecture.html\nProviders Local：保存在本地缓存中的账户信息 LDAP, Kerberos, AD, IPA ：用于 Linux/UNIX 网络环境中集成身份和身份验证解决方案。 IdM：一种使用本地 Linux 工具在 Linux 系统上创建身份存储、集中身份验证、Kerberos 和 DNS 服务的域控制以及授权策略的目录树后端 sudo，autofs 与LDAP集成的功能 Responders nss：名称解析服务，用于解析组与用户信息 pam：用于用户验证的模块 autofs：自动挂载模块，通常用于与LDAP集成，用于映射LDAP目录树 sudo：linux中用户权限控制，通常也是与LDAP集成 ssh： sssd_be：SSSD的后端进程：其中每一种后端都代表都作为一个sssd_be进程启动 monitor monitor是SSSD的进程，是用于管理（启动，停止，监控服务状态）Provider与Responders的功能\nSSSD工作流程 当每次用户登录，使用 id, getent, su , sudo 等命令时，都会触发一次查询，下图是整个查询的流程\n图：SSSD查询流程 Source：https://sssd.io/docs/architecture.html\n可以看到图中描述了对用户 Alice 进行查询，从调用函数getpwnam 开始，首先会在memcache中检索用户数据，如果检索不到，此时 sssd_nss（sssd内置的模块）将从本地cache检索，如果此时还是检索不到，那么 sssd_be (上面提到过一个后端会有一个 sssd_be 进程) 将从远程后端检索。\n通过这种模式，增加了缓存功能，有效的减少了对于后端服务器的负载，以及完整的脱机查询功能（即使LDAP服务短暂不可用），而存在的问题则是可能会造成本地资源负载过高，例如这个例子：由于服务器忙时，并且sssd_nss 造成与其他进程争抢资源 [2]\n迁移nslcd到sssd 安装sssd CentOS 6/CentOS 7：yum install sssd sssd-tools [3] Ubuntu/Debian：apt install sssd-ldap ldap-utils [4] 配置SSSD 先决条件：建议使用SSL模式与openldap进行通信，此时数据是加密传输的\n默认安装好sssd后，不存在配置文件，需要手动创建配置文件 /etc/sssd/sssd.conf\nbash 1 2 chown root:root /etc/sssd/sssd.conf chmod 600 /etc/sssd/sssd.conf 复制下列配置到 /etc/sssd/sssd.conf\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 [sssd] # sssd全局配置，service为需要使用的模块，这里将会启动一个子进程 # 例如传统的nss+pam作为linux认证的基础，这里开启就为nss,pam services = nss, pam config_file_version = 2 # domains作为给后端配置提供的一个名称 domains = default # 如果需要对每个模块定义的配置可以[\u0026lt;module_name\u0026gt;]进行配置 [pam] # 成功登录后的用户在sssd中缓存的天数。 如果为0将意味着永久保存。 offline_credentials_expiration = 60 [domain/default] # 启用tls通讯 ldap_id_use_start_tls = True # 这个与offline_credentials_expiration进行配合的参数 # 如果true 将在offline_credentials_expiration天后是否查找缓存 # 如果为false，或不填写该参数将不查找 cache_credentials = True # 搜索的跟域 ldap_search_base = dc=ldapmaster,dc=kifarunix-demo,dc=com # 下面是一系列provider id_provider = ldap auth_provider = ldap chpass_provider = ldap access_provider = ldap # ldap相关配置 ldap_uri = ldap://ldapmaster.kifarunix-demo.com # 搜索使用的ldap用户 ldap_default_bind_dn = cn=readonly,ou=system,dc=ldapmaster,dc=kifarunix-demo,dc=com # 搜索使用的ldap用户的密码，仅支持明文 ldap_default_authtok = P@ssWOrd # tls相关参数 # 这个参数是指定TLS绘画是否对服务器证书进行检查 # never 客户端不检查服务器证书 # allow 请求验证服务端证书，如果没有证书则会话正常进行，如果证书错误，将被忽略 # demand 请求验证服务端证书，如果证书错误或者没有证书，终止会话 # try 请求验证服务端证书，如果没有证书则会话正常进行，如果证书错误，终止会话 ldap_tls_reqcert = demand ldap_tls_cacert = /etc/openldap/certs/cacert.pem # 与ldap服务端通信超时相关配置 ldap_search_timeout = 50 ldap_network_timeout = 60 # 搜索用户的参数，下列是默认条件，这是强制参数，sssd在ldap上搜索用户的搜索条件，如果目录树是特别的名称需要更改 ldap_access_order = filter ldap_access_filter = (objectClass=posixAccount) # 例如 ldap_access_filter = memberOf=cn=allowedusers,ou=Groups,dc=example,dc=com 配置完成后可以启动服务，该服务与使用 nlscd 一样，需要开机自启，否则远端用户将不能完成认证\nbash 1 2 systemctl start sssd systemctl enable sssd 完成后需要配置下 nss 与 pam 的配置\nCentOS 7：authconfig --enablesssd --enablesssdauth --enablemkhomedir --update CentOS 8：authselect apply-changes -b --backup=ldap-configuration-backup Ubuntu：pam-auth-update --enable mkhomedir Notes：参数根据平台不同命令也不同，可以man查看下具体需要配置什么\nsudo over sssd 对于sudo方面，配置没有使用nss+pam架构那么复杂只需要加几个参数即可使用sssd作为sudo认证\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 [sssd] .. # service 增加 sudo services = nss, pam, sudo, ssh domains = default debug_level = 6 [sudo] # 枚举授信域 subdomain_enumerate = true debug_level = 9 [domain/default] ... # provider 增加 sudo_provider sudo_provider = ldap # 配置sudo默认搜索域，也就是sudoers的跟容器，这个必须设置 ldap_sudo_search_base = ou=SUDOers,dc=test,dc=com # sssd在下载ldap服务端的sudo规则间隔秒数 # 默认21600 ldap_sudo_full_refresh_interval=86400 # 智能刷新，默认900秒，可以设置为0禁止只能刷新 # 该参数是指，下载条目为服务端USD高于当前SSSD的USN最高值的所有规则 # USD Update Sequence Number 代表数据变化的序列 ldap_sudo_smart_refresh_interval=3600 下面为sudo 与 NSS+PAM 迁移至SSSD的完整配置\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [sssd] config_file_version = 2 services = nss, pam, sudo, ssh domains = default debug_level = 6 [pam] offline_credentials_expiration = 60 [sudo] subdomain_enumerate = true debug_level = 9 [domain/default] id_provider = ldap auth_provider = ldap sudo_provider = ldap ldap_uri = ldaps://10.0.0.10/ ldap_search_base = dc=test,dc=com ldap_sudo_search_base = ou=SUDOers,dc=test,dc=com ldap_default_bind_dn = uid=searchUser,ou=tvb,dc=test,dc=com ldap_default_authtok_type = password ldap_default_authtok = 1 cache_credentials = True ldap_search_timeout = 50 ldap_network_timeout = 60 ldap_access_order = filter ldap_access_filter = (objectClass=posixAccount) ldap_tls_cacert = /etc/ssl/certs/cacert.crt ldap_id_use_start_tls = true ldap_tls_reqcert = allow ldap_sudo_full_refresh_interval=86400 ldap_sudo_smart_refresh_interval=3600 此时验证用户登录与sudo是使用SSSD缓存还是通过每次请求slapd\nNotes：对于更多的配置参数的说明，可以使用 man sssd , man sssd-ldap .. 进行查询 ，也可以通过 linux man 手册进行查询\nTroubleshooting ldap 日志报错 TLS established tls_ssf=256 ssf=256 [5]\ntext 1 2 Sep 19 12:16:40 centos6 slapd[16620]: conn=228 fd=14 ACCEPT from IP=client-IP:client-Port (IP=0.0.0.0:636) Sep 19 12:16:40 centos6 slapd[16620]: conn=228 fd=14 TLS established tls_ssf=256 ssf=256 这里原因是，如果你使用TLS进行通讯，只有基于 ldap://[port_389] 才是TLS，如果使用 ldaps://[port_636] 那么是通过SSL隧道进行的\n解决：对于SSSD端需要开启对应的TLS配置，如下\ntext 1 2 3 4 ldap_tls_cacert = /etc/ssl/certs/cacert.crt ldap_id_use_start_tls = true # 对服务器提供的证书执行的检查,因为服务端配置了要验证客户端证书 ldap_tls_reqcert = allow Reference [1] sssd architecture\n[2] High CPU usage by sssd_nss during heavy disk IO\n[3] SSSD and LDAP\n[4] Chapter 10. Migrating authentication from nslcd to SSSD\n[5] OpenLDAP Client 2.4.23: TLS negotiation failure\n[6] Chapter 10. Migrating authentication from nslcd to SSSD\n[7] Configure SSSD\n[8] Configure OpenLDAP SSSD client on CentOS 6/7\n","permalink":"https://www.161616.top/ch11-sssd/","summary":"openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 Overview SSSD (System Security Services Daemon) 是一套用于远程身份验证的套件服务，为使用SSSD服务的客户端提供了远程访问身份认证服务来获取权限，其后端包括AD, LDAP等，本文将围绕下列方向来阐述SSSD：\n为什么需要SSSD，以及使用SSSD来解决什么 使用SSSD的好处 SSSD服务工作原理及架构 如何在Linux上配置SSSD+LDAP 为什么需要SSSD SSSD设计主要是为了传统使用身份认证服务，例如PAM+NSS架构中存在的一些问题：\nPAM+NSS扩展性差，并配置较为复杂，尽管提供了 authconfig ，通常在大多数教程中以及不同的系统中配置都不相同 PAM+NSS不是真正意义上的离线身份认证，如果当 nslcd 或者 slapd 等服务异常时，无法完成用户认证 以及越来越多的后端，例如LDAP, AD, IPA, IdM,Kerberos等无法做到很好的适配 SSSD就是为了解决上述的问题，对于Linux平台中，SSSD拥有比传统PAM+NSS更好的优势：","title":"理解ldap - 使用SSSD接入OpenLDAP实现身份验证"},{"content":"测试方法 基于使用场景，最后⽣成的规则会是按照 ip 或者 ip:port 来进行过滤，测试时将使用10万条 iptables 规则来模拟对性能的压力；为了最大化测试压力情况，10万条 iptables 规则将都是==不会匹配==机房流量，通俗来讲，就是链式匹配会进行所有匹配并最后以无匹配告终。\n网络负载的模拟将使用同机房 scp 来模拟，并按照下述条件进行匹配：\n查看正常的拷贝速度，cpu负载等 我们建⽴10万条的普通 iptables 规则，查看规则建立速度，拷贝速度，CPU负载，CPU主要耗时操作等 我们建⽴10万的 ipset ，并把普通的 iptables 规则转为结合 ipset 的规则，查看规则建立速度，拷贝速度，CPU负载，CPU主要耗时等。 实验开始 步骤一：在同机房的⼀个机器构造⼀个大文件\n同机房拷贝\n观察网卡速度，CPU，系统主要耗时操作的等，此场景将在iptables 规则为空的情况下进行观察\n使用 sar 观测网卡速度\n使用 top 观察CPU负载\n使用 perf top -G 观察CPU占用\n步骤二：创建10万条iptables，观察⽹卡速度、cpu、系统主要耗时操作的等，会发现cpu利⽤率⼤部分被ipt占⽤，拷⻉速度下降到不到⼗分之⼀\nbash 1 2 3 4 5 6 7 8 #!/bin/bash echo *filter for ((i=1;i\u0026lt;=$1;i++)) do echo -I INPUT -S $i -j ACCEPT done echo COMMIT 执行脚本\nbash 1 $ time ./mkrule.sh 100000 | sudo iptables-restore 观察添加规则后的⽹卡速度，CPU，系统主要耗时操作的等\n使用 sar 观测网卡速度\n使用 top 观察CPU负载\n使用 perf top -G 观察CPU占用\n步骤三：使用ipset替换iptables\n此时改为使⽤ ipset ⽅式观察网卡卡速度，CPU，系统主要耗时操作的等，会发现跟没有规则没有明显变化。ipset的内存量不到2M。初步估计内存使⽤量 = $hashsize \\times 16 + 存⼊数 \\times (4～32之间)$\nbash 1 2 3 4 5 6 7 8 9 10 11 12 #!/bin/bash #ipset create whitelist hash:ip maxelem 1000000 -exist #ipset flush whitelist echo \u0026#39; creae whitelist hash:ip family inet hashsize 65536 maxelem 100000000\u0026#39; for ((i=1;i\u0026lt;=$1;i++)) do\t#ipset add whitelist $i echo add whitelist $i done # iptables -I INPUT -m set --match-set whitelisti src -j ACCEPT 执行脚本\nbash 1 2 3 $ time ./mkset.sh 100000 | sudo ipset restore $ sudo iptables -Ln $ sudo iptables -I INPUT -m set --match-set whitelisti src -j ACCEPT 观察添加规则后的⽹卡速度，CPU，系统主要耗时操作的等\n使用 sar 观测网卡速度\n使用 top 观察CPU负载\n使用 perf top -G 观察CPU占用\nbash 1 2 3 4 5 6 $ sudo ipset list | head Name: whitelist Type: hash:ip Header: family inet hashsize 65536 maxelem 100000000 Size in memory: 1891208 References: 1 总结：ipset对于CPU和内存的影响很小，在大量规则场景下符合预期\n","permalink":"https://www.161616.top/ipset-preformance/","summary":"测试方法 基于使用场景，最后⽣成的规则会是按照 ip 或者 ip:port 来进行过滤，测试时将使用10万条 iptables 规则来模拟对性能的压力；为了最大化测试压力情况，10万条 iptables 规则将都是==不会匹配==机房流量，通俗来讲，就是链式匹配会进行所有匹配并最后以无匹配告终。\n网络负载的模拟将使用同机房 scp 来模拟，并按照下述条件进行匹配：\n查看正常的拷贝速度，cpu负载等 我们建⽴10万条的普通 iptables 规则，查看规则建立速度，拷贝速度，CPU负载，CPU主要耗时操作等 我们建⽴10万的 ipset ，并把普通的 iptables 规则转为结合 ipset 的规则，查看规则建立速度，拷贝速度，CPU负载，CPU主要耗时等。 实验开始 步骤一：在同机房的⼀个机器构造⼀个大文件\n同机房拷贝\n观察网卡速度，CPU，系统主要耗时操作的等，此场景将在iptables 规则为空的情况下进行观察\n使用 sar 观测网卡速度\n使用 top 观察CPU负载\n使用 perf top -G 观察CPU占用\n步骤二：创建10万条iptables，观察⽹卡速度、cpu、系统主要耗时操作的等，会发现cpu利⽤率⼤部分被ipt占⽤，拷⻉速度下降到不到⼗分之⼀\nbash 1 2 3 4 5 6 7 8 #!/bin/bash echo *filter for ((i=1;i\u0026lt;=$1;i++)) do echo -I INPUT -S $i -j ACCEPT done echo COMMIT 执行脚本\nbash 1 $ time .","title":"ipset性能测试"},{"content":"Linux 架构概述 [1] 本章节简单阐述Linux系统的结构，并讨论子系统中的模块之间以及与其他子系统之间的关系。\nLinux内核本身鼓励无用，是作为一个操作系统的一部分参与的，只有为一个整体时他才是一个有用的实体，下图展示了Linux操作系统的分层\n图：Linux子系统分层图 Source：https://docs.huihoo.com/linux/kernel/a1/index.html\n由图可以看出Linux操作系统由四部分组成：\n用户应用 OS服务，操作系统的一部分（例如shell）内核编程接口等 内核 硬件控制器，CPU、内存硬件、硬盘和NIC等都数据这部分 Linux内核阐述 Linux内核将所有硬件抽象为一致的接口，为用户进程提供了一个虚拟接口，使用户无需知道计算机上安装了哪些物理硬件即可编写进程，并且Linux支持用户进程的多任务处理，每个进程都可以视作为操作系统的唯一进程独享硬件资源。内核负责维护多个用户进程，并协调其对硬件资源的访问，使得每个进程都可以公平的访问资源，并保证进程间安全。\nLinux内核主要为五个子系统组成：\n进程调度器(SCHED)， 控制进程对 CPU 的访问。调度程序执行策略，确保进程可以公平地访问 CPU。 内存管理器 (MM)， 允许多个进程安全地共享操作系统的内存 虚拟文件系统 (VFS)，向所有设备提供通用文件接口来抽象出各种硬件设备 网络接口 (NET)，提供对多种网络标准与各种网络硬件的访问 进程间通信 (IPC)，在单个操作系统上的多种机制进程间通信机制 网络子系统架构 [2] 网络子系统功能主要是允许 Linux 系统通过网络连接到其他系统。支持多种硬件设备，以及可以使用的多种网络协议。网络子系统抽象了这两个实现细节，以便用户进程和其他内核子系统可以访问网络，而不必知道使用什么物理设备或协议。\n子系统模块包含\n网络设备驱动层 (Network device drivers)，网络设备驱动程序与硬件设备通信。每个硬件设备都有对应的设备驱动程序模块。 独立设备接口层(device independent interface)，设备独立接口提供了所有硬件设备的统一视图，因此在网络子系统之上的级别无需了解硬件信息 网络协议层 (network protocol)，网络协议实现了网络传输的协议 协议独立/无关接口层 (protocol independent interface)，提供了独立于硬件设备的网络接口，为内核内其他子系统访问网络时不依赖特定的协议和硬件接口。 系统调用层 (system call) 用于限制用户进程导出资源的访问 网络子系统的结构图如下图所示，\n图：网络子系统中的上下文 Source：https://docs.huihoo.com/linux/kernel/a1/index.html\n当网络子系统转换为网络栈时，如下图所示\n图：ISO Stack与TCP/IP Stack Source：https://www.washington.edu/R870/Networking.html\n当然Linux网络子系统是类似于TCP/IP栈的一种结构，当发生一个网络传输时，数据包会按照所经过的层进行封装。例如应用层应用提供了REST API，那么应用将要传输的数据封装为HTTP协议，然后传递给向下的传输层。传输层是TCP协议就会被添加对应的TCP包头。整个封装过程原始包保持不变，会根据所经过层的不同增加固定格式的包头。\n图：数据包传输在每层被封装的过程 Source：http://www.embeddedlinux.org.cn/linux_net/0596002556/understandlni-CHP-13-SECT-1.html\n对于Linux来说TCP/IP 的五层结构则是构成网络子系统的的核心组件，下图是Linux网络栈结构图\n图：Linux网络栈的结构图 Source：https://medium.com/geekculture/linux-networking-deep-dive-731848d791c0\n图中橙色部分是位于TCP/IP的五层结构中的应用层，应用层向下通讯通过 system call 与 socket接口进行交互 蓝色部分是位于内核空间，socket向下则是传输层与网络层 最底层是物理层包含网卡驱动与NIC 通过图可以看出，NIC是发送与接收数据包的基本单位，当系统启动时内核通过驱动程序向操作系统注册网卡，当数据包到达网卡时，被放入队列中。内核通过硬中断，运行中断处理程序，为网络帧分配内核数据结构(sk_buff)，并将其拷贝到缓冲区中，此为内核与网卡交互的过程。\n网卡硬中断只处理网卡核心数据的读取或发送，网络协议栈中的大部分处理都在软中断中进行处理。内核协议栈将从缓冲区中取出网络帧，通过网络协议栈，从下到上的根据网络栈结构逐层处理这个网络帧。\nSocket [4] Unix Socket是一种使用了Unix文件描述符的IPC机制，在网络栈中是位于内核空间网络栈的一层，是一个用户空间与传输层之间的一个接口，可以为网络连接, 文本文件, 终端或其他；他的行为很像一个文件描述符，因为信息的读写，read(), write()与文件的方式很相似。下图是socket通信模型。\n图：socket通信模型 Source：https://slideplayer.com/slide/10740698/\n作为用户空间到内核空间的第一层，Socket位于两层之间，由于IPC机制支持不同的通讯协议以及需要对不同的网络协议进行访问，故这些协议实现为位于socket的层，这种情况下，用户空间仅通过系统调用socket接口，而内核空间负责一些其他工作，例如，缓冲区管理，标准协议接口，网络接口与各种不同的网络协议。\nNotes:\n/etc/protocols 定义的协议号 /etc/services 定义的服务的端口号 网络栈的工作原理 当网络包到达时，网卡（硬中断+DMA）通过DMA将网络数据包放入队列中，告知中断程序硬中断已收到网络数据包。\n数据包的发送 用户程序发送网络包时，通过网络栈模型自上而下逐层处理帧：\n应用层：通过系统调用，调用socket API发送网络包，会被限制在内核空间的socket层，socket层将数据包放入到缓冲区内。 传输层：网络栈从socket取出数据包，传输层添加TCP标头 网络层：将IP添加到数据标头，根据MTU大小分片 数据链路层：MAC地址寻址，并添加到帧头尾，将帧放入发送队列，触发软中断通知 物理层：网卡驱动通过DMA从发送队列读取网络帧，通过网卡发送出 数据包的接收 内核网络栈从缓冲区读取帧，通过网络栈模型自下而上逐层处理帧：\n数据链路层： 检查数据包的有效性 确定网络协议类型 IPV4 or IPV6 去除帧 头, 尾 网络层： 取出IP头，确定网络流量的方向（转发或者本机流量） 删除标头，传递给传输层 传输层：取出TCP/UDP协议头，根据源IP, 目的IP, 源端口, 目的端口作为标识找到socket，将数据报文放置socket缓冲区 应用层：应用程序通过socket来读数据 下图为网络栈收/发数据的结构图\n图：Linux网络进程接收网络数据包流程图 Source：https://slideplayer.com/slide/10740698/\n网络子系统分层结构 在了解了网络接受网络数据包的流程后，还需要对网络子系统中分层结构进行了解，在该结构中将需要基础掌握一些对于工作与网络子系统中的API的命令是如何调用的。\n下图是结合 《深入理解Linux网络技术内幕》第13章 [3] 中插图13-2与 托马斯格拉夫发表于2019年的文章 \u0026ldquo;How to Make Linux Microservice-Aware with Cilium and eBPF\u0026rdquo; [5] 的结合旨在让零基础同学可以更好的了解到各API的分层调用\n图：Linux网络子系统分层调用\n图中可以看出，是一个基于TCP/IP栈的调用模型，其中应用层包含了常用的工具：\n配置IP路由：ip ip防火墙（包过滤）：iptables 流量整形：tc 网络抓包：tcpdump 网卡信息：ethtool 对于云原生网络中，了解完整的分层是非常重要的，这将有利于开发基于eBPF服务。下面就简单的论证下该图\n正如图中所示，所有的网络命令都是提供给用户的用户空间API，当发生网络动作时是需要通过内核将数据导入/出，这里使用了系统调用，调用内核提供的导入到用户空间的接口，例如 socket，sysctl 等，更多的接口介绍可以详见《深入理解Linux网络技术内幕》第3章 [6]\n到达socket后，继续向下通信时，socket提供了几种级别的接口，这些可以在常见编程语言包中被提供\nAE_PACKAGE / PE_PACKAGE：提供设备级别的API，通俗来讲，就是在网络层之下发送/接受消息的接口，工作于2层，这将允许用户在用户空间实现物理层数据包发送和接收 AF_INET / PE_INET：是基于网络层Socket类型，AF_INET是指IPv4，AF_INET6 是IPv6，这里就是IP 地址和端口号。 如图所示，对于 PE_PACKAGE 套接字类型而言，Linux在链路层捕捉帧并将其注入至链路层的方式，这样跳过了所有的中间层，例如 tcpdump 与 ethtool， PE_PACKAGE 套接字通过将帧直接交给 dev_queue_xmit。\ndev_queue_xmit 是传输 buffer (sk_buff) 到网络设备中的函数，将封包传递给TC或QoS层，L3封包时调用 接下来是iptables，netfilter，是工作与多层协议栈中一系列hook，用户端由命令行工具iptables/nftables控制，可以在数据包经由的数据点上被调用对应的hook函数来改变包的行为。所有的数据包都独立存在于对应的协议栈，经过的数据包会便利所有对应的hook，因为iptables(etables)支持工作于L2的ARP协议。所有的hook都存在与每个网络名称空间内，并且每个网络设备都拥有ingress hook，这也是云原生网络中提到的为什么使用eBPF 跳过netfilter框架可以提升网络性能。\n图：Linux 栈中经由netfilter框架示意图 Source：http://www.embeddedlinux.org.cn/linux_net/0596002556/understandlni-CHP-18-SECT-1.html\n接下来就是传统的一些应用，例如telnet，ping都是使用了AE_PACKAGE / PE_PACKAGE 传统联网模式\n最后一个点就是 traffic control TC，是工作与L2的一组队列与其机制组成的，通常情况下是一个队列，上面也提到，所有的设备都是使用队列来调度底层设备进入的数据包，liunx中默认的队列是 qdisc 。\nReference ​[1] Conceptual Architecture of the Linux Kernel ​ [2] Linux — Networking Deep Dive\n​[3] Network Stack Chapter13\n​[4] User Datagram Protocol (UDP) and IP Fragmentation\n​[5] How to Make Linux Microservice-Aware with Cilium and eBPF\n​[6] Network Stack Chapter13\n","permalink":"https://www.161616.top/network-stack/","summary":"Linux 架构概述 [1] 本章节简单阐述Linux系统的结构，并讨论子系统中的模块之间以及与其他子系统之间的关系。\nLinux内核本身鼓励无用，是作为一个操作系统的一部分参与的，只有为一个整体时他才是一个有用的实体，下图展示了Linux操作系统的分层\n图：Linux子系统分层图 Source：https://docs.huihoo.com/linux/kernel/a1/index.html\n由图可以看出Linux操作系统由四部分组成：\n用户应用 OS服务，操作系统的一部分（例如shell）内核编程接口等 内核 硬件控制器，CPU、内存硬件、硬盘和NIC等都数据这部分 Linux内核阐述 Linux内核将所有硬件抽象为一致的接口，为用户进程提供了一个虚拟接口，使用户无需知道计算机上安装了哪些物理硬件即可编写进程，并且Linux支持用户进程的多任务处理，每个进程都可以视作为操作系统的唯一进程独享硬件资源。内核负责维护多个用户进程，并协调其对硬件资源的访问，使得每个进程都可以公平的访问资源，并保证进程间安全。\nLinux内核主要为五个子系统组成：\n进程调度器(SCHED)， 控制进程对 CPU 的访问。调度程序执行策略，确保进程可以公平地访问 CPU。 内存管理器 (MM)， 允许多个进程安全地共享操作系统的内存 虚拟文件系统 (VFS)，向所有设备提供通用文件接口来抽象出各种硬件设备 网络接口 (NET)，提供对多种网络标准与各种网络硬件的访问 进程间通信 (IPC)，在单个操作系统上的多种机制进程间通信机制 网络子系统架构 [2] 网络子系统功能主要是允许 Linux 系统通过网络连接到其他系统。支持多种硬件设备，以及可以使用的多种网络协议。网络子系统抽象了这两个实现细节，以便用户进程和其他内核子系统可以访问网络，而不必知道使用什么物理设备或协议。\n子系统模块包含\n网络设备驱动层 (Network device drivers)，网络设备驱动程序与硬件设备通信。每个硬件设备都有对应的设备驱动程序模块。 独立设备接口层(device independent interface)，设备独立接口提供了所有硬件设备的统一视图，因此在网络子系统之上的级别无需了解硬件信息 网络协议层 (network protocol)，网络协议实现了网络传输的协议 协议独立/无关接口层 (protocol independent interface)，提供了独立于硬件设备的网络接口，为内核内其他子系统访问网络时不依赖特定的协议和硬件接口。 系统调用层 (system call) 用于限制用户进程导出资源的访问 网络子系统的结构图如下图所示，\n图：网络子系统中的上下文 Source：https://docs.huihoo.com/linux/kernel/a1/index.html\n当网络子系统转换为网络栈时，如下图所示\n图：ISO Stack与TCP/IP Stack Source：https://www.washington.edu/R870/Networking.html\n当然Linux网络子系统是类似于TCP/IP栈的一种结构，当发生一个网络传输时，数据包会按照所经过的层进行封装。例如应用层应用提供了REST API，那么应用将要传输的数据封装为HTTP协议，然后传递给向下的传输层。传输层是TCP协议就会被添加对应的TCP包头。整个封装过程原始包保持不变，会根据所经过层的不同增加固定格式的包头。\n图：数据包传输在每层被封装的过程 Source：http://www.embeddedlinux.org.cn/linux_net/0596002556/understandlni-CHP-13-SECT-1.html\n对于Linux来说TCP/IP 的五层结构则是构成网络子系统的的核心组件，下图是Linux网络栈结构图\n图：Linux网络栈的结构图 Source：https://medium.com/geekculture/linux-networking-deep-dive-731848d791c0\n图中橙色部分是位于TCP/IP的五层结构中的应用层，应用层向下通讯通过 system call 与 socket接口进行交互 蓝色部分是位于内核空间，socket向下则是传输层与网络层 最底层是物理层包含网卡驱动与NIC 通过图可以看出，NIC是发送与接收数据包的基本单位，当系统启动时内核通过驱动程序向操作系统注册网卡，当数据包到达网卡时，被放入队列中。内核通过硬中断，运行中断处理程序，为网络帧分配内核数据结构(sk_buff)，并将其拷贝到缓冲区中，此为内核与网卡交互的过程。","title":"Linux网络栈"},{"content":"Overview [1] 协议数据单元 Protocol Data Unit (PDU) 是应用于OSI模型中的数据结构，在OSI模型中每一层都会被添加一个header，tailer进行封装，header, tailer加原始报文的组合就是PDU。\n在每层中，PDU的名称都是不同的，这也是很多人的疑问，一会数据报文称为数据包，一会数据报文成为数据帧，该文介绍网络中的单元，以了解之间的区别\n物理层 物理层数据的呈现方式是以 “位” (bit) 为单位的，即0 1，在该层中数据以二进制形式进行传输\n数据链路层 [2] 到达数据链路层，实际上可以说进入了TCP/IP栈对底层，而该层的单位为 ”帧“ (frame)，该层中，MAC地址会被封装到数据包中，比如以太网帧，PPP帧都是指该层的数据包\n该层中数据帧包含：\n源MAC 目的MAC 数据，由网络层给出的 数据的总长度 校验序列 网络层 [3] 在网络层中协议数据单元被称为数据 “包\u0026quot; (package) ，是网络间节点通讯的基本单位。该层中IP地址会被封装到数据包内。\n该层中数据包包含：\n标头：源IP，目的IP，协议，数据包编号，帮助数据包匹配网络的位 payload：数据包的主体 标尾：包含几个位，用于告知已到达数据包的末尾与错误检查（循环冗余检查 (CRC)） 图：数据包组成 Source：https://computer.howstuffworks.com/question525.htm\n例如一个电子邮件，假设电子邮件大小尾3500bit，发送时使用1024的固定大小数据包进行发送，那么每个数据包标头为 96bits，标尾为 32bit，剩余 896bits 将用于实际的数据大小。这里为3500bits，会被分为4个数据包，前三个数据包为 896bits，最后一个数据包大小为 812bits。接收端会根据包编号进行解包重组\n传输层 Segment 在传输层TCP协议的协议数据单元被称为 ”段“ (Segment) ，上面讲到，IP数据包会以固定大小的数据包进行发送，如果超出大小的会被划分为多个数据包，每个数据包的碎片就被称之为Segment。\n数据包分割通常会发生在该层，当发生下列场景时会需要分段\n数据包大于网络支持的最大传输单元 (MTU) 网络不可靠，将数据包分为更小的包 datagram [4] 在传输层UDP协议的协议数据单元被称为 ”数据报“ (datagram) ，datagram是一种逐层增加的设计，用于无连接通讯\n下图是一个UDP数据报被封装位一个IP数据包：IPv4字段值位17 表示udp协议\n图：udp的IP包 Source：https://notes.shichao.io/tcpv1/ch10\n对于udp数据报的组成包含header与payload，udp的header大小为固定的8字节\n源端口：可选 目的端口：识别接收信息的进程 Length：udp header + udp payload的长度，最小值为8 checksum：与lenght一样其实是多余的，因为第三层包含了这两个信息 图：udp数据报组成 Source：https://notes.shichao.io/tcpv1/ch10\nNotes：使用UDP时应注意避免分段，例如帧中MTU为 1500 字节，假设 IPv4 header为 20 字节，UDP header 为 8 字节，则应用程序最多为数据留下 1472 字节以避免碎片\n数据 对于传输层之上，协议数据单元没有特定的名词，可以统称为协议数据单元或者数据，整个PDU分层结构图如下图所示，其中 T 表示 Trailer，H 表示 Header，通常H包含源地址和目的地址及一些用于管理通信的控制信息。T含错误检查之类的信息或标志 PDU 结束的字段。\n图：PDU分层结构图 Source：http://www.telecomworld101.com/Intro2dcRev2/page108.html\nNotes：每层的数据字段都由上一层PDU组成，通常情况下，每层只知道自己该层的信息，如网络层仅知道对端网络层，而不知道数据链路层或传输层\nReference ​[1] Protocol Data Unit\n​[2] difference between segments packets and frames\n​[3] What is a packet?\n​[4] User Datagram Protocol (UDP) and IP Fragmentation\n","permalink":"https://www.161616.top/network-unit-in-osi/","summary":"Overview [1] 协议数据单元 Protocol Data Unit (PDU) 是应用于OSI模型中的数据结构，在OSI模型中每一层都会被添加一个header，tailer进行封装，header, tailer加原始报文的组合就是PDU。\n在每层中，PDU的名称都是不同的，这也是很多人的疑问，一会数据报文称为数据包，一会数据报文成为数据帧，该文介绍网络中的单元，以了解之间的区别\n物理层 物理层数据的呈现方式是以 “位” (bit) 为单位的，即0 1，在该层中数据以二进制形式进行传输\n数据链路层 [2] 到达数据链路层，实际上可以说进入了TCP/IP栈对底层，而该层的单位为 ”帧“ (frame)，该层中，MAC地址会被封装到数据包中，比如以太网帧，PPP帧都是指该层的数据包\n该层中数据帧包含：\n源MAC 目的MAC 数据，由网络层给出的 数据的总长度 校验序列 网络层 [3] 在网络层中协议数据单元被称为数据 “包\u0026quot; (package) ，是网络间节点通讯的基本单位。该层中IP地址会被封装到数据包内。\n该层中数据包包含：\n标头：源IP，目的IP，协议，数据包编号，帮助数据包匹配网络的位 payload：数据包的主体 标尾：包含几个位，用于告知已到达数据包的末尾与错误检查（循环冗余检查 (CRC)） 图：数据包组成 Source：https://computer.howstuffworks.com/question525.htm\n例如一个电子邮件，假设电子邮件大小尾3500bit，发送时使用1024的固定大小数据包进行发送，那么每个数据包标头为 96bits，标尾为 32bit，剩余 896bits 将用于实际的数据大小。这里为3500bits，会被分为4个数据包，前三个数据包为 896bits，最后一个数据包大小为 812bits。接收端会根据包编号进行解包重组\n传输层 Segment 在传输层TCP协议的协议数据单元被称为 ”段“ (Segment) ，上面讲到，IP数据包会以固定大小的数据包进行发送，如果超出大小的会被划分为多个数据包，每个数据包的碎片就被称之为Segment。\n数据包分割通常会发生在该层，当发生下列场景时会需要分段\n数据包大于网络支持的最大传输单元 (MTU) 网络不可靠，将数据包分为更小的包 datagram [4] 在传输层UDP协议的协议数据单元被称为 ”数据报“ (datagram) ，datagram是一种逐层增加的设计，用于无连接通讯\n下图是一个UDP数据报被封装位一个IP数据包：IPv4字段值位17 表示udp协议\n图：udp的IP包 Source：https://notes.shichao.io/tcpv1/ch10\n对于udp数据报的组成包含header与payload，udp的header大小为固定的8字节\n源端口：可选 目的端口：识别接收信息的进程 Length：udp header + udp payload的长度，最小值为8 checksum：与lenght一样其实是多余的，因为第三层包含了这两个信息 图：udp数据报组成 Source：https://notes.","title":"为什么网络是分层的"},{"content":"eBPF介绍 eBPF是 Extended Berkeley Packet Filter，主要是用于包过滤的。为什么叫Berkeley Packet Filter 是因为论文出自 Lawrence Berkeley Laboratory（相对的论文可以参考 [1]）。“E\u0026quot; 是使BPF不仅仅是包过滤。\neBPF 目前提供的功能不仅仅是包过滤，它是一个允许用户在操作系统内核加载自定义程序的框架，来自于 ”What Is eBPF?“\neBPF is a framework that allows users to load and run custom programs within the kernel of the operating system. That means it can extend or even modify the way the kernel behaves. [2]\neBPF验证器\n对于如果想改变Linux内核功能需要合并代码到内核或者编写内核模块。前者需要被社区接受，这需要很长一个周期；而后者可以很好的扩展内核功能，但都存在一个问题 ”==安全运行==“\n”安全运行“ 问题包含”漏洞“和“崩溃”，考虑到这些，eBPF为安全运行提供了一个非常不同的方法**：eBPF verifier** ，eBPF verifier 将确保应用只能够在安全情况下被运行。\neBPF verifier 保证了 eBPF 程序运行的 ”安全“ 和 ”验证“\n”验证“ (Verification) 是指对程序进行分析，确保无论输入时什么，都会在有限的之阵内终止。例如在解除指针时，确保指针不是空置，解除对指针引用意味着将会 “查找这个地址的值”，解引用空置，会程序崩溃，而在内核中空指针会引起整个机器崩溃。\n”安全“ (Security)是指将确保eBPF程序运行时安全的，这种场景将限制eBPF访问的内存为只能被访问的内存。例如，有一个 eBPF 程序出发在一个网络stack上，并通过内核的socket buffer，包括正在传输的数据。这里有一些特殊的辅助函数，例如 bpf_skb_load_bytes()，这个 eBPF 程序可以从套接字缓冲区中读取字节数据。与此同时，另一个由系统调用触发的 eBPF 程序，没有可用的套接字缓冲区，将不允许使用这个辅助函数（what is eBPF chapter2 [2]）。\neBPF的动态加载\n上面也提到了eBPF是一个允许用户自定义程序加载内核功能的框架，也就意味着使用内核无需对内核代码的改变，what is eBPF chapter2 中提到的eBPF动态加载，可以理解为触发器与事件，eBFP可以使程序可以动态地加载到内核中和从内核中删除。当附加到事件上的程序遇到对应事件时就会被触发。\neBPF程序\neBPF是事件驱动型程序，它允许在内核中，并挂在到对应的挂载点上，当发生系统调用，函数的进入/退出，内核tracepoints，网络事件等发生，将触发对应程序的操作。\neBPF包含两部分，eBPF程序，eBPF工具\neBPF程序，只运行在内核空间内的代码，这部分仅可以用C或者Rust编写，目前eBPF使用的C语言编写\neBPF工具，是指运行在用户空间内的代码，这部分可以使用任意编程语言编写，例如Python, Go, C 等。\n探针\n探针 (probes) 是指内核定义的扩展点，可以通过eBPF程序将其附加到对应的扩展点上。常用的有kprobe 与 uprobe\nkprobe 是跟踪内核函数调用的探针（k是kernel的前缀），例如 execve() kprobe 是跟踪用户空间程序调用的探针，例如对运行程序 nginx 状态的跟踪 程序的加载\n用户空间的程序在 eBPF验证器的允许下，可以使用 bpf() 系统调用从 ELF 文件中加载 eBPF程序到内核中；一旦将程序加载到内核时，就必须绑定到对应的 ”事件“ 上，每当事件发生时，对应的eBPF程序就会被触发。下面宝海一些常用的事件：\n函数的进入进出 Tracepoints，是Linux内核中定义的一些hook可以将 eBPF 程序附加到内核内定义的 tracepoints 中 Perf 是一个收集性能数据的子系统。可以将 eBPF 程序挂到所有收集 perf 数据的地方 Linux的安全模块 LSM 例如 SELinux 和 APPAmor 使用他们的接口，通过eBPF，可以将程序附加到对应的检查点上。 网络接口，eXpress Data Path (XDP) 允许将eBPF程序附加到网络接口上，当收到包时，会触发对应的事件，事件可以检查或者改变一个数据包。 套接字和网络钩子，当应用程序在网络套接字上打开或执行其他操作时，以及发出或收到数据包时，你可以附加运行eBPF程序。在内核网络stack中也被叫做 traffic control (TC)。 eBPF MAP\n在一些情况下，我们希望eBPF是从用户空间的应用来接收信息，或者将数据传递给用户空间的应用，允许eBPF程序与用户空间之间传递数据，或者不同的eBPF程序间传递数据的机制被称为 MAP\nMAP 是数据类型，本质上是一个 key-value 的存储，用于存储不同类型数据的通用数据结构。允许不同eBPF程序之间以及内核和用户空间应用程序之间共享数据。\nMAP用途：\n存储数据，供多eBPF程序信息协调 eBPF写入事件或指标供用户空间程序检索 用户空间配置，供eBPF程序读取并作出相应行为 eBPF在云原生 在kubernetes中，运行在机器上的Pod共享一个内核，可以通过eBPF检测该机器上运行的应用程序，将eBPF加载到内核并附加到事件之上，就会触发相关事件，而不需要考虑进程与事件的关系。\neBPF 与 sidecar\n传统的可观测性应用都使用了 sidecar 方式进行部署的，这种模式是单独部署一个与应用相同的程序到pod中。\nsidecar 模式的两大缺点：\n浪费资源**：sidecar** 容器都会消耗大量资源，取决于注入的数量 安全运行：不能确保每个运行的Pod都被注入 eBPF的隔离\neBPF 检查器可以确保 eBPF 程序只能访问它有权限的内存。检查器检查程序时不可能超出其职权范围，包括确保内存为当前进程所拥有或为当前网络包的一部分。这意味着 eBPF 代码比它周围的内核代码受到更严格的控制，内核代码不需要通过任何类型的检查器。当遇到攻击者通过容器化的应用程序部署到节点上，并且可以提权，那么该攻击程序就可以危害到同一节点上的其他应用程序。当然eBPF检查器可以避免这个问题。\neBPF的应用\neBPF 官网 ebpf.io 中介绍，在通常情况下eBPF不会被单独使用而是通过其他项目在eBPF之上提供一个用户空间工具进行使用，例如 Cilium, bcc等。\neBPF工具\n通常情况下eBPF工具都是围绕 网络(networking) ，可观测性(observability) ，安全 (security） 这三个重要方面使用eBPF功能的。\neBPF程序可以连接到网络接口和内核的网络堆栈的各个点（可以通过tracepoint实现）。在每个追踪点上，eBPF程序都可以选择接收/丢弃/操作数据包。基于这个条件下eBPF就可以实现很强大的网络功能，常见的eBPF 实现的网络功能有 负载均衡，分布式防火墙，cni。\nKatran L4 LoadBalancer, facebook开源的4层负载均衡器，使用的eBPF与C++结合的技术 Cilium eBPF Kubernetes 网络插件 eBPF Tools\nBCC\nBCC (BPF Compiler Collection) 是一组基于eBPF技术用于分析操作系统和网络性能的一组工具。主要提供了以下功能\n用于观测/追踪 在运行的Linux系统状态工具 对于 如何编写eBPF程序 eBPF 程序可以用受限的 C 来编写，使用 clang 编译器编译成 eBPF 字节码。\nNotes: 受限 C 语言是指省略了一些特性，例如循环，全局变量，可变参数函数，浮点数以及作为函数参数传递的结构。\nGo使用 libbpfgo 会包装 libbpf （一个C语言实现的系统调用库） 的系统调用 BPF() 函数，通过加载BPF对象（是通过C语言编写的BPF函数）然后绑定到对应的事件上。\neBPF程序必须使用C编写，通过clang编译为BPF code，然后Go/Python等代码去读取这个文件 xxx.o 将其插入到内核中。所以通常情况下，我们会看到是一个Go/Python或者其他语言来包裹C代码来运行的。\n一个eBPF程序的结构如下图所示，包含两部分，在用户空间运行的应用程序与运行在内核空间的eBPF程序，用户空间的应用通过系统调用 BPF() 来调用运行在内核空间内的eBPF程序的函数。\n图：eBPF program structure Source：https://files.gotocon.com/uploads/slides/conference_39/1688/original/Beginners%20guide%20to%20eBPF%20with%20Go.pdf\neBPF的加载过程如下图所示\n图：eBPF program loading flow Source：https://files.gotocon.com/uploads/slides/conference_39/1688/original/Beginners%20guide%20to%20eBPF%20with%20Go.pdf\n一个 eBPF obj 代码在用户空间通过 bpf() 系统调用加载到内核中；在内核中首先需要验证器确保eBPF程序是可以安全运行的，如果可以安全运行，将开始在BPF 虚拟机中开始运行\n图：eBPF program lifetime Source：https://files.gotocon.com/uploads/slides/conference_39/1688/original/Beginners%20guide%20to%20eBPF%20with%20Go.pdf\nhttps://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#data\nhttps://man7.org/linux/man-pages/man7/bpf-helpers.7.html\nhttps://gitlab.epfl.ch/debeule/bpf/-/blob/master/LOG.md\nhttps://github.com/lizrice/learning-ebpf/blob/main/chapter5/hello.bpf.c\nhttps://medium.com/@phylake/bottom-up-ebpf-d7ca9cbe8321\nReference ​[1] The BSD Packet Filter\n​[2] What Is eBPF?\n​[3] how are ebpf programs written\nhttps://www.youtube.com/watch?v=uBqRv8bDroc\n","permalink":"https://www.161616.top/what-is-ebpf/","summary":"eBPF介绍 eBPF是 Extended Berkeley Packet Filter，主要是用于包过滤的。为什么叫Berkeley Packet Filter 是因为论文出自 Lawrence Berkeley Laboratory（相对的论文可以参考 [1]）。“E\u0026quot; 是使BPF不仅仅是包过滤。\neBPF 目前提供的功能不仅仅是包过滤，它是一个允许用户在操作系统内核加载自定义程序的框架，来自于 ”What Is eBPF?“\neBPF is a framework that allows users to load and run custom programs within the kernel of the operating system. That means it can extend or even modify the way the kernel behaves. [2]\neBPF验证器\n对于如果想改变Linux内核功能需要合并代码到内核或者编写内核模块。前者需要被社区接受，这需要很长一个周期；而后者可以很好的扩展内核功能，但都存在一个问题 ”==安全运行==“\n”安全运行“ 问题包含”漏洞“和“崩溃”，考虑到这些，eBPF为安全运行提供了一个非常不同的方法**：eBPF verifier** ，eBPF verifier 将确保应用只能够在安全情况下被运行。\neBPF verifier 保证了 eBPF 程序运行的 ”安全“ 和 ”验证“","title":"科普ebpf"},{"content":"Visual Studio使用 离线安装包 在页面 [4] 下载安装引导命令，下载完成后使用命令（对于C++来说）\nbat 1 vs_Professional.exe --layout ‪1111 --add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended --lang en-US zh-CN 随后会触发下载，等待下载完成后，在 --layout 指定的目录上点击 vs_setup 开始离线安装。\nNote: 对于完全脱离C盘安装可以使用下面的脚本，更改变量为要安装的路径\nbat 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 :: 关闭终端回显 @echo off SET ROOT_PATH=D:\\Program Files\\Microsoft Visual Studio SET X86_PATH=%ROOT_PATH%\\Program Files (x86) SET X86_VS_PATH=%X86_PATH%\\Microsoft Visual Studio SET X86_SDK_PATH=%X86_PATH%\\Microsoft SDKs SET X86_KITS_PATH=%X86_PATH%\\Windows Kits SET X86_AV_PATH=%X86_PATH%\\Application Verifier SET X64_PATH=%ROOT_PATH%\\Program Files rem SET X64_VS_PATH=%X64_PATH%\\Microsoft Visual Studio SET X64_AV_PATH=%X64_PATH%\\Application Verifier SET X64_SQL_PATH=%X64_PATH%\\Microsoft SQL Server SET PD_PATH=%ROOT_PATH%\\ProgramData SET PD_VS_PATH=%PD_PATH%\\Microsoft\\VisualStudio SET PD_PC_PATH=%PD_PATH%\\Package Cache @echo =======link directory to %ROOT_PATH%=======: SET S_X86_SKD_PATH=C:\\Program Files (x86)\\Microsoft SDKs SET S_X86_VS_PATH=C:\\Program Files (x86)\\Microsoft Visual Studio SET S_X86_KITS_PATH=C:\\Program Files (x86)\\Windows Kits SET S_X86_AV_PATH=C:\\Program Files (x86)\\Application Verifier SET S_X64_AV_PATH=C:\\Program Files\\Application Verifier SET S_X64_SQL_PATH=C:\\Program Files\\Microsoft SQL Server SET S_PD_VS_PATH=C:\\ProgramData\\Microsoft\\VisualStudio SET S_PD_PC_PATH=C:\\ProgramData\\Package Cache pause @echo =======setting visual studio environment=======: @echo =======check directory exist=======: if not exist %ROOT_PATH% ( echo \u0026#34;%ROOT_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%ROOT_PATH%\u0026#34; ) if not exist %X86_PATH% ( echo \u0026#34;%X86_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X86_PATH%\u0026#34; ) if not exist %X86_VS_PATH% ( echo \u0026#34;%X86_VS_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X86_VS_PATH%\u0026#34; ) if not exist %X86_SDK_PATH% ( echo \u0026#34;%X86_SDK_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X86_SDK_PATH%\u0026#34; ) if not exist %X86_KITS_PATH% ( echo \u0026#34;%X86_KITS_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X86_KITS_PATH%\u0026#34; ) if not exist %X86_AV_PATH% ( echo \u0026#34;%X86_AV_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X86_AV_PATH%\u0026#34; ) if not exist %X64_PATH% ( echo \u0026#34;%X64_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X64_PATH%\u0026#34; ) if not exist %X64_AV_PATH% ( echo \u0026#34;%X64_AV_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X64_AV_PATH%\u0026#34; ) if not exist %X64_SQL_PATH% ( echo \u0026#34;%X64_SQL_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X64_SQL_PATH%\u0026#34; ) if not exist %PD_PATH% ( echo \u0026#34;%PD_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%PD_PATH%\u0026#34; ) if not exist %PD_VS_PATH% ( echo \u0026#34;%PD_VS_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%PD_VS_PATH%\u0026#34; ) if not exist %PD_PC_PATH% ( echo \u0026#34;%PD_PC_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%PD_PC_PATH%\u0026#34; ) @echo =======link directory to %ROOT_PATH%=======: :: x86 link mklink /j \u0026#34;%S_X86_SKD_PATH%\u0026#34; \u0026#34;%X86_SDK_PATH%\u0026#34; mklink /j \u0026#34;%S_X86_VS_PATH%\u0026#34; \u0026#34;%X86_VS_PATH%\u0026#34; mklink /j \u0026#34;%S_X86_KITS_PATH%\u0026#34; \u0026#34;%X86_KITS_PATH%\u0026#34; mklink /j \u0026#34;%S_X86_AV_PATH%\u0026#34; \u0026#34;%X86_AV_PATH%\u0026#34; :: x64 link mklink /j \u0026#34;%S_X64_AV_PATH%\u0026#34; \u0026#34;%X64_AV_PATH%\u0026#34; mklink /j \u0026#34;%S_X64_SQL_PATH%\u0026#34; \u0026#34;%X64_SQL_PATH%\u0026#34; :: ProgramData link mklink /j \u0026#34;%S_PD_VS_PATH%\u0026#34; \u0026#34;%PD_VS_PATH%\u0026#34; mklink /j \u0026#34;%S_PD_PC_PATH%\u0026#34; \u0026#34;%PD_PC_PATH%\u0026#34; pause VS快捷键 快捷键 含义 Ctrl + k,Ctrl + f 自动格式化代码 Ctrl + k,Ctrl + c 注释代码 Ctrl + k,Ctrl + u 取消注释代码 F9 设置断点 F5 调试运行 Ctrl + F5 不调试运行 Ctrl + Shift + b 编译，不运行 F10 next调试 F11 step调试 调试 添加行号：工具\u0026ndash;》选项 \u0026ndash;》文本编辑器\u0026ndash;》C/C++ \u0026ndash;》行号\n调试步骤\n设置断点。F5启动调试 停止（断点处）的位置，是尚未执行的指令。 逐语句执行一下条 （F11）：进入函数内部，逐条执行跟踪。 逐过程执行一下条 （F10）：不进入函数内部，逐条执行程序。 监视：调试 \u0026ndash;》窗口 \u0026ndash;》监视：输入监视变量名。自动监视变量值的变化。 VS Code使用 安装扩展 C/C++ Extension Pack，Code Runner\n调试相关快捷键：\nF5 进入调试 F9 切换断点 F10 单步跳过（逐过程执行） F11 单步执行（逐语句执行，可进入执行函数体） Shift+F5 停止调试 Ctrl+Shift+F5重启调试 Ctrl+F5 开始执行，不进入断点 Ctrl+F9 启用/停止断点 Ctrl+Shift+F9 删除全部断点 Ctrl+b 隐藏/打开侧边框 Ctrl+` 隐藏/打开terminal Ctrl+j 隐藏/打开下边框（plannel） Ctrl+Shift+D 打开侧边框 Run and Debug Ctrl+Shift+E 打开侧边框 Explorer Ctrl+Alt+N run code 配置 launch.json 根据提示，替换gcc路径即可\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。 // 欲了解更多信息，请访问: https://go.microsoft.com/fwlink/?linkid=830387 \u0026#34;version\u0026#34;: \u0026#34;0.2.0\u0026#34;, \u0026#34;configurations\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;gcc.exe - 生成和调试活动文件\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;cppdbg\u0026#34;, \u0026#34;request\u0026#34;: \u0026#34;launch\u0026#34;, \u0026#34;program\u0026#34;: \u0026#34;${fileDirname}\\\\${fileBasenameNoExtension}.exe\u0026#34;, \u0026#34;args\u0026#34;: [], \u0026#34;stopAtEntry\u0026#34;: false, \u0026#34;cwd\u0026#34;: \u0026#34;${workspaceFolder}\u0026#34;, \u0026#34;environment\u0026#34;: [], \u0026#34;externalConsole\u0026#34;: false, \u0026#34;MIMode\u0026#34;: \u0026#34;gdb\u0026#34;, \u0026#34;miDebuggerPath\u0026#34;: \u0026#34;D:\\Program Files\\mingw64\\bin\\gdb.exe\u0026#34;, \u0026#34;setupCommands\u0026#34;: [ { \u0026#34;description\u0026#34;: \u0026#34;为 gdb 启用整齐打印\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;-enable-pretty-printing\u0026#34;, \u0026#34;ignoreFailures\u0026#34;: true } ], \u0026#34;preLaunchTask\u0026#34;: \u0026#34;C/C++: gcc.exe build active file\u0026#34; } ] } ","permalink":"https://www.161616.top/ch0-ide/","summary":"Visual Studio使用 离线安装包 在页面 [4] 下载安装引导命令，下载完成后使用命令（对于C++来说）\nbat 1 vs_Professional.exe --layout ‪1111 --add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended --lang en-US zh-CN 随后会触发下载，等待下载完成后，在 --layout 指定的目录上点击 vs_setup 开始离线安装。\nNote: 对于完全脱离C盘安装可以使用下面的脚本，更改变量为要安装的路径\nbat 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 :: 关闭终端回显 @echo off SET ROOT_PATH=D:\\Program Files\\Microsoft Visual Studio SET X86_PATH=%ROOT_PATH%\\Program Files (x86) SET X86_VS_PATH=%X86_PATH%\\Microsoft Visual Studio SET X86_SDK_PATH=%X86_PATH%\\Microsoft SDKs SET X86_KITS_PATH=%X86_PATH%\\Windows Kits SET X86_AV_PATH=%X86_PATH%\\Application Verifier SET X64_PATH=%ROOT_PATH%\\Program Files rem SET X64_VS_PATH=%X64_PATH%\\Microsoft Visual Studio SET X64_AV_PATH=%X64_PATH%\\Application Verifier SET X64_SQL_PATH=%X64_PATH%\\Microsoft SQL Server SET PD_PATH=%ROOT_PATH%\\ProgramData SET PD_VS_PATH=%PD_PATH%\\Microsoft\\VisualStudio SET PD_PC_PATH=%PD_PATH%\\Package Cache @echo =======link directory to %ROOT_PATH%=======: SET S_X86_SKD_PATH=C:\\Program Files (x86)\\Microsoft SDKs SET S_X86_VS_PATH=C:\\Program Files (x86)\\Microsoft Visual Studio SET S_X86_KITS_PATH=C:\\Program Files (x86)\\Windows Kits SET S_X86_AV_PATH=C:\\Program Files (x86)\\Application Verifier SET S_X64_AV_PATH=C:\\Program Files\\Application Verifier SET S_X64_SQL_PATH=C:\\Program Files\\Microsoft SQL Server SET S_PD_VS_PATH=C:\\ProgramData\\Microsoft\\VisualStudio SET S_PD_PC_PATH=C:\\ProgramData\\Package Cache pause @echo =======setting visual studio environment=======: @echo =======check directory exist=======: if not exist %ROOT_PATH% ( echo \u0026#34;%ROOT_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%ROOT_PATH%\u0026#34; ) if not exist %X86_PATH% ( echo \u0026#34;%X86_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X86_PATH%\u0026#34; ) if not exist %X86_VS_PATH% ( echo \u0026#34;%X86_VS_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X86_VS_PATH%\u0026#34; ) if not exist %X86_SDK_PATH% ( echo \u0026#34;%X86_SDK_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X86_SDK_PATH%\u0026#34; ) if not exist %X86_KITS_PATH% ( echo \u0026#34;%X86_KITS_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X86_KITS_PATH%\u0026#34; ) if not exist %X86_AV_PATH% ( echo \u0026#34;%X86_AV_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X86_AV_PATH%\u0026#34; ) if not exist %X64_PATH% ( echo \u0026#34;%X64_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X64_PATH%\u0026#34; ) if not exist %X64_AV_PATH% ( echo \u0026#34;%X64_AV_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X64_AV_PATH%\u0026#34; ) if not exist %X64_SQL_PATH% ( echo \u0026#34;%X64_SQL_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%X64_SQL_PATH%\u0026#34; ) if not exist %PD_PATH% ( echo \u0026#34;%PD_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%PD_PATH%\u0026#34; ) if not exist %PD_VS_PATH% ( echo \u0026#34;%PD_VS_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%PD_VS_PATH%\u0026#34; ) if not exist %PD_PC_PATH% ( echo \u0026#34;%PD_PC_PATH%目录不存在，已创建该目录！\u0026#34; md \u0026#34;%PD_PC_PATH%\u0026#34; ) @echo =======link directory to %ROOT_PATH%=======: :: x86 link mklink /j \u0026#34;%S_X86_SKD_PATH%\u0026#34; \u0026#34;%X86_SDK_PATH%\u0026#34; mklink /j \u0026#34;%S_X86_VS_PATH%\u0026#34; \u0026#34;%X86_VS_PATH%\u0026#34; mklink /j \u0026#34;%S_X86_KITS_PATH%\u0026#34; \u0026#34;%X86_KITS_PATH%\u0026#34; mklink /j \u0026#34;%S_X86_AV_PATH%\u0026#34; \u0026#34;%X86_AV_PATH%\u0026#34; :: x64 link mklink /j \u0026#34;%S_X64_AV_PATH%\u0026#34; \u0026#34;%X64_AV_PATH%\u0026#34; mklink /j \u0026#34;%S_X64_SQL_PATH%\u0026#34; \u0026#34;%X64_SQL_PATH%\u0026#34; :: ProgramData link mklink /j \u0026#34;%S_PD_VS_PATH%\u0026#34; \u0026#34;%PD_VS_PATH%\u0026#34; mklink /j \u0026#34;%S_PD_PC_PATH%\u0026#34; \u0026#34;%PD_PC_PATH%\u0026#34; pause VS快捷键 快捷键 含义 Ctrl + k,Ctrl + f 自动格式化代码 Ctrl + k,Ctrl + c 注释代码 Ctrl + k,Ctrl + u 取消注释代码 F9 设置断点 F5 调试运行 Ctrl + F5 不调试运行 Ctrl + Shift + b 编译，不运行 F10 next调试 F11 step调试 调试 添加行号：工具\u0026ndash;》选项 \u0026ndash;》文本编辑器\u0026ndash;》C/C++ \u0026ndash;》行号","title":"ch0 ide"},{"content":"C语言关键字 [1] ==C语言有32个关键字==\nauto：定义自动变量，主要是声明变量的生存周期 break, continue : break 语句在遇到最内层循环时立即终止。还用于终止 switch 语句。 case, switch, default：使用 switch 和 case 语句声明一个switch分支 char：用于声明character 类型的变量 const：声明常量 do\u0026hellip;while： double： double-precision 浮点数变量类型 float：single-precision 浮点数的变量类型 if, else：声明if/else 条件判断 enum：用于声明枚举类型 extern：关键字声明变量或函数在其声明的文件之外具有外部链接。 for：C 语言的三种循环之一，for循环 goto： 用于将程序的控制权转移到指定的标签 int：声明 integer 类型的变量 short, long, signed, unsigned：是类型修饰符，它们改变基本数据类型的含义以产生新类型。 short int： -32768 to 32767 long int： -2147483648 to 214743648 signed int： -32768 to 32767 unsigned int： 0 to 65535 return： 终止函数并返回值 sizeof：评估变量或常量的大小 register：创建比普通变量快得多的寄存器变量。 static：创建一个静态变量。静态变量的值持续到程序结束。 struct：用于声明结构体。结构体可以包含不同类型的变量。 typedef：用于将类型与标识符显式关联。 union：用于将不同类型的变量分组在一个名称下。 void：没有任何意义，函数修饰为没有返回值，参数修饰为没有参数 volatile：提醒编译器它后面所定义的变量随时都有可能改变 C语言控制语句 ==C语言有9种控制语句== (control statements)\nIf..else for while do..while continue break switch goto return C语言运算符 [2] ==C语言有45种运算符== (operator)\n算数运算符 (Arithmetic Operators) ：+, -，*，/，% 赋值运算符 (Assignment Operators) ：=，+=，-=，*=，/=，%= 关系运算符 (Relational Operators)：==，\u0026gt; ，\u0026lt;，!= ，\u0026gt;=，\u0026lt;= 逻辑运算符 (Logical Operators)：\u0026amp;\u0026amp;，||，! 位运算符 (Bitwise Operators)：\u0026amp;，|，^，~，\u0026lt;\u0026lt;，\u0026gt;\u0026gt; 逗号运算符 (Comma Operator)：链接相关表达式 ，int a, c = 5, d; sizeof运算符(sizeof operator)：一元运算符，它返回数据的大小（常量、变量、数组、结构） 杂项运算符（）：\u0026amp; 取址，* 取指针，?: 二元条件表达式 GCC编译四部曲 [3] 预处理 (Preprocessing)：在预处理步骤，将生成一个扩展名为 .i 的文件；使用命令 gcc -E file.c 操作 头文件展开，不检查语法错误，将展开所有头（include）文件（任意） 宏定义替换 删除注释 展开条件编译，根据条件来展开指令 编译 (Compilation) ：会生成一个扩展名为 .s 的文件，命令是：gcc -S file.c 检查语法错误 将文件翻译成汇编语言 汇编 (Assembler)：将汇编代码转换为纯二进制代码或机器代码（零和一）。此代码也称为目标代码；将生成一个带有 .o 扩展名的文件：gcc -c file.c 链接 (Linker)：链接是编译的最后一步。链接器将来自多个模块的所有目标代码合并为一个，如果使用了库也会引用。这个步骤也是包含前三个步骤的。gcc file.o -o hello.exe 接收由汇编步骤生成的 .o 扩展名文件 数据地址回填 数据段合并 库引入 变量 变量 (variables) 是用于存储数据的内存位置名称，可以改变的内容\n变量的命名规则 不能以数字开头 由数字、字母，甚至是下划线 (_) 等特殊符号组成 变量名不能是任何关键字 变量名中不能有空格或空白 变量名是==区分大小写的== 变量的数据类型 C 语言中数据类型主要包含以下类型\n变量类型 实际代表名称 描述 用途 char Character 代表1bytes(8bit)，是以单引号引起的字符 通常以单个字母的形式使用X、r等，或 ASCII 字符集。 int Integer 自然整数 用来存储整数，如 4, 300, 8000 \u0026hellip; float Floating- Point 单精度浮点数 表示实数值或小数值（7位小数），例如 20.8, 18.56 \u0026hellip; double Double 双精度浮点值 比float类型要大 4bytes,允许15位小数 void Void 表示没有类型。 这种数据类型是为了用于修饰没有意义函数或变量，如函数用其修饰标识没有返回值，参数用其修饰表示没有参数。 变量声明与定义 变量的定义 (Declaration)：告诉编译器应为变量创建多少存储空间或者在哪里创建存储空间（借助于数据类型） **变量声明 **(Definition)：只声明不赋值的变量叫做变量定义， int a 定义与声明的区别：\n变量定义会开辟内存空间。变量声明不会开辟内存空间 变量要想使用必须有定义 声明指示编译器存在变量，而定义表示编译器为变量创建的存储位置和存储量 变量的分类 全局变量 (global) ：在块或函数之外声明的变量称为全局变量 局部变量 (Local)：在块或函数中声明的一种变量 静态变量 (static)：使用 static 关键字声明的变量。该变量在各种函数调用之间保留给定值 自动变量 (auto)：变量具有自动存储期，程序在进入该变量声明所在的块时变量存在，程序在退出该块时变量消失 外部变量 (extren)：能够在多个源文件中共享一个变量 extern int a=10; 变量的数据大小 [6] C 编程语言有两种基本数据类型：基本与衍生\n类型 范围 大小（以字节为单位） 格式化符号 unsigned char 0 ~ 255 1 %c signed char/char -128 ~ +127 1 %c unsigned int 0 ~ 65535 2 %u signed int or int -32,768 ~ +32767 2 %d unsigned short int 0~ 65535 2 %hu signed short int/short int -32,768 ~ +32767 2 %hd unsigned long int 0 ~ +4,294,967,295 4 %lu signed long int/long int -2,147,483,648 ~ 2,147,483,647 4 %ld long long int -(2^63) to (2^63)-1 8 %lld unsigned long long int 0 to 18,446,744,073,709,551,615 8 %llu float [5] 7位精度 4 %f double [5] 15位精度 8 %lf 变量类型 C语言中根据变量的声明周期和范围可以被分为两种类型 局部变量和全局变量与静态变量\n局部变量 局部变量 (local variables) 被声明在函数内部，只要函数存在，它们就只存在于内存中，直到函数结束，局部变量就会消失！\n例如创建一个局部变量 a，a在函数运行时被创建在stack段中，当函数foo() 结束，被释放，故下列代码编译错误。\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include \u0026lt;stdio.h\u0026gt; void\tfoo(void) { int\ta; a = 10; printf(\u0026#34;Foo function: Variable a = %d\\n\u0026#34;, a); } // the variable \u0026#39;a\u0026#39; ceases to exist in RAM here. int\tmain(void) { foo(); printf(\u0026#34;Main: Variable a = %d\\n\u0026#34;, a); // ERROR : main does not know any variable named \u0026#39;a\u0026#39;! return (0); } Notes：函数的参数也是局部变量，如果需要外部更改，则通过指针方式传递进去\n全局变量 全局变量 (global variables) 是指在函数外部声明的变量；全局变量随函数生命周期结束时消失，因为全局变量被存储在内存结构的data分段中，是属于二进制文件本身的。另外==默认情况下，未被赋值的全局变量会被初始化为 0==。\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;stdio.h\u0026gt; int\ta; // Global variable initialized to 0 by default void\tfoo(void) { a = 42; // Global variable accessible without // having been declared in the function printf(\u0026#34;Foo: a = %d\\n\u0026#34;, a); // a == 42 } int\tmain(void) { printf(\u0026#34;Main: a = %d\\n\u0026#34;, a); // a == 0 foo(); printf(\u0026#34;Main: a = %d\\n\u0026#34;, a); // a == 42 a = 200; printf(\u0026#34;Main: a = %d\\n\u0026#34;, a); // a == 200 return (0); } Notes：局部变量的作用域高于全局变量，如果同名会被覆盖\n全局变量的作用域\n如果想在一个文件中使用另一个文件中定义的全局变量，需要使用关键字 ”extern“ 再次声明。这代表告诉编译器正在声明我们在程序文件的其他地方定义的变量。\n例如下面代码中，main.c 中，使用 extern 关键字声明全局变量，表示我们在其他地方定义了这个变量。并做了 foo() 函数原型的声明。并在 foo.c 文件中，定义了全局变量 a 及 foo() 函数\nmain.c\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include \u0026lt;stdio.h\u0026gt; extern int\ta; // 在其他文件内定义的全局变量 void foo(void);\t// 定义在其他方面的函数，这种写法等同于extern void foo(void); int\tmain(void) { printf(\u0026#34;Main: a = %d\\n\u0026#34;, a); // a == 100 foo(); printf(\u0026#34;Main: a = %d\\n\u0026#34;, a); // a == 42 a = 200; printf(\u0026#34;Main: a = %d\\n\u0026#34;, a); // a == 200 return (0); } void.c\nc 1 2 3 4 5 6 7 8 9 #include \u0026lt;stdio.h\u0026gt; int\ta = 100; // 全局变量的定义 void foo(void) { a = 42; printf(\u0026#34;Foo: a = %d\\n\u0026#34;, a); // a == 42 } 输出结果为\nc 1 2 3 4 Main: a = 100 Foo: a = 42 Main: a = 42 Main: a = 200 也可以使用头文件来定义，这种方式比上面的更好，示例只是说明全局变量\n静态变量 静态变量是指使用关键字 “static\u0026quot; 修饰的变量，静态变量可以分为 静态全局变量 与 静态局部变量 ，静态变量默认是全局的，因为他存储的地方是data区而不是堆，栈中。\n静态变量有两点区分与全局变量：\n在函数内部定义的静态变量是这个函数的全局变量（第一个结束的括号） 在函数外声明的静态变量仅在这个声明他的文件内有效。 下面代码会编译异常，因为b生命周期存在与for循环中\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; void test() { static int a = 10; for(int i=0; i\u0026lt;10; i++) { static int b = 10; a++; b++; } printf(\u0026#34;value a is %d\\n\u0026#34;,a); printf(\u0026#34;value b is %d\\n\u0026#34;,b); } void main() { test(); } 局部静态变量 局部静态变量不能说是真正的局部变量，因为其存储内存位置与局部变量不同，局部变量存储在堆，栈中，而静态变量存储在data中只是说会被限制在对应的作用域中。\n下面代码说明了普通局部变量和静态局部变量的区别，由于存储位置不同，静态局部变量只是被访问限制在作用域中，而不会随函数结束释放掉。\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include \u0026lt;stdio.h\u0026gt; void foo(void) { int\ta = 100; static int\tb = 100; printf(\u0026#34;a = %d, b = %d\\n\u0026#34;, a, b); a++; b++; } int\tmain(void) { foo(); foo(); foo(); foo(); foo(); return (0); } 输出结果\nc 1 2 3 4 5 a = 100, b = 100 a = 100, b = 101 a = 100, b = 102 a = 100, b = 103 a = 100, b = 104 全局静态变量 全局静态变量是声明在函数外面用static修饰的变量，与全局变量不同的是，静态全局变量访问域被限制在声明它们的文件中，无法从程序的另一个文件中访问它。\n在全局变量部分，可以通过关键字 ”extern“ 来访问全局变量，如果此时声明一个静态变量 a ，那么通过跨文件的方式这时编译器会提示 ”undefined reference to ‘a\u0026rsquo;“。通常情况下使用这种场景被用于加速编译。\nglobal VS local VS static 作用域方面不同：局部变量作用域仅在 同一个 {}，而静态变量和全局变量在为整个进程 访问作用域不同：全局变量为进程共享，局部变量为函数运行时，静态全局变量为定义它的文件，静态全局变量为 同一个 {} 存储位置不同，局部变量被存储与堆，栈中，而静态变量和全局变量被存储在data中 类型转换 隐式类型转换 隐式类型 (Implicit) 转换也称自动类型转换，这种类型的转换包含如下特点：\n编译器自动完成，无需用户干预触发 当表达式中存在多种类型时触发，这是为了保证数据不被丢失 所有的数据类型都将升级为该类型最大值 转换的顺序为：bool -\u0026gt; char -\u0026gt; short int -\u0026gt; int -\u0026gt; unsigned int -\u0026gt; long -\u0026gt; unsigned -\u0026gt; long long -\u0026gt; float -\u0026gt; double -\u0026gt; long double 该转换类型会存在一些问题，如符号消失，数据丢失等。 c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include\u0026lt;stdio.h\u0026gt; int main() { int x = 10; // integer x char y = \u0026#39;a\u0026#39;; // character c // y 被隐式转换为 char类型，a=97 x = x + y; // 计算中，存在浮点数值，结果将被转换为float float z = x + 1.0; // 自动转换为long long int h = 2147483648; // int 到 short int值溢出将为23352减去int大小65536 short int g = 88888 + x; printf(\u0026#34;x = %d, z = %f\\n\u0026#34;, x, z); printf(\u0026#34;g = %li, h = %lli\\n\u0026#34;, g, h); return 0; } 显式类型转换 用户定义类型转换的过程称为显式类型转换 (Explicit)\nc 1 (type) expression 显示转换示例\nc 1 2 3 4 5 6 7 8 9 10 11 12 #include\u0026lt;stdio.h\u0026gt; int main() { float x = 1.2; // 显示转换一个float为int int sum = (int) x + 1; printf(\u0026#34;sum = %d\u0026#34;, sum); return 0; } 进制转换 十进制 十进制转二进制： 除2反向取余法\n十进制转八进制：除8反向取余法\n十进制转十六进制：除16反向取余法\n例如：16进制转10进制\n将十进制数除以 16。将除法视为整数除法 写下余数（十六进制） 将结果再次除以 16。将除法视为整数除法 重复步骤 2 和 3，直到结果为 0 求出的十六进制值是从最后到第一个的余数的数字序列 427的16进制\n将数字除以 16，余数（小数部分乘16为余数），最终为1AB\n八进制 8进制转10进制：从后向前，8的0次方，8的1次方，8的2次方\u0026hellip;按照该顺序乘8的\n8进制75转10进制为：$56+5=61$ 8进制77655转10进制为：$7(8^4)+7(8^3)+6(8^2)+5(8^1)+5(8^0)=28672+3584+384+40+5=32685$ 2进制转8进制：自右向左，每3位一组，按421码转换。高位不足三位补0\n1 010 111 010 110 二进制转八进制如下表，最后算出结果为12726\n4 2 1 0 0 1 0 1 0 1 1 1 0 1 0 1 1 0 十六进制 16进制转10进制：从后向前依次展开，16的0次方，16的1次方，16的2次方\u0026hellip;，每位相加，例如：\n0x1A = $16 + 10 = 26$ 15DE = $1(16^3)+5(16^2)+13(16^1)+14= 4096+1280+208+14=5598$ 16进制转二进制：4位一组一次填充。例如 0X1A的二进制，即00011010如下\n8 4 2 1 1 0 1 0 0 0 0 1 二进制转16进制：自右向左，每4位一组，按8421码转换。高位不足三位补0\n例如 0001 0011 1111的16进制为，如下表 1 3 F(15)\n8 4 2 1 0 0 0 1 0 0 1 1 1 1 1 1 源码反码补码 源码 (*** true form****), 反码 (1‘s complement) [7], 补码 (2‘s complement) [8] 是操作系统中存储和计算数据的一种方式\n任何数据都以二进制机器码存储与计算机中。对于的机器码，第一位是用来表示正负值的：0是正数，1是负数。故要表示 -2，对应的机器码是 10000010。\n机器码不可以直接通过权重展开计算，例如 10000010 为 $1(2^7) + 1(2^1) = 130$ 。因为第一位是1，所以是负数，接下来计算后一位的权重展开为 $-2$\n原码：机器码表示的值成为源码：如 43 = 00101011，-43 = 10101011\n反码：符号位不变，其余位取反：如 43 = 00101011，-43 = 11010100\n补码：符号位不变，counter code then LSB (least significant bit) + 1：如 43 = 00101011，-43 = 11010101\n128 64 32 16 8 4 2 1 0 0 1 0 1 0 1 1 1 1 0 1 0 1 0 0+1(如果需要进位则进一位) 1 1 0 1 0 1 0 1 Note：二进制 10000000 为 -128\n反码, 补码 是为了计算和存储正负数诞生的，正如 C语言的数据结构 中 有符号和没符号表示的数值位置不一样。\nReference ​[1] keywords c language\n​[2] Arithmetic Operators\n​[3] four stages of compilation c\n​[4] offline installation visual studio\n​[5] difference float double\n​[6] data type in c\n​[7] 1‘s complement\n​[8] 2‘s complement\n","permalink":"https://www.161616.top/ch01-parmeter-and-data-structrue/","summary":"C语言关键字 [1] ==C语言有32个关键字==\nauto：定义自动变量，主要是声明变量的生存周期 break, continue : break 语句在遇到最内层循环时立即终止。还用于终止 switch 语句。 case, switch, default：使用 switch 和 case 语句声明一个switch分支 char：用于声明character 类型的变量 const：声明常量 do\u0026hellip;while： double： double-precision 浮点数变量类型 float：single-precision 浮点数的变量类型 if, else：声明if/else 条件判断 enum：用于声明枚举类型 extern：关键字声明变量或函数在其声明的文件之外具有外部链接。 for：C 语言的三种循环之一，for循环 goto： 用于将程序的控制权转移到指定的标签 int：声明 integer 类型的变量 short, long, signed, unsigned：是类型修饰符，它们改变基本数据类型的含义以产生新类型。 short int： -32768 to 32767 long int： -2147483648 to 214743648 signed int： -32768 to 32767 unsigned int： 0 to 65535 return： 终止函数并返回值 sizeof：评估变量或常量的大小 register：创建比普通变量快得多的寄存器变量。 static：创建一个静态变量。静态变量的值持续到程序结束。 struct：用于声明结构体。结构体可以包含不同类型的变量。 typedef：用于将类型与标识符显式关联。 union：用于将不同类型的变量分组在一个名称下。 void：没有任何意义，函数修饰为没有返回值，参数修饰为没有参数 volatile：提醒编译器它后面所定义的变量随时都有可能改变 C语言控制语句 ==C语言有9种控制语句== (control statements)","title":"ch01 变量和数据类型"},{"content":"格式化 printf printf() 用于打印消息以及变量的值。\nc 1 2 3 4 5 6 7 8 #include\u0026lt;stdio.h\u0026gt; int main() { int a = 24; printf(\u0026#34;Welcome! \\n\u0026#34;); printf(\u0026#34;The value of a : %d\u0026#34;,a); getchar(); return 0; } sprintf sprintf() 不打印字符串，是将字符值和格式化结构一并存储在一个数组中。\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 int main() { char buffer[50]; int a = 10, b = 20, c; c = a + b; sprintf(buffer, \u0026#34;Sum of %d and %d is %d\u0026#34;, a, b, c); // The string \u0026#34;sum of 10 and 20 is 30\u0026#34; is stored // into buffer instead of printing on stdout printf(\u0026#34;%s\u0026#34;, buffer); return 0; } scanf 从标准输入读取用户输入的\ntype Argument \u0026amp; Description ***** 读取标准输入用户输入的值，但不存储在对应接受的变量中 width 这个操作中读取的最大字符 type 指定要读取的数据类型以及预期如何读取数据 修饰符类型\n类型 标识符 int %d char %c float %f double %lf short int %hd unsigned int %u long int %li long long int %lli unsigned long int %lu unsigned long long int %llu signed char %c unsigned char %c long double %Lf 格式化\nDescription Code Result 接受字符类型保存在数组中 scanf(\u0026quot;%19c\u0026quot;, \u0026amp;a); \u0026lsquo;1234567890abcfefg\u0026rsquo; 整型类型 scanf(\u0026quot;%d\u0026quot;, \u0026amp;testInteger); \u0026lsquo;10\u0026rsquo; 多个接收值 scanf(\u0026quot;%d%f\u0026quot;, \u0026amp;a, \u0026amp;b); scanf的缺点\n如果存储空间不足，数据能存储到内存中，但不被保护。 scanf 函数接收字符串时， 碰到 空格 和 换行 会自动终止。不能使用 scanf 的 %s 接收带有空格的字符串。 格式化标记符 [1] 标记符 标记符 %i / %d int %c char %f float %s string %u unsigned decimal %o octal %x hexadecimal 对字符串填充 在 % 符号后添加一个零 (0)，可以对 printf 整数输出进行零填充\nCode Result printf(\u0026quot;%03d\u0026quot;, 0); 000 printf(\u0026quot;%03d\u0026quot;, 1); 001 printf(\u0026quot;%03d\u0026quot;, 123456789); 123456789 printf(\u0026quot;%03d\u0026quot;, -10); -10 printf(\u0026quot;%03d\u0026quot;, -123456789); -123456789 对于此类格式化方式总结有如下几种模式\nDescription Code Result 填充5位（默认以空白填充，左对齐填充） printf(\u0026quot;\u0026rsquo;%5d\u0026rsquo;\u0026quot;, 10); \u0026rsquo; 10' 填充5位（右对齐填充） printf(\u0026quot;\u0026rsquo;%-5d\u0026rsquo;\u0026quot;, 10); \u0026lsquo;10 ' 填充5位“0”（默认左对齐填充） printf(\u0026quot;\u0026rsquo;%05d\u0026rsquo;\u0026quot;, 10); \u0026lsquo;00010\u0026rsquo; 有符号的表示的数字（默认左对齐填充） printf(\u0026quot;\u0026rsquo;%+5d\u0026rsquo;\u0026quot;, 10); \u0026rsquo; +10\u0026rsquo; 有符号的表示的数字，右对齐填充空白 printf(\u0026quot;\u0026rsquo;%-+5d\u0026rsquo;\u0026quot;, 10); \u0026lsquo;+10 ' 浮点数格式化 Description Code Result 保留1位小数 printf(\u0026quot;\u0026rsquo;%.1f\u0026rsquo;\u0026quot;, 10.3456); \u0026lsquo;10.3\u0026rsquo; 保留2位小数 printf(\u0026quot;\u0026rsquo;%.2f\u0026rsquo;\u0026quot;, 10.3456); \u0026lsquo;10.35\u0026rsquo; 整数位最少8位宽度，小数位2位 printf(\u0026quot;\u0026rsquo;%8.2f\u0026rsquo;\u0026quot;, 10.3456); \u0026rsquo; 10.35\u0026rsquo; 整数位最少8位宽度，小数位4位 printf(\u0026quot;\u0026rsquo;%8.4f\u0026rsquo;\u0026quot;, 10.3456); \u0026rsquo; 10.3456' 整数位最少8位，小数位2位，不足8位将用0填充（默认左对齐填充） printf(\u0026quot;\u0026rsquo;%08.2f\u0026rsquo;\u0026quot;, 10.3456); \u0026lsquo;00010.35\u0026rsquo; 整数位最少8位，小数位2位，不足8位将用空白右对齐填充 printf(\u0026quot;\u0026rsquo;%-8.2f\u0026rsquo;\u0026quot;, 10.3456); \u0026lsquo;10.35 ' 打印更大的浮点数，小数位2位 printf(\u0026quot;\u0026rsquo;%-8.2f\u0026rsquo;\u0026quot;, 101234567.3456); \u0026lsquo;101234567.35\u0026rsquo; 字符串格式化 Description Code Result 字符串输出 printf(\u0026quot;\u0026rsquo;%s\u0026rsquo;\u0026quot;, \u0026ldquo;Hello\u0026rdquo;); \u0026lsquo;Hello\u0026rsquo; 保证输出结果是10位，不足位用空白填充（默认左对齐填充） printf(\u0026quot;\u0026rsquo;%10s\u0026rsquo;\u0026quot;, \u0026ldquo;Hello\u0026rdquo;); \u0026rsquo; Hello\u0026rsquo; 保证输出结果是10位，不足位用空白右对齐填充 printf(\u0026quot;\u0026rsquo;%-10s\u0026rsquo;\u0026quot;, \u0026ldquo;Hello\u0026rdquo;); \u0026lsquo;Hello ' 特殊字符 \\a audible alert \\b backspace（退格） \\f form feed （换页） \\n newline（换行） \\r carriage return（回车） \\t tab \\v vertical tab（垂直制表符） \\ backslash （反斜杠） 运算符 C语言中运算符优先级为下表所示\n优先级 运算符 说明 关联性 1 ++ -- 前缀/后缀 自增/减 从左向右 () 函数调用 [] 数组下标 (subscripting) . 结构体成员访问 -\u0026gt; 指针结构体成员访问 2 ++ -- 前缀/后缀 自增/减 从右向左 + - (Unary) 一元 +/-（正负号） ! ~ 逻辑非与按位非 (type) 转换 * 取消引用 \u0026amp; 地址符 sizeof Size-of 3 * / % Multiplication, division, remainder 从左向右 4 + - Addition and subtraction 5 \u0026lt;\u0026lt; \u0026gt;\u0026gt; Bitwise left shift and right shift 6 \u0026lt; \u0026lt;= \u0026gt; \u0026gt;= == != 关系运算符 \u0026lt; , ≤ , \u0026gt; , ≥ ,= , ≠ 7 \u0026amp; 按位与 8 ^ 按位异或 9 ` ` 按位异或 10 \u0026amp;\u0026amp; 逻辑与 11 ` ` 12 ?: 三元运算(Ternary conditional) 从右向左 13 = 赋值 += -= 按和差赋值 *= /= %= 按乘积，商，余赋值 \u0026lt;\u0026lt;= \u0026gt;\u0026gt;= 按左，右位移赋值 \u0026amp;= ^= ` =` 按位 与或非赋值 14 , 逗号 从左向右 流程控制 [2] C语言中提供了两种流程控制(flow control)\nBranching Looping Branching 分支 (Branching) 将决定采取什么动作，循环将决定采取某种行动的次数。\nif 形态1：\nc 1 2 3 4 5 6 7 if (expression) statement; if (expression) { Block of statements; } 形态2:\nc 1 2 3 4 5 6 7 8 if (expression) { Block of statements; } else { Block of statements; } 形态3：\nc 1 2 3 4 5 6 7 8 9 10 11 12 if (expression) { Block of statements; } else if(expression) { Block of statements; } else { Block of statements; } 三元运算 \u0026lt;value1\u0026gt; ? \u0026lt;value2\u0026gt; : \u0026lt;value3\u0026gt; 是三元运算符，因为它需要三个值，这是 C 中唯一的三元运算符。语法\nc 1 if condition is true ? then X return value : otherwise Y value; switch c 1 2 3 4 5 6 7 switch( expression ) { case constant-expression1:\tstatements1; [case constant-expression2:\tstatements2;] [case constant-expression3:\tstatements3;] [default : statements4;] } break 关键字用作退出 switch 语句。在 switch case 中满足条件，则执行继续到下一个 case 子句，如果没有明确指定执行应该退出 switch 语句。\ndefault 关键字用于在所有case中都不满足条件，则执行default\ncase穿透：case分支中如果,没有 break；那么它会向下继续执行下一个case分支.\nif VS switch 检查表达式：if-else 可以基于值或条件检查表达式，而 switch 语句仅基于字符表达式或整数类型检查表达式。 运行速度：在大量条件检查中进行选择，switch 语句的运行速度将比使用 if-else 的逻辑快得多。 适合条件不同：if-else 适合导致布尔值的可变条件，而 switch 适合固定值。 可读性：if-else较switch-case语句可读性较差 Looping 循环 (Looping) 提供了一种重复命令和控制重复次数的方法。\nwhile while 是 c 语言中最基础的循环，while将检查expression，直到expression为false将推出循环\nc 1 2 3 4 5 6 while ( expression ) { Single statement or Block of statements; } for for是类似与while的循环，只是语法上不同，for提供了三个表达式\nc 1 2 3 4 5 6 for( expression1; expression2; expression3) { Single statement or Block of statements; } expression1 - 通常用于初始化变量（在此初始化的变量作用域仅为该循环中） expression2 - 条件表达式，只要该表达式为true则循环将一直被执行 expression3 - 修饰符，通常用于变量的自增自减操作 三个表达式都可以为空，这种场景下循环将一直进行 do\u0026hellip;while 类似与while ，只不过do..while循环，在循环结束开始检查测试条件。这意味着循环的内容将==至少执行一次==。\nc 1 2 3 4 5 6 7 do { Single statement or Block of statements; } while(expression); break VS continue C语言提供了两个命令来控制循环：\nbreak，退出循环或switch continue，跳过当前迭代 (iteration)，继续循环 c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include main() { int i; int j = 10; for( i = 0; i \u0026lt;= j; i ++ ) { if( i == 5 ) { continue; } printf(\u0026#34;Hello %d\\n\u0026#34;, i ); } } 输出结果将没有第五次迭代\nbash 1 2 3 4 5 6 7 8 9 10 Hello 0 Hello 1 Hello 2 Hello 3 Hello 4 Hello 6 Hello 7 Hello 8 Hello 9 Hello 10 goto goto 声明在C语言中提供了了一个无条件跳转到goto label出的\nc 1 2 3 4 goto label; .. . label: statement; 下面例子中，将从10开始执行，跳过15继续从16开始到20结束。\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include \u0026lt;stdio.h\u0026gt; int main () { /* 局部变量定义 */ int a = 10; /* do循环体 */ LOOP:do { if( a == 15) { /* 跳出迭代 */ a = a + 1; goto LOOP; } printf(\u0026#34;value of a: %d\\n\u0026#34;, a); a++; }while( a \u0026lt; 20 ); return 0; } Reference ​[1] printf format\n​[2] control_statements\n","permalink":"https://www.161616.top/ch02-control-statements-and-format/","summary":"格式化 printf printf() 用于打印消息以及变量的值。\nc 1 2 3 4 5 6 7 8 #include\u0026lt;stdio.h\u0026gt; int main() { int a = 24; printf(\u0026#34;Welcome! \\n\u0026#34;); printf(\u0026#34;The value of a : %d\u0026#34;,a); getchar(); return 0; } sprintf sprintf() 不打印字符串，是将字符值和格式化结构一并存储在一个数组中。\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 int main() { char buffer[50]; int a = 10, b = 20, c; c = a + b; sprintf(buffer, \u0026#34;Sum of %d and %d is %d\u0026#34;, a, b, c); // The string \u0026#34;sum of 10 and 20 is 30\u0026#34; is stored // into buffer instead of printing on stdout printf(\u0026#34;%s\u0026#34;, buffer); return 0; } scanf 从标准输入读取用户输入的","title":"ch02 格式化与流程控制"},{"content":"Array [1] 数组是由单个元素组成的一组数据类型的变量 数组的元素存储在连续的内存位置 声明数组时应提及数组的大小 数组的计数从0开始 数组为一位数组与多维数组 数组首元素的地址与数组地址相同 数组包含 int, float, char, double 数据类型 Declaration and Initialization 表达式 说明 int my_array1[20]; 指定大小，来声明一个有20个元素的int数组 char my_array2[5]; 指定大小，来声明一个有5个元素的char数组 int my_array[] = {100, 200, 300, 400, 500} 声明时初始化一个数组（编译器自动求数组元素个数） int my_array1[5] = {100, 200, 300, 400, 500}; 声明时初始化 int my_array2[5] = {100, 200, 300}; 声明时初始化（剩余未初始化的元素，默认 0 值） int my_array2[5] = {0}; 声明时初始化（声明一个全0值的数组） int arr[10]; arr[0] = 5;arr[1] = 6;arr[2] = 7; 声明数组并初始化值（这种方法为初始化部分的默认值为随机数） char str[] = \u0026ldquo;zhangsan\u0026rdquo; 声明一个字符串（字符串是一个char类型数组） Advantages and Disadvantages 缺点**：大小限制**：声明（定义）后是固定的大小，不能通过运行时改变其大小\n优点：\n代码优化，可以通过数组更好的对数据进行检索或排序 随机存储，可以将数据存储在不同的位置 muitl-dimensional [5] 数组中的数组，又称为多维数组(*** multidimensional arrays***)。包含 2D 3D数组。2D是包含行(rows), 列(columns) 的数组；而3D数组是在2D的基础上，增加了一个维度。包含如下：\n第一个维度：大小 第二个维度：二维数组的行 第三个维度：二维数组的列 而更高维度的数组，实际上就是在3D, 4D\u0026hellip; 上再增加一个维度。\ndeclare 声明一个多维数组方式如下，声明一个二维数组\nc 1 float x[3][4]; Initialization 初始化方式 说明 代码 常规初始化 int arr[3][5] = { {2, 3, 54, 56, 7 }, {2, 67, 4, 35, 9}, {1, 4, 16, 3, 78}}; 不完全初始化 未被初始化的数值为 0 int arr[3][5] = { {2, 3}, {2, 67, 4, }, {1, 4, 16, 78}}; 初始化一个 初值全为0的二维数组 int arr[3][5] = {0}; 系统自动分配行列 int arr[3][5] = {2, 3, 2, 67, 4, 1, 4, 16, 78}; 不完全指定行列初始化 二维数组定义必须指定列值 int arr[][] = {1, 3, 4, 6, 7};（==错误示例==） 二维数组定义可以不指定行值 int arr[][2] = { 1, 3, 4, 6, 7 }; 示例：遍历一个二维数组\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;math.h\u0026gt; #include \u0026lt;time.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; void main() { int row,colume; int arr[][2] = {1, 3, 4, 6, 7, 10}; row = sizeof(arr)/ sizeof(arr[0]); colume = sizeof(arr[0])/ sizeof(arr[0][0]); for(int i=0;i\u0026lt;row;i++) { for(int j=0;j\u0026lt;colume;j++) { printf(\u0026#34;%d \u0026#34;, arr[i][j]); } printf(\u0026#34;\\n\u0026#34;); } } 声明和便利一个三维数组\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include \u0026lt;stdio.h\u0026gt; void main() { int i, j, k; int arr[3][3][3]= { { {11, 12, 13}, {14, 15, 16}, {17, 18, 19} }, { {21, 22, 23}, {24, 25, 26}, {27, 28, 29} }, { {31, 32, 33}, {34, 35, 36}, {37, 38, 39} }, }; printf(\u0026#34;:::3D Array Elements:::\\n\u0026#34;); for(i=0;i\u0026lt;3;i++) { for(j=0;j\u0026lt;3;j++) { for(k=0;k\u0026lt;3;k++) { printf(\u0026#34;%d\\t\u0026#34;,arr[i][j][k]); } printf(\u0026#34;\\n\u0026#34;); } printf(\u0026#34;\\n\u0026#34;); } } String [3] 字符串 (string) 是一组字符 (char)，以 ”\\0“ 结尾，抽象来说，C语言中字符串就是数组类型的char\n定义，定义一个值为”colour“的字符串。\nc 1 char message[6] = {\u0026#39;C\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;l\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;u\u0026#39;, \u0026#39;r\u0026#39;, \u0026#39;\\0\u0026#39;}; 而 \u0026ldquo;\\0\u0026rdquo; 可以省略，定义可以如下\nc 1 char message[]= “Colour”; C语言初始化字符串的4中方法\n表达式 说明 char str[] = \u0026ldquo;hello world\u0026rdquo;; 分配不带大小的字符串 char str[50] = \u0026ldquo;hello world\u0026rdquo;; 分配具有预定义大小的字符串 char str[14] = { \u0026lsquo;h\u0026rsquo;,\u0026rsquo;e\u0026rsquo;,\u0026rsquo;l\u0026rsquo;,\u0026rsquo;l\u0026rsquo;,\u0026lsquo;o\u0026rsquo;,\u0026rsquo;\\0\u0026rsquo;}; 按字符分配大小的字符串 char str[] = { \u0026lsquo;h\u0026rsquo;,\u0026rsquo;e\u0026rsquo;,\u0026rsquo;l\u0026rsquo;,\u0026rsquo;l\u0026rsquo;,\u0026lsquo;o\u0026rsquo;,\u0026rsquo;\\0\u0026rsquo;}; 不带大小的按字符分配大小的字符串 在C语言中，数组和字符串都是二等公民，一旦声明后，不支持赋值运算符\nc 1 2 3 4 5 6 7 8 9 #include\u0026lt;stdio.h\u0026gt; int main() { char message[6] = {\u0026#39;C\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;l\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;u\u0026#39;, \u0026#39;r\u0026#39;}; message = \u0026#34;aaaaaa\u0026#34;; printf(\u0026#34;sum = %c\\n\u0026#34;, message); return 0; } 上面代码对字符串二次赋值，这种编译器直接报错\nbash 1 2 3 1.c: In function \u0026#39;main\u0026#39;: 1.c:6:13: error: assignment to expression with array type message = \u0026#34;aaaaaa\u0026#34;; Notes：复制字符串可以使用函数 strcpy()\n字符串获取 scanf：\n存储字符串的空间必须足够大，防止溢出。\n获取字符串，%s， 遇到 空格 和 \\n 终止。\n使用“正则表达式”可以获取带有空格的字符串，如：scanf(\u0026quot;%[^\\n]\u0026quot;, str);\ngets：类似与 scanf ；从stdin中读取字符串保存在变量中，遇到换行符终止（可以获取带有“空格”的字符串）。\n参数：用来存储字符串的空间地址 返回值：返回实际获取到的字符串首地址。 fgets：从指定流读取一行字符串，遇到换行符或到达结尾终止\n*str：存储读取字符串的变量指针。 n：读取的最大字符 *stream：输入流的对象指针，如stdin 字符串写入 puts：将一行字符串写入输出流 (stdout)， 输出字符串后会自动添加 \\n 换行符。\nchar* str：被打印的字符串\nreturn value：成功返回非0的integer，失败返回 EOF\nfputs：将字符串写入指定流，不包含换行符 \\n\nconst char *str：写入的以NULL字符结尾的字符串 FILE *stream： FILE 对象的指针，代表要将字符串写入的流 return value：成功返回非0的integer，失败返回 EOF Array VS String [2] 数据类型不同：数组可以保存 int, float, doubles类型，字符串只能保存char类型 长度不同：数组长度是固定的，字符串长度可变（通过指针） 数据结构不同：数组可以是一维或多维，字符串是一维数组，结束是一个空字符 ”\\0\u0026quot; char * VS char [] char a[10] char *a a是一个数组 a是一个指针 sizeof为数组的大小 sizeof为指针类型的大小 存储在内存中的栈段 a的地址被存储在栈中，但是内容被存储在.rodata中 a不可以被修改 a可以被修改 a[0]可以被修改 a[0]不可以被修改，因为内容在.rodata char *a=\u0026ldquo;text\u0026rdquo;; *a 存储的 text 内容，只读区内容不能修改 a 代表存储的 .rodata的地址 a=\u0026ldquo;text1\u0026rdquo; text1位于内存中其他地方的，将这个地址赋值给a 字符串的拷贝 使用指针运算方式\nc 1 2 3 4 5 6 7 void copy_string02(char* dest, char* src){ while (*source != \u0026#39;\\0\u0026#39; /* *src != 0 */){ *dest = *src; src++; dest++; } } 使用数组方式\nc 1 2 3 4 5 void copy_string01(char* dest, char* src ){ for (int i = 0; src[i] != \u0026#39;\\0\u0026#39;;i++){ dest[i] = src[i]; } } 使用while循环\nc 1 2 3 4 void copy_string03(char* dest, char* source){ // 判断时赋值结尾 0=0也会退出循环 while (*dest++ = *source++){} } 字符串的格式化 用于将字符串打印在标准输出的：int printf(const char* str, ...); 用于将字符串格式化打印在缓冲区中的（stdin, stdout, stderr是隐式缓冲资源）：int fprintf(FILE *fptr, const char *str, ...); 用于格式化而不打印的：int sprintf(char *str, const char *string,...); array sorting 杯子交换 三杯水交换算法 ( The Cup Swapping algorithm)\n有两杯装满水的杯子来代表变量的值，如果需要交换两杯水到对方，就如同交换两个变量的值，此时需要第三个杯子来交换液体，就像第三个变量用作临时存储变量的值一样。\n例如，数组的倒序可以使用该方法，也是其他算法中的基础。\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include\u0026lt;stdio.h\u0026gt; #include\u0026lt;string.h\u0026gt; int main() { int arr[] = {22,321,56,1,66,23,9,10}; // 数组的长度 int len = sizeof(arr) / sizeof(arr[0]); // 临时变量 int tmp;\t// 交换 数组元素，做逆序 for (int i=0;i\u0026lt;len;i++) { if (i \u0026gt; len/2) { break; } tmp = arr[i]; // 第三杯水 arr[i] = arr[len-i-1]; arr[len-i-1] = tmp; } } 冒泡 [4] 冒泡排序 (bubble sort) 是最简单的排序算法，其核心是==如果两个相邻元素的位置排序不对，就交换相邻的元素==，例如： arr[] = {5, 1, 4, 2, 8} ，从前两个元素开始比较检查哪个更大\n第一轮（迭代）：\n( 5 1 4 2 8 ) -\u0026gt; ( 1 5 4 2 8 )，比较前两个元素 5 \u0026gt; 1 交换两个位置。\n( 1 5 4 2 8 ) –\u0026gt; ( 1 4 5 2 8 )，5 \u0026gt; 4 交换两个位置\n( 1 4 5 2 8 ) –\u0026gt; ( 1 4 2 5 8 )，5 \u0026gt; 2 交换两个位置\n( 1 4 2 5 8 ) -\u0026gt; ( 1 4 2 5 8 )，(8 \u0026gt; 5)，不会交换，至此最后一位排序正确\n第二轮\n( 1 4 2 5 8 ) -\u0026gt; ( 1 4 2 5 8 ) ( 1 4 2 5 8 ) –\u0026gt; ( 1 2 4 5 8 )，4 \u0026gt; 2 交换两个位置 ( 1 2 4 5 8 ) -\u0026gt; ( 1 2 4 5 8 ) ( 1 2 4 5 8 ) -\u0026gt; ( 1 2 4 5 8 ) 第三轮（没法发生交换）\n( 1 2 4 5 8 ) -\u0026gt; ( 1 2 4 5 8 ) ( 1 2 4 5 8 ) -\u0026gt; ( 1 2 4 5 8 ) ( 1 2 4 5 8 ) -\u0026gt; ( 1 2 4 5 8 ) ( 1 2 4 5 8 ) -\u0026gt; ( 1 2 4 5 8 ) 算法实现\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include \u0026lt;stdio.h\u0026gt; // 三杯水交换 void swap(int* x, int* y) { int temp = *x; *x = *y; *y = temp; } // 冒泡实现 void bubbleSort(int arr[], int n) { int i, j; for (i = 0; i \u0026lt; n - 1; i++) // Last i elements are already in place for (j = 0; j \u0026lt; n - i - 1; j++) if (arr[j] \u0026gt; arr[j + 1]) swap(\u0026amp;arr[j], \u0026amp;arr[j + 1]); } // 打印数组 void printArray(int arr[], int size) { int i; for (i = 0; i \u0026lt; size; i++) printf(\u0026#34;%d \u0026#34;, arr[i]); printf(\u0026#34;\\n\u0026#34;); } int main() { int arr[] = { 64, 34, 25, 12, 22, 11, 90 }; int n = sizeof(arr) / sizeof(arr[0]); bubbleSort(arr, n); printf(\u0026#34;Sorted array: \\n\u0026#34;); printArray(arr, n); return 0; } 输出结果为\nc 1 2 Sorted array: 11 12 22 25 34 64 90 Reference ​[1] Arrays in c\n​[2] difference between array and string\n​[3] string in c\n​[4] bubble sort with c\n​[5] 3d array in c\n","permalink":"https://www.161616.top/ch03-array/","summary":"Array [1] 数组是由单个元素组成的一组数据类型的变量 数组的元素存储在连续的内存位置 声明数组时应提及数组的大小 数组的计数从0开始 数组为一位数组与多维数组 数组首元素的地址与数组地址相同 数组包含 int, float, char, double 数据类型 Declaration and Initialization 表达式 说明 int my_array1[20]; 指定大小，来声明一个有20个元素的int数组 char my_array2[5]; 指定大小，来声明一个有5个元素的char数组 int my_array[] = {100, 200, 300, 400, 500} 声明时初始化一个数组（编译器自动求数组元素个数） int my_array1[5] = {100, 200, 300, 400, 500}; 声明时初始化 int my_array2[5] = {100, 200, 300}; 声明时初始化（剩余未初始化的元素，默认 0 值） int my_array2[5] = {0}; 声明时初始化（声明一个全0值的数组） int arr[10]; arr[0] = 5;arr[1] = 6;arr[2] = 7; 声明数组并初始化值（这种方法为初始化部分的默认值为随机数） char str[] = \u0026ldquo;zhangsan\u0026rdquo; 声明一个字符串（字符串是一个char类型数组） Advantages and Disadvantages 缺点**：大小限制**：声明（定义）后是固定的大小，不能通过运行时改变其大小","title":"ch03 数组"},{"content":"concept [1] 函数 (function) 是执行任务的语句块。\n函数的作用：\n提高代码的可重用性并减少冗余 代码模块化 代码易读性 使代码模块化 函数的分类 C语言中有两种类型的函数：\n标准库函数：C中的内置函数，在头文件中定义 #include \u0026lt;stdio.h\u0026gt; 用户自定义函数：用户自定义的函数 #include \u0026quot;stdio.h\u0026quot; 函数三部曲 C语言中函数分为三个方面，声明(declaration)，定义(defining)，调用(calling)\n声明 声明是让编译器知道函数的名称、参数信息、参数的返回值的类型。\nc 1 (type) function_name({type args...}); 隐式声明(implicit) ：当在main之后定义的函数而未声明，默认编译器会做隐式声明。\nISO/IEC 9899:1990 中 关于函数声明的部分：\n函数在调用前必须有一个可用的声明，如果没有被声明，则该函数默认被隐式声明，该隐式声明没有参数，返回值为int [2]\n定义 C中函数定义的语法如下\nc 1 2 3 4 return_type function_name(arg1, arg2, ... argn) { function body // 函数中要处理任务的逻辑 } return_type：函数返回值的数据类型 function_name：函数名 arg1, arg2, \u0026hellip;argn：参数列表（可选），定义传递给函数的数据类型、顺序和参数的数量。 function body：调用函数时任务处理和执行的语句 调用 调用是指要由编译器执行的函数，可以在任何部分调用\n虚函数void 如果函数没有返回值，则使用关键字 void，主要用于两个方面：\n打印具体信息供用户阅读的函数 引用参数，函数通常不是用于返回一个内容，而是修改引用参数的，无需返回值 void 关键字使用注意：\nvoid仅用于限定函数返回值，函数参数，不可以修饰变量，因为无法对无类型的变量分配指针\nvoid修饰指针时表示泛指针，可以无需强制转换为其他类型的指针\nc 1 2 3 4 5 void *ptr=NULL; char * a=\u0026#34;1234\u0026#34;; printf(\u0026#34;a %s\\n\u0026#34;, a); ptr = a; printf(\u0026#34;%s\\n\u0026#34;, (char*)ptr); 宏函数 宏函数是指带有参数的宏(Macro-Arguments)，具有类似函数的功能，例如下列时一个获取最小值的宏函数\nc 1 2 3 4 5 6 #define min(X, Y) ((X) \u0026lt; (Y) ? (X) : (Y)) char a=\u0026#39;a\u0026#39;; char b=\u0026#39;b\u0026#39;; char x = min(a, b); // → x = ((a) \u0026lt; (b) ? (a) : (b)); char y = min(1, 2); // → y = ((1) \u0026lt; (2) ? (1) : (2)); printf(\u0026#34;x is %c, y is %c\\n\u0026#34;, x, y); 宏函数危险部分 在上面例子中存在一些不安全部分\n错误嵌套：\n括号优先级：#define ceil_div(x, y) x + y 因为宏函数带有的括号是围绕这个宏函数的，会存在运算符优先级问题，如果用上述宏函数进行输出得到的结果为：ceil_div(2, 3) * 10 = 32 ，因为括号不是表达式的括号\n解决方法：每一个宏函数的参数需要用括号括起来 #define ceil_div(x, y) (x + y) 吞分号：#define NEW_MACRO() ({ int x = 1; int y = 2; x+y; }) 上述宏函数在GCC预处理步骤替换时，通常调用宏函数的部分会加分号 NEW_MACRO(); ，例如下列代码中\nc 1 2 3 4 if(1) SKIP_SPACES(); else ... 被替换后为\nc 1 2 3 4 5 6 if(1) { int x = 1; int y = 2; x+y; }; // 《这里的分号会跳过else else ... 通常情况下使用do\u0026hellip;while替换\nc 1 2 3 4 5 6 7 8 9 #define NEW_MACRO() do { int x = 1; int y = 2; x+y; } while (0) if(1) do{ int x = 1; int y = 2; x+y; } while(0); else ... 重复替换的副作用：#define min(X, Y) ((X) \u0026lt; (Y) ? (X) : (Y)) 这个宏函数在gcc预处理中如果调用时是 next = min (x + y, foo (z)); 将被重复替换，如下列代码所示：\nc 1 next = ((x + y) \u0026lt; (foo (z)) ? (x + y) : (foo (z))); // 参数Y被替换为两个foo 这代表foo被执行两次，这种显然不安全，推荐使用typeof\nc 1 2 3 4 #define min(X, Y) \\ ({ typeof (X) x_ = (X); \\ typeof (Y) y_ = (Y); \\ (x_ \u0026lt; y_) ? x_ : y_; }) 直接自引用：是指定义的宏引用自己，例如 #define foo (4 + foo)，为了方式无限扩展为 (4+(4+foo)), (4+(4+(4+foo))) \u0026hellip; 直到内存耗尽，这种情况编译器将不允许 each undeclared identifier is reported only once for each function it appears in\n间接自引用：指a引用b，b引用a，例如下列代码，是不被允许的\nc 1 2 #define x (4 + y) #define y (2 * x) 参数的换行符不被允许\n函数的退出 exit() 是一个终止当前进程的系统调用（无论在代码哪里调用）；非C语言内置功能 return：向调用函数提供退出状态并将控制权返回给调用函数，C语言内置功能 多文件编程 [3] 多文件程序(multi-file) 是指多个含有不同功能的代码文件（ .c 文件模块），编译到一起，生成一个二进制文件。\n通常包含三部分：\n编译：通过编译器编译多个文件程序\n函数原型（声明）：告知编译器如何使用，表现为：\n函数在一个文件中定义，在另一个文件中调用 想对文件中的函数重新排序 函数相互调用，递归 头文件：使多个文件中的函数可以访问定义和声明，通常情况下包含：\n全局变量和全局常量 类，结构体，联合体，枚举等 创建类型名称的 typedef 语句 函数声明 包含其他文件的语句，如math.h 防止头文件重复包含\nwindows\nc 1 #pragma once linux\nc 1 2 3 4 5 6 #ifndef __HEAD_H__ #define __HEAD_H__ .... head file body #endif Reference ​[1] c function\n​[2] Are prototypes required for all functions in C89, C90 or C99?\n​[3] multi-file\n​[4] Macros\n","permalink":"https://www.161616.top/ch04-function/","summary":"concept [1] 函数 (function) 是执行任务的语句块。\n函数的作用：\n提高代码的可重用性并减少冗余 代码模块化 代码易读性 使代码模块化 函数的分类 C语言中有两种类型的函数：\n标准库函数：C中的内置函数，在头文件中定义 #include \u0026lt;stdio.h\u0026gt; 用户自定义函数：用户自定义的函数 #include \u0026quot;stdio.h\u0026quot; 函数三部曲 C语言中函数分为三个方面，声明(declaration)，定义(defining)，调用(calling)\n声明 声明是让编译器知道函数的名称、参数信息、参数的返回值的类型。\nc 1 (type) function_name({type args...}); 隐式声明(implicit) ：当在main之后定义的函数而未声明，默认编译器会做隐式声明。\nISO/IEC 9899:1990 中 关于函数声明的部分：\n函数在调用前必须有一个可用的声明，如果没有被声明，则该函数默认被隐式声明，该隐式声明没有参数，返回值为int [2]\n定义 C中函数定义的语法如下\nc 1 2 3 4 return_type function_name(arg1, arg2, ... argn) { function body // 函数中要处理任务的逻辑 } return_type：函数返回值的数据类型 function_name：函数名 arg1, arg2, \u0026hellip;argn：参数列表（可选），定义传递给函数的数据类型、顺序和参数的数量。 function body：调用函数时任务处理和执行的语句 调用 调用是指要由编译器执行的函数，可以在任何部分调用\n虚函数void 如果函数没有返回值，则使用关键字 void，主要用于两个方面：\n打印具体信息供用户阅读的函数 引用参数，函数通常不是用于返回一个内容，而是修改引用参数的，无需返回值 void 关键字使用注意：\nvoid仅用于限定函数返回值，函数参数，不可以修饰变量，因为无法对无类型的变量分配指针","title":"ch04 函数"},{"content":"指针 指针声明 [1] 指针/指针变量 (pointer) 是用于存储地址的变量\n使用 \u0026amp; 运算符 来访问变量的地址。例如\nc 1 2 3 4 5 6 7 #include \u0026lt;stdio.h\u0026gt; void main() { int a = 100; printf(\u0026#34;%x\u0026#34;, \u0026amp;a); } 输出结果为 16进制的内存地址\nc 1 61fe1c 使用地址运算符 * 可以从变量地址中获取变量的值，这个行为被称为间接引用/解引用(indirection/dereferencing)。例如：\nc 1 2 3 4 5 6 7 8 9 #include \u0026lt;stdio.h\u0026gt; void main() { int a = 100; printf(\u0026#34;%d\u0026#34;, *(\u0026amp;a)); // 也可以写为，因为*与\u0026amp;优先级相同，从右到左的顺序，所以有没有()意思是相同的 printf(\u0026#34;%d\u0026#34;, *\u0026amp;a); } 输出结果为 100\n指针变量 指针变量是指存储一个变量的地址的变量，可以使用符号 * 来修饰变量，定义语法为：\nc 1 dataType *pointerVariableName = \u0026amp;variableName; 例如，下面的两个输出结果是相同的地址\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 #include \u0026lt;stdio.h\u0026gt; void main() { int a = 100; int *pointer; pointer = \u0026amp;a; printf(\u0026#34;address of a is: %x\\n\u0026#34;,\u0026amp;a); printf(\u0026#34;address of pointer is: %x\\n\u0026#34;,pointer); } address of a is: 61fe14 address of pointer is: 61fe14 Notes：指针可以通过变量修改也可以直接通过地址进行修改，指针变量就是通过地址进行修改\n修饰符说明 修饰符 说明 * 两个用途：\n指针变量的声明\n返回被引用变量的值 \u0026amp; 返回变量地址 使用const修饰指针 [3] 使用关键字 const 修饰的指针变量是不能改变指针变量所指向的地址的变量，通俗来讲即不能被改变值的指针变量\n声明语句\nc 1 2 \u0026lt;type of pointer\u0026gt; *const \u0026lt;name of pointer\u0026gt;; int *const ptr; const位置：const修饰的部分（const所在位置）不可改变\n例如 const int *p; 与 int const *p; 这里修饰的都是 *p 故\n*p （变量地址）不能被改变\np （变量值）可以被改变\nc 1 2 3 4 5 6 7 8 9 10 11 12 #include\u0026lt;stdio.h\u0026gt; #include\u0026lt;stdlib.h\u0026gt; void main() { int a = 10; int b = 20; int const *p; *p = \u0026amp;b; // result assignment of read-only location \u0026#39;*p\u0026#39; p = 30; // result 30 printf(\u0026#34;%d\u0026#34;,p); } int * const p; const在*后p前，这里修饰的都是 p 故\np不可以被修改 *p 可以被修改 const int *const p; 这里 const 修饰 * 和 p，所以两个都不可修改\nNotes：通常情况下常用只有第一种情况\n使用场景：最为参数形参修饰该参数为只读参数，例如 printf\nc 1 int printf (const char *__format, ...) 指针的类型 [2] C语言中包含多种指针类型：\n空指针(Null Pointer) 野指针(Wild Pointer) 悬空指针(Dangling pointer) 泛型指针(void Pointer) 一些早期Dos中的概念 近指针(Near)：不能存储大小大于 16 位的地址 远指针(Far)：32 位大小的指针 大指针(Huge)：类似于远指针。 空指针 在声明期间将 NULL 分配给指针的指针称为 空 (NULL) 指针，例如\nc 1 2 3 4 5 6 7 #include \u0026lt;stdio.h\u0026gt; void main() { int *var = NULL; printf(\u0026#34;address of var is: %p\\n\u0026#34;,var); } 空指针不能解引用：NULL指针因引用是一个非法的操作，在解引用之前，必须确保它不是一个NULL指针 空指针不能拷贝内容：strcpy(*p,\u0026quot;1111); 泛型指针 使用 void 关键字声明指针变量，可以接受任意一种类型的变量地址，如果需要使用泛型指针，需要强转为对应类型才可以使用。如下示例：\nc 1 2 3 4 5 6 7 8 9 10 11 12 #include \u0026lt;stdio.h\u0026gt; void main() { int a = 6666; void *p = \u0026amp;a; printf(\u0026#34;address of p is: %p\\n\u0026#34;, (int*) p); printf(\u0026#34;value of p is: %d\\n\u0026#34;, *(int*) p); } address of p is: 000000000061FE14 value of p is: 6666 野指针 野指针是指，没有有效地址的空间的指针，例如声明了指针变量没有对其赋值，这种情况下会出现 Segmentation fault 异常。\nc 1 2 3 4 5 6 7 #include \u0026lt;stdio.h\u0026gt; void main() { int *p; printf(\u0026#34;address of p is: %p\\n\u0026#34;, *p); } 再例如一个无效的地址空间也会发生 Segmentation fault 异常；例如下列赋值中，指针p赋值被视为一个内存地址，而不是变量的值，这个地址无效。\nc 1 2 3 4 5 6 7 8 #include \u0026lt;stdio.h\u0026gt; void main() { int *p; *p = 10000; printf(\u0026#34;address of p is: %p\\n\u0026#34;, *p); } 野指针出现场景：\n指针变量声明但未初始化 指针释放后未置空 指针操作超出变量作用域 避免野指针的出现：\n初始化置 NULL 释放后置 NULL 悬空指针 悬空指针是指”已经被释放的内存“的指针变量，此时这个地址空间是无效的。如下所示\nc 1 2 3 4 5 6 7 8 9 10 11 #include\u0026lt;stdio.h\u0026gt; #include\u0026lt;stdlib.h\u0026gt; void main() { int *P=(int *)malloc(sizeof(int)); int a=5; P=\u0026amp;a; free(P); printf(\u0026#34;After deallocating its memory *p=%d\u0026#34;, *P); } 指针的偏移量 指针步长是指指针在运算是偏移多少字节\n指针+1之后跳跃的字节数取决于指针的类型，int 4, char 1, struct struct长度\n指针解引用时需要转换成对应的数据类型，从而判断被解引用后的大小，* (int *) p 指针类型变量转换为指针int类型变量\n对于结构体指针来说，offsetof函数定位属性对应的偏移量\nc 1 2 #include\u0026lt;stddef.h\u0026gt; offsetof(\u0026lt;struct struct_name\u0026gt;, \u0026lt;obj_name\u0026gt;) 指针数组 在C语言中 数组是由两部分组成，数组名与数组本身。\nc 1 2 3 4 5 6 7 8 9 10 11 #include\u0026lt;stdio.h\u0026gt; void main() { int a[10] = {0}; a[1] = 20; for (int i=0; i\u0026lt; sizeof(a) / sizeof(a[0]); i++){ printf(\u0026#34;%d\\n\u0026#34;,a[i]); } printf(\u0026#34;arr %p = \u0026amp;arr %p\u0026#34;, a, \u0026amp;a[0]); } 上面例子中，变量a是一个数组，而变量a代表的是一个指向该数组第一个元素的地址，a=\u0026amp;a[0] ，这是一个const修饰的指针是不可以被改变的。\n可以看到变量a和 \u0026amp;a[0] 值是相同的，而一个const修饰的指针变量是不可改变的，故a不能被赋值，下列代码是不合法的。总结为：不允许将任何地址分配给数组变量\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include\u0026lt;stdio.h\u0026gt; void main() { int a[10] = {0}; int b[1] = {0}; int c = 10; a = \u0026amp;b; a = \u0026amp;c; for (int i=0; i\u0026lt; sizeof(a) / sizeof(a[0]); i++){ printf(\u0026#34;%d\\n\u0026#34;,a[i]); } printf(\u0026#34;arr %p = \u0026amp;arr %p\u0026#34;, a, \u0026amp;a[0]); } 使用指针访问数组 上面知道了，数组变量指向的是数组的起始元素（第一个元素）的地址指针，那么通过指针可以对数组进行访问。\n因为 a = \u0026amp;a[0] 那么 a[0] = *a，由此可推导出下列公式：\n\u0026amp;a[1] = a+1 （取数组元素的地址） 那么 a[1] = *(a+1) （取数组元素的值） \u0026amp;a[2] = a+2 那么 a[2] = *(a+2) 这种情况下数组的访问就有四种方法\nArray VS Pointer [4] 数组名是常量，指针是变量 sizeof(array) 得到的是数组实际占用内存空间的字节数，sizeof(pointer) 是4/8 取决于操作系统 指针运算 左值和右值 [5] 了解对于指针运算前，需要对左值(lvalue)和右值(rvalue)进行了解\n左值：通常来说是在占有内存地址（即具有地址）的对象 具有存储数据的内力，例如变量 不能是函数，表达式，或常量 综合来说，左值可以是以下几种： 任何类型的变量：int, float, pointer, struct等 数组的下标表达式，如a[1] 括号内的表达式（指针） 指针的间接引用 常量（不可改变的左值） 通过指针访问对象属性或成员 (-\u0026gt; or .) 右值：在内存中没有占有内存地址的对象 返回不可改变的表达式或值，例如a+b是一个常量，函数运行结果是一个右值 左值的示例\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include\u0026lt;stdio.h\u0026gt; void main() {\t// 声明变量a为int类型 int a; // a是一个左值，引用对象为int a = 1; // 左值a出现在右边的场景 int b = a; // 非法，a是左值 9 = a; // 左值，*p是指针，p就是值，*p+4是后面一个int类型的地址，是左值 int *p; int a = 10; p = \u0026amp;a; // 这里实际上是指针运算，p+0为自己，p存储指针 *p为值，那么a=10000,p=10000 *(p+0) = 10000; printf(\u0026#34;%p\\n\u0026#34;,a); printf(\u0026#34;%p\\n\u0026#34;,*p); printf(\u0026#34;%d\\n\u0026#34;,a); printf(\u0026#34;%d\\n\u0026#34;,*p); } 右值的示例\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // 声明变量a b int a = 1, b; // 非法，a+1为常量，不是左值 a + 1 = b; // 声明指针变量 p q int *p, *q; // *p, *q 为左值 *p = 1; // 合法，左值可以赋值 // 非法 - \u0026#34;p + 2\u0026#34; 是右值 p + 2 = 18; q = p + 5; // \u0026#34;p + 5\u0026#34; 是右值，合法 // 解引用表达式是左值 *(p + 2) = 18; p = \u0026amp;b; int arr[20]; // 数组元素访问arr[12] = *(arr+12) 所以是左值有效 struct S { int m; }; struct S obj; // obj and obj.m are lvalues // ptr-\u0026gt; 等于 (*ptr).m 是左值有效 一元表达式需要有左值，当a是左值\u0026amp;a才生效，12本身是一个右值，不能\u0026amp;12\nc 1 2 3 int a, *p; // a 和 *p都是左值 p = \u0026amp;a; // 合法，\u0026amp;a是常量为右值，p是左值 \u0026amp;a = p; // 缺少左值，非法 三元表达式是一个右值（C++是左值）\nc 1 ( x \u0026lt; y ? y : x) = 10; // c无效，c++有效 左值 VS 右值\n左值为内存中可识别的对象，右值为一个常量（广义上，不是const） 左值可以在左边和右边，右值必须在右边 右值必须有左值才生效 指针运算可左可右，变量运算是右值 arithmetic [6] C语言中，指针支持四种算术运算符，吧地址当作数值进行算数运算\n运算符 说明 = 可以将值赋值给指针 + 从指针加整数值以指向不同的内存位置。 - 从指针中减去整数值以指向不同的内存位置 比较运算（==, !=, \u0026lt;, \u0026gt;, \u0026lt;= , \u0026gt;=） 仅比较两个指针地址，例如\npointer == NULL ++ 指针使用递增运算符将向前位移一位 \u0026ndash; 指针使用递减运算符将向后位移一位 当对指针变量进行递增和递减操作时，会改变指针变量本身所在地址空间 当对指针变量进行+-运算时，不会改变指针变量本身 指针数组 指针数组是指数组存储的内容是指针，即数组内所有的元素都是指针\nc 1 2 3 4 5 6 7 8 #include\u0026lt;stdio.h\u0026gt; void main() {\tint a = 10; int b = 20; int c = 30; int *arr[] = {\u0026amp;a, \u0026amp;b, \u0026amp;c}; } 指针数组本质也是一个多级指针，例如一个2D数组每行(rows) 存储的值是一列(colums)的地址\nc 1 2 3 4 5 6 7 8 #include\u0026lt;stdio.h\u0026gt; void main() {\tint a[] = { 10 }; int b[] = { 20 }; int c[] = { 30 }; int *arr[] = {a, b, c}; } Pointer VS Array sizeof sizeof(array) 返回数组中所有元素占用内存的大小 sizeof(pointer) 只返回指针变量本身用内存的大小 \u0026amp;运算符 数组名是 \u0026amp;array[0] 的别名，返回数组中第一个元素的地址 \u0026amp;pointer 返回指针的地址 指针变量可以赋值，而数组变量不可以 数组是收集了相同类型元素的集合，而指针是一个存储地址的变量 多级指针 [7] 一个指针用于存储变量的地址，而另一个指针用于存储第一个指针的地址，这种指针被称为多级指针 (Multi-Pointer or Pointer to Pointer)\n声明多级指针必须在指针变量名称前多家一个 ”*“\nc 1 int **p; 通过示例更好的了解多级指针\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include \u0026lt;stdio.h\u0026gt; // C program to demonstrate pointer to pointer int main() { int var = 123; // pointer for var int *ptr1; // double pointer for ptr2 int **ptr2; // third pointer for ptr2 int ***ptr3; // storing address of var in ptr1 ptr1 = \u0026amp;var; // Storing address of ptr2 in ptr1 ptr2 = \u0026amp;ptr1; // Storing address of ptr3 in ptr2 ptr3 = \u0026amp;ptr2; // Displaying value of var using // both single and double pointers printf(\u0026#34;Value of var = %d\\n\u0026#34;, var ); printf(\u0026#34;Value of var using single pointer = %d\\n\u0026#34;, *ptr1 ); printf(\u0026#34;Value of var using double pointer = %d\\n\u0026#34;, **ptr2); printf(\u0026#34;Value of var using third pointer = %d\\n\u0026#34;, ***ptr3); return 0; } 输出结构\nc 1 2 3 4 Value of var = 123 Value of var using single pointer = 123 Value of var using double pointer = 123 Value of var using third pointer = 123 Note：\n多级指针，不能跨越定义，即二级指针必须拥有一级指针才可以 此时的 *ptr 可以是左值或右值 作为左值时，存储数据到该变量存储的地址空间内 作为右值时，取出该空间内的内容 c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include \u0026lt;stdio.h\u0026gt; // C program to demonstrate pointer to pointer int main() { int var = 123; // pointer for var int *ptr1; // double pointer for ptr2 int **ptr2; // third pointer for ptr2 int ***ptr3; // storing address of var in ptr1 ptr1 = \u0026amp;var; // Storing address of ptr2 in ptr1 ptr2 = \u0026amp;ptr1; // Storing address of ptr3 in ptr2 ptr3 = \u0026amp;ptr2; // Displaying value of var using // both single and double pointers ***ptr3 = 100; printf(\u0026#34;l-value test, the result of value = %d\\n\u0026#34;, var ); printf(\u0026#34;l-value test, the result of ptr1 = %d\\n\u0026#34;, *ptr1); printf(\u0026#34;l-value test, the result of ptr2 = %d\\n\u0026#34;, **ptr2); printf(\u0026#34;l-value test, the result of ptr3 = %d\\n\u0026#34;, ***ptr3); var = (***ptr3+1); printf(\u0026#34;r-value test, the result of value = %d\\n\u0026#34;, var ); return 0; } 输出结果为\nbash 1 2 3 4 5 l-value test, the result of value = 100 l-value test, the result of ptr1 = 100 l-value test, the result of ptr2 = 100 l-value test, the result of ptr3 = 100 r-value test, the result of value = 101 指针和函数 指向普通数据类型的指针 指针可以被当作函数参数传递，会改变原有的变量值\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include \u0026lt;stdio.h\u0026gt; void swap(int *n1, int *n2); int main() { int num1 = 5, num2 = 10; // address of num1 and num2 is passed swap( \u0026amp;num1, \u0026amp;num2); printf(\u0026#34;num1 = %d\\n\u0026#34;, num1); printf(\u0026#34;num2 = %d\u0026#34;, num2); return 0; } void swap(int* n1, int* n2) { int temp; temp = *n1; *n1 = *n2; *n2 = temp; } 指向函数的指针 [8] C语言中，指针也可以被指向一个函数，下面代码是一个指向函数的指针\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include \u0026lt;stdio.h\u0026gt; // 定义一个无返回值的常规函数 void fun(int a) { printf(\u0026#34;Value of a is %d\\n\u0026#34;, a); } int main() { // fun_ptr是一个指针类型，他指向函数fun的地址 void (*fun_ptr)(int) = \u0026amp;fun; /* 也可以写为如下代码 void (*fun_ptr)(int); fun_ptr = \u0026amp;fun; */ // 调用指向函数的指针 (*fun_ptr)(10); return 0; } 声明指针函数函数的说明，通常情况下声明函数语法为 int foo(int); 则代表声明了一个foo函数，具有int类型参数和int类型的返回值，而在中间加一个 ”*\u0026quot; 则可以表示一个指针函数的定义 int * foo(int); 这种类型是错误的。\n因为在c语言中，* 的优先级要高于 ()， 上面说到的情况则表示了声明一个foo函数，int类型的参数和 *int 类型的返回值。所以必须使用 () 改变其优先级\nc 1 int (*foo)(int); 函数指针数组 函数指针数组是指元素为函数指针的数组，有些特殊的地方是，定义时不能定义为数组指针，需要定义为函数指针，函数指针数组也可以替代switch\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include \u0026lt;stdio.h\u0026gt; void add(int a, int b) { printf(\u0026#34;Addition is %d\\n\u0026#34;, a+b); } void subtract(int a, int b) { printf(\u0026#34;Subtraction is %d\\n\u0026#34;, a-b); } void multiply(int a, int b) { printf(\u0026#34;Multiplication is %d\\n\u0026#34;, a*b); } int main() { // fun_ptr_arr is an array of function pointers void (*fun_ptr_arr[])(int, int) = {add, subtract, multiply}; unsigned int ch, a = 15, b = 10; printf(\u0026#34;Enter Choice: 0 for add, 1 for subtract and 2 \u0026#34; \u0026#34;for multiply\\n\u0026#34;); scanf(\u0026#34;%d\u0026#34;, \u0026amp;ch); if (ch \u0026gt; 2) return 0; (*fun_ptr_arr[ch])(a, b); return 0; } 关于函数指针的说明：\n普通指针指向的数据，函数指针指向的是代码 函数名代表函数的地址 函数去指不用加取址符 “\u0026amp;”，函数指针变量调用不用加“ * ” 函数指针可以作为参数，也可以作为返回值 数组与函数 数组作为参数时：数组作为函数参数传入时，传递不再是整个数组，而是数组的第一元素的地址，也就是指针，此时不能用size()获取数组的元素，获取到的时指针类型的大小。 数组作为返回值时：不允许返回数组，返回的是数组第一个元素指针，sizeof() 查看的大小也是指针类型的大小 main函数的参数 在C语言中main()函数之前提供了一个函数 _start()，但通常情况下 main() 是作为程序执行的第一个函数。main() 函数提供了两个参数，argc 和 argv 。\nargc 命令行传入的参数数量，int类型 argv 命令行传入的实际参数，参数索引从1开始，0为程序本身名称 正常情况下声明main函数： main(int argc, char *argv[]) **argv 是 *argv[] 的另一种表现方式 main(int argc, char **argv) Notes：这里有一个比较难理解的地方就是二级指针作为形参来替换指针数组。\n由于二级指针变量存放为一个一级指针的地址，而数组名本身是数组首元素的地址，其后的每一个元素都是指针就是将首元素指针传入。由于上面讲到数组作为参数传入时传入的是指针而不是数组本身。所以 *argv[] 与 **argv 是等价的。\nReference ​[1] c pointer\n​[2] pointer type\n​[3] const pointer\n​[4] pointer VS array\n​[5] lvalue VS rvlaue\n​[6] pointer arithmetic\n​[7] pointer to pointer\n​[8] function pointer in c\n","permalink":"https://www.161616.top/ch05-pointer/","summary":"指针 指针声明 [1] 指针/指针变量 (pointer) 是用于存储地址的变量\n使用 \u0026amp; 运算符 来访问变量的地址。例如\nc 1 2 3 4 5 6 7 #include \u0026lt;stdio.h\u0026gt; void main() { int a = 100; printf(\u0026#34;%x\u0026#34;, \u0026amp;a); } 输出结果为 16进制的内存地址\nc 1 61fe1c 使用地址运算符 * 可以从变量地址中获取变量的值，这个行为被称为间接引用/解引用(indirection/dereferencing)。例如：\nc 1 2 3 4 5 6 7 8 9 #include \u0026lt;stdio.h\u0026gt; void main() { int a = 100; printf(\u0026#34;%d\u0026#34;, *(\u0026amp;a)); // 也可以写为，因为*与\u0026amp;优先级相同，从右到左的顺序，所以有没有()意思是相同的 printf(\u0026#34;%d\u0026#34;, *\u0026amp;a); } 输出结果为 100\n指针变量 指针变量是指存储一个变量的地址的变量，可以使用符号 * 来修饰变量，定义语法为：","title":"ch05 指针"},{"content":"Overview 在编写程序时包含任意指令如，已初始化和未初始化数据，局部变量，函数等都是用于动态分配内存的指令。当程序编译后（默认生成 x.out 文件）这是一个可执行的链接文件( Executable and linking format)。在执行时这些不组织成几部分，包含不同的内存分段 (segments)\nELF：这是系统中标准二进制格式，其一些功能包含，动态链接，动态加载，对程序运行时控制。\n可以使用 size {ELF_file} 查看被分配的每个段的大小（Linux操作系统）；\ndec 列给出的是这个程序 text + data + bss 段的总大小，用十进制表示 text 段是存储可执行命令的段 data 段包含所有初始化数据，全局与静态变量 BSS 段包含未初始化数据 bash 1 2 3 $ size 1 text data bss dec hex filename 1843 584 8 2435 983 1 Memory Layout in C [1] 在C语言中内存布局模型包含六个部分\n命令行参数 (Command Line Arguments) 栈 (Stack) 堆 (Heap) 未初始化数据段 (Uninitialized Data Segment BSS) 已初始化数据段 (Initialized Data Segment) 文本/代码段 (Text/Code Segment) 这6部分结构可以再划分为两种类型：\n静态内存结构 (Static Memory Layout)：包含代码段, 数据段 动态内存结构 (Dynamic Memory Layout)：包含栈, 堆 通过Overview中可以看到可以执行文件包含一些段，而缺少一些段，这部分是由运行时构建出来的。\n整个C程序的内存布局为下图所示\n图：memory layout C Source：https://hackthedeveloper.com/memory-layout-c-program/\n静态内存布局 静态内存布局中，包含代码段(Code Segment)，数据段(Data Segment)；数据段中又分为已初始化段，通常称为数据分段(DS)，未初始化分段(BSS)。\n代码段 代码段包含可执行的机器指令，这部分包含了程序的逻辑，为了防止堆, 栈的溢出，代码段在内存结构中处于布局中最下方。而且为了防止指令被修改，这部分是只读的。\n已编译二进制文件 只读段，防止程序被修改 可共享 可以通过 objdump -S \u0026lt;file\u0026gt; 来导出代码段中存的汇编代码\n已初始化数据段 所有已初始化的静态变量和全局变量都被存储在DS中，该段具有写权限，程序可以在运行时修改该段中变量的值。\n定义一个C程序，通过size观看其data段的大小\nc 1 2 3 4 5 #include \u0026lt;stdio.h\u0026gt; char main() { return \u0026#39;1\u0026#39;; } size输出为528\nc 1 2 3 $ size 1 text data bss dec hex filename 1358 528 8 1894 766 1 通过增加两个变量，一个全局变量一个静态变量，观看编译后可执行文件data段的大小与之前大小相比较\nc 1 2 3 4 5 6 7 8 9 #include \u0026lt;stdio.h\u0026gt; static int a = 10; // int 类型占4byte char b = \u0026#39;a\u0026#39;; // char 类型占1byte char main() { return \u0026#39;1\u0026#39;; } size查看data输出值为533 与之前 528 增加 5 bytes，与定义的类型相符合\nc 1 2 3 size 1 text data bss dec hex filename 1358 533 3 1894 766 1 未初始化数据段 未初始化数据段包含如下内容\n未初始化的全局和静态变量 初始化为0或空指针的变量 接着上述例子，添加两个变量，一个不初始化值，一个初始化为0\nc 1 2 3 4 5 6 7 8 #include \u0026lt;stdio.h\u0026gt; int a; static int b=0; char main() { return \u0026#39;1\u0026#39;; } 通过 size 命令可以看出，这些都被分配到 BSS 部分\nc 1 2 3 $ size 1 text data bss dec hex filename 1358 528 16 1902 76e 1 对于初始化段与未初始化段和只读数据段 (.rodata) 都会被分配到数据段中\n动态内存布局 动态内存是指程序运行时创建的的内存\nheap 堆段是由BSS往上更高部分动态内存分配的段，heap段具有以下特点\n程序运行可以没有heap段\nheap位于在BSS之上stack之下，与stack成反方向增长和减少\n运行时分配内存\n由函数 malloc() , calloc() , free() 等函数管理\nheap段内存由进程中共享库和动态模块等共享内存\nheap对于stack来说，最大的特点就是没有自动的内存管理功能，所有内存的申请和销毁都是通过开发者自行定义的，C中的Glibc API 提供了申请和销毁heap内存的功能。\n函数 malloc() / calloc() 用户空间实现的库函数，用于申请heap内存，可用于windows/linux 函数 free() 释放由 malloc() / calloc() 申请的内存 brk() / sbrk() 是linux下的系统调用，在内核空间实现的库函数 下列代码为heap内存分配示例\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; int func() { int a = 10; int *aptr = \u0026amp;a; int *ptr = (int *)malloc(sizeof(int)); *ptr = 20; printf(\u0026#34;Heap Memory Value = %d\\n\u0026#34;, *ptr); printf(\u0026#34;Pointing in Stack = %d\\n\u0026#34;, *aptr); free(ptr); } int main() { func(); return 0; } 下图为上述代码的图形化布局，通过申明一个指针变量 *ptr 指向了通过函数 malloc() 申请的 heap内存\nNotes: heap变量的存储实际存储时在物理内存上，而heap,stack.. 都是虚拟内存中某个进程的地址空间，通过MMU将其转为物理地址进行读写。[2]\n图：heap layout of C Source：https://hackthedeveloper.com/memory-layout-c-program/\nstack stack是与heap相邻的地区，并与heap以反方向增长，当遇到heap时表示可用内存耗尽。stack段具有如下特点：\n程序运行必须拥有的内存段 以先进先出 (LIFO) 的顺序添加和移除数据 包含以下内容 所有局部变量 函数参数（逆序） 函数调用的返回地址 基于指针的函数调用 stack段自动分配和销毁内存，开发者无法控制stack段内存 当函数执行完函数局部变量会从stack中弹出释放，也就是局部变量的作用域范围 例如\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include int sum(int a, int b) { return a + b; } float avg(int a, int b) { int s = sum(a, b); return (float)s / 2; } int main() { int a = 10; int b = 20; printf(\u0026#34;Average of %d, %d = %f\\n\u0026#34;, a, b, avg(a, b)); return 0; } 下图是上述代码对于stack内存段执行时的说明，如图所示，整个如下：\n当main函数被执行时会被压入stack中 main函数会调用avg函数求平均值，此时avg被压入stack avg执行sum函数，sum被压入stack 此时正在执行的帧时位于最顶层的，被称为基指针 (base pointer) 栈帧指向stack段顶部，存储stack最顶部地址 s是一个指针保存着sum的位置，即sum函数的结尾，依次类推 图：stack layout of C Source：https://hackthedeveloper.com/memory-layout-c-program/\n栈异常 栈异常常见异常情况有\n栈溢出 (Stack Overflow)：栈溢出是指超出stack的大小，例如很长的函数调用，造成该错误原因如下： 递归函数调用 大数据声明 栈毁坏 (Stack Corruption)：是指stack段中的某些内存位置由于错误的编码而被无意访问，导致内存位置发生变化。由于数据毁坏位置发生在Stack段因此被称为 “Stack Corruption” [4] 例如下面代码模拟了一个 SC 异常\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; int copy(char *argv) { char name[10]; strcpy(name, argv); } int main(int argc, char **argv) { copy(argv[1]); printf(\u0026#34;Exit\\n\u0026#34;); return 0; } 输出结果为：可以看到当char大小大于10，会覆盖其他stack位置，使程序无法继续执行从而输出“Exit”。\nbash 1 2 3 4 5 $ gcc .\\1.c -o 1.exe $ .\\1.exe testargs Exit $ .\\1.exe testargs000000000000000000 Stack VS Heap [3] Stack与Heap都存储与RAM中 Stack自动管理内存，而Heap则需要手动申请和取消 Stack分配速度快（一段程序启动时预先分配好的连续内存），而Heap分配速度较慢（动态分配的非连续内存） Stack在使用是会出现溢出问题，而Heap可以分配大数据 Stack常见错误为内存溢出，Heap常见错误为内存泄漏 Stack 和 Heap 的一些常见问题\n默认Stack大小为多少：Linux中通过 ulimit -s 可以查看 默认的Heap大小为多少：没有默认的Heap大小，在32位操作系统中，每个进程可以看到连续的4GB空间，这个空间没有被映射到物理地址中，而是根据使用情况进行映射，在64位操作系统中，这个空间会更大 stack和heap存放在哪里？：在虚拟内存中，通过MMU进行映射到物理地址上 如何手动配置heap？：可以使用 ulimit -v 设置虚拟内存的大小 函数调用栈 函数调用与栈有不可密切的关系，在一个函数调用过程所需要的信息一般包括以下几个方面：\n函数返回地址 函数参数 变量 保存的上下文 ：包括在函数调用前后需要保持不变的寄存器。 当在调用一个函数时，控制流从调用函数转移到被调用函数。如下列代码在运行时产生了如下几项疑问：\n函数参数和用于调用函数的变量的区别 为什么具有多个相同名称但位于不同函数中的变量可以共存？ 为什么函数doing有一定的限制？ 为什么未初始化的局部变量可能包含任何值？ c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include \u0026lt;stdio.h\u0026gt; int mogrify(int a, int b){ int tmp = a*4 - b / 3; return tmp; // (mogrify函数返回值) } double truly_half(int x){ double tmp = x / 2.0; return tmp; } int main(){ int a = 7, y = 17; int mog = mogrify(a,y); // 调用mogrify printf(\u0026#34;Done with mogrify\\n\u0026#34;); double x = truly_half(y); // 调用truly_half printf(\u0026#34;Done with truly_half\\n\u0026#34;); a = mogrify(x, mog); // 第二次调用mogrify printf(\u0026#34;Results: %d %lf\\n\u0026#34;,mog,x); // (last_print) return 0; // (main函数返回) } 输出\nbash 1 2 3 4 5 lila [stack-demo-code]% gcc simple_calls.c lila [stack-demo-code]% ./a.out Done with mogrify Done with truly_half Results: 23 8.500000 栈行为 [4] main函数的调用\n上述代码调用stack发生的变化，程序从第一行的 main() 函数开始。 main() 有 3 个局部变量：a,y 是int，x 是double。栈的初始状态如下表（其中地址栏为虚构地址）\nMethod Line Var Value Addr Notes main() 12行开始 a ? 1024 y ? 1028 mod ? 1032 x ? 1036 表1\nmain函数的第一行被执行\n在运行 main（从第 12 行开始）时，会为所有局部变量分配了栈空间，但没有定义值（随机被初始化）在向下移动时，为局部变量a,y 定义了值。\nMethod Line Var Value Addr Notes main() 13行 a 7 1024 y 17 1028 mod ? 1032 x ? 1036 表2\nmogrify()被调用 在第13行时产生一个函数调用，main函数被暂停，至函数 mogrify 完成。函数调用使一个栈push到调用栈，如下表所示。\nMethod Line Var Value Addr Notes main() 13行 a 7 1024 y 17 1028 mod ? 1032 x ? 1036 mogrify() 4行 a 7 1044 b 17 1048 tmp ? 1052 表3\nmogrify()第一行被执行 此时从 mogrify 的第一行开始，完成后返回至 main 函数，将在第 13 行继续执行。表3中由于没有执行到tmp，所以还没被分配值。\n表4是 完成mogrify 函数执行，局部变量 tmp 被赋值。\nMethod Line Var Value Addr Notes main() 13行 a 7 1024 y 17 1028 mod ? 1032 x ? 1036 mogrify() 5行 a 7 1044 b 17 1048 tmp 23 1052 表4\nmogrify()函数返回\nmogrify函数返回在这里有两个作用：\n返回值被存储在调用函数位置：main函数 （第 13 行）变量 mog 中。 弹出栈帧，从调用堆栈中移除。 此时状态为表5\nMethod Line Var Value Addr Notes ain() | 4 | | | 024 | | | | | 7 | 028 | | | | od | 3 | 032 | | | | | | 036 | | 表5\n执行Printf()\nMethod Line Var Value Addr Notes ain() | 3行 | | | 024 | | | | | 7 | 028 | | | | od | | 032 | | | | | | 036 | | rintf() | ib call | ormat | | 044 | ointer | 表6\nprintf()也是作为函数，将另一个栈帧推入栈中，并为其参数和局部变量预留空间。 printf()是一个可变参数的函数。\n第二次函数调用\n从第 16 行起，调用了函数 truly_half 此时会将一个栈帧推入调用栈。此时状态如表5相同\n调用函数truly_half()\n当函数 truly_half() 被调用，对应的栈帧被push到main的栈帧下，表7中所示的地址（局部变量）与之前 mogrify() 函数是相同的地址，这是因为栈中的空间是可重用的。\nMethod Line Var Value Addr Notes main() | 16 | a | 7 | 024 | | | | y | 17 | 028 | | | | og | 23 | 032 | | | | x | ? | 036 | | truly_half()` | 8 | x | 17 | 044 | | | | mp | ? | 048 | | 表7\ntruly_half()被执行\n执行 truly_half 函数的第二行返回计算后的值来赋值给 main 中的局部变量 x，并从调用栈中弹出 truly_half 的栈帧，如表8所示\nMethod Line Var Value Addr Notes main() | 16 | a | 7 | 024 | | | | y | 17 | 028 | | | | og | 23 | 032 | | | | x | ? | 036 | | truly_half()` | 9 | x | 17 | 044 | | | | mp | 8.5 | 048 | | 表8\n返回main函数控制流\nmain函数中会打印这个值，此时内存结构为表8所示\nMethod Line Var Value Addr Notes main() 17 a 7 1024 y 17 1028 mog 23 1032 x 8.5 1036 表9\n再次调用函数mogrify()\n此时，main函数在第19行暂停，在 mogrify() 第一行开始。\n需要注意的一点是 mogrify() 参数类型是int，这里会强制转换 8 字节double 为一个 4 字节的int，小数点被省去。如表10所示\nMethod Line Var Value Addr Notes main() | 19 | a | 7 | 024 | | | | y | 17 | 028 | | | | og | 23 | 032 | | | | x | 8.5 | 036 | double | ogrify() | 4 | a | 8 | 044 | onvert to int | | | b | 23 | 048 | | | | mp | ? | 052 | | 表10\nmogrify()被执行后：\nMethod Line Var Value Addr Notes main() | 19 | a | 7 | 024 | | | | y | 17 | 028 | | | | og | 23 | 032 | | | | x | 8.5 | 036 | double | ogrify() | 5 | a | 8 | 044 | onvert to int | | | b | 23 | 048 | | | | mp | 25 | 052 | | 表11\nmogrify()被执行后： mogrify() 执行完成后将结果分配给 main 函数中的局部变量 a 并弹出栈帧。此时数据如表12所示\nMethod Line Var Value Addr Notes ain() | 19 | a | 25 | 024 | 的值被覆盖 | | | y | 17 | 028 | | | | og | 23 | 032 | | | | x | 8.5 | 036 | double | 表12\n至此返回最开始的部分，一个函数的过程包含四个部分 (function call stack)\n函数调用栈 动态分配内存区域 (heap) 存储全局变量的区域 程序允许的实际代码(data text) 栈帧(stack frame) 指的是：栈内存中单个函数调用（正在允许的函数）的一部分内存块，（参数和局部变量）。编译器在编译期间确定函数的栈帧大小。栈上的栈帧通常与尚未返回的函数一样多。\n栈行为：\npushing ：当函数被调用时，新的帧被推到调用堆栈的 “顶部”。 popping：当函数执行完成，会将控制权返回给调用它的函数。并将函数关联的帧从栈顶部弹出。 溢出：如果在返回之前调用了太多函数（例如递归），程序可能会耗尽栈空间。\n关于栈的总结 C语言的执行模型 C 语言是过程式编程，不支持在函数之外编写代码 C 语言的执行模型是指函数调用工作原理（函数调用栈行为分析的）及函数工作原理。 C 语言使用 ”栈“ 数据结构来实现函数与函数调用。 关于函数调用栈 函数调用栈是动态数据结构，用于参数传递、局部变量分配、保存调用的返回地址、保存寄存器以供恢复。\n栈向下增长，从较高的地址开始，向较低的地址。\nPush 将栈帧添加到栈，Pop从栈中弹出\n栈帧的增长在x86架构下是4字节：\n假设栈指位于1000，此时push一个函数，则该栈指指向996(1000 - 4) 假设此时弹出函数，那么会从996处从栈中弹出，并递增并指向地址1000 调用惯例 [6] 调用惯例 (Calling Conventions) 是指函数调用的标准化方法，当在函数调用时例如，如何将参数传递给子程序？子程序可以覆盖寄存器中的值，还是调用者希望保留寄存器内容？子程序中的局部变量应该存储在哪里？函数应该如何返回结果？\nC语言中调用惯例在很大程度上使用了基于硬件支持栈。对C中调用惯例的理解就需要对函数执行模型的理解（应确保完全理解 push、pop、call 和 ret 指令的行为）。在此调用约定中，子程序参数在stack上传递。寄存器保存在stack上，子程序使用的局部变量放在stack上的内存中。\ncdecl (c declaration)：C/C++默认调用约定，调用时按照从右向左的参数入\nc 1 2 3 4 5 push arg3 ; rightmost argument push arg2 push arg1 ; leftmost argument call f add esp, 12 ; 12 = 3 arguments each being 4 bytes fastcall：通过寄存器传递值（从右到左）\nthiscall：指针类型被存储在寄存器 ecx ，其他类型放置堆栈\nReference ​[1] memory layout c program\n​[2] stack and heap locations in ram\n​[3] what and where are the stack and heap\n​[4] understanding stack corruption c\n​[5] static\n​[6] calling convention\n","permalink":"https://www.161616.top/ch06-memory-layout/","summary":"Overview 在编写程序时包含任意指令如，已初始化和未初始化数据，局部变量，函数等都是用于动态分配内存的指令。当程序编译后（默认生成 x.out 文件）这是一个可执行的链接文件( Executable and linking format)。在执行时这些不组织成几部分，包含不同的内存分段 (segments)\nELF：这是系统中标准二进制格式，其一些功能包含，动态链接，动态加载，对程序运行时控制。\n可以使用 size {ELF_file} 查看被分配的每个段的大小（Linux操作系统）；\ndec 列给出的是这个程序 text + data + bss 段的总大小，用十进制表示 text 段是存储可执行命令的段 data 段包含所有初始化数据，全局与静态变量 BSS 段包含未初始化数据 bash 1 2 3 $ size 1 text data bss dec hex filename 1843 584 8 2435 983 1 Memory Layout in C [1] 在C语言中内存布局模型包含六个部分\n命令行参数 (Command Line Arguments) 栈 (Stack) 堆 (Heap) 未初始化数据段 (Uninitialized Data Segment BSS) 已初始化数据段 (Initialized Data Segment) 文本/代码段 (Text/Code Segment) 这6部分结构可以再划分为两种类型：","title":"ch06 内存布局"},{"content":"Overview C语言中复合类型 (composite type) 是指用户自定义类型，通常由多种元素组成的类型，其元素被紧密存储在内存中。C语言常见的复合类型有：\n数组 字符串 结构体 联合类型 结构体 [1] 结构体 (structure) 是指用户定义的数据类型，允许将不同类型的多个元素组合在一起，来创建出更复杂的数据类型，类似于数组，但又区别于数组，数组只能保存同类型的元素，而结构体可以保存不同类型的元素。\n定义 声明结构体的语法如下\nc 1 2 3 4 5 6 struct structureName { dataType memberVariable1; datatype memberVariable2; ... } variable01, variable02...; 这里需要注意的一些地方：\nstruct是关键字，structureName定义的新数据类型，variable{}是作为使用 structureName 声明的新变量名 每个成员方法结尾都是 “;\u0026quot; 而不是逗号 ”,\u0026quot; 结构体不能递归 变量可以有多个 例如声明一个学生的结构体，而student是作为一个新的数据类型存在\nc 1 2 3 4 5 6 struct student { char name[20]; int roll; char gender; }; Notes：在定义（创建）结构体变量前，结构体成员不会占用内存\n声明 使用结构体声明变量\n也可以一次性定义结构体和声明变量\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 struct student { char name[20]; int roll; char gender; } stu1,stu2; // 结构体名称可以省略 struct { char name[20]; int roll; char gender; } stu1,stu2; 赋值 在声明结构体后，student结构体只是自定义数据结构，要使用还需要进行初始化，或者赋值\nc 1 stu1 = {\u0026#34;zhangsan\u0026#34;, 20, 0}; 或者\ntext 1 stu1.name=\u0026#34;zhangsan\u0026#34;; 或者\nc 1 2 3 4 5 6 7 struct Student { char name[25]; int age; char branch[10]; char gender; }stu1 = {\u0026#34;zhangsan\u0026#34;, 20, 0}; 或者使用不同顺序进行初始化\nc 1 stu1 = {.age=20, .gender=0, .name=\u0026#34;zhangsan\u0026#34;}; 也可以仅初始化部分成员，未初始化的成员应该按顺序在后位\nc 1 stu1 = {\u0026#34;zhangsan\u0026#34;}; 访问 访问结构体可以使用符号 ”.“ 来访问，成员名称==.==成员属性\nc#include\u0026lt;stdio.h\u0026gt; #include\u0026lt;string.h\u0026gt; struct Student { char name[25]; int age; char branch[10]; char gender; }; int main() { struct Student s1; s1.age = 18; strcpy(s1.name, \u0026#34;Viraaj\u0026#34;); printf(\u0026#34;Name of Student 1: %s\\n\u0026#34;, s1.name); printf(\u0026#34;Age of Student 1: %d\\n\u0026#34;, s1.age); return 0; } 也可以使用scanf() 赋值\n结构体运算 结构体不能够执行算术运算符 +, -, x, ÷ ，关系运算符 \u0026lt; \u0026gt; \u0026lt;= \u0026gt;=, 等式运算符，但是可以在两个相同结构体变量的场景下进行赋值运算。\nc 1 2 3 4 5 6 7 8 /* 无效的操作 */ st1 + st2 st1 - st2 st1 == st2 st1 != st2 etc. /* 在相同类型下的结构体，操作是有效的 */ st1 = st2 因为C语言没有提供比较运算，所以没法进行结构体比较，需要自行比较结构体成员来比较结构体是否一样\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; struct student { char name[20]; double roll; char gender; int marks[5]; }st1,st2; void main() { struct student st1= { \u0026#34;Alex\u0026#34;, 43, \u0026#39;M\u0026#39;, {76, 78, 56, 98, 92}}; struct student st2 = { \u0026#34;Max\u0026#34;, 33, \u0026#39;M\u0026#39;, {87, 84, 82, 96, 78}}; if( strcmp(st1.name,st2.name) == 0 \u0026amp;\u0026amp; st1.roll == st2.roll) printf(\u0026#34;Both are the records of the same student.\\n\u0026#34;); else printf(\u0026#34;Different records, different students.\\n\u0026#34;); /* Copiying the structure variable */ st2 = st1; if( strcmp(st1.name,st2.name) == 0 \u0026amp;\u0026amp; st1.roll == st2.roll) printf(\u0026#34;\\nBoth are the records of the same student.\\n\u0026#34;); else printf(\u0026#34;\\nDifferent records, different students.\\n\u0026#34;); } 输出结果\nc 1 2 3 Different records, different students. Both are the records of the same student. 结构体数组 结构体数组是指数组元素是结构体，例如下面声明一个类型为student的数组\nc 1 2 3 4 5 6 7 8 9 struct student { char name[20]; double roll; char gender; int marks[5]; }; struct student stu[4]; 初始化和访问可以通过循环进行\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 for (int i = 0; i \u0026lt; 4; i++) { printf(\u0026#34;Enter name:\\n\u0026#34;); scanf(\u0026#34;%s\u0026#34;,\u0026amp;stu[i].name); printf(\u0026#34;Enter roll:\\n\u0026#34;); scanf(\u0026#34;%d\u0026#34;,\u0026amp;stu[i].roll); printf(\u0026#34;Enter gender:\\n\u0026#34;); scanf(\u0026#34; %c\u0026#34;,\u0026amp;stu[i].gender); for( int j = 0; j \u0026lt; 5; j++) { printf(\u0026#34;Enter marks of %dth subject:\\n\u0026#34;,j); scanf(\u0026#34;%d\u0026#34;,\u0026amp;stu[i].marks[j]); } printf(\u0026#34;\\n-------------------\\n\\n\u0026#34;); } /* Finding the average marks and printing it */ for(int i = 0; i \u0026lt; 4; i++) { float sum = 0; for( int j = 0; j \u0026lt; 5; j++) { sum += stu[i].marks[j]; } printf(\u0026#34;Name: %s\\nAverage Marks = %.2f\\n\\n\u0026#34;, stu[i].name, sum / (sizeof(stu[i].marks) / sizeof(stu[i].marks[0]))); } 将代码整合为一起\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include \u0026lt;stdio.h\u0026gt; struct student { char name[20]; double roll; char gender; int marks[5]; }; struct student stu[4]; void main() { for (int i = 0; i \u0026lt; 4; i++) { printf(\u0026#34;Enter name:\\n\u0026#34;); scanf(\u0026#34;%s\u0026#34;,\u0026amp;stu[i].name); printf(\u0026#34;Enter roll:\\n\u0026#34;); scanf(\u0026#34;%d\u0026#34;,\u0026amp;stu[i].roll); printf(\u0026#34;Enter gender:\\n\u0026#34;); scanf(\u0026#34; %c\u0026#34;,\u0026amp;stu[i].gender); for( int j = 0; j \u0026lt; 5; j++) { printf(\u0026#34;Enter marks of %dth subject:\\n\u0026#34;,j); scanf(\u0026#34;%d\u0026#34;,\u0026amp;stu[i].marks[j]); } printf(\u0026#34;\\n-------------------\\n\\n\u0026#34;); } /* Finding the average marks and printing it */ for(int i = 0; i \u0026lt; 4; i++) { float sum = 0; for( int j = 0; j \u0026lt; 5; j++) { sum += stu[i].marks[j]; } printf(\u0026#34;Name: %s\\nAverage Marks = %.2f\\n\\n\u0026#34;, stu[i].name, sum / (sizeof(stu[i].marks) / sizeof(stu[i].marks[0]))); } } 结构体嵌套结构体 嵌套结构体表示，结构体的成员是另外一个结构体\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct date { int date; int month; int year; }; struct student { char name[20]; int roll; char gender; int marks[5]; struct date birthday; }; 其定义语法为：struct \u0026lt;other struct\u0026gt; \u0026lt;member_name\u0026gt;; 这里 birthday 是名为data类型结构体\nNotes：结构体内部不能嵌套自己\n访问嵌套结构体和正常结构体访问一样使用符号，成员名称==.==成员属性==.==成员属性\nc 1 2 3 4 stu1.birthday.date stu1.birthday.month stu1.birthday.year stu1.name 结构体内存分配 结构体声明后是不占用内存，只有被初始化后才占用内存，结构体内每个成员会被分配到连续的内存内，sizeof()的大小是每个元素所占用的大小。\n示例代码为一个student的结构体，有四个成员，name为20 bytes的字符串，roll是4字节的int类型，gender是1字节的char，marks为5个元素的数组，那么这个结构体的总大小应该为 $20+4+1+5\\times4$\nc 1 2 3 4 5 6 7 struct student { char name[20]; int roll; char gender; int marks[5]; } stu1; 将上述代码整合为\nc 1 2 3 4 5 6 7 8 9 void main() { printf(\u0026#34;Sum of the size of members = %I64d bytes\\n\u0026#34;, sizeof(stu1.name) + sizeof(stu1.roll) + sizeof(stu1.gender) + sizeof(stu1.marks)); printf(\u0026#34;Using sizeof() operator = %I64d bytes\\n\u0026#34;,sizeof(stu1)); } // 输出结果为 Sum of the size of members = 45 bytes Using sizeof() operator = 48 bytes 可以看到两个结果并不相等，可以看出实际被多分配了3个字节，需要知道为什么被多分配需要先打印他们的地址\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include \u0026lt;stdio.h\u0026gt; struct student { char name[20]; int roll; char gender; int marks[5]; } stu1; void main() { printf(\u0026#34;Address of member name = %d\\n\u0026#34;, \u0026amp;stu1.name); printf(\u0026#34;Address of member roll = %d\\n\u0026#34;, \u0026amp;stu1.roll); printf(\u0026#34;Address of member gender = %d\\n\u0026#34;, \u0026amp;stu1.gender); printf(\u0026#34;Address of member marks = %d\\n\u0026#34;, \u0026amp;stu1.marks); } 输出结果为\nbash 1 2 3 4 Address of member name = 4225408 Address of member roll = 4225428 Address of member gender = 4225432 Address of member marks = 4225436 可以看到char类型占用一个字节，而接下来的成员 marks 却是从4225436开始的，而不是4225433。这就需要引入下面的概念数据对齐 (Data alignment)\n数据对齐 数据对齐是指处理器在数据对齐时访问效率最高，这将代表了数据存储在内存中的大小的倍数。而现代计算机字长通常为4 字节（32 位操作胸痛）或 8 字节（64 位操作系统）的字长。\n对于一个int类型的变量，占用的资产时4字节，此时符合处理器读取机制，因为符合计算机字长长度。而作为char类型，占用一个字节。如果不做数据对齐操作，就会出现如下图出现的问题，数据在存储时读取的字长永远是多一个步骤的。\n下图是一个错位的数据，粉红代表char类型，蓝色代表short类型，绿色代表int类型，如果不进行对齐，再继续存储int时，在读取数据时一个字长位移都将不足以读取一个int类型，这就需要进行两次数据访问才能读取一个int类型，也就是花费了两倍的时间\n图：Misaligned memory Source：https://hps.vi4io.org/_media/teaching/wintersemester_2013_2014/epc-14-haase-svenhendrik-alignmentinc-paper.pdf\n出于上述原因才有了数据对齐的概念，下图所示的对齐模式被称为自然对齐 (naturally aligned)\n图：Properly aligned memory using padding Source：https://hps.vi4io.org/_media/teaching/wintersemester_2013_2014/epc-14-haase-svenhendrik-alignmentinc-paper.pdf\n内容填充 在对齐时所插入的额外字节数的部分被称为填充 (padding)，在上图中，黑色部分为填充的部分，而在上述代码示例中所填充的部分为3字节，而4225433位的内存地址存储int类型（marks[0]的地址）不是4的倍数。\n下表说明了需要对其的数据类型规则\n数据类型 占字节数大小 地址倍数 char 1 1的倍数 short 2 2的倍数 int, float 4 4的倍数 double, long, *(pointer) 8 8的倍数 long double 16 16的倍数 另外一个示例，应该是多少？\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include \u0026lt;stdio.h\u0026gt; struct student { int i1; double d1; char c1; } stu1; void main() { // long long int a, b, c; // a = 1, b = 30000000000009, c = 5; // %I64d是微软风格的%lld，为了避免大于4字节的类型被省略，而输出异常，兼容%d // printf(\u0026#34;%I64d %I64d %I64d\\n\u0026#34;, a, b, c); // rintf(\u0026#34;%d %d %d\\n\u0026#34;, a, b, c); printf(\u0026#34;size = %I64d bytes\\n\u0026#34;,sizeof(stu1)); } 由于 i1 为int类型4字节，d1 为 double类型8字节，c1 为char类型1字节 ，那么 $4+8+1=13$ 被填充后应该是16字节，那么看下输出结果\ntext 1 size = 24 bytes 实际上在C语言中结构体的数据类型对齐不是这么计算的，实际上结构体数据对齐条件是根据结构体内最大的元素进行调整 [2]，例如这里最大元素为8，那么对齐标准就是补足8字节 i1 需要补4，c1 需要补7\n通过调整结构体顺序可以减少填充的大小，例如下列代码，其实际大小为1 byte + 8 bytes + 1 bytes = 10 bytes，而实际大小为24bytes，因为double将影响填充的大小\ntext 1 2 3 4 5 struct Foo { char x; // 1 byte double y // 8 bytes char z; // 1 bytes }; 而通过按照类型的由小到大的顺序进行定义成员，可以减少填充的次数与大小，这样1+1+(6)+8=16\ntext 1 2 3 4 5 struct Foo { char x; // 1 byte char z; // 1 bytes double y // 8 bytes }; 为此得出的结论为，对结构体成员重新排序可以提高内存效率\n数据打包 数据打包 (Packing) 是指强制编译器不进行数据填充，与数据填充是相反的作用\n在windows上使用宏定义 #pragma pack(1) 来指定对齐方式，也可以使用 __attribute__((packed)) 指定一个结构体补进行填充。\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;stdio.h\u0026gt; // #pragma pack(1) struct student { int i1; double d1; char c1; } stu1; struct Foo { char x; // 1 byte char z; // 1 bytes double y // 8 bytes } __attribute__((packed)) f1; void main() { printf(\u0026#34;size = %I64d bytes\\n\u0026#34;,sizeof(stu1)); printf(\u0026#34;size = %I64d bytes\\n\u0026#34;,sizeof(f1)); } 输出结果为\nbash 1 2 size = 24 bytes size = 10 bytes 还可以指定特定的大小进行填充，例如\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;stdio.h\u0026gt; // #pragma pack(1) struct student { int i1; double d1; char c1; } stu1; struct Foo { char x; // 1 byte char z; // 1 bytes double y // 8 bytes } __attribute__((packed, aligned(4))) f1; void main() { printf(\u0026#34;size = %I64d bytes\\n\u0026#34;,sizeof(stu1)); printf(\u0026#34;size = %I64d bytes\\n\u0026#34;,sizeof(f1)); } 输出结果为\nc 1 2 size = 24 bytes size = 12 bytes 指针结构体 这里包含指针作为结构体成员和指针指向结构体\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include \u0026lt;stdio.h\u0026gt; struct student { char *name; int *roll; char gender; int marks[5]; }; void main() { int alexRoll = 44; struct student stu1 = { \u0026#34;Alex\u0026#34;, \u0026amp;alexRoll, \u0026#39;M\u0026#39;, { 76, 78, 56, 98, 92 }}; struct student *stu2 = \u0026amp;stu1; printf(\u0026#34;stu1 Name is %s\\n\u0026#34;, stu1.name); // 无效的访问 // printf(\u0026#34;stu1 roll is %s\\n\u0026#34;, stu1.(*roll)); // 错误的访问，输出的地址 printf(\u0026#34;stu1 roll is %d\\n\u0026#34;, stu1.roll); // 正确的访问方式 printf(\u0026#34;stu1 roll is %d\\n\u0026#34;, *(stu1.roll)); // 访问指针结构体成员的方法 printf(\u0026#34;stu2 Name is %s\\n\u0026#34;, stu2-\u0026gt;name); printf(\u0026#34;stu2 Name is %s\\n\u0026#34;, (*stu2).name); } 总结：\n. 运算符优先于 * 运算符，需要加括号改变优先级\n如果成员属性是指针类型，访问其内容应先解引用成员 *(stu1.roll)\n如果指针是结构体需要解引用结构体 (*stu2).name\n指针类型访问成员的特殊方法为 -\u0026gt;\n结构体数组 结构体也可以作为数组的形式，每个数组元素为一个结构体。作为数组结构体时，指针类型需要解引用或者使用 -\u0026gt; 来访问。\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include \u0026lt;stdio.h\u0026gt; struct student { char *name; int *roll; char gender; int marks[5]; }; void main() { int alexRoll = 44; struct student stu1 = { \u0026#34;Alex\u0026#34;, \u0026amp;alexRoll, \u0026#39;M\u0026#39;, { 76, 78, 56, 98, 92 }}; struct student stu[10]; struct student *stuPtr = stu; struct student (*stuPtr)[10] = \u0026amp;stu; printf(\u0026#34;name %s\\n\u0026#34;, stuPtr[10]-\u0026gt;name); printf(\u0026#34;name %s\\n\u0026#34;, (*stuPtr)[10].name); } 结构体函数 在C语言中，函数不能作为结构体成员，但是函数指针可以，使用 . 可以调用指针函数成员\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include \u0026lt;stdio.h\u0026gt; struct example { int i; void (*ptrMessage)(int i); }; void message(int); void message(int i) { printf(\u0026#34;Hello, I\u0026#39;m a member of a structure. This structure also has an integer with value %d\u0026#34;, i); } void main() { struct example eg1 = {6, message}; eg1.ptrMessage(eg1.i); } 结构体作为函数参数 当函数参数过多时，传递大量参数效率很低，可以将结构体作为参数传递给函数\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include \u0026lt;stdio.h\u0026gt; struct student { char name[20]; int roll; char gender; int marks[5]; }; void display(struct student a) { printf(\u0026#34;Name: %s\\n\u0026#34;, a.name); printf(\u0026#34;Roll: %d\\n\u0026#34;, a.roll); printf(\u0026#34;Gender: %c\\n\u0026#34;, a.gender); for(int i = 0; i \u0026lt; 5; i++) printf(\u0026#34;Marks in %dth subject: %d\\n\u0026#34;,i,a.marks[i]); } void main() { struct student stu1 = {\u0026#34;Alex\u0026#34;, 43, \u0026#39;M\u0026#39;, {76, 98, 68, 87, 93}}; display(stu1); } 如果结构体比较复杂，传递副本参数效率不高，也可以传递指针结构体作为函数参数\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include \u0026lt;stdio.h\u0026gt; struct student { char name[20]; int roll; char gender; int marks[5]; }; void display(struct student *a) { printf(\u0026#34;Name: %s\\n\u0026#34;, a-\u0026gt;name); printf(\u0026#34;Roll: %d\\n\u0026#34;, a-\u0026gt;roll); printf(\u0026#34;Gender: %c\\n\u0026#34;, a-\u0026gt;gender); for(int i = 0; i \u0026lt; 5; i++) printf(\u0026#34;Marks in %dth subject: %d\\n\u0026#34;,i,a-\u0026gt;marks[i]); } void main() { struct student stu1 = {\u0026#34;Alex\u0026#34;, 43, \u0026#39;M\u0026#39;, {76, 98, 68, 87, 93}}; struct student *stuPtr = \u0026amp;stu1; display(stuPtr); } 结构体作为函数返回值 结构体可以作为函数的返回值\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include \u0026lt;stdio.h\u0026gt; struct student { char name[20]; int roll; char gender; int marks[5]; }; struct student increaseBy5(struct student p) { for( int i =0; i \u0026lt; 5; i++) if(p.marks[i] + 5 \u0026lt;= 100) { p.marks[i]+=5; } return p; } void main() { struct student stu1 = {\u0026#34;Alex\u0026#34;, 43, \u0026#39;M\u0026#39;, {76, 98, 68, 87, 93}}; stu1 = increaseBy5(stu1); printf(\u0026#34;Name: %s\\n\u0026#34;, stu1.name); printf(\u0026#34;Roll: %d\\n\u0026#34;, stu1.roll); printf(\u0026#34;Gender: %c\\n\u0026#34;, stu1.gender); for(int i = 0; i \u0026lt; 5; i++) printf(\u0026#34;Marks in %dth subject: %d\\n\u0026#34;,i,stu1.marks[i]); } 当然如果结构体交复杂，也可以用结构体指针作为返回值\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; struct student { char name[20]; int roll; char gender; int marks[5]; }; struct student* increaseBy5(struct student *p) { for( int i =0; i \u0026lt; 5; i++) if(p-\u0026gt;marks[i] + 5 \u0026lt;= 100) { p-\u0026gt;marks[i]+=5; } return p; } void main() {\tstruct student stu1 = {\u0026#34;Alex\u0026#34;, 43, \u0026#39;M\u0026#39;, {76, 98, 68, 87, 93}}; struct student *stuptr = (struct student *) malloc(sizeof(struct student)); stuptr = \u0026amp;stu1; stuptr = increaseBy5(stuptr); printf(\u0026#34;Name: %s\\n\u0026#34;, stuptr-\u0026gt;name); printf(\u0026#34;Roll: %d\\n\u0026#34;, stuptr-\u0026gt;roll); printf(\u0026#34;Gender: %c\\n\u0026#34;, stuptr-\u0026gt;gender); for(int i = 0; i \u0026lt; 5; i++) printf(\u0026#34;Marks in %dth subject: %d\\n\u0026#34;,i,stuptr-\u0026gt;marks[i]); } typedef typedef 是C语言中的关键字，功能是为现有数据类型分配别名，例如为long类型声明一个别名\nc 1 typedef long int64 也可以在结构体中使用\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; struct employee { char *name; int salary; } emp; typedef struct employee1 { char *name; int salary; }emp1; void main() {\t// 不使用typedef定义的结构体,在使用时需要加关键字struct struct employee e1; e1.salary=10; e1.name=\u0026#34;zhangsan\u0026#34;; // 使用typedef定义结构体,在使用时，可以直接使用结构体名称 emp1 e2; e2.salary=100; e2.name=\u0026#34;lisi\u0026#34;; printf(\u0026#34;name1: %s\\n\u0026#34; ,e1.name); printf(\u0026#34;salary1: %d\\n\u0026#34; ,e1.salary); printf(\u0026#34;name2: %d\\n\u0026#34; ,*(e2.name)); printf(\u0026#34;salary2: %d\\n\u0026#34; ,e2.salary); } typedef主要功能：\n别名，简化结构体类型struct关键字\n区分数据类型\nc 1 char * p1,p2; // 声明两个变量 p1为char指针类型，p2为char类型 c 1 2 typedef char* charPtr; charPtr p1,p2; // 声明两个变量为char*类型 提高代码的可移植性\nc 1 2 3 4 typedef long long int64; typedef long long int32; // 在大量别名情况下无需每个替换 int64 a=10; int64 b=20; union union是类似于结构体的一种用户自定义类型，与结构体最大的区别是，结构体是存储一系列元素的联合体，而union是多个成员，仅有一个元素能被存储。\n定义 定义union语法：\nunion \u0026lt;attr-spec-seq(optional)\u0026gt; \u0026lt;name(optional)\u0026gt; { struct-declaration-list } \u0026lt;union var,\u0026hellip;\u0026gt; union \u0026lt;attr-spec-seq(optional)\u0026gt; name c 1 2 3 4 5 union car { char name[50]; int price; }; 定义union不会被分配内存，如果要分配内存则需要创建变量使用它\n访问 c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include \u0026lt;stdio.h\u0026gt; union Job { float salary; int workerNo; } j; int main() { j.salary = 12.3; // 当对j.workerNo成员分配了值 // j.salary持有的12.3将不再拥有 j.workerNo = 100; printf(\u0026#34;Salary = %.1f\\n\u0026#34;, j.salary); printf(\u0026#34;Number of workers = %d\u0026#34;, j.workerNo); return 0; } 总结\nunion是用户自定义的数据类型 union中成员都是相同的内存地址 union保存的内容仅为最近一次赋值的元素的值（哪个元素被赋值，哪个元素被激活） union大小为其占用空间最大的那个成员 enum 枚举(enumeration)是C语言中特殊的数据类型，通常是包含具有共同性数据的集合，例如性别，男，女\nc 1 2 3 4 enum gender { MALE, FEMALE }; 声明 常用的有两种方式来使用枚举类型\nc 1 2 3 4 5 enum textEditor { BOLD, ITALIC, UNDERLINE } feature; 与\nc 1 2 3 4 5 6 7 8 9 enum textEditor { BOLD, ITALIC, UNDERLINE }; int main() { enum textEditor feature; return 0; } 赋值 c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 enum textEditor { BOLD, ITALIC, UNDERLINE } feature; int main() { // Initializing enum variable enum textEditor feature = BOLD; printf(\u0026#34;Selected feature is %d\\n\u0026#34;, feature); // Initializing enum with integer equivalent feature = 5; printf(\u0026#34;Selected feature is %d\\n\u0026#34;, feature); return 0; } 总结：\n枚举是整数常量类型 枚举包含的元素即为对应变量可以拥有的值 因为是整数常量，可以转换为char，bool等 枚举的元素结尾是逗号，最后一个元素没有符号；结构体的元素结尾为分号 Reference ​[1] structured data types in c explained\n​[2] Alignment in C\n","permalink":"https://www.161616.top/ch07-composite-type/","summary":"Overview C语言中复合类型 (composite type) 是指用户自定义类型，通常由多种元素组成的类型，其元素被紧密存储在内存中。C语言常见的复合类型有：\n数组 字符串 结构体 联合类型 结构体 [1] 结构体 (structure) 是指用户定义的数据类型，允许将不同类型的多个元素组合在一起，来创建出更复杂的数据类型，类似于数组，但又区别于数组，数组只能保存同类型的元素，而结构体可以保存不同类型的元素。\n定义 声明结构体的语法如下\nc 1 2 3 4 5 6 struct structureName { dataType memberVariable1; datatype memberVariable2; ... } variable01, variable02...; 这里需要注意的一些地方：\nstruct是关键字，structureName定义的新数据类型，variable{}是作为使用 structureName 声明的新变量名 每个成员方法结尾都是 “;\u0026quot; 而不是逗号 ”,\u0026quot; 结构体不能递归 变量可以有多个 例如声明一个学生的结构体，而student是作为一个新的数据类型存在\nc 1 2 3 4 5 6 struct student { char name[20]; int roll; char gender; }; Notes：在定义（创建）结构体变量前，结构体成员不会占用内存\n声明 使用结构体声明变量\n也可以一次性定义结构体和声明变量\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 struct student { char name[20]; int roll; char gender; } stu1,stu2; // 结构体名称可以省略 struct { char name[20]; int roll; char gender; } stu1,stu2; 赋值 在声明结构体后，student结构体只是自定义数据结构，要使用还需要进行初始化，或者赋值","title":"ch07 复合类型"},{"content":"文件类型 文件是指以字节的形式存储的数据源，使用C语言将文件数据以输出输出的形式处理叫做文件处理。\n文件在C语言中以两种形式存在：\n文本文件：文本文件是简单的文件类型，这些文件内容以 ASCII 字符格式存储信息。 二进制文件：二进制文件以 0 和 1 的二进制格式存储数据，不是人类可读的文件 文件指针 文件指针 (FILE) 是一种数据类型，是被定义在 stdio.h 中的一种结构体，包含了文件的一些信息\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 typedef struct { // fill/empty level of buffer int level; // File status flags unsigned flags; // File descripter char fd; // ungetc char if no buffer unsigned char hold; // buffer size int bsize; // data transfer buffer unsigned char *buffer; // Current active pointer unsigned char *curp; //Temporary file indicator unsigned istemp; //Used for validity checking short token; } FILE; // This is FILE object 文件指针通常被用于处理正在访问的文件，fopen() 是用于打开文件并返回文件的 FILE 指针，而后通过文件只恨进行I/O操作。fopen() 会发生下列事件：\n文件的内容被加载到缓冲区（操作系统层面） 在内存中创建 FILE 的数据结构体，并返回这个结构体指针 文件处理函数 函数 功能 fopen() 打开现有文件或新文件 fprintf() 将数据写入打开的文件 fscanf() 读取文件中数据 fputc() 向文件写入一个字符 fgetc() 从文件中读取一个字符 fclose() 关闭打开的文件 fseek() 设置文件指针的位置 fputw() 将一个整数写入到文件 fgetw() 从文件中读取一个整数 ftell() 文件指针的当前位置 rewind() 设置文件指针位置为初始位置 fread() 读取文件内容（二进制与文本） fwrite() 向文件写入内容（二进制与文本） feof() 是否到达文件结尾\n非0 True 到达文件结尾\n0 False 没有到达文件结尾 fscanf VS fgets fscanf读取的是字符，fgets读取的是字符串 fgets读取换行符结束，fscanf读取到空白就结束，不用换行符 fgets以行为单位，fscanf以字符为单位（参数2匹配的模式） fscanf每次会判断是否匹配，如不匹配则提前退出读取 c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; void main() {\tFILE *fp; fp = fopen(\u0026#34;./k8s restart.txt\u0026#34;, \u0026#34;r\u0026#34;); if (fp == NULL) { printf(\u0026#34;Error! opening file\u0026#34;); exit(1); } char buf[1024]; while( feof(fp) == 0 ){ printf(\u0026#34;i=%d\\n\u0026#34;, i); // fscanf(fp, \u0026#34;%s\u0026#34;, \u0026amp;buf); fgets(buf, 1024, fp); printf(\u0026#34;%s\\n\u0026#34;, buf); } fclose(fp); } 文件的打开模式 模式 含义 当文件不存在时处理方法 r 只读方式打开文件 当文件路径不存在时fopen()返回NULL rb 以只读方式打开二进制文件 当文件路径不存在时fopen()返回NULL w 写入方式 如果文件存在则覆盖，如果不存在则创建新文件 wb 写入方式（二进制模式） 如果文件存在则覆盖，如果不存在则创建新文件 a 打开文件并向结尾追加内容 如果文件路径不存在则创建新文件 ab 打开文件并向结尾追加内容（二进制模式） 如果文件路径不存在则创建新文件 r+ 读写方式打开文件 当文件路径不存在时fopen()返回NULL rb+ 读写方式打开文件（二进制模式） 当文件路径不存在时fopen()返回NULL w+ 读写方式打开文件 如果文件存在则覆盖，如果不存在则创建新文件 wb+ 读写方式打开文件（二进制模式） 如果文件存在则覆盖，如果不存在则创建新文件 a+ 追加和读取 如果文件路径不存在则创建新文件 ab+ 追加和读取（二进制模式） 如果文件路径不存在则创建新文件 文件操作的步骤 打开文件 fopen() （ FILE *Pointer）\n读写文件 fputc , fgetc, fputs, fgets, fread, fwrite \u0026hellip;.\n关闭文件 fclose()\n打开文件 c 1 2 FILE * ptr ptr = fopen(\u0026#34;file dir\u0026#34;,\u0026#34;mode\u0026#34;); 例如\nc 1 fopen(\u0026#34;/etc/hosts\u0026#34;,\u0026#34;rb\u0026#34;); 关闭文件 c 1 fclose(fptr); 读/写文件 向文本文件中写入数据\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; int main() { int num; FILE *fptr; fptr = fopen(\u0026#34;~/1.txt\u0026#34;,\u0026#34;w\u0026#34;); if(fptr == NULL) { printf(\u0026#34;Error!\u0026#34;); exit(1); } printf(\u0026#34;Enter num: \u0026#34;); scanf(\u0026#34;%d\u0026#34;,\u0026amp;num); fputc(str[i], fptr); // fputc向文件写入数据 fputs(\u0026#34;fputs向文件写入数据\\n\u0026#34;, fptr); fprintf(fptr, \u0026#34;fprintf向文件写入数据\\n\u0026#34;); fclose(fptr); return 0; } 从文本文件中读取内容\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; int main() { int num; FILE *fptr; if ((fptr = fopen(\u0026#34;~/1.txt\u0026#34;,\u0026#34;r\u0026#34;)) == NULL){ printf(\u0026#34;Error! opening file\u0026#34;); exit(1); } fscanf(fptr,\u0026#34;%d\u0026#34;, \u0026amp;num); printf(\u0026#34;Value of n=%d\u0026#34;, num); fclose(fptr); return 0; } 写入二进制文件 二进制文件的读取使用，fwrite()/fread()函数，通常情况下二进制文件读取没有意义，只是做类似文件拷贝的操作。\nfwrite(addressData, sizeData, numbersData, pointerToFile);\naddressData：写入磁盘的数据的地址 sizeData：要写入磁盘的数据大小 numbersData：写出的数据个数 pointerToFile：FILE指针 return： 成功：参数3的大小 失败：0 Notes：通常参数2为1，参数3为写入的总大小。 参2 * 参3 = 写入的总大小\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; struct threeNum { int n1, n2, n3; }; int main() { int n; struct threeNum num; FILE *fptr; if ((fptr = fopen(\u0026#34;C:\\\\program.bin\u0026#34;,\u0026#34;wb\u0026#34;)) == NULL){ printf(\u0026#34;Error! opening file\u0026#34;); // Program exits if the file pointer returns NULL. exit(1); } for(n = 1; n \u0026lt; 5; ++n) { num.n1 = n; num.n2 = 5*n; num.n3 = 5*n + 1; fwrite(\u0026amp;num, sizeof(struct threeNum), 1, fptr); } fclose(fptr); return 0; } 从二进制文件读取数据 fread(addressData, sizeData, numbersData, pointerToFile);：\naddressData：读取到的数据存储的位置 sizeData：一次读取的字节数 numbersData：读取次数 pointerToFile：文件指针 return： 成功：参数3的大小 失败：0 到达文件结尾：feof(fp)为真 c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; struct threeNum { int n1, n2, n3; }; int main() { int n; struct threeNum num; FILE *fptr; if ((fptr = fopen(\u0026#34;C:\\\\program.bin\u0026#34;,\u0026#34;rb\u0026#34;)) == NULL){ printf(\u0026#34;Error! opening file\u0026#34;); // Program exits if the file pointer returns NULL. exit(1); } for(n = 1; n \u0026lt; 5; ++n) { fread(\u0026amp;num, sizeof(struct threeNum), 1, fptr); printf(\u0026#34;n1: %d\\tn2: %d\\tn3: %d\\n\u0026#34;, num.n1, num.n2, num.n3); } fclose(fptr); return 0; } 缓冲区 缓冲区是操作系统的内存空间中的一部分。操作系统在内存空间中预留了一定的存储空间，在输入或输出达到一定量后进行I/O操作，这部分空间就叫做缓冲区。\n程序在启动时，预定义了三种缓冲区，不需要显式开启：\n标准输入 (stdin)： 标准输出 (stdout)： 标准错误 (stderr)：标准错误是一个无缓冲 stdio.h 库中提供了三种缓冲模式 [1]：\n无缓冲 (unbuffered)：写入到无缓冲的数据会立即被写入到文件 行缓冲 (line buffered)：当遇到换行符，此类缓冲区内容会被写入到文件 全缓冲 (fully buffered)：缓冲区满或以任意大小的块被写入到文件 可以通过库函数setvbuf(), setbuffer(), setbuf() 三者之一设置 stdio 的缓冲模式，例如\nc 1 2 3 4 5 6 7 #define BUF_SIZE 4096 static char buf[BUF_SIZE]; FILE *fp; fp = fopen(\u0026#34;test.txt\u0026#34;, \u0026#39;w\u0026#39;); if(setvbuf(fp, buf, _IOFBF, BUF_SIZE) !=0 ) exit(EXIT_FAILURE); 可以使用库函数 fflush() 手动刷新缓冲区 [2]，例如\nc 1 2 3 4 5 6 fp = fopen(\u0026#34;test.txt\u0026#34;, \u0026#39;w\u0026#39;); fputs(\u0026#34;fputs向文件写入数据\\n\u0026#34;, fptr); fflush(fp); fputs(\u0026#34;fputs向文件写入数据\\n\u0026#34;, fptr); fflush(fp); 如果，全缓冲模式下，缓冲区没满也没刷新，那么只有在文件关闭时， 缓冲区会被自动刷新（写入到文件）\nTips：内存的隐式回收：关闭文件、刷新缓冲区、释放malloc\nReference ​[1] IO cache\n​[2] Controlling Buffering\n","permalink":"https://www.161616.top/ch08-file-handling/","summary":"文件类型 文件是指以字节的形式存储的数据源，使用C语言将文件数据以输出输出的形式处理叫做文件处理。\n文件在C语言中以两种形式存在：\n文本文件：文本文件是简单的文件类型，这些文件内容以 ASCII 字符格式存储信息。 二进制文件：二进制文件以 0 和 1 的二进制格式存储数据，不是人类可读的文件 文件指针 文件指针 (FILE) 是一种数据类型，是被定义在 stdio.h 中的一种结构体，包含了文件的一些信息\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 typedef struct { // fill/empty level of buffer int level; // File status flags unsigned flags; // File descripter char fd; // ungetc char if no buffer unsigned char hold; // buffer size int bsize; // data transfer buffer unsigned char *buffer; // Current active pointer unsigned char *curp; //Temporary file indicator unsigned istemp; //Used for validity checking short token; } FILE; // This is FILE object 文件指针通常被用于处理正在访问的文件，fopen() 是用于打开文件并返回文件的 FILE 指针，而后通过文件只恨进行I/O操作。fopen() 会发生下列事件：","title":"ch08 文件处理"},{"content":"Principle of token bucket 随着互联网的发展，在处理流量的方法也不仅仅为 first-come，first-served，而在共享网络中实现流量管理的基本机制就是排队。而公平算法则是实现在优先级队列中基于哪些策略来排队的 “公平队列” 。Token Bucket 则是为公平排队提供了替代方案。Fair Queue 与 Token Bucket的区别主要在，对于Fair Queue来讲，如果请求者目前空闲，Queue会将该请求者的带宽分配给其他请求者；而 Token Bucket 则是分配给请求者的带宽是带宽的上限。\n通过例子了解算法原理\n假设出站带宽是 4个数据包/ms，此时有一个需求为，为一个特定的发送端 A 来分配 1个数据包/ms的带宽。此时可以使用公平排队的方法分给发送 A 25%的带宽。\n此时存在的问题是我们希望可以灵活地允许 A 的数据包以无规则的时间间隔发送。例如假设 A 在每个数据包发送后等待1毫秒后再开始下一个数据包的发送。\nsence1：此时假设 A 以 1ms 的间隔去发送数据包，而由于某种原因导致应该在 t=6 到达的数据包却在 t=6.5 到达。随后的数据包在 t=7 准时到达，在这种情况下是否应该保留到t=7.5？ sence2：或者是否允许在 t=6.5 发送一个迟到的数据包，在 t=7 发送下一个数据包，此时理论上平均速率仍然还是 1 个数据包/ms？ 显然sence2是合理的，这个场景的解决方法就是令牌桶算法，规定 A 的配额，允许指定平均速率和突发容量。当数据包不符合令牌桶规范，那么就认为其不合理，此时会做出一下相应：\ndelay，直到桶准备好 drop mark，标记为不合规的数据包 delay 被称为 整形 shaping , shaping 是指在某个时间间隔内发送超过 Bc（Committed Burst）的大小，Bc 在这里指桶的尺寸。由于数据流量是突发性的，当在一段时间内不活动后，再次激活后的在一个间隔内发送的数量大于 Bc ，那么额外的流量被称为Be （burst excess）。\n将流量丢弃或标记超额流量，保持在一个流量速率限制称为 “管制” policing。\nDefinition 令牌桶的定义是指，有一个桶，以稳定的速度填充令牌；桶中的任何一个溢出都会被丢弃。当要发送一个数据包，需要能够从桶中取出一个令牌；如果桶是空的那么此时数据包是不合规的数据包，必须进行 delay , drop , mark 操作。如果桶是满的，则会发送与桶容量相对应的突发（短时间内的高带宽传输），这是桶是空的。\n令牌桶的规范：$TB(r,B_{max})$\n$r$ ：r个token每秒的令牌填充率，表示桶填充令牌的速率 $B$ ：桶容量，$B_{mac} \u0026gt; 0$ 那么公式则表示，桶以指定的速率填充令牌，最大为 $B_{max}$ 。这就说明了为了使大小为 S 的数据包合规，桶内必须至少有 S 个令牌，即 $B \\ge S$，否则数据包不合规，在发送时，桶为 $B=B-S$\nExamples 场景1：假设令牌桶规范为 $TB(\\frac{1}{3}\\ packet/ms, 4\\ packet)$，桶最初是满的，数据包在以下时间到达 [0, 0, 0, 2, 3, 6, 9, 12]\n在处理完所有 T=0 的数据包后，桶中还剩 1 个令牌。到第四个数据包 T=2 到达时，桶内已经有1个令牌 + $\\frac{2}{3}$ 个令牌；当发送完第四个数据包时，桶内令牌数为 $\\frac{2}{3}$ 。到 T=3 数据包时，桶内令牌为1，满足发送第 5 个数据包。万松完成后桶是空的，在后面 6 9 12时，都满足3/ms 一个数据包，都可以发送成功\n场景2：另外一个实例，在同样的令牌桶规范下 $TB(\\frac{1}{3}, 4)$，数据包到达时间为 [0, 0, 0, 0, 12, 12, 12, 12, 24, 24, 24, 24] ，可以看到在这个场景下，数据到达为3个突发，每个突发4个数据包，此时每次发送完成后桶被清空，当再次填满时需要12ms，此时另外一组突发达。故这组数据是合规的。、\n场景3：在同样的令牌桶规范下 $TB(\\frac{1}{3}, 4)$，数据包到达时间为 [0, 1, 2, 3, 4, 5] , 这组数据是不合规的\n用表格形式表示如下：\n数据包到达时间 0 1 2 3 4 5 发送前桶内令牌 4 3 $\\frac{1}{3}$ 2 $\\frac{2}{3}$ 2 1 $\\frac{1}{3}$ $\\frac{2}{3}$ 发送后桶内令牌 3 2 $\\frac{1}{3}$ 1 $\\frac{2}{3}$ 1 $\\frac{1}{3}$ $\\frac{2}{3}$ 如果一个数据包在桶中没有足够的令牌来发送它时到达，可以进行整形或管制，整形使数据包等到足够的令牌积累。管制会丢弃数据包。或者发送方可以立即发送数据包，但将其标记为不合规。\nPrinciple of leaky bucket 漏桶 （leaky bucket）是一种临时存储可变数量的请求并将它们组织成设定速率输出的数据包的方法。漏桶的概念与令牌桶比起是相反的，漏桶可以理解为是一个具有恒定服务时间的队列。\n由下图可以看出，漏桶的概念是一个底部有孔的桶。无论水进入桶的速度是多少，它都会以恒定的速度通过孔从桶中泄漏出来。如果桶中没有水，则流速为零，如果桶已满，则多余的水溢出并丢失。\n和令牌桶一样，漏桶用于流量整形和流量管制\nDifference between Token and Leaky Leaky Token 桶中存放的是所有到达的数据包，必须入桶 桶中存放的是定期生成的令牌 桶以恒定速率泄漏 桶有最大容量 $B_{max}$ 突发流量入桶转换为恒定流量发送 发送数据包需要小号对应的token token较leaky的优势：\n在令牌桶中，如果桶已满，处理的方式有 shaping和policing两种模型三种方式（延迟、丢弃、标记），而漏桶中的流量仅为shaping。 通俗来说，就是令牌桶已满，丢弃的是令牌，漏桶中丢弃的则是数据包 令牌桶可以更快的速率发送大突发流量，而漏桶仅是恒定速率 Implementation with go Token 在golang中，内置的 rate 包实现了一个令牌桶算法，通过 rate.NewLimiter(r,B) 进行构造。与公式$TB(r,B_{max})$ 意思相同。\ngo 1 2 3 4 5 6 7 8 type Limiter struct { limit Limit // 向桶中放置令牌的速率 burst int // 桶的容量 mu sync.Mutex tokens float64 // 可用令牌容量 last time.Time // 上次放入token的时间 lastEvent time.Time } Limiter中带有三种方法， Allow、Reserve、Wait 分别表示Token Bucket中的 shaping 和 policing：\nAllow：丢弃超过速率的事件，类似 drop Wait：等待，直到获取到令牌或者取消或deadline/timeout Reserve：等待或减速，不丢弃事件，类似于 delay Reserve/ReserveN Reserve() 返回了 ReserveN(time.Now(), 1) ReserveN() 无论如何都会返回一个 Reservation，指定了调用者在 n 个事件发生之前必须等待多长时间。 Reservation 是一个令牌桶事件信息 Reservation 中的 Delay() 方法返回了需要等待的时间，如果时间为0则不需要等待 Reservation 中的 Cancel() 将取消等待 wait/waitN\nAllow/AllowN 在获取不到令牌是丢弃对应的事件 返回的是一个 reserveN() 拿到token是合规的，并消耗掉token AllowN 为截止到某一时刻，当前桶内桶中数目是否至少为 n 个，满足则返回 true，同时从桶中消费 n 个 token。反之不消费 Token，false。\ngo 1 2 3 func (lim *Limiter) AllowN(now time.Time, n int) bool { return lim.reserveN(now, n, 0).ok // 由于仅需要一个合规否，顾合规的通过，不合规的丢弃 } reserveN() 是三个行为的核心，AllowN中指定的为 0 ，因为 maxFutureReserve 是最大的等待时间，AllowN给定的是0，即如果突发大的情况下丢弃额外的 Bc。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation { lim.mu.Lock() if lim.limit == Inf { lim.mu.Unlock() return Reservation{ ok: true, lim: lim, tokens: n, timeToAct: now, } } // 这里拿到的是now，上次更新token时间和桶内token数量 now, last, tokens := lim.advance(now) // 计算剩余的token tokens -= float64(n) // Calculate the wait duration var waitDuration time.Duration if tokens \u0026lt; 0 { waitDuration = lim.limit.durationFromTokens(-tokens) } // 确定是否合规，n是token // token 的数量要小于桶的容量，并且 等待时间小于最大等待时间 ok := n \u0026lt;= lim.burst \u0026amp;\u0026amp; waitDuration \u0026lt;= maxFutureReserve // Prepare reservation r := Reservation{ ok: ok, lim: lim, limit: lim.limit, } if ok { r.tokens = n r.timeToAct = now.Add(waitDuration) } // Update state if ok { lim.last = now lim.tokens = tokens lim.lastEvent = r.timeToAct } else { lim.last = last } lim.mu.Unlock() return r } 在reserveN中调用了一个 advance() 函数，\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) { last := lim.last if now.Before(last) { // 计算上次放入token是否在传入now之前 last = now } // 当 last 很旧时，避免在下面进行 delta 溢出。 // maxElapsed 计算装满需要多少时间 maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens) elapsed := now.Sub(last) // 上次装入到现在的时差 if elapsed \u0026gt; maxElapsed { // 上次如果放入token时间超长，就让他与装满时间相等 elapsed = maxElapsed // 即，让桶为满的 } // 装桶的动作，下面函数表示，elapsed时间内可以生成多少个token delta := lim.limit.tokensFromDuration(elapsed) tokens := lim.tokens + delta // 当前的token if burst := float64(lim.burst); tokens \u0026gt; burst { tokens = burst // 这里表示token溢出，让他装满就好 } return now, last, tokens } wait/waitN 桶内令牌可以\u0026gt;N时，返回，在获取不到令牌是阻塞，等待context取消或者超时 返回的是一个 reserveN() 拿到token是合规的，并消耗掉token go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) { if n \u0026gt; lim.burst \u0026amp;\u0026amp; lim.limit != Inf { return fmt.Errorf(\u0026#34;rate: Wait(n=%d) exceeds limiter\u0026#39;s burst %d\u0026#34;, n, lim.burst) } // 外部已取消 select { case \u0026lt;-ctx.Done(): return ctx.Err() default: } // Determine wait limit now := time.Now() waitLimit := InfDuration if deadline, ok := ctx.Deadline(); ok { waitLimit = deadline.Sub(now) } // 三个方法的核心，这里给定了deatline r := lim.reserveN(now, n, waitLimit) if !r.ok { return fmt.Errorf(\u0026#34;rate: Wait(n=%d) would exceed context deadline\u0026#34;, n) } // Wait if necessary delay := r.DelayFrom(now) if delay == 0 { return nil } t := time.NewTimer(delay) defer t.Stop() select { case \u0026lt;-t.C: // We can proceed. return nil case \u0026lt;-ctx.Done(): // Context was canceled before we could proceed. Cancel the // reservation, which may permit other events to proceed sooner. r.Cancel() return ctx.Err() } } Dynamic Adjustment 在 rate.limiter 中，支持调整速率和桶大小，这样就可以根据现有环境和条件，来动态的改变 Token生成速率和桶容量\nSetLimit(Limit) 更改生成 Token 的速率 SetBurst(int) 改变桶容量 Example 一个流量整形的场景 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package main import ( \u0026#34;log\u0026#34; \u0026#34;strconv\u0026#34; \u0026#34;time\u0026#34; \u0026#34;golang.org/x/time/rate\u0026#34; ) func main() { timeLayout := \u0026#34;2006-01-02:15:04:05.0000\u0026#34; limiter := rate.NewLimiter(1, 5) // BT(1,5) log.Println(\u0026#34;bucket current capacity: \u0026#34; + strconv.Itoa(limiter.Burst())) length := 20 // 一共请求20次 chs := make([]chan string, length) for i := 0; i \u0026lt; length; i++ { chs[i] = make(chan string, 1) go func(taskId string, ch chan string, r *rate.Limiter) { err := limiter.Allow() if !err { ch \u0026lt;- \u0026#34;Task-\u0026#34; + taskId + \u0026#34; unallow \u0026#34; + time.Now().Format(timeLayout) } time.Sleep(time.Duration(5) * time.Millisecond) ch \u0026lt;- \u0026#34;Task-\u0026#34; + taskId + \u0026#34; run success \u0026#34; + time.Now().Format(timeLayout) return }(strconv.FormatInt(int64(i), 10), chs[i], limiter) } for _, ch := range chs { log.Println(\u0026#34;task start at \u0026#34; + \u0026lt;-ch) } } 通过执行结果可以看出，在突发为20的情况下，allow仅允许了获得token的事件执行，，这种场景下实现了流量整形的特性。\n一个流量管制的场景 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package main import ( \u0026#34;context\u0026#34; \u0026#34;log\u0026#34; \u0026#34;strconv\u0026#34; \u0026#34;time\u0026#34; \u0026#34;golang.org/x/time/rate\u0026#34; ) func main() { timeLayout := \u0026#34;2006-01-02:15:04:05.0000\u0026#34; limiter := rate.NewLimiter(1, 5) // BT(1,5) log.Println(\u0026#34;bucket current capacity: \u0026#34; + strconv.Itoa(limiter.Burst())) length := 20 // 一共请求20次 chs := make([]chan string, length) for i := 0; i \u0026lt; length; i++ { chs[i] = make(chan string, 1) go func(taskId string, ch chan string, r *rate.Limiter) { err := limiter.Wait(context.TODO()) if err != nil { ch \u0026lt;- \u0026#34;Task-\u0026#34; + taskId + \u0026#34; unallow \u0026#34; + time.Now().Format(timeLayout) } ch \u0026lt;- \u0026#34;Task-\u0026#34; + taskId + \u0026#34; run success \u0026#34; + time.Now().Format(timeLayout) return }(strconv.FormatInt(int64(i), 10), chs[i], limiter) } for _, ch := range chs { log.Println(\u0026#34;task start at \u0026#34; + \u0026lt;-ch) } } 结果可以看出，在大突发的情况下，在拿到token的任务会立即执行，没有拿到token的会等待拿到token后继续执行，这种场景下实现了流量管制的特性\nReference tokenbucket QoS Policing ","permalink":"https://www.161616.top/ch10-token-bucket-algorithm/","summary":"Principle of token bucket 随着互联网的发展，在处理流量的方法也不仅仅为 first-come，first-served，而在共享网络中实现流量管理的基本机制就是排队。而公平算法则是实现在优先级队列中基于哪些策略来排队的 “公平队列” 。Token Bucket 则是为公平排队提供了替代方案。Fair Queue 与 Token Bucket的区别主要在，对于Fair Queue来讲，如果请求者目前空闲，Queue会将该请求者的带宽分配给其他请求者；而 Token Bucket 则是分配给请求者的带宽是带宽的上限。\n通过例子了解算法原理\n假设出站带宽是 4个数据包/ms，此时有一个需求为，为一个特定的发送端 A 来分配 1个数据包/ms的带宽。此时可以使用公平排队的方法分给发送 A 25%的带宽。\n此时存在的问题是我们希望可以灵活地允许 A 的数据包以无规则的时间间隔发送。例如假设 A 在每个数据包发送后等待1毫秒后再开始下一个数据包的发送。\nsence1：此时假设 A 以 1ms 的间隔去发送数据包，而由于某种原因导致应该在 t=6 到达的数据包却在 t=6.5 到达。随后的数据包在 t=7 准时到达，在这种情况下是否应该保留到t=7.5？ sence2：或者是否允许在 t=6.5 发送一个迟到的数据包，在 t=7 发送下一个数据包，此时理论上平均速率仍然还是 1 个数据包/ms？ 显然sence2是合理的，这个场景的解决方法就是令牌桶算法，规定 A 的配额，允许指定平均速率和突发容量。当数据包不符合令牌桶规范，那么就认为其不合理，此时会做出一下相应：\ndelay，直到桶准备好 drop mark，标记为不合规的数据包 delay 被称为 整形 shaping , shaping 是指在某个时间间隔内发送超过 Bc（Committed Burst）的大小，Bc 在这里指桶的尺寸。由于数据流量是突发性的，当在一段时间内不活动后，再次激活后的在一个间隔内发送的数量大于 Bc ，那么额外的流量被称为Be （burst excess）。\n将流量丢弃或标记超额流量，保持在一个流量速率限制称为 “管制” policing。","title":"漏桶算法与令牌桶算法"},{"content":" 本文是关于深入理解Kubernetes网络原理系列第4章 深入理解Kubernetes Pod网络原理 - 网络名称空间 深入理解Kubernetes Pod网络原理 - Linux虚拟网络技术 深入理解Kubernetes Pod网络原理 - CNI 深入理解Kubernetes Pod网络原理 - 跟随 flannel 学习CNI原理 深入理解Kubernetes Pod网络原理 - 跟随 flannel + multus 剖析 Chained Plugins 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 1 (Shell) 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 2 (libcni) 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 1 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 2 深入理解Kubernetes Pod网络原理 - Pod网络排错思路 Overview 本文将引入一个思路：“在Kubernetes集群发生网络异常时如何排查”。文章将引入Kubernetes 集群中网络排查的思路，包含网络异常模型，常用工具，并且提出一些案例以供学习。\nPod常见网络异常分类 网络排查工具 Pod网络异常排查思路及流程模型 CNI网络异常排查步骤 案例学习 Pod网络异常 网络异常大概分为如下几类：\n网络不可达，主要现象为ping不通，其可能原因为：\n源端和目的端防火墙（iptables, selinux）限制 网络路由配置不正确 源端和目的端的系统负载过高，网络连接数满，网卡队列满 网络链路故障 端口不可达：主要现象为可以ping通，但telnet端口不通，其可能原因为：\n源端和目的端防火墙限制 源端和目的端的系统负载过高，网络连接数满，网卡队列满，端口耗尽 目的端应用未正常监听导致（应用未启动，或监听为127.0.0.1等） DNS解析异常：主要现象为基础网络可以连通，访问域名报错无法解析，访问IP可以正常连通。其可能原因为\nPod的DNS配置不正确 DNS服务异常 pod与DNS服务通讯异常 大数据包丢包：主要现象为基础网络和端口均可以连通，小数据包收发无异常，大数据包丢包。可能原因为：\n数据包的大小超过了 dockero，CNI 插件，或者宿主机网卡的 MTU 值。 可使用 ping -s 指定数据包大小进行测试 CNI异常：主要现象为Node可以通，但Pod无法访问集群地址，可能原因有：\nkube-proxy 服务异常，没有生成 iptables 策略或者 ipvs 规则导致无法访问 CIDR耗尽，无法为Node注入 PodCIDR 导致 CNI 插件异常 其他 CNI 插件问题 那么整个Pod网络异常分类可以如下图所示：\n图：Pod network trouble hirarchy\n总结一下，Pod最常见的网络故障有，网络不可达（ping不通）；端口不可达（telnet不通）；DNS解析异常（域名不通）与大数据包丢失（大包不通）。\n常用网络排查工具 在了解到常见的网络异常后，在排查时就需要使用到一些网络工具才可以很有效的定位到网络故障原因，下面会介绍一些网络排查工具。\ntcpdump [1] tcpdump网络嗅探器，将强大和简单结合到一个单一的命令行界面中，能够将网络中的报文抓取，输出到屏幕或者记录到文件中。\n各系统下的安装\nUbuntu/Debian: tcpdump ；apt-get install -y tcpdump Centos/Fedora: tcpdump ；yum install -y tcpdump Apline：tcpdump ；apk add tcpdump --no-cache 查看指定接口上的所有通讯\n语法\n参数 说明 -i [interface] -w [flle] 第一个n表示将地址解析为数字格式而不是主机名，第二个N表示将端口解析为数字格式而不是服务名 -n 不显示IP地址 -X hex and ASCII -A ASCII（实际上是以人类可读懂的包进行显示） -XX -v 详细信息 -r 读取文件而不是实时抓包 关键字 type host（主机名，域名，IP地址）, net, port, portrange direction src, dst, src or dst , src and ds protocol ether, ip，arp, tcp, udp, wlan 捕获所有网络接口 bash 1 tcpdump -D ####按IP查找流量\n最常见的查询之一 host，可以看到来往于 1.1.1.1 的流量。\nbash 1 tcpdump host 1.1.1.1 ####按源/目的 地址过滤\n如果只想查看来自/向某方向流量，可以使用 src 和 dst。\nbash 1 tcpdump src|dst 1.1.1.1 通过网络查找数据包 使用 net 选项，来要查找出/入某个网络或子网的数据包。\nbash 1 tcpdump net 1.2.3.0/24 使用十六进制输出数据包内容 hex 可以以16进制输出包的内容\nbash 1 tcpdump -c 1 -X icmp 查看特定端口的流量 使用 port 选项来查找特定的端口流量。\nbash 1 2 tcpdump port 3389 tcpdump src port 1025 查找端口范围的流量 bash 1 tcpdump portrange 21-23 过滤包的大小 如果需要查找特定大小的数据包，可以使用以下选项。你可以使用 less，greater。\nbash 1 2 3 tcpdump less 32 tcpdump greater 64 tcpdump \u0026lt;= 128 捕获流量输出为文件 -w 可以将数据包捕获保存到一个文件中以便将来进行分析。这些文件称为PCAP（PEE-cap）文件，它们可以由不同的工具处理，包括 Wireshark 。\nbash 1 tcpdump port 80 -w capture_file 组合条件 tcpdump也可以结合逻辑运算符进行组合条件查询\nAND and or \u0026amp;\u0026amp;\nOR or or ||\nEXCEPT not or !\nbash 1 2 3 4 tcpdump -i eth0 -nn host 220.181.57.216 and 10.0.0.1 # 主机之间的通讯 tcpdump -i eth0 -nn host 220.181.57.216 or 10.0.0.1 # 获取10.0.0.1与 10.0.0.9或 10.0.0.1 与10.0.0.3之间的通讯 tcpdump -i eth0 -nn host 10.0.0.1 and \\(10.0.0.9 or 10.0.0.3\\) 原始输出 并显示人类可读的内容进行输出包（不包含内容）。\nbash 1 2 tcpdump -ttnnvvS -i eth0 tcpdump -ttnnvvS -i eth0 IP到端口 让我们查找从某个IP到端口任何主机的某个端口所有流量。\nbash 1 tcpdump -nnvvS src 10.5.2.3 and dst port 3389 去除特定流量 可以将指定的流量排除，如这显示所有到192.168.0.2的 非ICMP的流量。\nbash 1 tcpdump dst 192.168.0.2 and src net and not icmp 来自非指定端口的流量，如，显示来自不是SSH流量的主机的所有流量。\nbash 1 tcpdump -vv src mars and not dst port 22 选项分组 在构建复杂查询时，必须使用单引号 '。单引号用于忽略特殊符号 () ，以便于使用其他表达式（如host, port, net等）进行分组。\nbash 1 tcpdump \u0026#39;src 10.0.2.4 and (dst port 3389 or 22)\u0026#39; 过滤TCP标记位 TCP RST\nThe filters below find these various packets because tcp[13] looks at offset 13 in the TCP header, the number represents the location within the byte, and the !=0 means that the flag in question is set to 1, i.e. it’s on.\nbash 1 2 tcpdump \u0026#39;tcp[13] \u0026amp; 4!=0\u0026#39; tcpdump \u0026#39;tcp[tcpflags] == tcp-rst\u0026#39; TCP SYN\nbash 1 2 tcpdump \u0026#39;tcp[13] \u0026amp; 2!=0\u0026#39; tcpdump \u0026#39;tcp[tcpflags] == tcp-syn\u0026#39; 同时忽略SYN和ACK标志的数据包\nbash 1 tcpdump \u0026#39;tcp[13]=18\u0026#39; TCP URG\nbash 1 2 tcpdump \u0026#39;tcp[13] \u0026amp; 32!=0\u0026#39; tcpdump \u0026#39;tcp[tcpflags] == tcp-urg\u0026#39; TCP ACK\nbash 1 2 tcpdump \u0026#39;tcp[13] \u0026amp; 16!=0\u0026#39; tcpdump \u0026#39;tcp[tcpflags] == tcp-ack\u0026#39; TCP PSH\nbash 1 2 tcpdump \u0026#39;tcp[13] \u0026amp; 8!=0\u0026#39; tcpdump \u0026#39;tcp[tcpflags] == tcp-push\u0026#39; TCP FIN\nbash 1 2 tcpdump \u0026#39;tcp[13] \u0026amp; 1!=0\u0026#39; tcpdump \u0026#39;tcp[tcpflags] == tcp-fin\u0026#39; 查找http包 查找 user-agent 信息\nbash 1 tcpdump -vvAls0 | grep \u0026#39;User-Agent:\u0026#39; 查找只是 GET 请求的流量\nbash 1 tcpdump -vvAls0 | grep \u0026#39;GET\u0026#39; 查找http客户端IP\nbash 1 tcpdump -vvAls0 | grep \u0026#39;Host:\u0026#39; 查询客户端cookie\nbash 1 tcpdump -vvAls0 | grep \u0026#39;Set-Cookie|Host:|Cookie:\u0026#39; 查找DNS流量 bash 1 tcpdump -vvAs0 port 53 查找对应流量的明文密码 bash 1 tcpdump port http or port ftp or port smtp or port imap or port pop3 or port telnet -lA | egrep -i -B5 \u0026#39;pass=|pwd=|log=|login=|user=|username=|pw=|passw=|passwd= |password=|pass:|user:|username:|password:|login:|pass |user \u0026#39; wireshark追踪流 wireshare追踪流可以很好的了解出在一次交互过程中都发生了那些问题。\nwireshare选中包，右键选择 “追踪流“ 如果该包是允许的协议是可以打开该选项的\n关于抓包节点和抓包设备 如何抓取有用的包，以及如何找到对应的接口，有以下建议\n抓包节点：\n通常情况下会在==源端==和==目的端==两端同时抓包，观察数据包是否从源端正常发出，目的端是否接收到数据包并给源端回包，以及源端是否正常接收到回包。如果有丢包现象，则沿网络链路上各节点抓包排查。例如，A节点经过c节点到B节点，先在AB两端同时抓包，如果B节点未收到A节点的包，则在c节点同时抓包。\n抓包设备：\n对于 Kubernetes 集群中的Pod，由于容器内不便于抓包，通常视情况在Pod数据包经过的veth设备，docker0 网桥，CNI 插件设备（如cni0，flannel.1 etc..）及Pod所在节点的网卡设备上指定Pod IP进行抓包。选取的设备根据怀疑导致网络问题的原因而定，比如范围由大缩小，从源端逐渐靠近目的端，比如怀疑是 CNI 插件导致，则在 CNI 插件设备上抓包。从pod发出的包逐一经过veth设备，cni0 设备，flannel0，宿主机网卡，到达对端，抓包时可按顺序逐一抓包，定位问题节点。\n需要注意在不同设备上抓包时指定的源目IP地址需要转换，如抓取某Pod时，ping {host} 的包，在 veth 和 cni0 上可以指定 Pod IP抓包，而在宿主机网卡上如果仍然指定Pod IP会发现抓不到包，因为此时Pod IP已被转换为宿主机网卡IP。\n下图是一个使用 VxLAN 模式的 flannel 的跨界点通讯的网络模型，在抓包时需要注意对应的网络接口\n图：VxLAN in kubernetes\nnsenter nsenter是一款可以进入进程的名称空间中。例如，如果一个容器以非 root 用户身份运行，而使用 docker exec 进入其中后，但该容器没有安装 sudo 或未 netstat ，并且您想查看其当前的网络属性，如开放端口，这种场景下将如何做到这一点？nsenter 就是用来解决这个问题的。\nnsenter (namespace enter) 可以在容器的宿主机上使用 nsenter 命令进入容器的命名空间，以容器视角使用宿主机上的相应网络命令进行操作。==当然需要拥有 root 权限==\n各系统下的安装 [2]\nUbuntu/Debian: util-linux ；apt-get install -y util-linux Centos/Fedora: util-linux ；yum install -y util-linux Apline：util-linux ；apk add util-linux --no-cache nsenter 的使用语法为，nsenter -t pid -n \u0026lt;commond\u0026gt;，-t 接 进程ID号，-n 表示进入名称空间内，\u0026lt;commond\u0026gt; 为执行的命令。更多的内容可以参考 [3]\n实例：如我们有一个Pod进程ID为30858，进入该Pod名称空间内执行 ifconfig ，如下列所示\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $ ps -ef|grep tail root 17636 62887 0 20:19 pts/2 00:00:00 grep --color=auto tail root 30858 30838 0 15:55 ? 00:00:01 tail -f $ nsenter -t 30858 -n ifconfig eth0: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1480 inet 192.168.1.213 netmask 255.255.255.0 broadcast 192.168.1.255 ether 5e:d5:98:af:dc:6b txqueuelen 0 (Ethernet) RX packets 92 bytes 9100 (8.8 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 92 bytes 8422 (8.2 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo: flags=73\u0026lt;UP,LOOPBACK,RUNNING\u0026gt; mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 loop txqueuelen 1000 (Local Loopback) RX packets 5 bytes 448 (448.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 5 bytes 448 (448.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 net1: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 10.1.0.201 netmask 255.255.255.0 broadcast 10.1.0.255 ether b2:79:f9:dd:2a:10 txqueuelen 0 (Ethernet) RX packets 228 bytes 21272 (20.7 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 216 bytes 20272 (19.7 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 如何定位Pod名称空间 首先需要确定Pod所在的节点名称\nbash 1 2 3 4 $ kubectl get pods -owide |awk \u0026#39;{print $1,$7}\u0026#39; NAME NODE netbox-85865d5556-hfg6v master-machine netbox-85865d5556-vlgr4 node01 如果Pod不在当前节点还需要用IP登录则还需要查看IP（可选）\nbash 1 2 3 4 $ kubectl get pods -owide |awk \u0026#39;{print $1,$6,$7}\u0026#39; NAME IP NODE netbox-85865d5556-hfg6v 192.168.1.213 master-machine netbox-85865d5556-vlgr4 192.168.0.4 node01 接下来，登录节点，获取容器lD，如下列所示，每个pod默认有一个 pause 容器，其他为用户yaml文件中定义的容器，理论上所有容器共享相同的网络命名空间，排查时可任选一个容器。\nbash 1 2 3 $ docker ps |grep netbox-85865d5556-hfg6v 6f8c58377aae f78dd05f11ff \u0026#34;tail -f\u0026#34; 45 hours ago Up 45 hours k8s_netbox_netbox-85865d5556-hfg6v_default_4a8e2da8-05d1-4c81-97a7-3d76343a323a_0 b9c732ee457e registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1 \u0026#34;/pause\u0026#34; 45 hours ago Up 45 hours k8s_POD_netbox-85865d5556-hfg6v_default_4a8e2da8-05d1-4c81-97a7-3d76343a323a_0 接下来获得获取容器在节点系统中对应的进程号，如下所示\nbash 1 2 $ docker inspect --format \u0026#34;{{ .State.Pid }}\u0026#34; 6f8c58377aae 30858 最后就可以通过 nsenter 进入容器网络空间执行命令了\npaping paping 命令可对目标地址指定端口以TCP协议进行连续ping，通过这种特性可以弥补 ping ICMP协议，以及 nmap , telnet 只能进行一次操作的的不足；通常情况下会用于测试端口连通性和丢包率\npaping download：paping\npaping 还需要安装以下依赖，这取决于你安装的 paping 版本\nRedHat/CentOS：yum install -y libstdc++.i686 glibc.i686 Ubuntu/Debian：最小化安装无需依赖 bash 1 2 3 4 5 6 7 8 9 10 11 $ paping -h paping v1.5.5 - Copyright (c) 2011 Mike Lovell Syntax: paping [options] destination Options: -?, --help display usage -p, --port N set TCP port N (required) --nocolor Disable color output -t, --timeout timeout in milliseconds (default 1000) -c, --count N set number of checks to N mtr mtr 是一个跨平台的网络诊断工具，将 traceroute 和 ping 的功能结合到一个工具。与 traceroute 不同的是 mtr 显示的信息比起 traceroute 更加丰富：通过 mtr 可以确定网络的条数，并且可以同时打印响应百分比以及网络中各跳跃点的响应时间。\n各系统下的安装 [2]\nUbuntu/Debian: mtr ；apt-get install -y mtr Centos/Fedora: mtr ；yum install -y mtr Apline：mtr ；apk add mtr --no-cache 简单的使用示例 最简单的示例，就是后接域名或IP，这将跟踪整个路由\nbash 1 2 3 4 5 6 7 8 9 10 11 $ mtr google.com Start: Thu Jun 28 12:10:13 2018 HOST: TecMint Loss% Snt Last Avg Best Wrst StDev 1.|-- 192.168.0.1 0.0% 5 0.3 0.3 0.3 0.4 0.0 2.|-- 5.5.5.211 0.0% 5 0.7 0.9 0.7 1.3 0.0 3.|-- 209.snat-111-91-120.hns.n 80.0% 5 7.1 7.1 7.1 7.1 0.0 4.|-- 72.14.194.226 0.0% 5 1.9 2.9 1.9 4.4 1.1 5.|-- 108.170.248.161 0.0% 5 2.9 3.5 2.0 4.3 0.7 6.|-- 216.239.62.237 0.0% 5 3.0 6.2 2.9 18.3 6.7 7.|-- bom05s12-in-f14.1e100.net 0.0% 5 2.1 2.4 2.0 3.8 0.5 -n 强制 mtr 打印 IP地址而不是主机名\nbash 1 2 3 4 5 6 7 8 9 10 11 $ mtr -n google.com Start: Thu Jun 28 12:12:58 2018 HOST: TecMint Loss% Snt Last Avg Best Wrst StDev 1.|-- 192.168.0.1 0.0% 5 0.3 0.3 0.3 0.4 0.0 2.|-- 5.5.5.211 0.0% 5 0.9 0.9 0.8 1.1 0.0 3.|-- ??? 100.0 5 0.0 0.0 0.0 0.0 0.0 4.|-- 72.14.194.226 0.0% 5 2.0 2.0 1.9 2.0 0.0 5.|-- 108.170.248.161 0.0% 5 2.3 2.3 2.2 2.4 0.0 6.|-- 216.239.62.237 0.0% 5 3.0 3.2 3.0 3.3 0.0 7.|-- 172.217.160.174 0.0% 5 3.7 3.6 2.0 5.3 1.4 -b 同时显示IP地址与主机名\nbash 1 2 3 4 5 6 7 8 9 10 11 $ mtr -b google.com Start: Thu Jun 28 12:14:36 2018 HOST: TecMint Loss% Snt Last Avg Best Wrst StDev 1.|-- 192.168.0.1 0.0% 5 0.3 0.3 0.3 0.4 0.0 2.|-- 5.5.5.211 0.0% 5 0.7 0.8 0.6 1.0 0.0 3.|-- 209.snat-111-91-120.hns.n 0.0% 5 1.4 1.6 1.3 2.1 0.0 4.|-- 72.14.194.226 0.0% 5 1.8 2.1 1.8 2.6 0.0 5.|-- 108.170.248.209 0.0% 5 2.0 1.9 1.8 2.0 0.0 6.|-- 216.239.56.115 0.0% 5 2.4 2.7 2.4 2.9 0.0 7.|-- bom07s15-in-f14.1e100.net 0.0% 5 3.7 2.2 1.7 3.7 0.9 -c 跟一个具体的值，这将限制 mtr ping的次数，到达次数后会退出\nbash 1 $ mtr -c5 google.com 如果需要指定次数，并且在退出后保存这些数据，使用 -r flag\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ mtr -r -c 5 google.com \u0026gt; 1 $ cat 1 Start: Sun Aug 21 22:06:49 2022 HOST: xxxxx.xxxxx.xxxx.xxxx Loss% Snt Last Avg Best Wrst StDev 1.|-- gateway 0.0% 5 0.6 146.8 0.6 420.2 191.4 2.|-- 212.xx.21.241 0.0% 5 0.4 1.0 0.4 2.3 0.5 3.|-- 188.xxx.106.124 0.0% 5 0.7 1.1 0.7 2.1 0.5 4.|-- ??? 100.0 5 0.0 0.0 0.0 0.0 0.0 5.|-- 72.14.209.89 0.0% 5 43.2 43.3 43.1 43.3 0.0 6.|-- 108.xxx.250.33 0.0% 5 43.2 43.1 43.1 43.2 0.0 7.|-- 108.xxx.250.34 0.0% 5 43.7 43.6 43.5 43.7 0.0 8.|-- 142.xxx.238.82 0.0% 5 60.6 60.9 60.6 61.2 0.0 9.|-- 142.xxx.238.64 0.0% 5 59.7 67.5 59.3 89.8 13.2 10.|-- 142.xxx.37.81 0.0% 5 62.7 62.9 62.6 63.5 0.0 11.|-- 142.xxx.229.85 0.0% 5 61.0 60.9 60.7 61.3 0.0 12.|-- xx-in-f14.1e100.net 0.0% 5 59.0 58.9 58.9 59.0 0.0 默认使用的是 ICMP 协议 -i ，可以指定 -u, -t 使用其他协议\nbash 1 mtr --tcp google.com -m 指定最大的跳数\nbash 1 mtr -m 35 216.58.223.78 -s 指定包的大小\nmtr输出的数据 colum describe last 最近一次的探测延迟值 avg 探测延迟的平均值 best 探测延迟的最小值 wrst 探测延迟的最大值 stdev 标准偏差。越大说明相应节点越不稳定 丢包判断 任一节点的 Loss%（丢包率）如果不为零，则说明这一跳网络可能存在问题。导致相应节点丢包的原因通常有两种。\n运营商基于安全或性能需求，人为限制了节点的ICMP发送速率，导致丢包。 节点确实存在异常，导致丢包。可以结合异常节点及其后续节点的丢包情况，来判定丢包原因。 Notes:\n如果随后节点均没有丢包，则通常说明异常节点丢包是由于运营商策略限制所致。可以忽略相关丢包。 如果随后节点也出现丢包，则通常说明节点确实存在网络异常，导致丢包。对于这种情况，如果异常节点及其后续节点连续出现丢包，而且各节点的丢包率不同，则通常以最后几跳的丢包率为准。如链路测试在第5、6、7跳均出现了丢包。最终丢包情况以第7跳作为参考。 延迟判断 由于链路抖动或其它因素的影响，节点的 Best 和 Worst 值可能相差很大。而 Avg（平均值）统计了自链路测试以来所有探测的平均值，所以能更好的反应出相应节点的网络质量。而 StDev（标准偏差值）越高，则说明数据包在相应节点的延时值越不相同（越离散）。所以标准偏差值可用于协助判断 Avg 是否真实反应了相应节点的网络质量。例如，如果标准偏差很大，说明数据包的延迟是不确定的。可能某些数据包延迟很小（例如：25ms），而另一些延迟却很大（例如：350ms），但最终得到的平均延迟反而可能是正常的。所以此时 Avg 并不能很好的反应出实际的网络质量情况。\n这就需要结合如下情况进行判断：\n如果 StDev 很高，则同步观察相应节点的 Best 和 wrst，来判断相应节点是否存在异常。 如果StDev 不高，则通过Avg来判断相应节点是否存在异常。 Tips：对于更多的网络工具的使用可以参考这篇文章\nPod网络排查流程 Pod网络异常时排查思路，可以按照下图所示\n图：Pod network exception troubleshooting idea\n案例学习 扩容节点访问service地址不通 测试环境k8s节点扩容后无法访问集群clusterlP类型的registry服务\n环境信息：\nIP Hostname role 10.153.204.15 yxxx-xxx-xxxfu12 worknode节点（本次扩容的问题节点） 10.153.203.14 yxxx-xxx-xxxxfu31 master节点 10.61.187.42 yxxx-xxx-xxxxxxxxf8e9 master节点 10.61.187.48 yxxx-xxx-xxxxxx61e25 master节点（本次registry服务pod所在节点） cni插件：flannel vxlan\nkube-proxy工作模式为iptables\nregistry服务\n单实例部署在10.61.187.48:5000 Pod IP：10.233.65.46， Cluster IP：10.233.0.100 现象：\n所有节点之间的pod通信正常\n任意节点和Pod curl registry的Pod 的 IP:5000 均可以连通\n新扩容节点10.153.204.15 curl registry服务的 Cluster lP 10.233.0.100:5000不通，其他节点curl均可以连通\n分析思路：\n根据现象1可以初步判断 CNI 插件无异常\n根据现象2可以判断 registry 的 Pod 无异常\n根据现象3可以判断 registry 的 service 异常的可能性不大，可能是新扩容节点访问 registry 的 service 存在异常\n怀疑方向：\n问题节点的kube-proxy存在异常 问题节点的iptables规则存在异常 问题节点到service的网络层面存在异常 排查过程：\n排查问题节点的 kube-proxy 执行 kubectl get pod -owide -nkube-system l grep kube-proxy 查看 kube-proxy Pod的状态，问题节点上的 kube-proxy Pod为 running 状态 执行 kubecti logs \u0026lt;nodename\u0026gt; \u0026lt;kube-proxy pod name\u0026gt; -nkube-system 查看问题节点 kube-proxy的Pod日志，没有异常报错 在问题节点操作系统上执行 iptables -S -t nat 查看 iptables 规则 排查过程：\n确认存在到 registry 服务的 Cluster lP 10.233.0.100 的 KUBE-SERVICES 链，跳转至 KUBE-SVC-* 链做负载均衡，再跳转至 KUBE-SEP-* 链通过 DNAT 替换为服务后端Pod的IP 10.233.65.46。因此判断iptables规则无异常执行route-n查看问题节点存在访问10.233.65.46所在网段的路由，如图所示\n图：10.233.65.46路由\n查看对端的回程路由\n图：回程路由\n以上排查证明问题原因不是 cni 插件或者 kube-proxy 异常导致，因此需要在访问链路上抓包，判断问题原因、问题节点执行 curl 10.233.0.100:5000，在问题节点和后端pod所在节点的flannel.1上同时抓包发包节点一直在重传，Cluster lP已 DNAT 转换为后端Pod IP，如图所示\n图：抓包过程，发送端\n后端Pod（ registry 服务）所在节点的 flannel.1 上未抓到任何数据包，如图所示\n图：抓包过程，服务端\n请求 service 的 ClusterlP 时，在两端物理机网卡抓包，发包端如图所示，封装的源端节点IP是10.153.204.15，但一直在重传\n图：包传送过程，发送端\n收包端收到了包，但未回包，如图所示\n图：包传送过程，服务端\n由此可以知道，NAT的动作已经完成，而只是后端Pod（ registry 服务）没有回包，接下来在问题节点执行 curl 10.233.65.46:5000，在问题节点和后端（ registry 服务）Pod所在节点的 flannel.1 上同时抓包，两节点收发正常，发包如图所示\n图：正常包发送端\n图：正常包接收端\n接下来在两端物理机网卡接口抓包，因为数据包通过物理机网卡会进行 vxlan 封装，需要抓 vxlan 设备的8472端口，发包端如图所示\n发现网络链路连通，==但封装的IP不对==，封装的源端节点IP是10.153.204.228，但是存在问题节点的IP是10.153.204.15\n图：问题节点物理机网卡接口抓包\n后端Pod所在节点的物理网卡上抓包，注意需要过滤其他正常节点的请求包，如图所示；发现收到的数据包，源地址是10.153.204.228，但是问题节点的IP是10.153.204.15。\n图：对端节点物理机网卡接口抓包\n此时问题以及清楚了，是一个Pod存在两个IP，导致发包和回包时无法通过隧道设备找到对端的接口，所以发可以收到，但不能回。\n问题节点执行ip addr，发现网卡 enp26s0f0上配置了两个IP，如图所示\n图：问题节点IP\n进一步查看网卡配置文件，发现网卡既配置了静态IP，又配置了dhcp动态获取IP。如图所示\n图：问题节点网卡配置\n最终定位原因为问题节点既配置了dhcp 获取IP，又配置了静态IP，导致IP冲突，引发网络异常\n解决方法：修改网卡配置文件 /etc/sysconfig/network-scripts/ifcfg-enp26s0f0 里 BOOTPROTO=\u0026quot;dhcp\u0026quot; 为 BOOTPROTO=\u0026quot;none\u0026quot;；重启 docker 和 kubelet 问题解决。\n集群外云主机调用集群内应用超时 问题现象：Kubernetes 集群外云主机以 http post 方式访问Kubernetes 集群应用接口超时\n环境信息：Kubernetes 集群：calicoIP-IP模式，应用接口以nodeport方式对外提供服务\n客户端：Kubernetes 集群之外的云主机\n排查过程：\n在云主机telnet应用接口地址和端口，可以连通，证明网络连通正常，如图所示\n云主机上调用接口不通，在云主机和Pod所在 Kubernetes节点同时抓包，使用wireshark分析数据包\n通过抓包结果分析结果为TCP链接建立没有问题，但是在传输大数据的时候会一直重传 **1514 **大小的第一个数据包直至超时。怀疑是链路两端MTU大小不一致导致（现象：某一个固定大小的包一直超时的情况）。如图所示，1514大小的包一直在重传。\n报文1-3 TCP三次握手正常\n报文1 info中MSS字段可以看到MSS协商为1460，MTU=1460+20bytes（IP包头）+20bytes（TCP包头）=1500\n报文7 k8s主机确认了包4的数据包，但是后续再没有对数据的ACK\n报文21-29 可以看到云主机一直在发送后面的数据，但是没有收到k8s节点的ACK，结合pod未收到任何报文，表明是k8s节点和POD通信出现了问题。\n图：wireshark分析\n在云主机上使用 ping -s 指定数据包大小，发现超过1400大小的数据包无法正常发送。结合以上情况，定位是云主机网卡配置的MTU是1500，tunl0 配置的MTU是1440，导致大数据包无法发送至 tunl0 ，因此Pod没有收到报文，接口调用失败。\n解决方法：修改云主机网卡MTU值为1440，或者修改calico的MTU值为1500，保持链路两端MTU值一致。\n集群pod访问对象存储超时 环境信息：公有云环境，Kubernetes 集群节点和对象存储在同一私有网络下，网络链路无防火墙限制k8s集群开启了节点自动弹缩（CA）和Pod自动弹缩（HPA），通过域名访问对象存储，Pod使用集群DNS服务，集群DNS服务配置了用户自建上游DNS服务器\n排查过程：\n使用nsenter工具进入pod容器网络命名空间测试，ping对象存储域名不通，报错unknown server name，ping对象存储lP可以连通。\ntelnet 对象存储80/443端口可以连通。\npaping 对象存储 80/443 端口无丢包。\n为了验证Pod创建好以后的初始阶段网络连通性，将以上测试动作写入dockerfile，重新生成容器镜像并创pod，测试结果一致。\n通过上述步骤，判断Pod网络连通性无异常，超时原因为域名解析失败，怀疑问题如下：\n集群DNS服务存在异常 上游DNS服务存在异常 集群DNS服务与上游DNS通讯异常 pod访问集群DNS服务异常 根据上述方向排查，集群DNS服务状态正常，无报错。测试Pod分别使用集群DNS服务和上游DNS服务解析域名，前者解析失败，后者解析成功。至此，证明上游DNS服务正常，并且集群DNS服务日志中没有与上游DNS通讯超时的报错。定位到的问题：==Pod访问集群DNS服务超时==\n此时发现，出现问题的Pod集中在新弹出的 Kubernetes 节点上。这些节点的 kube-proxy Pod状态全部为pending，没有正常调度到节点上。因此导致该节点上其他Pod无法访问包括 dns 在内的所有Kubernetes service。\n再进一步排查发现 kube-proxy Pod没有配置priorityclass为最高优先级，导致节点资源紧张时为了将高优先级的应用Pod调度到该节点，将原本已运行在该节点的kube-proxy驱逐。\n解决方法：将 kube-proxy 设置 priorityclass 值为 system-node-critical 最高优先级，同时建议应用Pod配置就绪探针，测试可以正常连通对象存储域名后再分配任务。\nReference ​[1] A tcpdump Tutorial with Examples ​ [2] How to install nsenter\n​[3] man nsenter\n","permalink":"https://www.161616.top/pod-network-troubleshooting/","summary":"本文是关于深入理解Kubernetes网络原理系列第4章 深入理解Kubernetes Pod网络原理 - 网络名称空间 深入理解Kubernetes Pod网络原理 - Linux虚拟网络技术 深入理解Kubernetes Pod网络原理 - CNI 深入理解Kubernetes Pod网络原理 - 跟随 flannel 学习CNI原理 深入理解Kubernetes Pod网络原理 - 跟随 flannel + multus 剖析 Chained Plugins 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 1 (Shell) 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 2 (libcni) 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 1 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 2 深入理解Kubernetes Pod网络原理 - Pod网络排错思路 Overview 本文将引入一个思路：“在Kubernetes集群发生网络异常时如何排查”。文章将引入Kubernetes 集群中网络排查的思路，包含网络异常模型，常用工具，并且提出一些案例以供学习。\nPod常见网络异常分类 网络排查工具 Pod网络异常排查思路及流程模型 CNI网络异常排查步骤 案例学习 Pod网络异常 网络异常大概分为如下几类：","title":"Kubernetes Pod网络排错思路"},{"content":" 本文是关于深入理解Kubernetes网络原理系列第4章 深入理解Kubernetes Pod网络原理 - 网络名称空间 深入理解Kubernetes Pod网络原理 - Linux虚拟网络技术 深入理解Kubernetes Pod网络原理 - CNI 深入理解Kubernetes Pod网络原理 - 跟随 flannel 学习CNI原理 深入理解Kubernetes Pod网络原理 - 跟随 flannel + multus 剖析 Chained Plugins 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 1 (Shell) 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 2 (libcni) 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 1 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 2 深入理解Kubernetes Pod网络原理 - Pod网络排错思路 概述 本文将简述探讨 Kubernetes 中常见的网络模型，以及对这些网络模型的 route path 进行分析。本章节是作为CNI 原理的前置条件，也为后续的练习提供一些基础知识。\nUnderlay Network Model 什么是Underlay Network 底层网络 Underlay Network 顾名思义是指网络设备基础设施，如交换机，路由器, DWDM 使用网络介质将其链接成的物理网络拓扑，负责网络之间的数据包传输。\n图：Underlay network topology Source：https://community.cisco.com/t5/data-center-switches/understanding-underlay-and-overlay-networks/td-p/4295870\nunderlay network 可以是二层，也可以是三层；二层 underlay network 的典型例子是以太网 Ethernet，三层是 underlay network 的典型例子是互联网 Internet。\n而工作与二层的技术是 vlan，工作在三层的技术是由 OSPF, BGP 等协议组成\nkubernetes中的underlay network 在kubernetes中，underlay network 是将宿主机作为路由器设备而，Pod 的网络则通过学习成路由条目从而实现跨节点通讯。\n图：underlay network topology in kubernetes\n这种模型下典型的有 flannel 的 host-gw 模式与 calico BGP 模式。\nflannel host-gw [1] flannel host-gw 模式中每个Node需要在同一个二层网络中，并将Node作为一个路由器，跨节点通讯将通过路由表方式进行，这样方式下将网络模拟成一个underlay network。\n图：layer2 ethernet topology Source：https://www.auvik.com/franklyit/blog/layer-3-switches-layer-2/\nNotes：因为是通过路由方式，集群的cidr至少要配置16，因为这样可以保证，跨节点的Node作为一层网络，同节点的Pod作为一个网络。如果不是这种用情况，路由表处于相同的网络中，会存在网络不可达\nCalico BGP [2] BGP（Border Gateway Protocol）是去中心化自治路由协议。它是通过维护IP路由表或\u0026rsquo;前缀\u0026rsquo;表来实现AS （Autonomous System）之间的可访问性，属于向量路由协议。\n图：BGP network topology Source：https://infocenter.nokia.com/public/7705SAR214R1A/index.jsp?topic=%2Fcom.sar.routing_protocols%\n与 flannel 不同的是，Calico 提供了的 BGP 网络解决方案，在网络模型上，Calico 与 Flannel host-gw 是近似的，但在软件架构的实现上，flannel 使用 flanneld 进程来维护路由信息；而 Calico 是包含多个守护进程的，其中 Brid 进程是一个 BGP 的客户端 与路由反射器(Router Reflector)，BGP 客户端负责从 Felix 中获取路由并分发到其他 BGP Peer，而反射器在BGP中起了优化的作用。在同一个IBGP中，BGP客户端仅需要和一个 RR 相连，这样减少了AS内部维护的大量的BGP连接。通常情况下，RR 是真实的路由设备，而 Bird 作为 BGP 客户端工作。\n图：Calico Network Architecture Source：https://www.cisco.com/c/en/us/td/docs/dcn/whitepapers/cisco-nx-os-calico-network-design.html\nIPVLAN \u0026amp; MACVLAN [4] IPVLAN 和 MACVLAN 是一种网卡虚拟化技术，两者之间的区别为， IPVLAN 允许一个物理网卡拥有多个IP地址，并且所有的虚拟接口用同一个MAC地址；而 MACVLAN 则是相反的，其允许同一个网卡拥有多个MAC地址，而虚拟出的网卡可以没有IP地址。\n因为是网卡虚拟化技术，而不是网络虚拟化技术，本质上来说属于 Overlay network，这种方式在虚拟化环境中与Overlay network 相比最大的特点就是可以将Pod的网络拉平到Node网络同级，从而提供更高的性能、低延迟的网络接口。本质上来说其网络模型属于下图中第二个。\n虚拟网桥：创建一个虚拟网卡对(veth pair)，一头栽容器内，一头栽宿主机的root namespaces内。这样一来容器内发出的数据包可以通过网桥直接进入宿主机网络栈，而发往容器的数据包也可以经过网桥进入容器。 多路复用：使用一个中间网络设备，暴露多个虚拟网卡接口，容器网卡都可以介入这个中间设备，并通过MAC/IP地址来区分packet应该发往哪个容器设备。 硬件交换，为每个Pod分配一个虚拟网卡，这样一来，Pod与Pod之间的连接关系就会变得非常清晰，因为近乎物理机之间的通信基础。如今大多数网卡都支持SR-IOV功能，该功能将单一的物理网卡虚拟成多个VF接口，每个VF接口都有单独的虚拟PCIe通道，这些虚拟的PCIe通道共用物理网卡的PCIe通道。 图：Virtual networking modes: bridging, multiplexing and SR-IOV Source：https://thenewstack.io/hackers-guide-kubernetes-networking/\n在kubernetes中 IPVLAN 这种网络模型下典型的CNI有，multus 与 danm。\nmultus multus 是 intel 开源的CNI方案，是由传统的 cni 与 multus，并且提供了 SR-IOV CNI 插件使 K8s pod 能够连接到 SR-IOV VF 。这是使用了 IPVLAN/MACVLAN 的功能。\n当创建新的Pod后，SR-IOV 插件开始工作。配置 VF 将被移动到新的 CNI 名称空间。该插件根据 CNI 配置文件中的 “name” 选项设置接口名称。最后将VF状态设置为UP。\n下图是一个 Multus 和 SR-IOV CNI 插件的网络环境，具有三个接口的 pod。\neth0 是 flannel 网络插件，也是作为Pod的默认网络 VF 是主机的物理端口 ens2f0 的实例化。这是英特尔X710-DA4上的一个端口。 在Pod端的 VF 接口名称为 south0 。 这个VF使用了 DPDK 驱动程序，此 VF 是从主机的物理端口 ens2f1 实例化出的。这个是英特尔® X710-DA4上另外一个端口。 Pod 内的 VF 接口名称为 north0。该接口绑定到 DPDK 驱动程序 vfio-pci 。 图：Mutus networking Architecture overlay and SR-IOV Source：https://builders.intel.com/docs/networkbuilders/enabling_new_features_in_kubernetes_for_NFV.pdf\nNotes：terminology\nNIC：network interface card，网卡 SR-IOV：single root I/O virtualization，硬件实现的功能，允许各虚拟机间共享PCIe设备。 VF：Virtual Function，基于PF，与PF或者其他VF共享一个物理资源。 PF：PCIe Physical Function，拥有完全控制PCIe资源的能力 DPDK：Data Plane Development Kit 于此同时，也可以将主机接口直接移动到Pod的网络名称空间，当然这个接口是必须存在，并且不能是与默认网络使用同一个接口。这种情况下，在普通网卡的环境中，就直接将Pod网络与Node网络处于同一个平面内了。\n图：Mutus networking Architecture overlay and ipvlan Source：https://devopstales.github.io/kubernetes/multus/\ndanm DANM是诺基亚开源的CNI项目，目的是将电信级网络引入kubernetes中，与multus相同的是，也提供了SR-IOV/DPDK 的硬件技术，并且支持IPVLAN.\nOverlay Network Model 什么是Overlay 叠加网络是使用网络虚拟化技术，在 underlay 网络上构建出的虚拟逻辑网络，而无需对物理网络架构进行更改。本质上来说，overlay network 使用的是一种或多种隧道协议 (tunneling)，通过将数据包封装，实现一个网络到另一个网络中的传输，具体来说隧道协议关注的是数据包（帧）。\n图：overlay network topology Source：https://www.researchgate.net/figure/Example-Overlay-Network-built-on-top-of-an-Internet-style-Underlay_fig4_230774628\n常见的网络隧道技术 通用路由封装 ( Generic Routing Encapsulation ) 用于将来自 IPv4/IPv6的数据包封装为另一个协议的数据包中，通常工作与L3网络层中。 VxLAN (Virtual Extensible LAN)，是一个简单的隧道协议，本质上是将L2的以太网帧封装为L4中UDP数据包的方法，使用 4789 作为默认端口。VxLAN 也是 VLAN 的扩展对于 4096（$2^{12}$ 位 VLAN ID） 扩展为1600万（$2^{24}$ 位 VNID ）个逻辑网络。 这种工作在 overlay 模型下典型的有 flannel 与 calico 中的的 VxLAN, IPIP 模式。\nIPIP IP in IP 也是一种隧道协议，与 VxLAN 类似的是，IPIP 的实现也是通过Linux内核功能进行的封装。IPIP 需要内核模块 ipip.ko 使用命令查看内核是否加载IPIP模块lsmod | grep ipip ；使用命令modprobe ipip 加载。\n图：A simple IPIP network workflow Source：https://ssup2.github.io/theory_analysis/IPIP_GRE_Tunneling/\nKubernetes中 IPIP 与 VxLAN 类似，也是通过网络隧道技术实现的。与 VxLAN 差别就是，VxLAN 本质上是一个 UDP包，而 IPIP 则是将包封装在本身的报文包上。\n图：IPIP in kubernetes\n图：IPIP packet with wireshark unpack\nNotes：公有云可能不允许IPIP流量，例如Azure\nVxLAN kubernetes中不管是 flannel 还是 calico VxLAN的实现都是使用Linux内核功能进行的封装，Linux 对 vxlan 协议的支持时间并不久，2012 年 Stephen Hemminger 才把相关的工作合并到 kernel 中，并最终出现在 kernel 3.7.0 版本。为了稳定性和很多的功能，你可以会看到某些软件推荐在 3.9.0 或者 3.10.0 以后版本的 kernel 上使用 VxLAN。\n图：A simple VxLAN network topology\n在kubernetes中vxlan网络，例如 flannel，守护进程会根据kubernetes的Node而维护 VxLAN，名称为 flannel.1 这是 VNID，并维护这个网络的路由，当发生跨节点的流量时，本地会维护对端 VxLAN 设备的MAC地址，通过这个地址可以知道发送的目的端，这样就可以封包发送到对端，收到包的对端 VxLAN设备 flannel.1 解包后得到真实的目的地址。\n查看 Forwarding database 列表\nbash 1 2 $ bridge fdb 26:5e:87:90:91:fc dev flannel.1 dst 10.0.0.3 self permanent 图：VxLAN in kubernetes\n图：VxLAN packet with wireshark unpack\nNotes：VxLAN使用的4789端口，wireshark应该是根据端口进行分析协议的，而flannel在linux中默认端口是8472，此时抓包仅能看到是一个UDP包。\n通过上述的架构可以看出，隧道实际上是一个抽象的概念，并不是建立的真实的两端的隧道，而是通过将数据包封装成另一个数据包，通过物理设备传输后，经由相同的设备（网络隧道）进行解包实现网络的叠加。\nweave vxlan [3] weave也是使用了 VxLAN 技术完成的包的封装，这个技术在 weave 中称之为 fastdp (fast data path)，与 calico 和 flannel 中用到的技术不同的，这里使用的是 Linux 内核中的 openvswitch datapath module。与其他的 VxLAN 模型不同的是，weave对网络流量进行了加密。\n图：weave fastdp network topology Source：https://www.weave.works/docs/net/latest/concepts/fastdp-how-it-works/\nNotes：fastdp工作在Linux 内核版本 3.12 及更高版本，如果低于此版本的例如CentOS7，weave将工作在用户空间，weave中称之为 sleeve mode\nReference ​[1] flannel host-gw\n​[2] calico bgp networking\n​[3] calico bgp networking\n​[4] sriov network\n​[5] danm\n","permalink":"https://www.161616.top/kubernetes-network-model-part1/","summary":"本文是关于深入理解Kubernetes网络原理系列第4章 深入理解Kubernetes Pod网络原理 - 网络名称空间 深入理解Kubernetes Pod网络原理 - Linux虚拟网络技术 深入理解Kubernetes Pod网络原理 - CNI 深入理解Kubernetes Pod网络原理 - 跟随 flannel 学习CNI原理 深入理解Kubernetes Pod网络原理 - 跟随 flannel + multus 剖析 Chained Plugins 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 1 (Shell) 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 2 (libcni) 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 1 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 2 深入理解Kubernetes Pod网络原理 - Pod网络排错思路 概述 本文将简述探讨 Kubernetes 中常见的网络模型，以及对这些网络模型的 route path 进行分析。本章节是作为CNI 原理的前置条件，也为后续的练习提供一些基础知识。","title":"深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 1"},{"content":"Overview 本文将深入讲解 如何扩展 Kubernetes scheduler 中各个扩展点如何使用，与扩展scheduler的原理，这些是作为扩展 scheduler 的所需的知识点。最后会完成一个实验，基于网络流量的调度器。\nkubernetes调度配置 kubernetes集群中允许运行多个不同的 scheduler ，也可以为Pod指定不同的调度器进行调度。在一般的Kubernetes调度教程中并没有提到这点，这也就是说，对于亲和性，污点等策略实际上并没有完全的使用kubernetes调度功能，在之前的文章中提到的一些调度插件，如基于端口占用的调度 NodePorts 等策略一般情况下是没有使用到的，本章节就是对这部分内容进行讲解，这也是作为扩展调度器的一个基础。\nScheduler Configuration [1] kube-scheduler 提供了配置文件的资源，作为给 kube-scheduler 的配置文件，启动时通过 --onfig= 来指定文件。目前各个kubernetes版本中使用的 KubeSchedulerConfiguration 为，\n1.21 之前版本使用 v1beta1 1.22 版本使用 v1beta2 ，但保留了 v1beta1 1.23, 1.24, 1.25 版本使用 v1beta3 ，但保留了 v1beta2，删除了 v1beta1 下面是一个简单的 kubeSchedulerConfiguration 示例，其中 kubeconfig 与启动参数 --kubeconfig 是相同的功效。而 kubeSchedulerConfiguration 与其他组件的配置文件类似，如 kubeletConfiguration 都是作为服务启动的配置文件。\nyaml 1 2 3 4 apiVersion: kubescheduler.config.k8s.io/v1beta1 kind: KubeSchedulerConfiguration clientConnection: kubeconfig: /etc/srv/kubernetes/kube-scheduler/kubeconfig Notes: --kubeconfig 与 --config 是不可以同时指定的，指定了 --config 则其他参数自然失效 [2]\nkubeSchedulerConfiguration使用 通过配置文件，用户可以自定义多个调度器，以及配置每个阶段的扩展点。而插件就是通过这些扩展点来提供在整个调度上下文中的调度行为。\n下面配置是对于配置扩展点的部分的一个示例，关于扩展点的讲解可以参考kubernetes官方文档调度上下文部分\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: kubescheduler.config.k8s.io/v1beta1 kind: KubeSchedulerConfiguration profiles: - plugins: score: disabled: - name: PodTopologySpread enabled: - name: MyCustomPluginA weight: 2 - name: MyCustomPluginB weight: 1 Notes: 如果name=\u0026quot;*\u0026quot; 的话，这种情况下将禁用/启用对应扩展点的所有插件\n既然kubernetes提供了多调度器，那么对于配置文件来说自然支持多个配置文件，profile也是列表形式，只要指定多个配置列表即可，下面是多配置文件示例，其中，如果存在多个扩展点，也可以为每个调度器配置多个扩展点。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: kubescheduler.config.k8s.io/v1beta2 kind: KubeSchedulerConfiguration profiles: - schedulerName: default-scheduler plugins: preScore: disabled: - name: \u0026#39;*\u0026#39; score: disabled: - name: \u0026#39;*\u0026#39; - schedulerName: no-scoring-scheduler plugins: preScore: disabled: - name: \u0026#39;*\u0026#39; score: disabled: - name: \u0026#39;*\u0026#39; scheduler调度插件 [3] kube-scheduler 默认提供了很多插件作为调度方法，默认不配置的情况下会启用这些插件，如：\nImageLocality：调度将更偏向于Node存在容器镜像的节点。扩展点：score. TaintToleration：实现污点与容忍度功能。扩展点：filter, preScore, score. NodeName：实现调度策略中最简单的调度方法 NodeName 的实现。扩展点：filter. NodePorts：调度将检查Node端口是否已占用。扩展点：preFilter, filter. NodeAffinity：提供节点亲和性相关功能。扩展点：filter, score. PodTopologySpread：实现Pod拓扑域的功能。扩展点：preFilter, filter, preScore, score. NodeResourcesFit：该插件将检查节点是否拥有 Pod 请求的所有资源。使用以下三种策略之一：LeastAllocated （默认）MostAllocated 和 RequestedToCapacityRatio。扩展点：preFilter, filter, score. VolumeBinding：检查节点是否有或是否可以绑定请求的 卷. 扩展点：preFilter, filter, reserve, preBind, score. VolumeRestrictions：检查安装在节点中的卷是否满足特定于卷提供程序的限制。扩展点：filter. VolumeZone：检查请求的卷是否满足它们可能具有的任何区域要求。扩展点：filter. InterPodAffinity： 实现Pod 间的亲和性与反亲和性的功能。扩展点：preFilter, filter, preScore, score. PrioritySort：提供基于默认优先级的排序。扩展点：queueSort. 对于更多配置文件使用案例可以参考官方给出的文档\n如何扩展kube-scheduler [4] 当在第一次考虑编写调度程序时，通常会认为扩展 kube-scheduler 是一件非常困难的事情，其实这些事情 kubernetes 官方早就想到了，kubernetes为此在 1.15 版本引入了framework的概念，framework旨在使 scheduler 更具有扩展性。\nframework 通过重新定义 各扩展点，将其作为 plugins 来使用，并且支持用户注册 out of tree 的扩展，使其可以被注册到 kube-scheduler 中，下面将对这些步骤进行分析。\n定义入口 scheduler 允许进行自定义，但是对于只需要引用对应的 NewSchedulerCommand，并且实现自己的 plugins 的逻辑即可。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 import ( scheduler \u0026#34;k8s.io/kubernetes/cmd/kube-scheduler/app\u0026#34; ) func main() { command := scheduler.NewSchedulerCommand( scheduler.WithPlugin(\u0026#34;example-plugin1\u0026#34;, ExamplePlugin1), scheduler.WithPlugin(\u0026#34;example-plugin2\u0026#34;, ExamplePlugin2)) if err := command.Execute(); err != nil { fmt.Fprintf(os.Stderr, \u0026#34;%v\\n\u0026#34;, err) os.Exit(1) } } 而 NewSchedulerCommand 允许注入 out of tree plugins，也就是注入外部的自定义 plugins，这种情况下就无需通过修改源码方式去定义一个调度器，而仅仅通过自行实现即可完成一个自定义调度器。\ngo 1 2 3 4 5 6 // WithPlugin 用于注入out of tree plugins 因此scheduler代码中没有其引用。 func WithPlugin(name string, factory runtime.PluginFactory) Option { return func(registry runtime.Registry) error { return registry.Register(name, factory) } } 插件实现 对于插件的实现仅仅需要实现对应的扩展点接口。下面通过内置插件进行分析\n对于内置插件 NodeAffinity ,我们通过观察他的结构可以发现，实现插件就是实现对应的扩展点抽象 interface 即可。\n定义插件结构体 其中 framework.FrameworkHandle 是提供了Kubernetes API与 scheduler 之间调用使用的，通过结构可以看出包含 lister，informer等等，这个参数也是必须要实现的。\ngo 1 2 3 type NodeAffinity struct { handle framework.FrameworkHandle } 实现对应的扩展点 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 func (pl *NodeAffinity) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) { nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName) if err != nil { return 0, framework.NewStatus(framework.Error, fmt.Sprintf(\u0026#34;getting node %q from Snapshot: %v\u0026#34;, nodeName, err)) } node := nodeInfo.Node() if node == nil { return 0, framework.NewStatus(framework.Error, fmt.Sprintf(\u0026#34;getting node %q from Snapshot: %v\u0026#34;, nodeName, err)) } affinity := pod.Spec.Affinity var count int64 // A nil element of PreferredDuringSchedulingIgnoredDuringExecution matches no objects. // An element of PreferredDuringSchedulingIgnoredDuringExecution that refers to an // empty PreferredSchedulingTerm matches all objects. if affinity != nil \u0026amp;\u0026amp; affinity.NodeAffinity != nil \u0026amp;\u0026amp; affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil { // Match PreferredDuringSchedulingIgnoredDuringExecution term by term. for i := range affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution { preferredSchedulingTerm := \u0026amp;affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[i] if preferredSchedulingTerm.Weight == 0 { continue } // TODO: Avoid computing it for all nodes if this becomes a performance problem. nodeSelector, err := v1helper.NodeSelectorRequirementsAsSelector(preferredSchedulingTerm.Preference.MatchExpressions) if err != nil { return 0, framework.NewStatus(framework.Error, err.Error()) } if nodeSelector.Matches(labels.Set(node.Labels)) { count += int64(preferredSchedulingTerm.Weight) } } } return count, nil } 最后在通过实现一个 New 函数来提供注册这个扩展的方法。通过这个 New 函数可以在 main.go 中将其作为 out of tree plugins 注入到 scheduler 中即可\ngo 1 2 3 4 // New initializes a new plugin and returns it. func New(_ runtime.Object, h framework.FrameworkHandle) (framework.Plugin, error) { return \u0026amp;NodeAffinity{handle: h}, nil } 实验：基于网络流量的调度 [7] 通过上面阅读了解到了如何扩展 scheduler 插件，下面实验将完成一个基于流量的调度，通常情况下，网络一个Node在一段时间内使用的网络流量也是作为生产环境中很常见的情况。例如在配置均衡的多个主机中，主机A作为业务拉单脚本运行，主机B作为计算服务运行。通常来说计算服务会使用更多的系统资源，而拉单需要更多的是网络流量，此时在调度时，默认调度器有限选择的是系统空闲资源多的节点，这种情况下如果有Pod被调度到该节点上，那么可能双方业务都会收到影响（前端代理觉得这个节点连接数少会被大量调度，而拉单脚本因为网络带宽的占用降低了效能）。\n实验环境 一个kubernetes集群，至少保证有两个节点。 提供的kubernetes集群都需要安装prometheus node_exporter，可以是集群内部的，也可以是集群外部的，这里使用的是集群外部的。 对 promQL 与 client_golang 有所了解 实验大致分为以下几个步骤：\n定义插件API 插件命名为 NetworkTraffic 定义扩展点 这里使用了 Score 扩展点，并且定义评分的算法 定义分数获取途径（从prometheus指标中拿到对应的数据） 定义对自定义调度器的参数传入 将项目部署到集群中（集群内部署与集群外部署） 实验的结果验证 实验将仿照内置插件 nodeaffinity 完成代码编写，为什么选择这个插件，只是因为这个插件相对比较简单，并且与我们实验目的基本相同，其实其他插件也是同样的效果。\n整个实验的代码上传至 github.com/CylonChau/customScheduler\n实验开始 错误处理 在初始化项目时，go mod tidy 等操作时，会遇到大量下面的错误\nbash 1 2 go: github.com/GoogleCloudPlatform/spark-on-k8s-operator@v0.0.0-20210307184338-1947244ce5f4 requires k8s.io/apiextensions-apiserver@v0.0.0: reading k8s.io/apiextensions-apiserver/go.mod at revision v0.0.0: unknown revision v0.0.0 kubernetes issue #79384 [5] 中有提到这个问题，粗略浏览下没有说明为什么会出现这个问题，在最下方有个大佬提供了一个脚本，出现上述问题无法解决时直接运行该脚本后正常。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #!/bin/sh set -euo pipefail VERSION=${1#\u0026#34;v\u0026#34;} if [ -z \u0026#34;$VERSION\u0026#34; ]; then echo \u0026#34;Must specify version!\u0026#34; exit 1 fi MODS=($( curl -sS https://raw.githubusercontent.com/kubernetes/kubernetes/v${VERSION}/go.mod | sed -n \u0026#39;s|.*k8s.io/\\(.*\\) =\u0026gt; ./staging/src/k8s.io/.*|k8s.io/\\1|p\u0026#39; )) for MOD in \u0026#34;${MODS[@]}\u0026#34;; do V=$( go mod download -json \u0026#34;${MOD}@kubernetes-${VERSION}\u0026#34; | sed -n \u0026#39;s|.*\u0026#34;Version\u0026#34;: \u0026#34;\\(.*\\)\u0026#34;.*|\\1|p\u0026#39; ) go mod edit \u0026#34;-replace=${MOD}=${MOD}@${V}\u0026#34; done go get \u0026#34;k8s.io/kubernetes@v${VERSION}\u0026#34; 定义插件API 通过上面内容描述了解到了定义插件只需要实现对应的扩展点抽象 interface ，那么可以初始化项目目录 pkg/networtraffic/networktraffice.go。\n定义插件名称与变量\ngo 1 2 const Name = \u0026#34;NetworkTraffic\u0026#34; var _ = framework.ScorePlugin(\u0026amp;NetworkTraffic{}) 定义插件的结构体\ngo 1 2 3 4 5 6 7 8 type NetworkTraffic struct { // 这个作为后面获取node网络流量使用 prometheus *PrometheusHandle // FrameworkHandle 提供插件可以使用的数据和一些工具。 // 它在插件初始化时传递给 plugin 工厂类。 // plugin 必须存储和使用这个handle来调用framework函数。 handle framework.FrameworkHandle } 定义扩展点 因为选用 Score 扩展点，需要定义对应的方法，来实现对应的抽象\ngo 1 2 3 4 5 6 7 8 9 10 func (n *NetworkTraffic) Score(ctx context.Context, state *framework.CycleState, p *corev1.Pod, nodeName string) (int64, *framework.Status) { // 通过promethes拿到一段时间的node的网络使用情况 nodeBandwidth, err := n.prometheus.GetGauge(nodeName) if err != nil { return 0, framework.NewStatus(framework.Error, fmt.Sprintf(\u0026#34;error getting node bandwidth measure: %s\u0026#34;, err)) } bandWidth := int64(nodeBandwidth.Value) klog.Infof(\u0026#34;[NetworkTraffic] node \u0026#39;%s\u0026#39; bandwidth: %s\u0026#34;, nodeName, bandWidth) return bandWidth, nil // 这里直接返回就行 } 接下来需要对结果归一化，这里就回到了调度框架中扩展点的执行问题上了，通过源码可以看出，Score 扩展点需要实现的并不只是这单一的方法。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // Run NormalizeScore method for each ScorePlugin in parallel. parallelize.Until(ctx, len(f.scorePlugins), func(index int) { pl := f.scorePlugins[index] nodeScoreList := pluginToNodeScores[pl.Name()] if pl.ScoreExtensions() == nil { return } status := f.runScoreExtension(ctx, pl, state, pod, nodeScoreList) if !status.IsSuccess() { err := fmt.Errorf(\u0026#34;normalize score plugin %q failed with error %v\u0026#34;, pl.Name(), status.Message()) errCh.SendErrorWithCancel(err, cancel) return } }) 通过上面代码了解到，实现 Score 就必须实现 ScoreExtensions，如果没有实现则直接返回。而根据 nodeaffinity 中示例发现这个方法仅仅返回的是这个扩展点对象本身，而具体的归一化也就是真正进行打分的操作在 NormalizeScore 中。\ngo 1 2 3 4 5 6 7 8 9 // NormalizeScore invoked after scoring all nodes. func (pl *NodeAffinity) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status { return pluginhelper.DefaultNormalizeScore(framework.MaxNodeScore, false, scores) } // ScoreExtensions of the Score plugin. func (pl *NodeAffinity) ScoreExtensions() framework.ScoreExtensions { return pl } 而在 framework 中，真正执行的操作的方法也是 NormalizeScore()\ngo 1 2 3 4 5 6 7 8 9 func (f *frameworkImpl) runScoreExtension(ctx context.Context, pl framework.ScorePlugin, state *framework.CycleState, pod *v1.Pod, nodeScoreList framework.NodeScoreList) *framework.Status { if !state.ShouldRecordPluginMetrics() { return pl.ScoreExtensions().NormalizeScore(ctx, state, pod, nodeScoreList) } startTime := time.Now() status := pl.ScoreExtensions().NormalizeScore(ctx, state, pod, nodeScoreList) f.metricsRecorder.observePluginDurationAsync(scoreExtensionNormalize, pl.Name(), status, metrics.SinceInSeconds(startTime)) return status } 下面来实现对应的方法\n在 NormalizeScore 中需要实现具体的选择node的算法，因为对node打分结果的区间为 $[0,100]$ ，所以这里实现的算法公式将为 $最高分 - (当前带宽 / 最高最高带宽 * 100)$，这样就保证了，带宽占用越大的机器，分数越低。\n例如，最高带宽为200000，而当前Node带宽为140000，那么这个Node分数为：$max - \\frac{140000}{200000}\\times 100 = 100 - (0.7\\times100)=30$\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // 如果返回framework.ScoreExtensions 就需要实现framework.ScoreExtensions func (n *NetworkTraffic) ScoreExtensions() framework.ScoreExtensions { return n } // NormalizeScore与ScoreExtensions是固定格式 func (n *NetworkTraffic) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *corev1.Pod, scores framework.NodeScoreList) *framework.Status { var higherScore int64 for _, node := range scores { if higherScore \u0026lt; node.Score { higherScore = node.Score } } // 计算公式为，满分 - (当前带宽 / 最高最高带宽 * 100) // 公式的计算结果为，带宽占用越大的机器，分数越低 for i, node := range scores { scores[i].Score = framework.MaxNodeScore - (node.Score * 100 / higherScore) klog.Infof(\u0026#34;[NetworkTraffic] Nodes final score: %v\u0026#34;, scores) } klog.Infof(\u0026#34;[NetworkTraffic] Nodes final score: %v\u0026#34;, scores) return nil } Notes：在kubernetes中最大的node数支持5000个，岂不是在获取最大分数时循环就占用了大量的性能，其实不必担心。scheduler 提供了一个参数 percentageOfNodesToScore。这个参数决定了这里要循环的数量。更多的细节可以参考官方文档对这部分的说明 [6]\n配置插件名称\n为了使插件注册时候使用，还需要为其配置一个名称\ngo 1 2 3 4 // Name returns name of the plugin. It is used in logs, etc. func (n *NetworkTraffic) Name() string { return Name } 定义PrometheusHandle 网络插件的扩展中还存在一个 prometheusHandle，这个就是操作prometheus-server拿去指标的动作。\n首先需要定义一个 PrometheusHandle 的结构体\ngo 1 2 3 4 5 6 type PrometheusHandle struct { deviceName string // 网络接口名称 timeRange time.Duration // 抓取的时间段 ip string // prometheus server的连接地址 client v1.API // 操作prometheus的客户端 } 有了结构就需要查询的动作和指标，对于指标来说，这里使用了 node_network_receive_bytes_total 作为获取Node的网络流量的计算方式。由于环境是部署在集群之外的，没有node的主机名，通过 promQL 获取，整个语句如下：\nbash 1 sum_over_time(node_network_receive_bytes_total{device=\u0026#34;eth0\u0026#34;}[1s]) * on(instance) group_left(nodename) (node_uname_info{nodename=\u0026#34;node01\u0026#34;}) 整个 Prometheus 部分如下：\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 type PrometheusHandle struct { deviceName string timeRange time.Duration ip string client v1.API } func NewProme(ip, deviceName string, timeRace time.Duration) *PrometheusHandle { client, err := api.NewClient(api.Config{Address: ip}) if err != nil { klog.Fatalf(\u0026#34;[NetworkTraffic] FatalError creating prometheus client: %s\u0026#34;, err.Error()) } return \u0026amp;PrometheusHandle{ deviceName: deviceName, ip: ip, timeRange: timeRace, client: v1.NewAPI(client), } } func (p *PrometheusHandle) GetGauge(node string) (*model.Sample, error) { value, err := p.query(fmt.Sprintf(nodeMeasureQueryTemplate, node, p.deviceName, p.timeRange)) fmt.Println(fmt.Sprintf(nodeMeasureQueryTemplate, p.deviceName, p.timeRange, node)) if err != nil { return nil, fmt.Errorf(\u0026#34;[NetworkTraffic] Error querying prometheus: %w\u0026#34;, err) } nodeMeasure := value.(model.Vector) if len(nodeMeasure) != 1 { return nil, fmt.Errorf(\u0026#34;[NetworkTraffic] Invalid response, expected 1 value, got %d\u0026#34;, len(nodeMeasure)) } return nodeMeasure[0], nil } func (p *PrometheusHandle) query(promQL string) (model.Value, error) { // 通过promQL查询并返回结果 results, warnings, err := p.client.Query(context.Background(), promQL, time.Now()) if len(warnings) \u0026gt; 0 { klog.Warningf(\u0026#34;[NetworkTraffic Plugin] Warnings: %v\\n\u0026#34;, warnings) } return results, err } 定义调度器传入的参数 因为需要指定 prometheus 的地址，网卡名称，和获取数据的大小，故整个结构体如下，另外，参数结构必须遵循\u0026lt;Plugin Name\u0026gt;Args 格式的名称。\ngo 1 2 3 4 5 type NetworkTrafficArgs struct { IP string `json:\u0026#34;ip\u0026#34;` DeviceName string `json:\u0026#34;deviceName\u0026#34;` TimeRange int `json:\u0026#34;timeRange\u0026#34;` } 为了使这个类型的数据作为 KubeSchedulerConfiguration 可以解析的结构，还需要做一步操作，就是在扩展APIServer时扩展对应的资源类型。在这里kubernetes中提供两种方法来扩展 KubeSchedulerConfiguration 的资源类型。\n一种是旧版中提供了 framework.DecodeInto 函数可以做这个操作\ngo 1 2 3 4 5 6 7 func New(plArgs *runtime.Unknown, handle framework.FrameworkHandle) (framework.Plugin, error) { args := Args{} if err := framework.DecodeInto(plArgs, \u0026amp;args); err != nil { return nil, err } ... } 另外一种方式是必须实现对应的深拷贝方法，例如 NodeLabel 中的\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // NodeLabelArgs holds arguments used to configure the NodeLabel plugin. type NodeLabelArgs struct { metav1.TypeMeta // PresentLabels should be present for the node to be considered a fit for hosting the pod PresentLabels []string // AbsentLabels should be absent for the node to be considered a fit for hosting the pod AbsentLabels []string // Nodes that have labels in the list will get a higher score. PresentLabelsPreference []string // Nodes that don\u0026#39;t have labels in the list will get a higher score. AbsentLabelsPreference []string } 最后将其注册到register中，整个行为与扩展APIServer是类似的\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // addKnownTypes registers known types to the given scheme func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, \u0026amp;KubeSchedulerConfiguration{}, \u0026amp;Policy{}, \u0026amp;InterPodAffinityArgs{}, \u0026amp;NodeLabelArgs{}, \u0026amp;NodeResourcesFitArgs{}, \u0026amp;PodTopologySpreadArgs{}, \u0026amp;RequestedToCapacityRatioArgs{}, \u0026amp;ServiceAffinityArgs{}, \u0026amp;VolumeBindingArgs{}, \u0026amp;NodeResourcesLeastAllocatedArgs{}, \u0026amp;NodeResourcesMostAllocatedArgs{}, ) scheme.AddKnownTypes(schema.GroupVersion{Group: \u0026#34;\u0026#34;, Version: runtime.APIVersionInternal}, \u0026amp;Policy{}) return nil } Notes：对于生成深拷贝函数及其他文件，可以使用 kubernetes 代码库中的脚本 kubernetes/hack/update-codegen.sh\n这里为了方便使用了 framework.DecodeInto 的方式。\n项目部署 准备 scheduler 的 profile，可以看到，我们自定义的参数，就可以被识别为 KubeSchedulerConfiguration 的资源类型了。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 apiVersion: kubescheduler.config.k8s.io/v1beta1 kind: KubeSchedulerConfiguration clientConnection: kubeconfig: /mnt/d/src/go_work/customScheduler/scheduler.conf profiles: - schedulerName: custom-scheduler plugins: score: enabled: - name: \u0026#34;NetworkTraffic\u0026#34; disabled: - name: \u0026#34;*\u0026#34; pluginConfig: - name: \u0026#34;NetworkTraffic\u0026#34; args: ip: \u0026#34;http://10.0.0.4:9090\u0026#34; deviceName: \u0026#34;eth0\u0026#34; timeRange: 60 如果需要部署到集群内部，可以打包成镜像\ndockerfile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 FROM golang:alpine AS builder MAINTAINER cylon WORKDIR /scheduler COPY ./ /scheduler ENV GOPROXY https://goproxy.cn,direct RUN \\ sed -i \u0026#39;s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g\u0026#39; /etc/apk/repositories \u0026amp;\u0026amp; \\ apk add upx \u0026amp;\u0026amp; \\ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags \u0026#34;-s -w\u0026#34; -o scheduler main.go \u0026amp;\u0026amp; \\ upx -1 scheduler \u0026amp;\u0026amp; \\ chmod +x scheduler FROM alpine AS runner WORKDIR /go/scheduler COPY --from=builder /scheduler/scheduler . COPY --from=builder /scheduler/scheduler.yaml /etc/ VOLUME [\u0026#34;./scheduler\u0026#34;] 部署在集群内部所需的资源清单\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 apiVersion: v1 kind: ServiceAccount metadata: name: scheduler-sa namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: scheduler subjects: - kind: ServiceAccount name: scheduler-sa namespace: kube-system roleRef: kind: ClusterRole name: system:kube-scheduler apiGroup: rbac.authorization.k8s.io --- apiVersion: apps/v1 kind: Deployment metadata: name: custom-scheduler namespace: kube-system labels: component: custom-scheduler spec: selector: matchLabels: component: custom-scheduler template: metadata: labels: component: custom-scheduler spec: serviceAccountName: scheduler-sa priorityClassName: system-cluster-critical containers: - name: scheduler image: cylonchau/custom-scheduler:v0.0.1 imagePullPolicy: IfNotPresent command: - ./scheduler - --config=/etc/scheduler.yaml - --v=3 livenessProbe: httpGet: path: /healthz port: 10251 initialDelaySeconds: 15 readinessProbe: httpGet: path: /healthz port: 10251 启动自定义 scheduler，这里通过简单的二进制方式启动，所以需要一个kubeconfig做认证文件\nbash 1 2 3 4 5 ./main --logtostderr=true \\ --address=127.0.0.1 \\ --v=3 \\ --config=`pwd`/scheduler.yaml \\ --kubeconfig=`pwd`/scheduler.conf 启动后为了验证方便性，关闭了原来的 kube-scheduler 服务，因为原来的 kube-scheduler 已经作为HA中的master，所以不会使用自定义的 scheduler 导致pod pending。\n验证结果 准备一个需要部署的Pod，指定使用的调度器名称\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 schedulerName: custom-scheduler 这里实验环境为2个节点的kubernetes集群，master与node01，因为master的服务比node01要多，这种情况下不管怎样，调度结果永远会被调度到node01上。\nbash 1 2 3 4 $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-deployment-69f76b454c-lpwbl 1/1 Running 0 43s 192.168.0.17 node01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-deployment-69f76b454c-vsb7k 1/1 Running 0 43s 192.168.0.16 node01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 而调度器的日志如下\ntext 1 2 3 4 5 6 7 8 9 10 11 I0808 01:56:31.098189 27131 networktraffic.go:83] [NetworkTraffic] node \u0026#39;node01\u0026#39; bandwidth: %!s(int64=12541068340) I0808 01:56:31.098461 27131 networktraffic.go:70] [NetworkTraffic] Nodes final score: [{master-machine 0} {node01 12541068340}] I0808 01:56:31.098651 27131 networktraffic.go:70] [NetworkTraffic] Nodes final score: [{master-machine 0} {node01 71}] I0808 01:56:31.098911 27131 networktraffic.go:73] [NetworkTraffic] Nodes final score: [{master-machine 0} {node01 71}] I0808 01:56:31.099275 27131 default_binder.go:51] Attempting to bind default/nginx-deployment-69f76b454c-vsb7k to node01 I0808 01:56:31.101414 27131 eventhandlers.go:225] add event for scheduled pod default/nginx-deployment-69f76b454c-lpwbl I0808 01:56:31.101414 27131 eventhandlers.go:205] delete event for unscheduled pod default/nginx-deployment-69f76b454c-lpwbl I0808 01:56:31.103604 27131 scheduler.go:609] \u0026#34;Successfully bound pod to node\u0026#34; pod=\u0026#34;default/nginx-deployment-69f76b454c-lpwbl\u0026#34; node=\u0026#34;no de01\u0026#34; evaluatedNodes=2 feasibleNodes=2 I0808 01:56:31.104540 27131 scheduler.go:609] \u0026#34;Successfully bound pod to node\u0026#34; pod=\u0026#34;default/nginx-deployment-69f76b454c-vsb7k\u0026#34; node=\u0026#34;no de01\u0026#34; evaluatedNodes=2 feasibleNodes=2 Reference ​[1] scheduling config\n​[2] kube-scheduler\n​[3] scheduling-plugins\n​[4] custom scheduler plugins\n​[5] ssues #79384\n​[6] scheduler perf tuning\n​[7] creating a kube-scheduler plugin\n","permalink":"https://www.161616.top/ch22-custom-scheduler/","summary":"Overview 本文将深入讲解 如何扩展 Kubernetes scheduler 中各个扩展点如何使用，与扩展scheduler的原理，这些是作为扩展 scheduler 的所需的知识点。最后会完成一个实验，基于网络流量的调度器。\nkubernetes调度配置 kubernetes集群中允许运行多个不同的 scheduler ，也可以为Pod指定不同的调度器进行调度。在一般的Kubernetes调度教程中并没有提到这点，这也就是说，对于亲和性，污点等策略实际上并没有完全的使用kubernetes调度功能，在之前的文章中提到的一些调度插件，如基于端口占用的调度 NodePorts 等策略一般情况下是没有使用到的，本章节就是对这部分内容进行讲解，这也是作为扩展调度器的一个基础。\nScheduler Configuration [1] kube-scheduler 提供了配置文件的资源，作为给 kube-scheduler 的配置文件，启动时通过 --onfig= 来指定文件。目前各个kubernetes版本中使用的 KubeSchedulerConfiguration 为，\n1.21 之前版本使用 v1beta1 1.22 版本使用 v1beta2 ，但保留了 v1beta1 1.23, 1.24, 1.25 版本使用 v1beta3 ，但保留了 v1beta2，删除了 v1beta1 下面是一个简单的 kubeSchedulerConfiguration 示例，其中 kubeconfig 与启动参数 --kubeconfig 是相同的功效。而 kubeSchedulerConfiguration 与其他组件的配置文件类似，如 kubeletConfiguration 都是作为服务启动的配置文件。\nyaml 1 2 3 4 apiVersion: kubescheduler.config.k8s.io/v1beta1 kind: KubeSchedulerConfiguration clientConnection: kubeconfig: /etc/srv/kubernetes/kube-scheduler/kubeconfig Notes: --kubeconfig 与 --config 是不可以同时指定的，指定了 --config 则其他参数自然失效 [2]","title":"基于Prometheus的Kubernetes网络调度器"},{"content":"调度框架 [1] 本文基于 kubernetes 1.24 进行分析\n调度框架（Scheduling Framework）是Kubernetes 的调度器 kube-scheduler 设计的的可插拔架构，将插件（调度算法）嵌入到调度上下文的每个扩展点中，并编译为 kube-scheduler\n在 kube-scheduler 1.22 之后，在 pkg/scheduler/framework/interface.go 中定义了一个 Plugin 的 interface，这个 interface 作为了所有插件的父级。而每个未调度的 Pod，Kubernetes 调度器会根据一组规则尝试在集群中寻找一个节点。\ngo 1 2 3 type Plugin interface { Name() string } 下面会对每个算法是如何实现的进行分析\n在初始化 scheduler 时，会创建一个 profile，profile是关于 scheduler 调度配置相关的定义\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func New(client clientset.Interface, ... profiles, err := profile.NewMap(options.profiles, registry, recorderFactory, stopCh, frameworkruntime.WithComponentConfigVersion(options.componentConfigVersion), frameworkruntime.WithClientSet(client), frameworkruntime.WithKubeConfig(options.kubeConfig), frameworkruntime.WithInformerFactory(informerFactory), frameworkruntime.WithSnapshotSharedLister(snapshot), frameworkruntime.WithPodNominator(nominator), frameworkruntime.WithCaptureProfile(frameworkruntime.CaptureProfile(options.frameworkCapturer)), frameworkruntime.WithClusterEventMap(clusterEventMap), frameworkruntime.WithParallelism(int(options.parallelism)), frameworkruntime.WithExtenders(extenders), ) if err != nil { return nil, fmt.Errorf(\u0026#34;initializing profiles: %v\u0026#34;, err) } if len(profiles) == 0 { return nil, errors.New(\u0026#34;at least one profile is required\u0026#34;) } .... } 关于 profile 的实现，则为 KubeSchedulerProfile，也是作为 yaml生成时传入的配置\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // KubeSchedulerProfile 是一个 scheduling profile. type KubeSchedulerProfile struct { // SchedulerName 是与此配置文件关联的调度程序的名称。 // 如果 SchedulerName 与 pod “spec.schedulerName”匹配，则使用此配置文件调度 pod。 SchedulerName string // Plugins指定应该启用或禁用的插件集。 // 启用的插件是除了默认插件之外应该启用的插件。禁用插件应是禁用的任何默认插件。 // 当没有为扩展点指定启用或禁用插件时，将使用该扩展点的默认插件（如果有）。 // 如果指定了 QueueSort 插件， // 则必须为所有配置文件指定相同的 QueueSort Plugin 和 PluginConfig。 // 这个Plugins展现的形式则是调度上下文中的所有扩展点(这是抽象)，实际中会表现为多个扩展点 Plugins *Plugins // PluginConfig 是每个插件的一组可选的自定义插件参数。 // 如果省略PluginConfig参数等同于使用该插件的默认配置。 PluginConfig []PluginConfig } 对于 profile.NewMap 就是根据给定的配置来构建这个framework，因为配置可能是存在多个的。而 Registry 则是所有可用插件的集合，内部构造则是 PluginFactory ,通过函数来构建出对应的 plugin\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func NewMap(cfgs []config.KubeSchedulerProfile, r frameworkruntime.Registry, recorderFact RecorderFactory, stopCh \u0026lt;-chan struct{}, opts ...frameworkruntime.Option) (Map, error) { m := make(Map) v := cfgValidator{m: m} for _, cfg := range cfgs { p, err := newProfile(cfg, r, recorderFact, stopCh, opts...) if err != nil { return nil, fmt.Errorf(\u0026#34;creating profile for scheduler name %s: %v\u0026#34;, cfg.SchedulerName, err) } if err := v.validate(cfg, p); err != nil { return nil, err } m[cfg.SchedulerName] = p } return m, nil } // newProfile 给的配置构建出一个profile func newProfile(cfg config.KubeSchedulerProfile, r frameworkruntime.Registry, recorderFact RecorderFactory, stopCh \u0026lt;-chan struct{}, opts ...frameworkruntime.Option) (framework.Framework, error) { recorder := recorderFact(cfg.SchedulerName) opts = append(opts, frameworkruntime.WithEventRecorder(recorder)) return frameworkruntime.NewFramework(r, \u0026amp;cfg, stopCh, opts...) } 可以看到最终返回的是一个 Framework 。那么来看下这个 Framework\nFramework 是一个抽象，管理着调度过程中所使用的所有插件，并在调度上下文中适当的位置去运行对应的插件\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 type Framework interface { Handle // QueueSortFunc 返回对调度队列中的 Pod 进行排序的函数 // 也就是less，在Sort打分阶段的打分函数 QueueSortFunc() LessFunc // RunPreFilterPlugins 运行配置的一组PreFilter插件。 // 如果这组插件中，任何一个插件失败，则返回 *Status 并设置为non-success。 // 如果返回状态为non-success，则调度周期中止。 // 它还返回一个 PreFilterResult，它可能会影响到要评估下游的节点。 RunPreFilterPlugins(ctx context.Context, state *CycleState, pod *v1.Pod) (*PreFilterResult, *Status) // RunPostFilterPlugins 运行配置的一组PostFilter插件。 // PostFilter 插件是通知性插件，在这种情况下应配置为先执行并返回 Unschedulable 状态， // 或者尝试更改集群状态以使 pod 在未来的调度周期中可能会被调度。 RunPostFilterPlugins(ctx context.Context, state *CycleState, pod *v1.Pod, filteredNodeStatusMap NodeToStatusMap) (*PostFilterResult, *Status) // RunPreBindPlugins 运行配置的一组 PreBind 插件。 // 如果任何一个插件返回错误，则返回 *Status 并且code设置为non-success。 // 如果code为“Unschedulable”，则调度检查失败， // 则认为是内部错误。在任何一种情况下，Pod都不会被bound。 RunPreBindPlugins(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) *Status // RunPostBindPlugins 运行配置的一组PostBind插件 RunPostBindPlugins(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) // RunReservePluginsReserve运行配置的一组Reserve插件的Reserve方法。 // 如果在这组调用中的任何一个插件返回错误，则不会继续运行剩余调用的插件并返回错误。 // 在这种情况下，pod将不能被调度。 RunReservePluginsReserve(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) *Status // RunReservePluginsUnreserve运行配置的一组Reserve插件的Unreserve方法。 RunReservePluginsUnreserve(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) // RunPermitPlugins运行配置的一组Permit插件。 // 如果这些插件中的任何一个返回“Success”或“Wait”之外的状态，则它不会继续运行其余插件并返回错误。 // 否则，如果任何插件返回 “Wait”，则此函数将创建等待pod并将其添加到当前等待pod的map中， // 并使用“Wait” code返回状态。 Pod将在Permit插件返回的最短持续时间内保持等待pod。 RunPermitPlugins(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) *Status // 如果pod是waiting pod，WaitOnPermit 将阻塞，直到等待的pod被允许或拒绝。 WaitOnPermit(ctx context.Context, pod *v1.Pod) *Status // RunBindPlugins运行配置的一组bind插件。 Bind插件可以选择是否处理Pod。 // 如果 Bind 插件选择跳过binding，它应该返回 code=5(\u0026#34;skip\u0026#34;)状态。 // 否则，它应该返回“Error”或“Success”。 // 如果没有插件处理绑定，则RunBindPlugins返回code=5(\u0026#34;skip\u0026#34;)的状态。 RunBindPlugins(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) *Status // 如果至少定义了一个filter插件，则HasFilterPlugins返回true HasFilterPlugins() bool // 如果至少定义了一个PostFilter插件，则HasPostFilterPlugins返回 true。 HasPostFilterPlugins() bool // 如果至少定义了一个Score插件，则HasScorePlugins返回 true。 HasScorePlugins() bool // ListPlugins将返回map。key为扩展点名称，value则是配置的插件列表。 ListPlugins() *config.Plugins // ProfileName则是与profile name关联的framework ProfileName() string } 而实现这个抽象的则是 frameworkImpl；frameworkImpl 是初始化与运行 scheduler plugins 的组件，并在调度上下文中会运行这些扩展点\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 type frameworkImpl struct { registry Registry snapshotSharedLister framework.SharedLister waitingPods *waitingPodsMap scorePluginWeight map[string]int queueSortPlugins []framework.QueueSortPlugin preFilterPlugins []framework.PreFilterPlugin filterPlugins []framework.FilterPlugin postFilterPlugins []framework.PostFilterPlugin preScorePlugins []framework.PreScorePlugin scorePlugins []framework.ScorePlugin reservePlugins []framework.ReservePlugin preBindPlugins []framework.PreBindPlugin bindPlugins []framework.BindPlugin postBindPlugins []framework.PostBindPlugin permitPlugins []framework.PermitPlugin clientSet clientset.Interface kubeConfig *restclient.Config eventRecorder events.EventRecorder informerFactory informers.SharedInformerFactory metricsRecorder *metricsRecorder profileName string extenders []framework.Extender framework.PodNominator parallelizer parallelize.Parallelizer } 那么来看下 Registry ，Registry 是作为一个可用插件的集合。framework 使用 registry 来启用和对插件配置的初始化。在初始化框架之前，所有插件都必须在注册表中。表现形式就是一个 map[]；key 是插件的名称，value是 PluginFactory 。\ngo 1 type Registry map[string]PluginFactory 而在 pkg\\scheduler\\framework\\plugins\\registry.go 中会将所有的 in-tree plugin 注册进来。通过 NewInTreeRegistry 。后续如果还有插件要注册，可以通过 WithFrameworkOutOfTreeRegistry 来注册其他的插件。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 func NewInTreeRegistry() runtime.Registry { fts := plfeature.Features{ EnableReadWriteOncePod: feature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod), EnableVolumeCapacityPriority: feature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority), EnableMinDomainsInPodTopologySpread: feature.DefaultFeatureGate.Enabled(features.MinDomainsInPodTopologySpread), EnableNodeInclusionPolicyInPodTopologySpread: feature.DefaultFeatureGate.Enabled(features.NodeInclusionPolicyInPodTopologySpread), } return runtime.Registry{ selectorspread.Name: selectorspread.New, imagelocality.Name: imagelocality.New, tainttoleration.Name: tainttoleration.New, nodename.Name: nodename.New, nodeports.Name: nodeports.New, nodeaffinity.Name: nodeaffinity.New, podtopologyspread.Name: runtime.FactoryAdapter(fts, podtopologyspread.New), nodeunschedulable.Name: nodeunschedulable.New, noderesources.Name: runtime.FactoryAdapter(fts, noderesources.NewFit), noderesources.BalancedAllocationName: runtime.FactoryAdapter(fts, noderesources.NewBalancedAllocation), volumebinding.Name: runtime.FactoryAdapter(fts, volumebinding.New), volumerestrictions.Name: runtime.FactoryAdapter(fts, volumerestrictions.New), volumezone.Name: volumezone.New, nodevolumelimits.CSIName: runtime.FactoryAdapter(fts, nodevolumelimits.NewCSI), nodevolumelimits.EBSName: runtime.FactoryAdapter(fts, nodevolumelimits.NewEBS), nodevolumelimits.GCEPDName: runtime.FactoryAdapter(fts, nodevolumelimits.NewGCEPD), nodevolumelimits.AzureDiskName: runtime.FactoryAdapter(fts, nodevolumelimits.NewAzureDisk), nodevolumelimits.CinderName: runtime.FactoryAdapter(fts, nodevolumelimits.NewCinder), interpodaffinity.Name: interpodaffinity.New, queuesort.Name: queuesort.New, defaultbinder.Name: defaultbinder.New, defaultpreemption.Name: runtime.FactoryAdapter(fts, defaultpreemption.New), } } 这里插入一个题外话，关于 in-tree plugin\n在这里没有找到关于，kube-scheduler ，只是找到有关的概念，大概可以解释为，in-tree表示为随kubernetes官方提供的二进制构建的 plugin 则为 in-tree，而独立于kubernetes代码库之外的为 out-of-tree [3] 。这种情况下，可以理解为，AA则是 out-of-tree 而 Pod, DeplymentSet 等是 in-tree。\n接下来回到初始化 scheduler ，在初始化一个 scheduler 时，会通过NewInTreeRegistry 来初始化\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func New(client clientset.Interface, .... registry := frameworkplugins.NewInTreeRegistry() if err := registry.Merge(options.frameworkOutOfTreeRegistry); err != nil { return nil, err } ... profiles, err := profile.NewMap(options.profiles, registry, recorderFactory, stopCh, frameworkruntime.WithComponentConfigVersion(options.componentConfigVersion), frameworkruntime.WithClientSet(client), frameworkruntime.WithKubeConfig(options.kubeConfig), frameworkruntime.WithInformerFactory(informerFactory), frameworkruntime.WithSnapshotSharedLister(snapshot), frameworkruntime.WithPodNominator(nominator), frameworkruntime.WithCaptureProfile(frameworkruntime.CaptureProfile(options.frameworkCapturer)), frameworkruntime.WithClusterEventMap(clusterEventMap), frameworkruntime.WithParallelism(int(options.parallelism)), frameworkruntime.WithExtenders(extenders), ) ... } 接下来在调度上下文 scheduleOne 中 schedulePod 时，会通过 framework 调用对应的插件来处理这个扩展点工作。具体的体现在，pkg\\scheduler\\schedule_one.go 中的预选阶段\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func (sched *Scheduler) schedulePod(ctx context.Context, fwk framework.Framework, state *framework.CycleState, pod *v1.Pod) (result ScheduleResult, err error) { trace := utiltrace.New(\u0026#34;Scheduling\u0026#34;, utiltrace.Field{Key: \u0026#34;namespace\u0026#34;, Value: pod.Namespace}, utiltrace.Field{Key: \u0026#34;name\u0026#34;, Value: pod.Name}) defer trace.LogIfLong(100 * time.Millisecond) if err := sched.Cache.UpdateSnapshot(sched.nodeInfoSnapshot); err != nil { return result, err } trace.Step(\u0026#34;Snapshotting scheduler cache and node infos done\u0026#34;) if sched.nodeInfoSnapshot.NumNodes() == 0 { return result, ErrNoNodesAvailable } feasibleNodes, diagnosis, err := sched.findNodesThatFitPod(ctx, fwk, state, pod) if err != nil { return result, err } trace.Step(\u0026#34;Computing predicates done\u0026#34;) 与其他扩展点部分，在调度上下文 scheduleOne 中可以很好的看出，功能都是 framework 提供的。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 func (sched *Scheduler) scheduleOne(ctx context.Context) { ... scheduleResult, err := sched.SchedulePod(schedulingCycleCtx, fwk, state, pod) ... // Run the Reserve method of reserve plugins. if sts := fwk.RunReservePluginsReserve(schedulingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost); !sts.IsSuccess() { } ... // Run \u0026#34;permit\u0026#34; plugins. runPermitStatus := fwk.RunPermitPlugins(schedulingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) // One of the plugins returned status different than success or wait. fwk.RunReservePluginsUnreserve(schedulingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) ... // bind the pod to its host asynchronously (we can do this b/c of the assumption step above). go func() { ... waitOnPermitStatus := fwk.WaitOnPermit(bindingCycleCtx, assumedPod) if !waitOnPermitStatus.IsSuccess() { ... // trigger un-reserve plugins to clean up state associated with the reserved Pod fwk.RunReservePluginsUnreserve(bindingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) } // Run \u0026#34;prebind\u0026#34; plugins. preBindStatus := fwk.RunPreBindPlugins(bindingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) ... // trigger un-reserve plugins to clean up state associated with the reserved Pod fwk.RunReservePluginsUnreserve(bindingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) ... ... // trigger un-reserve plugins to clean up state associated with the reserved Pod fwk.RunReservePluginsUnreserve(bindingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) ... // Run \u0026#34;postbind\u0026#34; plugins. fwk.RunPostBindPlugins(bindingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) ... } 插件 [4] 插件（Plugins）（也可以算是调度策略）在 kube-scheduler 中的实现为 framework plugin，插件API的实现分为两个步骤**：register** 和 configured，然后都实现了其父方法 Plugin。然后可以通过配置（kube-scheduler --config 提供）启动或禁用插件；除了默认插件外，还可以实现自定义调度插件与默认插件进行绑定。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 type Plugin interface { Name() string } // sort扩展点 type QueueSortPlugin interface { Plugin Less(*v1.pod, *v1.pod) bool } // PreFilter扩展点 type PreFilterPlugin interface { Plugin PreFilter(context.Context, *framework.CycleState, *v1.pod) error } 插件的载入过程 在 scheduler 被启动时，会 scheduler.New(cc.Client.. 这个时候会传入 profiles，整个的流如下：\nNewScheduler ：kubernetes/cmd/kube-scheduler/app/server.go profile.NewMap：kubernetes/pkg/scheduler/scheduler.go newProfile：kubernetes/pkg/scheduler/scheduler.go frameworkruntime.NewFramework：kubernetes/pkg/scheduler/framework/runtime/framework.go pluginsNeeded：kubernetes/pkg/scheduler/framework/runtime/framework.go NewScheduler 我们了解如何 New 一个 scheduler 即为 Setup 中去配置这些参数，\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func Setup(ctx context.Context, opts *options.Options, outOfTreeRegistryOptions ...Option) (*schedulerserverconfig.CompletedConfig, *scheduler.Scheduler, error) { ... // Create the scheduler. sched, err := scheduler.New(cc.Client, cc.InformerFactory, cc.DynInformerFactory, recorderFactory, ctx.Done(), scheduler.WithComponentConfigVersion(cc.ComponentConfig.TypeMeta.APIVersion), scheduler.WithKubeConfig(cc.KubeConfig), scheduler.WithProfiles(cc.ComponentConfig.Profiles...), scheduler.WithPercentageOfNodesToScore(cc.ComponentConfig.PercentageOfNodesToScore), scheduler.WithFrameworkOutOfTreeRegistry(outOfTreeRegistry), scheduler.WithPodMaxBackoffSeconds(cc.ComponentConfig.PodMaxBackoffSeconds), scheduler.WithPodInitialBackoffSeconds(cc.ComponentConfig.PodInitialBackoffSeconds), scheduler.WithPodMaxInUnschedulablePodsDuration(cc.PodMaxInUnschedulablePodsDuration), scheduler.WithExtenders(cc.ComponentConfig.Extenders...), scheduler.WithParallelism(cc.ComponentConfig.Parallelism), scheduler.WithBuildFrameworkCapturer(func(profile kubeschedulerconfig.KubeSchedulerProfile) { // Profiles are processed during Framework instantiation to set default plugins and configurations. Capturing them for logging completedProfiles = append(completedProfiles, profile) }), ) ... } profile.NewMap 在 scheduler.New 中，会根据配置生成profile，而 profile.NewMap 会完成这一步\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func New(client clientset.Interface, ... clusterEventMap := make(map[framework.ClusterEvent]sets.String) profiles, err := profile.NewMap(options.profiles, registry, recorderFactory, stopCh, frameworkruntime.WithComponentConfigVersion(options.componentConfigVersion), frameworkruntime.WithClientSet(client), frameworkruntime.WithKubeConfig(options.kubeConfig), frameworkruntime.WithInformerFactory(informerFactory), frameworkruntime.WithSnapshotSharedLister(snapshot), frameworkruntime.WithPodNominator(nominator), frameworkruntime.WithCaptureProfile(frameworkruntime.CaptureProfile(options.frameworkCapturer)), frameworkruntime.WithClusterEventMap(clusterEventMap), frameworkruntime.WithParallelism(int(options.parallelism)), frameworkruntime.WithExtenders(extenders), ) ... } NewFramework newProfile 返回的则是一个创建好的 framework\ngo 1 2 3 4 5 6 func newProfile(cfg config.KubeSchedulerProfile, r frameworkruntime.Registry, recorderFact RecorderFactory, stopCh \u0026lt;-chan struct{}, opts ...frameworkruntime.Option) (framework.Framework, error) { recorder := recorderFact(cfg.SchedulerName) opts = append(opts, frameworkruntime.WithEventRecorder(recorder)) return frameworkruntime.NewFramework(r, \u0026amp;cfg, stopCh, opts...) } 最终会走到 pluginsNeeded，这里会根据配置中开启的插件而返回一个插件集，这个就是最终在每个扩展点中药执行的插件。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func (f *frameworkImpl) pluginsNeeded(plugins *config.Plugins) sets.String { pgSet := sets.String{} if plugins == nil { return pgSet } find := func(pgs *config.PluginSet) { for _, pg := range pgs.Enabled { pgSet.Insert(pg.Name) } } // 获取到所有的扩展点，找到为Enabled的插件加入到pgSet for _, e := range f.getExtensionPoints(plugins) { find(e.plugins) } // Parse MultiPoint separately since they are not returned by f.getExtensionPoints() find(\u0026amp;plugins.MultiPoint) return pgSet } 插件的执行 在对插件源码部分分析，会找几个典型的插件进行分析，而不会对全部的进行分析，因为总的来说是大同小异，分析的插件有 NodePorts，NodeResourcesFit，podtopologyspread\nNodePorts 这里以一个简单的插件来分析；NodePorts 插件用于检查Pod请求的端口，在节点上是否为空闲端口。\nNodePorts 实现了 FilterPlugin 和 PreFilterPlugin\nPreFilter 将会被 framework 中 PreFilter 扩展点被调用。\ngo 1 2 3 4 5 6 func (pl *NodePorts) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) { s := getContainerPorts(pod) // 或得Pod得端口 // 写入状态 cycleState.Write(preFilterStateKey, preFilterState(s)) return nil, nil } Filter 将会被 framework 中 Filter 扩展点被调用。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // Filter invoked at the filter extension point. func (pl *NodePorts) Filter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status { wantPorts, err := getPreFilterState(cycleState) if err != nil { return framework.AsStatus(err) } fits := fitsPorts(wantPorts, nodeInfo) if !fits { return framework.NewStatus(framework.Unschedulable, ErrReason) } return nil } func fitsPorts(wantPorts []*v1.ContainerPort, nodeInfo *framework.NodeInfo) bool { // 对比existingPorts 和 wantPorts是否冲突，冲突则调度失败 existingPorts := nodeInfo.UsedPorts for _, cp := range wantPorts { if existingPorts.CheckConflict(cp.HostIP, string(cp.Protocol), cp.HostPort) { return false } } return true } New ，初始化新插件，在 register 中注册得\ngo 1 2 3 func New(_ runtime.Object, _ framework.Handle) (framework.Plugin, error) { return \u0026amp;NodePorts{}, nil } 在调用中，如果有任何一个插件返回错误，则跳过该扩展点注册得其他插件，返回失败。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func (f *frameworkImpl) RunFilterPlugins( ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo, ) framework.PluginToStatus { statuses := make(framework.PluginToStatus) for _, pl := range f.filterPlugins { pluginStatus := f.runFilterPlugin(ctx, pl, state, pod, nodeInfo) if !pluginStatus.IsSuccess() { if !pluginStatus.IsUnschedulable() errStatus := framework.AsStatus(fmt.Errorf(\u0026#34;running %q filter plugin: %w\u0026#34;, pl.Name(), pluginStatus.AsError())).WithFailedPlugin(pl.Name()) return map[string]*framework.Status{pl.Name(): errStatus} } pluginStatus.SetFailedPlugin(pl.Name()) statuses[pl.Name()] = pluginStatus } } return statuses } 返回得状态是一个 Status 结构体，该结构体表示了插件运行的结果。由 Code、reasons、（可选）err 和 failedPlugin （失败的那个插件名）组成。当 code 不是 Success 时，应说明原因。而且，当 code 为 Success 时，其他所有字段都应为空。nil 状态也被视为成功。\ngo 1 2 3 4 5 6 7 8 type Status struct { code Code reasons []string err error // failedPlugin is an optional field that records the plugin name a Pod failed by. // It\u0026#39;s set by the framework when code is Error, Unschedulable or UnschedulableAndUnresolvable. failedPlugin string } NodeResourcesFit [5] NodeResourcesFit 扩展检查节点是否拥有 Pod 请求的所有资源。分数可以使用以下三种策略之一，扩展点为：preFilter， filter，score\nLeastAllocated （默认） MostAllocated RequestedToCapacityRatio Fit NodeResourcesFit PreFilter 可以看到调用得 computePodResourceRequest\ntext 1 2 3 4 5 // PreFilter invoked at the prefilter extension point. func (f *Fit) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) { cycleState.Write(preFilterStateKey, computePodResourceRequest(pod)) return nil, nil } computePodResourceRequest 这里有一个注释，总体解释起来是这样得：computePodResourceRequest ，返回值（ framework.Resource）覆盖了每一个维度中资源的最大宽度。因为将按照 init-containers , containers 得顺序运行，会通过迭代方式收集每个维度中的最大值。计算时会对常规容器的资源向量求和，因为containers 运行会同时运行多个容器。计算示例为：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Pod: InitContainers IC1: CPU: 2 Memory: 1G IC2: CPU: 2 Memory: 3G Containers C1: CPU: 2 Memory: 1G C2: CPU: 1 Memory: 1G 在维度1中（InitContainers）所需资源最大值时，CPU=2, Memory=3G；而维度2（Containers）所需资源最大值为：CPU=2, Memory=1G；那么最终结果为 CPU=3, Memory=3G，因为在维度1，最大资源时Memory=3G；而维度2最大资源是CPU=1+2, Memory=1+1，取每个维度中最大资源最大宽度即为 CPU=3, Memory=3G。\n下面则看下代码得实现\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 func computePodResourceRequest(pod *v1.Pod) *preFilterState { result := \u0026amp;preFilterState{} for _, container := range pod.Spec.Containers { result.Add(container.Resources.Requests) } // 取最大得资源 for _, container := range pod.Spec.InitContainers { result.SetMaxResource(container.Resources.Requests) } // 如果Overhead正在使用，需要将其计算到总资源中 if pod.Spec.Overhead != nil { result.Add(pod.Spec.Overhead) } return result } // SetMaxResource 是比较ResourceList并为每个资源取最大值。 func (r *Resource) SetMaxResource(rl v1.ResourceList) { if r == nil { return } for rName, rQuantity := range rl { switch rName { case v1.ResourceMemory: r.Memory = max(r.Memory, rQuantity.Value()) case v1.ResourceCPU: r.MilliCPU = max(r.MilliCPU, rQuantity.MilliValue()) case v1.ResourceEphemeralStorage: if utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) { r.EphemeralStorage = max(r.EphemeralStorage, rQuantity.Value()) } default: if schedutil.IsScalarResourceName(rName) { r.SetScalar(rName, max(r.ScalarResources[rName], rQuantity.Value())) } } } } leastAllocate LeastAllocated 是 NodeResourcesFit 的打分策略 ，LeastAllocated 打分的标准是更偏向于请求资源较少的Node。将会先计算出Node上调度的pod请求的内存、CPU与其他资源的百分比，然后并根据请求的比例与容量的平均值的最小值进行优先级排序。\n计算公式是这样的：$\\frac{\\frac{cpu((capacity-requested) \\times MaxNodeScore \\times cpuWeight)}{capacity} + \\frac{memory((capacity-requested) \\times MaxNodeScore \\times memoryWeight}{capacity}) + \u0026hellip;}{weightSum}$\n下面来看下实现\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func leastResourceScorer(resToWeightMap resourceToWeightMap) func(resourceToValueMap, resourceToValueMap) int64 { return func(requested, allocable resourceToValueMap) int64 { var nodeScore, weightSum int64 for resource := range requested { weight := resToWeightMap[resource] // 计算出的资源分数乘weight resourceScore := leastRequestedScore(requested[resource], allocable[resource]) nodeScore += resourceScore * weight weightSum += weight } if weightSum == 0 { return 0 } // 最终除weightSum return nodeScore / weightSum } } leastRequestedScore 计算标准为未使用容量的计算范围为 0~MaxNodeScore，0 为最低优先级，MaxNodeScore 为最高优先级。未使用的资源越多，得分越高。\ngo 1 2 3 4 5 6 7 8 9 10 func leastRequestedScore(requested, capacity int64) int64 { if capacity == 0 { return 0 } if requested \u0026gt; capacity { return 0 } // 容量 - 请求的 x 预期值（100）/ 容量 return ((capacity - requested) * int64(framework.MaxNodeScore)) / capacity } Topology [6] Concept 在对 podtopologyspread 插件进行分析前，先需要掌握Pod拓扑的概念。\nPod拓扑（Pod Topology）是Kubernetes Pod调度机制，可以将Pod分布在集群中不同 Zone ，以及用户自定义的各种拓扑域 （topology domains）。当有了拓扑域后，用户可以更高效的利用集群资源。\n如何来解释拓扑域，首先需要提及为什么需要拓扑域，在集群有3个节点，并且当Pod副本数为2时，又不希望两个Pod在同一个Node上运行。在随着扩大Pod的规模，副本数扩展到到15个时，这时候最理想的方式是每个Node运行5个Pod，在这种背景下，用户希望对集群中Zone的安排为相似的副本数量，并且在集群存在部分问题时可以更好的自愈（也是按照相似的副本数量均匀的分布在Node上）。在这种情况下Kubernetes 提供了Pod 拓扑约束来解决这个问题。\n定义一个Topology yaml 1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: v1 kind: Pod metadata: name: example-pod spec: # Configure a topology spread constraint topologySpreadConstraints: - maxSkew: \u0026lt;integer\u0026gt; # minDomains: \u0026lt;integer\u0026gt; # optional; alpha since v1.24 topologyKey: \u0026lt;string\u0026gt; whenUnsatisfiable: \u0026lt;string\u0026gt; labelSelector: \u0026lt;object\u0026gt; 参数的描述：\nmaxSkew：Required，Pod分布不均的程度，并且数字必须大于零 当 whenUnsatisfiable: DoNotSchedule，则定义目标拓扑中匹配 pod 的数量与 全局最小值（拓扑域中的标签选择器匹配的 pod 的最小数量 ）maxSkew之间的最大允许差异。例如有 3 个 Zone，分别具有 2、4 和 5 个匹配的 pod，则全局最小值为 2 当 whenUnsatisfiable: ScheduleAnyway，scheduler 会为减少倾斜的拓扑提供更高的优先级。 minDomains：optional，符合条件的域的最小数量。 如果不指定该选项 minDomains，则约束的行为 minDomains: 1 。 minDomains必须大于 0。minDomains与 whenUnsatisfiable 一起时为whenUnsatisfiable: DoNotSchedule。 topologyKey：Node label的key，如果多个Node都使用了这个lable key那么 scheduler 将这些 Node 看作为相同的拓扑域。 whenUnsatisfiable：当 Pod 不满足分布的约束时，怎么去处理 DoNotSchedule（默认）不要调度。 ScheduleAnyway仍然调度它，同时优先考虑最小化倾斜节点 labelSelector：查找匹配的 Pod label选择器的node进行技术，以计算Pod如何分布在拓扑域中 对于拓扑域的理解 对于拓扑域，官方是这么说明的，假设有一个带有以下lable的 4 节点集群：\nbash 1 2 3 4 5 NAME STATUS ROLES AGE VERSION LABELS node1 Ready \u0026lt;none\u0026gt; 4m26s v1.16.0 node=node1,zone=zoneA node2 Ready \u0026lt;none\u0026gt; 3m58s v1.16.0 node=node2,zone=zoneA node3 Ready \u0026lt;none\u0026gt; 3m17s v1.16.0 node=node3,zone=zoneB node4 Ready \u0026lt;none\u0026gt; 2m43s v1.16.0 node=node4,zone=zoneB 那么集群拓扑如图：\n图1：集群拓扑图 Source：https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ 假设一个 4 节点集群，其中 3个label被标记为foo: bar的 Pod 分别位于Node1、Node2 和 Node3：\n图2：集群拓扑图 Source：https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ 这种情况下，新部署一个Pod，并希望新Pod与现有Pod跨 Zone均匀分布，资源清单文件如下：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 kind: Pod apiVersion: v1 metadata: name: mypod labels: foo: bar spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: foo: bar containers: - name: pause image: k8s.gcr.io/pause:3.1 这个清单对于拓扑域来说，topologyKey: zone 表示对Pod均匀分布仅应用于已标记的节点（如 foo: bar），将会跳过没有标签的节点（如zone: \u0026lt;any value\u0026gt;）。如果 scheduler 找不到满足约束的方法，whenUnsatisfiable: DoNotSchedule 设置的策略则是 scheduler 对新部署的Pod保持 Pendding\n如果此时 scheduler 将新Pod 调度至 $Zone_A$，此时Pod分布在拓扑域间为 $[3,1]$ ，而 maxSkew 配置的值是1。此时倾斜值为 $Zone_A - Zone_B = 3-1=2$，不满足 maxSkew=1，故这个Pod只能被调度到 $Zone_B$。\n此时Pod调度拓扑图为图3或图4\n图3：集群拓扑图 Source：https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ 图4：集群拓扑图 Source：https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ 如果需要将Pod调度到 $Zone_A$ ,可以按照如下方式进行：\n修改 maxSkew=2 修改 topologyKey: node 而不是 Zone ，这种模式下可以将 Pod 均匀分布在Node而不是Zone之间。 修改 whenUnsatisfiable: DoNotSchedule 为 whenUnsatisfiable: ScheduleAnyway 确保新的Pod始终可被调度 下面再通过一个例子增强对拓扑域了解\n多拓扑约束\n设拥有一个 4 节点集群，其中 3 个现有 Pod 标记 foo: bar 分别位于 node1、node2 和 node3\n图5：集群拓扑图 Source：https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ 部署的资源清单如下：可以看出拓扑分布约束配置了多个\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 kind: Pod apiVersion: v1 metadata: name: mypod labels: foo: bar spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: foo: bar - maxSkew: 1 topologyKey: node whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: foo: bar containers: - name: pause image: k8s.gcr.io/pause:3.1 在这种情况下，为了匹配第一个约束条件，新Pod 只能放置在 $Zone_B$ ；而就第二个约束条件，新Pod只能调度到 node4。在这种配置多约束条件下， scheduler 只考虑满足所有约束的值，因此唯一有效的是 node4。\n如何为集群设置一个默认拓扑域约束 默认情况下，拓扑域约束也作 scheduler 的为 scheduler configurtion 中的一部分参数，这也意味着，可以通过profile为整个集群级别指定一个默认的拓扑域调度约束，\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: kubescheduler.config.k8s.io/v1beta3 kind: KubeSchedulerConfiguration profiles: - schedulerName: default-scheduler pluginConfig: - name: PodTopologySpread args: defaultConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: ScheduleAnyway defaultingType: List 默认约束策略 如果在没有配置集群级别的约束策略时，kube-scheduler 内部 topologyspread 插件提供了一个默认的拓扑约束策略，大致上如下列清单所示\ntext 1 2 3 4 5 6 7 defaultConstraints: - maxSkew: 3 topologyKey: \u0026#34;kubernetes.io/hostname\u0026#34; whenUnsatisfiable: ScheduleAnyway - maxSkew: 5 topologyKey: \u0026#34;topology.kubernetes.io/zone\u0026#34; whenUnsatisfiable: ScheduleAnyway 上述清单中内容可以在 pkg\\scheduler\\framework\\plugins\\podtopologyspread\\plugin.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 var systemDefaultConstraints = []v1.TopologySpreadConstraint{ { TopologyKey: v1.LabelHostname, WhenUnsatisfiable: v1.ScheduleAnyway, MaxSkew: 3, }, { TopologyKey: v1.LabelTopologyZone, WhenUnsatisfiable: v1.ScheduleAnyway, MaxSkew: 5, }, } 可以通过在配置文件中留空，来禁用默认配置\ndefaultConstraints: [] defaultingType: List yaml 1 2 3 4 5 6 7 8 9 10 apiVersion: kubescheduler.config.k8s.io/v1beta3 kind: KubeSchedulerConfiguration profiles: - schedulerName: default-scheduler pluginConfig: - name: PodTopologySpread args: defaultConstraints: [] defaultingType: List 通过源码学习Topology podtopologyspread 实现了4种扩展点方法，包含 filter 和 score\nPreFilter 可以看到 PreFilter 的核心为 calPreFilterState\ngo 1 2 3 4 5 6 7 8 func (pl *PodTopologySpread) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) { s, err := pl.calPreFilterState(ctx, pod) if err != nil { return nil, framework.AsStatus(err) } cycleState.Write(preFilterStateKey, s) return nil, nil } calPreFilterState 主要功能是用在计算如何在拓扑域中分布Pod，首先看段代码时，需要掌握下属几个概念\npreFilterState criticalPaths update go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 func (pl *PodTopologySpread) calPreFilterState(ctx context.Context, pod *v1.Pod) (*preFilterState, error) { // 获取Node allNodes, err := pl.sharedLister.NodeInfos().List() if err != nil { return nil, fmt.Errorf(\u0026#34;listing NodeInfos: %w\u0026#34;, err) } var constraints []topologySpreadConstraint if len(pod.Spec.TopologySpreadConstraints) \u0026gt; 0 { // 这里会构建出TopologySpreadConstraints，因为约束是不确定的 constraints, err = filterTopologySpreadConstraints( pod.Spec.TopologySpreadConstraints, v1.DoNotSchedule, pl.enableMinDomainsInPodTopologySpread, pl.enableNodeInclusionPolicyInPodTopologySpread, ) if err != nil { return nil, fmt.Errorf(\u0026#34;obtaining pod\u0026#39;s hard topology spread constraints: %w\u0026#34;, err) } } else { // buildDefaultConstraints使用\u0026#34;.DefaultConstraints\u0026#34;与pod匹配的 // service、replication controllers、replica sets // 和stateful sets的选择器为pod构建一个约束。 constraints, err = pl.buildDefaultConstraints(pod, v1.DoNotSchedule) if err != nil { return nil, fmt.Errorf(\u0026#34;setting default hard topology spread constraints: %w\u0026#34;, err) } } if len(constraints) == 0 { // 如果是空的，则返回空preFilterState return \u0026amp;preFilterState{}, nil } // 初始化一个 preFilterState 状态 s := preFilterState{ Constraints: constraints, TpKeyToCriticalPaths: make(map[string]*criticalPaths, len(constraints)), TpPairToMatchNum: make(map[topologyPair]int, sizeHeuristic(len(allNodes), constraints)), } // 根据node统计拓扑域数量 tpCountsByNode := make([]map[topologyPair]int, len(allNodes)) // 获取pod亲和度配置 requiredNodeAffinity := nodeaffinity.GetRequiredNodeAffinity(pod) processNode := func(i int) { nodeInfo := allNodes[i] node := nodeInfo.Node() if node == nil { klog.ErrorS(nil, \u0026#34;Node not found\u0026#34;) return } // 通过spreading去过滤node以用作filters，错误解析以向后兼容 if !pl.enableNodeInclusionPolicyInPodTopologySpread { if match, _ := requiredNodeAffinity.Match(node); !match { return } } // 确保node的lable 包含topologyKeys定义的值 if !nodeLabelsMatchSpreadConstraints(node.Labels, constraints) { return } tpCounts := make(map[topologyPair]int, len(constraints)) for _, c := range constraints { // 对应的约束列表 if pl.enableNodeInclusionPolicyInPodTopologySpread \u0026amp;\u0026amp; !c.matchNodeInclusionPolicies(pod, node, requiredNodeAffinity) { continue } // 构建出 topologyPair 以key value形式， // 通常情况下TopologyKey属于什么类型的拓扑 // node.Labels[c.TopologyKey] 则是属于这个拓扑中那个子域 pair := topologyPair{key: c.TopologyKey, value: node.Labels[c.TopologyKey]} // 计算与标签选择器相匹配的pod有多少个 count := countPodsMatchSelector(nodeInfo.Pods, c.Selector, pod.Namespace) tpCounts[pair] = count } tpCountsByNode[i] = tpCounts // 最终形成的拓扑结构 } // 执行上面的定义的processNode，执行的数量就是node的数量 pl.parallelizer.Until(ctx, len(allNodes), processNode) // 最后构建出 TpPairToMatchNum // 表示每个拓扑域中的每个子域各分布多少Pod，如图6所示 for _, tpCounts := range tpCountsByNode { for tp, count := range tpCounts { s.TpPairToMatchNum[tp] += count } } if pl.enableMinDomainsInPodTopologySpread { // 根据状态进行构建 preFilterState s.TpKeyToDomainsNum = make(map[string]int, len(constraints)) for tp := range s.TpPairToMatchNum { s.TpKeyToDomainsNum[tp.key]++ } } // 计算最小匹配出的拓扑对 for i := 0; i \u0026lt; len(constraints); i++ { key := constraints[i].TopologyKey s.TpKeyToCriticalPaths[key] = newCriticalPaths() } for pair, num := range s.TpPairToMatchNum { s.TpKeyToCriticalPaths[pair.key].update(pair.value, num) } return \u0026amp;s, nil // 返回的值则包含最小的分布 } preFilterState\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // preFilterState 是在PreFilter处计算并在Filter处使用。 // 它结合了 “TpKeyToCriticalPaths” 和 “TpPairToMatchNum” 来表示： //（1）在每个分布约束上匹配最少pod的criticalPaths。 // (2) 在每个分布约束上匹配的pod的数量。 // “nil preFilterState” 则表示没有设置（在PreFilter阶段）； // empty “preFilterState”对象则表示它是一个合法的状态，并在PreFilter阶段设置。 type preFilterState struct { Constraints []topologySpreadConstraint // 这里记录2条关键路径而不是所有关键路径。 // criticalPaths[0].MatchNum 始终保存最小匹配数。 // criticalPaths[1].MatchNum 总是大于或等于criticalPaths[0].MatchNum，但不能保证是第二个最小匹配数。 TpKeyToCriticalPaths map[string]*criticalPaths // TpKeyToDomainsNum 以 “topologyKey” 作为key ，并以zone的数量作为值。 TpKeyToDomainsNum map[string]int // TpPairToMatchNum 以 “topologyPair作为key” ，并以匹配到pod的数量作为value。 TpPairToMatchNum map[topologyPair]int } criticalPaths\ngo 1 2 3 4 5 6 7 8 9 // [2]criticalPath能够工作的原因是基于当前抢占算法的实现，特别是以下两个事实 // 事实 1：只抢占同一节点上的Pod，而不是多个节点上的 Pod。 // 事实 2：每个节点在其抢占周期期间在“preFilterState”的单独副本上进行评估。如果我们计划转向更复杂的算法，例如“多个节点上的任意pod”时则需要重新考虑这种结构。 type criticalPaths [2]struct { // TopologyValue代表映射到拓扑键的拓扑值。 TopologyValue string // MatchNum代表匹配到的pod数量 MatchNum int } 单元测试中的测试案例，具有两个约束条件的场景，通过表格来解析如下：\nNode列表与标签如下表：\nNode Name \u0026#x1f3f7;\u0026#xfe0f;Lable-zone \u0026#x1f3f7;\u0026#xfe0f;Lable-node node-a zone1 node-a node-b zone1 node-b node-x zone2 node-x node-y zone2 node-y Pod列表与标签如下表：\nPod Name Node \u0026#x1f3f7;\u0026#xfe0f;Label p-a1 node-a foo: p-a2 node-a foo: p-b1 node-b foo: p-y1 node-y foo: p-y2 node-y foo: p-y3 node-y foo: p-y4 node-y foo: 对应的拓扑约束\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 spec: topologySpreadConstraints: - MaxSkew: 1 TopologyKey: zone labelSelector: matchLabels: foo: bar MinDomains: 1 NodeAffinityPolicy: Honor NodeTaintsPolicy: Ignore - MaxSkew: 1 TopologyKey: node labelSelector: matchLabels: foo: bar MinDomains: 1 NodeAffinityPolicy: Honor NodeTaintsPolicy: Ignore 那么整个分布如下：\n图6：具有两个场景的分布图 实现的测试代码如下\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 ... { name: \u0026#34;normal case with two spreadConstraints\u0026#34;, pod: st.MakePod().Name(\u0026#34;p\u0026#34;).Label(\u0026#34;foo\u0026#34;, \u0026#34;\u0026#34;). SpreadConstraint(1, \u0026#34;zone\u0026#34;, v1.DoNotSchedule, fooSelector, nil, nil, nil). SpreadConstraint(1, \u0026#34;node\u0026#34;, v1.DoNotSchedule, fooSelector, nil, nil, nil). Obj(), nodes: []*v1.Node{ st.MakeNode().Name(\u0026#34;node-a\u0026#34;).Label(\u0026#34;zone\u0026#34;, \u0026#34;zone1\u0026#34;).Label(\u0026#34;node\u0026#34;, \u0026#34;node-a\u0026#34;).Obj(), st.MakeNode().Name(\u0026#34;node-b\u0026#34;).Label(\u0026#34;zone\u0026#34;, \u0026#34;zone1\u0026#34;).Label(\u0026#34;node\u0026#34;, \u0026#34;node-b\u0026#34;).Obj(), st.MakeNode().Name(\u0026#34;node-x\u0026#34;).Label(\u0026#34;zone\u0026#34;, \u0026#34;zone2\u0026#34;).Label(\u0026#34;node\u0026#34;, \u0026#34;node-x\u0026#34;).Obj(), st.MakeNode().Name(\u0026#34;node-y\u0026#34;).Label(\u0026#34;zone\u0026#34;, \u0026#34;zone2\u0026#34;).Label(\u0026#34;node\u0026#34;, \u0026#34;node-y\u0026#34;).Obj(), }, existingPods: []*v1.Pod{ st.MakePod().Name(\u0026#34;p-a1\u0026#34;).Node(\u0026#34;node-a\u0026#34;).Label(\u0026#34;foo\u0026#34;, \u0026#34;\u0026#34;).Obj(), st.MakePod().Name(\u0026#34;p-a2\u0026#34;).Node(\u0026#34;node-a\u0026#34;).Label(\u0026#34;foo\u0026#34;, \u0026#34;\u0026#34;).Obj(), st.MakePod().Name(\u0026#34;p-b1\u0026#34;).Node(\u0026#34;node-b\u0026#34;).Label(\u0026#34;foo\u0026#34;, \u0026#34;\u0026#34;).Obj(), st.MakePod().Name(\u0026#34;p-y1\u0026#34;).Node(\u0026#34;node-y\u0026#34;).Label(\u0026#34;foo\u0026#34;, \u0026#34;\u0026#34;).Obj(), st.MakePod().Name(\u0026#34;p-y2\u0026#34;).Node(\u0026#34;node-y\u0026#34;).Label(\u0026#34;foo\u0026#34;, \u0026#34;\u0026#34;).Obj(), st.MakePod().Name(\u0026#34;p-y3\u0026#34;).Node(\u0026#34;node-y\u0026#34;).Label(\u0026#34;foo\u0026#34;, \u0026#34;\u0026#34;).Obj(), st.MakePod().Name(\u0026#34;p-y4\u0026#34;).Node(\u0026#34;node-y\u0026#34;).Label(\u0026#34;foo\u0026#34;, \u0026#34;\u0026#34;).Obj(), }, want: \u0026amp;preFilterState{ Constraints: []topologySpreadConstraint{ { MaxSkew: 1, TopologyKey: \u0026#34;zone\u0026#34;, Selector: mustConvertLabelSelectorAsSelector(t, fooSelector), MinDomains: 1, NodeAffinityPolicy: v1.NodeInclusionPolicyHonor, NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, { MaxSkew: 1, TopologyKey: \u0026#34;node\u0026#34;, Selector: mustConvertLabelSelectorAsSelector(t, fooSelector), MinDomains: 1, NodeAffinityPolicy: v1.NodeInclusionPolicyHonor, NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, TpKeyToCriticalPaths: map[string]*criticalPaths{ \u0026#34;zone\u0026#34;: {{\u0026#34;zone1\u0026#34;, 3}, {\u0026#34;zone2\u0026#34;, 4}}, \u0026#34;node\u0026#34;: {{\u0026#34;node-x\u0026#34;, 0}, {\u0026#34;node-b\u0026#34;, 1}}, }, for pair, num := range s.TpPairToMatchNum { s.TpKeyToCriticalPaths[pair.key].update(pair.value, num) } TpPairToMatchNum: map[topologyPair]int{ {key: \u0026#34;zone\u0026#34;, value: \u0026#34;zone1\u0026#34;}: 3, {key: \u0026#34;zone\u0026#34;, value: \u0026#34;zone2\u0026#34;}: 4, {key: \u0026#34;node\u0026#34;, value: \u0026#34;node-a\u0026#34;}: 2, {key: \u0026#34;node\u0026#34;, value: \u0026#34;node-b\u0026#34;}: 1, {key: \u0026#34;node\u0026#34;, value: \u0026#34;node-x\u0026#34;}: 0, {key: \u0026#34;node\u0026#34;, value: \u0026#34;node-y\u0026#34;}: 4, }, }, } ... update\nupdate 函数实际上时用于计算 criticalPaths 中的第一位始终保持为是一个最小Pod匹配值\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 func (p *criticalPaths) update(tpVal string, num int) { // first verify if `tpVal` exists or not i := -1 if tpVal == p[0].TopologyValue { i = 0 } else if tpVal == p[1].TopologyValue { i = 1 } if i \u0026gt;= 0 { // `tpVal` 表示已经存在 p[i].MatchNum = num if p[0].MatchNum \u0026gt; p[1].MatchNum { // swap paths[0] and paths[1] p[0], p[1] = p[1], p[0] } } else { // `tpVal` 表示不存在，如一个新初始化的值 // num对应子域分布的pod // 说明第一个元素不是最小的，则作为交换 if num \u0026lt; p[0].MatchNum { // update paths[1] with paths[0] p[1] = p[0] // update paths[0] p[0].TopologyValue, p[0].MatchNum = tpVal, num } else if num \u0026lt; p[1].MatchNum { // 如果小于 paths[1]，则更新它，永远保证元素0是最小，1是次小的 p[1].TopologyValue, p[1].MatchNum = tpVal, num } } } 综合来讲 Prefilter 主要做的工作是。循环所有的节点，先根据 NodeAffinity 或者 NodeSelector 进行过滤，然后根据约束中定义的 topologyKeys （拓扑划分的依据） 来选择节点。\n接下来会计算出每个拓扑域下的拓扑对（可以理解为子域）匹配的 Pod 数量，存入 TpPairToMatchNum 中，最后就是要把所有约束中匹配的 Pod 数量最小（第二小）匹配出来的路径（代码是这么定义的，理解上可以看作是分布图）放入 TpKeyToCriticalPaths 中保存起来。整个 preFilterState 保存下来传递到后续的 filter 插件中使用。\nFilter 在 preFilter 中 最后的计算结果会保存在 CycleState 中\ngo 1 cycleState.Write(preFilterStateKey, s) Filter 主要是从 PreFilter 处理的过程中拿到状态 preFilterState，然后看下每个拓扑约束中的 MaxSkew 是否合法，具体的计算公式为：$matchNum + selfMatchNum - minMatchNum$\nmatchNum：Prefilter 中计算出的对应的拓扑分布数量，可以在Prefilter中参考对应的内容 if tpCount, ok := s.TpPairToMatchNum[pair]; ok { selfMatchNum：匹配到label的数量，匹配到则是1，否则为0 minMatchNum：获的 Prefilter 中计算出来的最小匹配的值 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 func (pl *PodTopologySpread) Filter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status { node := nodeInfo.Node() if node == nil { return framework.AsStatus(fmt.Errorf(\u0026#34;node not found\u0026#34;)) } // 拿到 prefilter处理的s，即preFilterState s, err := getPreFilterState(cycleState) if err != nil { return framework.AsStatus(err) } // 一个 空类型的 preFilterState是合法的，这种情况下将容忍每一个被调度的 Pod if len(s.Constraints) == 0 { return nil } podLabelSet := labels.Set(pod.Labels) // 设置标签 for _, c := range s.Constraints { // 因为拓扑约束允许多个所以 tpKey := c.TopologyKey tpVal, ok := node.Labels[c.TopologyKey] if !ok { klog.V(5).InfoS(\u0026#34;Node doesn\u0026#39;t have required label\u0026#34;, \u0026#34;node\u0026#34;, klog.KObj(node), \u0026#34;label\u0026#34;, tpKey) return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonNodeLabelNotMatch) } // 判断标准 // 现有的匹配数量 + 子匹配（1|0） - 全局minimum \u0026lt;= maxSkew minMatchNum, err := s.minMatchNum(tpKey, c.MinDomains, pl.enableMinDomainsInPodTopologySpread) if err != nil { klog.ErrorS(err, \u0026#34;Internal error occurred while retrieving value precalculated in PreFilter\u0026#34;, \u0026#34;topologyKey\u0026#34;, tpKey, \u0026#34;paths\u0026#34;, s.TpKeyToCriticalPaths) continue } selfMatchNum := 0 if c.Selector.Matches(podLabelSet) { selfMatchNum = 1 } pair := topologyPair{key: tpKey, value: tpVal} matchNum := 0 if tpCount, ok := s.TpPairToMatchNum[pair]; ok { matchNum = tpCount } skew := matchNum + selfMatchNum - minMatchNum if skew \u0026gt; int(c.MaxSkew) { klog.V(5).InfoS(\u0026#34;Node failed spreadConstraint: matchNum + selfMatchNum - minMatchNum \u0026gt; maxSkew\u0026#34;, \u0026#34;node\u0026#34;, klog.KObj(node), \u0026#34;topologyKey\u0026#34;, tpKey, \u0026#34;matchNum\u0026#34;, matchNum, \u0026#34;selfMatchNum\u0026#34;, selfMatchNum, \u0026#34;minMatchNum\u0026#34;, minMatchNum, \u0026#34;maxSkew\u0026#34;, c.MaxSkew) return framework.NewStatus(framework.Unschedulable, ErrReasonConstraintsNotMatch) } } return nil } minMatchNum\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // minMatchNum用于计算 倾斜的全局最小值，同时考虑 MinDomains。 func (s *preFilterState) minMatchNum(tpKey string, minDomains int32, enableMinDomainsInPodTopologySpread bool) (int, error) { paths, ok := s.TpKeyToCriticalPaths[tpKey] if !ok { return 0, fmt.Errorf(\u0026#34;failed to retrieve path by topology key\u0026#34;) } // 通常来说最小值是第一个 minMatchNum := paths[0].MatchNum if !enableMinDomainsInPodTopologySpread { // 就是plugin的配置的 enableMinDomainsInPodTopologySpread return minMatchNum, nil } domainsNum, ok := s.TpKeyToDomainsNum[tpKey] if !ok { return 0, fmt.Errorf(\u0026#34;failed to retrieve the number of domains by topology key\u0026#34;) } if domainsNum \u0026lt; int(minDomains) { // 当有匹配拓扑键的符合条件的域的数量小于 配置的\u0026#34;minDomains\u0026#34;(每个约束条件的这个配置) 时， //它将全局“minimum” 设置为0。 // 因为minimum默认就为1，如果他小于1，就让他为0 minMatchNum = 0 } return minMatchNum, nil } PreScore 与 Filter 类似， PreScore 也是类似 PreFilter 的构成。 initPreScoreState 来完成过滤。\n有了 PreFilter 基础后，对于 Score 来说大同小异\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 func (pl *PodTopologySpread) PreScore( ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, filteredNodes []*v1.Node, ) *framework.Status { allNodes, err := pl.sharedLister.NodeInfos().List() if err != nil { return framework.AsStatus(fmt.Errorf(\u0026#34;getting all nodes: %w\u0026#34;, err)) } if len(filteredNodes) == 0 || len(allNodes) == 0 { // No nodes to score. return nil } state := \u0026amp;preScoreState{ IgnoredNodes: sets.NewString(), TopologyPairToPodCounts: make(map[topologyPair]*int64), } // Only require that nodes have all the topology labels if using // non-system-default spreading rules. This allows nodes that don\u0026#39;t have a // zone label to still have hostname spreading. // 如果使用非系统默认分布规则，则仅要求节点具有所有拓扑标签。 // 这将允许没有zone标签的节点仍然具有hostname分布。 requireAllTopologies := len(pod.Spec.TopologySpreadConstraints) \u0026gt; 0 || !pl.systemDefaulted err = pl.initPreScoreState(state, pod, filteredNodes, requireAllTopologies) if err != nil { return framework.AsStatus(fmt.Errorf(\u0026#34;calculating preScoreState: %w\u0026#34;, err)) } // return if incoming pod doesn\u0026#39;t have soft topology spread Constraints. if len(state.Constraints) == 0 { cycleState.Write(preScoreStateKey, state) return nil } // Ignore parsing errors for backwards compatibility. requiredNodeAffinity := nodeaffinity.GetRequiredNodeAffinity(pod) processAllNode := func(i int) { nodeInfo := allNodes[i] node := nodeInfo.Node() if node == nil { return } if !pl.enableNodeInclusionPolicyInPodTopologySpread { // `node` should satisfy incoming pod\u0026#39;s NodeSelector/NodeAffinity if match, _ := requiredNodeAffinity.Match(node); !match { return } } // All topologyKeys need to be present in `node` if requireAllTopologies \u0026amp;\u0026amp; !nodeLabelsMatchSpreadConstraints(node.Labels, state.Constraints) { return } for _, c := range state.Constraints { if pl.enableNodeInclusionPolicyInPodTopologySpread \u0026amp;\u0026amp; !c.matchNodeInclusionPolicies(pod, node, requiredNodeAffinity) { continue } pair := topologyPair{key: c.TopologyKey, value: node.Labels[c.TopologyKey]} // If current topology pair is not associated with any candidate node, // continue to avoid unnecessary calculation. // Per-node counts are also skipped, as they are done during Score. tpCount := state.TopologyPairToPodCounts[pair] if tpCount == nil { continue } count := countPodsMatchSelector(nodeInfo.Pods, c.Selector, pod.Namespace) atomic.AddInt64(tpCount, int64(count)) } } pl.parallelizer.Until(ctx, len(allNodes), processAllNode) // 保存状态给后面sorce调用 cycleState.Write(preScoreStateKey, state) return nil } 与Filter中Update使用的函数一样，这里也会到这一步，这里会构建出TopologySpreadConstraints，因为约束是不确定的\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 func filterTopologySpreadConstraints(constraints []v1.TopologySpreadConstraint, action v1.UnsatisfiableConstraintAction, enableMinDomainsInPodTopologySpread, enableNodeInclusionPolicyInPodTopologySpread bool) ([]topologySpreadConstraint, error) { var result []topologySpreadConstraint for _, c := range constraints { if c.WhenUnsatisfiable == action { // 始终调度时 selector, err := metav1.LabelSelectorAsSelector(c.LabelSelector) if err != nil { return nil, err } tsc := topologySpreadConstraint{ MaxSkew: c.MaxSkew, TopologyKey: c.TopologyKey, Selector: selector, MinDomains: 1, // If MinDomains is nil, we treat MinDomains as 1. NodeAffinityPolicy: v1.NodeInclusionPolicyHonor, // If NodeAffinityPolicy is nil, we treat NodeAffinityPolicy as \u0026#34;Honor\u0026#34;. NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, // If NodeTaintsPolicy is nil, we treat NodeTaintsPolicy as \u0026#34;Ignore\u0026#34;. } if enableMinDomainsInPodTopologySpread \u0026amp;\u0026amp; c.MinDomains != nil { tsc.MinDomains = *c.MinDomains } if enableNodeInclusionPolicyInPodTopologySpread { if c.NodeAffinityPolicy != nil { tsc.NodeAffinityPolicy = *c.NodeAffinityPolicy } if c.NodeTaintsPolicy != nil { tsc.NodeTaintsPolicy = *c.NodeTaintsPolicy } } result = append(result, tsc) } } return result, nil } Score GO 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 // 在分数扩展点调用分数。该函数返回的“score”是 `nodeName` 上匹配的 pod 数量，稍后会进行归一化。 func (pl *PodTopologySpread) Score(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) { nodeInfo, err := pl.sharedLister.NodeInfos().Get(nodeName) if err != nil { return 0, framework.AsStatus(fmt.Errorf(\u0026#34;getting node %q from Snapshot: %w\u0026#34;, nodeName, err)) } node := nodeInfo.Node() s, err := getPreScoreState(cycleState) if err != nil { return 0, framework.AsStatus(err) } // Return if the node is not qualified. if s.IgnoredNodes.Has(node.Name) { return 0, nil } // 对于每个当前的 \u0026lt;pair\u0026gt;，当前节点获得 \u0026lt;matchSum\u0026gt; 的信用分。 // 计算 \u0026lt;matchSum\u0026gt;总和 并将其作为该节点的分数返回。 var score float64 for i, c := range s.Constraints { if tpVal, ok := node.Labels[c.TopologyKey]; ok { var cnt int64 if c.TopologyKey == v1.LabelHostname { cnt = int64(countPodsMatchSelector(nodeInfo.Pods, c.Selector, pod.Namespace)) } else { pair := topologyPair{key: c.TopologyKey, value: tpVal} cnt = *s.TopologyPairToPodCounts[pair] } score += scoreForCount(cnt, c.MaxSkew, s.TopologyNormalizingWeight[i]) } } return int64(math.Round(score)), nil } 在 Framework 中会运行 ScoreExtension ，即 NormalizeScore\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // Run NormalizeScore method for each ScorePlugin in parallel. f.Parallelizer().Until(ctx, len(f.scorePlugins), func(index int) { pl := f.scorePlugins[index] nodeScoreList := pluginToNodeScores[pl.Name()] if pl.ScoreExtensions() == nil { return } status := f.runScoreExtension(ctx, pl, state, pod, nodeScoreList) if !status.IsSuccess() { err := fmt.Errorf(\u0026#34;plugin %q failed with: %w\u0026#34;, pl.Name(), status.AsError()) errCh.SendErrorWithCancel(err, cancel) return } }) if err := errCh.ReceiveError(); err != nil { return nil, framework.AsStatus(fmt.Errorf(\u0026#34;running Normalize on Score plugins: %w\u0026#34;, err)) } NormalizeScore 会为所有的node根据之前计算出的权重进行打分\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 func (pl *PodTopologySpread) NormalizeScore(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status { s, err := getPreScoreState(cycleState) if err != nil { return framework.AsStatus(err) } if s == nil { return nil } // 计算 \u0026lt;minScore\u0026gt; 和 \u0026lt;maxScore\u0026gt; var minScore int64 = math.MaxInt64 var maxScore int64 for i, score := range scores { // it\u0026#39;s mandatory to check if \u0026lt;score.Name\u0026gt; is present in m.IgnoredNodes if s.IgnoredNodes.Has(score.Name) { scores[i].Score = invalidScore continue } if score.Score \u0026lt; minScore { minScore = score.Score } if score.Score \u0026gt; maxScore { maxScore = score.Score } } for i := range scores { if scores[i].Score == invalidScore { scores[i].Score = 0 continue } if maxScore == 0 { scores[i].Score = framework.MaxNodeScore continue } s := scores[i].Score scores[i].Score = framework.MaxNodeScore * (maxScore + minScore - s) / maxScore } return nil } 到此，对于pod拓扑插件功能大概可以明了了，\nFilter 部分（PreFilter，Filter）完成拓扑对(Topology Pair)划分 Score部分（PreScore, Score , NormalizeScore ）主要是对拓扑对（可以理解为拓扑结构划分）来选择一个最适合的pod的节点（即分数最优的节点） 而在 scoring_test.go 给了很多用例，可以更深入的了解这部分算法\nReference ​[1] scheduling code hierarchy\n​[2] scheduler algorithm\n​[3] in tree VS out of tree volume plugins\n​[4] scheduler_framework_plugins\n​[5] scheduling config\n​[6] topology spread constraints\n","permalink":"https://www.161616.top/ch21-scheduling-algorithm/","summary":"调度框架 [1] 本文基于 kubernetes 1.24 进行分析\n调度框架（Scheduling Framework）是Kubernetes 的调度器 kube-scheduler 设计的的可插拔架构，将插件（调度算法）嵌入到调度上下文的每个扩展点中，并编译为 kube-scheduler\n在 kube-scheduler 1.22 之后，在 pkg/scheduler/framework/interface.go 中定义了一个 Plugin 的 interface，这个 interface 作为了所有插件的父级。而每个未调度的 Pod，Kubernetes 调度器会根据一组规则尝试在集群中寻找一个节点。\ngo 1 2 3 type Plugin interface { Name() string } 下面会对每个算法是如何实现的进行分析\n在初始化 scheduler 时，会创建一个 profile，profile是关于 scheduler 调度配置相关的定义\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func New(client clientset.Interface, .","title":"如何理解kubernetes调度框架与插件？"},{"content":"Scheduler Scheduler 是整个 kube-scheduler 的一个 structure，提供了 kube-scheduler 运行所需的组件。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 type Scheduler struct { // Cache是一个抽象，会缓存pod的信息，作为scheduler进行查找，操作是基于Pod进行增加 Cache internalcache.Cache // Extenders 算是调度框架中提供的调度插件，会影响kubernetes中的调度策略 Extenders []framework.Extender // NextPod 作为一个函数提供，会阻塞获取下一个ke\u0026#39;diao\u0026#39;du NextPod func() *framework.QueuedPodInfo // Error is called if there is an error. It is passed the pod in // question, and the error Error func(*framework.QueuedPodInfo, error) // SchedulePod 尝试将给出的pod调度到Node。 SchedulePod func(ctx context.Context, fwk framework.Framework, state *framework.CycleState, pod *v1.Pod) (ScheduleResult, error) // 关闭scheduler的信号 StopEverything \u0026lt;-chan struct{} // SchedulingQueue保存要调度的Pod SchedulingQueue internalqueue.SchedulingQueue // Profiles中是多个调度框架 Profiles profile.Map client clientset.Interface nodeInfoSnapshot *internalcache.Snapshot percentageOfNodesToScore int32 nextStartNodeIndex int } 作为实际执行的两个核心，SchedulingQueue ，与 scheduleOne 将会分析到这两个\nSchedulingQueue 在知道 kube-scheduler 初始化过程后，需要对 kube-scheduler 的整个 structure 和 workflow 进行分析\n在 Run 中，运行的是 一个 SchedulingQueue 与 一个 scheduleOne ，从结构上看是属于 Scheduler\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (sched *Scheduler) Run(ctx context.Context) { sched.SchedulingQueue.Run() // We need to start scheduleOne loop in a dedicated goroutine, // because scheduleOne function hangs on getting the next item // from the SchedulingQueue. // If there are no new pods to schedule, it will be hanging there // and if done in this goroutine it will be blocking closing // SchedulingQueue, in effect causing a deadlock on shutdown. go wait.UntilWithContext(ctx, sched.scheduleOne, 0) \u0026lt;-ctx.Done() sched.SchedulingQueue.Close() } SchedulingQueue 是一个队列的抽象，用于存储等待调度的Pod。该接口遵循类似于 cache.FIFO 和 cache.Heap 的模式。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 type SchedulingQueue interface { framework.PodNominator Add(pod *v1.Pod) error // Activate moves the given pods to activeQ iff they\u0026#39;re in unschedulablePods or backoffQ. // The passed-in pods are originally compiled from plugins that want to activate Pods, // by injecting the pods through a reserved CycleState struct (PodsToActivate). Activate(pods map[string]*v1.Pod) // 将不可调度的Pod重入到队列中 AddUnschedulableIfNotPresent(pod *framework.QueuedPodInfo, podSchedulingCycle int64) error // SchedulingCycle returns the current number of scheduling cycle which is // cached by scheduling queue. Normally, incrementing this number whenever // a pod is popped (e.g. called Pop()) is enough. SchedulingCycle() int64 // Pop会弹出一个pod，并从head优先级队列中删除 Pop() (*framework.QueuedPodInfo, error) Update(oldPod, newPod *v1.Pod) error Delete(pod *v1.Pod) error MoveAllToActiveOrBackoffQueue(event framework.ClusterEvent, preCheck PreEnqueueCheck) AssignedPodAdded(pod *v1.Pod) AssignedPodUpdated(pod *v1.Pod) PendingPods() []*v1.Pod // Close closes the SchedulingQueue so that the goroutine which is // waiting to pop items can exit gracefully. Close() // Run starts the goroutines managing the queue. Run() } 而 PriorityQueue 是 SchedulingQueue 的实现，该部分的核心构成是两个子队列与一个数据结构，即 activeQ、backoffQ 和 unschedulablePods\nactiveQ：是一个 heap 类型的优先级队列，是 sheduler 从中获得优先级最高的Pod进行调度 backoffQ：也是一个 heap 类型的优先级队列，存放的是不可调度的Pod unschedulablePods ：保存确定不可被调度的Pod GO 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 type SchedulingQueue interface { framework.PodNominator Add(pod *v1.Pod) error // Activate moves the given pods to activeQ iff they\u0026#39;re in unschedulablePods or backoffQ. // The passed-in pods are originally compiled from plugins that want to activate Pods, // by injecting the pods through a reserved CycleState struct (PodsToActivate). Activate(pods map[string]*v1.Pod) // AddUnschedulableIfNotPresent adds an unschedulable pod back to scheduling queue. // The podSchedulingCycle represents the current scheduling cycle number which can be // returned by calling SchedulingCycle(). AddUnschedulableIfNotPresent(pod *framework.QueuedPodInfo, podSchedulingCycle int64) error // SchedulingCycle returns the current number of scheduling cycle which is // cached by scheduling queue. Normally, incrementing this number whenever // a pod is popped (e.g. called Pop()) is enough. SchedulingCycle() int64 // Pop removes the head of the queue and returns it. It blocks if the // queue is empty and waits until a new item is added to the queue. Pop() (*framework.QueuedPodInfo, error) Update(oldPod, newPod *v1.Pod) error Delete(pod *v1.Pod) error MoveAllToActiveOrBackoffQueue(event framework.ClusterEvent, preCheck PreEnqueueCheck) AssignedPodAdded(pod *v1.Pod) AssignedPodUpdated(pod *v1.Pod) PendingPods() []*v1.Pod // Close closes the SchedulingQueue so that the goroutine which is // waiting to pop items can exit gracefully. Close() // Run starts the goroutines managing the queue. Run() } 在New scheduler 时可以看到会初始化这个queue\ngo 1 2 3 4 5 6 7 8 9 10 podQueue := internalqueue.NewSchedulingQueue( // 实现pod对比的一个函数即less profiles[options.profiles[0].SchedulerName].QueueSortFunc(), informerFactory, internalqueue.WithPodInitialBackoffDuration(time.Duration(options.podInitialBackoffSeconds)*time.Second), internalqueue.WithPodMaxBackoffDuration(time.Duration(options.podMaxBackoffSeconds)*time.Second), internalqueue.WithPodNominator(nominator), internalqueue.WithClusterEventMap(clusterEventMap), internalqueue.WithPodMaxInUnschedulablePodsDuration(options.podMaxInUnschedulablePodsDuration), ) 而 NewSchedulingQueue 则是初始化这个 PriorityQueue\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 // NewSchedulingQueue initializes a priority queue as a new scheduling queue. func NewSchedulingQueue( lessFn framework.LessFunc, informerFactory informers.SharedInformerFactory, opts ...Option) SchedulingQueue { return NewPriorityQueue(lessFn, informerFactory, opts...) } // NewPriorityQueue creates a PriorityQueue object. func NewPriorityQueue( lessFn framework.LessFunc, informerFactory informers.SharedInformerFactory, opts ...Option, ) *PriorityQueue { options := defaultPriorityQueueOptions for _, opt := range opts { opt(\u0026amp;options) } // 这个就是 less函数，作为打分的一部分 comp := func(podInfo1, podInfo2 interface{}) bool { pInfo1 := podInfo1.(*framework.QueuedPodInfo) pInfo2 := podInfo2.(*framework.QueuedPodInfo) return lessFn(pInfo1, pInfo2) } if options.podNominator == nil { options.podNominator = NewPodNominator(informerFactory.Core().V1().Pods().Lister()) } pq := \u0026amp;PriorityQueue{ PodNominator: options.podNominator, clock: options.clock, stop: make(chan struct{}), podInitialBackoffDuration: options.podInitialBackoffDuration, podMaxBackoffDuration: options.podMaxBackoffDuration, podMaxInUnschedulablePodsDuration: options.podMaxInUnschedulablePodsDuration, activeQ: heap.NewWithRecorder(podInfoKeyFunc, comp, metrics.NewActivePodsRecorder()), unschedulablePods: newUnschedulablePods(metrics.NewUnschedulablePodsRecorder()), moveRequestCycle: -1, clusterEventMap: options.clusterEventMap, } pq.cond.L = \u0026amp;pq.lock pq.podBackoffQ = heap.NewWithRecorder(podInfoKeyFunc, pq.podsCompareBackoffCompleted, metrics.NewBackoffPodsRecorder()) pq.nsLister = informerFactory.Core().V1().Namespaces().Lister() return pq } 了解了Queue的结构，就需要知道 入队列与出队列是在哪里操作的。在初始化时，需要注册一个 addEventHandlerFuncs 这个时候，会注入三个动作函数，也就是controller中的概念；而在AddFunc中可以看到会入队列。\n注入是对 Pod 的informer注入的，注入的函数 addPodToSchedulingQueue 就是入栈\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 Handler: cache.ResourceEventHandlerFuncs{ AddFunc: sched.addPodToSchedulingQueue, UpdateFunc: sched.updatePodInSchedulingQueue, DeleteFunc: sched.deletePodFromSchedulingQueue, }, func (sched *Scheduler) addPodToSchedulingQueue(obj interface{}) { pod := obj.(*v1.Pod) klog.V(3).InfoS(\u0026#34;Add event for unscheduled pod\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod)) if err := sched.SchedulingQueue.Add(pod); err != nil { utilruntime.HandleError(fmt.Errorf(\u0026#34;unable to queue %T: %v\u0026#34;, obj, err)) } } 而这个 SchedulingQueue 的实现就是 PriorityQueue ，而Add中则对 activeQ进行的操作\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func (p *PriorityQueue) Add(pod *v1.Pod) error { p.lock.Lock() defer p.lock.Unlock() // 格式化入栈数据，包含podinfo，里会包含v1.Pod // 初始化的时间，创建的时间，以及不能被调度时的记录其plugin的名称 pInfo := p.newQueuedPodInfo(pod) // 入栈 if err := p.activeQ.Add(pInfo); err != nil { klog.ErrorS(err, \u0026#34;Error adding pod to the active queue\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod)) return err } if p.unschedulablePods.get(pod) != nil { klog.ErrorS(nil, \u0026#34;Error: pod is already in the unschedulable queue\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod)) p.unschedulablePods.delete(pod) } // Delete pod from backoffQ if it is backing off if err := p.podBackoffQ.Delete(pInfo); err == nil { klog.ErrorS(nil, \u0026#34;Error: pod is already in the podBackoff queue\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod)) } metrics.SchedulerQueueIncomingPods.WithLabelValues(\u0026#34;active\u0026#34;, PodAdd).Inc() p.PodNominator.AddNominatedPod(pInfo.PodInfo, nil) p.cond.Broadcast() return nil } 在上面看 scheduler 结构时，可以看到有一个 nextPod的，nextPod就是从队列中弹出一个pod，这个在scheduler 时会传入 MakeNextPodFunc 就是这个 nextpod\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func MakeNextPodFunc(queue SchedulingQueue) func() *framework.QueuedPodInfo { return func() *framework.QueuedPodInfo { podInfo, err := queue.Pop() if err == nil { klog.V(4).InfoS(\u0026#34;About to try and schedule pod\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(podInfo.Pod)) for plugin := range podInfo.UnschedulablePlugins { metrics.UnschedulableReason(plugin, podInfo.Pod.Spec.SchedulerName).Dec() } return podInfo } klog.ErrorS(err, \u0026#34;Error while retrieving next pod from scheduling queue\u0026#34;) return nil } } 而这个 queue.Pop() 对应的就是 PriorityQueue 的 Pop() ，在这里会将作为 activeQ 的消费端\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func (p *PriorityQueue) Pop() (*framework.QueuedPodInfo, error) { p.lock.Lock() defer p.lock.Unlock() for p.activeQ.Len() == 0 { // When the queue is empty, invocation of Pop() is blocked until new item is enqueued. // When Close() is called, the p.closed is set and the condition is broadcast, // which causes this loop to continue and return from the Pop(). if p.closed { return nil, fmt.Errorf(queueClosed) } p.cond.Wait() } obj, err := p.activeQ.Pop() if err != nil { return nil, err } pInfo := obj.(*framework.QueuedPodInfo) pInfo.Attempts++ p.schedulingCycle++ return pInfo, nil } 在上面入口部分也看到了，scheduleOne 和 scheduler，scheduleOne 就是去消费一个Pod，他会调用 NextPod，NextPod就是在初始化传入的 MakeNextPodFunc ，至此回到对应的 Pop来做消费。\nschedulerOne是为一个Pod做调度的流程。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func (sched *Scheduler) scheduleOne(ctx context.Context) { podInfo := sched.NextPod() // pod could be nil when schedulerQueue is closed if podInfo == nil || podInfo.Pod == nil { return } pod := podInfo.Pod fwk, err := sched.frameworkForPod(pod) if err != nil { // This shouldn\u0026#39;t happen, because we only accept for scheduling the pods // which specify a scheduler name that matches one of the profiles. klog.ErrorS(err, \u0026#34;Error occurred\u0026#34;) return } if sched.skipPodSchedule(fwk, pod) { return } ... 调度上下文 当了解了scheduler结构后，下面分析下调度上下文的过程。看看扩展点是怎么工作的。这个时候又需要提到官网的调度上下文的图。\n图1：Pod的调度上下文 Source：https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework 而 scheduler 对于调度上下文来就是这个 scheduleOne ，下面就是看这个调度上下文\nSort Sort 插件提供了排序功能，用于对在调度队列中待处理 Pod 进行排序。一次只能启用一个队列排序。\n在进入 scheduleOne 后，NextPod 从 activeQ 中队列中得到一个Pod，然后的 frameworkForPod 会做打分的动作就是调度上下文的第一个扩展点 sort\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func (sched *Scheduler) scheduleOne(ctx context.Context) { podInfo := sched.NextPod() // pod could be nil when schedulerQueue is closed if podInfo == nil || podInfo.Pod == nil { return } pod := podInfo.Pod fwk, err := sched.frameworkForPod(pod) ... func (sched *Scheduler) frameworkForPod(pod *v1.Pod) (framework.Framework, error) { // 获取指定的profile fwk, ok := sched.Profiles[pod.Spec.SchedulerName] if !ok { return nil, fmt.Errorf(\u0026#34;profile not found for scheduler name %q\u0026#34;, pod.Spec.SchedulerName) } return fwk, nil } 回顾，因为在New scheduler时会初始化这个 sort 函数\ngo 1 2 3 4 5 6 7 8 9 podQueue := internalqueue.NewSchedulingQueue( profiles[options.profiles[0].SchedulerName].QueueSortFunc(), informerFactory, internalqueue.WithPodInitialBackoffDuration(time.Duration(options.podInitialBackoffSeconds)*time.Second), internalqueue.WithPodMaxBackoffDuration(time.Duration(options.podMaxBackoffSeconds)*time.Second), internalqueue.WithPodNominator(nominator), internalqueue.WithClusterEventMap(clusterEventMap), internalqueue.WithPodMaxInUnschedulablePodsDuration(options.podMaxInUnschedulablePodsDuration), ) preFilter preFilter作为第一个扩展点，是用于在过滤之前预处理或检查 Pod 或集群的相关信息。这里会终止调度\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 func (sched *Scheduler) scheduleOne(ctx context.Context) { podInfo := sched.NextPod() // pod could be nil when schedulerQueue is closed if podInfo == nil || podInfo.Pod == nil { return } pod := podInfo.Pod fwk, err := sched.frameworkForPod(pod) if err != nil { // This shouldn\u0026#39;t happen, because we only accept for scheduling the pods // which specify a scheduler name that matches one of the profiles. klog.ErrorS(err, \u0026#34;Error occurred\u0026#34;) return } if sched.skipPodSchedule(fwk, pod) { return } klog.V(3).InfoS(\u0026#34;Attempting to schedule pod\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod)) // Synchronously attempt to find a fit for the pod. start := time.Now() state := framework.NewCycleState() state.SetRecordPluginMetrics(rand.Intn(100) \u0026lt; pluginMetricsSamplePercent) // Initialize an empty podsToActivate struct, which will be filled up by plugins or stay empty. podsToActivate := framework.NewPodsToActivate() state.Write(framework.PodsToActivateKey, podsToActivate) schedulingCycleCtx, cancel := context.WithCancel(ctx) defer cancel() // 这里将进入prefilter scheduleResult, err := sched.SchedulePod(schedulingCycleCtx, fwk, state, pod) schedulePod 尝试将给定的 pod 调度到节点列表中的节点之一。如果成功，它将返回节点的名称。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func (sched *Scheduler) schedulePod(ctx context.Context, fwk framework.Framework, state *framework.CycleState, pod *v1.Pod) (result ScheduleResult, err error) { trace := utiltrace.New(\u0026#34;Scheduling\u0026#34;, utiltrace.Field{Key: \u0026#34;namespace\u0026#34;, Value: pod.Namespace}, utiltrace.Field{Key: \u0026#34;name\u0026#34;, Value: pod.Name}) defer trace.LogIfLong(100 * time.Millisecond) // 用于将cache更新为当前内容 if err := sched.Cache.UpdateSnapshot(sched.nodeInfoSnapshot); err != nil { return result, err } trace.Step(\u0026#34;Snapshotting scheduler cache and node infos done\u0026#34;) if sched.nodeInfoSnapshot.NumNodes() == 0 { return result, ErrNoNodesAvailable } // 找到一个合适的pod时，会执行扩展点 feasibleNodes, diagnosis, err := sched.findNodesThatFitPod(ctx, fwk, state, pod) ... findNodesThatFitPod 会执行对应的过滤插件来找到最适合的Node，包括备注，以及方法名都可以看到，这里运行的插件😁😁，后面会分析算法内容，只对workflow学习。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 func (sched *Scheduler) findNodesThatFitPod(ctx context.Context, fwk framework.Framework, state *framework.CycleState, pod *v1.Pod) ([]*v1.Node, framework.Diagnosis, error) { diagnosis := framework.Diagnosis{ NodeToStatusMap: make(framework.NodeToStatusMap), UnschedulablePlugins: sets.NewString(), } // Run \u0026#34;prefilter\u0026#34; plugins. preRes, s := fwk.RunPreFilterPlugins(ctx, state, pod) allNodes, err := sched.nodeInfoSnapshot.NodeInfos().List() if err != nil { return nil, diagnosis, err } if !s.IsSuccess() { if !s.IsUnschedulable() { return nil, diagnosis, s.AsError() } // All nodes will have the same status. Some non trivial refactoring is // needed to avoid this copy. for _, n := range allNodes { diagnosis.NodeToStatusMap[n.Node().Name] = s } // Status satisfying IsUnschedulable() gets injected into diagnosis.UnschedulablePlugins. if s.FailedPlugin() != \u0026#34;\u0026#34; { diagnosis.UnschedulablePlugins.Insert(s.FailedPlugin()) } return nil, diagnosis, nil } // \u0026#34;NominatedNodeName\u0026#34; can potentially be set in a previous scheduling cycle as a result of preemption. // This node is likely the only candidate that will fit the pod, and hence we try it first before iterating over all nodes. if len(pod.Status.NominatedNodeName) \u0026gt; 0 { feasibleNodes, err := sched.evaluateNominatedNode(ctx, pod, fwk, state, diagnosis) if err != nil { klog.ErrorS(err, \u0026#34;Evaluation failed on nominated node\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;node\u0026#34;, pod.Status.NominatedNodeName) } // Nominated node passes all the filters, scheduler is good to assign this node to the pod. if len(feasibleNodes) != 0 { return feasibleNodes, diagnosis, nil } } nodes := allNodes if !preRes.AllNodes() { nodes = make([]*framework.NodeInfo, 0, len(preRes.NodeNames)) for n := range preRes.NodeNames { nInfo, err := sched.nodeInfoSnapshot.NodeInfos().Get(n) if err != nil { return nil, diagnosis, err } nodes = append(nodes, nInfo) } } feasibleNodes, err := sched.findNodesThatPassFilters(ctx, fwk, state, pod, diagnosis, nodes) if err != nil { return nil, diagnosis, err } feasibleNodes, err = findNodesThatPassExtenders(sched.Extenders, pod, feasibleNodes, diagnosis.NodeToStatusMap) if err != nil { return nil, diagnosis, err } return feasibleNodes, diagnosis, nil } filter filter插件相当于调度上下文中的 Predicates，用于排除不能运行 Pod 的节点。Filter 会按配置的顺序进行调用。如果有一个filter将节点标记位不可用，则将 Pod 标记为不可调度（即不会向下执行）。\n对于代码中来讲，filter还是处于 findNodesThatFitPod 函数中，findNodesThatPassFilters 就是获取到 FN，即可行节点，而这个过程就是 filter 扩展点\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (sched *Scheduler) findNodesThatFitPod(ctx context.Context, fwk framework.Framework, state *framework.CycleState, pod *v1.Pod) ([]*v1.Node, framework.Diagnosis, error) { ... feasibleNodes, err := sched.findNodesThatPassFilters(ctx, fwk, state, pod, diagnosis, nodes) if err != nil { return nil, diagnosis, err } feasibleNodes, err = findNodesThatPassExtenders(sched.Extenders, pod, feasibleNodes, diagnosis.NodeToStatusMap) if err != nil { return nil, diagnosis, err } return feasibleNodes, diagnosis, nil } Postfilter 当没有为 pod 找到FN时，该插件会按照配置的顺序进行调用。如果任何postFilter插件将 Pod 标记为schedulable，则不会调用其余插件。即 filter 成功后不会进行这步骤，那我们来验证下这里把😊\n还是在 scheduleOne 中，当我们运行的 SchedulePod 完成后（成功或失败），这时会返回一个err，而 postfilter 会根据这个 err进行选择执行或不执行，符合官方给出的说法。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 scheduleResult, err := sched.SchedulePod(schedulingCycleCtx, fwk, state, pod) if err != nil { // SchedulePod() may have failed because the pod would not fit on any host, so we try to // preempt, with the expectation that the next time the pod is tried for scheduling it // will fit due to the preemption. It is also possible that a different pod will schedule // into the resources that were preempted, but this is harmless. var nominatingInfo *framework.NominatingInfo if fitError, ok := err.(*framework.FitError); ok { if !fwk.HasPostFilterPlugins() { klog.V(3).InfoS(\u0026#34;No PostFilter plugins are registered, so no preemption will be performed\u0026#34;) } else { // Run PostFilter plugins to try to make the pod schedulable in a future scheduling cycle. result, status := fwk.RunPostFilterPlugins(ctx, state, pod, fitError.Diagnosis.NodeToStatusMap) if status.Code() == framework.Error { klog.ErrorS(nil, \u0026#34;Status after running PostFilter plugins for pod\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;status\u0026#34;, status) } else { fitError.Diagnosis.PostFilterMsg = status.Message() klog.V(5).InfoS(\u0026#34;Status after running PostFilter plugins for pod\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;status\u0026#34;, status) } if result != nil { nominatingInfo = result.NominatingInfo } } // Pod did not fit anywhere, so it is counted as a failure. If preemption // succeeds, the pod should get counted as a success the next time we try to // schedule it. (hopefully) metrics.PodUnschedulable(fwk.ProfileName(), metrics.SinceInSeconds(start)) } else if err == ErrNoNodesAvailable { nominatingInfo = clearNominatedNode // No nodes available is counted as unschedulable rather than an error. metrics.PodUnschedulable(fwk.ProfileName(), metrics.SinceInSeconds(start)) } else { nominatingInfo = clearNominatedNode klog.ErrorS(err, \u0026#34;Error selecting node for pod\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod)) metrics.PodScheduleError(fwk.ProfileName(), metrics.SinceInSeconds(start)) } sched.handleSchedulingFailure(ctx, fwk, podInfo, err, v1.PodReasonUnschedulable, nominatingInfo) return } PreScore,Score 可用于进行预Score工作，作为通知性的扩展点，会在在filter完之后直接会关联 preScore 插件进行继续工作，而不是返回，如果配置的这些插件有任何一个返回失败，则Pod将被拒绝。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 func (sched *Scheduler) schedulePod(ctx context.Context, fwk framework.Framework, state *framework.CycleState, pod *v1.Pod) (result ScheduleResult, err error) { trace := utiltrace.New(\u0026#34;Scheduling\u0026#34;, utiltrace.Field{Key: \u0026#34;namespace\u0026#34;, Value: pod.Namespace}, utiltrace.Field{Key: \u0026#34;name\u0026#34;, Value: pod.Name}) defer trace.LogIfLong(100 * time.Millisecond) if err := sched.Cache.UpdateSnapshot(sched.nodeInfoSnapshot); err != nil { return result, err } trace.Step(\u0026#34;Snapshotting scheduler cache and node infos done\u0026#34;) if sched.nodeInfoSnapshot.NumNodes() == 0 { return result, ErrNoNodesAvailable } feasibleNodes, diagnosis, err := sched.findNodesThatFitPod(ctx, fwk, state, pod) if err != nil { return result, err } trace.Step(\u0026#34;Computing predicates done\u0026#34;) if len(feasibleNodes) == 0 { return result, \u0026amp;framework.FitError{ Pod: pod, NumAllNodes: sched.nodeInfoSnapshot.NumNodes(), Diagnosis: diagnosis, } } // When only one node after predicate, just use it. if len(feasibleNodes) == 1 { return ScheduleResult{ SuggestedHost: feasibleNodes[0].Name, EvaluatedNodes: 1 + len(diagnosis.NodeToStatusMap), FeasibleNodes: 1, }, nil } // 这里会完成prescore，score priorityList, err := prioritizeNodes(ctx, sched.Extenders, fwk, state, pod, feasibleNodes) if err != nil { return result, err } host, err := selectHost(priorityList) trace.Step(\u0026#34;Prioritizing done\u0026#34;) return ScheduleResult{ SuggestedHost: host, EvaluatedNodes: len(feasibleNodes) + len(diagnosis.NodeToStatusMap), FeasibleNodes: len(feasibleNodes), }, err } priorityNodes 会通过配置的插件给Node打分，并返回每个Node的分数，将每个插件打分结果计算总和获得Node的分数，最后获得节点的加权总分数。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 func prioritizeNodes( ctx context.Context, extenders []framework.Extender, fwk framework.Framework, state *framework.CycleState, pod *v1.Pod, nodes []*v1.Node, ) (framework.NodeScoreList, error) { // If no priority configs are provided, then all nodes will have a score of one. // This is required to generate the priority list in the required format if len(extenders) == 0 \u0026amp;\u0026amp; !fwk.HasScorePlugins() { result := make(framework.NodeScoreList, 0, len(nodes)) for i := range nodes { result = append(result, framework.NodeScore{ Name: nodes[i].Name, Score: 1, }) } return result, nil } // Run PreScore plugins. preScoreStatus := fwk.RunPreScorePlugins(ctx, state, pod, nodes) if !preScoreStatus.IsSuccess() { return nil, preScoreStatus.AsError() } // Run the Score plugins. scoresMap, scoreStatus := fwk.RunScorePlugins(ctx, state, pod, nodes) if !scoreStatus.IsSuccess() { return nil, scoreStatus.AsError() } // Additional details logged at level 10 if enabled. klogV := klog.V(10) if klogV.Enabled() { for plugin, nodeScoreList := range scoresMap { for _, nodeScore := range nodeScoreList { klogV.InfoS(\u0026#34;Plugin scored node for pod\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;plugin\u0026#34;, plugin, \u0026#34;node\u0026#34;, nodeScore.Name, \u0026#34;score\u0026#34;, nodeScore.Score) } } } // Summarize all scores. result := make(framework.NodeScoreList, 0, len(nodes)) for i := range nodes { result = append(result, framework.NodeScore{Name: nodes[i].Name, Score: 0}) for j := range scoresMap { result[i].Score += scoresMap[j][i].Score } } if len(extenders) != 0 \u0026amp;\u0026amp; nodes != nil { var mu sync.Mutex var wg sync.WaitGroup combinedScores := make(map[string]int64, len(nodes)) for i := range extenders { if !extenders[i].IsInterested(pod) { continue } wg.Add(1) go func(extIndex int) { metrics.SchedulerGoroutines.WithLabelValues(metrics.PrioritizingExtender).Inc() defer func() { metrics.SchedulerGoroutines.WithLabelValues(metrics.PrioritizingExtender).Dec() wg.Done() }() prioritizedList, weight, err := extenders[extIndex].Prioritize(pod, nodes) if err != nil { // Prioritization errors from extender can be ignored, let k8s/other extenders determine the priorities klog.V(5).InfoS(\u0026#34;Failed to run extender\u0026#39;s priority function. No score given by this extender.\u0026#34;, \u0026#34;error\u0026#34;, err, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;extender\u0026#34;, extenders[extIndex].Name()) return } mu.Lock() for i := range *prioritizedList { host, score := (*prioritizedList)[i].Host, (*prioritizedList)[i].Score if klogV.Enabled() { klogV.InfoS(\u0026#34;Extender scored node for pod\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;extender\u0026#34;, extenders[extIndex].Name(), \u0026#34;node\u0026#34;, host, \u0026#34;score\u0026#34;, score) } combinedScores[host] += score * weight } mu.Unlock() }(i) } // wait for all go routines to finish wg.Wait() for i := range result { // MaxExtenderPriority may diverge from the max priority used in the scheduler and defined by MaxNodeScore, // therefore we need to scale the score returned by extenders to the score range used by the scheduler. result[i].Score += combinedScores[result[i].Name] * (framework.MaxNodeScore / extenderv1.MaxExtenderPriority) } } if klogV.Enabled() { for i := range result { klogV.InfoS(\u0026#34;Calculated node\u0026#39;s final score for pod\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;node\u0026#34;, result[i].Name, \u0026#34;score\u0026#34;, result[i].Score) } } return result, nil } Reserve Reserve 因为绑定事件时异步发生的，该插件是为了避免Pod在绑定到节点前时，调度到新的Pod，使节点使用资源超过可用资源情况。如果后续阶段发生错误或失败，将触发 UnReserve 回滚（通知性扩展点）。这也是作为调度周期中最后一个状态，要么成功到 postBind ，要么失败触发 UnReserve。\ngo 1 2 3 4 5 6 7 8 9 10 11 // Run the Reserve method of reserve plugins. if sts := fwk.RunReservePluginsReserve(schedulingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost); !sts.IsSuccess() { // 当处理不成功时 metrics.PodScheduleError(fwk.ProfileName(), metrics.SinceInSeconds(start)) // 触发 un-reserve 来清理相关Pod的状态 fwk.RunReservePluginsUnreserve(schedulingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) if forgetErr := sched.Cache.ForgetPod(assumedPod); forgetErr != nil { klog.ErrorS(forgetErr, \u0026#34;Scheduler cache ForgetPod failed\u0026#34;) } sched.handleSchedulingFailure(ctx, fwk, assumedPodInfo, sts.AsError(), SchedulerError, clearNominatedNode) return } permit Permit 插件可以阻止或延迟 Pod 的绑定\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // Run \u0026#34;permit\u0026#34; plugins. runPermitStatus := fwk.RunPermitPlugins(schedulingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) if !runPermitStatus.IsWait() \u0026amp;\u0026amp; !runPermitStatus.IsSuccess() { var reason string if runPermitStatus.IsUnschedulable() { metrics.PodUnschedulable(fwk.ProfileName(), metrics.SinceInSeconds(start)) reason = v1.PodReasonUnschedulable } else { metrics.PodScheduleError(fwk.ProfileName(), metrics.SinceInSeconds(start)) reason = SchedulerError } // 只要其中一个插件返回的状态不是 success 或者 wait fwk.RunReservePluginsUnreserve(schedulingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) // 从cache中忘掉pod if forgetErr := sched.Cache.ForgetPod(assumedPod); forgetErr != nil { klog.ErrorS(forgetErr, \u0026#34;Scheduler cache ForgetPod failed\u0026#34;) } sched.handleSchedulingFailure(ctx, fwk, assumedPodInfo, runPermitStatus.AsError(), reason, clearNominatedNode) return } Binding Cycle 在选择好 FN 后则做一个假设绑定，并更新到cache中，接下来回去执行真正的bind操作，也就是 binding cycle\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 func (sched *Scheduler) scheduleOne(ctx context.Context) { ... ... // binding cycle 是一个异步的操作，这里表现就是go协程 go func() { bindingCycleCtx, cancel := context.WithCancel(ctx) defer cancel() metrics.SchedulerGoroutines.WithLabelValues(metrics.Binding).Inc() defer metrics.SchedulerGoroutines.WithLabelValues(metrics.Binding).Dec() // 运行WaitOnPermit插件，如果失败则，unReserve回滚 waitOnPermitStatus := fwk.WaitOnPermit(bindingCycleCtx, assumedPod) if !waitOnPermitStatus.IsSuccess() { var reason string if waitOnPermitStatus.IsUnschedulable() { metrics.PodUnschedulable(fwk.ProfileName(), metrics.SinceInSeconds(start)) reason = v1.PodReasonUnschedulable } else { metrics.PodScheduleError(fwk.ProfileName(), metrics.SinceInSeconds(start)) reason = SchedulerError } // trigger un-reserve plugins to clean up state associated with the reserved Pod fwk.RunReservePluginsUnreserve(bindingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) if forgetErr := sched.Cache.ForgetPod(assumedPod); forgetErr != nil { klog.ErrorS(forgetErr, \u0026#34;scheduler cache ForgetPod failed\u0026#34;) } else { // \u0026#34;Forget\u0026#34;ing an assumed Pod in binding cycle should be treated as a PodDelete event, // as the assumed Pod had occupied a certain amount of resources in scheduler cache. // TODO(#103853): de-duplicate the logic. // Avoid moving the assumed Pod itself as it\u0026#39;s always Unschedulable. // It\u0026#39;s intentional to \u0026#34;defer\u0026#34; this operation; otherwise MoveAllToActiveOrBackoffQueue() would // update `q.moveRequest` and thus move the assumed pod to backoffQ anyways. defer sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(internalqueue.AssignedPodDelete, func(pod *v1.Pod) bool { return assumedPod.UID != pod.UID }) } sched.handleSchedulingFailure(ctx, fwk, assumedPodInfo, waitOnPermitStatus.AsError(), reason, clearNominatedNode) return } // 运行Prebind 插件 preBindStatus := fwk.RunPreBindPlugins(bindingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) if !preBindStatus.IsSuccess() { metrics.PodScheduleError(fwk.ProfileName(), metrics.SinceInSeconds(start)) // trigger un-reserve plugins to clean up state associated with the reserved Pod fwk.RunReservePluginsUnreserve(bindingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) if forgetErr := sched.Cache.ForgetPod(assumedPod); forgetErr != nil { klog.ErrorS(forgetErr, \u0026#34;scheduler cache ForgetPod failed\u0026#34;) } else { // \u0026#34;Forget\u0026#34;ing an assumed Pod in binding cycle should be treated as a PodDelete event, // as the assumed Pod had occupied a certain amount of resources in scheduler cache. // TODO(#103853): de-duplicate the logic. sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(internalqueue.AssignedPodDelete, nil) } sched.handleSchedulingFailure(ctx, fwk, assumedPodInfo, preBindStatus.AsError(), SchedulerError, clearNominatedNode) return } // bind是真正的绑定操作 err := sched.bind(bindingCycleCtx, fwk, assumedPod, scheduleResult.SuggestedHost, state) if err != nil { metrics.PodScheduleError(fwk.ProfileName(), metrics.SinceInSeconds(start)) // 如果失败了就触发 un-reserve plugins fwk.RunReservePluginsUnreserve(bindingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) if err := sched.Cache.ForgetPod(assumedPod); err != nil { klog.ErrorS(err, \u0026#34;scheduler cache ForgetPod failed\u0026#34;) } else { // \u0026#34;Forget\u0026#34;ing an assumed Pod in binding cycle should be treated as a PodDelete event, // as the assumed Pod had occupied a certain amount of resources in scheduler cache. // TODO(#103853): de-duplicate the logic. sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(internalqueue.AssignedPodDelete, nil) } sched.handleSchedulingFailure(ctx, fwk, assumedPodInfo, fmt.Errorf(\u0026#34;binding rejected: %w\u0026#34;, err), SchedulerError, clearNominatedNode) return } // Calculating nodeResourceString can be heavy. Avoid it if klog verbosity is below 2. klog.V(2).InfoS(\u0026#34;Successfully bound pod to node\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;node\u0026#34;, scheduleResult.SuggestedHost, \u0026#34;evaluatedNodes\u0026#34;, scheduleResult.EvaluatedNodes, \u0026#34;feasibleNodes\u0026#34;, scheduleResult.FeasibleNodes) metrics.PodScheduled(fwk.ProfileName(), metrics.SinceInSeconds(start)) metrics.PodSchedulingAttempts.Observe(float64(podInfo.Attempts)) metrics.PodSchedulingDuration.WithLabelValues(getAttemptsLabel(podInfo)).Observe(metrics.SinceInSeconds(podInfo.InitialAttemptTimestamp)) // 运行 \u0026#34;postbind\u0026#34; 插件 // 是通知性的扩展点，该插件在绑定 Pod 后调用，可用于清理相关资源（）。 fwk.RunPostBindPlugins(bindingCycleCtx, state, assumedPod, scheduleResult.SuggestedHost) // At the end of a successful binding cycle, move up Pods if needed. if len(podsToActivate.Map) != 0 { sched.SchedulingQueue.Activate(podsToActivate.Map) // Unlike the logic in scheduling cycle, we don\u0026#39;t bother deleting the entries // as `podsToActivate.Map` is no longer consumed. } }() } 调度上下文中的失败流程 上面说到的都是正常的请求，下面会对失败的请求是如何重试的进行分析，而 scheduler 中关于失败处理方面相关的属性会涉及到上面 scheduler 结构中的 backoffQ 与 unschedulablePods backoffQ：也是一个 heap 类型的优先级队列，存放的是不可调度的Pod unschedulablePods ：保存确定不可被调度的Pod，一个map类型 backoffQ 与 unschedulablePods 会在初始化 scheduler 时初始化，\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 func NewPriorityQueue( lessFn framework.LessFunc, informerFactory informers.SharedInformerFactory, opts ...Option, ) *PriorityQueue { options := defaultPriorityQueueOptions for _, opt := range opts { opt(\u0026amp;options) } comp := func(podInfo1, podInfo2 interface{}) bool { pInfo1 := podInfo1.(*framework.QueuedPodInfo) pInfo2 := podInfo2.(*framework.QueuedPodInfo) return lessFn(pInfo1, pInfo2) } if options.podNominator == nil { options.podNominator = NewPodNominator(informerFactory.Core().V1().Pods().Lister()) } pq := \u0026amp;PriorityQueue{ PodNominator: options.podNominator, clock: options.clock, stop: make(chan struct{}), podInitialBackoffDuration: options.podInitialBackoffDuration, podMaxBackoffDuration: options.podMaxBackoffDuration, podMaxInUnschedulablePodsDuration: options.podMaxInUnschedulablePodsDuration, activeQ: heap.NewWithRecorder(podInfoKeyFunc, comp, metrics.NewActivePodsRecorder()), unschedulablePods: newUnschedulablePods(metrics.NewUnschedulablePodsRecorder()), moveRequestCycle: -1, clusterEventMap: options.clusterEventMap, } pq.cond.L = \u0026amp;pq.lock // 初始化backoffQ // NewWithRecorder作为一个可选的 metricRecorder 的 Heap 对象。 // podInfoKeyFunc是一个函数，返回错误与字符串 // pq.podsCompareBackoffCompleted 比较两个pod的回退时间，如果第一个在第二个之前为true， // 反之 false pq.podBackoffQ = heap.NewWithRecorder(podInfoKeyFunc, pq.podsCompareBackoffCompleted, metrics.NewBackoffPodsRecorder()) pq.nsLister = informerFactory.Core().V1().Namespaces().Lister() return pq } 对于初始化 backoffQ 会产生的两个函数，getBackoffTime 与 calculateBackoffDuration\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // getBackoffTime returns the time that podInfo completes backoff func (p *PriorityQueue) getBackoffTime(podInfo *framework.QueuedPodInfo) time.Time { duration := p.calculateBackoffDuration(podInfo) backoffTime := podInfo.Timestamp.Add(duration) return backoffTime } // calculateBackoffDuration is a helper function for calculating the backoffDuration // based on the number of attempts the pod has made. func (p *PriorityQueue) calculateBackoffDuration(podInfo *framework.QueuedPodInfo) time.Duration { duration := p.podInitialBackoffDuration for i := 1; i \u0026lt; podInfo.Attempts; i++ { // Use subtraction instead of addition or multiplication to avoid overflow. if duration \u0026gt; p.podMaxBackoffDuration-duration { return p.podMaxBackoffDuration } duration += duration } return duration } 对于整个故障错误会按照如下流程进行，在初始化 scheduler 会注册一个 Error 函数，这个函数用作对不可调度Pod进行处理，实际上被注册的函数是 MakeDefaultErrorFunc。这个函数将作为 Error 函数被调用。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 sched := newScheduler( schedulerCache, extenders, internalqueue.MakeNextPodFunc(podQueue), MakeDefaultErrorFunc(client, podLister, podQueue, schedulerCache), stopEverything, podQueue, profiles, client, snapshot, options.percentageOfNodesToScore, ) 而在 调度周期中，也就是 scheduleOne 可以看到，每个扩展点操作失败后都会调用 handleSchedulingFailure 而该函数，使用了注册的 Error 函数来处理Pod\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 func (sched *Scheduler) scheduleOne(ctx context.Context) { ... defer cancel() scheduleResult, err := sched.SchedulePod(schedulingCycleCtx, fwk, state, pod) if err != nil { var nominatingInfo *framework.NominatingInfo if fitError, ok := err.(*framework.FitError); ok { if !fwk.HasPostFilterPlugins() { klog.V(3).InfoS(\u0026#34;No PostFilter plugins are registered, so no preemption will be performed\u0026#34;) } else { result, status := fwk.RunPostFilterPlugins(ctx, state, pod, fitError.Diagnosis.NodeToStatusMap) if status.Code() == framework.Error { klog.ErrorS(nil, \u0026#34;Status after running PostFilter plugins for pod\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;status\u0026#34;, status) } else { fitError.Diagnosis.PostFilterMsg = status.Message() klog.V(5).InfoS(\u0026#34;Status after running PostFilter plugins for pod\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;status\u0026#34;, status) } if result != nil { nominatingInfo = result.NominatingInfo } } metrics.PodUnschedulable(fwk.ProfileName(), metrics.SinceInSeconds(start)) } else if err == ErrNoNodesAvailable { nominatingInfo = clearNominatedNode // No nodes available is counted as unschedulable rather than an error. metrics.PodUnschedulable(fwk.ProfileName(), metrics.SinceInSeconds(start)) } else { nominatingInfo = clearNominatedNode klog.ErrorS(err, \u0026#34;Error selecting node for pod\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod)) metrics.PodScheduleError(fwk.ProfileName(), metrics.SinceInSeconds(start)) } // 处理不可调度Pod sched.handleSchedulingFailure(ctx, fwk, podInfo, err, v1.PodReasonUnschedulable, nominatingInfo) return } 来到了注册的 Error 函数 MakeDefaultErrorFunc\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 func MakeDefaultErrorFunc(client clientset.Interface, podLister corelisters.PodLister, podQueue internalqueue.SchedulingQueue, schedulerCache internalcache.Cache) func(*framework.QueuedPodInfo, error) { return func(podInfo *framework.QueuedPodInfo, err error) { pod := podInfo.Pod if err == ErrNoNodesAvailable { klog.V(2).InfoS(\u0026#34;Unable to schedule pod; no nodes are registered to the cluster; waiting\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod)) } else if fitError, ok := err.(*framework.FitError); ok { // Inject UnschedulablePlugins to PodInfo, which will be used later for moving Pods between queues efficiently. podInfo.UnschedulablePlugins = fitError.Diagnosis.UnschedulablePlugins klog.V(2).InfoS(\u0026#34;Unable to schedule pod; no fit; waiting\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;err\u0026#34;, err) } else if apierrors.IsNotFound(err) { klog.V(2).InfoS(\u0026#34;Unable to schedule pod, possibly due to node not found; waiting\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;err\u0026#34;, err) if errStatus, ok := err.(apierrors.APIStatus); ok \u0026amp;\u0026amp; errStatus.Status().Details.Kind == \u0026#34;node\u0026#34; { nodeName := errStatus.Status().Details.Name // when node is not found, We do not remove the node right away. Trying again to get // the node and if the node is still not found, then remove it from the scheduler cache. _, err := client.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{}) if err != nil \u0026amp;\u0026amp; apierrors.IsNotFound(err) { node := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} if err := schedulerCache.RemoveNode(\u0026amp;node); err != nil { klog.V(4).InfoS(\u0026#34;Node is not found; failed to remove it from the cache\u0026#34;, \u0026#34;node\u0026#34;, node.Name) } } } } else { klog.ErrorS(err, \u0026#34;Error scheduling pod; retrying\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod)) } // Check if the Pod exists in informer cache. cachedPod, err := podLister.Pods(pod.Namespace).Get(pod.Name) if err != nil { klog.InfoS(\u0026#34;Pod doesn\u0026#39;t exist in informer cache\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;err\u0026#34;, err) return } // In the case of extender, the pod may have been bound successfully, but timed out returning its response to the scheduler. // It could result in the live version to carry .spec.nodeName, and that\u0026#39;s inconsistent with the internal-queued version. if len(cachedPod.Spec.NodeName) != 0 { klog.InfoS(\u0026#34;Pod has been assigned to node. Abort adding it back to queue.\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod), \u0026#34;node\u0026#34;, cachedPod.Spec.NodeName) return } // As \u0026lt;cachedPod\u0026gt; is from SharedInformer, we need to do a DeepCopy() here. podInfo.PodInfo = framework.NewPodInfo(cachedPod.DeepCopy()) // 添加到unschedulable队列中 if err := podQueue.AddUnschedulableIfNotPresent(podInfo, podQueue.SchedulingCycle()); err != nil { klog.ErrorS(err, \u0026#34;Error occurred\u0026#34;) } } } 下面来到 AddUnschedulableIfNotPresent ，这个也是操作 backoffQ 和 unschedulablePods 的真正的动作\nAddUnschedulableIfNotPresent 函数会吧无法调度的 pod 插入队列，除非它已经在队列中。通常情况下，PriorityQueue 将不可调度的 Pod 放在 unschedulablePods 中。但如果最近有 move request，则将 pod 放入 podBackoffQ 中。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 func (p *PriorityQueue) AddUnschedulableIfNotPresent(pInfo *framework.QueuedPodInfo, podSchedulingCycle int64) error { p.lock.Lock() defer p.lock.Unlock() pod := pInfo.Pod // 如果已经存在则不添加 if p.unschedulablePods.get(pod) != nil { return fmt.Errorf(\u0026#34;Pod %v is already present in unschedulable queue\u0026#34;, klog.KObj(pod)) } // 检查是否在activeQ中 if _, exists, _ := p.activeQ.Get(pInfo); exists { return fmt.Errorf(\u0026#34;Pod %v is already present in the active queue\u0026#34;, klog.KObj(pod)) } // 检查是否在podBackoffQ中 if _, exists, _ := p.podBackoffQ.Get(pInfo); exists { return fmt.Errorf(\u0026#34;Pod %v is already present in the backoff queue\u0026#34;, klog.KObj(pod)) } // 在重新添加时，会刷新 Pod时间为最新操作的时间 pInfo.Timestamp = p.clock.Now() for plugin := range pInfo.UnschedulablePlugins { metrics.UnschedulableReason(plugin, pInfo.Pod.Spec.SchedulerName).Inc() } // 如果接受到move request那么则放入BackoffQ if p.moveRequestCycle \u0026gt;= podSchedulingCycle { if err := p.podBackoffQ.Add(pInfo); err != nil { return fmt.Errorf(\u0026#34;error adding pod %v to the backoff queue: %v\u0026#34;, pod.Name, err) } metrics.SchedulerQueueIncomingPods.WithLabelValues(\u0026#34;backoff\u0026#34;, ScheduleAttemptFailure).Inc() } else { // 否则将放入到 unschedulablePods p.unschedulablePods.addOrUpdate(pInfo) metrics.SchedulerQueueIncomingPods.WithLabelValues(\u0026#34;unschedulable\u0026#34;, ScheduleAttemptFailure).Inc() } p.PodNominator.AddNominatedPod(pInfo.PodInfo, nil) return nil } 在启动 scheduler 时，会将这两个队列异步启用两个loop来操作队列。表现在 Run()\ngo 1 2 3 4 func (p *PriorityQueue) Run() { go wait.Until(p.flushBackoffQCompleted, 1.0*time.Second, p.stop) go wait.Until(p.flushUnschedulablePodsLeftover, 30*time.Second, p.stop) } 可以看到 flushBackoffQCompleted 作为 BackoffQ 实现；而 flushUnschedulablePodsLeftover 作为 UnschedulablePods 实现。\nflushBackoffQCompleted 是用于将所有已完成回退的 pod 从 backoffQ 移到 activeQ 中\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 func (p *PriorityQueue) flushBackoffQCompleted() { p.lock.Lock() defer p.lock.Unlock() broadcast := false for { // 这就是heap实现的方法，窥视下，但不弹出 rawPodInfo := p.podBackoffQ.Peek() if rawPodInfo == nil { break } pod := rawPodInfo.(*framework.QueuedPodInfo).Pod boTime := p.getBackoffTime(rawPodInfo.(*framework.QueuedPodInfo)) if boTime.After(p.clock.Now()) { break } _, err := p.podBackoffQ.Pop() // 弹出一个 if err != nil { klog.ErrorS(err, \u0026#34;Unable to pop pod from backoff queue despite backoff completion\u0026#34;, \u0026#34;pod\u0026#34;, klog.KObj(pod)) break } p.activeQ.Add(rawPodInfo) // 放入到活动队列中 metrics.SchedulerQueueIncomingPods.WithLabelValues(\u0026#34;active\u0026#34;, BackoffComplete).Inc() broadcast = true } if broadcast { p.cond.Broadcast() } } flushUnschedulablePodsLeftover 函数用于将在 unschedulablePods 中的存放时间超过 podMaxInUnschedulablePodsDuration 值的 pod 移动到 backoffQ 或 activeQ 中。\npodMaxInUnschedulablePodsDuration 会根据配置传入，当没有传入，也就是使用了 Deprecated 那么会为5分钟。\ngo 1 2 3 4 5 6 7 8 func NewOptions() *Options { o := \u0026amp;Options{ SecureServing: apiserveroptions.NewSecureServingOptions().WithLoopback(), Authentication: apiserveroptions.NewDelegatingAuthenticationOptions(), Authorization: apiserveroptions.NewDelegatingAuthorizationOptions(), Deprecated: \u0026amp;DeprecatedOptions{ PodMaxInUnschedulablePodsDuration: 5 * time.Minute, }, 对于 flushUnschedulablePodsLeftover 就是做一个时间对比，然后添加到对应的队列中\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func (p *PriorityQueue) flushUnschedulablePodsLeftover() { p.lock.Lock() defer p.lock.Unlock() var podsToMove []*framework.QueuedPodInfo currentTime := p.clock.Now() for _, pInfo := range p.unschedulablePods.podInfoMap { lastScheduleTime := pInfo.Timestamp if currentTime.Sub(lastScheduleTime) \u0026gt; p.podMaxInUnschedulablePodsDuration { podsToMove = append(podsToMove, pInfo) } } if len(podsToMove) \u0026gt; 0 { p.movePodsToActiveOrBackoffQueue(podsToMove, UnschedulableTimeout) } } 总结调度上下文流程 在构建一个 scheduler 时经历如下步骤： 准备cache，informer，queue，错误处理函数等 添加事件函数，会监听资源（如Pod），当有变动则触发对应事件函数，这是入站 activeQ 构建完成后会 run，run时会run一个 SchedulingQueue，这个是作为不可调度队列 BackoffQ UnschedulablePods 不可调度队列会根据注册时定期消费队列中Pod将其添加到 activeQ 中 启动一个 scheduleOne 的loop，这个是调度上下文中所有的扩展点的执行，也是 activeQ 的消费端 scheduleOne 获取 pod 执行各个扩展点，如果出错则 Error 函数 MakeDefaultErrorFunc 将其添加到不可调度队列中 回到不可调度队列中消费部分 Reference ​[1] kubernetes scheduler extender\n","permalink":"https://www.161616.top/ch20-schedule-workflow/","summary":"Scheduler Scheduler 是整个 kube-scheduler 的一个 structure，提供了 kube-scheduler 运行所需的组件。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 type Scheduler struct { // Cache是一个抽象，会缓存pod的信息，作为scheduler进行查找，操作是基于Pod进行增加 Cache internalcache.Cache // Extenders 算是调度框架中提供的调度插件，会影响kubernetes中的调度策略 Extenders []framework.Extender // NextPod 作为一个函数提供，会阻塞获取下一个ke\u0026#39;diao\u0026#39;du NextPod func() *framework.QueuedPodInfo // Error is called if there is an error. It is passed the pod in // question, and the error Error func(*framework.","title":"kube-scheduler的调度上下文"},{"content":"Overview [1] kubernetes集群中的调度程序 kube-scheduler 会 watch 未分配节点的新创建的Pod，并未该Pod找到可运行的最佳（特定）节点。那么这些动作或者说这些原理是怎么实现的呢，让我们往下剖析下。\n对于新创建的 pod 或其他未调度的 pod来讲，kube-scheduler 选择一个最佳节点供它们运行。但是，Pod 中的每个容器对资源的要求都不同，每个 Pod 也有不同的要求。因此，需要根据具体的调度要求对现有节点进行过滤。\n在Kubernetes集群中，满足 Pod 调度要求的节点称为可行节点 （ feasible nodes FN） 。如果没有合适的节点，则 pod 将保持未调度状态，直到调度程序能够放置它。也就是说，当我们创建Pod时，如果长期处于 Pending 状态，这个时候应该看你的集群调度器是否因为某些问题没有合适的节点了\n调度器为 Pod 找到 FN 后，然后运行一组函数对 FN 进行评分，并在 FN 中找到得分最高的节点来运行 Pod。\n调度策略在决策时需要考虑的因素包括个人和集体资源需求、硬件/软件/策略约束 （constraints）、亲和性 (affinity) 和反亲和性（ anti-affinity ）规范、数据局部性、工作负载间干扰等。\n如何为pod选择节点？ kube-scheduler 为pod选择节点会分位两部：\n过滤 (Filtering) 打分 (Scoring) 过滤也被称为预选 （Predicates），该步骤会找到可调度的节点集，然后通过是否满足特定资源的请求，例如通过 PodFitsResources 过滤器检查候选节点是否有足够的资源来满足 Pod 资源的请求。这个步骤完成后会得到一个包含合适的节点的列表（通常为多个），如果列表为空，则Pod不可调度。\n打分也被称为优选（Priorities），在该步骤中，会对上一个步骤的输出进行打分，Scheduer 通过打分的规则为每个通过 Filtering 步骤的节点计算出一个分数。\n完成上述两个步骤之后，kube-scheduler 会将Pod分配给分数最高的 Node，如果存在多个相同分数的节点，会随机选择一个。\nkubernetes的调度策略 Kubernetes 1.21之前版本可以在代码 kubernetes\\pkg\\scheduler\\algorithmprovider\\registry.go 中看到对应的注册模式，在1.22 scheduler 更换了其路径，对于registry文件更换到了kubernetes\\pkg\\scheduler\\framework\\plugins\\registry.go ；对于kubernetes官方说法为，调度策略是用于“预选” (Predicates )或 过滤（filtering ） 和 用于 优选（Priorities）或 评分 (scoring)的\n注：kubernetes官方没有找到预选和优选的概念，而Predicates和filtering 是处于预选阶段的动词，而Priorities和scoring是优选阶段的动词。后面用PF和PS代替这个两个词。\n为Pod预选节点 [2] 上面也提到了，filtering 的目的是为了排除（过滤）掉不满足 Pod 要求的节点。例如，某个节点上的闲置资源小于 Pod 所需资源，则该节点不会被考虑在内，即被过滤掉。在 “Predicates” 阶段实现的 filtering 策略，包括：\nNoDiskConflict ：评估是否有合适Pod请求的卷 NoVolumeZoneConflict：在给定zone限制情况下，评估Pod请求所需的卷在Node上是否可用 PodFitsResources：检查空闲资源（CPU、内存）是否满足Pod请求 PodFitsHostPorts：检查Pod所需端口在Node上是否被占用 HostName： 过滤除去，PodSpec 中 NodeName 字段中指定的Node之外的所有Node。 MatchNodeSelector：检查Node的 label 是否与 Pod 配置中 nodeSelector字段中指定的 label 匹配，并且从 Kubernetes v1.2 开始， 如果存在 nodeAffinity 也会匹配。 CheckNodeMemoryPressure：检查是否可以在已出现内存压力情况节点上调度 Pod。 CheckNodeDiskPressure：检查是否可以在报告磁盘压力情况的节点上调度 Pod 具体对应得策略可以在 kubernetes\\pkg\\scheduler\\framework\\plugins\\registry.go 看到\n对预选节点打分 [2] 通过上面步骤过滤过得列表则是适合托管的Pod，这个结果通常来说是一个列表，如何选择最优Node进行调度，则是接下来打分的步骤步骤。\n例如：Kubernetes对剩余节点进行优先级排序，优先级由一组函数计算；优先级函数将为剩余节点给出从0~10 的分数，10 表示最优，0 表示最差。每个优先级函数由一个正数加权组成，每个Node的得分是通过将所有加权得分相加来计算的。设有两个优先级函数，priorityFunc1 和 priorityFunc2 加上权重因子 weight1 和weight2，那么这个Node的最终得分为：$finalScore = (w1 \\times priorityFunc1) + (w2 \\times priorityFunc2)$。计算完分数后，选择最高分数的Node做为Pod的宿主机，存在多个相同分数Node情况下会随机选择一个Node。\n目前kubernetes提供了一些在打分 Scoring 阶段算法：\nLeastRequestedPriority：Node的优先级基于Node的空闲部分$\\frac{capacity\\ -\\ Node上所有存在的Pod\\ -\\ 正在调度的Pod请求}{capacity}$，通过计算具有最高分数的Node是FN BalancedResourceAllocation ：该算法会将 Pod 放在一个Node上，使得在Pod 部署后 CPU 和内存的使用率为平衡的 SelectorSpreadPriority：通过最小化资源方式，将属于同一种服务、控制器或同一Node上的Replica的 Pod的数量来分布Pod。如果节点上存在Zone，则会调整优先级，以便 pod可以分布在Zone之上。 CalculateAntiAffinityPriority：根据label来分布，按照相同service上相同label值的pod进行分配 ImageLocalityPriority ：根据Node上镜像进行打分，Node上存在Pod请求所需的镜像优先级较高。 在代码中查看上述的代码 以 PodFitsHostPorts 算法为例，因为是Node类算法，在kubernetes\\pkg\\scheduler\\framework\\plugins\\nodeports\n调度框架 [3] 调度框架 (scheduling framework SF ) 是kubernetes为 scheduler设计的一个pluggable的架构。SF 将scheduler设计为 Plugin 式的 API，API将上一章中提到的一些列调度策略实现为 Plugin。\n在 SF 中，定义了一些扩展点 （extension points EP ），而被实现为Plugin的调度程序将被注册在一个或多个 EP 中，换句话来说，在这些 EP 的执行过程中如果注册在多个 EP 中，将会在多个 EP 被调用。\n每次调度都分为两个阶段，调度周期（Scheduling Cycel）与绑定周期（Binding Cycle）。\nSC 表示为，为Pod选择一个节点；SC 是串行运行的。 BC 表示为，将 SC 决策结果应用于集群中；BC 可以同时运行。 调度周期与绑定周期结合一起，被称为调度上下文 （Scheduling Context）,下图则是调度上下文的工作流\n注：如果决策结果为Pod的调度结果无可用节点，或存在内部错误，则中止 SC 或 BC。Pod将重入队列重试\n图1：Pod的调度上下文 Source：https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework 扩展点 [4] 扩展点（Extension points）是指在调度上下文中的每个可扩展API，通过图提现为[图1]。其中 Filter 相当于 Predicate 而 Scoring 相当于 Priority。\n对于调度阶段会通过以下扩展点：\nSort：该插件提供了排序功能，用于对在调度队列中待处理 Pod 进行排序。一次只能启用一个队列排序。\npreFilter：该插件用于在过滤之前预处理或检查 Pod 或集群的相关信息。这里会终止调度\nfilter：该插件相当于调度上下文中的 Predicates，用于排除不能运行 Pod 的节点。Filter 会按配置的顺序进行调用。如果有一个filter将节点标记位不可用，则将 Pod 标记为不可调度（即不会向下执行）。\npostFilter：当没有为 pod 找到FN时，该插件会按照配置的顺序进行调用。如果任何postFilter插件将 Pod 标记为schedulable，则不会调用其余插件。即 filter 成功后不会进行这步骤\npreScore：可用于进行预Score工作（通知性的扩展点）。\nscore：该插件为每个通过 filter 阶段的Node提供打分服务。然后Scheduler将选择具有最高加权分数总和的Node。\nreserve：因为绑定事件时异步发生的，该插件是为了避免Pod在绑定到节点前时，调度到新的Pod，使节点使用资源超过可用资源情况。如果后续阶段发生错误或失败，将触发 UnReserve 回滚（通知性扩展点）。这也是作为调度周期中最后一个状态，要么成功到 postBind ，要么失败触发 UnReserve。\npermit：该插件可以阻止或延迟 Pod 的绑定，一般情况下这步骤会做三件事：\nappove ：调度器继续绑定过程 Deny：如果任何一个Premit拒绝了Pod与节点的绑定，那么将触发 UnReserve ，并重入队列 Wait： 如果 Permit 插件返回 Wait，该 Pod 将保留在内部 Wait Pod 列表中，直到被 Appove。如果发生超时，wait 变为 deny ，将Pod放回至调度队列中，并触发 Unreserve 回滚 。 preBind：该插件用于在 bind Pod 之前执行所需的前置工作。如，preBind 可能会提供一个网络卷并将其挂载到目标节点上。如果在该步骤中的任意插件返回错误，则Pod 将被 deny 并放置到调度队列中。\nbind：在所有的 preBind 完成后，该插件将用于将Pod绑定到Node，并按顺序调用绑定该步骤的插件。如果有一个插件处理了这个事件，那么则忽略其余所有插件。\npostBind：该插件在绑定 Pod 后调用，可用于清理相关资源（通知性的扩展点）。\nmultiPoint：这是一个仅配置字段，允许同时为所有适用的扩展点启用或禁用插件。\nkube-scheduler工作流分析 对于 kube-scheduler 组件的分析，包含 kube-scheduler 启动流程，以及scheduler调度流程。这里会主要针对启动流程分析，后面算法及二次开发部分会切入调度分析。\n对于我们部署时使用的 kube-scheduler 位于 cmd/kube-scheduler ，在 Alpha (1.16) 版本提供了调度框架的模式，到 Stable (1.19) ，从代码结构上是相似的；直到1.22后改变了代码风格。\n首先看到的是 kube-scheduler 的入口 cmd/kube-scheduler ，这里主要作为两部分，构建参数与启动server ,这里严格来讲 kube-scheduer 是作为一个server，而调度框架等部分是另外的。\ngo 1 2 3 4 5 func main() { command := app.NewSchedulerCommand() code := cli.Run(command) os.Exit(code) } cli.Run 提供了cobra构成的命令行cli，日志将输出为标准输出\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 // 这里是main中执行的Run func Run(cmd *cobra.Command) int { if logsInitialized, err := run(cmd); err != nil { if !logsInitialized { fmt.Fprintf(os.Stderr, \u0026#34;Error: %v\\n\u0026#34;, err) } else { klog.ErrorS(err, \u0026#34;command failed\u0026#34;) } return 1 } return 0 } // 这个run作为 func run(cmd *cobra.Command) (logsInitialized bool, err error) { rand.Seed(time.Now().UnixNano()) defer logs.FlushLogs() cmd.SetGlobalNormalizationFunc(cliflag.WordSepNormalizeFunc) if !cmd.SilenceUsage { cmd.SilenceUsage = true cmd.SetFlagErrorFunc(func(c *cobra.Command, err error) error { // Re-enable usage printing. c.SilenceUsage = false return err }) } // In all cases error printing is done below. cmd.SilenceErrors = true // This is idempotent. logs.AddFlags(cmd.PersistentFlags()) // Inject logs.InitLogs after command line parsing into one of the // PersistentPre* functions. switch { case cmd.PersistentPreRun != nil: pre := cmd.PersistentPreRun cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { logs.InitLogs() logsInitialized = true pre(cmd, args) } case cmd.PersistentPreRunE != nil: pre := cmd.PersistentPreRunE cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { logs.InitLogs() logsInitialized = true return pre(cmd, args) } default: cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { logs.InitLogs() logsInitialized = true } } err = cmd.Execute() return } 可以看到最终是调用 command.Execute() 执行，这个是执行本身构建的命令，而真正被执行的则是上面的 app.NewSchedulerCommand() ,那么来看看这个是什么\napp.NewSchedulerCommand() 构建了一个cobra.Commond对象， runCommand() 被封装在内，这个是作为启动scheduler的函数\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 func NewSchedulerCommand(registryOptions ...Option) *cobra.Command { opts := options.NewOptions() cmd := \u0026amp;cobra.Command{ Use: \u0026#34;kube-scheduler\u0026#34;, Long: `The Kubernetes scheduler is a control plane process which assigns Pods to Nodes. The scheduler determines which Nodes are valid placements for each Pod in the scheduling queue according to constraints and available resources. The scheduler then ranks each valid Node and binds the Pod to a suitable Node. Multiple different schedulers may be used within a cluster; kube-scheduler is the reference implementation. See [scheduling](https://kubernetes.io/docs/concepts/scheduling-eviction/) for more information about scheduling and the kube-scheduler component.`, RunE: func(cmd *cobra.Command, args []string) error { return runCommand(cmd, opts, registryOptions...) }, Args: func(cmd *cobra.Command, args []string) error { for _, arg := range args { if len(arg) \u0026gt; 0 { return fmt.Errorf(\u0026#34;%q does not take any arguments, got %q\u0026#34;, cmd.CommandPath(), args) } } return nil }, } nfs := opts.Flags verflag.AddFlags(nfs.FlagSet(\u0026#34;global\u0026#34;)) globalflag.AddGlobalFlags(nfs.FlagSet(\u0026#34;global\u0026#34;), cmd.Name(), logs.SkipLoggingConfigurationFlags()) fs := cmd.Flags() for _, f := range nfs.FlagSets { fs.AddFlagSet(f) } cols, _, _ := term.TerminalSize(cmd.OutOrStdout()) cliflag.SetUsageAndHelpFunc(cmd, *nfs, cols) if err := cmd.MarkFlagFilename(\u0026#34;config\u0026#34;, \u0026#34;yaml\u0026#34;, \u0026#34;yml\u0026#34;, \u0026#34;json\u0026#34;); err != nil { klog.ErrorS(err, \u0026#34;Failed to mark flag filename\u0026#34;) } return cmd } 下面来看下 runCommand() 在启动 scheduler 时提供了什么功能。\n在新版中已经没有 algorithmprovider 的概念，所以在 runCommand 中做的也就是仅仅启动这个 scheduler ，而 scheduler 作为kubernetes组件，也是会watch等操作，自然少不了informer。其次作为和 controller-manager 相同的工作特性，kube-scheduler 也是 基于Leader选举的。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 func Run(ctx context.Context, cc *schedulerserverconfig.CompletedConfig, sched *scheduler.Scheduler) error { // To help debugging, immediately log version klog.InfoS(\u0026#34;Starting Kubernetes Scheduler\u0026#34;, \u0026#34;version\u0026#34;, version.Get()) klog.InfoS(\u0026#34;Golang settings\u0026#34;, \u0026#34;GOGC\u0026#34;, os.Getenv(\u0026#34;GOGC\u0026#34;), \u0026#34;GOMAXPROCS\u0026#34;, os.Getenv(\u0026#34;GOMAXPROCS\u0026#34;), \u0026#34;GOTRACEBACK\u0026#34;, os.Getenv(\u0026#34;GOTRACEBACK\u0026#34;)) // Configz registration. if cz, err := configz.New(\u0026#34;componentconfig\u0026#34;); err == nil { cz.Set(cc.ComponentConfig) } else { return fmt.Errorf(\u0026#34;unable to register configz: %s\u0026#34;, err) } // Start events processing pipeline. cc.EventBroadcaster.StartRecordingToSink(ctx.Done()) defer cc.EventBroadcaster.Shutdown() // Setup healthz checks. var checks []healthz.HealthChecker if cc.ComponentConfig.LeaderElection.LeaderElect { checks = append(checks, cc.LeaderElection.WatchDog) } waitingForLeader := make(chan struct{}) isLeader := func() bool { select { case _, ok := \u0026lt;-waitingForLeader: // if channel is closed, we are leading return !ok default: // channel is open, we are waiting for a leader return false } } // Start up the healthz server. if cc.SecureServing != nil { handler := buildHandlerChain(newHealthzAndMetricsHandler(\u0026amp;cc.ComponentConfig, cc.InformerFactory, isLeader, checks...), cc.Authentication.Authenticator, cc.Authorization.Authorizer) // TODO: handle stoppedCh and listenerStoppedCh returned by c.SecureServing.Serve if _, _, err := cc.SecureServing.Serve(handler, 0, ctx.Done()); err != nil { // fail early for secure handlers, removing the old error loop from above return fmt.Errorf(\u0026#34;failed to start secure server: %v\u0026#34;, err) } } // Start all informers. cc.InformerFactory.Start(ctx.Done()) // DynInformerFactory can be nil in tests. if cc.DynInformerFactory != nil { cc.DynInformerFactory.Start(ctx.Done()) } // Wait for all caches to sync before scheduling. cc.InformerFactory.WaitForCacheSync(ctx.Done()) // DynInformerFactory can be nil in tests. if cc.DynInformerFactory != nil { cc.DynInformerFactory.WaitForCacheSync(ctx.Done()) } // If leader election is enabled, runCommand via LeaderElector until done and exit. if cc.LeaderElection != nil { cc.LeaderElection.Callbacks = leaderelection.LeaderCallbacks{ OnStartedLeading: func(ctx context.Context) { close(waitingForLeader) sched.Run(ctx) }, OnStoppedLeading: func() { select { case \u0026lt;-ctx.Done(): // We were asked to terminate. Exit 0. klog.InfoS(\u0026#34;Requested to terminate, exiting\u0026#34;) os.Exit(0) default: // We lost the lock. klog.ErrorS(nil, \u0026#34;Leaderelection lost\u0026#34;) klog.FlushAndExit(klog.ExitFlushTimeout, 1) } }, } leaderElector, err := leaderelection.NewLeaderElector(*cc.LeaderElection) if err != nil { return fmt.Errorf(\u0026#34;couldn\u0026#39;t create leader elector: %v\u0026#34;, err) } leaderElector.Run(ctx) return fmt.Errorf(\u0026#34;lost lease\u0026#34;) } // Leader election is disabled, so runCommand inline until done. close(waitingForLeader) sched.Run(ctx) return fmt.Errorf(\u0026#34;finished without leader elect\u0026#34;) } 上面看到了 runCommend 是作为启动 scheduler 的工作，那么通过参数构建一个 scheduler 则是在 Setup 中完成的。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 // Setup creates a completed config and a scheduler based on the command args and options func Setup(ctx context.Context, opts *options.Options, outOfTreeRegistryOptions ...Option) (*schedulerserverconfig.CompletedConfig, *scheduler.Scheduler, error) { if cfg, err := latest.Default(); err != nil { return nil, nil, err } else { opts.ComponentConfig = cfg } // 验证参数 if errs := opts.Validate(); len(errs) \u0026gt; 0 { return nil, nil, utilerrors.NewAggregate(errs) } // 构建一个config对象 c, err := opts.Config() if err != nil { return nil, nil, err } // 返回一个config对象，包含了scheduler所需的配置，如informer，leader selection cc := c.Complete() outOfTreeRegistry := make(runtime.Registry) for _, option := range outOfTreeRegistryOptions { if err := option(outOfTreeRegistry); err != nil { return nil, nil, err } } recorderFactory := getRecorderFactory(\u0026amp;cc) completedProfiles := make([]kubeschedulerconfig.KubeSchedulerProfile, 0) // 创建出来的scheduler sched, err := scheduler.New(cc.Client, cc.InformerFactory, cc.DynInformerFactory, recorderFactory, ctx.Done(), scheduler.WithComponentConfigVersion(cc.ComponentConfig.TypeMeta.APIVersion), scheduler.WithKubeConfig(cc.KubeConfig), scheduler.WithProfiles(cc.ComponentConfig.Profiles...), scheduler.WithPercentageOfNodesToScore(cc.ComponentConfig.PercentageOfNodesToScore), scheduler.WithFrameworkOutOfTreeRegistry(outOfTreeRegistry), scheduler.WithPodMaxBackoffSeconds(cc.ComponentConfig.PodMaxBackoffSeconds), scheduler.WithPodInitialBackoffSeconds(cc.ComponentConfig.PodInitialBackoffSeconds), scheduler.WithPodMaxInUnschedulablePodsDuration(cc.PodMaxInUnschedulablePodsDuration), scheduler.WithExtenders(cc.ComponentConfig.Extenders...), scheduler.WithParallelism(cc.ComponentConfig.Parallelism), scheduler.WithBuildFrameworkCapturer(func(profile kubeschedulerconfig.KubeSchedulerProfile) { // Profiles are processed during Framework instantiation to set default plugins and configurations. Capturing them for logging completedProfiles = append(completedProfiles, profile) }), ) if err != nil { return nil, nil, err } if err := options.LogOrWriteConfig(opts.WriteConfigTo, \u0026amp;cc.ComponentConfig, completedProfiles); err != nil { return nil, nil, err } return \u0026amp;cc, sched, nil } 上面了解到了 scheduler 是如何被构建出来的，下面就看看 构建时参数是如何传递进来的，而对象 option就是对应需要的配置结构，而 ApplyTo 则是将启动时传入的参数转化为构建 scheduler 所需的配置。\n对于Deprecated flags可以参考官方对于kube-scheduler启动参数的说明 [5]\n对于如何编写一个scheduler config请参考 [6] 与 [7]\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 func (o *Options) ApplyTo(c *schedulerappconfig.Config) error { if len(o.ConfigFile) == 0 { // 在没有指定 --config时会找到 Deprecated flags:参数 // 通过kube-scheduler --help可以看到这些被弃用的参数 o.ApplyDeprecated() o.ApplyLeaderElectionTo(o.ComponentConfig) c.ComponentConfig = *o.ComponentConfig } else { // 这里就是指定了--config cfg, err := loadConfigFromFile(o.ConfigFile) if err != nil { return err } // 这里会将leader选举的参数附加到配置中 o.ApplyLeaderElectionTo(cfg) if err := validation.ValidateKubeSchedulerConfiguration(cfg); err != nil { return err } c.ComponentConfig = *cfg } if err := o.SecureServing.ApplyTo(\u0026amp;c.SecureServing, \u0026amp;c.LoopbackClientConfig); err != nil { return err } if o.SecureServing != nil \u0026amp;\u0026amp; (o.SecureServing.BindPort != 0 || o.SecureServing.Listener != nil) { if err := o.Authentication.ApplyTo(\u0026amp;c.Authentication, c.SecureServing, nil); err != nil { return err } if err := o.Authorization.ApplyTo(\u0026amp;c.Authorization); err != nil { return err } } o.Metrics.Apply() // Apply value independently instead of using ApplyDeprecated() because it can\u0026#39;t be configured via ComponentConfig. if o.Deprecated != nil { c.PodMaxInUnschedulablePodsDuration = o.Deprecated.PodMaxInUnschedulablePodsDuration } return nil } Setup 后会new一个 schedueler , New 则是这个动作，在里面可以看出，会初始化一些informer与 Pod的list等操作。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 func New(client clientset.Interface, informerFactory informers.SharedInformerFactory, dynInformerFactory dynamicinformer.DynamicSharedInformerFactory, recorderFactory profile.RecorderFactory, stopCh \u0026lt;-chan struct{}, opts ...Option) (*Scheduler, error) { stopEverything := stopCh if stopEverything == nil { stopEverything = wait.NeverStop } options := defaultSchedulerOptions // 默认调度策略，如percentageOfNodesToScore for _, opt := range opts { opt(\u0026amp;options) // opt 是传入的函数，会返回一个schedulerOptions即相应的一些配置 } if options.applyDefaultProfile { // 这个是个bool类型，默认scheduler会到这里 // Profile包含了调度器的名称与调度器在两个过程中使用的插件 var versionedCfg v1beta3.KubeSchedulerConfiguration scheme.Scheme.Default(\u0026amp;versionedCfg) cfg := schedulerapi.KubeSchedulerConfiguration{} // 初始化一个配置，这个是--config传入的类型。因为默认的调度策略会初始化 // convert 会将in转为out即versionedCfg转换为cfg if err := scheme.Scheme.Convert(\u0026amp;versionedCfg, \u0026amp;cfg, nil); err != nil { return nil, err } options.profiles = cfg.Profiles } registry := frameworkplugins.NewInTreeRegistry() // 调度框架的注册 if err := registry.Merge(options.frameworkOutOfTreeRegistry); err != nil { return nil, err } metrics.Register() // 指标类 extenders, err := buildExtenders(options.extenders, options.profiles) if err != nil { return nil, fmt.Errorf(\u0026#34;couldn\u0026#39;t build extenders: %w\u0026#34;, err) } podLister := informerFactory.Core().V1().Pods().Lister() nodeLister := informerFactory.Core().V1().Nodes().Lister() // The nominator will be passed all the way to framework instantiation. nominator := internalqueue.NewPodNominator(podLister) snapshot := internalcache.NewEmptySnapshot() clusterEventMap := make(map[framework.ClusterEvent]sets.String) profiles, err := profile.NewMap(options.profiles, registry, recorderFactory, stopCh, frameworkruntime.WithComponentConfigVersion(options.componentConfigVersion), frameworkruntime.WithClientSet(client), frameworkruntime.WithKubeConfig(options.kubeConfig), frameworkruntime.WithInformerFactory(informerFactory), frameworkruntime.WithSnapshotSharedLister(snapshot), frameworkruntime.WithPodNominator(nominator), frameworkruntime.WithCaptureProfile(frameworkruntime.CaptureProfile(options.frameworkCapturer)), frameworkruntime.WithClusterEventMap(clusterEventMap), frameworkruntime.WithParallelism(int(options.parallelism)), frameworkruntime.WithExtenders(extenders), ) if err != nil { return nil, fmt.Errorf(\u0026#34;initializing profiles: %v\u0026#34;, err) } if len(profiles) == 0 { return nil, errors.New(\u0026#34;at least one profile is required\u0026#34;) } podQueue := internalqueue.NewSchedulingQueue( profiles[options.profiles[0].SchedulerName].QueueSortFunc(), informerFactory, internalqueue.WithPodInitialBackoffDuration(time.Duration(options.podInitialBackoffSeconds)*time.Second), internalqueue.WithPodMaxBackoffDuration(time.Duration(options.podMaxBackoffSeconds)*time.Second), internalqueue.WithPodNominator(nominator), internalqueue.WithClusterEventMap(clusterEventMap), internalqueue.WithPodMaxInUnschedulablePodsDuration(options.podMaxInUnschedulablePodsDuration), ) schedulerCache := internalcache.New(durationToExpireAssumedPod, stopEverything) // Setup cache debugger. debugger := cachedebugger.New(nodeLister, podLister, schedulerCache, podQueue) debugger.ListenForSignal(stopEverything) sched := newScheduler( schedulerCache, extenders, internalqueue.MakeNextPodFunc(podQueue), MakeDefaultErrorFunc(client, podLister, podQueue, schedulerCache), stopEverything, podQueue, profiles, client, snapshot, options.percentageOfNodesToScore, ) // 这个就是controller中onAdd等那三个必须的事件函数 addAllEventHandlers(sched, informerFactory, dynInformerFactory, unionedGVKs(clusterEventMap)) return sched, nil } 接下来会启动这个 scheduler， 在上面我们看到 NewSchedulerCommand 构建了一个cobra.Commond对象， runCommand() 最终会返回个 Run，而这个Run就是启动这个 sche 的。\n下面这个 run 是 sche 的运行，他运行并watch资源，直到上下文完成。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (sched *Scheduler) Run(ctx context.Context) { sched.SchedulingQueue.Run() // We need to start scheduleOne loop in a dedicated goroutine, // because scheduleOne function hangs on getting the next item // from the SchedulingQueue. // If there are no new pods to schedule, it will be hanging there // and if done in this goroutine it will be blocking closing // SchedulingQueue, in effect causing a deadlock on shutdown. go wait.UntilWithContext(ctx, sched.scheduleOne, 0) \u0026lt;-ctx.Done() sched.SchedulingQueue.Close() } 而调用这个 Run 的部分则是作为server的 kube-scheduler 中的 run\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 // Run executes the scheduler based on the given configuration. It only returns on error or when context is done. func Run(ctx context.Context, cc *schedulerserverconfig.CompletedConfig, sched *scheduler.Scheduler) error { // To help debugging, immediately log version klog.InfoS(\u0026#34;Starting Kubernetes Scheduler\u0026#34;, \u0026#34;version\u0026#34;, version.Get()) klog.InfoS(\u0026#34;Golang settings\u0026#34;, \u0026#34;GOGC\u0026#34;, os.Getenv(\u0026#34;GOGC\u0026#34;), \u0026#34;GOMAXPROCS\u0026#34;, os.Getenv(\u0026#34;GOMAXPROCS\u0026#34;), \u0026#34;GOTRACEBACK\u0026#34;, os.Getenv(\u0026#34;GOTRACEBACK\u0026#34;)) // Configz registration. if cz, err := configz.New(\u0026#34;componentconfig\u0026#34;); err == nil { cz.Set(cc.ComponentConfig) } else { return fmt.Errorf(\u0026#34;unable to register configz: %s\u0026#34;, err) } // Start events processing pipeline. cc.EventBroadcaster.StartRecordingToSink(ctx.Done()) defer cc.EventBroadcaster.Shutdown() // Setup healthz checks. var checks []healthz.HealthChecker if cc.ComponentConfig.LeaderElection.LeaderElect { checks = append(checks, cc.LeaderElection.WatchDog) } waitingForLeader := make(chan struct{}) isLeader := func() bool { select { case _, ok := \u0026lt;-waitingForLeader: // if channel is closed, we are leading return !ok default: // channel is open, we are waiting for a leader return false } } // Start up the healthz server. if cc.SecureServing != nil { handler := buildHandlerChain(newHealthzAndMetricsHandler(\u0026amp;cc.ComponentConfig, cc.InformerFactory, isLeader, checks...), cc.Authentication.Authenticator, cc.Authorization.Authorizer) // TODO: handle stoppedCh and listenerStoppedCh returned by c.SecureServing.Serve if _, _, err := cc.SecureServing.Serve(handler, 0, ctx.Done()); err != nil { // fail early for secure handlers, removing the old error loop from above return fmt.Errorf(\u0026#34;failed to start secure server: %v\u0026#34;, err) } } // Start all informers. cc.InformerFactory.Start(ctx.Done()) // DynInformerFactory can be nil in tests. if cc.DynInformerFactory != nil { cc.DynInformerFactory.Start(ctx.Done()) } // Wait for all caches to sync before scheduling. cc.InformerFactory.WaitForCacheSync(ctx.Done()) // DynInformerFactory can be nil in tests. if cc.DynInformerFactory != nil { cc.DynInformerFactory.WaitForCacheSync(ctx.Done()) } // If leader election is enabled, runCommand via LeaderElector until done and exit. if cc.LeaderElection != nil { cc.LeaderElection.Callbacks = leaderelection.LeaderCallbacks{ OnStartedLeading: func(ctx context.Context) { close(waitingForLeader) sched.Run(ctx) }, OnStoppedLeading: func() { select { case \u0026lt;-ctx.Done(): // We were asked to terminate. Exit 0. klog.InfoS(\u0026#34;Requested to terminate, exiting\u0026#34;) os.Exit(0) default: // We lost the lock. klog.ErrorS(nil, \u0026#34;Leaderelection lost\u0026#34;) klog.FlushAndExit(klog.ExitFlushTimeout, 1) } }, } leaderElector, err := leaderelection.NewLeaderElector(*cc.LeaderElection) if err != nil { return fmt.Errorf(\u0026#34;couldn\u0026#39;t create leader elector: %v\u0026#34;, err) } leaderElector.Run(ctx) return fmt.Errorf(\u0026#34;lost lease\u0026#34;) } // Leader election is disabled, so runCommand inline until done. close(waitingForLeader) sched.Run(ctx) return fmt.Errorf(\u0026#34;finished without leader elect\u0026#34;) } 而上面的 server.Run 会被 runCommand 也就是在 NewSchedulerCommand 时被返回，在 kube-scheduler 的入口文件中被执行。\ngo 1 2 3 4 5 6 cc, sched, err := Setup(ctx, opts, registryOptions...) if err != nil { return err } return Run(ctx, cc, sched) 至此，整个 kube-scheduler 启动流就分析完了，这个的流程可以用下图表示\n图2：scheduler server运行流程 Reference ​[1] kube scheduler\n​[2] Scheduler Algorithm in Kubernetes\n​[3] scheduling framework\n​[4] permit\n​[5] kube-scheduler parmater\n​[6] kube-scheduler config.v1beta3/\n​[7] kube-scheduler config\n","permalink":"https://www.161616.top/ch16-scheduler/","summary":"Overview [1] kubernetes集群中的调度程序 kube-scheduler 会 watch 未分配节点的新创建的Pod，并未该Pod找到可运行的最佳（特定）节点。那么这些动作或者说这些原理是怎么实现的呢，让我们往下剖析下。\n对于新创建的 pod 或其他未调度的 pod来讲，kube-scheduler 选择一个最佳节点供它们运行。但是，Pod 中的每个容器对资源的要求都不同，每个 Pod 也有不同的要求。因此，需要根据具体的调度要求对现有节点进行过滤。\n在Kubernetes集群中，满足 Pod 调度要求的节点称为可行节点 （ feasible nodes FN） 。如果没有合适的节点，则 pod 将保持未调度状态，直到调度程序能够放置它。也就是说，当我们创建Pod时，如果长期处于 Pending 状态，这个时候应该看你的集群调度器是否因为某些问题没有合适的节点了\n调度器为 Pod 找到 FN 后，然后运行一组函数对 FN 进行评分，并在 FN 中找到得分最高的节点来运行 Pod。\n调度策略在决策时需要考虑的因素包括个人和集体资源需求、硬件/软件/策略约束 （constraints）、亲和性 (affinity) 和反亲和性（ anti-affinity ）规范、数据局部性、工作负载间干扰等。\n如何为pod选择节点？ kube-scheduler 为pod选择节点会分位两部：\n过滤 (Filtering) 打分 (Scoring) 过滤也被称为预选 （Predicates），该步骤会找到可调度的节点集，然后通过是否满足特定资源的请求，例如通过 PodFitsResources 过滤器检查候选节点是否有足够的资源来满足 Pod 资源的请求。这个步骤完成后会得到一个包含合适的节点的列表（通常为多个），如果列表为空，则Pod不可调度。\n打分也被称为优选（Priorities），在该步骤中，会对上一个步骤的输出进行打分，Scheduer 通过打分的规则为每个通过 Filtering 步骤的节点计算出一个分数。\n完成上述两个步骤之后，kube-scheduler 会将Pod分配给分数最高的 Node，如果存在多个相同分数的节点，会随机选择一个。\nkubernetes的调度策略 Kubernetes 1.21之前版本可以在代码 kubernetes\\pkg\\scheduler\\algorithmprovider\\registry.go 中看到对应的注册模式，在1.22 scheduler 更换了其路径，对于registry文件更换到了kubernetes\\pkg\\scheduler\\framework\\plugins\\registry.go ；对于kubernetes官方说法为，调度策略是用于“预选” (Predicates )或 过滤（filtering ） 和 用于 优选（Priorities）或 评分 (scoring)的","title":"kubernetes的决策组件 - kube-scheduler原理分析"},{"content":" 本文是关于Kubernetes 4A解析的第3章 深入理解Kubernetes 4A - Authentication源码解析 深入理解Kubernetes 4A - Authorization源码解析 深入理解Kubernetes 4A - Admission Control源码解析 深入理解Kubernetes 4A - Audit源码解析 TLS Everywhere - 解密kubernetes集群的安全认证 所有关于Kubernetes 4A部分代码上传至仓库 github.com/cylonchau/hello-k8s-4A\n如有错别字或理解错误地方请多多担待，代码是以1.24进行整理，实验是以1.19环境进行，差别不大\nBACKGROUND admission controllers的特点：\n可定制性：准入功能可针对不同的场景进行调整。 可预防性：审计则是为了检测问题，而准入控制器可以预防问题发生 可扩展性：在kubernetes自有的验证机制外，增加了另外的防线，弥补了RBAC仅能对资源提供安全保证。 下图，显示了用户操作资源的流程，可以看出 admission controllers 作用是在通过身份验证资源持久化之前起到拦截作用。在准入控制器的加入会使kubernetes增加了更高级的安全功能。\n图：Kubernetes API 请求的请求处理步骤图 Source：https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/ 这里找到一个大佬博客画的图，通过两张图可以很清晰的了解到admission webhook流程，与官方给出的不一样的地方在于，这里清楚地定位了kubernetes admission webhook 处于准入控制中，RBAC之后，push 之前。\n图：Kubernetes API 请求的请求处理步骤图（详细） Source：https://www.armosec.io/blog/kubernetes-admission-controller/ 两种控制器有什么区别？ 根据官方提供的说法是\nMutating controllers may modify related objects to the requests they admit; validating controllers may not\n从结构图中也可以看出，validating 是在持久化之前，而 Mutating 是在结构验证前，根据这些特性我们可以使用 Mutating 修改这个资源对象内容（如增加验证的信息），在 validating 中验证是否合法。\ncomposition of admission controllers kubernetes中的 admission controllers 由两部分组成：\n内置在APIServer中的准入控制器 build-in li.st 特殊的控制器；也是内置在APIServer中，但提供一些自定义的功能 MutatingAdmission ValidatingAdmission Mutating 控制器可以修改他们处理的资源对象，Validating 控制器不会。当在任何一个阶段中的任何控制器拒绝这个了请求，则会立即拒绝整个请求，并将错误返回。\nadmission webhook 由于准入控制器是内置在 kube-apiserver 中的，这种情况下就限制了admission controller的可扩展性。在这种背景下，kubernetes提供了一种可扩展的准入控制器 extensible admission controllers，这种行为叫做动态准入控制 Dynamic Admission Control，而提供这个功能的就是 admission webhook 。\nadmission webhook 通俗来讲就是 HTTP 回调，通过定义一个http server，接收准入请求并处理。用户可以通过kubernetes提供的两种类型的 admission webhook，validating admission webhook 和 mutating admission webhook。来完成自定义的准入策略的处理。\nwebhook 就是\n注：从上面的流程图也可以看出，admission webhook 也是有顺序的。首先调用mutating webhook，然后会调用validating webhook。\n如何使用准入控制器 使用条件：kubernetes v1.16 使用 admissionregistration.k8s.io/v1 ；kubernetes v1.9 使用 admissionregistration.k8s.io/v1beta1。\n如何在集群中开启准入控制器? ：查看kube-apiserver 的启动参数 --enable-admission-plugins ；通过该参数来配置要启动的准入控制器，如 --enable-admission-plugins=NodeRestriction 多个准入控制器以 , 分割，顺序无关紧要。 反之使用 --disable-admission-plugins 参数可以关闭相应的准入控制器（Refer to apiserver opts）。\n通过 kubectl 命令可以看到，当前kubernetes集群所支持准入控制器的版本\nbash 1 2 3 $ kubectl api-versions | grep admissionregistration.k8s.io/v1 admissionregistration.k8s.io/v1 admissionregistration.k8s.io/v1beta1 webhook工作原理 通过上面的学习，已经了解到了两种webhook的工作原理如下所示：\nmutating webhook，会在持久化前拦截在 MutatingWebhookConfiguration 中定义的规则匹配的请求。MutatingAdmissionWebhook 通过向 mutating webhook 服务器发送准入请求来执行验证。\nvalidaing webhook，会在持久化前拦截在 ValidatingWebhookConfiguration 中定义的规则匹配的请求。ValidatingAdmissionWebhook 通过将准入请求发送到 validating webhook server来执行验证。\n那么接下来将从源码中看这个在这个工作流程中，究竟做了些什么？\n资源类型 对于 1.9 版本之后，也就是 v1 版本 ，admission 被定义在 k8s.io\\api\\admissionregistration\\v1\\types.go ，大同小异，因为本地只有1.18集群，所以以这个讲解。\n对于 Validating Webhook 来讲实现主要都在webhook中\ngo 1 2 3 4 5 6 7 8 9 10 type ValidatingWebhookConfiguration struct { // 每个api必须包含下列的metadata，这个是kubernetes规范，可以在注释中的url看到相关文档 metav1.TypeMeta `json:\u0026#34;,inline\u0026#34;` metav1.ObjectMeta `json:\u0026#34;metadata,omitempty\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=metadata\u0026#34;` // Webhooks在这里被表示为[]ValidatingWebhook，表示我们可以注册多个 // +optional // +patchMergeKey=name // +patchStrategy=merge Webhooks []ValidatingWebhook `json:\u0026#34;webhooks,omitempty\u0026#34; patchStrategy:\u0026#34;merge\u0026#34; patchMergeKey:\u0026#34;name\u0026#34; protobuf:\u0026#34;bytes,2,rep,name=Webhooks\u0026#34;` } webhook，则是对这种类型的webhook提供的操作、资源等。对于这部分不做过多的注释了，因为这里本身为kubernetes API资源，官网有很详细的例子与说明。这里更多字段的意思的可以参考官方 doc\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type ValidatingWebhook struct { // admission webhook的名词，Required Name string `json:\u0026#34;name\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=name\u0026#34;` // ClientConfig 定义了与webhook通讯的方式 Required ClientConfig WebhookClientConfig `json:\u0026#34;clientConfig\u0026#34; protobuf:\u0026#34;bytes,2,opt,name=clientConfig\u0026#34;` // rule表示了webhook对于哪些资源及子资源的操作进行关注 Rules []RuleWithOperations `json:\u0026#34;rules,omitempty\u0026#34; protobuf:\u0026#34;bytes,3,rep,name=rules\u0026#34;` // FailurePolicy 对于无法识别的value将如何处理，allowed/Ignore optional FailurePolicy *FailurePolicyType `json:\u0026#34;failurePolicy,omitempty\u0026#34; protobuf:\u0026#34;bytes,4,opt,name=failurePolicy,casttype=FailurePolicyType\u0026#34;` // matchPolicy 定义了如何使用“rules”列表来匹配传入的请求。 MatchPolicy *MatchPolicyType `json:\u0026#34;matchPolicy,omitempty\u0026#34; protobuf:\u0026#34;bytes,9,opt,name=matchPolicy,casttype=MatchPolicyType\u0026#34;` NamespaceSelector *metav1.LabelSelector `json:\u0026#34;namespaceSelector,omitempty\u0026#34; protobuf:\u0026#34;bytes,5,opt,name=namespaceSelector\u0026#34;` SideEffects *SideEffectClass `json:\u0026#34;sideEffects\u0026#34; protobuf:\u0026#34;bytes,6,opt,name=sideEffects,casttype=SideEffectClass\u0026#34;` AdmissionReviewVersions []string `json:\u0026#34;admissionReviewVersions\u0026#34; protobuf:\u0026#34;bytes,8,rep,name=admissionReviewVersions\u0026#34;` } 到这里了解了一个webhook资源的定义，那么这个如何使用呢？通过 Find Usages 找到一个 k8s.io/apiserver/pkg/admission/plugin/webhook/accessors.go 在使用它。这里没有注释，但在结构上可以看出，包含客户端与一系列选择器组成\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type mutatingWebhookAccessor struct { *v1.MutatingWebhook uid string configurationName string initObjectSelector sync.Once objectSelector labels.Selector objectSelectorErr error initNamespaceSelector sync.Once namespaceSelector labels.Selector namespaceSelectorErr error initClient sync.Once client *rest.RESTClient clientErr error } accessor 因为包含了整个webhookconfig定义的一些动作（这里个人这么觉得）。\naccessor.go 下面 有一个 GetRESTClient 方法 ，通过这里可以看出，这里做的就是使用根据 accessor 构造一个客户端。\ngo 1 2 3 4 5 6 func (m *mutatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error) { m.initClient.Do(func() { m.client, m.clientErr = clientManager.HookClient(hookClientConfigForWebhook(m)) }) return m.client, m.clientErr } 到这步骤已经没必要往下看了，因已经知道这里是请求webhook前的步骤了，下面就是何时请求了。\nk8s.io\\apiserver\\pkg\\admission\\plugin\\webhook\\validating\\dispatcher.go 下面有两个方法，Dispatch去请求我们自己定义的webhook\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error { var relevantHooks []*generic.WebhookInvocation // Construct all the versions we need to call our webhooks versionedAttrs := map[schema.GroupVersionKind]*generic.VersionedAttributes{} for _, hook := range hooks { invocation, statusError := d.plugin.ShouldCallHook(hook, attr, o) if statusError != nil { return statusError } if invocation == nil { continue } relevantHooks = append(relevantHooks, invocation) // If we already have this version, continue if _, ok := versionedAttrs[invocation.Kind]; ok { continue } versionedAttr, err := generic.NewVersionedAttributes(attr, invocation.Kind, o) if err != nil { return apierrors.NewInternalError(err) } versionedAttrs[invocation.Kind] = versionedAttr } if len(relevantHooks) == 0 { // no matching hooks return nil } // Check if the request has already timed out before spawning remote calls select { case \u0026lt;-ctx.Done(): // parent context is canceled or timed out, no point in continuing return apierrors.NewTimeoutError(\u0026#34;request did not complete within requested timeout\u0026#34;, 0) default: } wg := sync.WaitGroup{} errCh := make(chan error, len(relevantHooks)) wg.Add(len(relevantHooks)) // 循环所有相关的注册的hook for i := range relevantHooks { go func(invocation *generic.WebhookInvocation) { defer wg.Done() // invacation 中有一个 Accessor,Accessor注册了一个相关的webhookconfig // 也就是我们 kubectl -f 注册进来的那个webhook的相关配置 hook, ok := invocation.Webhook.GetValidatingWebhook() if !ok { utilruntime.HandleError(fmt.Errorf(\u0026#34;validating webhook dispatch requires v1.ValidatingWebhook, but got %T\u0026#34;, hook)) return } versionedAttr := versionedAttrs[invocation.Kind] t := time.Now() // 调用了callHook去请求我们自定义的webhook err := d.callHook(ctx, hook, invocation, versionedAttr) ignoreClientCallFailures := hook.FailurePolicy != nil \u0026amp;\u0026amp; *hook.FailurePolicy == v1.Ignore rejected := false if err != nil { switch err := err.(type) { case *webhookutil.ErrCallingWebhook: if !ignoreClientCallFailures { rejected = true admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, \u0026#34;validating\u0026#34;, string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionCallingWebhookError, 0) } case *webhookutil.ErrWebhookRejection: rejected = true admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, \u0026#34;validating\u0026#34;, string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionNoError, int(err.Status.ErrStatus.Code)) default: rejected = true admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, \u0026#34;validating\u0026#34;, string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionAPIServerInternalError, 0) } } admissionmetrics.Metrics.ObserveWebhook(time.Since(t), rejected, versionedAttr.Attributes, \u0026#34;validating\u0026#34;, hook.Name) if err == nil { return } if callErr, ok := err.(*webhookutil.ErrCallingWebhook); ok { if ignoreClientCallFailures { klog.Warningf(\u0026#34;Failed calling webhook, failing open %v: %v\u0026#34;, hook.Name, callErr) utilruntime.HandleError(callErr) return } klog.Warningf(\u0026#34;Failed calling webhook, failing closed %v: %v\u0026#34;, hook.Name, err) errCh \u0026lt;- apierrors.NewInternalError(err) return } if rejectionErr, ok := err.(*webhookutil.ErrWebhookRejection); ok { err = rejectionErr.Status } klog.Warningf(\u0026#34;rejected by webhook %q: %#v\u0026#34;, hook.Name, err) errCh \u0026lt;- err }(relevantHooks[i]) } wg.Wait() close(errCh) var errs []error for e := range errCh { errs = append(errs, e) } if len(errs) == 0 { return nil } if len(errs) \u0026gt; 1 { for i := 1; i \u0026lt; len(errs); i++ { // TODO: merge status errors; until then, just return the first one. utilruntime.HandleError(errs[i]) } } return errs[0] } callHook 可以理解为真正去请求我们自定义的webhook服务的动作\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 func (d *validatingDispatcher) callHook(ctx context.Context, h *v1.ValidatingWebhook, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes) error { if attr.Attributes.IsDryRun() { if h.SideEffects == nil { return \u0026amp;webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf(\u0026#34;Webhook SideEffects is nil\u0026#34;)} } if !(*h.SideEffects == v1.SideEffectClassNone || *h.SideEffects == v1.SideEffectClassNoneOnDryRun) { return webhookerrors.NewDryRunUnsupportedErr(h.Name) } } uid, request, response, err := webhookrequest.CreateAdmissionObjects(attr, invocation) if err != nil { return \u0026amp;webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err} } // 发生请求，可以看到，这里从上面的讲到的地方获取了一个客户端 client, err := invocation.Webhook.GetRESTClient(d.cm) if err != nil { return \u0026amp;webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err} } trace := utiltrace.New(\u0026#34;Call validating webhook\u0026#34;, utiltrace.Field{\u0026#34;configuration\u0026#34;, invocation.Webhook.GetConfigurationName()}, utiltrace.Field{\u0026#34;webhook\u0026#34;, h.Name}, utiltrace.Field{\u0026#34;resource\u0026#34;, attr.GetResource()}, utiltrace.Field{\u0026#34;subresource\u0026#34;, attr.GetSubresource()}, utiltrace.Field{\u0026#34;operation\u0026#34;, attr.GetOperation()}, utiltrace.Field{\u0026#34;UID\u0026#34;, uid}) defer trace.LogIfLong(500 * time.Millisecond) // 这里设置超时，超时时长就是在yaml资源清单中设置的那个值 if h.TimeoutSeconds != nil { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, time.Duration(*h.TimeoutSeconds)*time.Second) defer cancel() } // 直接用post请求我们自己定义的webhook接口 r := client.Post().Body(request) // if the context has a deadline, set it as a parameter to inform the backend if deadline, hasDeadline := ctx.Deadline(); hasDeadline { // compute the timeout if timeout := time.Until(deadline); timeout \u0026gt; 0 { // if it\u0026#39;s not an even number of seconds, round up to the nearest second if truncated := timeout.Truncate(time.Second); truncated != timeout { timeout = truncated + time.Second } // set the timeout r.Timeout(timeout) } } if err := r.Do(ctx).Into(response); err != nil { return \u0026amp;webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err} } trace.Step(\u0026#34;Request completed\u0026#34;) result, err := webhookrequest.VerifyAdmissionResponse(uid, false, response) if err != nil { return \u0026amp;webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err} } for k, v := range result.AuditAnnotations { key := h.Name + \u0026#34;/\u0026#34; + k if err := attr.Attributes.AddAnnotation(key, v); err != nil { klog.Warningf(\u0026#34;Failed to set admission audit annotation %s to %s for validating webhook %s: %v\u0026#34;, key, v, h.Name, err) } } if result.Allowed { return nil } return \u0026amp;webhookutil.ErrWebhookRejection{Status: webhookerrors.ToStatusErr(h.Name, result.Result)} } 走到这里基本上对 admission webhook 有了大致的了解，可以知道这个操作是由 apiserver 完成的。下面就实际操作下自定义一个webhook。\n这里还有两个概念，就是请求参数 AdmissionRequest 和相应参数 AdmissionResponse，这些可以在 callHook 中看到，这两个参数被定义在 k8s.io\\api\\admission\\v1\\types.go ；这两个参数也就是我们在自定义 webhook 时需要处理接收到的body的结构，以及我们响应内容数据结构。\n如何编写一个自定义的admission webhook 通过上面的学习了解到了，自定义的webhook就是做为kubernetes提供给用户两种admission controller来验证自定义业务的一个中间件 admission webhook。本质上他是一个HTTP Server，用户可以使用任何语言来完成这部分功能。当然，如果涉及到需要对kubernetes集群资源操作的话，还是建议使用kubernetes官方提供了SDK的编程语言来完成自定义的webhook。\n那么完成一个自定义admission webhook需要两个步骤：\n将相关的webhook config注册给kubernetes，也就是让kubernetes知道你的webhook 准备一个http server来处理 apiserver发过来验证的信息 注：这里使用go net/http包，本身不区分方法处理HTTP的何种请求，如果用其他框架实现的，如django，需要指定对应方法需要为POST\n向kubernetes注册webhook对象 kubernetes提供的两种类型可自定义的准入控制器，和其他资源一样，可以利用资源清单，动态配置那些资源要被adminssion webhook处理。 kubernetes将这种形式抽象为两种资源：\nValidatingWebhookConfiguration\nMutatingWebhookConfiguration\nValidatingAdmission yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: \u0026#34;pod-policy.example.com\u0026#34; webhooks: - name: \u0026#34;pod-policy.example.com\u0026#34; rules: - apiGroups: [\u0026#34;\u0026#34;] # 拦截资源的Group \u0026#34;\u0026#34; 表示 core。\u0026#34;*\u0026#34; 表示所有。 apiVersions: [\u0026#34;v1\u0026#34;] # 拦截资源的版本 operations: [\u0026#34;CREATE\u0026#34;] # 什么请求下拦截 resources: [\u0026#34;pods\u0026#34;] # 拦截什么资源 scope: \u0026#34;Namespaced\u0026#34; # 生效的范围，cluster还是namespace \u0026#34;*\u0026#34;表示没有范围限制。 clientConfig: # 我们部署的webhook服务， service: # service是在cluster-in模式下 namespace: \u0026#34;example-namespace\u0026#34; name: \u0026#34;example-service\u0026#34; port: 443 # 服务的端口 path: \u0026#34;/validate\u0026#34; # path是对应用于验证的接口 # caBundle是提供给 admission webhook CA证书 caBundle: \u0026#34;Ci0tLS0tQk...\u0026lt;base64-encoded PEM bundle containing the CA that signed the webhook\u0026#39;s serving certificate\u0026gt;...tLS0K\u0026#34; admissionReviewVersions: [\u0026#34;v1\u0026#34;, \u0026#34;v1beta1\u0026#34;] sideEffects: None timeoutSeconds: 5 # 1-30s直接，表示请求api的超时时间 MutatingAdmission yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: \u0026#34;valipod-policy.example.com\u0026#34; webhooks: - name: \u0026#34;valipod-policy.example.com\u0026#34; rules: - apiGroups: [\u0026#34;apps\u0026#34;] # 拦截资源的Group \u0026#34;\u0026#34; 表示 core。\u0026#34;*\u0026#34; 表示所有。 apiVersions: [\u0026#34;v1\u0026#34;] # 拦截资源的版本 operations: [\u0026#34;CREATE\u0026#34;] # 什么请求下拦截 resources: [\u0026#34;deployments\u0026#34;] # 拦截什么资源 scope: \u0026#34;Namespaced\u0026#34; # 生效的范围，cluster还是namespace \u0026#34;*\u0026#34;表示没有范围限制。 clientConfig: # 我们部署的webhook服务， url: \u0026#34;https://10.0.0.1:81/validate\u0026#34; # 这里是外部模式 # service: # service是在cluster-in模式下 # namespace: \u0026#34;default\u0026#34; # name: \u0026#34;admission-webhook\u0026#34; # port: 81 # 服务的端口 # path: \u0026#34;/mutate\u0026#34; # path是对应用于验证的接口 # caBundle是提供给 admission webhook CA证书 caBundle: \u0026#34;Ci0tLS0tQk...\u0026lt;base64-encoded PEM bundle containing the CA that signed the webhook\u0026#39;s serving certificate\u0026gt;...tLS0K\u0026#34; admissionReviewVersions: [\u0026#34;v1\u0026#34;] sideEffects: None timeoutSeconds: 5 # 1-30s直接，表示请求api的超时时间 注：对于webhook，也可以引入外部的服务，并非必须部署到集群内部\n对于外部服务来讲，需要 clientConfig 中的 service , 更换为 url ; 通过 url 参数可以将一个外部的服务引入\nyaml 1 2 3 4 5 6 7 8 apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration ... webhooks: - name: my-webhook.example.com clientConfig: url: \u0026#34;https://my-webhook.example.com:9443/my-webhook-path\u0026#34; ... 注：这里的url规则必须准守下列形式：\nscheme://host:port/path 使用了url 时，这里不应填写集群内的服务 scheme 必须是 https，不能为http，这就意味着，引入外部时也需要 配置时使用了，?xx=xx 的参数也是不被允许的（官方说法是这样的，通过源码学习了解到因为会发送特定的请求体，所以无需管参数） 更多的配置可以参考kubernetes官方提供的 doc\n准备一个webhook 让我们编写我们的 webhook server。将创建两个钩子，/mutate 与 /validate；\n/mutate 将在创建deployment资源时，基于版本，给资源加上注释 webhook.example.com/allow: true /validate 将对 /mutate 增加的 allow:true 那么则继续，否则拒绝。 这里为了方便，全部写在一起了，实际上不符合软件的设计。在kubernetes代码库中也提供了一个webhook server，可以参考他这个webhook server来学习具体要做什么\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 package main import ( \u0026#34;context\u0026#34; \u0026#34;crypto/tls\u0026#34; \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;os/signal\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;syscall\u0026#34; v1admission \u0026#34;k8s.io/api/admission/v1\u0026#34; \u0026#34;k8s.io/apimachinery/pkg/runtime\u0026#34; \u0026#34;k8s.io/apimachinery/pkg/runtime/serializer\u0026#34; appv1 \u0026#34;k8s.io/api/apps/v1\u0026#34; metav1 \u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34; \u0026#34;k8s.io/klog\u0026#34; ) type patch struct { Op string `json:\u0026#34;op\u0026#34;` Path string `json:\u0026#34;path\u0026#34;` Value map[string]string `json:\u0026#34;value\u0026#34;` } func serve(w http.ResponseWriter, r *http.Request) { var body []byte if data, err := ioutil.ReadAll(r.Body); err == nil { body = data } klog.Infof(fmt.Sprintf(\u0026#34;receive request: %v....\u0026#34;, string(body)[:130])) if len(body) == 0 { klog.Error(fmt.Sprintf(\u0026#34;admission request body is empty\u0026#34;)) http.Error(w, fmt.Errorf(\u0026#34;admission request body is empty\u0026#34;).Error(), http.StatusBadRequest) return } var admission v1admission.AdmissionReview codefc := serializer.NewCodecFactory(runtime.NewScheme()) decoder := codefc.UniversalDeserializer() _, _, err := decoder.Decode(body, nil, \u0026amp;admission) if err != nil { msg := fmt.Sprintf(\u0026#34;Request could not be decoded: %v\u0026#34;, err) klog.Error(msg) http.Error(w, msg, http.StatusBadRequest) return } if admission.Request == nil { klog.Error(fmt.Sprintf(\u0026#34;admission review can\u0026#39;t be used: Request field is nil\u0026#34;)) http.Error(w, fmt.Errorf(\u0026#34;admission review can\u0026#39;t be used: Request field is nil\u0026#34;).Error(), http.StatusBadRequest) return } switch strings.Split(r.RequestURI, \u0026#34;?\u0026#34;)[0] { case \u0026#34;/mutate\u0026#34;: req := admission.Request var admissionResp v1admission.AdmissionReview admissionResp.APIVersion = admission.APIVersion admissionResp.Kind = admission.Kind klog.Infof(\u0026#34;AdmissionReview for Kind=%v, Namespace=%v Name=%v UID=%v Operation=%v\u0026#34;, req.Kind.Kind, req.Namespace, req.Name, req.UID, req.Operation) switch req.Kind.Kind { case \u0026#34;Deployment\u0026#34;: var ( respstr []byte err error deploy appv1.Deployment ) if err = json.Unmarshal(req.Object.Raw, \u0026amp;deploy); err != nil { respStructure := v1admission.AdmissionResponse{Result: \u0026amp;metav1.Status{ Message: fmt.Sprintf(\u0026#34;could not unmarshal resouces review request: %v\u0026#34;, err), Code: http.StatusInternalServerError, }} klog.Error(fmt.Sprintf(\u0026#34;could not unmarshal resouces review request: %v\u0026#34;, err)) if respstr, err = json.Marshal(respStructure); err != nil { klog.Error(fmt.Errorf(\u0026#34;could not unmarshal resouces review response: %v\u0026#34;, err)) http.Error(w, fmt.Errorf(\u0026#34;could not unmarshal resouces review response: %v\u0026#34;, err).Error(), http.StatusInternalServerError) return } http.Error(w, string(respstr), http.StatusBadRequest) return } current_annotations := deploy.GetAnnotations() pl := []patch{} for k, v := range current_annotations { pl = append(pl, patch{ Op: \u0026#34;add\u0026#34;, Path: \u0026#34;/metadata/annotations\u0026#34;, Value: map[string]string{ k: v, }, }) } pl = append(pl, patch{ Op: \u0026#34;add\u0026#34;, Path: \u0026#34;/metadata/annotations\u0026#34;, Value: map[string]string{ deploy.Name + \u0026#34;/Allow\u0026#34;: \u0026#34;true\u0026#34;, }, }) annotationbyte, err := json.Marshal(pl) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } respStructure := \u0026amp;v1admission.AdmissionResponse{ UID: req.UID, Allowed: true, Patch: annotationbyte, PatchType: func() *v1admission.PatchType { t := v1admission.PatchTypeJSONPatch return \u0026amp;t }(), Result: \u0026amp;metav1.Status{ Message: fmt.Sprintf(\u0026#34;could not unmarshal resouces review request: %v\u0026#34;, err), Code: http.StatusOK, }, } admissionResp.Response = respStructure klog.Infof(\u0026#34;sending response: %s....\u0026#34;, admissionResp.Response.String()[:130]) respByte, err := json.Marshal(admissionResp) if err != nil { klog.Errorf(\u0026#34;Can\u0026#39;t encode response messages: %v\u0026#34;, err) http.Error(w, err.Error(), http.StatusInternalServerError) } klog.Infof(\u0026#34;prepare to write response...\u0026#34;) w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) if _, err := w.Write(respByte); err != nil { klog.Errorf(\u0026#34;Can\u0026#39;t write response: %v\u0026#34;, err) http.Error(w, fmt.Sprintf(\u0026#34;could not write response: %v\u0026#34;, err), http.StatusInternalServerError) } default: klog.Error(fmt.Sprintf(\u0026#34;unsupport resouces review request type\u0026#34;)) http.Error(w, \u0026#34;unsupport resouces review request type\u0026#34;, http.StatusBadRequest) } case \u0026#34;/validate\u0026#34;: req := admission.Request var admissionResp v1admission.AdmissionReview admissionResp.APIVersion = admission.APIVersion admissionResp.Kind = admission.Kind klog.Infof(\u0026#34;AdmissionReview for Kind=%v, Namespace=%v Name=%v UID=%v Operation=%v\u0026#34;, req.Kind.Kind, req.Namespace, req.Name, req.UID, req.Operation) var ( deploy appv1.Deployment respstr []byte ) switch req.Kind.Kind { case \u0026#34;Deployment\u0026#34;: if err = json.Unmarshal(req.Object.Raw, \u0026amp;deploy); err != nil { respStructure := v1admission.AdmissionResponse{Result: \u0026amp;metav1.Status{ Message: fmt.Sprintf(\u0026#34;could not unmarshal resouces review request: %v\u0026#34;, err), Code: http.StatusInternalServerError, }} klog.Error(fmt.Sprintf(\u0026#34;could not unmarshal resouces review request: %v\u0026#34;, err)) if respstr, err = json.Marshal(respStructure); err != nil { klog.Error(fmt.Errorf(\u0026#34;could not unmarshal resouces review response: %v\u0026#34;, err)) http.Error(w, fmt.Errorf(\u0026#34;could not unmarshal resouces review response: %v\u0026#34;, err).Error(), http.StatusInternalServerError) return } http.Error(w, string(respstr), http.StatusBadRequest) return } } al := deploy.GetAnnotations() respStructure := v1admission.AdmissionResponse{ UID: req.UID, } if al[fmt.Sprintf(\u0026#34;%s/Allow\u0026#34;, deploy.Name)] == \u0026#34;true\u0026#34; { respStructure.Allowed = true respStructure.Result = \u0026amp;metav1.Status{ Code: http.StatusOK, } } else { respStructure.Allowed = false respStructure.Result = \u0026amp;metav1.Status{ Code: http.StatusForbidden, Reason: func() metav1.StatusReason { return metav1.StatusReasonForbidden }(), Message: fmt.Sprintf(\u0026#34;the resource %s couldn\u0026#39;t to allow entry.\u0026#34;, deploy.Kind), } } admissionResp.Response = \u0026amp;respStructure klog.Infof(\u0026#34;sending response: %s....\u0026#34;, admissionResp.Response.String()[:130]) respByte, err := json.Marshal(admissionResp) if err != nil { klog.Errorf(\u0026#34;Can\u0026#39;t encode response messages: %v\u0026#34;, err) http.Error(w, err.Error(), http.StatusInternalServerError) } klog.Infof(\u0026#34;prepare to write response...\u0026#34;) w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) if _, err := w.Write(respByte); err != nil { klog.Errorf(\u0026#34;Can\u0026#39;t write response: %v\u0026#34;, err) http.Error(w, fmt.Sprintf(\u0026#34;could not write response: %v\u0026#34;, err), http.StatusInternalServerError) } } } func main() { var ( cert, key string ) if cert = os.Getenv(\u0026#34;TLS_CERT\u0026#34;); len(cert) == 0 { cert = \u0026#34;./tls/tls.crt\u0026#34; } if key = os.Getenv(\u0026#34;TLS_KEY\u0026#34;); len(key) == 0 { key = \u0026#34;./tls/tls.key\u0026#34; } ca, err := tls.LoadX509KeyPair(cert, key) if err != nil { klog.Error(err.Error()) return } server := \u0026amp;http.Server{ Addr: \u0026#34;:81\u0026#34;, TLSConfig: \u0026amp;tls.Config{ Certificates: []tls.Certificate{ ca, }, }, } httpserver := http.NewServeMux() httpserver.HandleFunc(\u0026#34;/validate\u0026#34;, serve) httpserver.HandleFunc(\u0026#34;/mutate\u0026#34;, serve) httpserver.HandleFunc(\u0026#34;/ping\u0026#34;, func(w http.ResponseWriter, r *http.Request) { klog.Info(fmt.Sprintf(\u0026#34;%s %s\u0026#34;, r.RequestURI, \u0026#34;pong\u0026#34;)) fmt.Fprint(w, \u0026#34;pong\u0026#34;) }) server.Handler = httpserver go func() { if err := server.ListenAndServeTLS(\u0026#34;\u0026#34;, \u0026#34;\u0026#34;); err != nil { klog.Errorf(\u0026#34;Failed to listen and serve webhook server: %v\u0026#34;, err) } }() klog.Info(\u0026#34;starting serve.\u0026#34;) signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) \u0026lt;-signalChan klog.Infof(\u0026#34;Got shut signal, shutting...\u0026#34;) if err := server.Shutdown(context.Background()); err != nil { klog.Errorf(\u0026#34;HTTP server Shutdown: %v\u0026#34;, err) } } 对应的Dockerfile\ndocker 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 FROM golang:alpine AS builder MAINTAINER cylon WORKDIR /admission COPY ./ /admission ENV GOPROXY https://goproxy.cn,direct RUN \\ sed -i \u0026#39;s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g\u0026#39; /etc/apk/repositories \u0026amp;\u0026amp; \\ apk add upx \u0026amp;\u0026amp; \\ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags \u0026#34;-s -w\u0026#34; -o webhook main.go \u0026amp;\u0026amp; \\ upx -1 webhook \u0026amp;\u0026amp; \\ chmod +x webhook FROM alpine AS runner WORKDIR /go/admission COPY --from=builder /admission/webhook . VOLUME [\u0026#34;/admission\u0026#34;] 集群内部部署所需的资源清单\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 apiVersion: v1 kind: Service metadata: name: admission-webhook labels: app: admission-webhook spec: ports: - port: 81 targetPort: 81 selector: app: simple-webhook --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: simple-webhook name: simple-webhook spec: replicas: 1 selector: matchLabels: app: simple-webhook template: metadata: labels: app: simple-webhook spec: containers: - image: cylonchau/simple-webhook:v0.0.2 imagePullPolicy: IfNotPresent name: webhook command: [\u0026#34;./webhook\u0026#34;] env: - name: \u0026#34;TLS_CERT\u0026#34; value: \u0026#34;./tls/tls.crt\u0026#34; - name: \u0026#34;TLS_KEY\u0026#34; value: \u0026#34;./tls/tls.key\u0026#34; - name: NS_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.namespace ports: - containerPort: 81 volumeMounts: - name: tlsdir mountPath: /go/admission/tls readOnly: true volumes: - name: tlsdir secret: secretName: webhook --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: name: \u0026#34;pod-policy.example.com\u0026#34; webhooks: - name: \u0026#34;pod-policy.example.com\u0026#34; rules: - apiGroups: [\u0026#34;apps\u0026#34;] # 拦截资源的Group \u0026#34;\u0026#34; 表示 core。\u0026#34;*\u0026#34; 表示所有。 apiVersions: [\u0026#34;v1\u0026#34;] # 拦截资源的版本 operations: [\u0026#34;CREATE\u0026#34;] # 什么请求下拦截 resources: [\u0026#34;deployments\u0026#34;] # 拦截什么资源 scope: \u0026#34;Namespaced\u0026#34; # 生效的范围，cluster还是namespace \u0026#34;*\u0026#34;表示没有范围限制。 clientConfig: # 我们部署的webhook服务， url: \u0026#34;https://10.0.0.1:81/mutate\u0026#34; # service: # service是在cluster-in模式下 # namespace: \u0026#34;default\u0026#34; # name: \u0026#34;admission-webhook\u0026#34; # port: 81 # 服务的端口 # path: \u0026#34;/mutate\u0026#34; # path是对应用于验证的接口 # caBundle是提供给 admission webhook CA证书 caBundle: Put you CA (base64 encode) in here admissionReviewVersions: [\u0026#34;v1\u0026#34;] sideEffects: None timeoutSeconds: 5 # 1-30s直接，表示请求api的超时时间 --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: \u0026#34;valipod-policy.example.com\u0026#34; webhooks: - name: \u0026#34;valipod-policy.example.com\u0026#34; rules: - apiGroups: [\u0026#34;apps\u0026#34;] # 拦截资源的Group \u0026#34;\u0026#34; 表示 core。\u0026#34;*\u0026#34; 表示所有。 apiVersions: [\u0026#34;v1\u0026#34;] # 拦截资源的版本 operations: [\u0026#34;CREATE\u0026#34;] # 什么请求下拦截 resources: [\u0026#34;deployments\u0026#34;] # 拦截什么资源 scope: \u0026#34;Namespaced\u0026#34; # 生效的范围，cluster还是namespace \u0026#34;*\u0026#34;表示没有范围限制。 clientConfig: # 我们部署的webhook服务， # service: # service是在cluster-in模式下 # namespace: \u0026#34;default\u0026#34; # name: \u0026#34;admission-webhook\u0026#34; # port: 81 # 服务的端口 # path: \u0026#34;/mutate\u0026#34; # path是对应用于验证的接口 # caBundle是提供给 admission webhook CA证书 caBundle: Put you CA (base64 encode) in here admissionReviewVersions: [\u0026#34;v1\u0026#34;] sideEffects: None timeoutSeconds: 5 # 1-30s直接，表示请求api的超时时间 这里需要主义的问题 证书问题\n如果需要 cluster-in ，那么则需要对对应webhookconfig资源配置 service ；如果使用的是外部部署，则需要配置对应访问地址，如：\u0026ldquo;https://xxxx:port/method\u0026rdquo;\n这两种方式的证书均需要对应的 subjectAltName ，cluster-in 模式 需要对应service名称，如，至少包含serviceName.NS.svc 这一个域名。\n下面就是证书类问题的错误\ntext 1 Failed calling webhook, failing closed pod-policy.example.com: failed calling webhook \u0026#34;pod-policy.example.com\u0026#34;: Post https://admission-webhook.default.svc:81/mutate?timeout=5s: x509: certificate signed by unknown authority (possibly because of \u0026#34;crypto/rsa: verification error\u0026#34; while trying to verify candidate authority certificate \u0026#34;admission-webhook-ca\u0026#34;) 相应信息问题\n上面我们了解到的APIServer是去发出 v1admission.AdmissionReview 也就是 Request 和 Response类型的，所以，为了更清晰的表示出问题所在，需要对响应格式中的 Reason 与 Message 配置，这也就是我们在客户端看到的报错信息。\ngo 1 2 3 4 5 6 7 \u0026amp;metav1.Status{ Code: http.StatusForbidden, Reason: func() metav1.StatusReason { return metav1.StatusReasonForbidden }(), Message: fmt.Sprintf(\u0026#34;the resource %s couldn\u0026#39;t to allow entry.\u0026#34;, deploy.Kind), } 通过上面的设置用户可以看到下列错误\nbash 1 2 $ kubectl apply -f nginx.yaml Error from server (Forbidden): error when creating \u0026#34;nginx.yaml\u0026#34;: admission webhook \u0026#34;valipod-policy.example.com\u0026#34; denied the request: the resource Deployment couldn\u0026#39;t to allow entry. 注：必须的参数还包含，UID，allowed，这两个是必须的，上面阐述的只是对用户友好的提示信息\n下面的报错就是对相应格式设置错误\ngo 1 Error from server (InternalError): error when creating \u0026#34;nginx.yaml\u0026#34;: Internal error occurred: failed calling webhook \u0026#34;pod-policy.example.com\u0026#34;: the server rejected our request for an unknown reason 相应信息版本问题\n相应信息也需要指定一个版本，这个与请求来的结构中拿即可\ngo 1 2 admissionResp.APIVersion = admission.APIVersion admissionResp.Kind = admission.Kind 下面是没有为对应相应信息配置对应KV的值出现的报错\ntext 1 Error from server (InternalError): error when creating \u0026#34;nginx.yaml\u0026#34;: Internal error occurred: failed calling webhook \u0026#34;pod-policy.example.com\u0026#34;: expected webhook response of admission.k8s.io/v1, Kind=AdmissionReview, got /, Kind= 关于patch\nkubernetes中patch使用的是特定的规范，如 jsonpatch\nkubernetes当前唯一支持的 patchType 是 JSONPatch。 有关更多详细信息，请参见 JSON patch\n对于 jsonpatch 是一个固定的类型，在go中必须定义其结构体\njson 1 2 3 4 5 { \u0026#34;op\u0026#34;: \u0026#34;add\u0026#34;, // 做什么操作 \u0026#34;path\u0026#34;: \u0026#34;/spec/replicas\u0026#34;, // 操作的路径 \u0026#34;value\u0026#34;: 3 // 对应添加的key value } 下面就是字符串类型设置为布尔型产生的报错\nbash 1 Error from server (InternalError): error when creating \u0026#34;nginx.yaml\u0026#34;: Internal error occurred: v1.Deployment.ObjectMeta: v1.ObjectMeta.Annotations: ReadString: expects \u0026#34; or n, but found t, error found in #10 byte of ...|t/Allow\u0026#34;:true},\u0026#34;crea|..., bigger context ...|tadata\u0026#34;:{\u0026#34;annotations\u0026#34;:{\u0026#34;nginx-deployment/Allow\u0026#34;:true},\u0026#34;creationTimestamp\u0026#34;:null,\u0026#34;managedFields\u0026#34;:[{\u0026#34;m|.. 准备证书 Ubuntu\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 touch ./demoCAindex.txt touch ./demoCA/serial touch ./demoCA/crlnumber echo 01 \u0026gt; ./demoCA/serial mkdir ./demoCA/newcerts openssl genrsa -out cakey.pem 2048 openssl req -new \\ -x509 \\ -key cakey.pem \\ -out cacert.pem \\ -days 3650 \\ -subj \u0026#34;/CN=admission webhook ca\u0026#34; openssl genrsa -out tls.key 2048 openssl req -new \\ -key tls.key \\ -subj \u0026#34;/CN=admission webhook client\u0026#34; \\ -reqexts webhook \\ -config \u0026lt;(cat /etc/ssl/openssl.cnf \\ \u0026lt;(printf \u0026#34;[webhook]\\nsubjectAltName=DNS: admission-webhook, DNS: admission-webhook.default.svc, DNS: admission-webhook.default.svc.cluster.local, IP:10.0.0.1, IP:10.0.0.4\u0026#34;)) \\ -out tls.csr sed -i \u0026#39;s/= match/= optional/g\u0026#39; /etc/ssl/openssl.cnf openssl ca \\ -in tls.csr \\ -cert cacert.pem \\ -keyfile cakey.pem \\ -out tls.crt \\ -days 300 \\ -extensions webhook \\ -extfile \u0026lt;(cat /etc/ssl/openssl.cnf \\ \u0026lt;(printf \u0026#34;[webhook]\\nsubjectAltName=DNS: admission-webhook, DNS: admission-webhook.default.svc, DNS: admission-webhook.default.svc.cluster.local, IP:10.0.0.1, IP:10.0.0.4\u0026#34;)) CentOS\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 touch /etc/pki/CA/index.txt touch /etc/pki/CA/serial # 下一个要颁发的编号 16进制 touch /etc/pki/CA/crlnumber echo 01 \u0026gt; /etc/pki/CA/serial openssl req -new \\ -x509 \\ -key cakey.pem \\ -out cacert.pem \\ -days 3650 \\ -subj \u0026#34;/CN=admission webhook ca\u0026#34; openssl genrsa -out tls.key 2048 openssl req -new \\ -key tls.key \\ -subj \u0026#34;/CN=admission webhook client\u0026#34; \\ -reqexts webhook \\ -config \u0026lt;(cat /etc/pki/tls/openssl.cnf \\ \u0026lt;(printf \u0026#34;[webhook]\\nsubjectAltName=DNS: admission-webhook, DNS: admission-webhook.default.svc, DNS: admission-webhook.default.svc.cluster.local, IP:10.0.0.1, IP:10.0.0.4\u0026#34;)) \\ -out tls.csr sed -i \u0026#39;s/= match/= optional/g\u0026#39; /etc/ssl/openssl.cnf openssl ca \\ -in tls.csr \\ -cert cacert.pem \\ -keyfile cakey.pem \\ -out tls.crt \\ -days 300 \\ -extensions webhook \\ -extfile \u0026lt;(cat /etc/pki/tls/openssl.cnf \\ \u0026lt;(printf \u0026#34;[webhook]\\nsubjectAltName=DNS: admission-webhook, DNS: admission-webhook.default.svc, DNS: admission-webhook.default.svc.cluster.local, IP:10.0.0.1, IP:10.0.0.4\u0026#34;)) 通过部署测试结果 可以看到我们自己注入的 annotation nginx-deployment/Allow: true，在该示例中，仅为演示过程，而不是真的策略，实际环境中可以根据情况进行定制自己的策略。\n结果可以看出，当在 mutating 中不通过，即缺少对应的 annotation 标签 , 则 validating 会不允许准入\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ kubectl describe deploy nginx-deployment Name: nginx-deployment Namespace: default CreationTimestamp: Mon, 11 Jul 2022 20:25:16 +0800 Labels: \u0026lt;none\u0026gt; Annotations: deployment.kubernetes.io/revision: 1 nginx-deployment/Allow: true Selector: app=nginx Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 25% max unavailable, 25% max surge Pod Template: Labels: app=nginx Containers: nginx: Image: nginx:1.14.2 Reference extensible admission controllers\nK8S client-go Patch example\nadmission controllers response\na guide to kubernetes admission controllers\n","permalink":"https://www.161616.top/ch33-admission-webhook/","summary":"本文是关于Kubernetes 4A解析的第3章 深入理解Kubernetes 4A - Authentication源码解析 深入理解Kubernetes 4A - Authorization源码解析 深入理解Kubernetes 4A - Admission Control源码解析 深入理解Kubernetes 4A - Audit源码解析 TLS Everywhere - 解密kubernetes集群的安全认证 所有关于Kubernetes 4A部分代码上传至仓库 github.com/cylonchau/hello-k8s-4A\n如有错别字或理解错误地方请多多担待，代码是以1.24进行整理，实验是以1.19环境进行，差别不大\nBACKGROUND admission controllers的特点：\n可定制性：准入功能可针对不同的场景进行调整。 可预防性：审计则是为了检测问题，而准入控制器可以预防问题发生 可扩展性：在kubernetes自有的验证机制外，增加了另外的防线，弥补了RBAC仅能对资源提供安全保证。 下图，显示了用户操作资源的流程，可以看出 admission controllers 作用是在通过身份验证资源持久化之前起到拦截作用。在准入控制器的加入会使kubernetes增加了更高级的安全功能。\n图：Kubernetes API 请求的请求处理步骤图 Source：https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/ 这里找到一个大佬博客画的图，通过两张图可以很清晰的了解到admission webhook流程，与官方给出的不一样的地方在于，这里清楚地定位了kubernetes admission webhook 处于准入控制中，RBAC之后，push 之前。\n图：Kubernetes API 请求的请求处理步骤图（详细） Source：https://www.armosec.io/blog/kubernetes-admission-controller/ 两种控制器有什么区别？ 根据官方提供的说法是\nMutating controllers may modify related objects to the requests they admit; validating controllers may not\n从结构图中也可以看出，validating 是在持久化之前，而 Mutating 是在结构验证前，根据这些特性我们可以使用 Mutating 修改这个资源对象内容（如增加验证的信息），在 validating 中验证是否合法。","title":"深入理解Kubernetes 4A - Admission Control源码解析"},{"content":"各个云厂商都会为自己的服务提供通用可缩放矢量图形 (SVG) 图标，以便用户为自己的软件绘制架构图，例如Microsoft 为Visio 提供 Azure 服务的图标。文本在这里简单整理了几个关于云服务的图标\nAzure-Design 提供了大量并完整的azure的一些图标 AWS-Architecture-Icons 提供了一些关于AWS的图标，不过图标为2019年时的 Microsoft-Integration-and-Azure 整合了一些关于微软的图标，并附带了矢量图 更多的图标可以在github或google搜索相关关键词 visio stencil 网络上还是有很多相关的图标库\n将图标导入到visio中 为了能使下载的图标在Visio 中可用，只需要简单的一个步骤即可。\n将下载下来的图标放置到 C:\\Users\\\u0026lt;UserName\u0026gt;\\Documents\\My Shapes 中文系统为 用户目录\\文档\\我的图形 我的图形需要安装visio后才会有这个文件夹\n如图所示：\n测试导入后的效果，我们在这里导入了aws与azure的图标库，故可以看到有两个，但是两个中又包含很多，已经足够使用了\n最后再附上一个大神制作的 VISIO Protable 版本，匿名网盘，失效不补\n","permalink":"https://www.161616.top/visio-custom-icon/","summary":"各个云厂商都会为自己的服务提供通用可缩放矢量图形 (SVG) 图标，以便用户为自己的软件绘制架构图，例如Microsoft 为Visio 提供 Azure 服务的图标。文本在这里简单整理了几个关于云服务的图标\nAzure-Design 提供了大量并完整的azure的一些图标 AWS-Architecture-Icons 提供了一些关于AWS的图标，不过图标为2019年时的 Microsoft-Integration-and-Azure 整合了一些关于微软的图标，并附带了矢量图 更多的图标可以在github或google搜索相关关键词 visio stencil 网络上还是有很多相关的图标库\n将图标导入到visio中 为了能使下载的图标在Visio 中可用，只需要简单的一个步骤即可。\n将下载下来的图标放置到 C:\\Users\\\u0026lt;UserName\u0026gt;\\Documents\\My Shapes 中文系统为 用户目录\\文档\\我的图形 我的图形需要安装visio后才会有这个文件夹\n如图所示：\n测试导入后的效果，我们在这里导入了aws与azure的图标库，故可以看到有两个，但是两个中又包含很多，已经足够使用了\n最后再附上一个大神制作的 VISIO Protable 版本，匿名网盘，失效不补","title":"如何为visio扩展云服务图标"},{"content":"Background NGINX 是一个通用且流行的应用程序。也是最流行的 Web 服务器，它可用于提供静态文件内容，但也通常与其他服务一起用作分布式系统中的组件，在其中它用作反向代理、负载均衡 或 API 网关。\n分布式追踪 distributed tracing 是一种可用于分析与监控应用程序的机制，将追踪在从源到目的的整个过程中的单个请求，这与仅通过单个应用程序域来追踪请求的形式不同。\n换句话说，我们可以说分布式追踪是对跨多个系统的多个请求的拼接。拼接通常由一个或多个相关 ID 完成，并且跟踪通常是一组记录的、跨所有系统的结构化日志事件，存储在一个中心位置。\n在这种背景的情况下， OpenTracing 应运而生。OpenTracing 是一个与应用供应商无关的 API，它可帮助开发人员轻松地跟踪单一请求的域。目前有多种开源产品都支持 OpenTracing（例如，Jaeger, skywalking 等），并将其作为一种检测分布式追踪的标准化方法。\n本文将围绕，从0到1实现在nginx配置分布式追踪的架构的简单实例说明。本文实例使用的组件为\nnginx v1.22 jaeger-all-in-on v1.38 nginx-opentracing v1.22 jaeger-client-cpp v0.9 源码构建nginx-opentracing 准备nginx-opentracing nginx-opentracing 仓库中可以看到，官方为每个nginx版本都提供了一个编译好的动态库（Nginx1.19.13+），我们可以直接拿来使用这个动态库，如果你想将这个利用Nginx 提供的编译参数 --add-module=/path/to/module 构建为nginx的内置功能的话，可能会出现一些问题，例如下面的一些错误：\ntext 1 ngx_http_opentracing_module.so/config was found bash 1 2 3 /root/nginx-opentracing-0.25.0/opentracing//src/ngx_http_opentracing_module.cpp In file included from /root/nginx-opentracing-0.25.0/opentracing//src/ngx_http_opentracing_module.cpp:1:0: /root/nginx-opentracing-0.25.0/opentracing//src/load_tracer.h:3:38: fatal error: opentracing/dynamic_load.h: No such file or directory 根据 issue 中查询得知 nginx-opentracing 需要嵌入到nginx中，是需要一些 opentracing-cpp 因为对c++不熟，尝试调试很久还是上面的错误，故直接使用了官方提供的动态库。\n准备jaeger-client-cpp 根据 nginx-opentracing 中提到的，还需要一个 jaeger-client-cpp 的 tracer 才可以正常运行（这也是作为jaeger架构中的角色）\n来到 jaeger-client-cpp 看到Release提供的编译好的动态库已经很久了，而最新版都没有提供相应编译的版本，需要我们自己编译\n说明： 编译依赖CMake 3.3+，gcc 4.9.2+\n我们的编译环境使用CentOS 7 默认gcc与CMake都符合要求需要自行编译两个的版本。\n编译gcc gcc下载地址：https://ftp.gnu.org/gnu/gcc/\nbash 1 2 3 4 5 6 7 8 9 10 11 12 cd gcc-5.4.0 ./contrib/download_prerequisites mkdir gcc-build-5.4.0 cd gcc-build-5.4.0 /usr/local/src/gcc-5.4.0/configure \\ --enable-checking=release \\ --enable-languages=c,c++ \\ --disable-multilib make \u0026amp;\u0026amp; make install 引用处理 refer 1\nbash 1 2 3 4 5 cd /usr/bin/ mv gcc gcc_back mv g++ g++_back ln -s /usr/local/bin/gcc gcc ln -s /usr/local/bin/g++ g++ 编译时遇到几个问题\n/lib64/libstdc++.so.6: version GLIBCXX_3.4.20' not found\ngcc 编译，libgcc动态库有改动，恢复原状即可\ntext 1 2 3 4 5 6 7 configure: error: C++ compiler missing or inoperational make[2]: \\*** [configure-stage1-libcpp] Error 1 make[2]: Leaving directory `/home/clay/programming/C++/gcc-4.8.1\u0026#39; make[1]: \\*** [stage1-bubble] Error 2 make[1]: Leaving directory `/home/clay/programming/C++/gcc-4.8.1\u0026#39; make: \\*** [all] Error 2 编译cmake bash 1 2 3 4 ./configure --prefix=/path/to/app make make install 这里遇到一个小问题 编译过程中遇到 [libstdc++.so.6: version GLIBCXX_3.4.20 not found\n因为这里使用了自己编译的gcc版本，需要指定下动态库的路径 refer 2\nbash 1 LD_LIBRARY_PATH=/usr/local/lib64 ./configure --prefix=/usr/local/cmake 编译jaeger-client-cpp 这里根据官方提供的步骤操作即可\nbash 1 2 3 4 5 6 cd jaeger-client-cpp-0.9.0/ mkdir build cd build # 这里建议使用下科学上网，编译过程中会使用Hunter自动下载所需的依赖项 ALL_PROXY=http://x.0.0.x:10811 /usr/local/cmake/bin/cmake .. make 注：依赖项挺大的，下载时间可能很长，会hang主，只需等待结束即可\n​\t编译完成后 libjaegertracing.so.0.9.0 则是我们需要的\n编译nginx bash 1 2 3 4 5 6 7 8 9 ./configure \\ --user=web_www \\ --group=web_www \\ --with-pcre \\ --with-compat \\ --with-http_ssl_module \\ --with-http_gzip_static_module \\ --prefix=/root/nginx \\ --with-http_stub_status_module --with-compat 必须加上，表面允许使用动态库，否则编译完在启动时会报下面的错误\nbash 1 nginx: [emerg] module \u0026#34;/root/nginx/conf/ngx_http_opentracing_module.so\u0026#34; is not binary compatible in /root/nginx/conf/nginx.conf:1 遇到的问题，cc nou found，这里只需将 gcc 软连接一份为 cc 即可\n配置nginx 准备jaeger-client的配置 jaeger.json，参数的说明可以参考configuration\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { \u0026#34;service_name\u0026#34;: \u0026#34;nginx\u0026#34;, // 服务名 \u0026#34;sampler\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;const\u0026#34;, \u0026#34;param\u0026#34;: 1 }, \u0026#34;reporter\u0026#34;: { \u0026#34;localAgentHostPort\u0026#34;: \u0026#34;jaeger:6831\u0026#34; // jaeger agent的地址 }, \u0026#34;headers\u0026#34;: { // jaeger的默认的jaeger Baggage头设置 \u0026#34;jaegerDebugHeader\u0026#34;: \u0026#34;jaeger-debug-id\u0026#34;, \u0026#34;jaegerBaggageHeader\u0026#34;: \u0026#34;jaeger-baggage\u0026#34;, \u0026#34;traceBaggageHeaderPrefix\u0026#34;: \u0026#34;uberctx-\u0026#34; }, \u0026#34;baggage_restrictions\u0026#34;: { \u0026#34;denyBaggageOnInitializationFailure\u0026#34;: false, \u0026#34;hostPort\u0026#34;: \u0026#34;\u0026#34; } } 在nginx中开启opentracing 对于 nginx-opentracing 更多的参数可以参考 Reference.md\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 # 加载 OpenTracing 动态模块。 load_module conf/ngx_http_opentracing_module.so; worker_processes 1; user root root; events { worker_connections 1024; } http { log_format opentracing \u0026#39;{\u0026#34;timestamp\u0026#34;:\u0026#34;$time_iso8601\u0026#34;,\u0026#39; \u0026#39;\u0026#34;source\u0026#34;:\u0026#34;$server_addr\u0026#34;,\u0026#39; \u0026#39;\u0026#34;hostname\u0026#34;:\u0026#34;$hostname\u0026#34;,\u0026#39; \u0026#39;\u0026#34;ip\u0026#34;:\u0026#34;$http_x_forwarded_for\u0026#34;,\u0026#39; \u0026#39;\u0026#34;traceID\u0026#34;:\u0026#34;$opentracing_context_uber_trace_id\u0026#34;,\u0026#39; \u0026#39;\u0026#34;client\u0026#34;:\u0026#34;$remote_addr\u0026#34;,\u0026#39; \u0026#39;\u0026#34;request_method\u0026#34;:\u0026#34;$request_method\u0026#34;,\u0026#39; \u0026#39;\u0026#34;scheme\u0026#34;:\u0026#34;$scheme\u0026#34;,\u0026#39; \u0026#39;\u0026#34;domain\u0026#34;:\u0026#34;$server_name\u0026#34;,\u0026#39; \u0026#39;\u0026#34;referer\u0026#34;:\u0026#34;$http_referer\u0026#34;,\u0026#39; \u0026#39;\u0026#34;request\u0026#34;:\u0026#34;$request_uri\u0026#34;,\u0026#39; \u0026#39;\u0026#34;args\u0026#34;:\u0026#34;$args\u0026#34;,\u0026#39; \u0026#39;\u0026#34;size\u0026#34;:$body_bytes_sent,\u0026#39; \u0026#39;\u0026#34;status\u0026#34;: $status,\u0026#39; \u0026#39;\u0026#34;responsetime\u0026#34;:$request_time,\u0026#39; \u0026#39;\u0026#34;upstreamtime\u0026#34;:\u0026#34;$upstream_response_time\u0026#34;,\u0026#39; \u0026#39;\u0026#34;upstreamaddr\u0026#34;:\u0026#34;$upstream_addr\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;https\u0026#34;:\u0026#34;$https\u0026#34;\u0026#39; \u0026#39;}\u0026#39;; # 加载 tracer，这里使用的jaeger，需要传递配置文件 opentracing_load_tracer conf/libjaegertracing.so conf/jaeger.json; # 启用 tracing opentracing on; # 设置tag，可选参数 opentracing_tag http_user_agent $http_user_agent; include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; location / { opentracing_operation_name $uri; opentracing_propagate_context; root html; index index.html index.htm; } access_log logs/access.log opentracing; error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } } 注：这里使用的 opentracing-nginx 的动态库为 ot16 ，linux-amd64-nginx-1.22.0-ot16-ngx_http_module.so.tgz ，另外一个版本不兼容，-t 检查语法时会提示\n配置说明\n对于每一个location都可以对其设置别名，这个就是 opentracing_operation_name 与 opentracing_location_operation_name 的区别\ntext 1 2 3 4 5 http { ... location = /upload/animal { opentracing_location_operation_name upload; ... 更多的配置说明可以参考 Tutorial.md\n此时我们可以在jaeger上查看，可以看到 NGINX 的 span（因为这里只配置了NGINX，没有配置更多的后端）。\nReference 1 CentOS7 升级 GCC 到 5.4.0 版本\n2 libstdc++.so.6: version GLIBCXX_3.4.20 not found\n3 nginx load_module\n","permalink":"https://www.161616.top/opentracing-nginx/","summary":"Background NGINX 是一个通用且流行的应用程序。也是最流行的 Web 服务器，它可用于提供静态文件内容，但也通常与其他服务一起用作分布式系统中的组件，在其中它用作反向代理、负载均衡 或 API 网关。\n分布式追踪 distributed tracing 是一种可用于分析与监控应用程序的机制，将追踪在从源到目的的整个过程中的单个请求，这与仅通过单个应用程序域来追踪请求的形式不同。\n换句话说，我们可以说分布式追踪是对跨多个系统的多个请求的拼接。拼接通常由一个或多个相关 ID 完成，并且跟踪通常是一组记录的、跨所有系统的结构化日志事件，存储在一个中心位置。\n在这种背景的情况下， OpenTracing 应运而生。OpenTracing 是一个与应用供应商无关的 API，它可帮助开发人员轻松地跟踪单一请求的域。目前有多种开源产品都支持 OpenTracing（例如，Jaeger, skywalking 等），并将其作为一种检测分布式追踪的标准化方法。\n本文将围绕，从0到1实现在nginx配置分布式追踪的架构的简单实例说明。本文实例使用的组件为\nnginx v1.22 jaeger-all-in-on v1.38 nginx-opentracing v1.22 jaeger-client-cpp v0.9 源码构建nginx-opentracing 准备nginx-opentracing nginx-opentracing 仓库中可以看到，官方为每个nginx版本都提供了一个编译好的动态库（Nginx1.19.13+），我们可以直接拿来使用这个动态库，如果你想将这个利用Nginx 提供的编译参数 --add-module=/path/to/module 构建为nginx的内置功能的话，可能会出现一些问题，例如下面的一些错误：\ntext 1 ngx_http_opentracing_module.so/config was found bash 1 2 3 /root/nginx-opentracing-0.25.0/opentracing//src/ngx_http_opentracing_module.cpp In file included from /root/nginx-opentracing-0.25.0/opentracing//src/ngx_http_opentracing_module.cpp:1:0: /root/nginx-opentracing-0.25.0/opentracing//src/load_tracer.h:3:38: fatal error: opentracing/dynamic_load.h: No such file or directory 根据 issue 中查询得知 nginx-opentracing 需要嵌入到nginx中，是需要一些 opentracing-cpp 因为对c++不熟，尝试调试很久还是上面的错误，故直接使用了官方提供的动态库。","title":"使nginx支持分布式追踪"},{"content":"Backgroud 前一章中，对kubernetes的选举原理进行了深度剖析，下面就通过一个example来实现一个，利用kubernetes提供的选举机制完成的高可用应用。\n对于此章需要提前对一些概念有所了解后才可以继续看下去\nleader election mechanism RBCA Pod runtime mechanism Implementation 代码实现 如果仅仅是使用Kubernetes中的锁，实现的代码也只有几行而已。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 package main import ( \u0026#34;context\u0026#34; \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;os/signal\u0026#34; \u0026#34;syscall\u0026#34; \u0026#34;time\u0026#34; metav1 \u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34; clientset \u0026#34;k8s.io/client-go/kubernetes\u0026#34; \u0026#34;k8s.io/client-go/rest\u0026#34; \u0026#34;k8s.io/client-go/tools/clientcmd\u0026#34; \u0026#34;k8s.io/client-go/tools/leaderelection\u0026#34; \u0026#34;k8s.io/client-go/tools/leaderelection/resourcelock\u0026#34; \u0026#34;k8s.io/klog/v2\u0026#34; ) func buildConfig(kubeconfig string) (*rest.Config, error) { if kubeconfig != \u0026#34;\u0026#34; { cfg, err := clientcmd.BuildConfigFromFlags(\u0026#34;\u0026#34;, kubeconfig) if err != nil { return nil, err } return cfg, nil } cfg, err := rest.InClusterConfig() if err != nil { return nil, err } return cfg, nil } func main() { klog.InitFlags(nil) var kubeconfig string var leaseLockName string var leaseLockNamespace string var id string // 初始化客户端的部分 flag.StringVar(\u0026amp;kubeconfig, \u0026#34;kubeconfig\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;absolute path to the kubeconfig file\u0026#34;) flag.StringVar(\u0026amp;id, \u0026#34;id\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;the holder identity name\u0026#34;) flag.StringVar(\u0026amp;leaseLockName, \u0026#34;lease-lock-name\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;the lease lock resource name\u0026#34;) flag.StringVar(\u0026amp;leaseLockNamespace, \u0026#34;lease-lock-namespace\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;the lease lock resource namespace\u0026#34;) flag.Parse() if leaseLockName == \u0026#34;\u0026#34; { klog.Fatal(\u0026#34;unable to get lease lock resource name (missing lease-lock-name flag).\u0026#34;) } if leaseLockNamespace == \u0026#34;\u0026#34; { klog.Fatal(\u0026#34;unable to get lease lock resource namespace (missing lease-lock-namespace flag).\u0026#34;) } config, err := buildConfig(kubeconfig) if err != nil { klog.Fatal(err) } client := clientset.NewForConfigOrDie(config) run := func(ctx context.Context) { // 实现的业务逻辑，这里仅仅为实验，就直接打印了 klog.Info(\u0026#34;Controller loop...\u0026#34;) for { fmt.Println(\u0026#34;I am leader, I was working.\u0026#34;) time.Sleep(time.Second * 5) } } // use a Go context so we can tell the leaderelection code when we // want to step down ctx, cancel := context.WithCancel(context.Background()) defer cancel() // 监听系统中断 ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt, syscall.SIGTERM) go func() { \u0026lt;-ch klog.Info(\u0026#34;Received termination, signaling shutdown\u0026#34;) cancel() }() // 创建一个资源锁 lock := \u0026amp;resourcelock.LeaseLock{ LeaseMeta: metav1.ObjectMeta{ Name: leaseLockName, Namespace: leaseLockNamespace, }, Client: client.CoordinationV1(), LockConfig: resourcelock.ResourceLockConfig{ Identity: id, }, } // 开启一个选举的循环 leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{ Lock: lock, ReleaseOnCancel: true, LeaseDuration: 60 * time.Second, RenewDeadline: 15 * time.Second, RetryPeriod: 5 * time.Second, Callbacks: leaderelection.LeaderCallbacks{ OnStartedLeading: func(ctx context.Context) { // 当选举为leader后所运行的业务逻辑 run(ctx) }, OnStoppedLeading: func() { // we can do cleanup here klog.Infof(\u0026#34;leader lost: %s\u0026#34;, id) os.Exit(0) }, OnNewLeader: func(identity string) { // 申请一个选举时的动作 if identity == id { return } klog.Infof(\u0026#34;new leader elected: %s\u0026#34;, identity) }, }, }) } 注：这种lease锁只能在in-cluster模式下运行，如果需要类似二进制部署的程序，可以选择endpoint类型的资源锁。\n生成镜像 这里已经制作好了镜像并上传到dockerhub（cylonchau/leaderelection:v0.0.2）上了，如果只要学习运行原理，则忽略此步骤\ndocker 1 2 3 4 5 6 7 8 9 10 11 12 FROM golang:alpine AS builder MAINTAINER cylon WORKDIR /election COPY . /election ENV GOPROXY https://goproxy.cn,direct RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o elector main.go FROM alpine AS runner WORKDIR /go/elector COPY --from=builder /election/elector . VOLUME [\u0026#34;/election\u0026#34;] ENTRYPOINT [\u0026#34;./elector\u0026#34;] 准备资源清单 默认情况下，Kubernetes运行的pod在请求Kubernetes集群内资源时，默认的账户是没有权限的，默认服务帐户无权访问协调 API，因此我们需要创建另一个serviceaccount并相应地设置 对应的RBAC权限绑定；在清单中配置上这个sa，此时所有的pod就会有协调锁的权限了\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 apiVersion: v1 kind: ServiceAccount metadata: name: sa-leaderelection --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: leaderelection rules: - apiGroups: - coordination.k8s.io resources: - leases verbs: - \u0026#39;*\u0026#39; --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: leaderelection roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: leaderelection subjects: - kind: ServiceAccount name: sa-leaderelection --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: leaderelection name: leaderelection namespace: default spec: replicas: 3 selector: matchLabels: app: leaderelection template: metadata: labels: app: leaderelection spec: containers: - image: cylonchau/leaderelection:v0.0.2 imagePullPolicy: IfNotPresent command: [\u0026#34;./elector\u0026#34;] args: - \u0026#34;-id=$(POD_NAME)\u0026#34; - \u0026#34;-lease-lock-name=test\u0026#34; - \u0026#34;-lease-lock-namespace=default\u0026#34; env: - name: POD_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.name name: elector serviceAccountName: sa-leaderelection 集群中运行 执行完清单后，当pod启动后，可以看到会创建出一个 lease\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 $ kubectl get lease NAME HOLDER AGE test leaderelection-5644c5f84f-frs5n 1s $ kubectl describe lease Name: test Namespace: default Labels: \u0026lt;none\u0026gt; Annotations: \u0026lt;none\u0026gt; API Version: coordination.k8s.io/v1 Kind: Lease Metadata: Creation Timestamp: 2022-06-28T16:39:45Z Managed Fields: API Version: coordination.k8s.io/v1 Fields Type: FieldsV1 fieldsV1: f:spec: f:acquireTime: f:holderIdentity: f:leaseDurationSeconds: f:leaseTransitions: f:renewTime: Manager: elector Operation: Update Time: 2022-06-28T16:39:45Z Resource Version: 131693 Self Link: /apis/coordination.k8s.io/v1/namespaces/default/leases/test UID: bef2b164-a117-44bd-bad3-3e651c94c97b Spec: Acquire Time: 2022-06-28T16:39:45.931873Z Holder Identity: leaderelection-5644c5f84f-frs5n Lease Duration Seconds: 60 Lease Transitions: 0 Renew Time: 2022-06-28T16:39:55.963537Z Events: \u0026lt;none\u0026gt; 通过其持有者的信息查看对应pod（因为程序中对holder Identity设置的是pod的名称），实际上是工作的pod。\n如上实例所述，这是利用Kubernetes集群完成的leader选举的方案，虽然这不是最完美解决方案，但这是一种简单的方法，因为可以无需在集群上部署更多东西或者进行大量的代码工作就可以利用Kubernetes集群来完成一个高可用的HA应用。\n","permalink":"https://www.161616.top/ch28-leader-election-eg/","summary":"Backgroud 前一章中，对kubernetes的选举原理进行了深度剖析，下面就通过一个example来实现一个，利用kubernetes提供的选举机制完成的高可用应用。\n对于此章需要提前对一些概念有所了解后才可以继续看下去\nleader election mechanism RBCA Pod runtime mechanism Implementation 代码实现 如果仅仅是使用Kubernetes中的锁，实现的代码也只有几行而已。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 package main import ( \u0026#34;context\u0026#34; \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;os/signal\u0026#34; \u0026#34;syscall\u0026#34; \u0026#34;time\u0026#34; metav1 \u0026#34;k8s.","title":"利用kubernetes中的leader选举机制自定义HA应用"},{"content":"Overview 在 Kubernetes的 kube-controller-manager , kube-scheduler, 以及使用 Operator 的底层实现 controller-rumtime 都支持高可用系统中的leader选举，本文将以理解 controller-rumtime （底层的实现是 client-go） 中的leader选举以在kubernetes controller中是如何实现的。\nBackground 在运行 kube-controller-manager 时，是有一些参数提供给cm进行leader选举使用的，可以参考官方文档提供的 参数 来了解相关参数。\nbash 1 2 3 4 5 6 7 --leader-elect Default: true --leader-elect-renew-deadline duration Default: 10s --leader-elect-resource-lock string Default: \u0026#34;leases\u0026#34; --leader-elect-resource-name string Default: \u0026#34;kube-controller-manager\u0026#34; --leader-elect-resource-namespace string Default: \u0026#34;kube-system\u0026#34; --leader-elect-retry-period duration Default: 2s ... 本身以为这些组件的选举动作时通过etcd进行的，但是后面对 controller-runtime 学习时，发现并没有配置其相关的etcd相关参数，这就引起了对选举机制的好奇。怀着这种好奇心搜索了下有关于 kubernetes的选举，发现官网是这么介绍的，下面是对官方的说明进行一个通俗总结。simple leader election with kubernetes\n通过阅读文章得知，kubernetes API 提供了一中选举机制，只要运行在集群内的容器，都是可以实现选举功能的。\nKubernetes API通过提供了两个属性来完成选举动作的\nResourceVersions：每个API对象唯一一个ResourceVersion Annotations：每个API对象都可以对这些key进行注释 注：这种选举会增加APIServer的压力。也就对etcd会产生影响\n那么有了这些信息之后，我们来看一下，在Kubernetes集群中，谁是cm的leader（我们提供的集群只有一个节点，所以本节点就是leader）\n在Kubernetes中所有启用了leader选举的服务都会生成一个 EndPoint ，在这个 EndPoint 中会有上面提到的label（Annotations）来标识谁是leader。\nbash 1 2 3 4 5 $ kubectl get ep -n kube-system NAME ENDPOINTS AGE kube-controller-manager \u0026lt;none\u0026gt; 3d4h kube-dns 3d4h kube-scheduler \u0026lt;none\u0026gt; 3d4h 这里以 kube-controller-manager 为例，来看下这个 EndPoint 有什么信息\nbash 1 2 3 4 5 6 7 8 9 10 11 12 $ kubectl describe ep kube-controller-manager -n kube-system Name: kube-controller-manager Namespace: kube-system Labels: \u0026lt;none\u0026gt; Annotations: control-plane.alpha.kubernetes.io/leader: {\u0026#34;holderIdentity\u0026#34;:\u0026#34;master-machine_06730140-a503-487d-850b-1fe1619f1fe1\u0026#34;,\u0026#34;leaseDurationSeconds\u0026#34;:15,\u0026#34;acquireTime\u0026#34;:\u0026#34;2022-06-27T15:30:46Z\u0026#34;,\u0026#34;re... Subsets: Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal LeaderElection 2d22h kube-controller-manager master-machine_76aabcb5-49ff-45ff-bd18-4afa61fbc5af became leader Normal LeaderElection 9m kube-controller-manager master-machine_06730140-a503-487d-850b-1fe1619f1fe1 became leader 可以看出 Annotations: control-plane.alpha.kubernetes.io/leader: 标出了哪个node是leader。\nelection in controller-runtime controller-runtime 有关leader选举的部分在 pkg/leaderelection 下面，总共100行代码，我们来看下做了些什么？\n可以看到，这里只提供了创建资源锁的一些选项\ngo 1 2 3 4 5 6 7 8 9 10 type Options struct { // 在manager启动时，决定是否进行选举 LeaderElection bool // 使用那种资源锁 默认为租用 lease LeaderElectionResourceLock string // 选举发生的名称空间 LeaderElectionNamespace string // 该属性将决定持有leader锁资源的名称 LeaderElectionID string } 通过 NewResourceLock 可以看到，这里是走的 client-go/tools/leaderelection下面，而这个leaderelection也有一个 example 来学习如何使用它。\n通过 example 可以看到，进入选举的入口是一个 RunOrDie() 的函数\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 // 这里使用了一个lease锁，注释中说愿意为集群中存在lease的监听较少 lock := \u0026amp;resourcelock.LeaseLock{ LeaseMeta: metav1.ObjectMeta{ Name: leaseLockName, Namespace: leaseLockNamespace, }, Client: client.CoordinationV1(), LockConfig: resourcelock.ResourceLockConfig{ Identity: id, }, } // 开启选举循环 leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{ Lock: lock, // 这里必须保证拥有的租约在调用cancel()前终止，否则会仍有一个loop在运行 ReleaseOnCancel: true, LeaseDuration: 60 * time.Second, RenewDeadline: 15 * time.Second, RetryPeriod: 5 * time.Second, Callbacks: leaderelection.LeaderCallbacks{ OnStartedLeading: func(ctx context.Context) { // 这里填写你的代码， // usually put your code run(ctx) }, OnStoppedLeading: func() { // 这里清理你的lease klog.Infof(\u0026#34;leader lost: %s\u0026#34;, id) os.Exit(0) }, OnNewLeader: func(identity string) { // we\u0026#39;re notified when new leader elected if identity == id { // I just got the lock return } klog.Infof(\u0026#34;new leader elected: %s\u0026#34;, identity) }, }, }) 到这里，我们了解了锁的概念和如何启动一个锁，下面看下，client-go都提供了那些锁。\n在代码 tools/leaderelection/resourcelock/interface.go 定义了一个锁抽象，interface提供了一个通用接口，用于锁定leader选举中使用的资源。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Interface interface { // Get 返回选举记录 Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) // Create 创建一个LeaderElectionRecord Create(ctx context.Context, ler LeaderElectionRecord) error // Update will update and existing LeaderElectionRecord Update(ctx context.Context, ler LeaderElectionRecord) error // RecordEvent is used to record events RecordEvent(string) // Identity 返回锁的标识 Identity() string // Describe is used to convert details on current resource lock into a string Describe() string } 那么实现这个抽象接口的就是，实现的资源锁，我们可以看到，client-go提供了四种资源锁\nleaselock configmaplock multilock endpointlock leaselock Lease是kubernetes控制平面中的通过ETCD来实现的一个Leases的资源，主要为了提供分布式租约的一种控制机制。相关对这个API的描述可以参考于：Lease 。\n在Kubernetes集群中，我们可以使用如下命令来查看对应的lease\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 $ kubectl get leases -A NAMESPACE NAME HOLDER AGE kube-node-lease master-machine master-machine 3d19h kube-system kube-controller-manager master-machine_06730140-a503-487d-850b-1fe1619f1fe1 3d19h kube-system kube-scheduler master-machine_1724e2d9-c19c-48d7-ae47-ee4217b27073 3d19h $ kubectl describe leases kube-controller-manager -n kube-system Name: kube-controller-manager Namespace: kube-system Labels: \u0026lt;none\u0026gt; Annotations: \u0026lt;none\u0026gt; API Version: coordination.k8s.io/v1 Kind: Lease Metadata: Creation Timestamp: 2022-06-24T11:01:51Z Managed Fields: API Version: coordination.k8s.io/v1 Fields Type: FieldsV1 fieldsV1: f:spec: f:acquireTime: f:holderIdentity: f:leaseDurationSeconds: f:leaseTransitions: f:renewTime: Manager: kube-controller-manager Operation: Update Time: 2022-06-24T11:01:51Z Resource Version: 56012 Self Link: /apis/coordination.k8s.io/v1/namespaces/kube-system/leases/kube-controller-manager UID: 851a32d2-25dc-49b6-a3f7-7a76f152f071 Spec: Acquire Time: 2022-06-27T15:30:46.000000Z Holder Identity: master-machine_06730140-a503-487d-850b-1fe1619f1fe1 Lease Duration Seconds: 15 Lease Transitions: 2 Renew Time: 2022-06-28T06:09:26.837773Z Events: \u0026lt;none\u0026gt; 下面来看下leaselock的实现，leaselock会实现了作为资源锁的抽象\ngo 1 2 3 4 5 6 7 8 9 type LeaseLock struct { // LeaseMeta 就是类似于其他资源类型的属性，包含name ns 以及其他关于lease的属性 LeaseMeta metav1.ObjectMeta Client coordinationv1client.LeasesGetter // Client 就是提供了informer中的功能 // lockconfig包含上面通过 describe 看到的 Identity与recoder用于记录资源锁的更改 LockConfig ResourceLockConfig // lease 就是 API中的Lease资源，可以参考下上面给出的这个API的使用 lease *coordinationv1.Lease } 下面来看下leaselock实现了那些方法？\nGet Get 是从spec中返回选举的记录\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 func (ll *LeaseLock) Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) { var err error ll.lease, err = ll.Client.Leases(ll.LeaseMeta.Namespace).Get(ctx, ll.LeaseMeta.Name, metav1.GetOptions{}) if err != nil { return nil, nil, err } record := LeaseSpecToLeaderElectionRecord(\u0026amp;ll.lease.Spec) recordByte, err := json.Marshal(*record) if err != nil { return nil, nil, err } return record, recordByte, nil } // 可以看出是返回这个资源spec里面填充的值 func LeaseSpecToLeaderElectionRecord(spec *coordinationv1.LeaseSpec) *LeaderElectionRecord { var r LeaderElectionRecord if spec.HolderIdentity != nil { r.HolderIdentity = *spec.HolderIdentity } if spec.LeaseDurationSeconds != nil { r.LeaseDurationSeconds = int(*spec.LeaseDurationSeconds) } if spec.LeaseTransitions != nil { r.LeaderTransitions = int(*spec.LeaseTransitions) } if spec.AcquireTime != nil { r.AcquireTime = metav1.Time{spec.AcquireTime.Time} } if spec.RenewTime != nil { r.RenewTime = metav1.Time{spec.RenewTime.Time} } return \u0026amp;r } Create Create 是在kubernetes集群中尝试去创建一个租约，可以看到，Client就是API提供的对应资源的REST客户端，结果会在Kubernetes集群中创建这个Lease\ngo 1 2 3 4 5 6 7 8 9 10 11 func (ll *LeaseLock) Create(ctx context.Context, ler LeaderElectionRecord) error { var err error ll.lease, err = ll.Client.Leases(ll.LeaseMeta.Namespace).Create(ctx, \u0026amp;coordinationv1.Lease{ ObjectMeta: metav1.ObjectMeta{ Name: ll.LeaseMeta.Name, Namespace: ll.LeaseMeta.Namespace, }, Spec: LeaderElectionRecordToLeaseSpec(\u0026amp;ler), }, metav1.CreateOptions{}) return err } Update Update 是更新Lease的spec\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (ll *LeaseLock) Update(ctx context.Context, ler LeaderElectionRecord) error { if ll.lease == nil { return errors.New(\u0026#34;lease not initialized, call get or create first\u0026#34;) } ll.lease.Spec = LeaderElectionRecordToLeaseSpec(\u0026amp;ler) lease, err := ll.Client.Leases(ll.LeaseMeta.Namespace).Update(ctx, ll.lease, metav1.UpdateOptions{}) if err != nil { return err } ll.lease = lease return nil } RecordEvent RecordEvent 是记录选举时出现的事件，这时候我们回到上部分 在kubernetes集群中查看 ep 的信息时可以看到的event中存在 became leader 的事件，这里就是将产生的这个event添加到 meta-data 中。\ntext 1 2 3 4 5 6 7 8 9 10 11 func (ll *LeaseLock) RecordEvent(s string) { if ll.LockConfig.EventRecorder == nil { return } events := fmt.Sprintf(\u0026#34;%v %v\u0026#34;, ll.LockConfig.Identity, s) subject := \u0026amp;coordinationv1.Lease{ObjectMeta: ll.lease.ObjectMeta} // Populate the type meta, so we don\u0026#39;t have to get it from the schema subject.Kind = \u0026#34;Lease\u0026#34; subject.APIVersion = coordinationv1.SchemeGroupVersion.String() ll.LockConfig.EventRecorder.Eventf(subject, corev1.EventTypeNormal, \u0026#34;LeaderElection\u0026#34;, events) } 到这里大致上了解了资源锁究竟是什么了，其他种类的资源锁也是相同的实现的方式，这里就不过多阐述了；下面的我们来看看选举的过程。\nelection workflow 选举的代码入口是在 leaderelection.go ，这里会继续上面的 example 向下分析整个选举的过程。\n前面我们看到了进入选举的入口是一个 RunOrDie() 的函数，那么就继续从这里开始来了解。进入 RunOrDie，看到其实只有几行而已，大致上了解到了RunOrDie会使用提供的配置来启动选举的客户端，之后会阻塞，直到 ctx 退出，或停止持有leader的租约。\ngo 1 2 3 4 5 6 7 8 9 10 func RunOrDie(ctx context.Context, lec LeaderElectionConfig) { le, err := NewLeaderElector(lec) if err != nil { panic(err) } if lec.WatchDog != nil { lec.WatchDog.SetLeaderElection(le) } le.Run(ctx) } 下面看下 NewLeaderElector 做了些什么？可以看到，LeaderElector是一个结构体，这里只是创建他，这个结构体提供了我们选举中所需要的一切（LeaderElector就是RunOrDie创建的选举客户端）。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 func NewLeaderElector(lec LeaderElectionConfig) (*LeaderElector, error) { if lec.LeaseDuration \u0026lt;= lec.RenewDeadline { return nil, fmt.Errorf(\u0026#34;leaseDuration must be greater than renewDeadline\u0026#34;) } if lec.RenewDeadline \u0026lt;= time.Duration(JitterFactor*float64(lec.RetryPeriod)) { return nil, fmt.Errorf(\u0026#34;renewDeadline must be greater than retryPeriod*JitterFactor\u0026#34;) } if lec.LeaseDuration \u0026lt; 1 { return nil, fmt.Errorf(\u0026#34;leaseDuration must be greater than zero\u0026#34;) } if lec.RenewDeadline \u0026lt; 1 { return nil, fmt.Errorf(\u0026#34;renewDeadline must be greater than zero\u0026#34;) } if lec.RetryPeriod \u0026lt; 1 { return nil, fmt.Errorf(\u0026#34;retryPeriod must be greater than zero\u0026#34;) } if lec.Callbacks.OnStartedLeading == nil { return nil, fmt.Errorf(\u0026#34;OnStartedLeading callback must not be nil\u0026#34;) } if lec.Callbacks.OnStoppedLeading == nil { return nil, fmt.Errorf(\u0026#34;OnStoppedLeading callback must not be nil\u0026#34;) } if lec.Lock == nil { return nil, fmt.Errorf(\u0026#34;Lock must not be nil.\u0026#34;) } le := LeaderElector{ config: lec, clock: clock.RealClock{}, metrics: globalMetricsFactory.newLeaderMetrics(), } le.metrics.leaderOff(le.config.Name) return \u0026amp;le, nil } LeaderElector 是建立的选举客户端，\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type LeaderElector struct { config LeaderElectionConfig // 这个的配置，包含一些时间参数，健康检查 // recoder相关属性 observedRecord rl.LeaderElectionRecord observedRawRecord []byte observedTime time.Time // used to implement OnNewLeader(), may lag slightly from the // value observedRecord.HolderIdentity if the transition has // not yet been reported. reportedLeader string // clock is wrapper around time to allow for less flaky testing clock clock.Clock // 锁定 observedRecord observedRecordLock sync.Mutex metrics leaderMetricsAdapter } 可以看到 Run 实现的选举逻辑就是在初始化客户端时传入的 三个 callback\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (le *LeaderElector) Run(ctx context.Context) { defer runtime.HandleCrash() defer func() { // 退出时执行callbacke的OnStoppedLeading le.config.Callbacks.OnStoppedLeading() }() if !le.acquire(ctx) { return } ctx, cancel := context.WithCancel(ctx) defer cancel() go le.config.Callbacks.OnStartedLeading(ctx) // 选举时，执行 OnStartedLeading le.renew(ctx) } 在 Run 中调用了 acquire，这个是 通过一个loop去调用 tryAcquireOrRenew，直到ctx传递过来结束信号\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func (le *LeaderElector) acquire(ctx context.Context) bool { ctx, cancel := context.WithCancel(ctx) defer cancel() succeeded := false desc := le.config.Lock.Describe() klog.Infof(\u0026#34;attempting to acquire leader lease %v...\u0026#34;, desc) // jitterUntil是执行定时的函数 func() 是定时任务的逻辑 // RetryPeriod是周期间隔 // JitterFactor 是重试系数，类似于延迟队列中的系数 （duration + maxFactor * duration） // sliding 逻辑是否计算在时间内 // 上下文传递 wait.JitterUntil(func() { succeeded = le.tryAcquireOrRenew(ctx) le.maybeReportTransition() if !succeeded { klog.V(4).Infof(\u0026#34;failed to acquire lease %v\u0026#34;, desc) return } le.config.Lock.RecordEvent(\u0026#34;became leader\u0026#34;) le.metrics.leaderOn(le.config.Name) klog.Infof(\u0026#34;successfully acquired lease %v\u0026#34;, desc) cancel() }, le.config.RetryPeriod, JitterFactor, true, ctx.Done()) return succeeded } 这里实际上选举动作在 tryAcquireOrRenew 中，下面来看下tryAcquireOrRenew；tryAcquireOrRenew 是尝试获得一个leader租约，如果已经获得到了，则更新租约；否则可以得到租约则为true，反之false\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 func (le *LeaderElector) tryAcquireOrRenew(ctx context.Context) bool { now := metav1.Now() // 时间 leaderElectionRecord := rl.LeaderElectionRecord{ // 构建一个选举record HolderIdentity: le.config.Lock.Identity(), // 选举人的身份特征，ep与主机名有关 LeaseDurationSeconds: int(le.config.LeaseDuration / time.Second), // 默认15s RenewTime: now, // 重新获取时间 AcquireTime: now, // 获得时间 } // 1. 从API获取或创建一个recode，如果可以拿到则已经有租约，反之创建新租约 oldLeaderElectionRecord, oldLeaderElectionRawRecord, err := le.config.Lock.Get(ctx) if err != nil { if !errors.IsNotFound(err) { klog.Errorf(\u0026#34;error retrieving resource lock %v: %v\u0026#34;, le.config.Lock.Describe(), err) return false } // 创建租约的动作就是新建一个对应的resource，这个lock就是leaderelection提供的四种锁， // 看你在runOrDie中初始化传入了什么锁 if err = le.config.Lock.Create(ctx, leaderElectionRecord); err != nil { klog.Errorf(\u0026#34;error initially creating leader election record: %v\u0026#34;, err) return false } // 到了这里就已经拿到或者创建了租约，然后记录其一些属性，LeaderElectionRecord le.setObservedRecord(\u0026amp;leaderElectionRecord) return true } // 2. 获取记录检查身份和时间 if !bytes.Equal(le.observedRawRecord, oldLeaderElectionRawRecord) { le.setObservedRecord(oldLeaderElectionRecord) le.observedRawRecord = oldLeaderElectionRawRecord } if len(oldLeaderElectionRecord.HolderIdentity) \u0026gt; 0 \u0026amp;\u0026amp; le.observedTime.Add(le.config.LeaseDuration).After(now.Time) \u0026amp;\u0026amp; !le.IsLeader() { // 不是leader，进行HolderIdentity比较，再加上时间，这个时候没有到竞选其，跳出 klog.V(4).Infof(\u0026#34;lock is held by %v and has not yet expired\u0026#34;, oldLeaderElectionRecord.HolderIdentity) return false } // 3.我们将尝试更新。 在这里leaderElectionRecord设置为默认值。让我们在更新之前更正它。 if le.IsLeader() { // 到这就说明是leader，修正他的时间 leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTime leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions } else { // LeaderTransitions 就是指leader调整（转变为其他）了几次，如果是， // 则为发生转变，保持原有值 // 反之，则+1 leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1 } // 完事之后更新APIServer中的锁资源，也就是更新对应的资源的属性信息 if err = le.config.Lock.Update(ctx, leaderElectionRecord); err != nil { klog.Errorf(\u0026#34;Failed to update lock: %v\u0026#34;, err) return false } // setObservedRecord 是通过一个新的record来更新这个锁中的record // 操作是安全的，会上锁保证临界区仅可以被一个线程/进程操作 le.setObservedRecord(\u0026amp;leaderElectionRecord) return true } summary 到这里，已经完整知道利用kubernetes进行选举的流程都是什么了；下面简单回顾下，上述leader选举所有的步骤：\n首选创建的服务就是该服务的leader，锁可以为 lease , endpoint 等资源进行上锁 已经是leader的实例会不断续租，租约的默认值是15秒 （leaseDuration）；leader在租约满时更新租约时间（renewTime）。 其他的follower，会不断检查对应资源锁的存在，如果已经有leader，那么则检查 renewTime，如果超过了租用时间（），则表明leader存在问题需要重新启动选举，直到有follower提升为leader。 而为了避免资源被抢占，Kubernetes API使用了 ResourceVersion 来避免被重复修改（如果版本号与请求版本号不一致，则表示已经被修改了，那么APIServer将返回错误） Reference Kubernetes 并发控制与数据一致性的实现原理 Controller manager 的高可用实现方式 deep dive into kubernetes simple leader election ","permalink":"https://www.161616.top/ch27-leader-election/","summary":"Overview 在 Kubernetes的 kube-controller-manager , kube-scheduler, 以及使用 Operator 的底层实现 controller-rumtime 都支持高可用系统中的leader选举，本文将以理解 controller-rumtime （底层的实现是 client-go） 中的leader选举以在kubernetes controller中是如何实现的。\nBackground 在运行 kube-controller-manager 时，是有一些参数提供给cm进行leader选举使用的，可以参考官方文档提供的 参数 来了解相关参数。\nbash 1 2 3 4 5 6 7 --leader-elect Default: true --leader-elect-renew-deadline duration Default: 10s --leader-elect-resource-lock string Default: \u0026#34;leases\u0026#34; --leader-elect-resource-name string Default: \u0026#34;kube-controller-manager\u0026#34; --leader-elect-resource-namespace string Default: \u0026#34;kube-system\u0026#34; --leader-elect-retry-period duration Default: 2s ... 本身以为这些组件的选举动作时通过etcd进行的，但是后面对 controller-runtime 学习时，发现并没有配置其相关的etcd相关参数，这就引起了对选举机制的好奇。怀着这种好奇心搜索了下有关于 kubernetes的选举，发现官网是这么介绍的，下面是对官方的说明进行一个通俗总结。simple leader election with kubernetes\n通过阅读文章得知，kubernetes API 提供了一中选举机制，只要运行在集群内的容器，都是可以实现选举功能的。\nKubernetes API通过提供了两个属性来完成选举动作的\nResourceVersions：每个API对象唯一一个ResourceVersion Annotations：每个API对象都可以对这些key进行注释 注：这种选举会增加APIServer的压力。也就对etcd会产生影响","title":"源码分析Kubernetes HA机制 - leader election"},{"content":"Overview controller-runtime 是 Kubernetes 社区提供可供快速搭建一套 实现了controller 功能的工具，无需自行实现Controller的功能了；在 Kubebuilder 与 Operator SDK 也是使用 controller-runtime 。本文将对 controller-runtime 的工作原理以及在不同场景下的使用方式进行简要的总结和介绍。\ncontroller-runtime structure controller-runtime 主要组成是需要用户创建的 Manager 和 Reconciler 以及 Controller Runtime 自己启动的 Cache 和 Controller 。\nManager：是用户在初始化时创建的，用于启动 Controller Runtime 组件 Reconciler：是用户需要提供来处理自己的业务逻辑的组件（即在通过 code-generator 生成的api-like而实现的controller中的业务处理部分）。 Cache：一个缓存，用来建立 Informer 到 ApiServer 的连接来监听资源并将被监听的对象推送到queue中。 Controller： 一方面向 Informer 注册 eventHandler，另一方面从队列中获取数据。controller 将从队列中获取数据并执行用户自定义的 Reconciler 功能。 图：controller-runtime structure 图：controller-runtime flowchart 由图可知，Controller会向 Informer 注册一些列eventHandler；然后Cache启动Informer（informer属于cache包中），与ApiServer建立监听；当Informer检测到资源变化时，将对象加入queue，Controller 将元素取出并在用户端执行 Reconciler。\nController引入 我们从 controller-rumtime项目的 example 进行引入看下，整个架构都是如何实现的。\n可以看到 example 下的实际上实现了一个 reconciler 的结构体，实现了 Reconciler 抽象和 Client 结构体\ngo 1 2 3 4 type reconciler struct { client.Client scheme *runtime.Scheme } 那么来看下 抽象的 Reconciler 是什么，可以看到就是抽象了 Reconcile 方法，这个是具体处理的逻辑过程\ngo 1 2 3 type Reconciler interface { Reconcile(context.Context, Request) (Result, error) } 下面在看下谁来实现了这个 Reconciler 抽象\ngo 1 2 3 4 5 6 7 8 type Controller interface { reconcile.Reconciler // 协调的具体步骤，通过ns/name\\ // 通过predicates来评估来源数据，并加入queue中（放入队列的是reconcile.Requests） Watch(src source.Source, eventhandler handler.EventHandler, predicates ...predicate.Predicate) error // 启动controller，类似于自定义的Run() Start(ctx context.Context) error GetLogger() logr.Logger } controller structure 在 controller-runtime\\pkg\\internal\\controller\\controller.go 中实现了这个 Controller\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 type Controller struct { Name string // controller的标识 MaxConcurrentReconciles int // 并发运行Reconciler的数量，默认1 // 实现了reconcile.Reconciler的调节器， 默认DefaultReconcileFunc Do reconcile.Reconciler // makeQueue会构建一个对应的队列，就是返回一个限速队列 MakeQueue func() workqueue.RateLimitingInterface // MakeQueue创造出来的，在出入队列就是操作的这个 Queue workqueue.RateLimitingInterface // 用于注入其他内容 // 已弃用 SetFields func(i interface{}) error mu sync.Mutex // 标识开始的状态 Started bool // 在启动时传递的上下文，用于停止控制器 ctx context.Context // 等待缓存同步的时间 默认2分钟 CacheSyncTimeout time.Duration // 维护了eventHandler predicates，在控制器启动时启动 startWatches []watchDescription // 日志构建器，输出入日志 LogConstructor func(request *reconcile.Request) logr.Logger // RecoverPanic为是否对reconcile引起的panic恢复 RecoverPanic bool } 看完了controller的structure，接下来看看controller是如何使用的\ninjection Controller.Watch 实现了注入的动作，可以看到 watch() 通过参数将 对应的事件函数传入到内部\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prct ...predicate.Predicate) error { c.mu.Lock() defer c.mu.Unlock() // 使用SetFields来完成注入操作 if err := c.SetFields(src); err != nil { return err } if err := c.SetFields(evthdler); err != nil { return err } for _, pr := range prct { if err := c.SetFields(pr); err != nil { return err } } // 如果Controller还未启动，那么将这些动作缓存到本地 if !c.Started { c.startWatches = append(c.startWatches, watchDescription{src: src, handler: evthdler, predicates: prct}) return nil } c.LogConstructor(nil).Info(\u0026#34;Starting EventSource\u0026#34;, \u0026#34;source\u0026#34;, src) return src.Start(c.ctx, evthdler, c.Queue, prct...) } 启动操作实际上为informer注入事件函数\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Source interface { // start 是Controller 调用，用以向 Informer 注册 EventHandler， 将 reconcile.Requests（一个入队列的动作） 排入队列。 Start(context.Context, handler.EventHandler, workqueue.RateLimitingInterface, ...predicate.Predicate) error } func (is *Informer) Start(ctx context.Context, handler handler.EventHandler, queue workqueue.RateLimitingInterface, prct ...predicate.Predicate) error { // Informer should have been specified by the user. if is.Informer == nil { return fmt.Errorf(\u0026#34;must specify Informer.Informer\u0026#34;) } is.Informer.AddEventHandler(internal.EventHandler{Queue: queue, EventHandler: handler, Predicates: prct}) return nil } 我们知道对于 eventHandler，实际上应该是一个 onAdd，onUpdate 这种类型的函数，queue则是workqueue，那么 Predicates 是什么呢？\n通过追踪可以看到定义了 Predicate 抽象，可以看出Predicate 是Watch到的事件时什么类型的，当对于每个类型的事件，对应的函数就为 true，在 eventHandler 中，这些被用作，事件的过滤。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // Predicate filters events before enqueuing the keys. type Predicate interface { // Create returns true if the Create event should be processed Create(event.CreateEvent) bool // Delete returns true if the Delete event should be processed Delete(event.DeleteEvent) bool // Update returns true if the Update event should be processed Update(event.UpdateEvent) bool // Generic returns true if the Generic event should be processed Generic(event.GenericEvent) bool } 在对应的动作中，可以看到这里作为过滤操作\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func (e EventHandler) OnAdd(obj interface{}) { c := event.CreateEvent{} // Pull Object out of the object if o, ok := obj.(client.Object); ok { c.Object = o } else { log.Error(nil, \u0026#34;OnAdd missing Object\u0026#34;, \u0026#34;object\u0026#34;, obj, \u0026#34;type\u0026#34;, fmt.Sprintf(\u0026#34;%T\u0026#34;, obj)) return } for _, p := range e.Predicates { if !p.Create(c) { return } } // Invoke create handler e.EventHandler.Create(c, e.Queue) } 上面就看到了，对应是 EventHandler.Create 进行添加的，那么这些动作具体是在做什么呢？\n在代码 pkg/handler ,可以看到这些操作，类似于create，这里将ns/name放入到队列中。\ngo 1 2 3 4 5 6 7 8 9 10 func (e *EnqueueRequestForObject) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) { if evt.Object == nil { enqueueLog.Error(nil, \u0026#34;CreateEvent received with no metadata\u0026#34;, \u0026#34;event\u0026#34;, evt) return } q.Add(reconcile.Request{NamespacedName: types.NamespacedName{ Name: evt.Object.GetName(), Namespace: evt.Object.GetNamespace(), }}) } unqueue 上面看到了，入队的动作实际上都是将 ns/name 加入到队列中，那么出队列时又做了些什么呢？\n通过 controller.Start() 可以看到controller在启动后都做了些什么动作\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 func (c *Controller) Start(ctx context.Context) error { c.mu.Lock() if c.Started { return errors.New(\u0026#34;controller was started more than once. This is likely to be caused by being added to a manager multiple times\u0026#34;) } c.initMetrics() // Set the internal context. c.ctx = ctx c.Queue = c.MakeQueue() // 初始化queue go func() { // 退出时，让queue关闭 \u0026lt;-ctx.Done() c.Queue.ShutDown() }() wg := \u0026amp;sync.WaitGroup{} err := func() error { defer c.mu.Unlock() defer utilruntime.HandleCrash() // 启动informer前，将之前准备好的 evnetHandle predictates source注册 for _, watch := range c.startWatches { c.LogConstructor(nil).Info(\u0026#34;Starting EventSource\u0026#34;, \u0026#34;source\u0026#34;, fmt.Sprintf(\u0026#34;%s\u0026#34;, watch.src)) // 上面我们看过了，start就是真正的注册动作 if err := watch.src.Start(ctx, watch.handler, c.Queue, watch.predicates...); err != nil { return err } } // Start the SharedIndexInformer factories to begin populating the SharedIndexInformer caches c.LogConstructor(nil).Info(\u0026#34;Starting Controller\u0026#34;) // startWatches上面我们也看到了，是evnetHandle predictates source被缓存到里面， // 这里是拿出来将其启动 for _, watch := range c.startWatches { syncingSource, ok := watch.src.(source.SyncingSource) if !ok { continue } if err := func() error { // use a context with timeout for launching sources and syncing caches. sourceStartCtx, cancel := context.WithTimeout(ctx, c.CacheSyncTimeout) defer cancel() // WaitForSync waits for a definitive timeout, and returns if there // is an error or a timeout if err := syncingSource.WaitForSync(sourceStartCtx); err != nil { err := fmt.Errorf(\u0026#34;failed to wait for %s caches to sync: %w\u0026#34;, c.Name, err) c.LogConstructor(nil).Error(err, \u0026#34;Could not wait for Cache to sync\u0026#34;) return err } return nil }(); err != nil { return err } } // which won\u0026#39;t be garbage collected if we hold a reference to it. c.startWatches = nil // Launch workers to process resources c.LogConstructor(nil).Info(\u0026#34;Starting workers\u0026#34;, \u0026#34;worker count\u0026#34;, c.MaxConcurrentReconciles) wg.Add(c.MaxConcurrentReconciles) // 启动controller消费端的线程 for i := 0; i \u0026lt; c.MaxConcurrentReconciles; i++ { go func() { defer wg.Done() for c.processNextWorkItem(ctx) { } }() } c.Started = true return nil }() if err != nil { return err } \u0026lt;-ctx.Done() // 阻塞，直到上下文关闭 c.LogConstructor(nil).Info(\u0026#34;Shutdown signal received, waiting for all workers to finish\u0026#34;) wg.Wait() // 等待所有线程都关闭 c.LogConstructor(nil).Info(\u0026#34;All workers finished\u0026#34;) return nil } 通过上面的分析，可以看到，每个消费的worker线程，实际上调用的是 processNextWorkItem 下面就来看看他究竟做了些什么？\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (c *Controller) processNextWorkItem(ctx context.Context) bool { obj, shutdown := c.Queue.Get() // 从队列中拿取数据 if shutdown { return false } defer c.Queue.Done(obj) // 下面应该是prometheus指标的一些东西 ctrlmetrics.ActiveWorkers.WithLabelValues(c.Name).Add(1) defer ctrlmetrics.ActiveWorkers.WithLabelValues(c.Name).Add(-1) // 获得的对象通过reconcileHandler处理 c.reconcileHandler(ctx, obj) return true } 那么下面看看 reconcileHandler 做了些什么\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) { // Update metrics after processing each item reconcileStartTS := time.Now() defer func() { c.updateMetrics(time.Since(reconcileStartTS)) }() // 检查下取出的数据是否为reconcile.Request，在之前enqueue时了解到是插入的这个类型的值 req, ok := obj.(reconcile.Request) if !ok { // 如果错了就忘记 c.Queue.Forget(obj) c.LogConstructor(nil).Error(nil, \u0026#34;Queue item was not a Request\u0026#34;, \u0026#34;type\u0026#34;, fmt.Sprintf(\u0026#34;%T\u0026#34;, obj), \u0026#34;value\u0026#34;, obj) return } log := c.LogConstructor(\u0026amp;req) log = log.WithValues(\u0026#34;reconcileID\u0026#34;, uuid.NewUUID()) ctx = logf.IntoContext(ctx, log) // 这里调用了自己在实现controller实现的Reconcile的动作 result, err := c.Reconcile(ctx, req) switch { case err != nil: c.Queue.AddRateLimited(req) ctrlmetrics.ReconcileErrors.WithLabelValues(c.Name).Inc() ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelError).Inc() log.Error(err, \u0026#34;Reconciler error\u0026#34;) case result.RequeueAfter \u0026gt; 0: c.Queue.Forget(obj) c.Queue.AddAfter(req, result.RequeueAfter) ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelRequeueAfter).Inc() case result.Requeue: c.Queue.AddRateLimited(req) ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelRequeue).Inc() default: c.Queue.Forget(obj) ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelSuccess).Inc() } } 通过对example中的 Reconcile 查找其使用，可以看到，调用他的就是上面我们说道的 reconcileHandler ，到这里我们就知道了，controller 的运行流为 Controller.Start() \u0026gt; Controller.processNextWorkItem \u0026gt; Controller.reconcileHandler \u0026gt; Controller.Reconcile 最终到达了我们自定义的业务逻辑处理 Reconcile\nManager 在上面学习 controller-runtime 时了解到，有一个 Manager 的组件，这个组件是做什么呢？我们来分析下。\nManager 是用来创建与启动 controller 的（允许多个 controller 与 一个 manager 关联），Manager会启动分配给他的所有controller，以及其他可启动的对象。\n在 example 看到，会初始化一个 ctrl.NewManager\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 func main() { ctrl.SetLogger(zap.New()) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{}) if err != nil { setupLog.Error(err, \u0026#34;unable to start manager\u0026#34;) os.Exit(1) } // in a real controller, we\u0026#39;d create a new scheme for this err = api.AddToScheme(mgr.GetScheme()) if err != nil { setupLog.Error(err, \u0026#34;unable to add scheme\u0026#34;) os.Exit(1) } err = ctrl.NewControllerManagedBy(mgr). For(\u0026amp;api.ChaosPod{}). Owns(\u0026amp;corev1.Pod{}). Complete(\u0026amp;reconciler{ Client: mgr.GetClient(), scheme: mgr.GetScheme(), }) if err != nil { setupLog.Error(err, \u0026#34;unable to create controller\u0026#34;) os.Exit(1) } err = ctrl.NewWebhookManagedBy(mgr). For(\u0026amp;api.ChaosPod{}). Complete() if err != nil { setupLog.Error(err, \u0026#34;unable to create webhook\u0026#34;) os.Exit(1) } setupLog.Info(\u0026#34;starting manager\u0026#34;) if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, \u0026#34;problem running manager\u0026#34;) os.Exit(1) } } 这个 manager 就是 controller-runtime\\pkg\\manager\\manager.go 下的 Manager， Manager 通过初始化 Caches 和 Clients 等共享依赖，并将它们提供给 Runnables。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 type Manager interface { // 提供了与APIServer交互的方式，如incluster，indexer，cache等 cluster.Cluster // Runnable 是任意可允许的cm中的组件，如 webhook，controller，Caches，在new中调用时， // 可以看到是传入的是一个controller，这里可以启动的是带有Start()方法的，通过调用Start() // 来启动组件 Add(Runnable) error // 实现选举方法。当elected关闭，则选举为leader Elected() \u0026lt;-chan struct{} // 这为一些列健康检查和指标的方法，和我们关注的没有太大关系 AddMetricsExtraHandler(path string, handler http.Handler) error AddHealthzCheck(name string, check healthz.Checker) error AddReadyzCheck(name string, check healthz.Checker) error // Start将启动所有注册进来的控制器，直到ctx取消。如果有任意controller报错，则立即退出 // 如果使用了 LeaderElection，则必须在此返回后立即退出二进制文件， Start(ctx context.Context) error // GetWebhookServer returns a webhook.Server GetWebhookServer() *webhook.Server // GetLogger returns this manager\u0026#39;s logger. GetLogger() logr.Logger // GetControllerOptions returns controller global configuration options. GetControllerOptions() v1alpha1.ControllerConfigurationSpec } controller-manager controllerManager 则实现了这个manager的抽象\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 type controllerManager struct { sync.Mutex started bool stopProcedureEngaged *int64 errChan chan error runnables *runnables cluster cluster.Cluster // recorderProvider 用于记录eventhandler source predictate recorderProvider *intrec.Provider // resourceLock forms the basis for leader election resourceLock resourcelock.Interface // 在退出时是否关闭选举租约 leaderElectionReleaseOnCancel bool // 一些指标性的，暂时不需要关注 metricsListener net.Listener metricsExtraHandlers map[string]http.Handler healthProbeListener net.Listener readinessEndpointName string livenessEndpointName string readyzHandler *healthz.Handler healthzHandler *healthz.Handler // 有关controller全局参数 controllerOptions v1alpha1.ControllerConfigurationSpec logger logr.Logger // 用于关闭 LeaderElection.Run(...) 的信号 leaderElectionStopped chan struct{} // 取消选举，在失去选举后，必须延迟到gracefulShutdown之后os.exit() leaderElectionCancel context.CancelFunc // leader取消选举 elected chan struct{} port int host string certDir string webhookServer *webhook.Server webhookServerOnce sync.Once // 非leader节点强制leader的等待时间 leaseDuration time.Duration // renewDeadline is the duration that the acting controlplane will retry // refreshing leadership before giving up. renewDeadline time.Duration // LeaderElector重新操作的时间 retryPeriod time.Duration // gracefulShutdownTimeout 是在manager停止之前让runnables停止的持续时间。 gracefulShutdownTimeout time.Duration // onStoppedLeading is callled when the leader election lease is lost. // It can be overridden for tests. onStoppedLeading func() shutdownCtx context.Context internalCtx context.Context internalCancel context.CancelFunc internalProceduresStop chan struct{} } workflow 了解完ControllerManager之后，我们通过 example 来看看 ControllerManager 的workflow\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 func main() { ctrl.SetLogger(zap.New()) // New一个manager mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{}) if err != nil { setupLog.Error(err, \u0026#34;unable to start manager\u0026#34;) os.Exit(1) } // in a real controller, we\u0026#39;d create a new scheme for this err = api.AddToScheme(mgr.GetScheme()) if err != nil { setupLog.Error(err, \u0026#34;unable to add scheme\u0026#34;) os.Exit(1) } err = ctrl.NewControllerManagedBy(mgr). For(\u0026amp;api.ChaosPod{}). Owns(\u0026amp;corev1.Pod{}). Complete(\u0026amp;reconciler{ Client: mgr.GetClient(), scheme: mgr.GetScheme(), }) if err != nil { setupLog.Error(err, \u0026#34;unable to create controller\u0026#34;) os.Exit(1) } err = ctrl.NewWebhookManagedBy(mgr). For(\u0026amp;api.ChaosPod{}). Complete() if err != nil { setupLog.Error(err, \u0026#34;unable to create webhook\u0026#34;) os.Exit(1) } setupLog.Info(\u0026#34;starting manager\u0026#34;) if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, \u0026#34;problem running manager\u0026#34;) os.Exit(1) } } 通过 manager.New() 初始化一个manager，这里面会初始化一些列的manager的参数 通过 ctrl.NewControllerManagedBy 注册 controller 到manager中 ctrl.NewControllerManagedBy 是 builder的一个别名，构建出一个builder类型的controller builder 中的 ctrl 就是 controller 启动manager builder 下面看来看下builder在构建时做了什么\ngo 1 2 3 4 5 6 7 8 9 10 11 // Builder builds a Controller. type Builder struct { forInput ForInput ownsInput []OwnsInput watchesInput []WatchesInput mgr manager.Manager globalPredicates []predicate.Predicate ctrl controller.Controller ctrlOptions controller.Options name string } 我们看到 example 中是调用了 For() 动作，那么这个 For() 是什么呢？\n通过注释，我们可以看到 For() 提供了 调解对象类型，ControllerManagedBy 通过 reconciling object 来相应对应create/delete/update 事件。调用 For() 相当于调用了 Watches(\u0026amp;source.Kind{Type: apiType}, \u0026amp;handler.EnqueueRequestForObject{}) 。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 func (blder *Builder) For(object client.Object, opts ...ForOption) *Builder { if blder.forInput.object != nil { blder.forInput.err = fmt.Errorf(\u0026#34;For(...) should only be called once, could not assign multiple objects for reconciliation\u0026#34;) return blder } input := ForInput{object: object} for _, opt := range opts { opt.ApplyToFor(\u0026amp;input) //最终把我们要监听的对象每个 opts注册进去 } blder.forInput = input return blder } 接下来是调用的 Owns() ，Owns() 看起来和 For() 功能是类似的。只是说属于不同，是通过Owns方法设置的\ngo 1 2 3 4 5 6 7 8 9 func (blder *Builder) Owns(object client.Object, opts ...OwnsOption) *Builder { input := OwnsInput{object: object} for _, opt := range opts { opt.ApplyToOwns(\u0026amp;input) } blder.ownsInput = append(blder.ownsInput, input) return blder } 最后到了 Complete()，Complete 是完成这个controller的构建\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 // Complete builds the Application Controller. func (blder *Builder) Complete(r reconcile.Reconciler) error { _, err := blder.Build(r) return err } // Build 创建控制器并返回 func (blder *Builder) Build(r reconcile.Reconciler) (controller.Controller, error) { if r == nil { return nil, fmt.Errorf(\u0026#34;must provide a non-nil Reconciler\u0026#34;) } if blder.mgr == nil { return nil, fmt.Errorf(\u0026#34;must provide a non-nil Manager\u0026#34;) } if blder.forInput.err != nil { return nil, blder.forInput.err } // Checking the reconcile type exist or not if blder.forInput.object == nil { return nil, fmt.Errorf(\u0026#34;must provide an object for reconciliation\u0026#34;) } // Set the ControllerManagedBy if err := blder.doController(r); err != nil { return nil, err } // Set the Watch if err := blder.doWatch(); err != nil { return nil, err } return blder.ctrl, nil } 这里面可以看到，会完成 doController 和 doWatch\ndoController会初始化好这个controller并返回\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 func (blder *Builder) doController(r reconcile.Reconciler) error { globalOpts := blder.mgr.GetControllerOptions() ctrlOptions := blder.ctrlOptions if ctrlOptions.Reconciler == nil { ctrlOptions.Reconciler = r } // 通过检索GVK获得默认的名称 gvk, err := getGvk(blder.forInput.object, blder.mgr.GetScheme()) if err != nil { return err } // 设置并发，如果最大并发为0则找到一个 // 追踪下去看似是对于没有设置时，例如会根据 app group中的 ReplicaSet设定 // 就是在For()传递的一个类型的数量来确定并发的数量 if ctrlOptions.MaxConcurrentReconciles == 0 { groupKind := gvk.GroupKind().String() if concurrency, ok := globalOpts.GroupKindConcurrency[groupKind]; ok \u0026amp;\u0026amp; concurrency \u0026gt; 0 { ctrlOptions.MaxConcurrentReconciles = concurrency } } // Setup cache sync timeout. if ctrlOptions.CacheSyncTimeout == 0 \u0026amp;\u0026amp; globalOpts.CacheSyncTimeout != nil { ctrlOptions.CacheSyncTimeout = *globalOpts.CacheSyncTimeout } // 给controller一个name，如果没有初始化传递，则使用Kind做名称 controllerName := blder.getControllerName(gvk) // Setup the logger. if ctrlOptions.LogConstructor == nil { log := blder.mgr.GetLogger().WithValues( \u0026#34;controller\u0026#34;, controllerName, \u0026#34;controllerGroup\u0026#34;, gvk.Group, \u0026#34;controllerKind\u0026#34;, gvk.Kind, ) lowerCamelCaseKind := strings.ToLower(gvk.Kind[:1]) + gvk.Kind[1:] ctrlOptions.LogConstructor = func(req *reconcile.Request) logr.Logger { log := log if req != nil { log = log.WithValues( lowerCamelCaseKind, klog.KRef(req.Namespace, req.Name), \u0026#34;namespace\u0026#34;, req.Namespace, \u0026#34;name\u0026#34;, req.Name, ) } return log } } // 这里就是构建一个新的控制器了，也就是前面说到的 manager.New() blder.ctrl, err = newController(controllerName, blder.mgr, ctrlOptions) return err } manager.New()\nstart Manager 接下来是manager的启动，也就是对应的 start() 与 doWatch()\n通过下述代码我们可以看出来，对于 doWatch() 就是把 compete() 前的一些资源的事件函数都注入到controller 中\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 func (blder *Builder) doWatch() error { // 调解类型，这也也就是对于For的obj来说，我们需要的是什么结构的，如非结构化数据或metadata-only // metadata-only就是配置成一个GVK schema.GroupVersionKind typeForSrc, err := blder.project(blder.forInput.object, blder.forInput.objectProjection) if err != nil { return err }\u0026amp;source.Kind{} // 一些准备工作，将对象封装为\u0026amp;source.Kind{} // src := \u0026amp;source.Kind{Type: typeForSrc} hdler := \u0026amp;handler.EnqueueRequestForObject{} // 就是包含obj的一个事件队列 allPredicates := append(blder.globalPredicates, blder.forInput.predicates...) // 这里又到之前说过的controller watch了 // 将一系列的准备动作注入到cache 如 source eventHandler predicate if err := blder.ctrl.Watch(src, hdler, allPredicates...); err != nil { return err } // 再重复 ownsInput 动作 for _, own := range blder.ownsInput { typeForSrc, err := blder.project(own.object, own.objectProjection) if err != nil { return err } src := \u0026amp;source.Kind{Type: typeForSrc} hdler := \u0026amp;handler.EnqueueRequestForOwner{ OwnerType: blder.forInput.object, IsController: true, } allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...) allPredicates = append(allPredicates, own.predicates...) if err := blder.ctrl.Watch(src, hdler, allPredicates...); err != nil { return err } } // 在对 ownsInput 进行重复的操作 for _, w := range blder.watchesInput { allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...) allPredicates = append(allPredicates, w.predicates...) // If the source of this watch is of type *source.Kind, project it. if srckind, ok := w.src.(*source.Kind); ok { typeForSrc, err := blder.project(srckind.Type, w.objectProjection) if err != nil { return err } srckind.Type = typeForSrc } if err := blder.ctrl.Watch(w.src, w.eventhandler, allPredicates...); err != nil { return err } } return nil } 由于前两部 builder 的操作将 mgr 指针传入到 builder中，并且操作了 complete() ，也就是操作了 build() ,这代表了对 controller 完成了初始化，和事件注入（watch）的操作，所以 Start()，就是将controller启动\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 func (cm *controllerManager) Start(ctx context.Context) (err error) { cm.Lock() if cm.started { cm.Unlock() return errors.New(\u0026#34;manager already started\u0026#34;) } var ready bool defer func() { if !ready { cm.Unlock() } }() // Initialize the internal context. cm.internalCtx, cm.internalCancel = context.WithCancel(ctx) // 这个channel代表了controller的停止 stopComplete := make(chan struct{}) defer close(stopComplete) // This must be deferred after closing stopComplete, otherwise we deadlock. defer func() { stopErr := cm.engageStopProcedure(stopComplete) if stopErr != nil { if err != nil { err = kerrors.NewAggregate([]error{err, stopErr}) } else { err = stopErr } } }() // Add the cluster runnable. if err := cm.add(cm.cluster); err != nil { return fmt.Errorf(\u0026#34;failed to add cluster to runnables: %w\u0026#34;, err) } // 指标类 if cm.metricsListener != nil { cm.serveMetrics() } if cm.healthProbeListener != nil { cm.serveHealthProbes() } if err := cm.runnables.Webhooks.Start(cm.internalCtx); err != nil { if !errors.Is(err, wait.ErrWaitTimeout) { return err } } // 等待informer同步完成 if err := cm.runnables.Caches.Start(cm.internalCtx); err != nil { if !errors.Is(err, wait.ErrWaitTimeout) { return err } } // 非选举模式，runnable将在cache同步完成后启动 if err := cm.runnables.Others.Start(cm.internalCtx); err != nil { if !errors.Is(err, wait.ErrWaitTimeout) { return err } } // Start the leader election and all required runnables. { ctx, cancel := context.WithCancel(context.Background()) cm.leaderElectionCancel = cancel go func() { if cm.resourceLock != nil { if err := cm.startLeaderElection(ctx); err != nil { cm.errChan \u0026lt;- err } } else { // Treat not having leader election enabled the same as being elected. if err := cm.startLeaderElectionRunnables(); err != nil { cm.errChan \u0026lt;- err } close(cm.elected) } }() } ready = true cm.Unlock() select { case \u0026lt;-ctx.Done(): // We are done return nil case err := \u0026lt;-cm.errChan: // Error starting or running a runnable return err } } 可以看到上面启动了4种类型的runnable，实际上就是对这runnable进行启动，例如 controller，cache等。\n回顾一下，我们之前在使用code-generator 生成，并自定义controller时，我们也是通过启动 informer.Start() ，否则会报错。\n最后可以通过一张关系图来表示，client-go与controller-manager之间的关系\nReference diving controller runtime ","permalink":"https://www.161616.top/ch15-controller-runtime/","summary":"Overview controller-runtime 是 Kubernetes 社区提供可供快速搭建一套 实现了controller 功能的工具，无需自行实现Controller的功能了；在 Kubebuilder 与 Operator SDK 也是使用 controller-runtime 。本文将对 controller-runtime 的工作原理以及在不同场景下的使用方式进行简要的总结和介绍。\ncontroller-runtime structure controller-runtime 主要组成是需要用户创建的 Manager 和 Reconciler 以及 Controller Runtime 自己启动的 Cache 和 Controller 。\nManager：是用户在初始化时创建的，用于启动 Controller Runtime 组件 Reconciler：是用户需要提供来处理自己的业务逻辑的组件（即在通过 code-generator 生成的api-like而实现的controller中的业务处理部分）。 Cache：一个缓存，用来建立 Informer 到 ApiServer 的连接来监听资源并将被监听的对象推送到queue中。 Controller： 一方面向 Informer 注册 eventHandler，另一方面从队列中获取数据。controller 将从队列中获取数据并执行用户自定义的 Reconciler 功能。 图：controller-runtime structure 图：controller-runtime flowchart 由图可知，Controller会向 Informer 注册一些列eventHandler；然后Cache启动Informer（informer属于cache包中），与ApiServer建立监听；当Informer检测到资源变化时，将对象加入queue，Controller 将元素取出并在用户端执行 Reconciler。\nController引入 我们从 controller-rumtime项目的 example 进行引入看下，整个架构都是如何实现的。\n可以看到 example 下的实际上实现了一个 reconciler 的结构体，实现了 Reconciler 抽象和 Client 结构体","title":"源码分析Kubernetes controller组件 - controller-runtime"},{"content":"Overview What is Kubernetes aggregation Kubernetes apiserver aggregation AA 是Kubernetes提供的一种扩展API的方法，目前并没有GA\nDifference between CRD and AA 众所周知，kubernetes扩展API的方法大概为三种：CRD、AA、手动扩展源码。根据CNCF分享中Min Kim说的AA更关注于实践，而用户无需了解底层的原理，这里使用过 kubebuilder， code-generator 的用户是很能体会到这点。官方也给出了CRD与AA的区别\nAPI Access Control Authentication CR: All strategies supported. Configured by root apiserver. AA: Supporting all root apiserver\u0026rsquo;s authenticating strategies but it has to be done via authentication token review api except for authentication proxy which will cause an extra cost of network RTT. Authorization CR: All strategies supported. Configured by root apiserver. AA: Delegating authorization requests to root apiserver via SubjectAccessReview api. Note that this approach will also cost a network RTT. Admission Control CR: You could extend via dynamic admission control webhook (which is costing network RTT). AA: While You can develop and customize your own admission controller which is dedicated to your AA. While You can\u0026rsquo;t reuse root-apiserver\u0026rsquo;s built-in admission controllers nomore. API Schema Note: CR\u0026rsquo;s integration with OpenAPI schema is being enhanced in the future releases and it will have a stronger integration with OpenAPI mechanism.\nValidating CR: (landed in 1.12) Defined via OpenAPIv3 Schema grammar. more AA: You can customize any validating flow you want. Conversion CR: (landed in 1.13) The CR conversioning (basically from storage version to requested version) could be done via conversioning webhook. AA: Develop any conversion you want. SubResource CR: Currently only status and scale sub-resource supported. AA: You can customize any sub-resouce you want. OpenAPI Schema CR: (landed in 1.13) The corresponding CRD\u0026rsquo;s OpenAPI schema will be automatically synced to root-apiserver\u0026rsquo;s openapi doc api. AA: OpenAPI doc has to be manually generated by code-generating tools. Authentication 要想很好的使用AA，就需要对kubernetes与 AA 之间认证机制进行有一定的了解，这里涉及到一些概念\n客户端证书认证 token认证 请求头认证 在下面的说明中，所有出现的APIServer都是指Kubernetes集群组件APIServer也可以为 root APIServer；所有的AA都是指 extension apiserver，就是自行开发的 AA。\n客户端证书 客户端证书就是CA签名的证书，由客户端指定CA证书，在客户端连接时进行身份验证，在Kubernetes APIserver也使用了相同的机制。\n默认情况下，APIServer在启动时指定参数 --client-ca-file ，这时APIServer会创建一个名为 extension-apiserver-authentication ，命名空间为 kube-system 下的 configMap。\nbash 1 2 3 4 5 $ kubectl get cm -A NAMESPACE NAME DATA AGE kube-system extension-apiserver-authentication 6 21h kubectl get cm extension-apiserver-authentication -n kube-system -o yaml 由上面的命令可以看出这个configMap将被填充到客户端（AA Pod实例）中，使用此CA证书作为用于验证客户端身份的CA。这样客户端会读取这个configMap，与APIServer进行身份认证。\nbash 1 2 I0622 14:24:00.509486 1 secure_serving.go:178] Serving securely on [::]:443 I0622 14:24:00.509556 1 configmap_cafile_content.go:202] Starting client-ca::kube-system::extension-apiserver-authentication::requestheader-client-ca-file token认证 Token认证是指通过HTTP Header传入 Authorization: Bearer $TOKEN 的方式进行客户端认证，这也是Kubernetes集群内认证常用的方法。\n在这种情况下，允许对APIServer进行认证也同样可以对AA进行认证。如果不想 AA 对同一集群进行身份验证，或AA在集群外部运行，可以将参数 --authentication-kubeconfig 以指定要使用的不同 Kubeconfig 认证。\n下面实例是AA的启动参数\nbash 1 2 3 4 ./bin/apiserver -h|grep authentication-kubeconfig --authentication-kubeconfig string kubeconfig file pointing at the \u0026#39;core\u0026#39; kubernetes server with enough righ ts to create tokenreviews.authentication.k8s.io. This is optional. If empty, all token requests are considered to be anonymous and no cli ent CA is looked up in the cluster. 请求头认证 RequestHeader 认证是指，APIServer对来自AA代理连接进行的身份认证。\n默认情况下，AA 从 extension-apiserver-authentication 中提到的 ConfigMap 中 提取 requestheader 客户端 CA 证书与 CN。如果主 Kubernetes APIServer 配置了选项 --requestheader-client-ca-file ，则它会填充此内容。\n跳过客户端认证 --authentication-skip-lookup\n授权 默认情况下，AA 服务器会通过自动注入到 Kubernetes 集群上运行的 pod 的连接信息和凭据，来连接到主 Kubernetes API 服务器。\nbash 1 E0622 11:20:12.375512 1 errors.go:77] Post \u0026#34;https://192.168.0.1:443/apis/authorization.k8s.io/v1/subjectaccessreviews\u0026#34;: write tcp 192.168.0.36:39324-\u0026gt;192.168.0.1:443: write: connection reset by peer 如果AA在集群外部部署，可以指定--authorization-kubeconfig 通过kubeconfig进行认证，这就类似于二进制部署中的信息。\n默认情况下，Kubernetes 集群会启用RBAC，这就意味着AA 创建多个clusterrolebinding。\n下面日志是 AA 对于集群中资源访问无权限的情况\nbash 1 2 3 E0622 09:01:26.750320 1 reflector.go:178] pkg/mod/k8s.io/client-go@v0.18.10/tools/cache/reflector.go:125: Failed to list *v1.MutatingWebhookConfiguration: mutatingwebhookconfigurations.admissionregistration.k8s.io is forbidden: User \u0026#34;system:serviceaccount:default:default\u0026#34; cannot list resource \u0026#34;mutatingwebhookconfigurations\u0026#34; in API group \u0026#34;admissionregistration.k8s.io\u0026#34; at the cluster scope E0622 09:01:29.357897 1 reflector.go:178] pkg/mod/k8s.io/client-go@v0.18.10/tools/cache/reflector.go:125: Failed to list *v1.Namespace: namespaces is forbidden: User \u0026#34;system:serviceaccount:default:default\u0026#34; cannot list resource \u0026#34;namespaces\u0026#34; in API group \u0026#34;\u0026#34; at the cluster scope E0622 09:01:39.998496 1 reflector.go:178] pkg/mod/k8s.io/client-go@v0.18.10/tools/cache/reflector.go:125: Failed to list *v1.ValidatingWebhookConfiguration: validatingwebhookconfigurations.admissionregistration.k8s.io is forbidden: User \u0026#34;system:serviceaccount:default:default\u0026#34; cannot list resource \u0026#34;validatingwebhookconfigurations\u0026#34; in API group \u0026#34;admissionregistration.k8s.io\u0026#34; at the cluster scope 需要手动在namespace kube-system 中创建rolebindding到 role extension-apiserver-authentication-reader 。这样就可以访问到configMap了。\napiserver-builder apiserver-builder 项目就是创建AA的工具，可以参考 installing.md 来安装\n初始化项目 初始化命令\n\u0026lt;your-domain\u0026gt; 这个是你的API资源的组，参考 k8s.io/api 如果组的名称是域名就设置为主域名，例如内置组 /apis/authentication.k8s.io /apis/batch 生成的go mod 包名为你所在的目录的名称 例如，在firewalld目录下，go.mod 的名称为 firewalld bash 1 apiserver-boot init repo --domain \u0026lt;your-domain\u0026gt; 例如\nbash 1 apiserver-boot init repo --domain fedoraproject.org 注：这里\u0026ndash;domain设置为主域名就可以了，后面生成的group会按照格式 +\ntext 1 2 apiserver-boot must be run from the directory containing the go package to bootstrap. This must be under $GOPATH/src/\u0026lt;package\u0026gt;. 必须在 $GOPATH/src 下创建你的项目，我这里的为 GOPATH=go/src ，这时创建项目必须在目录 go/src/src/{project} 下创建\n创建一个GVK bash 1 2 3 4 apiserver-boot create group version resource \\ --group firewalld \\ --version v1 \\ --kind PortRule 在创建完成之后会生成 api-like的类型，我们只需要填充自己需要的就可以了\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type PortRule struct { metav1.TypeMeta `json:\u0026#34;,inline\u0026#34;` metav1.ObjectMeta `json:\u0026#34;metadata,omitempty\u0026#34;` Spec PortRuleSpec `json:\u0026#34;spec,omitempty\u0026#34;` Status PortRuleStatus `json:\u0026#34;status,omitempty\u0026#34;` } // PortRuleSpec defines the desired state of PortRule type PortRuleSpec struct { // 这里内容都为空的，自己添加即可 Name string `json:\u0026#34;name\u0026#34;` Host string `json:\u0026#34;host\u0026#34;` Port int `json:\u0026#34;port\u0026#34;` IsPremanent bool `json:\u0026#34;isPremanent,omitempty\u0026#34;` } // PortRuleStatus defines the observed state of PortRule type PortRuleStatus struct { } 生成代码 apiserver-boot 没有专门用来生成代码的命令，可以执行任意生成命令即可，这里使用生成二进制执行文件命令，这个过程相当长。\nbash 1 apiserver-boot build executables 如果编译错误可以使用 --generate=false 跳过生成，这样就可以节省大量时间。\n运行方式 运行方式无非三种，本地运行，集群内运行，集群外运行\nrunning_locally 本地运行需要有一个etcd服务，不用配置ca证书，这里使用docker运行\nsh 1 2 3 4 5 6 docker run -d --name Etcd-server \\ --publish 2379:2379 \\ --publish 2380:2380 \\ --env ALLOW_NONE_AUTHENTICATION=yes \\ --env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-server:2379 \\ bitnami/etcd:latest 然后执行命令，执行成功后会弹出对应的访问地址\nbash 1 2 apiserver-boot build executables apiserver-boot run local running_in_cluster 构建镜像 需要先构建容器镜像，apiserver-boot build container --image \u0026lt;image\u0026gt; 这将生成代码，构建 apiserver 和controller二进制文件，然后构建容器映像。构建完成后还需要将对应的镜像push到仓库（可选）\nbash 1 2 3 4 apiserver-boot build config \\ --name \u0026lt;servicename\u0026gt; \\ --namespace \u0026lt;namespace to run in\u0026gt; \\ --image \u0026lt;image to run\u0026gt; 注，这个操作需要在支持Linux内核的环境下构建，wsl不具备内核功能故会报错，需要替换为wsl2，而工具是下载的，如果需要wsl1+Docker Desktop构建，需要自己修改\n构建配置 text 1 2 3 4 apiserver-boot build config \\ --name \u0026lt;servicename\u0026gt; \\ --namespace \u0026lt;namespace to run in\u0026gt; \\ --image \u0026lt;image to run\u0026gt; 构建配置的操作会执行以下几个步骤：\n在 \u0026lt;project/config/certificates 目录下创建一个 CA证书 在目录 \u0026lt;project/config/*.yaml 下生成kubernetes所需的资源清单。 注：\n实际上这个清单并不能完美适配任何环境，需要手动修改一下配置\n运行的Pod中包含apiserver与controller，如果使用kubebuilder创建的controller可以自行修改资源清单\n修改apiserver的配置 下面参数是有关于 AA 认证的参数\ntext 1 2 3 4 5 --proxy-client-cert-file=/etc/kubernetes/pki/firewalld.crt \\ --proxy-client-key-file=/etc/kubernetes/pki/firewalld.key \\ --requestheader-allowed-names=kube-apiserver-kubelet-client,firewalld.default.svc,firewalld-certificate-authority \\ --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt \\ --requestheader-extra-headers-prefix=X-Remote-Extra- \\ --requestheader-username-headers：用于存储用户名的标头 --requestheader-group-headers：用于存储组的标题 --requestheader-extra-headers-prefix：附加到所有额外标头的前缀 --proxy-client-key-file ：私钥文件 --proxy-client-cert-file：客户端证书文件 --requestheader-client-ca-file：签署客户端证书文件的 CA 的证书 --requestheader-allowed-names：签名客户端证书中的CN) 由以上信息得知，实际上 apiserver-boot 所生成的ca用不上，需要kubernetes自己的ca进行签署，这里简单提供两个命令，使用kubernetes集群证书进行颁发证书。这里kubernetes集群证书使用kubernetes-generator 生产的。这里根据这个ca再次生成用于 AA 认证的证书。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 openssl req -new \\ -key firewalld.key \\ -subj \u0026#34;/CN=firewalld.default.svc\u0026#34; \\ -config \u0026lt;(cat /etc/pki/tls/openssl.cnf \u0026lt;(printf \u0026#34;[aa]\\nsubjectAltName=DNS:firewalld, DNS:firewalld.default.svc, DNS:firewalld-certificate-authority, DNS:kubernetes.default.svc\u0026#34;)) \\ -out firewalld.csr openssl ca \\ -in firewalld.csr \\ -cert front-proxy-ca.crt \\ -keyfile front-proxy-ca.key \\ -out firewalld.crt \\ -days 3650 \\ -extensions aa \\ -extfile \u0026lt;(cat /etc/pki/tls/openssl.cnf \u0026lt;(printf \u0026#34;[aa]\\nsubjectAltName=DNS:firewalld, DNS:firewalld.default.svc, DNS:firewalld-certificate-authority, DNS:kubernetes.default.svc\u0026#34;)) 完成后重新生成所需的yaml资源清单即可，通过资源清单来测试下扩展的API\nyaml 1 2 3 4 5 6 7 8 apiVersion: firewalld.fedoraproject.org/v1 kind: PortRule metadata: name: portrule-example spec: name: \u0026#34;nginx\u0026#34; host: \u0026#34;10.0.0.3\u0026#34; port: 80 bash 1 2 3 4 5 $ kubectl apply -f http.yaml portrule.firewalld.fedoraproject.org/portrule-example created $ kubectl get portrule NAME CREATED AT portrule-example 2022-06-22T15:12:59Z 更详细的说明建议阅读下Reference，都是官方提供的详细说明文档\nReference aggregation layer\napiserver-builder doc\n","permalink":"https://www.161616.top/ch04-apiserver-aggregation/","summary":"Overview What is Kubernetes aggregation Kubernetes apiserver aggregation AA 是Kubernetes提供的一种扩展API的方法，目前并没有GA\nDifference between CRD and AA 众所周知，kubernetes扩展API的方法大概为三种：CRD、AA、手动扩展源码。根据CNCF分享中Min Kim说的AA更关注于实践，而用户无需了解底层的原理，这里使用过 kubebuilder， code-generator 的用户是很能体会到这点。官方也给出了CRD与AA的区别\nAPI Access Control Authentication CR: All strategies supported. Configured by root apiserver. AA: Supporting all root apiserver\u0026rsquo;s authenticating strategies but it has to be done via authentication token review api except for authentication proxy which will cause an extra cost of network RTT. Authorization CR: All strategies supported. Configured by root apiserver.","title":"扩展Kubernetes API的另一种方式 - APIServer aggregation"},{"content":"Overview Kubernetes中提供了多种自定义控制器的方式：\ncode-generator kubebuilder Operator Controller 作为CRD的核心，这里将解释如何使用 code-generator 来创建自定义的控制器，作为文章的案例，将完成一个 Firewalld Port 规则的控制器作为描述，通过 Kubernetes 规则来生成对应节点上的 iptables规则。\nPrerequisites CRD yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: ports.firewalld.fedoraproject.org spec: group: firewalld.fedoraproject.org scope: Namespaced names: plural: ports singular: port kind: PortRule shortNames: - fp versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: name: type: string port: type: integer host: type: string isPermanent: type: boolean code-generator 需要预先下载 code-generator 。因为这个工具不是必需要求的。\n注意，下载完成后需要将代码库的的分支更改为你目前使用的版本，版本的选择与client-go类似，如果使用master分支，会与当前的 Kubernetes 集群不兼容。\nbash 1 2 git clone https://github.com/kubernetes/code-generator cd code-generator; git checkout {version} # eg. v0.18.0 编写代码模板 要想使用 code-generator 生成控制器，必须准备三个文件 doc.go , register.go , types.go 。\ndoc.go 中声明了这个包全局内，要使用生成器的tag register.go 类似于kubernetes API，是将声明的类型注册到schema中 type.go 是需要具体声明对象类型 code-generator Tag说明 在使用 code-generator 时，就需要对 code-generator 的tag进行了解。code-generator 的tag是根据几个固定格式进行定义的，tag是 +k8s: + conversion 的组合，在仓库中 cmd 中的 *-gen* 文件夹就代表了 conversion 的替换位置。\n对于 client-gen的tag 参数可以在 code-generator\\cmd\\client-gen\\generators\\util\\tags.go 对于其他类型的使用方法，例如 deepcopy-gen ,可以在包 main.go中看注释说明 +k8s:openapi-gen=true：启用一个生成器 注：最终准备完成的文件（ doc.go , register.go , types.go）应该为：apis/example.com/v1 这种类型的\n需要遵循的是，将这些文件放在 \u0026lt;version\u0026gt; 目录中，例如 v1 。这里 v1, v1alpha1, 根据自己需求定义。\n开始填写文件内容 type.go go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package v1 import ( metav1 \u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34; ) // +genclient // +genclient:noStatus // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type Port struct { metav1.TypeMeta `json:\u0026#34;,inline\u0026#34;` // Standard object metadata. // +optional metav1.ObjectMeta `json:\u0026#34;metadata,omitempty\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=metadata\u0026#34;` // Specification of the desired behavior of the Deployment. // +optional Spec PortSpec `json:\u0026#34;spec,omitempty\u0026#34; protobuf:\u0026#34;bytes,2,opt,name=spec\u0026#34;` } // +k8s:deepcopy-gen=false type PortSpec struct { Name string `json:\u0026#34;name\u0026#34;` Host string `json:\u0026#34;host\u0026#34;` Port int `json:\u0026#34;port\u0026#34;` IsPermanent bool `json:\u0026#34;isPermanent\u0026#34;` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type PortList struct { metav1.TypeMeta `json:\u0026#34;,inline\u0026#34;` // +optional metav1.ListMeta `json:\u0026#34;metadata,omitempty\u0026#34;` Items []Port `json:\u0026#34;items\u0026#34;` } doc.go go 1 2 3 4 5 6 7 // +k8s:deepcopy-gen=package // +k8s:protobuf-gen=package // +k8s:openapi-gen=true // +groupName=firewalld.fedoraproject.org package v1 // import \u0026#34;k8s.io/api/firewalld/v1\u0026#34; register.go 这里是从 k8s.io/api 里任意一个复制的，例如 k8s.io/api/core/v1/register.go\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package v1 import ( metav1 \u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34; \u0026#34;k8s.io/apimachinery/pkg/runtime\u0026#34; \u0026#34;k8s.io/apimachinery/pkg/runtime/schema\u0026#34; ) // GroupName is the group name use in this package const GroupName = \u0026#34;firewalld.fedoraproject.org\u0026#34; // SchemeGroupVersion is group version used to register these objects var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: \u0026#34;v1\u0026#34;} // Resource takes an unqualified resource and returns a Group qualified GroupResource func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } var ( // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) localSchemeBuilder = \u0026amp;SchemeBuilder AddToScheme = localSchemeBuilder.AddToScheme ) // Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, \u0026amp;Port{}, \u0026amp;PortList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil } 生成所需文件 使用 code-generator 时，实际上就是使用这个库中的脚本 generate-groups.sh ，该脚本又四个参数\n第一个参数：使用那些生成器，就是 *.gen，用逗号分割，all表示使用全部 第二个参数：client（client-go中informer, lister等）生成的文件存放到哪里 第三个参数：api（api结构，k8s.io/api/） 生成的文件存放到哪里，可以和定义的文件为一个目录 第四个参数：定义group:version -output-base：输出包存放的根目录 -go-header-file：生成文件的头注释信息，这个是必要参数，除非生成失败 注：对于参数二，三，与-output-base，指定的路径，这里可以使用相对路径也可以使用go.mod中的定义的包名，对于使用相对路径而言，生成的文件中的import也将会为 \u0026ldquo;../../\u0026rdquo; 的格式\n一个完整的示例\nbash 1 2 3 4 5 6 ../code-generator/generate-groups.sh all \\ ../code-controller/client \\ ../code-controller/apis \\ firewalld:v1 \\ --output-base ../code-controller/ \\ --go-header-file ../code-generator/hack/boilerplate.go.txt Reference CRD Programming ","permalink":"https://www.161616.top/ch14-code-generator/","summary":"Overview Kubernetes中提供了多种自定义控制器的方式：\ncode-generator kubebuilder Operator Controller 作为CRD的核心，这里将解释如何使用 code-generator 来创建自定义的控制器，作为文章的案例，将完成一个 Firewalld Port 规则的控制器作为描述，通过 Kubernetes 规则来生成对应节点上的 iptables规则。\nPrerequisites CRD yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: ports.firewalld.fedoraproject.org spec: group: firewalld.fedoraproject.org scope: Namespaced names: plural: ports singular: port kind: PortRule shortNames: - fp versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: name: type: string port: type: integer host: type: string isPermanent: type: boolean code-generator 需要预先下载 code-generator 。因为这个工具不是必需要求的。","title":"kubernetes代码生成器 - code-generator"},{"content":"Overview 根据Kuberneter文档对Controller的描述，Controller在kubernetes中是负责协调的组件，根据设计模式可知，controller会不断的你的对象（如Pod）从当前状态与期望状态同步的一个过程。当然Controller会监听你的实际状态与期望状态。\nWriting Controllers go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 package main import ( \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;time\u0026#34; v1 \u0026#34;k8s.io/api/core/v1\u0026#34; \u0026#34;k8s.io/apimachinery/pkg/fields\u0026#34; utilruntime \u0026#34;k8s.io/apimachinery/pkg/util/runtime\u0026#34; \u0026#34;k8s.io/apimachinery/pkg/util/wait\u0026#34; \u0026#34;k8s.io/client-go/kubernetes\u0026#34; \u0026#34;k8s.io/client-go/rest\u0026#34; \u0026#34;k8s.io/client-go/tools/cache\u0026#34; \u0026#34;k8s.io/client-go/tools/clientcmd\u0026#34; \u0026#34;k8s.io/client-go/util/homedir\u0026#34; \u0026#34;k8s.io/client-go/util/workqueue\u0026#34; \u0026#34;k8s.io/klog\u0026#34; ) type Controller struct { lister cache.Indexer controller cache.Controller queue workqueue.RateLimitingInterface } func NewController(lister cache.Indexer, controller cache.Controller, queue workqueue.RateLimitingInterface) *Controller { return \u0026amp;Controller{ lister: lister, controller: controller, queue: queue, } } func (c *Controller) processItem() bool { item, quit := c.queue.Get() if quit { return false } defer c.queue.Done(item) fmt.Println(item) err := c.processWrapper(item.(string)) if err != nil { c.handleError(item.(string)) } return true } func (c *Controller) handleError(key string) { if c.queue.NumRequeues(key) \u0026lt; 3 { c.queue.AddRateLimited(key) return } c.queue.Forget(key) klog.Infof(\u0026#34;Drop Object %s in queue\u0026#34;, key) } func (c *Controller) processWrapper(key string) error { item, exists, err := c.lister.GetByKey(key) if err != nil { klog.Error(err) return err } if !exists { klog.Info(fmt.Sprintf(\u0026#34;item %v not exists in cache.\\n\u0026#34;, item)) } else { fmt.Println(item.(*v1.Pod).GetName()) } return err } func (c *Controller) Run(threadiness int, stopCh chan struct{}) { defer utilruntime.HandleCrash() defer c.queue.ShutDown() klog.Infof(\u0026#34;Starting custom controller\u0026#34;) go c.controller.Run(stopCh) if !cache.WaitForCacheSync(stopCh, c.controller.HasSynced) { utilruntime.HandleError(fmt.Errorf(\u0026#34;sync failed.\u0026#34;)) return } for i := 0; i \u0026lt; threadiness; i++ { go wait.Until(func() { for c.processItem() { } }, time.Second, stopCh) } \u0026lt;-stopCh klog.Info(\u0026#34;Stopping custom controller\u0026#34;) } func main() { var ( k8sconfig *string //使用kubeconfig配置文件进行集群权限认证 restConfig *rest.Config err error ) if home := homedir.HomeDir(); home != \u0026#34;\u0026#34; { k8sconfig = flag.String(\u0026#34;kubeconfig\u0026#34;, fmt.Sprintf(\u0026#34;%s/.kube/config\u0026#34;, home), \u0026#34;kubernetes auth config\u0026#34;) } k8sconfig = k8sconfig flag.Parse() if _, err := os.Stat(*k8sconfig); err != nil { panic(err) } if restConfig, err = rest.InClusterConfig(); err != nil { // 这里是从masterUrl 或者 kubeconfig传入集群的信息，两者选一 restConfig, err = clientcmd.BuildConfigFromFlags(\u0026#34;\u0026#34;, *k8sconfig) if err != nil { panic(err) } } restset, err := kubernetes.NewForConfig(restConfig) lister := cache.NewListWatchFromClient(restset.CoreV1().RESTClient(), \u0026#34;pods\u0026#34;, \u0026#34;default\u0026#34;, fields.Everything()) queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) indexer, controller := cache.NewIndexerInformer(lister, \u0026amp;v1.Pod{}, 0, cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { fmt.Println(\u0026#34;add \u0026#34;, obj.(*v1.Pod).GetName()) key, err := cache.MetaNamespaceKeyFunc(obj) if err == nil { queue.Add(key) } }, UpdateFunc: func(oldObj, newObj interface{}) { fmt.Println(\u0026#34;update\u0026#34;, newObj.(*v1.Pod).GetName()) if newObj.(*v1.Pod).Status.Conditions[0].Status == \u0026#34;True\u0026#34; { fmt.Println(\u0026#34;update: the Initialized Status\u0026#34;, newObj.(*v1.Pod).Status.Conditions[0].Status) } else { fmt.Println(\u0026#34;update: the Initialized Status \u0026#34;, newObj.(*v1.Pod).Status.Conditions[0].Status) fmt.Println(\u0026#34;update: the Initialized Reason \u0026#34;, newObj.(*v1.Pod).Status.Conditions[0].Reason) } if len(newObj.(*v1.Pod).Status.Conditions) \u0026gt; 1 { if newObj.(*v1.Pod).Status.Conditions[1].Status == \u0026#34;True\u0026#34; { fmt.Println(\u0026#34;update: the Ready Status\u0026#34;, newObj.(*v1.Pod).Status.Conditions[1].Status) } else { fmt.Println(\u0026#34;update: the Ready Status \u0026#34;, newObj.(*v1.Pod).Status.Conditions[1].Status) fmt.Println(\u0026#34;update: the Ready Reason \u0026#34;, newObj.(*v1.Pod).Status.Conditions[1].Reason) } if newObj.(*v1.Pod).Status.Conditions[2].Status == \u0026#34;True\u0026#34; { fmt.Println(\u0026#34;update: the PodCondition Status\u0026#34;, newObj.(*v1.Pod).Status.Conditions[2].Status) } else { fmt.Println(\u0026#34;update: the PodCondition Status \u0026#34;, newObj.(*v1.Pod).Status.Conditions[2].Status) fmt.Println(\u0026#34;update: the PodCondition Reason \u0026#34;, newObj.(*v1.Pod).Status.Conditions[2].Reason) } if newObj.(*v1.Pod).Status.Conditions[3].Status == \u0026#34;True\u0026#34; { fmt.Println(\u0026#34;update: the PodScheduled Status\u0026#34;, newObj.(*v1.Pod).Status.Conditions[3].Status) } else { fmt.Println(\u0026#34;update: the PodScheduled Status \u0026#34;, newObj.(*v1.Pod).Status.Conditions[3].Status) fmt.Println(\u0026#34;update: the PodScheduled Reason \u0026#34;, newObj.(*v1.Pod).Status.Conditions[3].Reason) } } }, DeleteFunc: func(obj interface{}) { fmt.Println(\u0026#34;delete \u0026#34;, obj.(*v1.Pod).GetName(), \u0026#34;Status \u0026#34;, obj.(*v1.Pod).Status.Phase) // 上面是事件函数的处理，下面是对workqueue的操作 key, err := cache.MetaNamespaceKeyFunc(obj) if err == nil { queue.Add(key) } }, }, cache.Indexers{}) c := NewController(indexer, controller, queue) stopCh := make(chan struct{}) stopCh1 := make(chan struct{}) c.Run(1, stopCh) defer close(stopCh) \u0026lt;-stopCh1 } 通过日志可以看出，Pod create后的步骤大概为4步：\nInitialized：初始化好后状态为Pending PodScheduled：然后调度 PodCondition Ready text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 add netbox default/netbox netbox update netbox status Pending to Pending update: the Initialized Status True update netbox status Pending to Pending update: the Initialized Status True update: the Ready Status False update: the Ready Reason ContainersNotReady update: the PodCondition Status False update: the PodCondition Reason ContainersNotReady update: the PodScheduled Status True update netbox status Pending to Running update: the Initialized Status True update: the Ready Status True update: the PodCondition Status True update: the PodScheduled Status True 大致上与 kubectl describe pod 看到的内容页相似\ntext 1 2 3 4 5 default-scheduler Successfully assigned default/netbox to master-machine Normal Pulling 85s kubelet Pulling image \u0026#34;cylonchau/netbox\u0026#34; Normal Pulled 30s kubelet Successfully pulled image \u0026#34;cylonchau/netbox\u0026#34; Normal Created 30s kubelet Created container netbox Normal Started 30s kubelet Started container netbox Reference controllers.md\n","permalink":"https://www.161616.top/ch12-controller/","summary":"Overview 根据Kuberneter文档对Controller的描述，Controller在kubernetes中是负责协调的组件，根据设计模式可知，controller会不断的你的对象（如Pod）从当前状态与期望状态同步的一个过程。当然Controller会监听你的实际状态与期望状态。\nWriting Controllers go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 package main import ( \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;time\u0026#34; v1 \u0026#34;k8s.","title":"手写一个kubernetes controller"},{"content":"Kubernetes的主节点或控制面板当中主要有三个组件，其中apiserver是整个系统的数据库，借助于Cluster Store（etcd）服务，来实现所有的包括用户所期望状态的定义，以及集群上资源当前状态的实时记录等。\netcd是分布式通用的K/V系统 KV Store ，可存储用户所定义的任何由KV Store所支持的可持久化的数据。它不仅仅被apiserver所使用，如flannel、calico二者也需要以etcd来保存当前应用程序对应的存储数据。 任何一个分布式应用程序几乎都会用到一个高可用的存储系统。\napiserver将etcd所提供的存储接口做了高度抽象，使用户通过apiserver来完成数据存取时，只能使用apiserver中所内建支持的数据范式。在某种情况之下，我们所期望管理的资源或存储对象在现有的Kubernetes资源无法满足需求时。\nOperator本身是建构在StatefulSet以及本身的基本Kubernetes资源之上，由开发者自定义的更高级的、更抽象的自定义资源类型。他可借助于底层的Pod、Service功能，再次抽象出新资源类型。更重要的是，整个集群本身可抽象成一个单一资源。\n为了实现更高级的资源管理，需要利用已有的基础资源类型，做一个更高级的抽象，来定义成更能符合用户所需要的、可单一管理的资源类型，而无需去分别管理每一个资源。\n在Kubernetes之上自定义资源一般被称为扩展Kubernetes所支持的资源类型，\n自定义资源类型 CRD Custom Resource Definition 自定义apiserver 修改APIServer源代码，改动内部的资源类型定义 CRD是kubernetes内建的资源类型，从而使得用户可以定义的不是具体的资源，而是资源类型，也是扩展Kubernetes最简单的方式。\nIntorduction CRD 什么是CRD 在 Kubernetes API 中，resources 是存储 API 对象集合的endpoint。例如，内置 Pod resource 包含 Pod 对象的集合。当我们想扩展API，原生的Kubernetes就不能满足我们的需求了，这时 CRD (CustomResourceDefinition) 就出现了。在 Kubernetes 中创建了 CRD 后，就可以像使用任何其他原生 Kubernetes 对象一样使用它，从而利用 Kubernetes 的所有功能、如安全性、API 服务、RBAC 等。\nKubernetes 1.7 之后增加了对 CRD 自定义资源二次开发能力来扩展 Kubernetes API，通过 CRD 我们可以向 Kubernetes API 中增加新资源类型，而不需要修改 Kubernetes 源码来创建自定义的 API server，该功能大大提高了 Kubernetes 的扩展能力。\n创建 CRD 前提条件： Kubernetes 服务器版本必须不低于版本 1.16\n再创建新的 CustomResourceDefinition（CRD）时，Kubernetes API 服务器会为指定的每一个版本生成一个 RESTful 的资源路径。（即定义一个Restful API）。CRD 可以是namespace作用域的，也可以是cluster作用域的，取决于 CRD 的 scope 字段设置。和其他现有的内置对象一样，删除一个namespace时，该namespace下的所有定制对象也会被删除。CustomResourceDefinition 本身是不受名字空间限制的，对所有名字空间可用。\n例如，编写一个firewall port 规则：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 # 1.16版本后固定格式 apiVersion: apiextensions.k8s.io/v1 # 类型crd kind: CustomResourceDefinition metadata: # 必须为name=spec.names.plural + spec.group name: ports.firewalld.fedoraproject.org spec: # api中的group # /apis/\u0026lt;group\u0026gt;/\u0026lt;version\u0026gt;/\u0026lt;plural\u0026gt; group: firewalld.fedoraproject.org # 此crd作用于 可选Namespaced|Cluster scope: Namespaced names: # 名字的复数形式，用于api plural: ports # 名称的单数形式。用于命令行 singular: port # 种类，资源清单类型 kind: PortRule # 名字简写，类似允许 CLI 上较短的字符串匹配的资源 shortNames: - fp versions: # 定义版本的类型 - name: v1 # 通过 served 标志来启用或禁止 served: true # 其中一个且只有一个版本必需被标记为存储版本 storage: true # 自定义资源的默认认证的模式 schema: # 使用的版本 openAPIV3Schema: # 定义一个参数为对象类型 type: object # 这个参数的类型 properties: # 参数属性spec spec: # spec属性的类型为对象 type: object # 对象属性 properties: # spec属性name name: # 类型为string type: string port: type: integer isPermanent: type: boolean 需要注意的是v1.16版本以后已经 GA了，使用的是v1版本，之前都是vlbeta1，定义规范有部分变化，所以要注意版本变化。\n这个地方的定义和我们定义普通的资源对象比较类似，我们说我们可以随意定义一个自定义的资源对象，但是在创建资源的时候，肯定不是任由我们随意去编写YAML文件的，当我们把上面的CRD文件提交给Kubernetes之后，Kubernetes会对我们提交的声明文件进行校验，从定义可以看出CRD是基于 OpenAPIv3 schem 进行规范的。当然这种校验只是对于字段的类型进行校验，比较初级，如果想要更加复杂的校验，这个时候就需要通过Kubernetes的admission webhook来实现了。关于校验的更多用法，可以前往官方文档查看。\n创建一个crd类型资源\nyaml 1 2 3 4 5 6 7 8 apiVersion: \u0026#34;firewalld.fedoraproject.org/v1\u0026#34; kind: PortRule metadata: name: http-port spec: name: \u0026#34;nginx\u0026#34; port: 80 isPermanent: false 查看创建的crd\ntext 1 2 3 $ kubectl get t NAME CREATED AT firewallds.port.fedoraproject.org 2022-06-19T09:27:09Z Reference CRD\nCRD Definition\n","permalink":"https://www.161616.top/ch13-crd/","summary":"Kubernetes的主节点或控制面板当中主要有三个组件，其中apiserver是整个系统的数据库，借助于Cluster Store（etcd）服务，来实现所有的包括用户所期望状态的定义，以及集群上资源当前状态的实时记录等。\netcd是分布式通用的K/V系统 KV Store ，可存储用户所定义的任何由KV Store所支持的可持久化的数据。它不仅仅被apiserver所使用，如flannel、calico二者也需要以etcd来保存当前应用程序对应的存储数据。 任何一个分布式应用程序几乎都会用到一个高可用的存储系统。\napiserver将etcd所提供的存储接口做了高度抽象，使用户通过apiserver来完成数据存取时，只能使用apiserver中所内建支持的数据范式。在某种情况之下，我们所期望管理的资源或存储对象在现有的Kubernetes资源无法满足需求时。\nOperator本身是建构在StatefulSet以及本身的基本Kubernetes资源之上，由开发者自定义的更高级的、更抽象的自定义资源类型。他可借助于底层的Pod、Service功能，再次抽象出新资源类型。更重要的是，整个集群本身可抽象成一个单一资源。\n为了实现更高级的资源管理，需要利用已有的基础资源类型，做一个更高级的抽象，来定义成更能符合用户所需要的、可单一管理的资源类型，而无需去分别管理每一个资源。\n在Kubernetes之上自定义资源一般被称为扩展Kubernetes所支持的资源类型，\n自定义资源类型 CRD Custom Resource Definition 自定义apiserver 修改APIServer源代码，改动内部的资源类型定义 CRD是kubernetes内建的资源类型，从而使得用户可以定义的不是具体的资源，而是资源类型，也是扩展Kubernetes最简单的方式。\nIntorduction CRD 什么是CRD 在 Kubernetes API 中，resources 是存储 API 对象集合的endpoint。例如，内置 Pod resource 包含 Pod 对象的集合。当我们想扩展API，原生的Kubernetes就不能满足我们的需求了，这时 CRD (CustomResourceDefinition) 就出现了。在 Kubernetes 中创建了 CRD 后，就可以像使用任何其他原生 Kubernetes 对象一样使用它，从而利用 Kubernetes 的所有功能、如安全性、API 服务、RBAC 等。\nKubernetes 1.7 之后增加了对 CRD 自定义资源二次开发能力来扩展 Kubernetes API，通过 CRD 我们可以向 Kubernetes API 中增加新资源类型，而不需要修改 Kubernetes 源码来创建自定义的 API server，该功能大大提高了 Kubernetes 的扩展能力。\n创建 CRD 前提条件： Kubernetes 服务器版本必须不低于版本 1.","title":"使用CRD扩展Kubernetes API"},{"content":"OSI Model OSI 七层网络模型如下（由下到上）：\n应用层 Application layer ：直接接触用户数据的层。软件应用程序依靠应用层发起通信。这里的应用值得是协议而不是客户端软件；应用层协议包括 HTTP, SMTP, FTP, DNS,Telnet, etc.. 表示层 Presentation layer：表示层充当角色为网络数据转换器，负责完成数据转换，加密和压缩 会话层 Session layer：负责建立、管理和终止两个设备之间的通信 传输层 Transport layer：负责两个设备间的端到端通信。包括从会话层提取数据，将数据分解为多个区块（称为数据段）；传输层协议包括，TCP, UDP 网络层 Network layer：负责管理网络地址，定位设备，决定路由，通俗来讲是负责*\u0026ldquo;不同\u0026rdquo;*网络之间的传输，也就是路由功能；网络层协议包括 IP,ARP,ICMP；代表设备 3 layer swtich, router, firewall。相应就代表对应网络协议也是三层的，如RIP, OSPF, BGP 数据链路层 Data link layer：数据链路层负责*\u0026ldquo;同一\u0026rdquo;*网络上设备之间的数据传输；该层协议包括 Ethernet, PPP(Point-to-Point Protocol)；代表设备 Switch,Bridges，同样的MAC地址也是该层的 物理层 Physical layer：该层表示参与数据传输的物理设备，如网线，同时还负责将数据转换为位流，也就是由 1 和 0 构成的字符串。 图：OSI七层模型 Source：https://www.cloudflare.com/zh-cn/learning/ddos/glossary/open-systems-interconnection-model-osi/ MAC MAC地址介绍 MAC (Media Access Control) 地址用来定义网络设备的位置，由48比特长，12位的16进制组成，其中从左到右，0-23bit为厂商想IETF等机构申请用来标识厂商的代码OUI Organizationally-Unique Identifier，24-47bit由厂商自行分配，是厂商制造所有网卡的唯一编号。如00-50-56-C0-00-08\nMAC地址类型 MAC地址分为三种类型：\n物理MAC地址：Mac地址唯一的标识了以太网的一个终端，该地址为全球唯一的硬件地址。 广播(broadcast) MAC地址：每个比特都是 1 的 MAC 地址。广播 MAC 地址是组播 MAC 地址的一个特例。11111111-11111111-11111111-11111111-11111111-11111111 16进制表示为 FF-FF-FF-FF-FF-FF。 组播(multicast) MAC地址：第一个字节的最低位是 1 的 MAC 地址。二进制表示为 xxxxxxx1-xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx ；16进制表示为01-00-00-00-00-00。如 a5-a9-a6-aa-5a-a6 这个mac地址的第一个字节的最低位 16进制a5 转换为二进制为10100101 最后一位为1就是组播MAC地址。 单播 (unicast) MAC 地址：第一个字节的最低位是 0 的 MAC 地址 xxxxxxx0-xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx。 静态MAC地址 由用户通过命令配置的静态转发的MAC地址，静态MAC地址和动态MAC地址的功能不同，静态地址一旦被加入，该地址在删除之前将一直有效，不受最大老化时间的限制\n动态MAC地址 由交换机从接受到报文自动学习到的MAC地址，当端口收到一个报文时，会查找报文的源MAC地址是否存在于MAC地址表中，如果不存在则会将相应的端口、VLAN和源MAC地址关联起来，并保存到MAC地址表中，动态MAC地址在达到一定老化时间后，会被老化删除，但如果该地址在老化时间内被正确使用过，则会重新激活地址的老化时间。\n过滤MAC地址、黑洞MAC地址 有用户通过命令配置的静态过滤MAC，当网关接收到的报文中，源或目的MAC地址为过滤MAC地址，则直接丢弃该报文。\nIP地址 IP地址是在计算机网络中用来标识一个设备的一组数字，IPv4是由32位二进制数值组成，单为了便于用户识别记忆，采用了点分十进制表示法，这种表示法的IPv4地址有4个点分十进制证书来标识，每个十进制证书对应一个字节。\nIPV4 IPv4地址有如下两个部分组成：\n网络段 Net-id：用来标识一个网络。\n主机段 Host-id：用来区分一个网络内的不同主机，对于网络号相同的设备，无论实际所处的物理位置如何，他们都处在同一个网络中。\nIP地址的分类 分类网络 classful addressing，描述互联网网络的一个术语，将IPv4的IP地址分为5类，每个类别地址都由他们前三位标识，定义了网络的大小或者类型。\n类型 前缀位 网络地址位数 剩余的位数 网络数 每个网络的主机数 A类地址 0 8 24 128 16,777,214 B类地址 10 16 16 16,384 65,534 C类地址 110 24 8 2,097,152 254 D类地址（群播） 1110 未定义 未定义 未定义 未定义 E类地址（保留） 1111 未定义 未定义 未定义 未定义 可用的主机地址总是 $2^n - 2$（ n 是所用的位数，减2是因为第一个和最后一个地址都是无效的）。因此，对于用8位来表示主机地址的C类地址来说，主机数就是254。\n分类 前缀码 开始地址 结束地址 对应CIDR修饰 默认子网掩码 A类地址 0 0.0.0.0 127.255.255.255 /8 255.0.0.0 B类地址 10 128.0.0.0 191.255.255.255 /16 255.255.0.0 C类地址 110 192.0.0.0 223.255.255.255 /24 255.255.255.0 D类地址 （群播） 1110 224.0.0.0 239.255.255.255 /4 未定义 E类地址 （保留） 1111 240.0.0.0 255.255.255.255 /4 未定义 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 A类地址 0. 0. 0. 0 = 00000000.00000000.00000000.00000000 127.255.255.255 = 01111111.11111111.11111111.11111111 0nnnnnnn.HHHHHHHH.HHHHHHHH.HHHHHHHH B类地址 128. 0. 0. 0 = 10000000.00000000.00000000.00000000 191.255.255.255 = 10111111.11111111.11111111.11111111 10nnnnnn.nnnnnnnn.HHHHHHHH.HHHHHHHH C类地址 192. 0. 0. 0 = 11000000.00000000.00000000.00000000 223.255.255.255 = 11011111.11111111.11111111.11111111 110nnnnn.nnnnnnnn.nnnnnnnn.HHHHHHHH D类地址 224. 0. 0. 0 = 11100000.00000000.00000000.00000000 239.255.255.255 = 11101111.11111111.11111111.11111111 1110XXXX.XXXXXXXX.XXXXXXXX.XXXXXXXX E类地址 240. 0. 0. 0 = 11110000.00000000.00000000.00000000 255.255.255.255 = 11111111.11111111.11111111.11111111 1111XXXX.XXXXXXXX.XXXXXXXX.XXXXXXXX 专用 IP 地址 专用 IP 地址是内部地址，不能通过互联网路由，例如 RFC 1918 地址。所有专用 IP 地址都是内部 IP 地址；但是，并非所有内部 IP 地址都是专用 IP 地址。\n因特网域名分配组织IANA组织（Internet Assigned Numbers Authority）保留了以下三个IP地址块用于私有网络。\n10.0.0.0 - 10.255.255.255 (10/8比特前缀)\n172.16.0.0 - 172.31.255.255 (172.16/12比特前缀)\n192.168.0.0 - 192.168.255.255 (192.168/16比特前缀)\nVLSM 可变长子网掩码 VLSM ，是为了有效使用无类别域间路由(CIDR)和路由汇聚(route summary)来控制路由表的大小，它是网络管理员常用的IP寻址技术，VLSM就是其中的常用方式，可以对子网进行层次化编址，以便最有效的利用现有的地址空间。\nVLSM的优点：\nIP地址的使用更加有效 应用路由汇总时，有更好的性能 与其他路由器的拓扑变化隔离 如 192.168.1.0/24 其二进制地址为\n11000000.10101000.00000001.00000000\n⑴ 确定子网掩码的长度为24；\n⑵ 确定子网下的主机可用地址范围 $2^n - 2$ 即为 192.168.1.1 ~ 192.168.1.254 （第一个可用IP和最后一个可用IP）；\n⑶ 确定网络地址（主机位全为0 192.168.0.0）和广播地址（主机位全为1 192.168.0.255）不能分配计算机主机。\nVLSM允许把子网继续划分为更小的网络\n子网掩码 子网数 主机数 IP数 /25255.255.255.1| 0000000255.255.255.128 2\n192.168.0.0 ~ 192.168.0.127\n192.168.0.128 ~ 192.168.0.254 $2^n - 2=128-2=126$ 128 /26255.255.255.11 | 000000255.255.255.192 4192.168.0.0 ~ 192.168.0.63192.168.0.64 ~ 192.168.0.127192.168.0.128 ~ 192.168.0.191192.168.0.192 ~ 192.168.0.255 $2^n - 2=64-2=62$ 64 /27255.255.255.111 | 00000255.255.255.224 8192.168.0.0 ~ 192.168.0.31192.168.0.32 ~ 192.168.0.63192.168.0.64 ~ 192.168.0.95192.168.0.96 ~ 192.168.0.127192.168.0.128 ~ 192.168.0.159192.168.0.160 ~ 192.168.0.191192.168.0.192 ~ 192.168.0.223192.168.0.224 ~ 192.168.0.255 $2^n - 2=32-2=30$ 32 /28255.255.255.1111 | 0000255.255.255.240 16192.168.0.0 ~ 192.168.0.15192.168.0.16 ~ 192.168.0.31\u0026hellip;\n192.168.0.224 ~ 192.168.0.239192.168.0.240 ~ 192.168.0.255 $2^n - 2=16-2=14$ 16 /29255.255.255.11111 | 000255.255.255.248 32192.168.0.0 ~ 192.168.0.7192.168.0.8 ~ 192.168.0.15\u0026hellip;192.168.0.232 ~ 192.168.0.247192.168.0.248 ~ 192.168.0.255 $2^n - 2=8-2=6$ 8 /30255.255.255.111111 | 00255.255.255.252 64192.168.0.0 ~ 192.168.0.3192.168.0.4 ~ 192.168.0.7\u0026hellip;192.168.0.248 ~ 192.168.0.251192.168.0.252 ~ 192.168.0.255 $2^n - 2=4-2=2$ 4 /31255.255.255.1111111 | 0255.255.255.254 作为网段是无效的，一个网络地址和一个广播地址，剩余可用IP数量为0，但是作为IP段这个段有两个IP地址，一般指当前位数与后一位的IP如：\n192.168.0.1/31 即代表\n192.168.0.1 192.168.0.2 $2^n - 2=2-2=0$ 2 /32255.255.255.11111111255.255.255.255 作为网段是无效的，网络地址和广播地址都不够分配，但是作为IP段这个段有两个IP地址，一般指当前IP本身，如：192.168.0.1/32 即代表192.168.0.1 1 CIDR [1] 无类别域间路由 Classless Inter-Domain Routing，是一个按位的、基于前缀的，用于解释IP地址的标准。通俗来讲是通过把多个地址块组合到一个路由表表项而使得路由更加方便。\n例如，指定一个CIDR块为10.10.1.32/27，则根据CIDR比特位比较，10.10.1.44是属于该块，但10.10.1.90则不是，如下图所示：\n图：CIDR块 Source：https://zh.wikipedia.org/zh-cn/%E6%97%A0%E7%B1%BB%E5%88%AB%E5%9F%9F%E9%97%B4%E8%B7%AF%E7%94%B1 那么这个IP地址表计算方式为：$2^n-2 = 32-2=30$，10.10.1.33 ~ 10.10.1.62 其中网络地址（主机位全为0 10.10.1.32, 10.10.1.111| 00000 ）和广播地址（主机位全为1 10.10.1.63，10.10.1.111| 10000 ）不能分配计算机主机。\nCIDR与VLSM的区别 CIDR 是把几个标准网络合成一个大的网络\nVLSM 是把一个标准网络分成几个小型网络(子网)\nCIDR 是子网掩码往左边移了，VLSM 是子网掩码往右边移了\nReference ​[1] CIDR\n","permalink":"https://www.161616.top/osi-network-basics/","summary":"OSI Model OSI 七层网络模型如下（由下到上）：\n应用层 Application layer ：直接接触用户数据的层。软件应用程序依靠应用层发起通信。这里的应用值得是协议而不是客户端软件；应用层协议包括 HTTP, SMTP, FTP, DNS,Telnet, etc.. 表示层 Presentation layer：表示层充当角色为网络数据转换器，负责完成数据转换，加密和压缩 会话层 Session layer：负责建立、管理和终止两个设备之间的通信 传输层 Transport layer：负责两个设备间的端到端通信。包括从会话层提取数据，将数据分解为多个区块（称为数据段）；传输层协议包括，TCP, UDP 网络层 Network layer：负责管理网络地址，定位设备，决定路由，通俗来讲是负责*\u0026ldquo;不同\u0026rdquo;*网络之间的传输，也就是路由功能；网络层协议包括 IP,ARP,ICMP；代表设备 3 layer swtich, router, firewall。相应就代表对应网络协议也是三层的，如RIP, OSPF, BGP 数据链路层 Data link layer：数据链路层负责*\u0026ldquo;同一\u0026rdquo;*网络上设备之间的数据传输；该层协议包括 Ethernet, PPP(Point-to-Point Protocol)；代表设备 Switch,Bridges，同样的MAC地址也是该层的 物理层 Physical layer：该层表示参与数据传输的物理设备，如网线，同时还负责将数据转换为位流，也就是由 1 和 0 构成的字符串。 图：OSI七层模型 Source：https://www.cloudflare.com/zh-cn/learning/ddos/glossary/open-systems-interconnection-model-osi/ MAC MAC地址介绍 MAC (Media Access Control) 地址用来定义网络设备的位置，由48比特长，12位的16进制组成，其中从左到右，0-23bit为厂商想IETF等机构申请用来标识厂商的代码OUI Organizationally-Unique Identifier，24-47bit由厂商自行分配，是厂商制造所有网卡的唯一编号。如00-50-56-C0-00-08\nMAC地址类型 MAC地址分为三种类型：\n物理MAC地址：Mac地址唯一的标识了以太网的一个终端，该地址为全球唯一的硬件地址。 广播(broadcast) MAC地址：每个比特都是 1 的 MAC 地址。广播 MAC 地址是组播 MAC 地址的一个特例。11111111-11111111-11111111-11111111-11111111-11111111 16进制表示为 FF-FF-FF-FF-FF-FF。 组播(multicast) MAC地址：第一个字节的最低位是 1 的 MAC 地址。二进制表示为 xxxxxxx1-xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx ；16进制表示为01-00-00-00-00-00。如 a5-a9-a6-aa-5a-a6 这个mac地址的第一个字节的最低位 16进制a5 转换为二进制为10100101 最后一位为1就是组播MAC地址。 单播 (unicast) MAC 地址：第一个字节的最低位是 0 的 MAC 地址 xxxxxxx0-xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx。 静态MAC地址 由用户通过命令配置的静态转发的MAC地址，静态MAC地址和动态MAC地址的功能不同，静态地址一旦被加入，该地址在删除之前将一直有效，不受最大老化时间的限制","title":"OSI模型与IP协议"},{"content":"通用队列 在kubernetes中，使用go的channel无法满足kubernetes的应用场景，如延迟、限速等；在kubernetes中存在三种队列通用队列 common queue ，延迟队列 delaying queue，和限速队列 rate limiters queue\nInferface Interface作为所有队列的一个抽象定义\ngo 1 2 3 4 5 6 7 8 type Interface interface { Add(item interface{}) Len() int Get() (item interface{}, shutdown bool) Done(item interface{}) ShutDown() ShuttingDown() bool } Implementation go 1 2 3 4 5 6 7 8 9 10 11 12 13 type Type struct { // 一个work queue queue []t // queue用slice做存储 dirty set // 脏位，定义了需要处理的元素，类似于操作系统，表示已修改但为写入 processing set // 当前正在处理的元素集合 cond *sync.Cond shuttingDown bool metrics queueMetrics unfinishedWorkUpdatePeriod time.Duration clock clock.Clock } type empty struct{} type t interface{} // t queue中的元素 type set map[t]empty // dirty 和 processing中的元素 可以看到其中核心属性就是 queue , dirty , processing\n延迟队列 在研究优先级队列前，需要对 Heap 有一定的了解，因为delay queue使用了 heap 做延迟队列\nHeap Heap 是基于树属性的特殊数据结构；heap是一种完全二叉树类型，具有两种类型：\n如：B 是 A 的子节点，则 $key(A) \\geq key(B)$ 。这就意味着具有最大Key的元素始终位于根节点，这类Heap称为最大堆 MaxHeap。 父节点的值小于或等于其左右子节点的值叫做 MinHeap 二叉堆的存储规则：\n每个节点包含的元素大于或等于该节点子节点的元素。 树是完全二叉树。 那么下列图片中，那个是堆\nheap的实现\n实例：向左边添加一个值为42的元素的过程 步骤一：将新元素放入堆中的第一个可用位置。这将使结构保持为完整的二叉树，但它可能不再是堆，因为新元素可能具有比其父元素更大的值。\n步骤二：如果新元素的值大于父元素，将新元素与父元素交换，直到达到新元素到根，或者新元素大于等于其父元素的值时将停止\n这种过程被称为 向上调整 （reheapification upward）\n实例：移除根 步骤一：将根元素复制到用于返回值的变量中，将最深层的最后一个元素复制到根，然后将最后一个节点从树中取出。该元素称为 out-of-place 。\n步骤二：而将异位元素与其最大值的子元素交换，并返回在步骤1中保存的值。\n这个过程被称为向下调整 （reheapification downward）\n优先级队列 优先级队列的行为：\n元素被放置在队列中，然后被取出。 优先级队列中的每个元素都有一个关联的数字，称为优先级。 当元素离开优先级队列时，最高优先级的元素最先离开。 如何实现的：\n在优先级队列中，heap的每个节点都包含一个元素以及元素的优先级，并且维护树以便它遵循使用元素的优先级来比较节点的堆存储规则：\n每个节点包含的元素的优先级大于或等于该节点子元素的优先级。 树是完全二叉树。 实现的代码：golang priorityQueue\nClient-go 的延迟队列 在Kubernetes中对 delaying queue 的设计非常精美，通过使用 heap 实现的延迟队列，加上kubernetes中的通过队列，完成了延迟队列的功能。\ngo 1 2 3 4 5 // 注释中给了一个hot-loop热循环，通过这个loop实现了delaying type DelayingInterface interface { Interface // 继承了workqueue的功能 AddAfter(item interface{}, duration time.Duration) // 在time后将内容添加到工作队列中 } 具体实现了 DelayingInterface 的实例\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type delayingType struct { Interface // 通用的queue clock clock.Clock // 对比的时间 ，包含一些定时器的功能 type Clock interface { PassiveClock type PassiveClock interface { Now() time.Time Since(time.Time) time.Duration } After(time.Duration) \u0026lt;-chan time.Time NewTimer(time.Duration) Timer Sleep(time.Duration) NewTicker(time.Duration) Ticker } stopCh chan struct{} // 停止loop stopOnce sync.Once // 保证退出只会触发一次 heartbeat clock.Ticker // 一个定时器，保证了loop的最大空事件等待时间 waitingForAddCh chan *waitFor // 普通的chan，用来接收数据插入到延迟队列中 metrics retryMetrics // 重试的指数 } 那么延迟队列的整个数据结构如下图所示\n而上面部分也说到了，这个延迟队列的核心就是一个优先级队列，而优先级队列又需要满足：\n优先级队列中的每个元素都有一个关联的数字，称为优先级。 当元素离开优先级队列时，最高优先级的元素最先离开。 而 waitFor 就是这个优先级队列的数据结构\ngo 1 2 3 4 5 type waitFor struct { data t // 数据 readyAt time.Time // 加入工作队列的时间 index int // 优先级队列中的索引 } 而 waitForPriorityQueue 是对 container/heap/heap.go.Inferface 的实现，其数据结构就是使最小 readyAt 位于Root 的一个 MinHeap\ngo 1 2 3 4 5 type Interface interface { sort.Interface Push(x interface{}) // add x as element Len() Pop() interface{} // remove and return element Len() - 1. } 而这个的实现是 waitForPriorityQueue\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 type waitForPriorityQueue []*waitFor func (pq waitForPriorityQueue) Len() int { return len(pq) } // 这个也是最重要的一个，就是哪个属性是排序的关键，也是heap.down和heap.up中使用的 func (pq waitForPriorityQueue) Less(i, j int) bool { return pq[i].readyAt.Before(pq[j].readyAt) } func (pq waitForPriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] pq[i].index = i pq[j].index = j } // push 和pop 必须使用heap.push 和heap.pop func (pq *waitForPriorityQueue) Push(x interface{}) { n := len(*pq) item := x.(*waitFor) item.index = n *pq = append(*pq, item) } func (pq *waitForPriorityQueue) Pop() interface{} { n := len(*pq) item := (*pq)[n-1] item.index = -1 *pq = (*pq)[0:(n - 1)] return item } // Peek returns the item at the beginning of the queue, without removing the // item or otherwise mutating the queue. It is safe to call directly. func (pq waitForPriorityQueue) Peek() interface{} { return pq[0] } 而整个延迟队列的核心就是 waitingLoop，作为了延迟队列的主要逻辑，检查 waitingForAddCh 有没有要延迟的内容，取出延迟的内容放置到 Heap 中；以及保证最大的阻塞周期\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 func (q *delayingType) waitingLoop() { defer utilruntime.HandleCrash() never := make(\u0026lt;-chan time.Time) // 作为占位符 var nextReadyAtTimer clock.Timer // 最近一个任务要执行的定时器 waitingForQueue := \u0026amp;waitForPriorityQueue{} // 优先级队列，heap heap.Init(waitingForQueue) waitingEntryByData := map[t]*waitFor{} // 检查是否反复添加 for { if q.Interface.ShuttingDown() { return } now := q.clock.Now() for waitingForQueue.Len() \u0026gt; 0 { entry := waitingForQueue.Peek().(*waitFor) if entry.readyAt.After(now) { break // 时间没到则不处理 } entry = heap.Pop(waitingForQueue).(*waitFor) // 从优先级队列中取出一个 q.Add(entry.data) // 添加到延迟队列中 delete(waitingEntryByData, entry.data) // 删除map表中的数据 } // 如果存在数据则设置最近一个内容要执行的定时器 nextReadyAt := never if waitingForQueue.Len() \u0026gt; 0 { if nextReadyAtTimer != nil { nextReadyAtTimer.Stop() } entry := waitingForQueue.Peek().(*waitFor) // 窥视[0]和值 nextReadyAtTimer = q.clock.NewTimer(entry.readyAt.Sub(now)) // 创建一个定时器 nextReadyAt = nextReadyAtTimer.C() } select { case \u0026lt;-q.stopCh: // 退出 return case \u0026lt;-q.heartbeat.C(): // 多久没有任何动作时重新一次循环 case \u0026lt;-nextReadyAt: // 如果有元素时间到了，则继续执行循环，处理上面添加的操作 case waitEntry := \u0026lt;-q.waitingForAddCh: if waitEntry.readyAt.After(q.clock.Now()) { // 时间没到，是用readyAt和now对比time.Now // 添加到延迟队列中，有两个 waitingEntryByData waitingForQueue insert(waitingForQueue, waitingEntryByData, waitEntry) } else { q.Add(waitEntry.data) } drained := false // 保证可以取完q.waitingForAddCh // addafter for !drained { select { // 这里是一个有buffer的队列，需要保障这个队列读完 case waitEntry := \u0026lt;-q.waitingForAddCh: if waitEntry.readyAt.After(q.clock.Now()) { insert(waitingForQueue, waitingEntryByData, waitEntry) } else { q.Add(waitEntry.data) } default: // 保证可以退出，但限制于上一个分支的0~n的读取 // 如果上一个分支阻塞，则为没有数据就是取尽了，走到这个分支 // 如果上个分支不阻塞则读取到上个分支阻塞为止，代表阻塞，则走default退出 drained = true } } } } } 限速队列 限速队列 RateLimiting 是在优先级队列是在延迟队列的基础上进行扩展的一个队列\ngo 1 2 3 4 5 6 7 8 9 type RateLimitingInterface interface { DelayingInterface // 继承延迟队列 // 在限速器准备完成后（即合规后）添加条目到队列中 AddRateLimited(item interface{}) // drop掉条目，无论成功或失败 Forget(item interface{}) // 被重新放入队列中的次数 NumRequeues(item interface{}) int } 可以看到一个限速队列的抽象对应只要满足了 AddRateLimited() , Forget() , NumRequeues() 的延迟队列都是限速队列。看了解规则之后，需要对具体的实现进行分析。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type rateLimitingType struct { DelayingInterface rateLimiter RateLimiter } func (q *rateLimitingType) AddRateLimited(item interface{}) { q.DelayingInterface.AddAfter(item, q.rateLimiter.When(item)) } func (q *rateLimitingType) NumRequeues(item interface{}) int { return q.rateLimiter.NumRequeues(item) } func (q *rateLimitingType) Forget(item interface{}) { q.rateLimiter.Forget(item) } rateLimitingType 则是对抽象规范 RateLimitingInterface 的实现，可以看出是在延迟队列的基础上增加了一个限速器 RateLimiter\ngo 1 2 3 4 5 6 7 8 9 type RateLimiter interface { // when决定等待多长时间 When(item interface{}) time.Duration // drop掉item // or for success, we\u0026#39;ll stop tracking it Forget(item interface{}) // 重新加入队列中的次数 NumRequeues(item interface{}) int } 抽象限速器的实现，有 BucketRateLimiter , ItemBucketRateLimiter , ItemExponentialFailureRateLimiter , ItemFastSlowRateLimiter , MaxOfRateLimiter ，下面对这些限速器进行分析\nBucketRateLimiter BucketRateLimiter 是实现 rate.Limiter 与 抽象 RateLimiter 的一个令牌桶，初始化时通过 workqueue.DefaultControllerRateLimiter() 进行初始化。\ngo 1 2 3 4 5 6 7 func DefaultControllerRateLimiter() RateLimiter { return NewMaxOfRateLimiter( NewItemExponentialFailureRateLimiter(5*time.Millisecond, 1000*time.Second), // 10 qps, 100 bucket size. This is only for retry speed and its only the overall factor (not per item) \u0026amp;BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)}, ) } 更多关于令牌桶算法可以参考这里\nItemBucketRateLimiter ItemBucketRateLimiter 是作为列表存储每个令牌桶的实现，每个key都是单独的限速器\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type ItemBucketRateLimiter struct { r rate.Limit burst int limitersLock sync.Mutex limiters map[interface{}]*rate.Limiter } func NewItemBucketRateLimiter(r rate.Limit, burst int) *ItemBucketRateLimiter { return \u0026amp;ItemBucketRateLimiter{ r: r, burst: burst, limiters: make(map[interface{}]*rate.Limiter), } } ItemExponentialFailureRateLimiter 如名所知 ItemExponentialFailureRateLimiter 限速器是一个错误指数限速器，根据错误的次数，将指数用于delay的时长，指数的计算公式为：$baseDelay\\times2^{}$。 可以看出When绝定了流量整形的delay时间，根据错误次数为指数进行延长重试时间\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 type ItemExponentialFailureRateLimiter struct { failuresLock sync.Mutex failures map[interface{}]int // 失败的次数 baseDelay time.Duration // 延迟基数 maxDelay time.Duration // 最大延迟 } func (r *ItemExponentialFailureRateLimiter) When(item interface{}) time.Duration { r.failuresLock.Lock() defer r.failuresLock.Unlock() exp := r.failures[item] r.failures[item] = r.failures[item] + 1 // The backoff is capped such that \u0026#39;calculated\u0026#39; value never overflows. backoff := float64(r.baseDelay.Nanoseconds()) * math.Pow(2, float64(exp)) if backoff \u0026gt; math.MaxInt64 { return r.maxDelay } calculated := time.Duration(backoff) if calculated \u0026gt; r.maxDelay { return r.maxDelay } return calculated } func (r *ItemExponentialFailureRateLimiter) NumRequeues(item interface{}) int { r.failuresLock.Lock() defer r.failuresLock.Unlock() return r.failures[item] } func (r *ItemExponentialFailureRateLimiter) Forget(item interface{}) { r.failuresLock.Lock() defer r.failuresLock.Unlock() delete(r.failures, item) } ItemFastSlowRateLimiter ItemFastSlowRateLimiter ，限速器先快速重试一定次数，然后慢速重试\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 type ItemFastSlowRateLimiter struct { failuresLock sync.Mutex failures map[interface{}]int maxFastAttempts int // 最大尝试次数 fastDelay time.Duration // 快的速度 slowDelay time.Duration // 慢的速度 } func NewItemFastSlowRateLimiter(fastDelay, slowDelay time.Duration, maxFastAttempts int) RateLimiter { return \u0026amp;ItemFastSlowRateLimiter{ failures: map[interface{}]int{}, fastDelay: fastDelay, slowDelay: slowDelay, maxFastAttempts: maxFastAttempts, } } func (r *ItemFastSlowRateLimiter) When(item interface{}) time.Duration { r.failuresLock.Lock() defer r.failuresLock.Unlock() r.failures[item] = r.failures[item] + 1 // 当错误次数没超过快速的阈值使用快速，否则使用慢速 if r.failures[item] \u0026lt;= r.maxFastAttempts { return r.fastDelay } return r.slowDelay } func (r *ItemFastSlowRateLimiter) NumRequeues(item interface{}) int { r.failuresLock.Lock() defer r.failuresLock.Unlock() return r.failures[item] } func (r *ItemFastSlowRateLimiter) Forget(item interface{}) { r.failuresLock.Lock() defer r.failuresLock.Unlock() delete(r.failures, item) } MaxOfRateLimiter MaxOfRateLimiter 是返回限速器列表中，延迟最大的那个限速器\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 type MaxOfRateLimiter struct { limiters []RateLimiter } func (r *MaxOfRateLimiter) When(item interface{}) time.Duration { ret := time.Duration(0) for _, limiter := range r.limiters { curr := limiter.When(item) if curr \u0026gt; ret { ret = curr } } return ret } func NewMaxOfRateLimiter(limiters ...RateLimiter) RateLimiter { return \u0026amp;MaxOfRateLimiter{limiters: limiters} } func (r *MaxOfRateLimiter) NumRequeues(item interface{}) int { ret := 0 // 找到列表內所有的NumRequeues（失败的次数），以最多次的为主。 for _, limiter := range r.limiters { curr := limiter.NumRequeues(item) if curr \u0026gt; ret { ret = curr } } return ret } func (r *MaxOfRateLimiter) Forget(item interface{}) { for _, limiter := range r.limiters { limiter.Forget(item) } } 如何使用Kubernetes的限速器 基于流量管制的限速队列实例，可以大量突发，但是需要进行整形，添加操作会根据 When() 中设计的需要等待的时间进行添加。根据不同的队列实现不同方式的延迟\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;strconv\u0026#34; \u0026#34;time\u0026#34; \u0026#34;k8s.io/client-go/util/workqueue\u0026#34; ) func main() { stopCh := make(chan string) timeLayout := \u0026#34;2006-01-02:15:04:05.0000\u0026#34; limiter := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) length := 20 // 一共请求20次 chs := make([]chan string, length) for i := 0; i \u0026lt; length; i++ { chs[i] = make(chan string, 1) go func(taskId string, ch chan string) { item := \u0026#34;Task-\u0026#34; + taskId + time.Now().Format(timeLayout) log.Println(item + \u0026#34; Added.\u0026#34;) limiter.AddRateLimited(item) // 添加会根据When() 延迟添加到工作队列中 }(strconv.FormatInt(int64(i), 10), chs[i]) go func() { for { key, quit := limiter.Get() if quit { return } log.Println(fmt.Sprintf(\u0026#34;%s process done\u0026#34;, key)) defer limiter.Done(key) } }() } \u0026lt;-stopCh } 因为默认的限速器不支持初始化 QPS，修改源码内的为 $BT(1, 5)$ ，执行结果可以看出，大突发流量时，超过桶内token数时，会根据token生成的速度进行放行。\n图中，任务的添加是突发性的，日志打印的是同时添加，但是在添加前输出的日志，消费端可以看到实际是被延迟了。配置的是每秒一个token，实际上放行流量也是每秒一个token。\nReference heap ","permalink":"https://www.161616.top/ch09-queue/","summary":"通用队列 在kubernetes中，使用go的channel无法满足kubernetes的应用场景，如延迟、限速等；在kubernetes中存在三种队列通用队列 common queue ，延迟队列 delaying queue，和限速队列 rate limiters queue\nInferface Interface作为所有队列的一个抽象定义\ngo 1 2 3 4 5 6 7 8 type Interface interface { Add(item interface{}) Len() int Get() (item interface{}, shutdown bool) Done(item interface{}) ShutDown() ShuttingDown() bool } Implementation go 1 2 3 4 5 6 7 8 9 10 11 12 13 type Type struct { // 一个work queue queue []t // queue用slice做存储 dirty set // 脏位，定义了需要处理的元素，类似于操作系统，表示已修改但为写入 processing set // 当前正在处理的元素集合 cond *sync.","title":"源码分析client-go架构 - queue"},{"content":"Overview K近邻值算法 KNN (K — Nearest Neighbors) 是一种机器学习中的分类算法；K-NN是一种非参数的惰性学习算法。非参数意味着没有对基础数据分布的假设，即模型结构是从数据集确定的。\n它被称为惰性算法的原因是，因为它**不需要任何训练数据点来生成模型。**所有训练数据都用于测试阶段，这使得训练更快，测试阶段更慢且成本更高。\n如何工作 KNN 算法是通过计算新对象与训练数据集中所有对象之间的距离，对新实例进行分类或回归预测。然后选择训练数据集中距离最小的 K 个示例，并通过平均结果进行预测。\n如图所示：一个未分类的数据（红色）和所有其他已分类的数据（黄色和紫色），每个数据都属于一个类别。因此，计算未分类数据与所有其他数据的距离，以了解哪些距离最小，因此当K= 3 （或K= 6 ）最接近的数据并检查出现最多的类，如下图所示，与新数据最接近的数据是在第一个圆圈内（圆圈内）的数据，在这个圆圈内还有 3 个其他数据（已经用黄色分类），我们将检查其中的主要类别，会被归类为紫色，因为有2个紫色球，1个黄色球。\nKNN算法要执行的步骤 将数据分为训练数据和测试数据 选择一个值 K 确定要使用的距离算法 从需要分类的测试数据中选择一个样本，计算到它的 n 个训练样本的距离。 对获得的距离进行排序并取 k最近的数据样本。 根据 k 个邻居的多数票将测试类分配给该类。 影响KNN算法性能的因素 用于确定最近邻居的距离的算法\n用于从 K 近邻派生分类的决策规则\n用于对新示例进行分类的邻居数\n如何计算距离 测量距离是KNN算法的核心，总结了问题域中两个对象之间的相对差异。比较常见的是，这两个对象是描述主题（例如人、汽车或房屋）或事件（例如购买、索赔或诊断）的数据行。\n汉明距离 汉明距离（Hamming Distance）计算两个二进制向量之间的距离，也简称为二进制串 binary strings 或位串 bitstrings ；换句话说，汉明距离是将一个字符串更改为另一个字符串所需的最小替换次数，或将一个字符串转换为另一个字符串的最小错误数。\n示例：如一列具有类别 “红色”、“绿色” 和 “蓝色”，您可以将每个示例独热编码为一个位串，每列一个位。\n注：独热编码 one-hot encoding：将分类数据，转换成二进制向量表示，这个二进制向量用来表示一种特殊的bit（二进制位）组合，该字节里，仅容许单一bit为1，其他bit都必须为0\n如：\napple banana pineapple 1 0 0 0 1 0 0 0 1 100 表示苹果，100就是苹果的二进制向量 010 表示香蕉，010就是香蕉的二进制向量\ntext 1 2 3 red = [1, 0, 0] green = [0, 1, 0] blue = [0, 0, 1] 而red和green之间的距离就是两个等长bitstrings之间bit差（对应符号不同的位置）的总和或平均数，这就是汉明距离\n$Hamming Distance d(a, b)\\ =\\ sum(xi\\ !=\\ yi\\ for\\ xi,\\ yi\\ in\\ zip(x, y))$ 上述的实现为：\npython 1 2 3 4 5 6 7 8 9 10 def hammingDistance(a, b): if len(a) != len(b): raise ValueError(\u0026#34;Undefined for sequences of unequal length.\u0026#34;) return sum(abs(e1 - e2) for e1, e2 in zip(a, b)) row1 = [0, 0, 0, 0, 0, 1] row2 = [0, 0, 0, 0, 1, 0] dist = hammingDistance(row1, row2) print(dist) 可以看到字符串之间有两个差异，或者 6 个位位置中有 2 个不同，平均 (2/6) 约为 1/3 或 0.333。\npython 1 2 3 4 5 6 7 8 9 from scipy.spatial.distance import hamming # define data row1 = [0, 0, 0, 0, 0, 1] row2 = [0, 0, 0, 0, 1, 0] # calculate distance dist = hamming(row1, row2) print(dist) 欧几里得距离 欧几里得距离（Euclidean distance） 是计算两个点之间的距离。在计算具体的数值（例如浮点数或整数）的两行数据之间的距离时，您最有可能使用欧几里得距离。\n欧几里得距离计算公式为两个向量之间的平方差之和的平方根。\n$EuclideanDistance=\\sqrt[]{\\sum(a-b)^2}$\n如果要执行数千或数百万次距离计算，通常会去除平方根运算以加快计算速度。修改后的结果分数将具有相同的相对比例，并且仍然可以在机器学习算法中有效地用于查找最相似的示例。\n$EuclideanDistance = sum\\ for\\ i\\ to\\ N\\ (v1[i]\\ –\\ v2[i])^2$\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # calculating euclidean distance between vectors from math import sqrt from scipy.spatial.distance import euclidean # calculate euclidean distance def euclidean_distance(a, b): return sqrt(sum((e1-e2)**2 for e1, e2 in zip(a,b))) # define data row1 = [10, 20, 15, 10, 5] row2 = [12, 24, 18, 8, 7] # calculate distance dist = euclidean_distance(row1, row2) print(dist) print(euclidean(row1, row2)) 曼哈顿距离 曼哈顿距离（ Manhattan distance ）又被称作出租车几何学 Taxicab geometry；用于计算两个向量之间的距离。\n对于描述网格上的对象（如棋盘或城市街区）的向量可能更有用。出租车在城市街区之间采取的最短路径（网格上的坐标）。\n粗略地说，欧几里得几何是中学常用的平面几何和立体几何 Plane geometry\n曼哈顿距离可以理解为：欧几里得空间的固定直角坐标系上两点所形成的线段对轴产生的投影的距离总和。\n图中： 红、蓝与黄线分别表示所有曼哈顿距离都拥有一样长度（12），绿线表示欧几里得距离 $6×\\sqrt2 ≈ 8.48$\n对于整数特征空间中的两个向量，应该计算曼哈顿距离而不是欧几里得距离\n曼哈顿距离在二维平面的计算公式是，在X轴的亮点\n$Manhattandistance\\ d(x,y)=\\left|x_{1}-x_{2}\\right|+\\left|y_{1}-y_{2}\\right|$\n如果所示，描述格子和格子之间的距离可以用曼哈顿距离，如国王移动到右下角的距离是？\n$King=|6-8|+|6-1| = 7$\n两个向量间的距离可以表示为 $MD\\ =\\ Σ|Ai – Bi|$\npython中的公式可以表示为 ：sum(abs(val1-val2) for val1, val2 in zip(a,b))\npython 1 2 3 4 5 6 7 8 9 10 11 12 from scipy.spatial.distance import cityblock # calculate manhattan distance def manhattan_distance(a, b): return sum(abs(e1-e2) for e1, e2 in zip(a,b)) # define data row1 = [10, 20, 15, 10, 5] row2 = [12, 24, 18, 8, 7] # calculate distance dist = manhattan_distance(row1, row2) print(dist) print(cityblock(row1, row2)) 闵可夫斯基距离 闵可夫斯基距离（Minkowski distance）并不是一种距离而是对是欧几里得距离和曼哈顿距离的概括，用来计算两个向量之间的距离。\n闵可夫斯基增并添加了一个参数，称为“阶数”或 p：$d(x,y) = (\\sum(|x-y|)^p)^\\frac{1}{p}$\n在python中的公式：\npython 1 (sum for i to N (abs(v1[i] – v2[i]))^p)^(1/p) p 是一个有序的参数，当 $p=1$ 时，计算的是曼哈顿距离。当 $p=2$ 时，计算的是欧几里得距离。\n在实现使用距离度量的机器学习算法时，通常会使用闵可夫斯基距离，因为可以通过调整参数“ p ”控制用于向量的距离度量算法的类型。\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # calculating minkowski distance between vectors from scipy.spatial import minkowski_distance # calculate minkowski distance def minkowski_distance(a, b, p): return sum(abs(e1-e2)**p for e1, e2 in zip(a,b))**(1/p) # define data row1 = [10, 20, 15, 10, 5] row2 = [12, 24, 18, 8, 7] # 手动实现的算法用来使用闵可夫斯基计算距离 dist = minkowski_distance(row1, row2, 1) # 1为曼哈顿 print(dist) # 1为欧几里得 dist = minkowski_distance(row1, row2, 2) print(dist) # 使用包 scipy.spatial来计算 print(minkowski_distance(row1, row2, 1)) print(minkowski_distance(row1, row2, 2)) KNN算法实现 Prerequisite 首先会用示例来实现KNN算法的每个步骤，并加以分析，然后将所有步骤关联在在一起，形成一个适用于真实数据集的实现。\nKNN在实现起来主要有三个步骤：\n计算距离（这里选择欧几里得距离） 获得临近邻居 做出预测 这三个步骤是KNN算法用以解决分类和回归预测建模问题的基础知识\n计算距离 第一步计算数据集中两行之间的距离。在数据集中的数据行主要由数字组成，计算两行或数字向量之间的距离的一种简单方法是画一条直线。这在 2D 或 3D 平面中都是很好地选择，并且可以很好地扩展到更高的维度。\n这里使用的是比较流行的计算距离的算法，欧几里得距离来计算两个向量之间的直线距离。欧几里得距离的公式是，两个向量的平方差的平方根，$Euclidean\\ Distance=\\sqrt[]{\\sum(a-b)^2}$ ；在python中可以表示为：sqrt(sum i to N (x1 – x2)^2) ；其中 x1 是第一行数据，x2 是第二行数据，i 表示特定列的索引，因为可能需要对所有行进行计算。\n在欧几里得距离中，值越小，两条记录就越相似； 0 表示两条记录之间没有差异。\n那么使用python实现一个计算欧几里得距离的算法\npython 1 2 3 4 5 def euclidean_distance(row1, row2): distance = 0.0 for i in range(len(row1)-1): distance += (row1[i] - row2[i])**2 return sqrt(distance) 准备一部分测试数据，来对测试距离算法\npython 1 2 3 4 5 6 7 8 9 10 11 X1\tX2\tY 2.7810836\t2.550537003\t0 1.465489372\t2.362125076\t0 3.396561688\t4.400293529\t0 1.38807019\t1.850220317\t0 3.06407232\t3.005305973\t0 7.627531214\t2.759262235\t1 5.332441248\t2.088626775\t1 6.922596716\t1.77106367\t1 8.675418651\t-0.242068655\t1 7.673756466\t3.508563011\t1 那么来测试这些数据，需要做到的是第一行与所有行之间的距离，对于第一行与自己的距离应该为0\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 from math import sqrt # 欧几里得距离，计算两个向量间距离的算法 def euclidean_distance(row1, row2): distance = 0.0 for i in range(len(row1)-1): distance += (row1[i] - row2[i])**2 # 平方差 return sqrt(distance) # 平方根 # 测试数据集 dataset = [ [2.7810836,2.550537003,0], [1.465489372,2.362125076,0], [3.396561688,4.400293529,0], [1.38807019,1.850220317,0], [3.06407232,3.005305973,0], [7.627531214,2.759262235,1], [5.332441248,2.088626775,1], [6.922596716,1.77106367,1], [8.675418651,-0.242068655,1], [7.673756466,3.508563011,1] ] row0 = dataset[0] for row in dataset: distance = euclidean_distance(row0, row) print(distance) # 0.0 # 1.3290173915275787 # 1.9494646655653247 # 1.5591439385540549 # 0.5356280721938492 # 4.850940186986411 # 2.592833759950511 # 4.214227042632867 # 6.522409988228337 # 4.985585382449795 获取最近邻居 数据集中新数据的邻居是k个最接近的实例（行），这个实例由距离定义。现在诞生的问题：如何找到最近的邻居？以及怎么找到最近的邻居？\n为了在数据集中找到 K 的邻居，首先必须计算数据集中每条记录与新数据之间的距离。\n有了距离之后，必须按照 K 的距离对训练集中的所有实例排序。然后选择前 k 个作为最近的邻居。\n这里实现起来是通过将数据集中每条记录的距离作为一个元组来跟踪，通过对元组列表进行排序（距离降序），然后检索最近邻居。下面是一个实现这些步骤的函数\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 找到最近的邻居 def get_neighbors(train, test_row, num_neighbors): \u0026#34;\u0026#34;\u0026#34; 计算训练集train中所有元素到test_row的距离 :param train: list, 数据集，可以是训练集 :param test_row: list, 新的实例，也就是K :param num_neighbors:int，需要多少个邻居 :return: None \u0026#34;\u0026#34;\u0026#34; distances = list() for train_row in train: # 计算出每一行的距离，把他添加到元组中 dist = euclidean_distance(test_row, train_row) distances.append((train_row, dist)) distances.sort(key=lambda knn: knn[1]) # 根据元素哪个字段进行排序 neighbors = list() for i in range(num_neighbors): neighbors.append(distances[i][0]) return neighbors 下面是完整的示例\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 from math import sqrt # 欧几里得距离，计算两个向量间距离的算法 def euclidean_distance(row1, row2): distance = 0.0 for i in range(len(row1)-1): distance += (row1[i] - row2[i])**2 # 平方差 return sqrt(distance) # 平方根 # 找到最近的邻居 def get_neighbors(train, test_row, num_neighbors): \u0026#34;\u0026#34;\u0026#34; 计算训练集train中所有元素到test_row的距离 :param train: list, 数据集，可以是训练集 :param test_row: list, 新的实例，也就是K :param num_neighbors:int，需要多少个邻居 :return: None \u0026#34;\u0026#34;\u0026#34; distances = list() for train_row in train: # 计算出每一行的距离，把他添加到元组中 dist = euclidean_distance(test_row, train_row) distances.append((train_row, dist)) distances.sort(key=lambda knn: knn[1]) # 根据元素哪个字段进行排序 neighbors = list() for i in range(num_neighbors): neighbors.append(distances[i][0]) return neighbors # 测试数据集 dataset = [ [2.7810836,2.550537003,0], [1.465489372,2.362125076,0], [3.396561688,4.400293529,0], [1.38807019,1.850220317,0], [3.06407232,3.005305973,0], [7.627531214,2.759262235,1], [5.332441248,2.088626775,1], [6.922596716,1.77106367,1], [8.675418651,-0.242068655,1], [7.673756466,3.508563011,1] ] neighbors = get_neighbors(dataset, dataset[0], 3) for neighbor in neighbors: print(neighbor) # [2.7810836, 2.550537003, 0] # [3.06407232, 3.005305973, 0] # [1.465489372, 2.362125076, 0] 可以看到，运行后会将数据集中最相似的 3 条记录按相似度顺序打印。和预测的一样，第一个记录与其本身最相似，并且位于列表的顶部。\n预测结果 预测结果在这里指定是，通过分类拿到了最近的邻居的实例，对邻居进行分类，找到邻居中最大类别的一类，作为预测值。这里使用的是对邻居值执行 max() 来实现这一点，下面是实现方式\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 # 预测值 def predict_classification(train, test_row, num_neighbors): \u0026#34;\u0026#34;\u0026#34; 计算训练集train中所有元素到test_row的距离 :param train: list, 数据集，可以是训练集 :param test_row: list, 新的实例，也就是K :param num_neighbors:int，需要多少个邻居 :return: None \u0026#34;\u0026#34;\u0026#34; neighbors = get_neighbors(train, test_row, num_neighbors) output_values = [row[-1] for row in neighbors] # 拿到所属类的真实类别 prediction = max(set(output_values), key=output_values.count) #算出邻居类别最大的数量 return prediction 下面是完整的示例\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 from math import sqrt # 欧几里得距离，计算两个向量间距离的算法 def euclidean_distance(row1, row2): distance = 0.0 for i in range(len(row1)-1): distance += (row1[i] - row2[i])**2 # 平方差 return sqrt(distance) # 平方根 # 找到最近的邻居 def get_neighbors(train, test_row, num_neighbors): \u0026#34;\u0026#34;\u0026#34; 计算训练集train中所有元素到test_row的距离 :param train: list, 数据集，可以是训练集 :param test_row: list, 新的实例，也就是K :param num_neighbors:int，需要多少个邻居 :return: None \u0026#34;\u0026#34;\u0026#34; distances = list() for train_row in train: # 计算出每一行的距离，把他添加到元组中 dist = euclidean_distance(test_row, train_row) distances.append((train_row, dist)) distances.sort(key=lambda knn: knn[1]) # 根据元素哪个字段进行排序 neighbors = list() for i in range(num_neighbors): neighbors.append(distances[i][0]) return neighbors # 预测值 def predict_classification(train, test_row, num_neighbors): \u0026#34;\u0026#34;\u0026#34; 计算训练集train中所有元素到test_row的距离 :param train: list, 数据集，可以是训练集 :param test_row: list, 新的实例，也就是K :param num_neighbors:int，需要多少个邻居 :return: None \u0026#34;\u0026#34;\u0026#34; neighbors = get_neighbors(train, test_row, num_neighbors) output_values = [row[-1] for row in neighbors] # 拿到所属类的真实类别 prediction = max(set(output_values), key=output_values.count) #算出邻居类别最大的数量 return prediction # 测试数据集 dataset = [ [2.7810836,2.550537003,0], [1.465489372,2.362125076,0], [3.396561688,4.400293529,0], [1.38807019,1.850220317,0], [3.06407232,3.005305973,0], [7.627531214,2.759262235,1], [5.332441248,2.088626775,1], [6.922596716,1.77106367,1], [8.675418651,-0.242068655,1], [7.673756466,3.508563011,1] ] for n in range(len(dataset)): prediction = predict_classification(dataset, dataset[n], 5) print(\u0026#39;Expected %d, Got %d.\u0026#39; % (dataset[n][-1], prediction)) # Expected 0, Got 0. # Expected 0, Got 0. # Expected 0, Got 0. # Expected 0, Got 0. # Expected 0, Got 0. # Expected 1, Got 1. # Expected 1, Got 1. # Expected 1, Got 1. # Expected 1, Got 1. # Expected 1, Got 1. 运行结果打印了预期分类与从数据集中 3 个相进邻居预测结果是一直的。\n鸢尾花种实例 这里使用的是 Iris Flower Species 数据集。\n鸢尾花数据集是根据鸢尾花的测量值预测花卉种类。这是一个多类分类问题。每个类的观察数量是平衡的。有 150 个观测值，有 4 个输入变量和 1 个输出变量。变量名称如下：\n萼片长度以厘米为单位。 萼片宽度以厘米为单位。 花瓣长度以厘米为单位。 花瓣宽度以厘米为单位。 真实类型 更多的关于数据集的说明可以参考：Iris-databases数据集的说明\nPrerequisite 实验的步骤大概分为如下：\n加载数据集并将数据转换为可用于均值和标准差计算的数字。将属性转为float，将类别转换为int。 使 5折的K折较差验证（K-Fold CV）评估该算法。 Start python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 from random import seed from random import randrange from csv import reader from math import sqrt # 加载CSV def load_csv(filename): dataset = list() with open(filename, \u0026#39;r\u0026#39;) as file: csv_reader = reader(file) for row in csv_reader: if not row: continue dataset.append(row) return dataset # 转换所有的值为float方便运算 def str_column_to_float(dataset, column): for row in dataset: row[column] = float(row[column].strip()) # 转换所有的类型为int def str_column_to_int(dataset, column): class_values = [row[column] for row in dataset] unique = set(class_values) lookup = dict() for i, value in enumerate(unique): lookup[value] = i for row in dataset: row[column] = lookup[row[column]] return lookup # # k-folds CV函数进行划分 def cross_validation_split(dataset, n_folds): dataset_split = list() dataset_copy = list(dataset) # 平均分成n_folds折数 fold_size = int(len(dataset) / n_folds) for _ in range(n_folds): fold = list() while len(fold) \u0026lt; fold_size: index = randrange(len(dataset_copy)) fold.append(dataset_copy.pop(index)) dataset_split.append(fold) return dataset_split # 计算精确度 def accuracy_metric(actual, predicted): correct = 0 for i in range(len(actual)): if actual[i] == predicted[i]: correct += 1 return correct / float(len(actual)) * 100.0 # 评估算法 def evaluate_algorithm(dataset, algorithm, n_folds, *args): \u0026#34;\u0026#34;\u0026#34; 评估算法，计算算法的精确度 :param dataset: list, 数据集 :param algorithm: function, 算法名 :param n_folds: int，折数 :param args: 用于algorithm的参数 :return: None \u0026#34;\u0026#34;\u0026#34; folds = cross_validation_split(dataset, n_folds) # 分成5折 scores = list() for fold in folds: train_set = list(folds) train_set.remove(fold) # 训练集不包含本身 train_set = sum(train_set, []) test_set = list() # 测试集 for row in fold: row_copy = list(row) test_set.append(row_copy) row_copy[-1] = None predicted = algorithm(train_set, test_set, *args) actual = [row[-1] for row in fold] accuracy = accuracy_metric(actual, predicted) scores.append(accuracy) return scores # 欧几里得距离，计算两个向量间距离的算法 def euclidean_distance(row1, row2): distance = 0.0 for i in range(len(row1)-1): distance += (row1[i] - row2[i])**2 return sqrt(distance) # 确定最邻近的邻居 def get_neighbors(train, test_row, num_neighbors): \u0026#34;\u0026#34;\u0026#34; 计算训练集train中所有元素到test_row的距离 :param train: list, 数据集，可以是训练集 :param test_row: list, 新的实例，也就是K :param num_neighbors:int，需要多少个邻居 :return: None \u0026#34;\u0026#34;\u0026#34; distances = list() for train_row in train: dist = euclidean_distance(test_row, train_row) distances.append((train_row, dist)) distances.sort(key=lambda tup: tup[1]) neighbors = list() for i in range(num_neighbors): neighbors.append(distances[i][0]) return neighbors # 与临近值进行比较并预测 def predict_classification(train, test_row, num_neighbors): \u0026#34;\u0026#34;\u0026#34; 计算训练集train中所有元素到test_row的距离 :param train: list, 数据集，可以是训练集 :param test_row: list, 新的实例，也就是K :param num_neighbors:int，需要多少个邻居 :return: None \u0026#34;\u0026#34;\u0026#34; neighbors = get_neighbors(train, test_row, num_neighbors) output_values = [row[-1] for row in neighbors] prediction = max(set(output_values), key=output_values.count) return prediction # kNN Algorithm def k_nearest_neighbors(train, test, num_neighbors): predictions = list() for row in test: output = predict_classification(train, row, num_neighbors) predictions.append(output) return(predictions) # 使用KNN算法计算鸢尾花数据集 seed(1) filename = \u0026#39;iris.csv\u0026#39; dataset = load_csv(filename) for i in range(len(dataset[0])-1): str_column_to_float(dataset, i) # 转换类型为int str_column_to_int(dataset, len(dataset[0])-1) # 评估算法 n_folds = 5 # 5折 num_neighbors = 5 #取5个邻居 scores = evaluate_algorithm(dataset, k_nearest_neighbors, n_folds, num_neighbors) print(\u0026#39;Scores: %s\u0026#39; % scores) print(\u0026#39;Mean Accuracy: %.3f%%\u0026#39; % (sum(scores)/float(len(scores)))) # Scores: [96.66666666666667, 96.66666666666667, 100.0, 90.0, 100.0] # Mean Accuracy: 96.667% 上述是对整个数据集的预测百分比，也可以对对应的类的信息进行输出\n首先在类别转换函数 str_column_to_int 中增加打印方法\npython 1 2 3 for i, value in enumerate(unique): lookup[value] = i print(\u0026#39;[%s] =\u0026gt; %d\u0026#39; % (value, i)) 然后在定义一个新的实例，这个实例是用于预测的信息 row = [5.7,2.9,4.2,1.3] ; 然后修改需要预测的数据，进行预测\npython 1 2 3 4 5 6 7 8 9 10 11 12 # 原来的整个数据集打分不需要了 # scores = evaluate_algorithm(dataset, k_nearest_neighbors, n_folds, num_neighbors) # print(\u0026#39;Scores: %s\u0026#39; % scores) # print(\u0026#39;Mean Accuracy: %.3f%%\u0026#39; % (sum(scores)/float(len(scores)))) # 定义一个新数据 row = [5.7,2.9,4.2,1.3] label = predict_classification(dataset, row, num_neighbors) print(\u0026#39;Data=%s, Predicted: %s\u0026#39; % (row, label)) # Data=[5.7, 2.9, 4.2, 1.3], Predicted: 1 通过预测，可以看出预测结果属于第 1 类，就知道该花为 Iris-setosa 。\nReference distance measures k nearest neighbors implement\n","permalink":"https://www.161616.top/knn/","summary":"Overview K近邻值算法 KNN (K — Nearest Neighbors) 是一种机器学习中的分类算法；K-NN是一种非参数的惰性学习算法。非参数意味着没有对基础数据分布的假设，即模型结构是从数据集确定的。\n它被称为惰性算法的原因是，因为它**不需要任何训练数据点来生成模型。**所有训练数据都用于测试阶段，这使得训练更快，测试阶段更慢且成本更高。\n如何工作 KNN 算法是通过计算新对象与训练数据集中所有对象之间的距离，对新实例进行分类或回归预测。然后选择训练数据集中距离最小的 K 个示例，并通过平均结果进行预测。\n如图所示：一个未分类的数据（红色）和所有其他已分类的数据（黄色和紫色），每个数据都属于一个类别。因此，计算未分类数据与所有其他数据的距离，以了解哪些距离最小，因此当K= 3 （或K= 6 ）最接近的数据并检查出现最多的类，如下图所示，与新数据最接近的数据是在第一个圆圈内（圆圈内）的数据，在这个圆圈内还有 3 个其他数据（已经用黄色分类），我们将检查其中的主要类别，会被归类为紫色，因为有2个紫色球，1个黄色球。\nKNN算法要执行的步骤 将数据分为训练数据和测试数据 选择一个值 K 确定要使用的距离算法 从需要分类的测试数据中选择一个样本，计算到它的 n 个训练样本的距离。 对获得的距离进行排序并取 k最近的数据样本。 根据 k 个邻居的多数票将测试类分配给该类。 影响KNN算法性能的因素 用于确定最近邻居的距离的算法\n用于从 K 近邻派生分类的决策规则\n用于对新示例进行分类的邻居数\n如何计算距离 测量距离是KNN算法的核心，总结了问题域中两个对象之间的相对差异。比较常见的是，这两个对象是描述主题（例如人、汽车或房屋）或事件（例如购买、索赔或诊断）的数据行。\n汉明距离 汉明距离（Hamming Distance）计算两个二进制向量之间的距离，也简称为二进制串 binary strings 或位串 bitstrings ；换句话说，汉明距离是将一个字符串更改为另一个字符串所需的最小替换次数，或将一个字符串转换为另一个字符串的最小错误数。\n示例：如一列具有类别 “红色”、“绿色” 和 “蓝色”，您可以将每个示例独热编码为一个位串，每列一个位。\n注：独热编码 one-hot encoding：将分类数据，转换成二进制向量表示，这个二进制向量用来表示一种特殊的bit（二进制位）组合，该字节里，仅容许单一bit为1，其他bit都必须为0\n如：\napple banana pineapple 1 0 0 0 1 0 0 0 1 100 表示苹果，100就是苹果的二进制向量 010 表示香蕉，010就是香蕉的二进制向量","title":"KNN算法"},{"content":"决策边界 (decision boundary)\n支持向量机获取这些数据点并输出最能分离标签的超平面。这条线是决策边界\n决策平面 （ decision surface ），是将空间划分为不同的区域。位于决策平面一侧的数据被定义为与位于另一侧的数据属于不同的类别。决策面可以作为学习过程的结果创建或修改，它们经常用于机器学习、模式识别和分类系统。\n环境空间 ( Ambient Space)，围绕数学对象即对象本身的空间，如一维 Line ，可以独立研究，这种情况下L则是L；再例如将L作为二维空间 $R^2$ 的对象进行研究，这种情况下 L 的环境空间是 $R^2$。\n超平面（Hyperplane）是一个子空间， N维空间的超平面是其具有维数的平面的子集。就其性质而言，它将空间分成两个半空间，其维度比其环境空间的维度小 1。如果空间是三维的，那么它的超平面就是二维维平面，而如果空间是 2 维的，那么它的超平面就是一维线。支持向量机 (SVM) 通过找到使两个类之间的边距最大化的超平面来执行分类。\n法向量 （Normal） 是垂直于该平面、另一个向量的 90° 角倾斜\n什么是支持向量 支持向量 （Support vectors），靠近决策平面（超平面）的数据点。\n如图所示，从一维平面来看，哪个是分离的超平面？\n一般而言，会有很多种解决方法（超平面），支持向量机就是如何找到最佳方法的解决方案。\n转置运算\n矩阵的转置是原始矩阵的翻转版本，可以通过转换矩阵的行和列来转置矩阵。我们用 $A^T$ 表示矩阵 A 的转置。例如，\n$$A=\\left[ \\begin{matrix} 1 \u0026 2 \u0026 3 \\\\ 4 \u0026 5 \u0026 6 \\\\ \\end{matrix} \\right]$$ ；那么 A 的转置就为 $$A=\\left[ \\begin{matrix} 1 \u0026 4 \\\\ 2 \u0026 5 \\\\ 3 \u0026 6 \\\\ \\end{matrix} \\right]$$ ；\n我们可以将向量的转置作为特例。由于 n 维向量 x 由 n×1 列矩阵表示：\n$$x=\\left[ \\begin{matrix} x_1 \\\\ x_2 \\\\ x_3 \\\\ .... \\\\ x_n \\\\ \\end{matrix} \\right]$$ ；那么 x 的转置（$x^T$）是一个 $1\\times n$ 行矩阵 $$x^T=\\left[ \\begin{matrix} x_1 \u0026 x_2 \u0026 x_3 \u0026 ... \u0026 x_n \\\\ \\end{matrix} \\right]$$ 。\n权重向量\n$wx+b=0$ w：权重向量 x n维向量 $x_i=[1,2,3\u0026hellip;n]$ $w_i=[1,2,3\u0026hellip;n]$ 每个输入的值都乘以一个“权重” $w_i$。权重是表示计算输出时每个输入的重要性的值\n权重决定了输入对输出的影响程度。 $Y=\\sum(Weight \\times input)+bias$ ；如果输入为 $[x_1,x_2\\ \u0026hellip; ,x_n]$ 权重是：$[w_1,w_2\\ \\ ,w_n]$\n通过场景来理解\n假设预估汽车的价格，汽车的价格取决于制造年份和行驶里程数。让我们假设汽车的年份越高，汽车价格越高。随后，汽车开得越多，汽车就越便宜。\n这个例子应该可以帮助您了解汽车价格与制造年份之间存在正相关关系，而汽车价格与其行驶里程之间存在负关关系。因此，我们希望看到代表年份的特征的权重为正，代表里程的特征的权重为负。公式为：$car = (w_1x\\ ear+w_2x\\ miles)$\n偏差 bais 是一个常数 const ，偏差用于将影响函数的结果向正或负方向移动。bias 会被被添加到 input 和 weight 的乘积中。偏差用于抵消结果。$x_1w_1+x_2w_2\u0026hellip;x_nw_n+bias$\n通过场景来理解\n假设希望在输入为 0 时返回 2。由于权重和输入的乘积之和为 0，您将如何确保返回 2？此时可以添加2的bias。如果不包含偏差，只是对 input 和 weight 执行矩阵乘法。这将很容易导致过度拟合数据集。\n过度拟合（overfitting）是指机器学习算模型在训练集上的误差和测试集上的误差之间差异过大。造成过度拟合的原因可能有多种．最常见的就是模型容量过高，模型过于复杂，换句话说是模型假设所包含的参数数量过多．如此一来，算法会将训练集中所包含的没有普遍性的一些特征也学习进来，结果降低了模型的泛化能力．\nhttps://machine-learning.paperspace.com/wiki/weights-and-biases\n范数\n向量的范数（norm）是它的长度 ，x的范数表示为 $\\parallel x \\parallel$；常用的范数为 P 范数，其中 P 是大于等于1的任何数，向量 x的 p 范数表示为 $\\parallel x \\parallel_p$ ；通常情况下向量 x 的 p 范数的计算公式为： $\\parallel x \\parallel_p = (x_1^p+x_2^p+x_3^p+ \\ \u0026hellip;\\ x_n^p)^{1/p}$ ；公式可以简写为：$\\parallel x \\parallel_p = (\\sum_{i=1}^n\\ x_i^p)^{1/p}$\n曼哈顿距离\n曼哈顿距离也被称为1-范数 1-norm，因为它测量的中两点之间的距离。假设：向量a，我们必须计算 1-范式 $\\vec{a} = [2,3]$ ，在图像中表示（红色线部分表示向量a的1-范式）\n通过公式来计算1-范式，可以将p替换为1，$\\parallel a \\parallel_1 = (x_1 + x_2) = (2+3)^1=5$\n欧几里得范数\n欧几里得范数又被称作2-范数 2-norm ，是范数中最常用的范数，欧几里得范数返回的是两点之间最短的距离，因此 $\\vec{a}$ 的2-范式为 $\\parallel x \\parallel_2 = (2^2+3^2)^{\\frac{1}{2}} = (4+9)^{\\frac{1}{2}} = \\sqrt{13}$ ；用图像表示为（红线部分表示2-范数，这是 $\\vec{a}$ 表示的点到点之间的最低按距离）\n无穷范数\n无穷范数 Infinity-norm 是返回给定向量中的最大绝对值；$\\vec{a}$ 的无穷范式为 $\\parallel a \\parallel_\\infty = 3$ （公式求得是上述图中 $[2,3]$ 这个实例）。\n例如，如果我们必须找到一个向量的无穷范数，比如 $\\vec{b}$ ，$\\vec{b} = [4,3,-1]$ ；那么 $\\parallel b \\parallel_\\infty = 6$ （这里最大是4，但是返回的是一个绝对值所以是6）\nReference norm\nvector norms\n拉格朗日乘子法\n拉格朗日乘子法 Lagrange multiplier，是一种寻找受等式约束的函数的局部最大值和最小值的策略（即，必须满足一个或多个方程必须完全满足所选变量值的条件）\n设置超平面为 $wx+b=0$ ，其中 $w=[1,2,\\ ..,\\ n]$ ，w是 $n \\times 1$ 维，n特征值的个数，x 训练的示例，b是bias，一个二维的超平面的特征为：$x=[x_1,x_2]$ ，$w=[w_1,w_2]$ ，b看做 wegiht $w_0$ ，\n那么这个超平面的方程就为：\n$$ f(n) \\begin{cases} w_1x_1+w_2x_2+w0 = 0\\ \\ 超平面(决策边界)方程 \\\\ w_1x_1+w_2x_2+w0 \u003e 0\\ \\ 超平面(决策边界)上部分 \\\\ w_1x_1+w_2x_2+w0 \u003c 0\\ \\ 超平面(决策边界)下部分 \\\\ \\end{cases} $$ ，那么在对公式进行分解，增加参数 y ，代表了对向量的分类，也就是说超平面两边的向量，这样公式为：\n$$ f(n) \\begin{cases} w_1x_1+w_2x_2+w0 \\ge 1\\ \\ 当 y_i = +1 \\\\ w_1x_1+w_2x_2+w0 \\le 1\\ \\ 当 y_i = -1 \\\\ \\end{cases} $$ ","permalink":"https://www.161616.top/decision-boundary/","summary":"决策边界 (decision boundary)\n支持向量机获取这些数据点并输出最能分离标签的超平面。这条线是决策边界\n决策平面 （ decision surface ），是将空间划分为不同的区域。位于决策平面一侧的数据被定义为与位于另一侧的数据属于不同的类别。决策面可以作为学习过程的结果创建或修改，它们经常用于机器学习、模式识别和分类系统。\n环境空间 ( Ambient Space)，围绕数学对象即对象本身的空间，如一维 Line ，可以独立研究，这种情况下L则是L；再例如将L作为二维空间 $R^2$ 的对象进行研究，这种情况下 L 的环境空间是 $R^2$。\n超平面（Hyperplane）是一个子空间， N维空间的超平面是其具有维数的平面的子集。就其性质而言，它将空间分成两个半空间，其维度比其环境空间的维度小 1。如果空间是三维的，那么它的超平面就是二维维平面，而如果空间是 2 维的，那么它的超平面就是一维线。支持向量机 (SVM) 通过找到使两个类之间的边距最大化的超平面来执行分类。\n法向量 （Normal） 是垂直于该平面、另一个向量的 90° 角倾斜\n什么是支持向量 支持向量 （Support vectors），靠近决策平面（超平面）的数据点。\n如图所示，从一维平面来看，哪个是分离的超平面？\n一般而言，会有很多种解决方法（超平面），支持向量机就是如何找到最佳方法的解决方案。\n转置运算\n矩阵的转置是原始矩阵的翻转版本，可以通过转换矩阵的行和列来转置矩阵。我们用 $A^T$ 表示矩阵 A 的转置。例如，\n$$A=\\left[ \\begin{matrix} 1 \u0026 2 \u0026 3 \\\\ 4 \u0026 5 \u0026 6 \\\\ \\end{matrix} \\right]$$ ；那么 A 的转置就为 $$A=\\left[ \\begin{matrix} 1 \u0026 4 \\\\ 2 \u0026 5 \\\\ 3 \u0026 6 \\\\ \\end{matrix} \\right]$$ ；","title":"决策边界算法"},{"content":"熵和基尼指数 信息增益 信息增益 information gain 是用于训练决策树的指标。具体来说，是指这些指标衡量拆分的质量。通俗来说是通过根据随机变量的给定值拆分数据集来衡量熵。\n通过描述一个事件是否\u0026quot;惊讶\u0026quot;，通常低概率事件更令人惊讶，因此具有更大的信息量。而具有相同可能性的事件的概率分布更\u0026quot;惊讶\u0026quot;并且具有更大的熵。\n定义：熵 entropy是一组例子中杂质、无序或不确定性的度量。熵控制决策树如何决定拆分数据。它实际上影响了决策树如何绘制边界。\n熵 熵的计算公式为：$E=-\\sum^i_{i=1}(p_i\\times\\log_2(p_i))$ ；$P_i$ 是类别 $i$ 的概率。我们来举一个例子来更好地理解熵及其计算。假设有一个由三种颜色组成的数据集，红色、紫色和黄色。如果我们的集合中有一个红色、三个紫色和四个黄色的观测值，我们的方程变为：$E=-(p_r \\times \\log_2(p_r) + p_p \\times \\log_2(p_p) + p_y \\times \\log_2(p_y)$\n其中 $p_r$ 、$p_p$ 和 $p_y$ 分别是选择红色、紫色和黄色的概率。假设 $p_r=\\frac{1}{8}$，$p_p=\\frac{3}{8}$ ，$p_y=\\frac{4}{8}$ 现在等式变为变为：\n$E=-(\\frac{1}{8} \\times \\log_2(\\frac{1}{8}) + \\frac{3}{8} \\times \\log_2(\\frac{3}{8}) + \\frac{4}{8} \\times \\log_2(\\frac{4}{8}))$ $0.125 \\times log_2(0.125) + 0.375 \\times log_2(0.375) + 0.5 \\times log_2(0.375)$ $0.125 \\times -3 + 0.375 \\times -1.415 + 0.5 \\times -1 = -0.375+-0.425 +-0.5 = 1.41$ ==当所有观测值都属于同一类时会发生什么？== 在这种情况下，熵将始终为零。$E=-(1log_21)=0$ ；这种情况下的数据集没有杂质，这就意味着没有数据集没有意义。又如果有两类数据集，一半是黄色，一半是紫色，那么熵为1，推导过程是：$E=−(\\ (0.5\\log_2(0.5))+(0.5\\times \\log_2(0.5))\\ ) = 1$\n基尼指数 基尼指数 Gini index 和熵 entropy 是计算信息增益的标准。决策树算法使用信息增益来拆分节点。\n基尼指数计算特定变量在随机选择时被错误分类的概率程度以及基尼系数的变化。它适用于分类变量，提供“成功”或“失败”的结果，因此仅进行二元拆分（二叉树结构）。基尼指数在 0 和 1 之间变化，其中，1 表示元素在各个类别中的随机分布。基尼指数为 0.5 表示元素在某些类别中分布均匀。：\n0 表示为所有元素都与某个类相关联，或只存在一个类。 1 表示所有元素随机分布在各个类中，并且0.5 表示元素均匀分布到某些类中 基尼指数公式：$1− \\sum_n^{i=1}(p_i)^2$ ； $P_i$ 为分类到特定类别的概率。在构建决策树时，更愿意选择具有最小基尼指数的属性作为根节点。\n通过实例了解公式\nPast Trend Open Interest Trading Volume Return Positive Low High Up Negative High Low Down Positive Low High Up Positive High High Up Negative Low High Down Positive Low Low Down Negative High High Down Negative Low High Down Positive Low Low Down Positive High High Up 计算基尼指数\n已知条件\n$P(Past\\ Trend=Positive) = \\frac{6}{10}$\n$P(Past\\ Trend=Negative) = \\frac{4}{10}$\n过去趋势基尼指数计算\n如果过去趋势为正面，回报为上涨，概率为：$P(Past\\ Trend=Positive\\ \u0026amp;\\ Return=Up) = \\frac{4}{6}$\n如果过去趋势为正面，回报为下降，概率为：$P(Past\\ Trend=Positive\\ \u0026amp;\\ Return=Down) = \\frac{2}{6}$\n那么这个基尼指数为：$gini(Past\\ Trend) = 1-(\\frac{4}{6}^2+\\frac{2}{6}^2) = 0.45$ 如果过去趋势为负面，回报为上涨，概率为：$P(Past\\ Trend=Negative\\ \u0026amp;\\ Return=Up) = 0$\n如果过去趋势为负面，回报为下降，概率为：$P(Past\\ Trend=Negative\\ \u0026amp;\\ Return=Down) = \\frac{4}{4}$\n那么这个基尼指数为：$gini(Past\\ Trend=Negative) = 1-(0^2+\\frac{4}{4}^2) = 1-(0+1)=0$ 那么过去交易量的的基尼指数加权 = $\\frac{6}{10} \\times 0.45 + \\frac{4}{10}\\times 0 = 0.27$\n未平仓量基尼指数计算\n已知条件\n$P(Open\\ Interest=High): \\frac{4}{10}$ $P(Open\\ Interest=Low): \\frac{6}{10}$ 如果未平仓量为 high 并且回报为上涨，概率为：$P(Open\\ Interest = High\\ \u0026amp;\\ Return\\ = Up)=\\frac{2}{4}$\n如果未平仓量为 high 并且回报为下降，概率为：$P(Open\\ Interest = High\\ \u0026amp;\\ Return\\ = Down)=\\frac{2}{4}$\n那么这个基尼指数为：$gini(Open\\ Interest=High) = 1-(\\frac{2}{4}^2+\\frac{2}{4}^2) = 0.5$ 如果未平仓量为 low 并且回报为上涨，概率为：$P(Open\\ Interest = High\\ \u0026amp;\\ Return\\ = Up)=\\frac{2}{6}$\n如果未平仓量为 low 并且回报为下降，概率为：$P(Open\\ Interest = High\\ \u0026amp;\\ Return\\ = Down)=\\frac{4}{6}$\n那么这个基尼指数为：$gini(Open\\ Interest=Low) = 1-(\\frac{2}{6}^2+\\frac{4}{6}^2) = 0.45$ 那么未平仓量基尼指数加权 = $\\frac{4}{10} \\times 0.5 + \\frac{6}{10}\\times 0.45 = 0.47$\n计算交易量基尼指数\n已知条件\n$P(Trading\\ Volume=High): \\frac{7}{10}$ $P(Trading\\ Volume=Low): \\frac{3}{10}$ 如果交易量为 high 并且回报为上涨，概率为：$P(Trading\\ Volume=High\\ \u0026amp;\\ Return\\ = Up)=\\frac{4}{7}$\n如果交易量为 high 并且回报为下降，概率为：$P(Trading\\ Volume = High\\ \u0026amp;\\ Return\\ = Down)=\\frac{3}{7}$\n那么这个基尼指数为：$gini(Trading\\ Volume=High) = 1-(\\frac{4}{7}^2+\\frac{3}{7}^2) = 0.49$ 如果交易量为 low 并且回报为上涨，概率为：$P(Trading\\ Volume = Low\\ \u0026amp;\\ Return\\ = Up)=0$\n如果交易量为 low 并且回报为下降，概率为：$P(Trading\\ Volume = Low\\ \u0026amp;\\ Return\\ = Down)=\\frac{3}{3}$\n那么这个基尼指数为：$gini(Trading\\ Volume=Low) = 1-(0^2+1^2) = 0$ 那么交易量基尼指数加权 = $\\frac{7}{10} \\times 0.49 + \\frac{3}{10}\\times 0 = 0.34$\n最终计算出的基尼指数列表如下，在表中可以观察到“Past Trend”的基尼指数最低，因此它将被选为决策树的根节点。\nAttributes Gini Index Past Trend 0.27 Open Interest 0.47 Trading Volume 0.34 这里将重复的过程来确定决策树的子节点或分支。将通过计算”Past Trend“的“Positive”分支的基尼指数如下：\nPast Trend Open Interest Trading Volume Return Positive Low High Up Positive Low High Up Positive High High Up Positive Low Low Down Positive Low Low Down Positive High High Up 针对过去正面趋势计算未平仓量的基尼指数\n已知条件\n$P(Open\\ Interest=High): \\frac{2}{6}$ $P(Open\\ Interest=Low): \\frac{4}{6}$ 如果未平仓量为 high 并且回报为上涨，概率为：$P(Open\\ Interest = High\\ \u0026amp;\\ Return\\ = Up)=\\frac{2}{2}$\n如果未平仓量为 high 并且回报为下降，概率为：$P(Open\\ Interest = High\\ \u0026amp;\\ Return\\ = Down)=0$\n那么这个基尼指数为：$gini(Open\\ Interest=High) = 1-(\\frac{2}{2}^2+0^2) = 0$ 如果未平仓量为 low 并且回报为上涨，概率为：$P(Open\\ Interest = Low\\ \u0026amp;\\ Return\\ = Up)=\\frac{2}{4}$\n如果未平仓量为 low 并且回报为下降，概率为：$P(Open\\ Interest = Low\\ \u0026amp;\\ Return\\ = Down)=\\frac{2}{4}$\n那么这个基尼指数为：$gini(Open\\ Interest=Low) = 1-(\\frac{2}{4}^2+\\frac{2}{4}^2) = 0.5$ 那么未平仓量基尼指数加权 = $\\frac{2}{6} \\times 0 + \\frac{4}{6}\\times 0.5 = 0.33$\n计算交易量基尼指数\n已知条件\n$P(Trading\\ Volume=High): \\frac{4}{6}$ $P(Trading\\ Volume=Low): \\frac{2}{6}$ 如果交易量为 high 并且回报为上涨，概率为：$P(Trading\\ Volume=High\\ \u0026amp;\\ Return\\ = Up)=\\frac{4}{4}$\n如果交易量为 high 并且回报为下降，概率为：$P(Trading\\ Volume = High\\ \u0026amp;\\ Return\\ = Down)=0$\n那么这个基尼指数为：$gini(Trading\\ Volume=High) = 1-(\\frac{4}{4}^2+0^2) = 0$ 如果交易量为 low 并且回报为上涨，概率为：$P(Trading\\ Volume = Low\\ \u0026amp;\\ Return\\ = Up)=0$\n如果交易量为 low 并且回报为下降，概率为：$P(Trading\\ Volume = Low\\ \u0026amp;\\ Return\\ = Down)=\\frac{2}{2}$\n那么这个基尼指数为：$gini(Trading\\ Volume=Low) = 1-(0^2+\\frac{2}{2}^2) = 0$ 那么交易量基尼指数加权 = $\\frac{4}{6} \\times 0 + \\frac{2}{6}\\times 0 = 0$\n最终计算出的基尼指数列表如下，这里将使用“Trading Volume”进一步拆分节点，因为它具有最小的基尼指数。\nAttributes/Features Gini Index Open Interest 0.33 Trading Volume 0 最终的模型就如图所示\n计算信息增益示例 我们可以根据属于一类数据的概率分布来考虑数据集的熵，例如，在二进制分类数据集的情况下为两个类。计算样本的熵如 $Entropy = -(P_0 \\times log(P_0) + P_1 \\times log(P_1)$ 。\n两类的样本拆分为 50/50 的数据集将具最大熵（最惊讶），而拆分为 10/90 的不平衡数据集将具有较小的熵。可以通过在 Python 中计算这个不平衡数据集的熵的例子来证明这一点。\npython 1 2 3 4 5 6 7 8 from math import log2 # 概率 class0 = 10/100 class1 = 90/100 # entropy formula entropy = -(class0 * log2(class0) + class1 * log2(class1)) # print the result print(\u0026#39;entropy: %.3f bits\u0026#39; % entropy) 运行示例，可以看到用于二分类的数据集的熵小于 1 。也就是说，对来自数据集中的任意示例类进行编码所需的信息不到1。通过这种方式，熵可以用作数据集纯度的计算，例如类别分布的平衡程度。\n熵为 0 位表示数据集包含一个类；1或更大位的熵表示平衡数据集的最大熵（取决于类别的数量），介于两者之间的值表示这些极端之间的水平。\n计算信息增益示例 要求：定义一个函数来根据属于 0 类和 1 类的样本的比率来计算一组样本的熵。\n假设有一个20 个示例的数据集，13 个为0 类，7 个为1 类。我们可以计算该数据集的熵，它的熵小于 1 位。\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 from math import log2 # calculate the entropy for the split in the dataset def entropy(class0, class1): return -(class0 * log2(class0) + class1 * log2(class1)) # split of the main dataset class0 = 13 / 20 class1 = 7 / 20 # calculate entropy before the change s_entropy = entropy(class0, class1) print(\u0026#39;Dataset Entropy: %.3f bits\u0026#39; % s_entropy) # Dataset Entropy: 0.934 bits 假设按照 value1 分割数据集，有一组 8 个样本的数据集，7 个为第 0 类，1 个用于第 1 类。然后我们可以计算这组样本的熵。\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 from math import log2 # calculate the entropy for the split in the dataset def entropy(class0, class1): return -(class0 * log2(class0) + class1 * log2(class1)) # split of the main dataset s1_class0 = 7 / 8 s1_class1 = 1 / 8 # calculate entropy before the change s_entropy = entropy(s1_class0, s1_class1) print(\u0026#39;Dataset Entropy: %.3f bits\u0026#39; % s_entropy) # Dataset Entropy: 0.544 bits 假设现在按 value2 分割数据集；一组 12 个样本数据集，每组 6 个。我们希望这个组的熵为 1。\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 from math import log2 # calculate the entropy for the split in the dataset def entropy(class0, class1): return -(class0 * log2(class0) + class1 * log2(class1)) # split of the main dataset s1_class0 = 6 / 12 s1_class1 = 6 / 12 # calculate entropy before the change s_entropy = entropy(s1_class0, s1_class1) print(\u0026#39;Dataset Entropy: %.3f bits\u0026#39; % s_entropy) # Dataset Entropy: 1.000 bits 最后，可以根据为变量的每个值创建的组和计算的熵来计算该变量的信息增益。例如：\n第一个变量从数据集中产生一组 8 个样本，第二组在数据集中有12 个样本。在这种情况下，信息增益计算：\n$Entropy(Dataset) – (\\frac{(Count(Group1)}{Count(Dataset)} \\times Entropy(Group1) + \\frac{Count(Group2)}{Count(Dataset)} \\times Entropy(Group2)))$ 这里是因为在每个子节点重复这个分裂过程直到空叶节点。这意味着每个节点的样本都属于同一类。但是，这种情况下会导致具有许多节点使非常深的树，这很容易导致过度拟合。因此，我们通常希望通过设置树的最大深度来修剪树。IG就是我们想确定给定训练特征向的量集中的哪个属性最有用，那么上面的公式推理就为：\n$IG(D_p) = I(D_p) − \\frac{N_{left}}{N_p}I(D_{left})−\\frac{N_{right}}{N_p}I(D_{right})$ $IG(D_P)$：数据集的信息增益 $I(D)$：叶子的熵或基尼指数 $\\frac{N}{N_P}$ ：页数据集占总数据集的比例 我们将使用它来决定决策树 节点中属性的顺序。该行为在python中表示为：\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 from math import log2 # calculate the entropy for the split in the dataset def entropy(class0, class1): return -(class0 * log2(class0) + class1 * log2(class1)) # split of the main dataset class0 = 13 / 20 class1 = 7 / 20 # calculate entropy before the change s_entropy = entropy(class0, class1) print(\u0026#39;Dataset Entropy: %.3f bits\u0026#39; % s_entropy) # split 1 (split via value1) s1_class0 = 7 / 8 s1_class1 = 1 / 8 # calculate the entropy of the first group s1_entropy = entropy(s1_class0, s1_class1) print(\u0026#39;Group1 Entropy: %.3f bits\u0026#39; % s1_entropy) # split 2 (split via value2) s2_class0 = 6 / 12 s2_class1 = 6 / 12 # calculate the entropy of the second group s2_entropy = entropy(s2_class0, s2_class1) print(\u0026#39;Group2 Entropy: %.3f bits\u0026#39; % s2_entropy) # calculate the information gain gain = s_entropy - (8/20 * s1_entropy + 12/20 * s2_entropy) print(\u0026#39;Information Gain: %.3f bits\u0026#39; % gain) # Dataset Entropy: 0.934 bits # Group1 Entropy: 0.544 bits # Group2 Entropy: 1.000 bits # Information Gain: 0.117 bits 通过实例，就可以很清楚的明白了，信息增益的概念：信息熵-条件熵，换句话来说就是==信息增益代表了在一个条件下，信息复杂度（不确定性）减少的程度==。\npython计算决策树实例 基于基尼指数的决策树 钞票数据集涉及根据从照片中采取的一系列措施来预测给定钞票是否是真实的。数据是取自真钞和伪钞样样本的图像中提取的。对于数字化，使用了通常用于印刷检查的工业相机，从图像中提取特征。\n该数据集包含 1372 行和 5 个数值变量。这是一个二元分类的问题。\n基尼指数 假设有两组数据，每组有 2 行。第一组的行都属于 0 类，第二组的行都属于 1 类，所以这是一个完美的拆分。\n首先需要计算每个组中类的比例。\npython 1 proportion = count(class_value) / count(rows) 这个比例是\npython 1 2 3 4 group_1_class_0 = 2 / 2 = 1 group_1_class_1 = 0 / 2 = 0 group_2_class_0 = 0 / 2 = 0 group_2_class_1 = 2 / 2 = 1 为每个子节点计算 Gini index\npython 1 2 gini_index = sum(proportion * (1.0 - proportion)) gini_index = 1.0 - sum(proportion * proportion) 然后对每组的基尼指数按组的大小加权，例如当前正在分组的所有样本。我们可以将此权重添加到组的基尼指数计算中，如下所示：\npython 1 gini_index = (1.0 - sum(proportion * proportion)) * (group_size/total_samples) 在该案例中，每个组的基尼指数为：\npython 1 2 3 4 5 6 Gini(group_1) = (1 - (1*1 + 0*0)) * 2/4 Gini(group_1) = 0.0 * 0.5 Gini(group_1) = 0.0 # 分类1的基尼指数 Gini(group_2) = (1 - (0*0 + 1*1)) * 2/4 Gini(group_2) = 0.0 * 0.5 Gini(group_2) = 0.0 # 分类2的基尼指数 然后在分割点的每个子节点上添加分数，以给出分割点的最终 Gini 分数，该分数可以与其他候选分割点进行比较。如该分割点的基尼系数为 $0.0 + 0.0$ 或完美的基尼系数 0.0。\n编写一个 gini_index() 的函数，用于计算组列表和已知类值列表的基尼指数。\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def gini_index(groups, classes): print(\u0026#34;------------\u0026#34;) # 计算所有样本的分割点，计算样本的总长度 n_instances = float(sum([len(group) for group in groups])) # 计算每个组的总基尼指数 gini = 0.0 for group in groups: size = float(len(group)) if size == 0: # avoid divide by zero continue score = 0.0 # score the group based on the score for each class for class_val in classes: # row[-1] 代表每个样本的最后一个值，是否存在分类 class_val p = [row[-1] for row in group] p1 = p.count(class_val) / size score += p1 * p1 # 按照对应的样本分割点，加权重 gini += (1.0 - score) * (size / n_instances) return gini print(gini_index([[[1, 1], [1, 0]], [[1, 1], [1, 0]]], [0, 1])) print(gini_index([[[1, 0], [1, 0]], [[1, 1], [1, 1]]], [0, 1])) 运行该示例会打印两组的Gini index，最差情况的为 0.5，最少情况的指数为 0.0。\n拆分 数据拆分 拆分是由数据集中的一个属性和一个值组成。可以将其总结为要拆分的属性的索引和拆分该属性上的行的值。这只是索引数据行的有用简写。\n创建拆分涉及三个部分，我们已经看过的第一个部分是计算基尼分数。剩下的两部分是：\n拆分数据集。 评估所有拆分。 拆分数据是给定数据集索引和拆分值，将数据集拆分为两个行列表形成一个分类。具体是拆分数据集涉及遍历每一行，检查属性值是否低于或高于拆分值，并将其分别分配给左组或右组。当存在两个组时，可以按照基尼指数进行评估\n编写一个**test_split()**函数，它实现了拆分。\npython 1 2 3 4 5 6 7 8 def test_split(index, value, dataset): left, right = list(), list() for row in dataset: if row[index] \u0026lt; value: left.append(row) else: right.append(row) return left, right 评估拆分的数据 给定一个数据集，必须检查每个属性上的每个值作为候选拆分，评估拆分的成本并找到我们可以进行的最佳拆分。一旦找到最佳值，就可以将其用作决策树中的节点。\n这里使用 dict 作为决策树中的节点，因为这样可以按名称存储数据。选择最佳基尼指数并将其用作树的新节点。\n每组数据都是其小数据集，其中仅包含通过拆分过程分配给左组或右组的那些行。可以想象我们如何在构建决策树时递归地再次拆分每个组。\npython 1 2 3 4 5 6 7 8 9 10 def get_split(dataset): class_values = list(set(row[-1] for row in dataset)) b_index, b_value, b_score, b_groups = 999, 999, 999, None for index in range(len(dataset[0])-1): for row in dataset: groups = test_split(index, row[index], dataset) gini = gini_index(groups, class_values) if gini \u0026lt; b_score: b_index, b_value, b_score, b_groups = index, row[index], gini, groups return {\u0026#39;index\u0026#39;:b_index, \u0026#39;value\u0026#39;:b_value, \u0026#39;groups\u0026#39;:b_groups} 之后准备一些测试数据集进行测试，其中 $Y$ 是测试集的分类\ntext 1 2 3 4 5 6 7 8 9 10 11 X1\tX2\tY 2.771244718\t1.784783929\t0 1.728571309\t1.169761413\t0 3.678319846\t2.81281357\t0 3.961043357\t2.61995032\t0 2.999208922\t2.209014212\t0 7.497545867\t3.162953546\t1 9.00220326\t3.339047188\t1 7.444542326\t0.476683375\t1 10.12493903\t3.234550982\t1 6.642287351\t3.319983761\t1 将上述代码整合为一起，运行该代码后会打印所有基尼指数，基尼指数为 0.0 或完美分割。\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 # Split a dataset based on an attribute and an attribute value def test_split(index, value, dataset): left, right = list(), list() for row in dataset: if row[index] \u0026lt; value: left.append(row) else: right.append(row) return left, right # Calculate the Gini index for a split dataset def gini_index(groups, classes): # 计算两组数据集的总数每个种类的列表数量和 n_instances = float(sum([len(group) for group in groups])) # 计算每组的基尼值 gini = 0.0 for group in groups: size = float(len(group)) # avoid divide by zero if size == 0: continue score = 0.0 # score the group based on the score for each class for class_val in classes: # 拿出数据集中每行的类型，拆开是为了更好的了解结构 p = [row[-1] for row in group] # print(\u0026#34;%f / %f = %f\u0026#34; % (p.count(class_val), size, p.count(class_val) / size )) # 这里计算的是当前的分类在总数据集中占比 p1 = p.count(class_val) / size score += p1 * p1 # gini index formula = 1 - sum(p_i^2) # 计算总的基尼指数，权重：当前分组占总数据集中的数量 gini += (1.0 - score) * (size / n_instances) return gini # Select the best split point for a dataset def get_split(dataset): class_values = list(set(row[-1] for row in dataset)) b_index, b_value, b_score, b_groups = 999, 999, 999, None for index in range(len(dataset[0])-1): # 最后分类不计算 for row in dataset: # 根据每个值分类计算出最优基尼值，这个值就作为决策树的节点 groups = test_split(index, row[index], dataset) gini = gini_index(groups, class_values) print(\u0026#39;X%d \u0026lt; %.3f Gini=%.3f\u0026#39; % ((index+1), row[index], gini)) if gini \u0026lt; b_score: b_index, b_value, b_score, b_groups = index, row[index], gini, groups return {\u0026#39;index\u0026#39;:b_index, \u0026#39;value\u0026#39;:b_value, \u0026#39;groups\u0026#39;:b_groups} dataset = [ [2.771244718,1.784783929,0], [1.728571309,1.169761413,0], [3.678319846,2.81281357,0], [3.961043357,2.61995032,0], [2.999208922,2.209014212,0], [7.497545867,3.162953546,1], [9.00220326,3.339047188,1], [7.444542326,0.476683375,1], [10.12493903,3.234550982,1], [6.642287351,3.319983761,1] ] split = get_split(dataset) print(\u0026#39;Split: [X%d \u0026lt; %.3f]\u0026#39; % ((split[\u0026#39;index\u0026#39;]+1), split[\u0026#39;value\u0026#39;])) 通过执行结果可以看出，X1 \u0026lt; 6.642 Gini=0.000 基尼指数为 0.0 为完美分割。\n如何构建树 构建树主要分为 3 个部分\n终端节点 Terminal Nodes 零度节点称为终端节点或叶节点 递归拆分 建造一棵树 终端节点 需要决定何时停止种植树，这里可以使用节点在训练数据集中负责的深度和行数来做到。\n树的最大深度：从树的根节点开始的最大节点数。一旦达到树的最大深度，停止拆分新节点。 最小节点：对一个节点的要训练的最小值。一旦达到或低于此最小值，则停止拆分和添加新节点。 这两种方法将是构建树的过程时用户的指定参数。当在给定点停止增长时，该节点称为终端节点，用于进行最终预测。\n编写一个函数to_terminal()，这个函数将为一组行选择一类。它返回行列表中最常见的输出值。\npython 1 2 3 def to_terminal(group): outcomes = [row[-1] for row in group] return max(set(outcomes), key=outcomes.count) 递归拆分 构建决策树会在为每个节点创建的组上一遍又一遍地调用 get_split() 函数。\n添加到现有节点的新节点称为子节点。一个节点可能有零个子节点（一个终端节点）、一个子节点或两个子节点，这里将在给定节点的字典表示中将子节点称为左和右。当一旦创建出一个节点，则通过再次调用相同的函数来递归地从拆分的每组数据以创建子节点。\n下面需要实现这个过程（递归函数）。函数接受一个节点作为参数，以及节点中的最大深度、最小模式数、节点的当前深度。\n调用的过程分步为。设置，传入根节点和深度1：\n首先，将拆分后的两组数据提取出来使用，当处理过这些组时，节点不再需要访问这些数据。 接下来，我们检查左或右两组是否为空，如果是，则使用我们拥有的记录创建一个终端节点。 不为空的情况下，检查是否达到了最大深度，如果是，则创建一个终端节点。 然后我们处理左子节点，如果行组太小，则创建一个终端节点，否则以深度优先的方式创建并添加左节点，直到在该分支上到达树的底部。最后再以相同的方式处理右侧。 python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # 创建子拆分或者终端节点 def split(node, max_depth, min_size, depth): \u0026#34;\u0026#34;\u0026#34; :param node: {},分割好的的{\u0026#39;index\u0026#39;:b_index, \u0026#39;value\u0026#39;:b_value, \u0026#39;groups\u0026#39;:b_groups} :param max_depth: int, 最大深度 :param min_size:int，最小模式数 :param depth:int， 当前深度 :return: None \u0026#34;\u0026#34;\u0026#34; left, right = node[\u0026#39;groups\u0026#39;] del(node[\u0026#39;groups\u0026#39;]) # 检查两边的分割问题 if not left or not right: node[\u0026#39;left\u0026#39;] = node[\u0026#39;right\u0026#39;] = to_terminal(left + right) return # 检查最大的深度 if depth \u0026gt;= max_depth: node[\u0026#39;left\u0026#39;], node[\u0026#39;right\u0026#39;] = to_terminal(left), to_terminal(right) return # 处理左分支，数量要小于最小模式数为terminal node if len(left) \u0026lt;= min_size: node[\u0026#39;left\u0026#39;] = to_terminal(left) else: node[\u0026#39;left\u0026#39;] = get_split(left) split(node[\u0026#39;left\u0026#39;], max_depth, min_size, depth+1) # 否则递归 # 处理左右支，数量要小于最小模式数为terminal node if len(right) \u0026lt;= min_size: node[\u0026#39;right\u0026#39;] = to_terminal(right) else: node[\u0026#39;right\u0026#39;] = get_split(right) split(node[\u0026#39;right\u0026#39;], max_depth, min_size, depth+1) 创建树 构建一个树就是一个上面的步骤的合并，通过**split()**函数打分并确定树的根节点，然后通过递归来构建出整个树；下面代码是实现此过程的函数 build_tree()。\npython 1 2 3 4 5 6 7 8 9 10 11 # Build a decision tree def build_tree(train, max_depth, min_size): \u0026#34;\u0026#34;\u0026#34; :param train: list, 数据集，可以是训练集 :param max_depth: int, 最大深度 :param min_size:int，最小模式数 :return: None \u0026#34;\u0026#34;\u0026#34; root = get_split(train) # 对整个数据集进行打分 split(root, max_depth, min_size, 1) return root 整合 将全部代码整合为一个\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 # Split a dataset based on an attribute and an attribute value def test_split(index, value, dataset): left, right = list(), list() for row in dataset: if row[index] \u0026lt; value: left.append(row) else: right.append(row) return left, right # Calculate the Gini index for a split dataset def gini_index(groups, classes): # 计算两组数据集的总数每个种类的列表数量和 n_instances = float(sum([len(group) for group in groups])) # 计算每组的基尼值 gini = 0.0 for group in groups: size = float(len(group)) # avoid divide by zero if size == 0: continue score = 0.0 # score the group based on the score for each class for class_val in classes: # 拿出数据集中每行的类型，拆开是为了更好的了解结构 p = [row[-1] for row in group] # print(\u0026#34;%f / %f = %f\u0026#34; % (p.count(class_val), size, p.count(class_val) / size )) # 这里计算的是当前的分类在总数据集中占比 p1 = p.count(class_val) / size score += p1 * p1 # gini index formula = 1 - sum(p_i^2) # 计算总的基尼指数，权重：当前分组占总数据集中的数量 gini += (1.0 - score) * (size / n_instances) return gini # Select the best split point for a dataset def get_split(dataset): class_values = list(set(row[-1] for row in dataset)) b_index, b_value, b_score, b_groups = 999, 999, 999, None for index in range(len(dataset[0])-1): # 最后分类不计算 for row in dataset: # 根据每个值分类计算出最优基尼值，这个值就作为决策树的节点 groups = test_split(index, row[index], dataset) gini = gini_index(groups, class_values) # print(\u0026#39;X%d \u0026lt; %.3f Gini=%.3f\u0026#39; % ((index+1), row[index], gini)) if gini \u0026lt; b_score: # 拿到最小的gini index那列 b_index, b_value, b_score, b_groups = index, row[index], gini, groups return {\u0026#39;index\u0026#39;:b_index, \u0026#39;value\u0026#39;:b_value, \u0026#39;groups\u0026#39;:b_groups} # 创建子拆分或者终端节点 def split(node, max_depth, min_size, depth): \u0026#34;\u0026#34;\u0026#34; :param node: {},分割好的的{\u0026#39;index\u0026#39;:b_index, \u0026#39;value\u0026#39;:b_value, \u0026#39;groups\u0026#39;:b_groups} :param max_depth: int, 最大深度 :param min_size:int，最小模式数 :param depth:int， 当前深度 :return: None \u0026#34;\u0026#34;\u0026#34; left, right = node[\u0026#39;groups\u0026#39;] del(node[\u0026#39;groups\u0026#39;]) # 检查两边的分割问题 if not left or not right: node[\u0026#39;left\u0026#39;] = node[\u0026#39;right\u0026#39;] = to_terminal(left + right) return # 检查最大的深度 if depth \u0026gt;= max_depth: node[\u0026#39;left\u0026#39;], node[\u0026#39;right\u0026#39;] = to_terminal(left), to_terminal(right) return # 处理左分支，数量要小于最小模式数为terminal node if len(left) \u0026lt;= min_size: node[\u0026#39;left\u0026#39;] = to_terminal(left) else: node[\u0026#39;left\u0026#39;] = get_split(left) split(node[\u0026#39;left\u0026#39;], max_depth, min_size, depth+1) # 否则递归 # 处理左右支，数量要小于最小模式数为terminal node if len(right) \u0026lt;= min_size: node[\u0026#39;right\u0026#39;] = to_terminal(right) else: node[\u0026#39;right\u0026#39;] = get_split(right) split(node[\u0026#39;right\u0026#39;], max_depth, min_size, depth+1) # Build a decision tree def build_tree(train, max_depth, min_size): \u0026#34;\u0026#34;\u0026#34; :param train: list, 数据集，可以是训练集 :param max_depth: int, 最大深度 :param min_size:int，最小模式数 :return: None \u0026#34;\u0026#34;\u0026#34; root = get_split(train) # 对整个数据集进行打分 split(root, max_depth, min_size, 1) return root # 打印树 def print_tree(node, depth=0): if isinstance(node, dict): print(\u0026#39;%s[X%d \u0026lt; %.3f]\u0026#39; % ( (depth*\u0026#39; \u0026#39;, (node[\u0026#39;index\u0026#39;]+1), node[\u0026#39;value\u0026#39;]) )) print_tree(node[\u0026#39;left\u0026#39;], depth+1) # 递归打印左右 print_tree(node[\u0026#39;right\u0026#39;], depth+1) else: print(\u0026#39;%s[%s]\u0026#39; % ((depth*\u0026#39; \u0026#39;, node))) # 不是对象就是terminal node # 创建一个terminal node def to_terminal(group): outcomes = [row[-1] for row in group] return max(set(outcomes), key=outcomes.count) dataset = [ [2.771244718,1.784783929,0], [1.728571309,1.169761413,0], [3.678319846,2.81281357,0], [3.961043357,2.61995032,0], [2.999208922,2.209014212,0], [7.497545867,3.162953546,1], [9.00220326,3.339047188,1], [7.444542326,0.476683375,1], [10.12493903,3.234550982,1], [6.642287351,3.319983761,1] ] if __name__==\u0026#39;__main__\u0026#39;: tree = build_tree(dataset, 4, 2) print_tree(tree) 可以看到打印结果是一个类似二叉树的\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 [X1 \u0026lt; 6.642] [X1 \u0026lt; 2.771] [0] [X1 \u0026lt; 2.771] [0] [0] [X1 \u0026lt; 7.498] [X1 \u0026lt; 7.445] [1] [1] [X1 \u0026lt; 7.498] [1] [1] 预测 预测是预测数据是该向右还是向左，是作为对数据进行导航的方式。这里可以使用递归来实现，其中使用左侧或右侧子节点再次调用相同的预测，具体取决于拆分如何影响提供的数据。\n我们必须检查子节点是否是要作为预测返回的终端值，或者它是否是包含要考虑的树的另一个级别的字典节点。\n下面是实现此过程的函数 predict()。\npython 1 2 3 4 5 6 7 8 9 10 11 12 # Make a prediction with a decision tree def predict(node, row): if row[node[\u0026#39;index\u0026#39;]] \u0026lt; node[\u0026#39;value\u0026#39;]: if isinstance(node[\u0026#39;left\u0026#39;], dict): return predict(node[\u0026#39;left\u0026#39;], row) else: return node[\u0026#39;left\u0026#39;] else: if isinstance(node[\u0026#39;right\u0026#39;], dict): return predict(node[\u0026#39;right\u0026#39;], row) else: return node[\u0026#39;right\u0026#39;] 下面是一个使用硬编码决策树的示例，该决策树具有一个最好地分割数据的节点（决策树桩，这个就是gini index的最优质值）。通过对上面的测试数据集例来对每一行进行预测。\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def predict(node, row): # 如果gini index与对应属性的值小于则向左， if row[node[\u0026#39;index\u0026#39;]] \u0026lt; node[\u0026#39;value\u0026#39;]: if isinstance(node[\u0026#39;left\u0026#39;], dict): return predict(node[\u0026#39;left\u0026#39;], row) # 递归处理完整个树 else: return node[\u0026#39;left\u0026#39;] else: # 否则的话，则为右 if isinstance(node[\u0026#39;right\u0026#39;], dict): return predict(node[\u0026#39;right\u0026#39;], row) else: return node[\u0026#39;right\u0026#39;] dataset = [[2.771244718,1.784783929,0], [1.728571309,1.169761413,0], [3.678319846,2.81281357,0], [3.961043357,2.61995032,0], [2.999208922,2.209014212,0], [7.497545867,3.162953546,1], [9.00220326,3.339047188,1], [7.444542326,0.476683375,1], [10.12493903,3.234550982,1], [6.642287351,3.319983761,1]] # 这是之前用于计算出最优的gini index stump = {\u0026#39;index\u0026#39;: 0, \u0026#39;right\u0026#39;: 1, \u0026#39;value\u0026#39;: 6.642287351, \u0026#39;left\u0026#39;: 0} for row in dataset: prediction = predict(stump, row) print(\u0026#39;Expected=%d, Got=%d\u0026#39; % (row[-1], prediction)) 通过观察可以看出预测结果和实际结果一样\npython 1 2 3 4 5 6 7 8 9 10 Expected=0, Got=0 Expected=0, Got=0 Expected=0, Got=0 Expected=0, Got=0 Expected=0, Got=0 Expected=1, Got=1 Expected=1, Got=1 Expected=1, Got=1 Expected=1, Got=1 Expected=1, Got=1 套用真实数据集来测试 这里将使用 CART 算法对银行钞票数据集进行预测。大概的流程为：\n加载数据集并转换格式。 编写拆分算法与准确度计算算法；这里使用 5折的k折交叉验证（k-fold cross validation）用于评估算法 编写 CART 算法，从训练数据集，创建树，对测试数据集进行预测操作 什么是 K折交叉验证 K折较差验证（K-Fold CV）是将给定的数据集分成K个部分，其中每个折叠在某时用作测试集。以 5 折（K=5）为例。这种情况下，数据集被分成5份。在第一次迭代中，第一份用于测试模型，其余用于训练模型。在第二次迭代中，第 2 份用作测试集，其余用作训练集。重复这个过程，直到 5 个折叠中的每个折叠都被用作测试集。\n下面来开始编写函数，函数的整个过程为\nevaluate_algorithm() 作为最外层调用 使用五折交叉进行评估 cross_validation_split() 使用决策树算法作为算法根据 decision_tree() 构建树：build_tree() 拿到最优基尼指数作为叶子 get_split() python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 from random import seed from random import randrange from csv import reader # 加载csv文件 def load_csv(filename): file = open(filename, \u0026#34;rt\u0026#34;) lines = reader(file) dataset = list(lines) return dataset # 将所有字段转换为float类型便于计算 def str_column_to_float(dataset, column): for row in dataset: row[column] = float(row[column].strip()) # k-folds CV函数 def cross_validation_split(dataset, n_folds): dataset_split = list() dataset_copy = list(dataset) # 平均分位折数n_folds fold_size = int(len(dataset) / n_folds) for i in range(n_folds): fold = list() while len(fold) \u0026lt; fold_size: index = randrange(len(dataset_copy)) # 随机 fold.append(dataset_copy.pop(index)) dataset_split.append(fold) return dataset_split # 计算精确度 def accuracy_metric(actual, predicted): correct = 0 for i in range(len(actual)): if actual[i] == predicted[i]: correct += 1 return correct / float(len(actual)) * 100.0 # Evaluate an algorithm using a cross validation split def evaluate_algorithm(dataset, algorithm, n_folds, *args): folds = cross_validation_split(dataset, n_folds) scores = list() for fold in folds: train_set = list(folds) train_set.remove(fold) train_set = sum(train_set, []) test_set = list() for row in fold: row_copy = list(row) test_set.append(row_copy) row_copy[-1] = None predicted = algorithm(train_set, test_set, *args) actual = [row[-1] for row in fold] accuracy = accuracy_metric(actual, predicted) scores.append(accuracy) return scores # 根据基尼指数划分value是应该在树的哪边？ def test_split(index, value, dataset): left, right = list(), list() for row in dataset: if row[index] \u0026lt; value: left.append(row) else: right.append(row) return left, right # 基尼指数打分 def gini_index(groups, classes): # 计算数据集中的多组数据的总个数 n_instances = float(sum([len(group) for group in groups])) # 计算每组中的最优基尼指数 gini = 0.0 for group in groups: size = float(len(group)) if size == 0: continue score = 0.0 # 总基尼指数 for class_val in classes: # 拿出数据集中每行的类型，拆开是为了更好的了解结构 # 计算的是当前的分类在总数据集中占比 p = [row[-1] for row in group] p1 = p.count(class_val) / size score += p1 * p1 # 计算总的基尼指数，并根据相应大小增加权重。权重：当前分组占总数据集中的数量 gini += (1.0 - score) * (size / n_instances) return gini # 从数据集中获得基尼指数最佳的值 def get_split(dataset): class_values = list(set(row[-1] for row in dataset)) b_index, b_value, b_score, b_groups = 999, 999, 999, None for index in range(len(dataset[0])-1): for row in dataset: groups = test_split(index, row[index], dataset) gini = gini_index(groups, class_values) if gini \u0026lt; b_score: b_index, b_value, b_score, b_groups = index, row[index], gini, groups return {\u0026#39;index\u0026#39;:b_index, \u0026#39;value\u0026#39;:b_value, \u0026#39;groups\u0026#39;:b_groups} # 创建终端节点 def to_terminal(group): outcomes = [row[-1] for row in group] return max(set(outcomes), key=outcomes.count) # 创建子节点，为终端节点或子节点 def split(node, max_depth, min_size, depth): \u0026#34;\u0026#34;\u0026#34; :param node: {},分割好的的{\u0026#39;index\u0026#39;:b_index, \u0026#39;value\u0026#39;:b_value, \u0026#39;groups\u0026#39;:b_groups} :param max_depth: int, 最大深度 :param min_size:int，最小模式数 :param depth:int， 当前深度 :return: None \u0026#34;\u0026#34;\u0026#34; left, right = node[\u0026#39;groups\u0026#39;] del(node[\u0026#39;groups\u0026#39;]) # check for a no split if not left or not right: node[\u0026#39;left\u0026#39;] = node[\u0026#39;right\u0026#39;] = to_terminal(left + right) return # check for max depth if depth \u0026gt;= max_depth: node[\u0026#39;left\u0026#39;], node[\u0026#39;right\u0026#39;] = to_terminal(left), to_terminal(right) return # process left child if len(left) \u0026lt;= min_size: node[\u0026#39;left\u0026#39;] = to_terminal(left) else: node[\u0026#39;left\u0026#39;] = get_split(left) split(node[\u0026#39;left\u0026#39;], max_depth, min_size, depth+1) # process right child if len(right) \u0026lt;= min_size: node[\u0026#39;right\u0026#39;] = to_terminal(right) else: node[\u0026#39;right\u0026#39;] = get_split(right) split(node[\u0026#39;right\u0026#39;], max_depth, min_size, depth+1) # 构建树 def build_tree(train, max_depth, min_size): \u0026#34;\u0026#34;\u0026#34; :param train: list, 数据集，可以是训练集 :param max_depth: int, 最大深度 :param min_size:int，最小模式数 :ret \u0026#34;\u0026#34;\u0026#34; root = get_split(train) split(root, max_depth, min_size, 1) return root # 打印树 def print_tree(node, depth=0): if isinstance(node, dict): print(\u0026#39;%s[X%d \u0026lt; %.3f]\u0026#39; % ( (depth*\u0026#39; \u0026#39;, (node[\u0026#39;index\u0026#39;]+1), node[\u0026#39;value\u0026#39;]) )) print_tree(node[\u0026#39;left\u0026#39;], depth+1) # 递归打印左右 print_tree(node[\u0026#39;right\u0026#39;], depth+1) else: print(\u0026#39;%s[%s]\u0026#39; % ((depth*\u0026#39; \u0026#39;, node))) # 不是对象就是terminal node # 预测，预测方式为当前基尼指数与最优基尼指数相比较，然后放入树两侧 def predict(node, row): \u0026#34;\u0026#34;\u0026#34; :param node: {} 叶子值 :param row: {}, 需要预测值 :ret \u0026#34;\u0026#34;\u0026#34; if row[node[\u0026#39;index\u0026#39;]] \u0026lt; node[\u0026#39;value\u0026#39;]: if isinstance(node[\u0026#39;left\u0026#39;], dict): return predict(node[\u0026#39;left\u0026#39;], row) else: return node[\u0026#39;left\u0026#39;] else: if isinstance(node[\u0026#39;right\u0026#39;], dict): return predict(node[\u0026#39;right\u0026#39;], row) else: return node[\u0026#39;right\u0026#39;] def decision_tree(train, test, max_depth, min_size): tree = build_tree(train, max_depth, min_size) predictions = list() for row in test: prediction = predict(tree, row) predictions.append(prediction) return(predictions) # Test CART on Bank Note dataset seed(1) # 加载数据 filename = \u0026#39;data_banknote_authentication.csv\u0026#39; dataset = load_csv(filename) # 转换格式 for i in range(len(dataset[0])): str_column_to_float(dataset, i) # 评估算法 n_folds = 5 max_depth = 5 min_size = 10 scores = evaluate_algorithm(dataset, decision_tree, n_folds, max_depth, min_size) print(\u0026#39;Scores: %s\u0026#39; % scores) print(\u0026#39;Mean Accuracy: %.3f%%\u0026#39; % (sum(scores)/float(len(scores)))) Reference Informatioin Gain\nimplement decision tree algorithm\ninplement information gain\n","permalink":"https://www.161616.top/decision-tree/","summary":"熵和基尼指数 信息增益 信息增益 information gain 是用于训练决策树的指标。具体来说，是指这些指标衡量拆分的质量。通俗来说是通过根据随机变量的给定值拆分数据集来衡量熵。\n通过描述一个事件是否\u0026quot;惊讶\u0026quot;，通常低概率事件更令人惊讶，因此具有更大的信息量。而具有相同可能性的事件的概率分布更\u0026quot;惊讶\u0026quot;并且具有更大的熵。\n定义：熵 entropy是一组例子中杂质、无序或不确定性的度量。熵控制决策树如何决定拆分数据。它实际上影响了决策树如何绘制边界。\n熵 熵的计算公式为：$E=-\\sum^i_{i=1}(p_i\\times\\log_2(p_i))$ ；$P_i$ 是类别 $i$ 的概率。我们来举一个例子来更好地理解熵及其计算。假设有一个由三种颜色组成的数据集，红色、紫色和黄色。如果我们的集合中有一个红色、三个紫色和四个黄色的观测值，我们的方程变为：$E=-(p_r \\times \\log_2(p_r) + p_p \\times \\log_2(p_p) + p_y \\times \\log_2(p_y)$\n其中 $p_r$ 、$p_p$ 和 $p_y$ 分别是选择红色、紫色和黄色的概率。假设 $p_r=\\frac{1}{8}$，$p_p=\\frac{3}{8}$ ，$p_y=\\frac{4}{8}$ 现在等式变为变为：\n$E=-(\\frac{1}{8} \\times \\log_2(\\frac{1}{8}) + \\frac{3}{8} \\times \\log_2(\\frac{3}{8}) + \\frac{4}{8} \\times \\log_2(\\frac{4}{8}))$ $0.125 \\times log_2(0.125) + 0.375 \\times log_2(0.375) + 0.5 \\times log_2(0.375)$ $0.125 \\times -3 + 0.375 \\times -1.415 + 0.5 \\times -1 = -0.375+-0.425 +-0.5 = 1.","title":"决策树"},{"content":"Overview 逻辑回归通常用于分类算法，例如预测某事是 true 还是 false（二元分类）。例如，对电子邮件进行分类，该算法将使用电子邮件中的单词作为特征，并据此预测电子邮件是否为垃圾邮件。用数学来讲就是指，假设因变量是 Y，而自变量集是 X，那么逻辑回归将预测因变量 $P(Y=1)$ 作为自变量集 X 的函数。\n逻辑回归性能在线性分类中是最好的，其核心为基于样本属于某个类别的概率。这里的概率必须是连续的并且在 (0, 1) 之间（有界）。它依赖于阈值函数来做出称为 Sigmoid 或 Logistic 函数决定的。\n学好逻辑回归，需要了解逻辑回归的概念、优势比 (OR) 、Logit 函数、Sigmoid 函数、 Logistic 函数及交叉熵或Log Loss\nPrerequisite odds ratio explain odds ratio是预测变量的影响。优势比取决于预测变量是分类变量还是连续变量。\n连续预测变量：$OR \u0026gt; 1$ 表示，随着预测变量的增加，事件发生的可能性增加。$OR \u0026lt; 1$ 表示随着预测变量的增加，事件发生的可能性较小。 分类预测变量：事件发生在预测变量的 2 个不同级别的几率；如 A,B，$OR \u0026gt; 1$ 表示事件在 A 级别的可能性更大。$OR\u0026lt;1$ 表示事件更低的可能是在A。 例如，假设 X 是受影响的概率，Y 是不受影响的概率，则 $OR= \\frac{X}{Y}$ ，那么 $OR = \\frac{P}{(1-P)}$ ，P是事件的概率。\n让概率的范围为 [0,1] ，假设 $P(success)=0.8$ ，$Q(failure) = 0.2$ ；$OR$ 则是 成功概率和失败概率的比值，如：$O(success)=\\frac{P}{Q} = \\frac{0.8}{0.2} = 4$ , $O(failure)=\\frac{Q}{P} = \\frac{0.2}{0.8} = 0.25$ 。\nodds和probability 的区别 probability 表示在多次实验中，看到改事件的几率，位于 [0,1] 之间\nodds 表示 $\\frac{(事件发生的概率)}{(事件不会发生的概率)}$ 的比率，位于 [0,∞]\n例如赛马，一匹马跑 100 场比赛，赢了 80 场，那么获胜的概率是 $\\frac{80}{100} = 0.80 = 80%$ ，获胜的几率是 $\\frac{80}{20}=4:1$\n总结：probability 和 odds 之间的主要区别：\n“odds”用于描述是否有可能发生事件。相反，probability决定了事件发生的可能性，即事件发生的频率。 odds以比例表示，probability以百分比形式或小数表示。 odds通常从 0 ~ ∞ ，其中0定义事件发生的可能性，∞ 表示发生的可能性。相反，probability 介于 0~1之间。因此，probability越接近于0，不发生的可能性就越大，越接近于1，发生的可能性就越高。 Reference The Difference Between \u0026ldquo;Probability\u0026rdquo; and \u0026ldquo;Odds\u0026rdquo;\n通过示例陈述公式 假设一个体校的录取率中，10 个男生中有 7 个被录取，而10 个女生中有3个被录取。找出男生被录取的概率？\n那么通过已知条件，设 P 为录取概率，Q则为未被录取的概率，那么\n男生被录取的概率为： $P=\\frac{7}{10} = 0.7$ $Q=1-0.7 = 0.3$ 女生被录取的概率为： $P=\\frac{3}{10}=0.3$ $Q=1-0.3=0.7$ 录取优势比： $OR(boy)=\\frac{0.7}{0.3}=2.33$ $OR(Gril) = \\frac{0.3}{0.7}=0.42$ 因此，一个男生被录取的几率为 $OR=\\frac{2.33}{0.42}=5.44$\nLogit 函数 logit函数是Odd Ratio 的对数 logarithm , 给出 0~1 范围内的输入，然后将它们转换为整个实数范围内的值。如：假设P，则 $\\frac{P}{(1-P)}$ 为对应的OR；OR 的 logit 的公式为：$loggit(P) = log(odds) = log(\\frac{P}{1-P})$.\n以一辆汽车是否出售为例，1为出售，0为不出售，则等式 $P_i=B_0+B_1 * (Price_i) + \\epsilon$\n$ln(\\frac{P}{1-P}) = \\beta_0 + \\beta_1X_1+\\beta_2X_2\u0026hellip; + \\beta_nX_N$ ,对于简单的逻辑回归，有两个系数：\n$\\beta_0$ 截距 ：X 变量为 0 时的对数 odds ratio $\\beta_1$ 斜率：odds ratio随X增加（或减少），1的变化 例如：假设简单逻辑回归模型是 $Ln(odds) = -5.5 + 1.2*X$ ,那么 $\\beta_0=-5.5$ ，$\\beta_1 = 1.2$ ，意味着，X=0时，$odds\\ ratio = 0$ ，X每增加一个单位 odds ration 增加 1.2（（X 增加2个单位odds ratio增加 2.4\u0026hellip;.）\n求解\n通过上面的公式实际上不明白这些具体是什么，就可以通过求P来找到有结果的概率与截距 $β_0$ 之间的关系，已知 $n=log_ab$ , $ a^n=b$ ，那么一个简单的逻辑回归公式为 $log(\\frac{P}{1-P}) = \\beta_0+\\beta1X$ ，对这个公式进行推导：\n$\\frac{P}{1-P} = e^{\\beta_0+e^\\beta1*X}$ $P = e^{\\beta_0+e^\\beta1X} - Pe^{\\beta_0+e^\\beta1X}$ $P(1+e^{\\beta_0+e^\\beta1X}) = e^{\\beta_0+e^\\beta1X}$ $P=\\frac{e^{\\beta_0+e^\\beta1X}}{1+e^{\\beta_0+e^\\beta1X}}$ 当 $X=0$ ,则 $\\beta_1*X$ 没意义，公式为：$P = frac{e^{β_0}}{(1+e^{β_0})}$ ，其中e是一个常数，python为 math.e\n如果单纯不算概率，只看截距符号，那么满足：\n如果截距为负号：则产生结果的概率将 \u0026lt; 0.5。 如果截距为正号：那么产生结果的概率将 \u0026gt; 0.5。 如果截距等于 0：那么得到结果的概率正好是 0.5。 通过例子来说明这点：假设研究为抽烟对心脏健康的影响，下表显示了一个逻辑回归\nCoefficient Standard Error p-value Intercept -1.93 0.13 \u0026lt; 0.001 Smoking 0.38 0.17 0.03 由表可知，截距为 -1.93，假设smoking系数为0，那么概率带入公式为：$P=\\frac{e^{\\beta_0}}{1+e^{\\beta_0}} = P=\\frac{e^{-1.93}}{1+e^{-1.93}} = 0.126$(math.e ** -1.93)/(1+math.e ** -1.93)\n如果 Smoking是一个连续变量（每年的吸烟量），在这种情况下，Smoking=0 意味着每年使用0公斤烟草的人即不抽烟的人群；那么这个结果就为，不抽烟的人群在未来10年内心脏有问题几率为 0.126。\n再如果是吸烟者应该怎么计算，假设，每年吸烟量为3kg，那么公式为：$P = \\frac{e^{β0 + β_1X}}{(1+e^{β0 + β_1X})}$ ，在这里 X=3，那么 $P=\\frac{e^{\\beta_1+\\beta_2X}}{(1-e^{\\beta_1+\\beta_2X})} = \\frac{e^{-1.93+0.383}}{(1-e^{-1.93+0.383})} = 0.31$ ；即得出，每年3KG烟草消耗量10年后有心脏问题的概率是 31%\ninterpret\nsigmoid logit 函数的逆函数称Sigmoid 函数，sigmoid方程来源于 logit 为：$P=\\frac{e^{log(odds)}}{(1-e^{log(odds)})} = \\frac{1}{e^{-log(odds)+1}} = \\frac{1}{1+e^{-z}}$ 。\n在python中，np.exp 是求 是求 $e^{x}$ 的值的函数。正好可以用在sigmod函数中，那么sigmoid可以写为\npython 1 2 def sigmoid(z): return 1 / (1 + np.exp(-z)) 交叉熵或对数损失 交叉熵 Cross-Entropy，通常用于量化两个概率分布之间的差异。用于逻辑回归，公式为：$H=\\sum^{x=n}(P(x) \\times log(q(x))$\nMaximum Likelihood Estimation 最大似然估计，Maximum Likelihood Estimation MLE，是概率估算的一种解决方案。MLE在其中寻找一组参数，这些参数将影响数据样本 X 的联合概率的最佳拟合。\n首先，定义一个称为 $\\theta$ theta 的参数，该参数定义概率密度函数的选择和该分布的参数。它可能是一个数值向量，其值平滑变化并映射到不同的概率分布及其参数。在最大似然估计中，我们希望在给定特定概率分布及其参数的最大化情况下从联合概率分布中观察数据的概率，形式上表示为：$P(X|\\theta)$ ，在这种情况下，条件概率通常使用分号 ; 而不是竖线 | ，因为 $\\theta$ 不是随机变量，而是未知参数。表达为 $P(X;\\theta)$ ,或 $P(x_1,x_2,\\ \u0026hellip;\\ x_n;\\theta)$ 。\n这样产生的条件概率被称为在给定模型参数 （$\\theta$）的情况下观察变量 $X$ 的概率，并使用符号 L 来 表示似然函数。例如：$L(X;\\theta)$。而最大似然估计的目标是找到使似然函数最大化的一组参数 ( $\\theta$ )，例如产生最大似然值，如：$max(L(X;\\theta))$\n鉴于上述提到的变量 $X$ 是由n个样本组成，可以将其定义为在给定概率分布参数 $\\theta$ 的情况下，变量 $X$ 的联合概率,如这里数据样本为 $x_1,x_2,\\ \u0026hellip;\\ ,x_n$ 的联合概率，同时表示为 $L(x_1,x2,\\ \u0026hellip;\\ ,x_n;\\theta)$\n大多数情况下，求解似然方程很复杂。会使用对数似然作为一种解决方案。由于对数函数是单调递增的，因此对数似然和似然中的最优参数是相同的。因此定义条件最大似然估计为：$log(P(x_i ; h))$。\n用逻辑回归模型替换h，需要假设一个概率分布。在逻辑回归的情况下，假设数据样本为二项式概率分布，其中每个示例都是二项式的一个结果。伯努利分布只有一个参数：成功结果的概率 P，那么为：\n$P(y=1)=P$ $P(y=0)=1-P$ 那么这个平均值为：$P(y=1)*1+P(y=0)0$，给出P的值公式可以转换为：$P1+(1-p)*0$；这种公式看似没有意义，那么通过一个小例子来了解下\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 二项式似然函数 def likelihood(y, p): return p * y + (1 - p) * (1 - y) # test for y=1 y, p = 1, 0.9 print(\u0026#39;y=%.1f, p=%.1f, likelihood: %.3f\u0026#39; % (y, p, likelihood(y, p))) y, yhat = 1, 0.1 print(\u0026#39;y=%.1f, p=%.1f, likelihood: %.3f\u0026#39; % (y, p, likelihood(y, p))) # test for y=0 y, yhat = 0, 0.1 print(\u0026#39;y=%.1f, p=%.1f, likelihood: %.3f\u0026#39; % (y, p, likelihood(y, p))) y, yhat = 0, 0.9 print(\u0026#39;y=%.1f, p=%.1f, likelihood: %.3f\u0026#39; % (y, p, likelihood(y, p))) # y=1.0, p=0.9, likelihood: 0.900 # y=1.0, p=0.9, likelihood: 0.900 # y=0.0, p=0.9, likelihood: 0.100 # y=0.0, p=0.9, likelihood: 0.100 运行示例会为每个案例打印类别y 和预测概率p，其中每个案例的概率是否接近；这里也可以使用对数更新似然函数，$log(p) * y + log(1 – p) * (1 – y)$；最后可以根据数据集中实例求最大似然和最小似然\n$\\sum^{i=1}_n log(p_i) * y_i + log(1 – p_i) * (1 – y_i)$ 最小似然使用反转函数，使负对数自然作为最小似然。上面的公式前加 - 对于计算二项式分布的对数似然相当于计算二项式分布[交叉熵，其中P(class)表示第 class 项概率，q() 表示概率分布，$-(log(q(class0)) \\times P(class0) + log(q(class1)) * P(class1))$\nLR算法实例 在研究如何从数据中估计模型的参数之前，我们需要了解逻辑回归准确计算的内容。\n模型的线性部分（输入的加权和）计算成功事件的log-odds。\nodds ratio：$\\beta_0+\\beta_1 \\times x_1 + \\beta_2 \\times x_2\\ \u0026hellip;\\ \\beta_n \\times x_n$ 该模型估计了每个级别的输入变量的log-odds。\n由上面信息了解到，几率 probability 是输赢的比率 如 1:10 ；probability 可以转换为 odds ratio 即成功概率除以不成功概率：$or=\\frac{P}{1-P}$ ；计算or的对数，被称为log-odds是一种度量单位：$log(\\frac{P}{1-P})$，而所求的即为 log-odds的逆函数，而在python中 log 函数是对数，求log的逆方法即 exp 返回n的x次方就是log的逆函数。\n到这里已经和逻辑回归模型很接近了，对数函数公式可以简化为，$P=\\frac{e^{log(odds)}}{(1-e^{log(odds)})}$ ，以上阐述了如何从log-odds转化为odds，然后在到逻辑回归模型。下面通过Python 中的示例来具体计算 probability 、odds 和 log-odds 之间的转换。假设将成功概率定义为 80% 或 0.8，然后将其转换为odds，然后再次转换为概率。\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from math import log from math import exp prob = 0.8 print(\u0026#39;Probability %.1f\u0026#39; % prob) # 将 probability 转换为 odds odds = prob / (1 - prob) print(\u0026#39;Odds %.1f\u0026#39; % odds) # 将 odds 转换为 log-odds logodds = log(odds) print(\u0026#39;Log-Odds %.1f\u0026#39; % logodds) # 转换 log-odds 为 probability prob = 1 / (1 + exp(-logodds)) print(\u0026#39;Probability %.1f\u0026#39; % prob) # Probability 0.8 # Odds 4.0 # Log-Odds 1.4 # Probability 0.8 通过这个例子，可以看到odds被转换成大约 1.4 的log-odds，然后正确地转回 0.8 的成功概率。\n逻辑回归实现 首先将实现分为3个步骤：\n预测 评估系数 真实数据集预测 预测 编写一个预测函数，在评估随机梯度下降中的候选系数值时以及在模型最终确定测试数据或新数据进行预测时。\n下面是预测**predict()**函数，它预测给定一组系数的行的输出值。第一个系数是截距，也称为偏差或 b0，它是独立的，不负责输入值。\npython 1 2 3 4 5 def predict(row, coefficients): p = coefficients[0] for i in range(len(row)-1): yhat += coefficients[i + 1] * row[i] return 1.0 / (1.0 + exp(-p)) 准备一些测试数据，Y代表真实的类别\npython 1 2 3 4 5 6 7 8 9 10 11 X1\tX2\tY 2.7810836 2.550537003\t0 1.465489372\t2.362125076\t0 3.396561688\t4.400293529\t0 1.38807019\t1.850220317\t0 3.06407232\t3.005305973\t0 7.627531214\t2.759262235\t1 5.332441248\t2.088626775\t1 6.922596716\t1.77106367\t1 8.675418651\t-0.242068655\t1 7.673756466\t3.508563011\t1 这里有两个输入值，和三个系数，系数是自定义的固定值，那么预测的公式就为\npython 1 2 3 4 5 # 系数为 coef = [-0.406605464, 0.852573316, -1.104746259] y = 1.0 / (1.0 + e^(-(b0 + b1 * X1 + b2 * X2))) # 套入公式（sigma） y = 1.0 / (1.0 + e^(-(-0.406605464 + 0.852573316 * X1 + -1.104746259 * X2))) 完整的代码\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # Make a prediction from math import exp # Make a prediction with coefficients def predict(row, coefficients): yhat = coefficients[0] for i in range(len(row)-1): yhat += coefficients[i + 1] * row[i] return 1.0 / (1.0 + exp(-yhat)) # test predictions dataset = [[2.7810836,2.550537003,0], [1.465489372,2.362125076,0], [3.396561688,4.400293529,0], [1.38807019,1.850220317,0], [3.06407232,3.005305973,0], [7.627531214,2.759262235,1], [5.332441248,2.088626775,1], [6.922596716,1.77106367,1], [8.675418651,-0.242068655,1], [7.673756466,3.508563011,1]] coef = [-0.406605464, 0.852573316, -1.104746259] for row in dataset: yhat = predict(row, coef) print(\u0026#34;Expected=%.3f, Predicted=%.3f [%d]\u0026#34; % (row[-1], yhat, round(yhat))) 估计系数 这里可以使用我随机梯度下降来估计训练数据的系数值。随机梯度下降需要两个参数：\n学习率 Learning rate：用于限制每个系数每次更新时的修正量。 Epochs：更新系数时遍历训练数据的次数。 在每个epoch更新训练数据中每一行的每个系数。系数会根据模型产生的错误进行更新，误差为预期输出与预测值之间的差异。错误会随着epoch增加而减少\n将每个都加权，并且这些系数以一致的方式进行更新，用公式可以表示为\npython 1 b1(t+1) = b1(t) + learning_rate * (y(t) - p(t)) * p(t) * (1 - p(t)) * x1(t) 那么整合一起为\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 from math import exp # 预测函数 def predict(row, coefficients): p = coefficients[0] for i in range(len(row)-1): p += coefficients[i + 1] * row[i] return 1.0 / (1.0 + exp(-p)) def coefficients_sgd(train, l_rate, n_epoch): coef = [0.0 for i in range(len(train[0]))] # 初始一个系数，第一次为都为0 for epoch in range(n_epoch): sum_error = 0 for row in train: p = predict(row, coef) # 错误为预期值与实际值直接差异 error = row[-1] - p sum_error += error**2 # 截距没有输入变量x，这里为row[0] coef[0] = coef[0] + l_rate * error * p * (1.0 - p) for i in range(len(row)-1): # 其他系数更新 coef[i + 1] = coef[i + 1] + l_rate * error * p * (1.0 - p) * row[i] print(\u0026#39;\u0026gt;epoch=%d, lrate=%.3f, error=%.3f\u0026#39; % (epoch, l_rate, sum_error)) return coef # Calculate coefficients dataset = [ [2.7810836,2.550537003,0], [1.465489372,2.362125076,0], [3.396561688,4.400293529,0], [1.38807019,1.850220317,0], [3.06407232,3.005305973,0], [7.627531214,2.759262235,1], [5.332441248,2.088626775,1], [6.922596716,1.77106367,1], [8.675418651,-0.242068655,1], [7.673756466,3.508563011,1] ] l_rate = 0.3 n_epoch = 100 coef = coefficients_sgd(dataset, l_rate, n_epoch) print(coef) # \u0026gt;epoch=92, lrate=0.300, error=0.024 # \u0026gt;epoch=93, lrate=0.300, error=0.024 # \u0026gt;epoch=94, lrate=0.300, error=0.024 # \u0026gt;epoch=95, lrate=0.300, error=0.023 # \u0026gt;epoch=96, lrate=0.300, error=0.023 # \u0026gt;epoch=97, lrate=0.300, error=0.023 # \u0026gt;epoch=98, lrate=0.300, error=0.023 # \u0026gt;epoch=99, lrate=0.300, error=0.022 #[-0.8596443546618897, 1.5223825112460005, -2.218700210565016] 这里跟踪了跟踪每个epoch误差平方的总和，以便我们可以在每个epoch中打印出error，实例中使用 0.3 学习率并训练100 个 epoch，每个epoch会打印出其误差平方，最终会打印总系数集\n套用真实数据集 糖尿病数据集 是根据基本的医疗信息，预测印第安人5年内患糖尿病的情况。这是一个二元分类，阴性0与阳性1直接的关系。采用了二项式分布，也可以采用其他分布，如高斯等。\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 from random import seed from random import randrange from csv import reader from math import exp # Load a CSV file def load_csv(filename): dataset = list() with open(filename, \u0026#39;r\u0026#39;) as file: csv_reader = reader(file) for row in csv_reader: if not row: continue dataset.append(row) return dataset # Convert string column to float def str_column_to_float(dataset, column): for row in dataset: row[column] = float(row[column].strip()) # 找到最小和最大的 def dataset_minmax(dataset): minmax = list() for i in range(len(dataset[0])): col_values = [row[i] for row in dataset] value_min = min(col_values) value_max = max(col_values) minmax.append([value_min, value_max]) return minmax # 归一化 def normalize_dataset(dataset, minmax): for row in dataset: for i in range(len(row)): row[i] = (row[i] - minmax[i][0]) / (minmax[i][1] - minmax[i][0]) # k-folds CV实现 def cross_validation_split(dataset, n_folds): dataset_split = list() dataset_copy = list(dataset) fold_size = int(len(dataset) / n_folds) for i in range(n_folds): fold = list() while len(fold) \u0026lt; fold_size: index = randrange(len(dataset_copy)) fold.append(dataset_copy.pop(index)) dataset_split.append(fold) return dataset_split # 计算准确度百分比 def accuracy_metric(actual, predicted): correct = 0 for i in range(len(actual)): if actual[i] == predicted[i]: correct += 1 return correct / float(len(actual)) * 100.0 # 使用CV评估算法 def evaluate_algorithm(dataset, algorithm, n_folds, *args): folds = cross_validation_split(dataset, n_folds) scores = list() for fold in folds: train_set = list(folds) train_set.remove(fold) train_set = sum(train_set, []) test_set = list() for row in fold: row_copy = list(row) test_set.append(row_copy) row_copy[-1] = None predicted = algorithm(train_set, test_set, *args) actual = [row[-1] for row in fold] accuracy = accuracy_metric(actual, predicted) scores.append(accuracy) return scores # 使用系数进行预测 def predict(row, coefficients): yhat = coefficients[0] for i in range(len(row)-1): yhat += coefficients[i + 1] * row[i] return 1.0 / (1.0 + exp(-yhat)) # 系数生成 def coefficients_sgd(self, train, l_rate, n_epoch): \u0026#34;\u0026#34;\u0026#34; 生成系数 :param train: list, 数据集，可以是训练集 :param l_rate: float, 学习率 :param n_epoch:int，epoch，这里代表进行多少次迭代 :return: None \u0026#34;\u0026#34;\u0026#34; coef = [0.0 for i in range(len(train[0]))] # 初始一个系数，第一次为都为0 for epoch in range(n_epoch): sum_error = 0 for row in train: p = self.predict(row, coef) # 错误为预期值与实际值直接差异 error = row[-1] - p sum_error += error**2 # 截距没有输入变量x，这里为row[0] coef[0] = coef[0] + l_rate * error * p * (1.0 - p) for i in range(len(row)-1): # 其他系数更新 coef[i + 1] = coef[i + 1] + l_rate * error * p * (1.0 - p) * row[i] # print(\u0026#39;\u0026gt;epoch=%d, lrate=%.3f, error=%.3f\u0026#39; % (epoch, l_rate, sum_error)) return coef # 随机梯度下降的逻辑回归算法 def logistic_regression(self, train, test, l_rate, n_epoch): predictions = list() coef = self.coefficients_sgd(train, l_rate, n_epoch) for row in test: p = self.predict(row, coef) p = round(p) predictions.append(p) return(predictions) seed(1) # 数据预处理 filename = \u0026#39;pima-indians-diabetes.csv\u0026#39; dataset = load_csv(filename) for i in range(len(dataset[0])): str_column_to_float(dataset, i) # 做归一化 minmax = dataset_minmax(dataset) normalize_dataset(dataset, minmax) # evaluate algorithm n_folds = 5 l_rate = 0.1 n_epoch = 100 scores = evaluate_algorithm(dataset, logistic_regression, n_folds, l_rate, n_epoch) print(\u0026#39;Scores: %s\u0026#39; % scores) print(\u0026#39;Mean Accuracy: %.3f%%\u0026#39; % (sum(scores)/float(len(scores)))) # 0.35294117647058826 # Scores: [73.8562091503268, 78.43137254901961, 81.69934640522875, 75.81699346405229, 75.81699346405229] # Mean Accuracy: 77.124% 上述是对整个数据集的预测百分比，也可以对对应的类的信息进行输出\nReference Maximum likelihood estimation\nSigmoid Function\nlogistic\nbinary logistic regression\nLR implementation\n","permalink":"https://www.161616.top/logistic-regression/","summary":"Overview 逻辑回归通常用于分类算法，例如预测某事是 true 还是 false（二元分类）。例如，对电子邮件进行分类，该算法将使用电子邮件中的单词作为特征，并据此预测电子邮件是否为垃圾邮件。用数学来讲就是指，假设因变量是 Y，而自变量集是 X，那么逻辑回归将预测因变量 $P(Y=1)$ 作为自变量集 X 的函数。\n逻辑回归性能在线性分类中是最好的，其核心为基于样本属于某个类别的概率。这里的概率必须是连续的并且在 (0, 1) 之间（有界）。它依赖于阈值函数来做出称为 Sigmoid 或 Logistic 函数决定的。\n学好逻辑回归，需要了解逻辑回归的概念、优势比 (OR) 、Logit 函数、Sigmoid 函数、 Logistic 函数及交叉熵或Log Loss\nPrerequisite odds ratio explain odds ratio是预测变量的影响。优势比取决于预测变量是分类变量还是连续变量。\n连续预测变量：$OR \u0026gt; 1$ 表示，随着预测变量的增加，事件发生的可能性增加。$OR \u0026lt; 1$ 表示随着预测变量的增加，事件发生的可能性较小。 分类预测变量：事件发生在预测变量的 2 个不同级别的几率；如 A,B，$OR \u0026gt; 1$ 表示事件在 A 级别的可能性更大。$OR\u0026lt;1$ 表示事件更低的可能是在A。 例如，假设 X 是受影响的概率，Y 是不受影响的概率，则 $OR= \\frac{X}{Y}$ ，那么 $OR = \\frac{P}{(1-P)}$ ，P是事件的概率。\n让概率的范围为 [0,1] ，假设 $P(success)=0.8$ ，$Q(failure) = 0.2$ ；$OR$ 则是 成功概率和失败概率的比值，如：$O(success)=\\frac{P}{Q} = \\frac{0.","title":"逻辑回归"},{"content":"什么是naive bayes 朴素贝叶斯 naive bayes，是一种概率类的机器学习算法，主要用于解决分类问题\n为什么被称为朴素贝叶斯？\n为什么被称为朴素，难道仅仅是因为贝叶斯很天真吗？实际上是因为，朴素贝叶斯会假设数据属性之间具有很强的的独立性。即该模型中的所有属性彼此之间都是独立的，改变一个属性的值，不会直接影响或改变算法中其他的属性的值\n贝叶斯定理 了解朴素贝叶斯之前，需要掌握一些概念才可继续\n条件概率 Conditional probability：在另一个事件已经发生的情况下，另外一个时间发生的概率。如，==在多云天气，下雨的概率是多少？== 这是一个条件概率 联合概率 Joint Probability：计算两个或多个事件同时发生的可能性 边界概率 Marginal Probability：事件发生的概率，与另一个变量的结果无关 比例 Proportionality 贝叶斯定理 Bayes' Theorem：概率的公式；贝叶斯定律是指根据可能与事件的先验概率描述了事件的后验概率 边界概率 边界概率是指事件发生的概率，可以认为是无条件概率。不以另一个事件为条件；用公式表示为 $P(X)$ 如：抽到的牌是红色的概率是 $P(red) = 0.5$ ；\n联合概率 联合概率是指两个事件在同一时间点发生的可能性，公式可以表示为 $P(A \\cap B)$\nA 和 B 是两个不同的事件相同相交，$P(A \\and B)$ $P(A,B)$ = A 和 B 的联合概率\n概率用于处理事件或现象发生的可能性。它被量化为介于 0 和 1 之间的数字，其中 0 表示不可能发生的机会，1 表示事件的一定结果。\n如，从一副牌中抽到一张红牌的概率是 $\\frac{1}{2}$。这意味着抽到红色和抽到黑色的概率相同；因为一副牌中有52张牌，其中 26 张是红色的，26 张是黑色的，所以抽到一张红牌与抽到一张黑牌的概率是 50%。\n而联合概率是对测量同时发生的两个事件，只能应用于可能同时发生多个情况。例如，从一副52张牌扑克中，拿起一张既是红色又是6的牌的联合概率是 $P(6\\cap red) = \\frac{2}{52} = \\frac{1}{26}$ ；这个是怎么得到的呢？因为抽到红色的概率为50%，而一副牌中有两个红色6（红桃6，方片6），而6和红色是两个独立的概率，那么计算公式就为：$P(6 \\cap red) = P(6) \\times P(red) = \\frac{4}{52} \\times \\frac{26}{52} = \\frac{1}{26}$\n在联合概率中 $ \\cap $ 称为交集，是事件 A 与 事件 B 发生的概率的相交点，通过图来表示为：两个圆的相交点，即6和红色牌共同的部分\n条件概率 条件概率是指事件发生的可能性，基于先有事件的结果的发生乘后续事件概率来计算的，例如，申请大学的通过率为80%，宿舍仅提供给60%学生使用，那么这个人被大学录取并提供宿舍的概率是多少？\n$P(accept\\ and\\ get\\ dorm) = P(Accept|Dorm) = P(Accept) \\times P(Dorm) = 0.8 \\times 0.6 = 0.48$\n条件概率将会考虑两个事件之间的关系，例如你被大学录取的概率， 以及为你提供宿舍的概率；条件概率中的关键点：\n另一个事件发生的情况下，这件事发生的几率 表示为，给定 B 的概率 A 发生的概率，用公式表示为：$P(A|B)$，其中 A 取决于 B 发生的概率 通过例子了解条件概率 上述大致上了解到了：条件概率取决于先前的结果，那么通过几个例子来熟悉条件概率\n例1：袋子里有红色，蓝色，绿色三颗玻璃球，每种被拿到的概率相等，那么摸到蓝色之后再摸到红色的条件概率是多少？\n这里需要先得到摸到蓝色的概率：$P(Blue) = \\frac{1}{3}$ 因为只有三种可能性 现在还剩下两颗玻璃球 红色和蓝色，那么摸到红色的概率为：$P(Red) = \\frac{1}{2}$ 因为只有两种可能性 那么已经摸到蓝色在摸到红色的概率为 $P(Red|Blue) = \\frac{1}{3} \\times \\frac{1}{2} = \\frac{1}{6}$ 例2：色子摇出5的概率为 $\\frac{1}{6}$ 那么在结果是奇数里摇出5 那么可能就是 $\\frac{1}{3}$，而这个奇数就是另外的一个条件，因为只有3个奇数，其中一个是5，那么在奇数中，抛出5的概率就是 $\\frac{1}{3}$。\n通过上述信息可知，B 作为附带条件修饰 A 发生的概率，称为给定 B ，A 发生的条件，用$P(A|B)$ 表示。那么可以的出：\n给定A，B发生的概率为，A和B的发生概率排除掉A的概率，即 $P(B|A) = \\frac{P(A \\cap B)}{P(A)} $ 联合概率和条件概率的区别 条件概率是一个事件在另一个事件发生的情况下的概率：$P(X\\ given\\ Y)$ 公式为： $P(X∣Y)$；即一个事件发生的概率取决于另一事件的发生；如：从一副牌中，假设你抽到一张红牌，那么抽到6的概率是 $\\frac{1}{13}$；因为26张红牌中仅有两张为6，用公式表示：$P(6|red) = \\frac{2}{26}$\n联合概率仅考虑两个事件发生的可能性，对比与条件概率可用于计算联合概率：$P(X \\cap Y) = P(X|Y) \\times P(Y)$\n通过合并上述例子得到，同时抽到6和红色的概率为：$\\frac{1}{26}$\n贝叶斯定理 贝叶斯定理是确定条件概率的数学公式。贝叶斯定理依赖于先验概率分布以计算后验概率。\n后验概率和先验概率 先验概率 prior probability：在收集新数据之前发送事件的概率 后验概率 posterior probability：得到新的数据来修正之前事件发生的概率；换句话说是后验概率是在事件 B 已经发生的情况下，事件 A 发生的概率。 例，从一副52 张牌中抽取一张牌，那么这张牌是K的概率是 $\\frac{4}{52}$ , 因为一副牌中有4张K；假设抽中的牌是一张人物牌，那么抽到是K的概率则是 $\\frac{4}{12}$；因为一副牌中有12张人物牌。那么贝叶斯定理的公式为：\n$P(A|B) = \\frac{P(A \\cap B)}{P(B)}$，$P(B|A) = \\frac{P(B \\cap A)}{P(A)}$ $P(A \\cap B)$，$P(B \\cap A)$ A和B同时发生和B和A同时发生时相等的 $P(B \\cap A) = P(B|A) \\times P(A)$ $P(A \\cap B) = P(A|B) \\times P(B)$ 那么根据上面的公式，已知 $P(A \\cap B) = P(B \\cap A)$ 可推导出公式： 因为 $P(B \\cap A) = P(A \\cap B)$ ，那么 $P(B|A) \\times P(A) = P(A|B) \\times P(B)$ 那么吧 $P(A)$ 放置等式右边即 $P(B|A) = \\frac{P(A|B) \\times P(B)}{P(A)}$ 那么最终 Formula for Bayes 为 $P(B|A) = \\frac{P(A|B) \\times P(B)}{P(A)}$ 其中：\n$P(A)$：A 的边界概率\n$P(B)$：B 的边界概率\n$P(A|B)$ ：条件概率，已知 B，A 的概率\n$P(B|A)$ ：条件概率，已知 A，B 的概率\n$P(B \\cap A)$：联合概率 B 与 A 同时发生的概率\n一个简单的概率问题可能会问：茅台股价下跌的概率是多少？条件概率通过询问这个问题更进一步：鉴于A股平均指数下跌，茅台股价下跌的概率是多少？ 给定 B 已经发生的条件下 A 的概率可以表示为：\n$P(Mao|AS) = \\frac{P(Mao \\cap AS)}{P(AS)}$\n$P(Mao \\cap AS)$ 是 A 和 B 同时发生的概率，与 A 发生的情况下 B 也发生的概率 乘 A 发生的概率相等表示为： $P(Mao) \\times P(AS|Mao)$；这两个表达式相等，也就是贝叶斯定理，可以表示为：\n如果， $P(Mao \\cap AS) = P(Mao) \\times P(AS|Mao)$ 那么， $P(Mao|AS) = \\frac{P(Mao) \\times P(AS|Mao)}{P(AS)}$ $P(Mao)$ 和 $P(AS)$ 分别为茅台和A股的下跌概率，彼此间没有关系\n一般情况下，都是以 x （输入） y （输出） 在函数中，假设 $x=AS$ , $y=MAO$ 那么替代到公式中就 $P(Y|X) = \\frac{P(X|Y) \\times P(Y)}{P(X)}$\n朴素贝叶斯算法 朴素贝叶斯不是一个的算法，而是一组算法，所有这些算法都基于一个共同的原则，即每一对被分类的特征必须相互独立。朴素贝叶斯是一个基本的贝叶斯称呼，包含三种算法的集合：多项式 Multinomial、 伯努利 Bernoulli、高斯 Gaussian。\n伯努利 伯努利朴素贝叶斯，又叫做二项式，只接受二进制值，简而言之，在伯努利中必须计算每个值的二进制出现特征，即一个单词是与否出现在文档中。\n通俗地来说，伯努利有两个互斥的结果：\n$$NB=\\begin{cases} P(X=1)\\ = \\ q\\\\ P(X=0)\\ = \\ 1-q\\\\ \\end{cases} $$ ，在伯努利中，可以有多个特征，但每个特征都假设为是二进制的变量，因此，需要将样本表示为二进制向量。\n那么扩展出的公式为：$P(A|B) = \\frac{P(B|A) \\times P(A)}{P(A) \\times P(B|A) + P(not A) \\times P(B|not A)}$\n例子：假设COVID-19测试并不准确，有**95%**几率在感染时测试出阳性（敏感性），这就意味着如果有人并未感染的概率是相同的（特异性）；问：如果Jeovanna检测为阳性，那么他感染COVID-19的概率是多少？\n敏感性和特异性是医学用语；敏感性，病人测出阳性的比例，特异性，非病人测试阴性的比例\n一般情况下没有更多的信息来确定Jeovanna是否感染，如驻留场所，是否发烧，丧失味觉等。就需要更多的信息来计算Jeovanna感染率，比如预估Jeovanna感染率为1%，这1%就是先验概率。\n此时有100000人的测试样本，预计1000人感染（先验1%），那么就是99000为感染，又因为测试具有 95% 的敏感性和 95% 的特异性，这代表了 1000的95% 和 99000的5% 是阳性。整理一个表格\nHas COVID-19 Do not Has COVID-19 count 阳性 950 4950 5900 阴性 50 94050 94100 那么可以看出，阳性的人并感染COVID-19的概率是，$\\frac{950}{5900} = 16%$ ；那么也就是Jeovanna有16%几率是感染 COVID-19。\n此时将先验概率设置为16%，那么爱丽丝得COVID-19的可能性为：\n$P(B|A)$ ：在95%成功率情况下又获得了阳性 $P(A)$：阳性的检测成功率 已知，$P(B|A) = 0.16$ ，$P(A) = 0.95$ $P(A|B) = \\frac{P(B|A) \\times P(A)}{P(A) \\times P(B|A) + P(not A) \\times P(B|not A)} = \\frac{0.16\\times0.95}{0.95\\times 0.16 + (1-0.95)\\times(1-0.16)}= \\frac{0.152}{0.194} = 0.7835 = 78.35%$\n那么就可以得知，在阳性情况下感染COVID-19的情况下，再去检测会有78%几率阳性\n多项式 多项式朴素贝叶斯是基于多项分布的朴素叶贝斯，用来处理文本，计算 $d$ 在 $c$ 中的概率计算如下：\n$P(c|d) \\ \\propto P(c) \\prod_{i=1}^n\\ P(t_k|c)$\n通俗来说就是二项式的一个变种，是计算多个不同的实例\n$P(t_k|c)$ 是 $t_k$ 的 条件概率，发生在数据集 $c$ 中，$P(t_k|c)$ 解释为 $t_k$ 有多少证据表明 $c$ 是正确的；$P(c)$ 是先验条件 $t1..\\ t2..\\ t3..\\ tn_d$ 中的标记 $d$，它们是我们用于分类的词汇表的一部分，$n_d$ 是 标记 $d$ 的数量。\n例如：\u0026ldquo;Peking and Taipei join the WTO\u0026rdquo;，$\u0026lt;Peking,\\ Taipei,\\ join,\\ WTO\u0026gt;$ ,那么 $n_d = 4$\n那么可以简化为，\n$P(c=x|d=c_k) = P(c^1=x^1..,\\ c^2=x^2..,\\ c^n=x^n|d=c_k) = \\prod_{i=1}^n(c^i|d)x^i + (1-P(c^i|d))\t(1-x^i)$\n$\\prod_{i=1}^n$ 连乘积，即从下标起乘到上标\n朴素贝叶斯实现 首先将朴素贝叶斯为 5 个部分：\n分类 数据集汇总 按类别汇总数据 高斯密度函数 分类概率 分类 根据数据所属的类别来计算数据的概率，即所谓的基本率。\n先创建一个字典对象，其中每个键都是类值，然后添加所有记录的列表作为字典中的值。\n假设每行中的最后一列是类型。\npython 1 2 3 4 5 6 7 8 9 10 # 按类拆分数据集，返回结构是一个词典 def separate_by_class(dataset): separated = dict() for i in range(len(dataset)): vector = dataset[i] class_value = vector[-1] # dataset最后一行是类别 if (class_value not in separated): separated[class_value] = list() separated[class_value].append(vector) return separated 准备一些数据集\ntext 1 2 3 4 5 6 7 8 9 10 11 X1\tX2\tClass 3.393533211\t2.331273381\t0 3.110073483\t1.781539638\t0 7.423436942\t4.696522875\t1 1.343808831\t3.368360954\t0 3.582294042\t4.67917911\t0 9.172168622\t2.511101045\t1 7.792783481\t3.424088941\t1 2.280362439\t2.866990263\t0 5.745051997\t3.533989803\t1 7.939820817\t0.791637231\t1 测试分类函数的功能\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def separate_by_class(dataset): separated = dict() for i in range(len(dataset)): vector = dataset[i] class_value = vector[-1] if (class_value not in separated): separated[class_value] = list() separated[class_value].append(vector) return separated # 测试数据集 dataset = [ [3.393533211,2.331273381,0], [3.110073483,1.781539638,0], [1.343808831,3.368360954,0], [7.423436942,4.696522875,1], [3.582294042,4.67917911,0], [9.172168622,2.511101045,1], [7.792783481,3.424088941,1], [2.280362439,2.866990263,0], [5.745051997,3.533989803,1], [7.939820817,0.791637231,1] ] separated = separate_by_class(dataset) for label in separated: print(label) for row in separated[label]: print(row) 可以看到分类是成功的\n数据集汇总 现在需要对给出数据集的两个数据进行统计，如何对指定数据集做概率计算？需要以下几步\n计算数据集两个数据的平均值和标准差\n平均值为： $\\frac{sum(x)}{n} \\times count(x)$ ；其中 $x$ 为正在查找值的列表\nmean函数用于计算平均值\npython 1 2 def mean(numbers): return sum(numbers) / float(len(numbers)) 样本标准差的计算方式为平均值的平均差。公式可以为 sqrt((sum i to N (x_i – mean(x))^2) / N-1)\n函数用来计算\npython 1 2 3 4 5 6 7 from math import sqrt # Calculate the standard deviation of a list of numbers def stdev(numbers): avg = mean(numbers) # 平均值 variance = sum([(x-avg)**2 for x in numbers]) / float(len(numbers)-1) return sqrt(variance) 还需要对每个数据的每一列计算平均值和标准偏差统计量。通过将每列的所有值收集到一个列表中并计算该列表的平均值和标准差。计算完成后，将统计信息收集到数据汇总的列表或元组中。然后，对数据集中的每一列重复此操作并返回统计元组列表。\n下面是 数据汇总的函数 summarise_dataset()用来统计每列列表的平均值和标准差\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from math import sqrt # 计算平均数 def mean(numbers): return sum(numbers)/float(len(numbers)) # 计算标准差 def stdev(numbers): # 标准差 avg = mean(numbers) # 计算平均值 variance = sum([(x-avg)**2 for x in numbers]) / float(len(numbers)-1) # 计算所有的平方 return sqrt(variance) # 数据汇总 def summarize_dataset(dataset): summaries = [(mean(column), stdev(column), len(column)) for column in zip(*dataset)] del(summaries[-1]) # 因为分类不需要所以。删除掉分类哪行 return summaries # Test summarizing a dataset dataset = [ [3.393533211,2.331273381,0], [3.110073483,1.781539638,0], [1.343808831,3.368360954,0], [3.582294042,4.67917911,0], [2.280362439,2.866990263,0], [7.423436942,4.696522875,1], [5.745051997,3.533989803,1], [9.172168622,2.511101045,1], [7.792783481,3.424088941,1], [7.939820817,0.791637231,1]] summary = summarize_dataset(dataset) print(summary) 这里使用的是zip() 函数，将每列作为提供的参数。使用 * 作为位置函数，运将数据集传递给 zip() ，这个运算会将每一行的分割为单独列表。然后zip() 遍历每一行的每个元素，返回一列作为数字列表。\n然后计算每列中的平均数、标准差和行数。删掉不需要的列（第三列类别列的平均数，标准差和行数）\n可以看到\ntext 1 2 3 4 [ (5.178333386499999, 2.7665845055177263, 10), (2.9984683241, 1.218556343617447, 10) ] 根据类别汇总数据 separate_by_class() 是将数据分成行，现在要编写一个 summarise_dataset()；是先计算每列的统计汇总信息，然后在按照子集分类（X1，X2）\npython 1 2 3 4 5 6 7 # 按类拆分数据集 def summarize_by_class(dataset): separated = separate_by_class(dataset) summaries = dict() for class_value, rows in separated.items(): summaries[class_value] = summarize_dataset(rows) return summaries 这是完整的代码\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 from math import sqrt # 计算平均数 def mean(numbers): return sum(numbers)/float(len(numbers)) # 计算标准差 def stdev(numbers): # 标准差 avg = mean(numbers) # 计算平均值 variance = sum([(x-avg)**2 for x in numbers]) / float(len(numbers)-1) # 计算所有的平方 return sqrt(variance) # 数据汇总 def summarize_dataset(dataset): summaries = [(mean(column), stdev(column), len(column)) for column in zip(*dataset)] del(summaries[-1]) # 因为分类不需要所以。删除掉分类哪行 return summaries # 按类进行数据汇总 def summarize_by_class(dataset): separated = separate_by_class(dataset) summaries = dict() for class_value, rows in separated.items(): summaries[class_value] = summarize_dataset(rows) return summaries # 测试数据集 dataset = [ [3.393533211,2.331273381,0], [3.110073483,1.781539638,0], [1.343808831,3.368360954,0], [3.582294042,4.67917911,0], [2.280362439,2.866990263,0], [7.423436942,4.696522875,1], [5.745051997,3.533989803,1], [9.172168622,2.511101045,1], [7.792783481,3.424088941,1], [7.939820817,0.791637231,1]] summary = summarize_by_class(dataset) for label in summary: print(label) for row in summary[label]: print(row) 按照分类，对每列计算平均值和标准差\n高斯分布函数 高斯分布 Gaussian distribution 可以用两个数字来概括，高斯分布是具有对称的钟形的分布，所以中心右侧是左侧的镜像，曲线下的面积代表概率，曲线总面积之和等于1。\n高斯分布中的大多数连续数据值倾向于围绕均值聚集，值离均值越远，那么它发生的可能性就越小。接近但从未完全贴合x 轴。\n高斯分布由均值和标准差两个参数决定的，任何点 (x) 都可以通过公式 $z = \\frac{x-mean}{standard\\ deviation}$ 来计算\nReference normal distribution\n通过这一点，就可以知道就可以计算出给定的概率，公式为：\n$P({x_i}|Y) = \\frac{1}{\\sqrt2\\pi\\sigma_y^2}exp(-(\\frac{(x_i-\\mu_y)^2}{2\\sigma_y^2})$\n其中，$\\sigma$ 为标准差，$\\mu$ 为平均值，那么转换为可读懂的公式为：\n$f(x) = \\frac{1}{\\sqrt{(2 \\times pi )}\\times sigma} \\times exp(-(\\frac{(x-mean)^2}{(2 \\times sigma)^2}))$\n其中，sigma是 x 的标准差，mean 是 x 的平均值，PI是 就是 $\\pi$ math.pi 的值。\n那么在转换成python中的代码为：\npython 1 f(x) = (1 / sqrt(2 * PI) * sigma) * exp(-((x-mean)^2 / (2 * sigma^2))) 那么用python实现一个函数，来实现高斯公式\npython 1 2 3 4 # 计算高斯分布的函数，需要三个参数，x 平均值，标准差 def calculate_probability(x, mean, stdev): exponent = exp(-((x-mean)**2 / (2 * stdev**2 ))) return (1 / (sqrt(2 * pi) * stdev)) * exponent 这里通过函数测试三个数据，(0,1,1)， (1,1,1)，(2,1,1)\npython 1 2 3 4 5 6 7 8 9 10 11 12 from math import sqrt from math import pi from math import exp # 计算高斯分布的函数，需要三个参数，x 平均值，标准差 def calculate_probability(x, mean, stdev): exponent = exp(-((x-mean)**2 / (2 * stdev**2 ))) return (1 / (sqrt(2 * pi) * stdev)) * exponent print(calculate_probability(1.0, 1.0, 1.0)) print(calculate_probability(2.0, 1.0, 1.0)) print(calculate_probability(0.0, 1.0, 1.0)) 这里可以看到结果，(1,1,1) 的概率最可能（三个值中趋于钟形顶部）\n分类概率 到这里，可以尝试通过测试数据来统计新数据的概率，这里每个类别的概率都是单独计算的，这里将简化概率计算公式 $P(class|data) = P(data|class) \\times P(class)$；这是一个常见的简化，这将意味着将结果为最大值的类的计算作为预测结果。因为通常对预测感兴趣，而不是概率\n对于上述例子，有两个变量，这里以 class=0 的类别来说明，公式为：\n$P(class=0|X1,X2) = P(X1|class=0) \\times P(X2|class=0) \\times P(class=0)$\n编写一个聚合函数，将上述四个步骤汇总处理，\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 # Example of calculating class probabilities from math import sqrt from math import pi from math import exp # 拆分数据集 def separate_by_class(dataset): separated = dict() for i in range(len(dataset)): vector = dataset[i] class_value = vector[-1] if (class_value not in separated): separated[class_value] = list() separated[class_value].append(vector) print(separated) return separated # 计算平均数 def mean(numbers): return sum(numbers)/float(len(numbers)) # 计算标准差 def stdev(numbers): avg = mean(numbers) # 计算平均值 variance = sum([(x-avg)**2 for x in numbers]) / float(len(numbers)-1) # 标准差 return sqrt(variance) # 数据汇总 def summarize_dataset(dataset): summaries = [(mean(column), stdev(column), len(column)) for column in zip(*dataset)] del(summaries[-1]) return summaries # 按类进行数据汇总 def summarize_by_class(dataset): separated = separate_by_class(dataset) summaries = dict() for class_value, rows in separated.items(): summaries[class_value] = summarize_dataset(rows) return summaries # 计算高斯分布的函数，需要三个参数，x 平均值，标准差 def calculate_probability(x, mean, stdev): exponent = exp(-((x-mean)**2 / (2 * stdev**2 ))) return (1 / (sqrt(2 * pi) * stdev)) * exponent # 计算每个分类的概率 def converge_probabilities(summaries, row): # 计算所有分类的个数 total_rows = sum([summaries[label][0][2] for label in summaries]) probabilities = dict() for class_value, class_summaries in summaries.items(): # 计算分类的概率，如这个分类在总分类里概率多少 probabilities[class_value] = summaries[class_value][0][2]/float(total_rows) for i in range(len(class_summaries)): mean, stdev, _ = class_summaries[i] probabilities[class_value] *= calculate_probability(row[i], mean, stdev) return probabilities # 测试数据集 dataset = [ [3.393533211,2.331273381,0], [3.110073483,1.781539638,0], [1.343808831,3.368360954,0], [3.582294042,4.67917911,0], [2.280362439,2.866990263,0], [7.423436942,4.696522875,1], [5.745051997,3.533989803,1], [9.172168622,2.511101045,1], [7.792783481,3.424088941,1], [7.939820817,0.791637231,1]] summaries = summarize_by_class(dataset) probabilities = converge_probabilities(summaries, dataset[0]) print(probabilities) 由结果可以得知，dataset[0] X1 的概率（0.0503）要大于 X2 的概率（0.0001），所以可以正确的判断出 dataset[0] 属于 X1 分类\n鸢尾花(Iris)分类 鸢尾花分类，是模式识别中非常出名的一种数据库，需要先将数据下载：\n关于Iris-databases数据集的说明\niris dataset\n实现开始 实验是根据上述实验的步骤，将朴素贝叶斯算法应用在鸢尾花数据集中，鸢尾花数据集的实验也是需要相同的步骤，只不过对于数据集中的数据还需要一些其他的步骤，大致可分为以下几种操作：\n数据的预处理 从文件中读取数据 将数据类型转换为可用于上面实验的类型（float） 将真实分类转换为数字 int 分类 数据集汇总 按类别汇总数据 高斯密度函数 分类概率 python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 from csv import reader from random import seed from random import randrange from math import sqrt from math import exp from math import pi # 读取数据集 def load_csv(filename): dataset = list() with open(filename, \u0026#39;r\u0026#39;) as file: csv_reader = reader(file) for row in csv_reader: if not row: continue dataset.append(row) return dataset # 将每行的数字转换为float def str_column_to_float(dataset, column): for row in dataset: row[column] = float(row[column].strip()) # 将真实分类转换为数字，按照下标 def str_column_to_int(dataset, column): \u0026#39;\u0026#39;\u0026#39; :param dataset: list, 数据集 :param column: string，是为类型的列要传入 :return: None \u0026#39;\u0026#39;\u0026#39; # 通过循环拿到所有分类 class_values = [row[column] for row in dataset] # 对分类型去重 unique = set(class_values) lookup = dict() # 拿到分类值的key 下标 for i, value in enumerate(unique): lookup[value] = i # 已对应的下标进行替换 for row in dataset: row[column] = lookup[row[column]] return lookup # 将数据的一部分作为训练数据 def cross_validation_split(dataset, n_folds): dataset_split = list() dataset_copy = list(dataset) fold_size = int(len(dataset) / n_folds) for _ in range(n_folds): fold = list() while len(fold) \u0026lt; fold_size: index = randrange(len(dataset_copy)) fold.append(dataset_copy.pop(index)) dataset_split.append(fold) return dataset_split # 计算准确度 def accuracy_metric(actual, predicted): correct = 0 for i in range(len(actual)): if actual[i] == predicted[i]: correct += 1 return correct / float(len(actual)) * 100.0 # 对算法数据进行评估 def evaluate_algorithm(dataset, algorithm, n_folds, *args): \u0026#34;\u0026#34;\u0026#34; :param dataset:list, 原始数据集 :param algorithm:function，算法函数 :param n_folds:int，取多少数据作为训练集 :param args:options ，参数 :return: None \u0026#34;\u0026#34;\u0026#34; folds = cross_validation_split(dataset, n_folds) scores = list() for fold in folds: train_set = list(folds) train_set.remove(fold) # 合并成一个数组 train_set = sum(train_set, []) test_set = list() for row in fold: row_copy = list(row) test_set.append(row_copy) row_copy[-1] = None # 将最后一个类型字段设置为None predicted = algorithm(train_set, test_set, *args) # 真实的类型 actual = [row[-1] for row in fold] # 精确的分数，即这一组数据正确率 accuracy = accuracy_metric(actual, predicted) scores.append(accuracy) print(scores) return scores # 按照分类拆分 def separate_by_class(dataset): separated = dict() for i in range(len(dataset)): vector = dataset[i] class_value = vector[-1] if (class_value not in separated): separated[class_value] = list() separated[class_value].append(vector) return separated # 计算这一系列的平均值 def mean(numbers): return sum(numbers)/float(len(numbers)) # 计算一系列数字的标准差 def stdev(numbers): avg = mean(numbers) variance = sum([(x-avg)**2 for x in numbers]) / float(len(numbers)-1) return sqrt(variance) # 计算数据集中每列的平均值 标准差 长度 def summarize_dataset(dataset): summaries = [(mean(column), stdev(column), len(column)) for column in zip(*dataset)] del(summaries[-1]) return summaries # 按照分类划分数据集 def summarize_by_class(dataset): separated = separate_by_class(dataset) summaries = dict() for class_value, rows in separated.items(): summaries[class_value] = summarize_dataset(rows) return summaries # 计算x的高斯概率 def calculate_probability(x, mean, stdev): \u0026#34;\u0026#34;\u0026#34; :param x:float, 计算这个值的高斯概率 :param mean:float，x的平均值 :param stdev:float，x的标准差 :return: None \u0026#34;\u0026#34;\u0026#34; exponent = exp(-((x-mean)**2 / (2 * stdev**2 ))) return (1 / (sqrt(2 * pi) * stdev)) * exponent # 计算每行的概率 def converge_probabilities(summaries, row): # 计算所有分类的个数 total_rows = sum([summaries[label][0][2] for label in summaries]) probabilities = dict() for class_value, class_summaries in summaries.items(): # 计算分类的概率，如这个分类在总分类里概率多少 # 公式中的P(class) probabilities[class_value] = summaries[class_value][0][2]/float(total_rows) # 通过公式 P(X1|class=0) * P(X2|class=0) * P(class=0) 计算高斯概率 for i in range(len(class_summaries)): mean, stdev, _ = class_summaries[i] probabilities[class_value] *= calculate_probability(row[i], mean, stdev) return probabilities # 通过计算出来的值，预测该花属于哪个品种，取高斯概率最大的值 def predict(summaries, row): probabilities = converge_probabilities(summaries, row) best_label, best_prob = None, -1 for class_value, probability in probabilities.items(): if best_label is None or probability best_prob: best_prob = probability best_label = class_value return best_label # Naive Bayes Algorithm def naive_bayes(train, test): # 训练数据按照类分类排序 summarize = summarize_by_class(train) predictions = list() for row in test: output = predict(summarize, row) predictions.append(output) print(predictions) return(predictions) # 测试 if __name__ == \u0026#39;__main__\u0026#39;: seed(1) filename = \u0026#39;iris.csv\u0026#39; dataset = load_csv(filename) # 转换数值为float for i in range(len(dataset[0])-1): str_column_to_float(dataset, i) # 将类型转换为数字 str_column_to_int(dataset, len(dataset[0])-1) # 将数据分位测试数据和训练数据，folds为多少数据为训练数据 n_folds = 5 scores = evaluate_algorithm(dataset, naive_bayes, n_folds) print(\u0026#39;Scores: %s\u0026#39; % scores) print(\u0026#39;Mean Accuracy: %.3f%%\u0026#39; % (sum(scores)/float(len(scores)))) 可以看到运行结果，对鸢尾花数据集的预测正确率，平均为95.333%\n现在对 main 部分进行修改，使用全部数据集作为训练，新增记录作为预测\npython 1 2 3 4 5 6 7 8 # 按照整个数据集分类 model = summarize_by_class(dataset) # 新加一行预测数据 row = [5.3,3.9,3.2,2.3] # 根据训练集进行对数据预测 label = predict(model, row) print(\u0026#39;Data=%s, Predicted: %s\u0026#39; % (row, label)) 完整修改过的代码如下：\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 from csv import reader from random import seed from random import randrange from math import sqrt from math import exp from math import pi # 读取数据集 def load_csv(filename): dataset = list() with open(filename, \u0026#39;r\u0026#39;) as file: csv_reader = reader(file) for row in csv_reader: if not row: continue dataset.append(row) return dataset # 将每行的数字转换为float def str_column_to_float(dataset, column): for row in dataset: row[column] = float(row[column].strip()) # 将真实分类转换为数字，按照下标 def str_column_to_int(dataset, column): \u0026#39;\u0026#39;\u0026#39; :param dataset: list, 数据集 :param column: string，是为类型的列要传入 :return: None \u0026#39;\u0026#39;\u0026#39; # 通过循环拿到所有分类 class_values = [row[column] for row in dataset] # 对分类型去重 unique = set(class_values) lookup = dict() # 拿到分类值的key 下标 for i, value in enumerate(unique): lookup[value] = i # 增加一行，来显示下标和真实名称对应的数据 print(lookup) # 已对应的下标进行替换 for row in dataset: row[column] = lookup[row[column]] return lookup # 将数据的一部分作为训练数据 def cross_validation_split(dataset, n_folds): dataset_split = list() dataset_copy = list(dataset) fold_size = int(len(dataset) / n_folds) for _ in range(n_folds): fold = list() while len(fold) \u0026lt; fold_size: index = randrange(len(dataset_copy)) fold.append(dataset_copy.pop(index)) dataset_split.append(fold) return dataset_split # 计算准确度 def accuracy_metric(actual, predicted): correct = 0 for i in range(len(actual)): if actual[i] == predicted[i]: correct += 1 return correct / float(len(actual)) * 100.0 # 对算法数据进行评估 def evaluate_algorithm(dataset, algorithm, n_folds, *args): \u0026#34;\u0026#34;\u0026#34; :param dataset:list, 原始数据集 :param algorithm:function，算法函数 :param n_folds:int，取多少数据作为训练集 :param args:options ，参数 :return: None \u0026#34;\u0026#34;\u0026#34; folds = cross_validation_split(dataset, n_folds) scores = list() for fold in folds: train_set = list(folds) train_set.remove(fold) # 合并成一个数组 train_set = sum(train_set, []) test_set = list() for row in fold: row_copy = list(row) test_set.append(row_copy) row_copy[-1] = None # 将最后一个类型字段设置为None predicted = algorithm(train_set, test_set, *args) # 真实的类型 actual = [row[-1] for row in fold] # 精确的分数，即这一组数据正确率 accuracy = accuracy_metric(actual, predicted) scores.append(accuracy) print(scores) return scores # 按照分类拆分 def separate_by_class(dataset): \u0026#34;\u0026#34;\u0026#34; :param dataset:list, 按分类好的列表 :return: dict, 每个分类的每列（属性）的平均值，标准差，个数 \u0026#34;\u0026#34;\u0026#34; separated = dict() for i in range(len(dataset)): vector = dataset[i] class_value = vector[-1] if (class_value not in separated): separated[class_value] = list() separated[class_value].append(vector) return separated # 计算这一系列的平均值 def mean(numbers): return sum(numbers)/float(len(numbers)) # 计算一系列数字的标准差 def stdev(numbers): avg = mean(numbers) variance = sum([(x-avg)**2 for x in numbers]) / float(len(numbers)-1) return sqrt(variance) # 计算数据集中每列的平均值 标准差 长度 def summarize_dataset(dataset): summaries = [(mean(column), stdev(column), len(column)) for column in zip(*dataset)] del(summaries[-1]) return summaries # 按照分类划分数据集 def summarize_by_class(dataset): separated = separate_by_class(dataset) summaries = dict() for class_value, rows in separated.items(): summaries[class_value] = summarize_dataset(rows) return summaries # 计算x的高斯概率 def calculate_probability(x, mean, stdev): \u0026#34;\u0026#34;\u0026#34; :param x:float, 计算这个值的高斯概率 :param mean:float，x的平均值 :param stdev:float，x的标准差 :return: None \u0026#34;\u0026#34;\u0026#34; exponent = exp(-((x-mean)**2 / (2 * stdev**2 ))) return (1 / (sqrt(2 * pi) * stdev)) * exponent # 计算每行的概率 def converge_probabilities(summaries, row): # 计算所有分类的个数 total_rows = sum([summaries[label][0][2] for label in summaries]) probabilities = dict() for class_value, class_summaries in summaries.items(): # 计算分类的概率，如这个分类在总分类里概率多少 # 公式中的P(class) probabilities[class_value] = summaries[class_value][0][2]/float(total_rows) # 通过公式 P(X1|class=0) * P(X2|class=0) * P(class=0) 计算高斯概率 for i in range(len(class_summaries)): mean, stdev, _ = class_summaries[i] probabilities[class_value] *= calculate_probability(row[i], mean, stdev) return probabilities # 通过计算出来的值，预测该花属于哪个品种，取高斯概率最大的值 def predict(summaries, row): probabilities = converge_probabilities(summaries, row) best_label, best_prob = None, -1 for class_value, probability in probabilities.items(): if best_label is None or probability best_prob: best_prob = probability best_label = class_value return best_label # Naive Bayes Algorithm def naive_bayes(train, test): # 训练数据按照类分类排序 summarize = summarize_by_class(train) predictions = list() for row in test: output = predict(summarize, row) predictions.append(output) print(predictions) return(predictions) # 测试 if __name__ == \u0026#39;__main__\u0026#39;: seed(1) filename = \u0026#39;iris.csv\u0026#39; dataset = load_csv(filename) # 转换数值为float for i in range(len(dataset[0])-1): str_column_to_float(dataset, i) # 将类型转换为数字 str_column_to_int(dataset, len(dataset[0])-1) # 按照整个数据集分类 model = summarize_by_class(dataset) # 新加一行预测数据 row = [5.3,3.9,3.2,2.3] # 根据训练集进行对数据预测 label = predict(model, row) print(\u0026#39;Data=%s, Predicted: %s\u0026#39; % (row, label)) 可以看到对数据集 [5.3,3.9,3.2,2.3] 预测为 versicolor，那将属性修改为，[2.3,0.9,0.2,1.3] 预测结果为 setosa\nReference gaussian naive bayes Naive Bayes Example caculator naive bayes [五分钟了解朴素贝叶斯](https://towardsdatascience.com/- a-mathematical-explanation-of-naive-bayes-in-5-minutes-44adebcdb5f8) Joint Probability Conditional Probability ","permalink":"https://www.161616.top/naive-bayes/","summary":"什么是naive bayes 朴素贝叶斯 naive bayes，是一种概率类的机器学习算法，主要用于解决分类问题\n为什么被称为朴素贝叶斯？\n为什么被称为朴素，难道仅仅是因为贝叶斯很天真吗？实际上是因为，朴素贝叶斯会假设数据属性之间具有很强的的独立性。即该模型中的所有属性彼此之间都是独立的，改变一个属性的值，不会直接影响或改变算法中其他的属性的值\n贝叶斯定理 了解朴素贝叶斯之前，需要掌握一些概念才可继续\n条件概率 Conditional probability：在另一个事件已经发生的情况下，另外一个时间发生的概率。如，==在多云天气，下雨的概率是多少？== 这是一个条件概率 联合概率 Joint Probability：计算两个或多个事件同时发生的可能性 边界概率 Marginal Probability：事件发生的概率，与另一个变量的结果无关 比例 Proportionality 贝叶斯定理 Bayes' Theorem：概率的公式；贝叶斯定律是指根据可能与事件的先验概率描述了事件的后验概率 边界概率 边界概率是指事件发生的概率，可以认为是无条件概率。不以另一个事件为条件；用公式表示为 $P(X)$ 如：抽到的牌是红色的概率是 $P(red) = 0.5$ ；\n联合概率 联合概率是指两个事件在同一时间点发生的可能性，公式可以表示为 $P(A \\cap B)$\nA 和 B 是两个不同的事件相同相交，$P(A \\and B)$ $P(A,B)$ = A 和 B 的联合概率\n概率用于处理事件或现象发生的可能性。它被量化为介于 0 和 1 之间的数字，其中 0 表示不可能发生的机会，1 表示事件的一定结果。\n如，从一副牌中抽到一张红牌的概率是 $\\frac{1}{2}$。这意味着抽到红色和抽到黑色的概率相同；因为一副牌中有52张牌，其中 26 张是红色的，26 张是黑色的，所以抽到一张红牌与抽到一张黑牌的概率是 50%。\n而联合概率是对测量同时发生的两个事件，只能应用于可能同时发生多个情况。例如，从一副52张牌扑克中，拿起一张既是红色又是6的牌的联合概率是 $P(6\\cap red) = \\frac{2}{52} = \\frac{1}{26}$ ；这个是怎么得到的呢？因为抽到红色的概率为50%，而一副牌中有两个红色6（红桃6，方片6），而6和红色是两个独立的概率，那么计算公式就为：$P(6 \\cap red) = P(6) \\times P(red) = \\frac{4}{52} \\times \\frac{26}{52} = \\frac{1}{26}$","title":"朴素贝叶斯算法"},{"content":"Preparation debian11几乎可以使用任何旧的计算机硬件，因为最小安装的要求非常低。以下是最低要求和推荐要求：\n最低要求 推荐要求 存储：10 Gigabytes\n内存：512 Megabytes\nCPU: 1 GigaHertz 存储：10 Gigabytes内存：2 GigabytesCPU: 1 GigaHertz or more Debian11 EOL：August 31st, 2026\n如何选择下载安装包 offical mirror aliyun mirror 官网提供了安装包的下载，其中CD是网络安装，DVD是离线安装\ndebian官方下载页面 Notes：CD安装包很小，下载下来是 debian-11.4.0-amd64-netinst.iso 如名所示，这是一个网络安装包，所以推荐下载DVD部分，可以达到离线安装的效果\n安装步骤 在界面中选择“Install”，安装将开始。如果图形化安装可以选择“Graphical install”，这里选择“Install”。\n欢迎页面 完成后，系统将提示选择安装时的“语言”。选择喜欢的语言，然后按“Enter”。这里选择英文\n选择语言页面 这将是接下来安装步骤\n安装步骤概述 选择位置与键盘布局 选择区域\n选择区域 下面部署时选择键盘布局：中国大陆使用的键盘布局是美国-英语，不要选择英国-英语之类，布局是不一样的，会存在按键输出的结果会不同\n选择键盘布局 完成上述操作后，将开始加载镜像。等待扫描完成。。。。\n等待扫描组件 设置主机名和域名 这步骤中将配置一个“主机名”。与一个“域”名称。\n配置主机名 “域” 可以选择留空确定\n配置域 完成上述操作后，安装程序将提示需要设置 root 密码。输入您的 root 密码，然后在重新输入以进行验证后继续。\n设置Root密码 设置非ROOT用户名、账户和密码 下一步创建一个非ROOT用户，这个步骤是必须的，并为这个新创建的帐户分配一个密码。以下截图将描述将如何完成此操作。\n配置普通用户 为这个用户配置密码\n为普通用户配置密码 为普通用户配置密码——二次确认 设置时钟时区 Eastern 美东时间\nCentral 北美中部\nMountain 北美山区时区\nPacific 太平洋时区\nAlaska 阿拉斯加夏令时间\nHawaii 夏威夷时区\nArizona 亞利桑时区\nEast Indiana 印第安纳时区\nSamoa 萨摩亚时间\n配置时区 对磁盘分区 此步骤磁盘进行分区。这里选择“手动”选项\n选择分区模式 选择手动进行划分为所需的分区。\n选择硬盘 创建新的分区表\n创建分区表 选择空闲的空间进行分区\n选择空闲空间 创建一个新分区\n创建一个新分区 为/boot划分分区\n为/boot划分分区 最终划分的分区 这里选No就行，提示是指不使用swap分区，No就是继续，Yes将返回分区页面\n对于swap分区的提示 创建新分区需要格式化，当前的分区将会被删除，如果是新磁盘选择Yes格式化分区\n确认格式化，进行分区 Base System安装 这里等待安装基础系统\n确认格式化，进行分区 几分钟后， 安装后会弹出一个界面，这里会扫描其他的media，这里因为没有，选择No就行。\n扫描其他媒介 离线安装 会扫描安装的媒介，这里也有提示，如果没有额外的媒介，可以跳过该步骤\n扫描其他媒介 配置网络镜像，建议配置下，如果不需要No即可\n配置网络镜像 接下来会弹出一个界面，请选择“Debian镜像国家”。这个是配置镜像地址的，选择自己的国家和镜像站即可\n选择镜像国家 这里选择的是中国和中科大镜像\n选择镜像地址 配置HTTP代理，不选择跳过\n配置http代理 如果选择网络安装，到这步骤时安装程序现在将在选择相关的。首先，选择离您所在国家最近的位置。Debian 镜像位置和域后检索剩余的文件\n这里提示有一个匿名调查，这里选No即可\n匿名调查 根据要求调整安装 在检索过程中，系统将提示需要自行选择以下预定义软件中的一个或多个。最小化安装仅选择基础系统与SSH即可\n安装组件选择 接下来等待安装即可\n安装过程 Notes：选择了DVD ISO将离线完成安装，如果使用了CD ISO，将从互联网上检索包并安装，这个时间将很长。\n其中会提示一个引导按章，直接Yes即可\n到了这里即将安装完成\n到了这里即将安装完成 完成Debian11最小化安装 看到到这里已经完成了安装，按“Continue”继续重启后即可\n完成安装 看到系统的引导界面 Enjoy 👏👏\n","permalink":"https://www.161616.top/debian11-install-tutorial/","summary":"Preparation debian11几乎可以使用任何旧的计算机硬件，因为最小安装的要求非常低。以下是最低要求和推荐要求：\n最低要求 推荐要求 存储：10 Gigabytes\n内存：512 Megabytes\nCPU: 1 GigaHertz 存储：10 Gigabytes内存：2 GigabytesCPU: 1 GigaHertz or more Debian11 EOL：August 31st, 2026\n如何选择下载安装包 offical mirror aliyun mirror 官网提供了安装包的下载，其中CD是网络安装，DVD是离线安装\ndebian官方下载页面 Notes：CD安装包很小，下载下来是 debian-11.4.0-amd64-netinst.iso 如名所示，这是一个网络安装包，所以推荐下载DVD部分，可以达到离线安装的效果\n安装步骤 在界面中选择“Install”，安装将开始。如果图形化安装可以选择“Graphical install”，这里选择“Install”。\n欢迎页面 完成后，系统将提示选择安装时的“语言”。选择喜欢的语言，然后按“Enter”。这里选择英文\n选择语言页面 这将是接下来安装步骤\n安装步骤概述 选择位置与键盘布局 选择区域\n选择区域 下面部署时选择键盘布局：中国大陆使用的键盘布局是美国-英语，不要选择英国-英语之类，布局是不一样的，会存在按键输出的结果会不同\n选择键盘布局 完成上述操作后，将开始加载镜像。等待扫描完成。。。。\n等待扫描组件 设置主机名和域名 这步骤中将配置一个“主机名”。与一个“域”名称。\n配置主机名 “域” 可以选择留空确定\n配置域 完成上述操作后，安装程序将提示需要设置 root 密码。输入您的 root 密码，然后在重新输入以进行验证后继续。\n设置Root密码 设置非ROOT用户名、账户和密码 下一步创建一个非ROOT用户，这个步骤是必须的，并为这个新创建的帐户分配一个密码。以下截图将描述将如何完成此操作。\n配置普通用户 为这个用户配置密码\n为普通用户配置密码 为普通用户配置密码——二次确认 设置时钟时区 Eastern 美东时间\nCentral 北美中部","title":"安装Debian11 (bullseye) Step-by-Step"},{"content":"之前了解了client-go中的架构设计，也就是 tools/cache 下面的一些概念，那么下面将对informer进行分析\nController 在client-go informer架构中存在一个 controller ，这个不是 Kubernetes 中的Controller组件；而是在 tools/cache 中的一个概念，controller 位于 informer 之下，Reflector 之上。code\nConfig 从严格意义上来讲，controller 是作为一个 sharedInformer 使用，通过接受一个 Config ，而 Reflector 则作为 controller 的 slot。Config 则包含了这个 controller 里所有的设置。\ngo 1 2 3 4 5 6 7 8 9 type Config struct { Queue // DeltaFIFO ListerWatcher // 用于list watch的 Process ProcessFunc // 定义如何从DeltaFIFO中弹出数据后处理的操作 ObjectType runtime.Object // Controller处理的对象数据，实际上就是kubernetes中的资源 FullResyncPeriod time.Duration // 全量同步的周期 ShouldResync ShouldResyncFunc // Reflector通过该标记来确定是否应该重新同步 RetryOnError bool } controller 然后 controller 又为 reflertor 的上层\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type controller struct { config Config reflector *Reflector reflectorMutex sync.RWMutex clock clock.Clock } type Controller interface { // controller 主要做两件事， // 1. 构建并运行 Reflector,将listerwacther中的泵压到queue（Delta fifo）中 // 2. Queue用Pop()弹出数据，具体的操作是Process // 直到 stopCh 不阻塞，这两个协程将退出 Run(stopCh \u0026lt;-chan struct{}) HasSynced() bool // 这个实际上是从store中继承的，标记这个controller已经 LastSyncResourceVersion() string } controller 中的方法，仅有一个 Run() 和 New()；这意味着，controller 只是一个抽象的概念，作为 Reflector, Delta FIFO 整合的工作流\n而 controller 则是 SharedInformer 了。\nQueue 这里的 queue 可以理解为是一个具有 Pop() 功能的 Indexer ;而 Pop() 的功能则是 controller 中的一部分；也就是说 queue 是一个扩展的 Store ， Store 是不具备弹出功能的。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type Queue interface { Store // Pop会阻塞等待，直到有内容弹出，删除对应的值并处理计数器 Pop(PopProcessFunc) (interface{}, error) // AddIfNotPresent puts the given accumulator into the Queue (in // association with the accumulator\u0026#39;s key) if and only if that key // is not already associated with a non-empty accumulator. AddIfNotPresent(interface{}) error // HasSynced returns true if the first batch of keys have all been // popped. The first batch of keys are those of the first Replace // operation if that happened before any Add, Update, or Delete; // otherwise the first batch is empty. HasSynced() bool Close() // 关闭queue } 而弹出的操作是通过 controller 中的 processLoop() 进行的，最终走到Delta FIFO中进行处理。\n通过忙等待去读取要弹出的数据，然后在弹出前 通过PopProcessFunc 进行处理\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (c *controller) processLoop() { for { obj, err := c.config.Queue.Pop(PopProcessFunc(c.config.Process)) if err != nil { if err == ErrFIFOClosed { return } if c.config.RetryOnError { // This is the safe way to re-enqueue. c.config.Queue.AddIfNotPresent(obj) } } } } DeltaFIFO.Pop()\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 func (f *DeltaFIFO) Pop(process PopProcessFunc) (interface{}, error) { f.lock.Lock() defer f.lock.Unlock() for { for len(f.queue) == 0 { // When the queue is empty, invocation of Pop() is blocked until new item is enqueued. // When Close() is called, the f.closed is set and the condition is broadcasted. // Which causes this loop to continue and return from the Pop(). if f.IsClosed() { return nil, ErrFIFOClosed } f.cond.Wait() } id := f.queue[0] f.queue = f.queue[1:] if f.initialPopulationCount \u0026gt; 0 { f.initialPopulationCount-- } item, ok := f.items[id] if !ok { // Item may have been deleted subsequently. continue } delete(f.items, id) err := process(item) // 进行处理 if e, ok := err.(ErrRequeue); ok { f.addIfNotPresent(id, item) // 如果失败，再重新加入到队列中 err = e.Err } // Don\u0026#39;t need to copyDeltas here, because we\u0026#39;re transferring // ownership to the caller. return item, err } } Informer 通过对 Reflector, Store, Queue, ListerWatcher、ProcessFunc, 等的概念，发现由 controller 所包装的起的功能并不能完成通过对API的动作监听，并通过动作来处理本地缓存的一个能力；这个情况下诞生了 informer 严格意义上来讲是 sharedInformer\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 func newInformer( lw ListerWatcher, objType runtime.Object, resyncPeriod time.Duration, h ResourceEventHandler, clientState Store, ) Controller { // This will hold incoming changes. Note how we pass clientState in as a // KeyLister, that way resync operations will result in the correct set // of update/delete deltas. fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{ KnownObjects: clientState, EmitDeltaTypeReplaced: true, }) cfg := \u0026amp;Config{ Queue: fifo, ListerWatcher: lw, ObjectType: objType, FullResyncPeriod: resyncPeriod, RetryOnError: false, Process: func(obj interface{}) error { // from oldest to newest for _, d := range obj.(Deltas) { switch d.Type { case Sync, Replaced, Added, Updated: if old, exists, err := clientState.Get(d.Object); err == nil \u0026amp;\u0026amp; exists { if err := clientState.Update(d.Object); err != nil { return err } h.OnUpdate(old, d.Object) } else { if err := clientState.Add(d.Object); err != nil { return err } h.OnAdd(d.Object) } case Deleted: if err := clientState.Delete(d.Object); err != nil { return err } h.OnDelete(d.Object) } } return nil }, } return New(cfg) } newInformer是位于 tools/cache/controller.go 下，可以看出，这里面并没有informer的概念，这里通过注释可以看到，newInformer实际上是一个提供了存储和事件通知的informer。他关联的 queue 则是 Delta FIFO，并包含了 ProcessFunc, Store 等 controller的概念。最终对外的方法为 NewInformer()\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func NewInformer( lw ListerWatcher, objType runtime.Object, resyncPeriod time.Duration, h ResourceEventHandler, ) (Store, Controller) { // This will hold the client state, as we know it. clientState := NewStore(DeletionHandlingMetaNamespaceKeyFunc) return clientState, newInformer(lw, objType, resyncPeriod, h, clientState) } type ResourceEventHandler interface { OnAdd(obj interface{}) OnUpdate(oldObj, newObj interface{}) OnDelete(obj interface{}) } 可以看到 NewInformer() 就是一个带有 Store功能的controller，通过这些可以假定出，Informer 就是controller ，将queue中相关操作分发给不同事件处理的功能\nSharedIndexInformer shareInformer 为客户端提供了与apiserver一致的数据对象本地缓存，并支持多事件处理程序的informer，而 shareIndexInformer 则是对shareInformer 的扩展\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 type SharedInformer interface { // AddEventHandler adds an event handler to the shared informer using the shared informer\u0026#39;s resync // period. Events to a single handler are delivered sequentially, but there is no coordination // between different handlers. AddEventHandler(handler ResourceEventHandler) // AddEventHandlerWithResyncPeriod adds an event handler to the // shared informer with the requested resync period; zero means // this handler does not care about resyncs. The resync operation // consists of delivering to the handler an update notification // for every object in the informer\u0026#39;s local cache; it does not add // any interactions with the authoritative storage. Some // informers do no resyncs at all, not even for handlers added // with a non-zero resyncPeriod. For an informer that does // resyncs, and for each handler that requests resyncs, that // informer develops a nominal resync period that is no shorter // than the requested period but may be longer. The actual time // between any two resyncs may be longer than the nominal period // because the implementation takes time to do work and there may // be competing load and scheduling noise. AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration) // GetStore returns the informer\u0026#39;s local cache as a Store. GetStore() Store // GetController is deprecated, it does nothing useful GetController() Controller // Run starts and runs the shared informer, returning after it stops. // The informer will be stopped when stopCh is closed. Run(stopCh \u0026lt;-chan struct{}) // HasSynced returns true if the shared informer\u0026#39;s store has been // informed by at least one full LIST of the authoritative state // of the informer\u0026#39;s object collection. This is unrelated to \u0026#34;resync\u0026#34;. HasSynced() bool // LastSyncResourceVersion is the resource version observed when last synced with the underlying // store. The value returned is not synchronized with access to the underlying store and is not // thread-safe. LastSyncResourceVersion() string } SharedIndexInformer 是对SharedInformer的实现，可以从结构中看出，SharedIndexInformer 大致具有如下功能：\n索引本地缓存 controller，通过list watch拉取API并推入 Deltal FIFO 事件的处理 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type sharedIndexInformer struct { indexer Indexer // 具有索引的本地缓存 controller Controller // controller processor *sharedProcessor // 事件处理函数集合 cacheMutationDetector MutationDetector listerWatcher ListerWatcher objectType runtime.Object resyncCheckPeriod time.Duration defaultEventHandlerResyncPeriod time.Duration clock clock.Clock started, stopped bool startedLock sync.Mutex blockDeltas sync.Mutex } 而在 tools/cache/share_informer.go 可以看到 shareIndexInformer 的运行过程\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 func (s *sharedIndexInformer) Run(stopCh \u0026lt;-chan struct{}) { defer utilruntime.HandleCrash() fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{ KnownObjects: s.indexer, EmitDeltaTypeReplaced: true, }) cfg := \u0026amp;Config{ Queue: fifo, ListerWatcher: s.listerWatcher, ObjectType: s.objectType, FullResyncPeriod: s.resyncCheckPeriod, RetryOnError: false, ShouldResync: s.processor.shouldResync, Process: s.HandleDeltas, // process 弹出时操作的流程 } func() { s.startedLock.Lock() defer s.startedLock.Unlock() s.controller = New(cfg) s.controller.(*controller).clock = s.clock s.started = true }() // Separate stop channel because Processor should be stopped strictly after controller processorStopCh := make(chan struct{}) var wg wait.Group defer wg.Wait() // Wait for Processor to stop defer close(processorStopCh) // Tell Processor to stop wg.StartWithChannel(processorStopCh, s.cacheMutationDetector.Run) wg.StartWithChannel(processorStopCh, s.processor.run) // 启动事件处理函数 defer func() { s.startedLock.Lock() defer s.startedLock.Unlock() s.stopped = true // Don\u0026#39;t want any new listeners }() s.controller.Run(stopCh) // 启动controller，controller会启动Reflector和fifo的Pop() } 而在操作Delta FIFO中可以看到，做具体操作时，会将动作分发至对应的事件处理函数中，这个是informer初始化时对事件操作的函数\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error { s.blockDeltas.Lock() defer s.blockDeltas.Unlock() for _, d := range obj.(Deltas) { switch d.Type { case Sync, Replaced, Added, Updated: s.cacheMutationDetector.AddObject(d.Object) if old, exists, err := s.indexer.Get(d.Object); err == nil \u0026amp;\u0026amp; exists { if err := s.indexer.Update(d.Object); err != nil { return err } isSync := false switch { case d.Type == Sync: isSync = true case d.Type == Replaced: if accessor, err := meta.Accessor(d.Object); err == nil { if oldAccessor, err := meta.Accessor(old); err == nil { isSync = accessor.GetResourceVersion() == oldAccessor.GetResourceVersion() } } } // 事件的分发 s.processor.distribute(updateNotification{oldObj: old, newObj: d.Object}, isSync) } else { if err := s.indexer.Add(d.Object); err != nil { return err } // 事件的分发 s.processor.distribute(addNotification{newObj: d.Object}, false) } case Deleted: if err := s.indexer.Delete(d.Object); err != nil { return err } s.processor.distribute(deleteNotification{oldObj: d.Object}, false) } } return nil } 事件处理函数 processor 启动informer时也会启动注册进来的事件处理函数；processor 就是这个事件处理函数。\nrun() 函数会启动两个 listener，j监听事件处理业务函数 listener.run 和 事件的处理\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 wg.StartWithChannel(processorStopCh, s.processor.run) func (p *sharedProcessor) run(stopCh \u0026lt;-chan struct{}) { func() { p.listenersLock.RLock() defer p.listenersLock.RUnlock() for _, listener := range p.listeners { p.wg.Start(listener.run) p.wg.Start(listener.pop) } p.listenersStarted = true }() \u0026lt;-stopCh p.listenersLock.RLock() defer p.listenersLock.RUnlock() for _, listener := range p.listeners { close(listener.addCh) // Tell .pop() to stop. .pop() will tell .run() to stop } p.wg.Wait() // Wait for all .pop() and .run() to stop } 可以看出，就是拿到的事件，根据注册的到informer的事件函数进行处理\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func (p *processorListener) run() { stopCh := make(chan struct{}) wait.Until(func() { for next := range p.nextCh { // 消费事件 switch notification := next.(type) { case updateNotification: p.handler.OnUpdate(notification.oldObj, notification.newObj) case addNotification: p.handler.OnAdd(notification.newObj) case deleteNotification: p.handler.OnDelete(notification.oldObj) default: utilruntime.HandleError(fmt.Errorf(\u0026#34;unrecognized notification: %T\u0026#34;, next)) } } // the only way to get here is if the p.nextCh is empty and closed close(stopCh) }, 1*time.Second, stopCh) } informer中的事件的设计 了解了informer如何处理事件，就需要学习下，informer的事件系统设计 prossorListener\n事件的添加 当在handleDelta时，会分发具体的事件\ngo 1 2 // 事件的分发 s.processor.distribute(updateNotification{oldObj: old, newObj: d.Object}, isSync) 此时，事件泵 Pop() 会根据接收到的事件进行处理\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // run() 时会启动一个事件泵 p.wg.Start(listener.pop) func (p *processorListener) pop() { defer utilruntime.HandleCrash() defer close(p.nextCh) var nextCh chan\u0026lt;- interface{} var notification interface{} for { select { case nextCh \u0026lt;- notification: // 这里实际上是一个阻塞的等待 // 单向channel 可能不会走到这步骤 var ok bool // deltahandle 中 distribute 会将事件添加到addCh待处理事件中 // 处理完事件会再次拿到一个事件 notification, ok = p.pendingNotifications.ReadOne() if !ok { // Nothing to pop nextCh = nil // Disable this select case } // 处理 分发过来的事件 addCh case notificationToAdd, ok := \u0026lt;-p.addCh: // distribute分发的事件 if !ok { return } // 这里代表第一次，没有任何事件时，或者上面步骤完成读取 if notification == nil { // 就会走这里 notification = notificationToAdd nextCh = p.nextCh } else { // notification否则代表没有处理完，将数据再次添加到待处理中 p.pendingNotifications.WriteOne(notificationToAdd) } } } } 该消息事件的流程图为\n通过一个简单实例来学习client-go中的消息通知机制\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; \u0026#34;k8s.io/utils/buffer\u0026#34; ) var nextCh1 = make(chan interface{}) var addCh = make(chan interface{}) var stopper = make(chan struct{}) var notification interface{} var pendding = *buffer.NewRingGrowing(2) func main() { // pop go func() { var nextCh chan\u0026lt;- interface{} var notification interface{} //var n int for { fmt.Println(\u0026#34;busy wait\u0026#34;) fmt.Println(\u0026#34;entry select\u0026#34;, notification) select { // 初始时，一个未初始化的channel，nil，形成一个阻塞（单channel下是死锁） case nextCh \u0026lt;- notification: fmt.Println(\u0026#34;entry nextCh\u0026#34;, notification) var ok bool // 读不到数据代表已处理完，置空锁 notification, ok = pendding.ReadOne() if !ok { fmt.Println(\u0026#34;unactive nextch\u0026#34;) nextCh = nil } // 事件的分发，监听，初始时也是一个阻塞 case notificationToAdd, ok := \u0026lt;-addCh: fmt.Println(notificationToAdd, notification) if !ok { return } // 线程安全 // 当消息为空时，没有被处理 // 锁为空，就分发数据 if notification == nil { fmt.Println(\u0026#34;frist notification nil\u0026#34;) notification = notificationToAdd nextCh = nextCh1 // 这步骤等于初始化了局部的nextCh，会触发上面的流程 } else { // 在第三次时，会走到这里，数据进入环 fmt.Println(\u0026#34;into ring\u0026#34;, notificationToAdd) pendding.WriteOne(notificationToAdd) } } } }() // producer go func() { i := 0 for { i++ if i%5 == 0 { addCh \u0026lt;- fmt.Sprintf(\u0026#34;thread 2 inner -- %d\u0026#34;, i) time.Sleep(time.Millisecond * 9000) } else { addCh \u0026lt;- fmt.Sprintf(\u0026#34;thread 2 outer -- %d\u0026#34;, i) time.Sleep(time.Millisecond * 500) } } }() // subsriber go func() { for { for next := range nextCh1 { time.Sleep(time.Millisecond * 300) fmt.Println(\u0026#34;consumer\u0026#34;, next) } } }() \u0026lt;-stopper } 总结，这里的机制类似于线程安全，进入临界区的一些算法，临界区就是 nextCh，notification 就是保证了至少有一个进程可以进入临界区（要么分发事件，要么生产事件）；nextCh 和 nextCh1 一个是局部管道一个是全局的，管道未初始化代表了死锁（阻塞）；当有消息要处理时，会将局部管道 nextCh 赋值给 全局 nextCh1 此时相当于解除了分发的步骤（对管道赋值，触发分发操作）；ringbuffer 实际上是提供了一个对 notification 加锁的操作，在没有处理的消息时，需要保障 notification 为空，同时也关闭了流程 nextCh 的写入。这里主要是考虑对golang中channel的用法\n","permalink":"https://www.161616.top/ch08-informer/","summary":"之前了解了client-go中的架构设计，也就是 tools/cache 下面的一些概念，那么下面将对informer进行分析\nController 在client-go informer架构中存在一个 controller ，这个不是 Kubernetes 中的Controller组件；而是在 tools/cache 中的一个概念，controller 位于 informer 之下，Reflector 之上。code\nConfig 从严格意义上来讲，controller 是作为一个 sharedInformer 使用，通过接受一个 Config ，而 Reflector 则作为 controller 的 slot。Config 则包含了这个 controller 里所有的设置。\ngo 1 2 3 4 5 6 7 8 9 type Config struct { Queue // DeltaFIFO ListerWatcher // 用于list watch的 Process ProcessFunc // 定义如何从DeltaFIFO中弹出数据后处理的操作 ObjectType runtime.Object // Controller处理的对象数据，实际上就是kubernetes中的资源 FullResyncPeriod time.Duration // 全量同步的周期 ShouldResync ShouldResyncFunc // Reflector通过该标记来确定是否应该重新同步 RetryOnError bool } controller 然后 controller 又为 reflertor 的上层","title":"源码分析client-go架构 - 什么是informer"},{"content":"Prepare Introduction 从2016年8月起，Kubernetes官方提取了与Kubernetes相关的核心源代码，形成了一个独立的项目，即client-go，作为官方提供的go客户端。Kubernetes的部分代码也是基于这个项目的。\nclient-go 是kubernetes中广义的客户端基础库，在Kubernetes各个组件中或多或少都有使用其功能。。也就是说，client-go可以在kubernetes集群中添加、删除和查询资源对象（包括deployment、service、pod、ns等）。\n在了解client-go前，还需要掌握一些概念\n在客户端验证 API 使用证书和使用令牌，来验证客户端 kubernetes集群的访问模式 使用证书和令牌来验证客户端 在访问apiserver时，会对访问者进行鉴权，因为是https请求，在请求时是需要ca的，也可以使用 -k 使用insecure模式\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 $ curl --cacert /etc/kubernetes/pki/ca.crt https://10.0.0.4:6443/version \\{ \u0026#34;major\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;minor\u0026#34;: \u0026#34;18+\u0026#34;, \u0026#34;gitVersion\u0026#34;: \u0026#34;v1.18.20-dirty\u0026#34;, \u0026#34;gitCommit\u0026#34;: \u0026#34;1f3e19b7beb1cc0110255668c4238ed63dadb7ad\u0026#34;, \u0026#34;gitTreeState\u0026#34;: \u0026#34;dirty\u0026#34;, \u0026#34;buildDate\u0026#34;: \u0026#34;2022-05-17T12:45:14Z\u0026#34;, \u0026#34;goVersion\u0026#34;: \u0026#34;go1.16.15\u0026#34;, \u0026#34;compiler\u0026#34;: \u0026#34;gc\u0026#34;, \u0026#34;platform\u0026#34;: \u0026#34;linux/amd64\u0026#34; } $ curl -k https://10.0.0.4:6443/api/v1/namespace/default/pods/netbox { \u0026#34;kind\u0026#34;: \u0026#34;Status\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: { }, \u0026#34;status\u0026#34;: \u0026#34;Failure\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;namespace \\\u0026#34;default\\\u0026#34; is forbidden: User \\\u0026#34;system:anonymous\\\u0026#34; cannot get resource \\\u0026#34;namespace/pods\\\u0026#34; in API group \\\u0026#34;\\\u0026#34; at the cluster scope\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;Forbidden\u0026#34;, \u0026#34;details\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;namespace\u0026#34; }, \u0026#34;code\u0026#34;: 403 } 从错误中可以看出，该请求已通过身份验证，用户是 system:anonymous，但该用户未授权列出对应的资源。而上述请求只是忽略 curl 的https请求需要做的验证，而Kubernetes也有对应验证的机制，这个时候需要提供额外的身份信息来获得所需的访问权限。Kubernetes支持多种身份认证机制，ssl证书也是其中一种。\n注：在Kubernetes中没有表示用户的资源。即kubernetes集群中，无法添加和创建。但由集群提供的有效证书的用户都视为允许的用户。Kubernetes从证书中的使用者CN和使用者可选名称中获得用户；然后，RBAC 判断用户是否有权限操作资源。从 Kubernetes1.4 开始，支持用户组，即证书中的O\n可以使用 curl 的 --cert 和 --key 指定用户的证书\ntext 1 2 3 4 curl --cacert /etc/kubernetes/pki/ca.crt \\ --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt \\ --key /etc/kubernetes/pki/apiserver-ubelet-client.key \\ https://10.0.0.4:6443/api/v1/namespaces/default/pods/netbox 使用serviceaccount验证客户端身份 使用一个serviceaccount JWT，获取一个SA的方式如下\ntext 1 2 3 4 5 6 7 kubectl get secrets \\ $(kubectl get serviceaccounts/default -o jsonpath=\u0026#39;{.secrets[0].name}\u0026#39;) -o jsonpath=\u0026#39;{.data.token}\u0026#39; \\ | base64 --decode JWT=$(kubectl get secrets \\ $(kubectl get serviceaccounts/default -o jsonpath=\u0026#39;{.secrets[0].name}\u0026#39;) -o jsonpath=\u0026#39;{.data.token}\u0026#39; \\ | base64 --decode) 使用secret来访问API\ntext 1 2 3 curl --cacert /etc/kubernetes/pki/ca.crt \\ --header \u0026#34;Authorization: Bearer $JWT\u0026#34; \\ https://10.0.0.4:6443/apis/apps/v1/namespaces/default/deployments Pod内部调用Kubernetes API kubernete会将Kubernetes API地址通过环境变量提供给 Pod，可以通过命令看到\ntext 1 2 3 4 5 6 7 8 9 $ env|grep -i kuber KUBERNETES_SERVICE_PORT=443 KUBERNETES_PORT=tcp://192.168.0.1:443 KUBERNETES_PORT_443_TCP_ADDR=192.168.0.1 KUBERNETES_PORT_443_TCP_PORT=443 KUBERNETES_PORT_443_TCP_PROTO=tcp KUBERNETES_PORT_443_TCP=tcp://192.168.0.1:443 KUBERNETES_SERVICE_PORT_HTTPS=443 KUBERNETES_SERVICE_HOST=192.168.0.1 并且还会在将 Kubernetes CA和SA等信息放置在目录 /var/run/secrets/kubernetes.io/serviceaccount/，通过这些就可以从Pod内部访问API\ntext 1 2 3 cd /var/run/secrets/kubernetes.io/serviceaccount/ curl --cacert ca.crt --header \u0026#34;Authorization: Bearer $(cat token)\u0026#34; https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1/namespaces/default/pods/netbox client-go 关于client-go的模块 k8s.io/api 与Pods、ConfigMaps、Secrets和其他Kubernetes 对象所对应的数据结构都在，k8s.io/api，此包几乎没有算法，仅仅是数据机构，该模块有多达上千个用于描述Kubernetes中资源API的结构；通常被client，server，controller等其他的组件使用。\nk8s.io/apimachinery 根据该库的描述文件可知，这个库是Server和Client中使用的Kubernetes API共享依赖库，也是kubernetes中更低一级的通用的数据结构。在我们构建自定义资源时，不需要为自定义结构创建属性，如 Kind, apiVersion，name\u0026hellip;，这些都是库 apimachinery 所提供的功能。\n如，在包 k8s.io/apimachinery/pkg/apis/meta 定义了两个结构 TypeMeta 和 ObjectMeta；将这这两个结构嵌入自定义的结构中，可以以通用的方式兼容对象，如Kubernetes中的资源 Deplyment 也是这么完成的\n通过图来了解Kubernetes的资源如何实现的\r如在 k8s.io/apimachinery/pkg/runtime/interfaces.go 中定义了 interface，这个类为在schema中注册的API都需要实现这个结构\ngo 1 2 3 4 type Object interface { GetObjectKind() schema.ObjectKind DeepCopyObject() Object } 非结构化数据 非结构化数据 Unstructured 是指在kubernete中允许将没有注册为Kubernetes API的对象，作为Json对象的方式进行操作，如，使用非结构化 Kubernetes 对象\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 desired := \u0026amp;unstructured.Unstructured{ Object: map[string]interface{}{ \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;ConfigMap\u0026#34;, \u0026#34;metadata\u0026#34;: map[string]interface{}{ \u0026#34;namespace\u0026#34;: namespace, \u0026#34;generateName\u0026#34;: \u0026#34;crud-dynamic-simple-\u0026#34;, }, \u0026#34;data\u0026#34;: map[string]interface{}{ \u0026#34;foo\u0026#34;: \u0026#34;bar\u0026#34;, }, }, } 非结构化数据的转换 在 k8s.io/apimachinery/pkg/runtime.UnstructuredConverter 中，也提供了将非结构化数据转换为Kubernetes API注册过的结构，参考如何将非结构化对象转换为Kubernetes Object。\ninstall client-go 如何选择 client-go 的版本\n​\t对于不同的kubernetes版本使用标签 v0.x.y 来表示对应的客户端版本。具体对应参考 client-go 。\n​\t例如使用的kubernetes版本为 v1.18.20 则使用对应的标签 v0.x.y 来替换符合当前版本的客户端库。例如：\ntext 1 go get k8s.io/client-go@v0.18.10 官网中给出了client-go的兼容性矩阵，可以很明了的看出如何选择适用于自己kubernetes版本的对应的client-go\n✓ 表示 该版本的 client-go 与对应的 kubernetes版本功能完全一致 + client-go 具有 kubernetes apiserver中不具备的功能。 - Kubernetes apiserver 具有client-go 无法使用的功。 一般情况下，除了对应的版本号完全一致外，其他都存在 功能的+-。\nclient-go 目录介绍 client-go的每一个目录都是一个go package\nkubernetes 包含与Kubernetes API所通信的客户端集 discovery 用于发现kube-apiserver所支持的api dynamic 包含了一个动态客户端，该客户端能够对kube-apiserver任意的API进行操作。 transport 提供了用于设置认证和启动链接的功能 tools/cache: 一些 low-level controller与一些数据结构如fifo，reflector等 structure of client-go RestClient：是最基础的基础架构，其作用是将是使用了http包进行封装成RESTClient。位于rest 目录，RESTClient封装了资源URL的通用格式，例如Get()、Put()、Post() Delete()。是与Kubernetes API的访问行为提供的基于RESTful方法进行交互基础架构。\n同时支持Json 与 protobuf 支持所有的原生资源和CRD ClientSet：Clientset基于RestClient进行封装对 Resource 与 version 管理集合；如何创建\nDiscoverySet：RestClient进行封装，可动态发现 kube-apiserver 所支持的 GVR（Group Version Resource）；如何创建，这种类型是一种非映射至clientset的客户端\nDynamicClient：基于RestClient，包含动态的客户端，可以对Kubernetes所支持的 API对象进行操作，包括CRD；如何创建\n仅支持json\nfakeClient， client-go 实现的mock对象，主要用于单元测试。\n以上client-go所提供的客户端，仅可使用kubeconfig进行连接。\n什么是clientset clientset代表了kubernetes中所有的资源类型，这里不包含CRD的资源，如：\ncore extensions batch \u0026hellip; client-go使用 DynamicClient客户端\n与 ClientSet 的区别是，可以对任意 Kubernetes 资源进行 RESTful 操作。同样提供管理的方法\n最大的不同，ClientSet 需要预先实现每种 Resource 和 Version 的操作，内部的数据都是结构化数据（已知数据结构）；DynamicClient 内部实现了 Unstructured，用于处理非结构化的数据（无法提前预知的数据结构），这是其可以处理 CRD 自定义资源的关键。\ndynamicClient 实现流程\n通过 NewForConfig 实例化 conf 为 DynamicInterface客户端\nDynamicInterface 客户端中，实现了一个Resource 方法即为实现了Interface接口\ndynamicClient 实现了非结构化数据类型与rest client，可以通过其方法将Resource 由rest从apiserver中获得api对象，runtime.DeafultUnstructuredConverter.FromUnstructrued 转为对应的类型。\n注意：GVR 中资源类型 resource为复数。kind:Pod 即为 Pods\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 package main import ( \u0026#34;context\u0026#34; \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; v1 \u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34; \u0026#34;k8s.io/apimachinery/pkg/runtime/schema\u0026#34; \u0026#34;k8s.io/client-go/dynamic\u0026#34; \u0026#34;k8s.io/client-go/kubernetes\u0026#34; \u0026#34;k8s.io/client-go/rest\u0026#34; \u0026#34;k8s.io/client-go/tools/clientcmd\u0026#34; \u0026#34;k8s.io/client-go/util/homedir\u0026#34; ) func main() { var ( k8sconfig *string //使用kubeconfig配置文件进行集群权限认证 restConfig *rest.Config err error ) if home := homedir.HomeDir(); home != \u0026#34;\u0026#34; { k8sconfig = flag.String(\u0026#34;kubeconfig\u0026#34;, fmt.Sprintf(\u0026#34;%s/.kube/config\u0026#34;, home), \u0026#34;kubernetes auth config\u0026#34;) } k8sconfig = k8sconfig flag.Parse() if _, err := os.Stat(*k8sconfig); err != nil { panic(err) } if restConfig, err = rest.InClusterConfig(); err != nil { // 这里是从masterUrl 或者 kubeconfig传入集群的信息，两者选一 restConfig, err = clientcmd.BuildConfigFromFlags(\u0026#34;\u0026#34;, *k8sconfig) if err != nil { panic(err) } } // 创建客户端类型 // NewForConfig creates a new dynamic client or returns an error. // dynamic.NewForConfig(restConfig) // NewForConfig creates a new Clientset for the given config // kubernetes.NewForConfig(restConfig) // NewDiscoveryClientForConfig creates a new DiscoveryClient for the given config. //clientset, err := discovery.NewDiscoveryClientForConfig(restConfig) dynamicset, err := dynamic.NewForConfig(restConfig) // 这里遵循的是 kubernetes Rest API，如Pod是 // /api/v1/namespaces/{namespace}/pods // /apis/apps/v1/namespaces/{namespace}/deployments // 遵循GVR格式填写 podList, err := dynamicset.Resource(schema.GroupVersionResource{ Group: \u0026#34;\u0026#34;, Version: \u0026#34;v1\u0026#34;, Resource: \u0026#34;pods\u0026#34;, }).Namespace(\u0026#34;default\u0026#34;).List(context.TODO(), v1.ListOptions{}) if err != nil { panic(err) } daemonsetList, err := dynamicset.Resource(schema.GroupVersionResource{ Group: \u0026#34;apps\u0026#34;, Version: \u0026#34;v1\u0026#34;, Resource: \u0026#34;daemonsets\u0026#34;, }).Namespace(\u0026#34;kube-system\u0026#34;).List(context.TODO(), v1.ListOptions{}) if err != nil { panic(err) } for _, row := range podList.Items { fmt.Println(row.GetName()) } for _, row := range daemonsetList.Items { fmt.Println(row.GetName()) } // clientset mode clientset, err := kubernetes.NewForConfig(restConfig) podIns, err := clientset.CoreV1().Pods(\u0026#34;default\u0026#34;).List(context.TODO(), v1.ListOptions{}) for _, row := range podIns.Items { fmt.Println(row.GetName()) } } Extension\n一些client-go使用\nInformer informer是client-go提供的 Listwatcher 接口，主要作为 Controller构成的组件，在Kubernetes中， Controller的一个重要作用是观察对象的期望状态 spec 和实际状态 statue 。为了观察对象的状态，Controller需要向 Apiserver发送请求；但是通常情况下，频繁向Apiserver发出请求的会增加etcd的压力，为了解决这类问题，client-go 一个缓存，通过缓存，控制器可以不必发出大量请求，并且只关心对象的事件。也就是 informer。\n从本质上来讲，informer是使用kubernetes API观察其变化，来维护状态的缓存，称为 indexer；并通过对应事件函数通知客户端信息的变化，informer为一系列组件，通过这些组件来实现的这些功能。\nReflector：与 apiserver交互的组件 Delta FIFO：一个特殊的队列，Reflector将状态的变化存储在里面 indexer：本地存储，与etcd保持一致，减轻API Server与etcd的压力 Processor：监听处理器，通过将监听到的事件发送给对应的监听函数 Controller：从队列中对整个数据的编排处理的过程 informer的工作模式 首先通过List从Kubernetes API中获取资源所有对象并同时缓存，然后通过Watch机制监控资源。这样，通过informer与缓存，就可以直接和informer交互，而不用每次都和Kubernetes API交互。\n另外，informer 还提供了事件的处理机制，以便 Controller 或其他应用程序根据回调钩子函数等处理特定的业务逻辑。因为Informer可以通过List/Watch机制监控所有资源的所有事件，只要在Informer中添加ResourceEventHandler实例的回调函数，如：onadd(obj interface {}), onupdate (oldobj, newobj interface {})和OnDelete( obj interface {}) 可以实现处理资源的创建、更新和删除。 在Kubernetes中，各种控制器都使用了Informer。\n分析informer的流程 通过代码 k8s.io/client-go/informers/apps/v1/deployment.go 可以看出，在每个控制器下，都实现了一个 Informer 和 Lister ，Lister就是indexer；\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type SharedInformer interface { // 添加一个事件处理函数，使用informer默认的resync period AddEventHandler(handler ResourceEventHandler) // 将事件处理函数注册到 share informer，将resyncPeriod作为参数传入 AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration) // 从本地缓存获取的信息作为infomer的返回 GetStore() Store // 已弃用 GetController() Controller // 运行一个informer，当stopCh停止时，informer也被关闭 Run(stopCh \u0026lt;-chan struct{}) // HasSynced returns true if the shared informer\u0026#39;s store has been // informed by at least one full LIST of the authoritative state // of the informer\u0026#39;s object collection. This is unrelated to \u0026#34;resync\u0026#34;. HasSynced() bool // LastSyncResourceVersion is the resource version observed when last synced with the underlying store. The value returned is not synchronized with access to the underlying store and is not thread-safe. LastSyncResourceVersion() string } 而 Shared Informer 对所有的API组提供一个shared informer\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // SharedInformerFactory provides shared informers for resources in all known // API group versions. type SharedInformerFactory interface { internalinterfaces.SharedInformerFactory ForResource(resource schema.GroupVersionResource) (GenericInformer, error) WaitForCacheSync(stopCh \u0026lt;-chan struct{}) map[reflect.Type]bool Admissionregistration() admissionregistration.Interface Apps() apps.Interface Auditregistration() auditregistration.Interface Autoscaling() autoscaling.Interface Batch() batch.Interface Certificates() certificates.Interface Coordination() coordination.Interface Core() core.Interface Discovery() discovery.Interface Events() events.Interface Extensions() extensions.Interface Flowcontrol() flowcontrol.Interface Networking() networking.Interface Node() node.Interface Policy() policy.Interface Rbac() rbac.Interface Scheduling() scheduling.Interface Settings() settings.Interface Storage() storage.Interface } 可以看到在 k8s.io/client-go/informers/apps/v1/deployment.go 实现了这个interface\ngo 1 2 3 4 type DeploymentInformer interface { Informer() cache.SharedIndexInformer Lister() v1.DeploymentLister } 而在对应的 deployment controller中会调用这个Informer 实现对状态的监听；``\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 // NewDeploymentController creates a new DeploymentController. // appsinformers.DeploymentInformer就是client-go 中的 /apps/v1/deployment实现的informer func NewDeploymentController(dInformer appsinformers.DeploymentInformer, rsInformer appsinformers.ReplicaSetInformer, podInformer coreinformers.PodInformer, client clientset.Interface) (*DeploymentController, error) { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(klog.Infof) eventBroadcaster.StartRecordingToSink(\u0026amp;v1core.EventSinkImpl{Interface: client.CoreV1().Events(\u0026#34;\u0026#34;)}) if client != nil \u0026amp;\u0026amp; client.CoreV1().RESTClient().GetRateLimiter() != nil { if err := ratelimiter.RegisterMetricAndTrackRateLimiterUsage(\u0026#34;deployment_controller\u0026#34;, client.CoreV1().RESTClient().GetRateLimiter()); err != nil { return nil, err } } dc := \u0026amp;DeploymentController{ client: client, eventRecorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: \u0026#34;deployment-controller\u0026#34;}), queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), \u0026#34;deployment\u0026#34;), } dc.rsControl = controller.RealRSControl{ KubeClient: client, Recorder: dc.eventRecorder, } dInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: dc.addDeployment, UpdateFunc: dc.updateDeployment, // This will enter the sync loop and no-op, because the deployment has been deleted from the store. DeleteFunc: dc.deleteDeployment, }) rsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: dc.addReplicaSet, UpdateFunc: dc.updateReplicaSet, DeleteFunc: dc.deleteReplicaSet, }) podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ DeleteFunc: dc.deletePod, }) dc.syncHandler = dc.syncDeployment dc.enqueueDeployment = dc.enqueue dc.dLister = dInformer.Lister() dc.rsLister = rsInformer.Lister() dc.podLister = podInformer.Lister() dc.dListerSynced = dInformer.Informer().HasSynced dc.rsListerSynced = rsInformer.Informer().HasSynced dc.podListerSynced = podInformer.Informer().HasSynced return dc, nil } Reflector reflector是client-go中负责监听 Kubernetes API 的组件，也是整个机制中的生产者，负责将 watch到的数据将其放入 watchHandler 中的delta FIFO队列中。也就是吧etcd的数据反射为 delta fifo的数据\n在代码 k8s.io/client-go/tools/cache/reflector.go 中定义了 Reflector 对象\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 type Reflector struct { // reflector的名称，默认为一个 file:line的格式 name string // 期待的类型名称，这里只做展示用， // 如果提供，是一个expectedGVK字符串类型，否则是expectedType字符串类型 expectedTypeName string // 期待放置在存储中的类型，如果是一个非格式化数据，那么其 APIVersion与Kind也必须为正确的格式 expectedType reflect.Type // GVK 存储中的对象，是GVK格式 expectedGVK *schema.GroupVersionKind // 同步数据的存储 store Store // 这个是reflector的一个核心，提供了 List和Watch功能 listerWatcher ListerWatcher // backoff manages backoff of ListWatch backoffManager wait.BackoffManager resyncPeriod time.Duration ShouldResync func() bool // clock allows tests to manipulate time clock clock.Clock paginatedResult bool // 最后资源的版本号 lastSyncResourceVersion string // 当 lastSyncResourceVersion 过期或者版本太大，这个值将为 true isLastSyncResourceVersionUnavailable bool // 读写锁，对lastSyncResourceVersion的读写操作的保护 lastSyncResourceVersionMutex sync.RWMutex // WatchListPageSize is the requested chunk size of initial and resync watch lists. // scalability problems. // 是初始化时，或者重新同步时的块大小。如果没有设置，将为任意的旧数据 // 因为是提供了分页功能，RV=0则为默认的页面大小 // WatchListPageSize int64 } 而 方法 NewReflector() 给用户提供了一个初始化 Reflector的接口\n在 cotroller.go 中会初始化一个 relector\ngo 1 2 3 4 5 6 7 8 9 10 11 12 func (c *controller) Run(stopCh \u0026lt;-chan struct{}) { defer utilruntime.HandleCrash() go func() { \u0026lt;-stopCh c.config.Queue.Close() }() r := NewReflector( c.config.ListerWatcher, c.config.ObjectType, c.config.Queue, c.config.FullResyncPeriod, ) Reflector下有三个可对用户提供的方法，Run(), ListAndWatch() , LastSyncResourceVersion()\nRun() 是对Reflector的运行，也就是对 ListAndWatch() ；\ngo 1 2 3 4 5 6 7 8 9 func (r *Reflector) Run(stopCh \u0026lt;-chan struct{}) { klog.V(2).Infof(\u0026#34;Starting reflector %s (%s) from %s\u0026#34;, r.expectedTypeName, r.resyncPeriod, r.name) wait.BackoffUntil(func() { if err := r.ListAndWatch(stopCh); err != nil { utilruntime.HandleError(err) } }, r.backoffManager, true, stopCh) klog.V(2).Infof(\u0026#34;Stopping reflector %s (%s) from %s\u0026#34;, r.expectedTypeName, r.resyncPeriod, r.name) } 而 ListAndWatch() 则是实际上真实的对Reflector业务的执行\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 // 前面一些都是对信息的初始化与日志输出 func (r *Reflector) ListAndWatch(stopCh \u0026lt;-chan struct{}) error { klog.V(3).Infof(\u0026#34;Listing and watching %v from %s\u0026#34;, r.expectedTypeName, r.name) var resourceVersion string options := metav1.ListOptions{ResourceVersion: r.relistResourceVersion()} // 分页功能 if err := func() error { initTrace := trace.New(\u0026#34;Reflector ListAndWatch\u0026#34;, trace.Field{\u0026#34;name\u0026#34;, r.name}) defer initTrace.LogIfLong(10 * time.Second) var list runtime.Object var paginatedResult bool var err error listCh := make(chan struct{}, 1) panicCh := make(chan interface{}, 1) go func() { .... // 清理和重新同步的一些 resyncerrc := make(chan error, 1) cancelCh := make(chan struct{}) defer close(cancelCh) go func() { ... }() for { // give the stopCh a chance to stop the loop, even in case of continue statements further down on errors select { case \u0026lt;-stopCh: return nil default: } timeoutSeconds := int64(minWatchTimeout.Seconds() * (rand.Float64() + 1.0)) options = metav1.ListOptions{ ResourceVersion: resourceVersion, // 为了避免watch的挂起设置一个超时 // 仅在工作窗口期，处理任何时间 TimeoutSeconds: \u0026amp;timeoutSeconds, // To reduce load on kube-apiserver on watch restarts, you may enable watch bookmarks. // Reflector doesn\u0026#39;t assume bookmarks are returned at all (if the server do not support // watch bookmarks, it will ignore this field). AllowWatchBookmarks: true, } start := r.clock.Now() // 开始监听 w, err := r.listerWatcher.Watch(options) if err != nil { switch { case isExpiredError(err): // 没有设置 LastSyncResourceVersionExpired 也就是过期，会保持与返回数据相同的 // 首次会先将RV列出 klog.V(4).Infof(\u0026#34;%s: watch of %v closed with: %v\u0026#34;, r.name, r.expectedTypeName, err) case err == io.EOF: // 通常为watch关闭 case err == io.ErrUnexpectedEOF: klog.V(1).Infof(\u0026#34;%s: Watch for %v closed with unexpected EOF: %v\u0026#34;, r.name, r.expectedTypeName, err) default: utilruntime.HandleError(fmt.Errorf(\u0026#34;%s: Failed to watch %v: %v\u0026#34;, r.name, r.expectedTypeName, err)) } // 如果出现 connection refuse，通常与apisserver通讯失败，这个时候会重新发送请求 if utilnet.IsConnectionRefused(err) { time.Sleep(time.Second) continue } return nil } if err := r.watchHandler(start, w, \u0026amp;resourceVersion, resyncerrc, stopCh); err != nil { if err != errorStopRequested { switch { case isExpiredError(err): // 同上步骤的功能 klog.V(4).Infof(\u0026#34;%s: watch of %v closed with: %v\u0026#34;, r.name, r.expectedTypeName, err) default: klog.Warningf(\u0026#34;%s: watch of %v ended with: %v\u0026#34;, r.name, r.expectedTypeName, err) } } return nil } } } 那么在实现时，如 deploymentinformer,会实现 Listfunc和 watchfunc，这其实就是clientset中的操作方法，也是就list与watch\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func NewFilteredDeploymentInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( \u0026amp;cache.ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { tweakListOptions(\u0026amp;options) } return client.AppsV1().Deployments(namespace).List(context.TODO(), options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(\u0026amp;options) } return client.AppsV1().Deployments(namespace).Watch(context.TODO(), options) }, }, \u0026amp;appsv1.Deployment{}, resyncPeriod, indexers, ) } tools/cache/controller.go 是存储controller的配置及实现。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Config struct { Queue // 对象的队列，必须为DeltaFIFO ListerWatcher // 这里能够监视并列出对象的一些信息，这个对象接受process函数的弹出 // Something that can process a popped Deltas. Process ProcessFunc // 处理Delta的弹出 // 对象类型，这个controller期待的处理类型，其apiServer与kind必须正确，即，GVR必须正确 ObjectType runtime.Object // FullResyncPeriod是每次重新同步的时间间隔 FullResyncPeriod time.Duration // type ShouldResyncFunc func() bool // 返回值nil或true，则表示reflector继续同步 ShouldResync ShouldResyncFunc RetryOnError bool // 标志位，true时，在process()返回错误时重新排列对象 // Called whenever the ListAndWatch drops the connection with an error. // 断开连接是出现错误调用这个函数处理 WatchErrorHandler WatchErrorHandler // WatchListPageSize is the requested chunk size of initial and relist watch lists. WatchListPageSize int64 } 实现这个接口\ngo 1 2 3 4 5 6 type controller struct { config Config reflector *Reflector reflectorMutex sync.RWMutex clock clock.Clock } New() 为给定controller 配置的设置，即为上面的config struct，用来初始化controller对象\nNewInformer() ：返回一个store（保存数据的最终接口）和一个用于store的controller，同时提供事件的通知(crud)等\nNewIndexerInformer()：返回一个索引与一个用于索引填充的控制器\n控制器的run()的功能实现\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func (c *controller) Run(stopCh \u0026lt;-chan struct{}) { defer utilruntime.HandleCrash() // 延迟销毁 go func() { // 信号处理，用于线程管理 \u0026lt;-stopCh c.config.Queue.Close() }() r := NewReflector( // 初始化Reflector c.config.ListerWatcher, // ls c.config.ObjectType, c.config.Queue, c.config.FullResyncPeriod, ) r.ShouldResync = c.config.ShouldResync // 配置是否应该继续同步 r.WatchListPageSize = c.config.WatchListPageSize r.clock = c.clock if c.config.WatchErrorHandler != nil { // 断开连接错误处理 r.watchErrorHandler = c.config.WatchErrorHandler } c.reflectorMutex.Lock() c.reflector = r c.reflectorMutex.Unlock() var wg wait.Group wg.StartWithChannel(stopCh, r.Run) // 这里是真正的运行。 // processLoop() 是DeltaFIFO的消费者方法 wait.Until(c.processLoop, time.Second, stopCh) // 消费队列的数据 wg.Wait() } 总结 在controller的初始化时就初始化了Reflector， controller.Run里面Reflector是结构体初始化时的Reflector，主要作用是watch指定的资源，并且将变化同步到本地的store中。\nReflector接着执行ListAndWatch函数，ListAndWatch第一次会列出所有的对象，并获取资源对象的版本号，然后watch资源对象的版本号来查看是否有被变更。首先会将资源版本号设置为0，list()可能会导致本地的缓存相对于etcd里面的内容存在延迟，Reflector会通过watch的方法将延迟的部分补充上，使得本地缓存数据与etcd的数据保持一致。\ncontroller.Run函数还会调用processLoop函数，processLoop通过调用HandleDeltas，再调用distribute，processorListener.add最终将不同更新类型的对象加入processorListener的channel中，供processorListener.Run使用。\nDelta FIFO 通过下图可以看出，Delta FIFO 是位于Reflector中的一个FIFO队列，那么 Delta FIFO 究竟是什么，让我们来进一步深剖。\n图源于：https://miro.medium.com/max/700/1*iI8uFsPRBY5m_g_WW4huMQ.png\r在代码中的注释可以看到一些信息，根据信息可以总结出\nDelta FIFO 是一个生产者-消费者的队列，生产者是 Reflector，消费者是 Pop() 与传统的FIFO有两点不同 Delta FIFO Delta FIFO也是实现了 Queue以及一些其他 interface 的类，\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 type DeltaFIFO struct { lock sync.RWMutex // 一个读写锁，保证线程安全 cond sync.Cond items map[string]Deltas // 存放的类型是一个key[string] =》 value[Delta] 类型的数据 queue []string // 用于存储item的key，是一个fifo populated bool // populated 是用来标记首次被加入的数据是否被变动 initialPopulationCount int // 首次调用 replace() 的数量 keyFunc KeyFunc knownObjects KeyListerGetter // 这里为indexer closed bool // 代表已关闭 closedLock sync.Mutex emitDeltaTypeReplaced bool // 表示事件的类型，true为 replace(), false 为 sync() } 那么delta的类型是，也就是说通常情况下，Delta为一个 string[runtime.object] 的对象\ngo 1 2 3 4 type Delta struct { Type DeltaType // 这就是一个string Object interface{} // 之前API部分有了解到，API的类型大致为两类，runtime.Object和非结构化数据 } apimachinery/pkg/runtime/interfaces.go\n那么此时，已经明白了Delta FIFO的结构，为一个Delta的队列，整个结构如下\n第一步创建一个Delta FIFO 现在版本中，对创建Delta FIFO是通过函数 NewDeltaFIFOWithOptions()\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func NewDeltaFIFOWithOptions(opts DeltaFIFOOptions) *DeltaFIFO { if opts.KeyFunction == nil { opts.KeyFunction = MetaNamespaceKeyFunc // 默认的计算key的方法 } f := \u0026amp;DeltaFIFO{ items: map[string]Deltas{}, queue: []string{}, keyFunc: opts.KeyFunction, knownObjects: opts.KnownObjects, emitDeltaTypeReplaced: opts.EmitDeltaTypeReplaced, } f.cond.L = \u0026amp;f.lock return f } queueActionLocked，Delta FIFO添加操作 这里说下之前说道的，在追加时的操作 queueActionLocked ，如add update delete实际上走的都是这里\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func (f *DeltaFIFO) queueActionLocked(actionType DeltaType, obj interface{}) error { id, err := f.KeyOf(obj) // 计算key if err != nil { return KeyError{obj, err} } // 把新数据添加到DeltaFIFO中，Detal就是 动作为key，对象为值 // item是DeltaFIFO中维护的一个 map[string]Deltas newDeltas := append(f.items[id], Delta{actionType, obj}) newDeltas = dedupDeltas(newDeltas) // 去重，去重我们前面讨论过了 if len(newDeltas) \u0026gt; 0 { if _, exists := f.items[id]; !exists { f.queue = append(f.queue, id) } // 不存在则添加 f.items[id] = newDeltas f.cond.Broadcast() } else { delete(f.items, id) // 这里走不到，因为添加更新等操作用newDelta是1 // 源码中也说要忽略这里 } return nil } 在FIFO继承的Stroe的方法中，如，Add, Update等都是需要去重的，去重的操作是通过对比最后一个和倒数第二个值\ngo 1 2 3 4 5 6 7 8 9 func (f *DeltaFIFO) queueActionLocked(actionType DeltaType, obj interface{}) error { id, err := f.KeyOf(obj) if err != nil { return KeyError{obj, err} } newDeltas := append(f.items[id], Delta{actionType, obj}) newDeltas = dedupDeltas(newDeltas) ... 在函数 dedupDeltas() 中实现的这个\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // re-listing and watching can deliver the same update multiple times in any order. This will combine the most recent two deltas if they are the same. func dedupDeltas(deltas Deltas) Deltas { n := len(deltas) if n \u0026lt; 2 { return deltas } a := \u0026amp;deltas[n-1] // 如 [1,2,3,4] a=4 b := \u0026amp;deltas[n-2] // b=3,这里两个值其实为事件 if out := isDup(a, b); out != nil { d := append(Deltas{}, deltas[:n-2]...) return append(d, *out) } return deltas } 如果b对象的类型是 DeletedFinalStateUnknown 也会认为是一个旧对象被删除，这里在去重时也只是对删除的操作进行去重。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // tools/cache/delta_fifo.go func isDup(a, b *Delta) *Delta { if out := isDeletionDup(a, b); out != nil { return out } // TODO: Detect other duplicate situations? Are there any? return nil } // keep the one with the most information if both are deletions. func isDeletionDup(a, b *Delta) *Delta { if b.Type != Deleted || a.Type != Deleted { return nil } // Do more sophisticated checks, or is this sufficient? if _, ok := b.Object.(DeletedFinalStateUnknown); ok { return a } return b } 为什么需要去重？什么情况下需合并\n代码中开发者给我们留了一个TODO\nTODO: is there anything other than deletions that need deduping?\n取决于Detal FIFO 生产-消费延迟 当在一个资源的创建时，其状态会频繁的更新，如 Creating，Runinng等，这个时候会出现大量写入FIFO中的数据，但是在消费端可能之前的并未消费完。 在上面那种情况下，以及Kubernetes 声明式 API 的设计，其实多余的根本不关注，只需要最后一个动作如Running，这种情况下，多个内容可以合并为一个步骤 然而在代码中，去重仅仅是在Delete状态生效，显然这不可用；那么结合这些得到： 在一个工作时间窗口内，如果对于删除操作来说发生多次，与发生一次实际上没什么区别，可以去重 但在更新于新增操作时，实际上在对于声明式 API 的设计个人感觉是完全可以做到去重操作。 同一个时间窗口内多次操作，如更新，实际上Kubernetes应该只关注最终状态而不是命令式？ Compute Key 上面大概对一些Detal FIFO的逻辑进行了分析，那么对于Detal FIFO如何去计算，也就是说 MetaNamespaceKeyFunc ，这个是默认的KeyFunc，作用是计算Detals中的唯一key。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 func MetaNamespaceKeyFunc(obj interface{}) (string, error) { if key, ok := obj.(ExplicitKey); ok { // 显示声明的则为这个值 return string(key), nil } meta, err := meta.Accessor(obj) // 那么使用Accessor,每一个资源都会实现这个Accessor if err != nil { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;object has no meta: %v\u0026#34;, err) } if len(meta.GetNamespace()) \u0026gt; 0 { return meta.GetNamespace() + \u0026#34;/\u0026#34; + meta.GetName(), nil } return meta.GetName(), nil } ObjectMetaAccessor 每个Kubernetes资源都会实现这个对象，如Deployment\ngo 1 2 3 4 5 6 7 8 9 10 11 12 // accessor interface type ObjectMetaAccessor interface { GetObjectMeta() Object } // 会被ObjectMeta所实现 func (obj *ObjectMeta) GetObjectMeta() Object { return obj } // 而每一个资源都会继承这个 ObjectMeta，如 ClusterRole type ClusterRole struct { metav1.TypeMeta `json:\u0026#34;,inline\u0026#34;` metav1.ObjectMeta `json:\u0026#34;metadata,omitempty\u0026#34;protobuf:\u0026#34;bytes,1,opt,name=metadata\u0026#34;` 那么这个Deltas的key则为集群类型的是资源本身的名字，namespace范围的则为 meta.GetNamespace() + \u0026quot;/\u0026quot; + meta.GetName()，可以在上面代码中看到，这样就可以给Detal生成了一个唯一的key\nkeyof，用于计算对象的key go 1 2 3 4 5 6 7 8 9 10 11 12 func (f *DeltaFIFO) KeyOf(obj interface{}) (string, error) { if d, ok := obj.(Deltas); ok { if len(d) == 0 { // 长度为0的时候是一个初始的类型 return \u0026#34;\u0026#34;, KeyError{obj, ErrZeroLengthDeltasObject} } obj = d.Newest().Object // 用最新的一个对象，如果为空则是nil } if d, ok := obj.(DeletedFinalStateUnknown); ok { return d.Key, nil // 到了这里，之前提到过，是一个过期的值将会被删除 } return f.keyFunc(obj) // 调用具体的key计算函数 } Indexer indexer 在整个 client-go 架构中提供了一个具有线程安全的数据存储的对象存储功能；对于Indexer这里会分析下对应的架构及使用方法。\nclient-go/tools/cache/index.go 中可以看到 indexer是一个实现了Store 的一个interface\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Indexer interface { // 继承了store，拥有store的所有方法 Store // 返回indexname的obj的交集 Index(indexName string, obj interface{}) ([]interface{}, error) // 通过对 indexName，indexedValue与之相匹配的集合 IndexKeys(indexName, indexedValue string) ([]string, error) // 给定一个indexName 返回所有的indexed ListIndexFuncValues(indexName string) []string // 通过indexname，返回与indexedvalue相关的 obj ByIndex(indexName, indexedValue string) ([]interface{}, error) // 返回所有的indexer GetIndexers() Indexers AddIndexers(newIndexers Indexers) error } 实际上对他的实现是一个 cache，cache是一个KeyFunc与ThreadSafeStore实现的indexer，有名称可知具有线程安全的功能\ngo 1 2 3 4 type cache struct { cacheStorage ThreadSafeStore keyFunc KeyFunc } 既然index继承了Store那么，也就是 ThreadSafeStore 必然实现了Store，这是一个基础保证\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type ThreadSafeStore interface { Add(key string, obj interface{}) Update(key string, obj interface{}) Delete(key string) Get(key string) (item interface{}, exists bool) List() []interface{} ListKeys() []string Replace(map[string]interface{}, string) Index(indexName string, obj interface{}) ([]interface{}, error) IndexKeys(indexName, indexKey string) ([]string, error) ListIndexFuncValues(name string) []string ByIndex(indexName, indexKey string) ([]interface{}, error) GetIndexers() Indexers AddIndexers(newIndexers Indexers) error Resync() error // Resync is a no-op and is deprecated } // KeyFunc是一个生成key的函数，给一个对象，返回一个key值 type KeyFunc func(obj interface{}) (string, error) 那么这个indexer structure可以通过图来很直观的看出来\ncache的结构 cache中会出现三种数据结构，也可以成为三种名词，为 Index , Indexers , Indices\ngo 1 2 3 type Index map[string]sets.String type Indexers map[string]IndexFunc type Indices map[string]Index 可以看出：\nIndex 映射到对象，sets.String 也是在API中定义的数据类型 [string]Struct{}， Indexers 是这个 Index 的 IndexFunc , 是一个如何计算Index的keyname的函数 Indices 通过Index 名词拿到对应的对象 这个名词的概念如下，通过图来了解会更加清晰\n从创建开始 创建一个cache有两种方式，一种是指定indexer，一种是默认indexer\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // NewStore returns a Store implemented simply with a map and a lock. func NewStore(keyFunc KeyFunc) Store { return \u0026amp;cache{ cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}), keyFunc: keyFunc, } } // NewIndexer returns an Indexer implemented simply with a map and a lock. func NewIndexer(keyFunc KeyFunc, indexers Indexers) Indexer { return \u0026amp;cache{ cacheStorage: NewThreadSafeStore(indexers, Indices{}), keyFunc: keyFunc, } } 更新操作 在indexer中的更新操作（诸如 add , update ），实际上操作的是 updateIndices， 通过在代码可以看出\ntools/cache/thread_safe_store.go 的 77行起，那么就来看下 updateIndices() 具体做了什么\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func (c *threadSafeMap) updateIndices(oldObj interface{}, newObj interface{}, key string) { // 在操作时，如果有旧对象，需要先删除 if oldObj != nil { c.deleteFromIndices(oldObj, key) } // 先对整个indexer遍历，拿到index name与 index function for name, indexFunc := range c.indexers { // 通过index function，计算出对象的indexed name indexValues, err := indexFunc(newObj) if err != nil { panic(fmt.Errorf(\u0026#34;unable to calculate an index entry for key %q on index %q: %v\u0026#34;, key, name, err)) } // 接下来通过遍历的index name 拿到这个index的对象 index := c.indices[name] if index == nil { // 确认这个index是否存在， index = Index{} // 如果不存在将一个Index{}初始化 c.indices[name] = index } // 通过计算出的indexed name来拿到对应的 set of object for _, indexValue := range indexValues { set := index[indexValue] if set == nil { // 如果这个set不存在，则初始化这个set set = sets.String{} index[indexValue] = set } set.Insert(key) // 然后将key插入set中 } } } 那么通过上面可以了解到了 updateIndices 的逻辑，那么通过对更新函数分析来看看他具体做了什么？这里是add函数，通过一段代码模拟操作来熟悉结构\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 testIndexer := \u0026#34;testIndexer\u0026#34; testIndex := \u0026#34;testIndex\u0026#34; indexers := cache.Indexers{ testIndexer: func(obj interface{}) (strings []string, e error) { indexes := []string{testIndex} // index的名词 return indexes, nil }, } indices := cache.Indices{} store := cache.NewThreadSafeStore(indexers, indices) fmt.Printf(\u0026#34;%#v\\n\u0026#34;, store.GetIndexers()) store.Add(\u0026#34;retain\u0026#34;, \u0026#34;pod--1\u0026#34;) store.Add(\u0026#34;delete\u0026#34;, \u0026#34;pod--2\u0026#34;) store.Update(\u0026#34;retain\u0026#34;, \u0026#34;pod-3\u0026#34;) //lists := store.Update(\u0026#34;retain\u0026#34;, \u0026#34;pod-3\u0026#34;) lists := store.List() for _, item := range lists { fmt.Println(item) } 这里是对add操作以及对updateIndices() 进行操作\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 // threadSafe.go func (c *threadSafeMap) Add(key string, obj interface{}) { c.lock.Lock() defer c.lock.Unlock() oldObject := c.items[key] // 这个item就是存储object的地方, 为空 c.items[key] = obj // 这里已经添加了新的值 c.updateIndices(oldObject, obj, key) // 转至updateIndices } // updateIndices func (c *threadSafeMap) updateIndices(oldObj interface{}, newObj interface{}, key string) { // 就当是新创建的，这里是空的忽略 if oldObj != nil { c.deleteFromIndices(oldObj, key) } // 这个时候拿到的就是 name=testKey function=testIndexer for name, indexFunc := range c.indexers { // 通过testIndexer对testKey计算出的结果是 []string{testIndexer} indexValues, err := indexFunc(newObj) if err != nil { panic(fmt.Errorf(\u0026#34;unable to calculate an index entry for key %q on index %q: %v\u0026#34;, key, name, err)) } index := c.indices[name] if index == nil { index = Index{} // 因为假设为空了，故到这里c.indices[testIndexer]= Index{} c.indices[name] = index } for _, indexValue := range indexValues { // indexValue=testIndexer // set := c.index[name] = c.indices[testIndexer]Index{} set := index[indexValue] if set == nil { set = sets.String{} index[indexValue] = set } set.Insert(key) // 到这里就为set=indices[testIndexer]Index{} } } } 总结一下，到这里，可以很明显的看出来，indexer中的三个概念是什么了，前面如果没有看明白话\nIndex：通过indexer计算出key的名称，值为对应obj的一个集合，可以理解为索引的数据结构 比如说 Pod:{\u0026quot;nginx-pod1\u0026quot;: v1.Pod{Name:Nginx}} Indexers ：这个很简单，就是，对于Index中如何计算每个key的名称；可以理解为分词器，索引的过程 Indices 通过Index 名词拿到对应的对象，是Index的集合；是将原始数据Item做了一个索引，可以理解为做索引的具体字段 比如说 Indices[\u0026quot;Pod\u0026quot;]{\u0026quot;nginx-pod1\u0026quot;: v1.Pod{Name:Nginx}, \u0026quot;nginx-pod2\u0026quot;: v1.Pod{Name:Nginx}} Items：实际上存储的在Indices中的set.String{key:value} ，中的 key=value 例如：Item:{\u0026quot;nginx-pod1\u0026quot;: v1.Pod{Name:Nginx}, \u0026quot;coredns-depoyment\u0026quot;: App.Deployment{Name:coredns}} 删除操作 对于删除操作，在最新版本中是使用了 updateIndices 就是 add update delete全都是相同的方法操作，对于旧版包含1.19- 是单独的一个操作\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // v1.2+ func (c *threadSafeMap) Delete(key string) { c.lock.Lock() defer c.lock.Unlock() if obj, exists := c.items[key]; exists { c.updateIndices(obj, nil, key) delete(c.items, key) } } // v1.19- func (c *threadSafeMap) Delete(key string) { c.lock.Lock() defer c.lock.Unlock() if obj, exists := c.items[key]; exists { c.deleteFromIndices(obj, key) delete(c.items, key) } } indexer使用 上面了解了indexer概念，可以通过写代码来尝试使用一些indexer\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package main import ( \u0026#34;fmt\u0026#34; appsV1 \u0026#34;k8s.io/api/apps/v1\u0026#34; metav1 \u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34; \u0026#34;k8s.io/client-go/tools/cache\u0026#34; ) func main() { indexers := cache.Indexers{ \u0026#34;getDeplyment\u0026#34;: func(obj interface{}) (strings []string, e error) { d, ok := obj.(*appsV1.Deployment) if !ok { return []string{}, nil } return []string{d.Name}, nil }, \u0026#34;getDaemonset\u0026#34;: func(obj interface{}) (strings []string, e error) { d, ok := obj.(*appsV1.DaemonSet) if !ok { return []string{}, nil } return []string{d.Name}, nil }, } // 第一个参数是计算set内的key的名称 就是map[string]sets.String的这个strings的名称/namespace/resorcename // 第二个参数是计算index即外部的key的名称 indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, indexers) deployment := \u0026amp;appsV1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: \u0026#34;nginx-deplyment\u0026#34;, Namespace: \u0026#34;test\u0026#34;, }, } daemonset := \u0026amp;appsV1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ Name: \u0026#34;firewall-daemonset\u0026#34;, Namespace: \u0026#34;test\u0026#34;, }, } daemonset2 := \u0026amp;appsV1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ Name: \u0026#34;etcd-daemonset\u0026#34;, Namespace: \u0026#34;default\u0026#34;, }, } indexer.Add(deployment) indexer.Add(daemonset) indexer.Add(daemonset2) // 第一个参数是索引器 // 第二个参数是所引起做索引的字段 lists, _ := indexer.ByIndex(\u0026#34;getDaemonset\u0026#34;, \u0026#34;etcd-daemonset\u0026#34;) for _, item := range lists { switch item.(type) { case *appsV1.Deployment: fmt.Println(item.(*appsV1.Deployment).Name) case *appsV1.DaemonSet: fmt.Println(item.(*appsV1.DaemonSet).Name) } } } Reference go types Kubernetes API Reference Docs ","permalink":"https://www.161616.top/ch06-client-go/","summary":"Prepare Introduction 从2016年8月起，Kubernetes官方提取了与Kubernetes相关的核心源代码，形成了一个独立的项目，即client-go，作为官方提供的go客户端。Kubernetes的部分代码也是基于这个项目的。\nclient-go 是kubernetes中广义的客户端基础库，在Kubernetes各个组件中或多或少都有使用其功能。。也就是说，client-go可以在kubernetes集群中添加、删除和查询资源对象（包括deployment、service、pod、ns等）。\n在了解client-go前，还需要掌握一些概念\n在客户端验证 API 使用证书和使用令牌，来验证客户端 kubernetes集群的访问模式 使用证书和令牌来验证客户端 在访问apiserver时，会对访问者进行鉴权，因为是https请求，在请求时是需要ca的，也可以使用 -k 使用insecure模式\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 $ curl --cacert /etc/kubernetes/pki/ca.crt https://10.0.0.4:6443/version \\{ \u0026#34;major\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;minor\u0026#34;: \u0026#34;18+\u0026#34;, \u0026#34;gitVersion\u0026#34;: \u0026#34;v1.18.20-dirty\u0026#34;, \u0026#34;gitCommit\u0026#34;: \u0026#34;1f3e19b7beb1cc0110255668c4238ed63dadb7ad\u0026#34;, \u0026#34;gitTreeState\u0026#34;: \u0026#34;dirty\u0026#34;, \u0026#34;buildDate\u0026#34;: \u0026#34;2022-05-17T12:45:14Z\u0026#34;, \u0026#34;goVersion\u0026#34;: \u0026#34;go1.16.15\u0026#34;, \u0026#34;compiler\u0026#34;: \u0026#34;gc\u0026#34;, \u0026#34;platform\u0026#34;: \u0026#34;linux/amd64\u0026#34; } $ curl -k https://10.","title":"Kubernetes组件核心 - client-go"},{"content":"背景 由于Windows10 开启WSL2后无法和 eNSP 做到兼容，但是 H3C HCL 在版本 HCL_v2.1.2.1 提供了 VirtualBox 6.0.14 作为虚拟化后端，理论上来说可以做到 WSL2 与 HCL 共存。\n并且开启了WSL2后并于其他虚拟化平台（VirtualBox, Vmvare）做到兼容的情况下，这种情况大部分禁止套娃（虚拟化下在虚拟化），通过安装虚拟机的方式再安装 eNSP 发现启动不报错，但是很长时间起不来。\nNotes [1]：HCL官方给的建议是，对于windows7装的版本为HCL_v2.1.1；对于Windows10 并且开启了 Hype-v 或者 Dokcer-Desktop，推荐使用 HCL_v3.0.1.1\n下载地址：HCL Download\n安装过程 下载好安装时，直接下一步直至完成即可，VirtualBox已被内嵌至安装包内了。\n图：HCL安装界面 Notes：如果需要抓包，自行安装Wireshark，安装好后，在HCL设置中配置 wireshark.exe 的路径即可\nVirtualBox启用hyper-v支持 [2] 进入VirtualBox安装目录, 确定当前目录下存在VBoxManage.exe文件, 在当前目录打开powershell. 或者你将VBoxManage.exe所在目录加入环境变量, 任意路径下打开powershell.\npowershell 1 2 # 或指定vbox所有虚拟系统开启 ./VBoxManage.exe setextradata global \u0026#34;VBoxInternal/NEM/UseRing0Runloop\u0026#34; 0 开启后，HCL所有的设备就工作正常了，这种情况下也不用牺牲WSL2或者Dokcer-Desktop。因为eNSP官方没有再更新，导致hype-v与VirtualBox无法兼容，暂时无解。\nReference ​[1] H3C Cloud Lab\n​[2] Windows 10 (2004) 启用wsl2, 并与VirtualBox 6.0+共存\n","permalink":"https://www.161616.top/h3c-hcl-preparation/","summary":"背景 由于Windows10 开启WSL2后无法和 eNSP 做到兼容，但是 H3C HCL 在版本 HCL_v2.1.2.1 提供了 VirtualBox 6.0.14 作为虚拟化后端，理论上来说可以做到 WSL2 与 HCL 共存。\n并且开启了WSL2后并于其他虚拟化平台（VirtualBox, Vmvare）做到兼容的情况下，这种情况大部分禁止套娃（虚拟化下在虚拟化），通过安装虚拟机的方式再安装 eNSP 发现启动不报错，但是很长时间起不来。\nNotes [1]：HCL官方给的建议是，对于windows7装的版本为HCL_v2.1.1；对于Windows10 并且开启了 Hype-v 或者 Dokcer-Desktop，推荐使用 HCL_v3.0.1.1\n下载地址：HCL Download\n安装过程 下载好安装时，直接下一步直至完成即可，VirtualBox已被内嵌至安装包内了。\n图：HCL安装界面 Notes：如果需要抓包，自行安装Wireshark，安装好后，在HCL设置中配置 wireshark.exe 的路径即可\nVirtualBox启用hyper-v支持 [2] 进入VirtualBox安装目录, 确定当前目录下存在VBoxManage.exe文件, 在当前目录打开powershell. 或者你将VBoxManage.exe所在目录加入环境变量, 任意路径下打开powershell.\npowershell 1 2 # 或指定vbox所有虚拟系统开启 ./VBoxManage.exe setextradata global \u0026#34;VBoxInternal/NEM/UseRing0Runloop\u0026#34; 0 开启后，HCL所有的设备就工作正常了，这种情况下也不用牺牲WSL2或者Dokcer-Desktop。因为eNSP官方没有再更新，导致hype-v与VirtualBox无法兼容，暂时无解。\nReference ​[1] H3C Cloud Lab\n​[2] Windows 10 (2004) 启用wsl2, 并与VirtualBox 6.0+共存","title":"H3C Cloud与WSL2共存"},{"content":"疫情期间上网课，对于英语听力较差或者需要观看英文视频，但实际上并没有双语字幕的这种情况下需要找一个实时的翻译工具。虽然说手机上此类软件比较多，但电脑上没有特别合适的应用可以做为一个免费实时翻译。哪怕是收费翻译工具实际上翻译效果也是很差，并且语种比较单一。\n电脑端有一个 speechlogger，可以做到实时翻译，但实际上也是使用的Google翻译，那么实际上我们就可以直接使用Google翻译作为一个同声翻译电脑的声音。\n此时就遇到一个问题，就是Google翻译无法识别到电脑的声音，只能识别到麦克风的声音。这里就需要将电脑输出的作为麦克风的输出。使用Windows电脑的可以尝试以下操作。\n以windows电脑为例：\nStep 1：电脑右下角调整音量图标，右键选择声音\nStep2：选择录音设备，立体声混音，将其启动并设置为默认设备（可选）\nStep3：右键属性，选择侦听，通过此设备播放选择扬声器对应的设备。\nTips：注意，无用选择侦听此设备，这个选项勾选后的意思是，你可以通过扬声器听到扬声器声音，此时会发生混音。影响我们听到的效果。\nStep4：可选步骤，如果Step2没有设置为默认设备，可以在浏览器选择对应的设备作为麦克风，如使用了立体声混音作为电脑的输入设备，那么不是默认设备情况下在浏览器选择该设备作为输入设备即可。\nTips：Google翻译的语音翻译功能貌似只能在Chrome里使用，其他有可能会出现无法使用语音设备的功能\n这样就做到了一个免费，无杂音，多语种的同声翻译，不管是上网课学习，还是说做笔记（同语言下还可以作为声音转换为文字）都是一个很不错的选择。\n后面再说下，科大讯飞的录音设备实际上翻译功能很差，语种也少，并且专业术语翻译的很烂，最便宜的小2000块钱，大量依赖云服务，实际上可以不用买这种产品，我是已经买过体验的，翻译结果实际上比Google翻译要差。如果不是在现场，没有网络或者声音很嘈杂（他的十几个麦克风其实效果也不咋地）的情况下可以选择其他方案，例如Google的同声翻译。\n如果你需要对翻译的结果划分角色的话，还是可以使用 speechlogger，这个也是使用的Google翻译作为翻译引擎的\nReference 如何将电脑的输出作为电脑麦克风的输入\n免费的实时翻译工具\n","permalink":"https://www.161616.top/google-interpretation/","summary":"疫情期间上网课，对于英语听力较差或者需要观看英文视频，但实际上并没有双语字幕的这种情况下需要找一个实时的翻译工具。虽然说手机上此类软件比较多，但电脑上没有特别合适的应用可以做为一个免费实时翻译。哪怕是收费翻译工具实际上翻译效果也是很差，并且语种比较单一。\n电脑端有一个 speechlogger，可以做到实时翻译，但实际上也是使用的Google翻译，那么实际上我们就可以直接使用Google翻译作为一个同声翻译电脑的声音。\n此时就遇到一个问题，就是Google翻译无法识别到电脑的声音，只能识别到麦克风的声音。这里就需要将电脑输出的作为麦克风的输出。使用Windows电脑的可以尝试以下操作。\n以windows电脑为例：\nStep 1：电脑右下角调整音量图标，右键选择声音\nStep2：选择录音设备，立体声混音，将其启动并设置为默认设备（可选）\nStep3：右键属性，选择侦听，通过此设备播放选择扬声器对应的设备。\nTips：注意，无用选择侦听此设备，这个选项勾选后的意思是，你可以通过扬声器听到扬声器声音，此时会发生混音。影响我们听到的效果。\nStep4：可选步骤，如果Step2没有设置为默认设备，可以在浏览器选择对应的设备作为麦克风，如使用了立体声混音作为电脑的输入设备，那么不是默认设备情况下在浏览器选择该设备作为输入设备即可。\nTips：Google翻译的语音翻译功能貌似只能在Chrome里使用，其他有可能会出现无法使用语音设备的功能\n这样就做到了一个免费，无杂音，多语种的同声翻译，不管是上网课学习，还是说做笔记（同语言下还可以作为声音转换为文字）都是一个很不错的选择。\n后面再说下，科大讯飞的录音设备实际上翻译功能很差，语种也少，并且专业术语翻译的很烂，最便宜的小2000块钱，大量依赖云服务，实际上可以不用买这种产品，我是已经买过体验的，翻译结果实际上比Google翻译要差。如果不是在现场，没有网络或者声音很嘈杂（他的十几个麦克风其实效果也不咋地）的情况下可以选择其他方案，例如Google的同声翻译。\n如果你需要对翻译的结果划分角色的话，还是可以使用 speechlogger，这个也是使用的Google翻译作为翻译引擎的\nReference 如何将电脑的输出作为电脑麦克风的输入\n免费的实时翻译工具","title":"PC端利用google翻译实现同声翻译"},{"content":"本地构建 选择要构建的版本 text 1 git checkout tags/v1.19.5 将依赖包复制到对应路径下 text 1 cp staging/src/k8s.io vendor/ 调整makefile 在windows上编译的克隆下可能文件编码变了，需要手动修改下文件编码。比如说出现 \\r not found 类似关键词时\n这里转换编码使用了 dos2unix，需要提前安装下\ntext 1 apt install dos2unix 转换原因是因为对于bash 脚本执行识别不了windows的换行\ntext 1 find . -name \u0026#39;*.sh\u0026#39; -exec dos2unix {} \\; 然后将 build/root/ 的文件复制到项目根目录\ntext 1 cp build/root/Makefile* ./ 编译 查看帮助 make help\n编译 make all WHAT=cmd/kube-apiserver GOFLAGS=-v\nWHAT=cmd/kube-apiserver 为仅编译单一组件，all 为所有的组件\n还可以增加其他的一些环境变量 KUBE_BUILD_PLATFORMS= 如编译的平台\n更多的可以 make help 查看帮助\n编译中问题 Makefile:93: recipe for target \u0026lsquo;all\u0026rsquo; failed\ntext 1 2 3 4 5 6 7 8 9 !!! [0515 21:32:52] Call tree: !!! [0515 21:32:52] 1: /mnt/d/src/go_work/src/kubernetes/hack/lib/golang.sh:717 kube::golang::build_some_binaries(...) !!! [0515 21:32:52] 2: /mnt/d/src/go_work/src/kubernetes/hack/lib/golang.sh:861 kube::golang::build_binaries_for_platform(...) !!! [0515 21:32:52] 3: hack/make-rules/build.sh:27 kube::golang::build_binaries(...) !!! [0515 21:32:52] Call tree: !!! [0515 21:32:52] 1: hack/make-rules/build.sh:27 kube::golang::build_binaries(...) !!! [0515 21:32:52] Call tree: !!! [0515 21:32:52] 1: hack/make-rules/build.sh:27 kube::golang::build_binaries(...) Makefile:93: recipe for target \u0026#39;all\u0026#39; failed 这里看报错根本不知道发生什么问题，使用 strace 追送了下，很明显看到是没有gcc\ncgo: exec gcc: exec: \u0026ldquo;gcc\u0026rdquo;: executable file not found in $PATH\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 rt_sigprocmask(SIG_BLOCK, [HUP INT QUIT TERM XCPU XFSZ], NULL, 8) = 0 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fbf45410a10) = 17890 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 wait4(-1, +++ [0515 21:34:40] Building go targets for linux/amd64: cmd/kubelet k8s.io/kubernetes/vendor/github.com/opencontainers/runc/libcontainer/system k8s.io/kubernetes/vendor/github.com/mindprince/gonvml # k8s.io/kubernetes/vendor/github.com/opencontainers/runc/libcontainer/system cgo: exec gcc: exec: \u0026#34;gcc\u0026#34;: executable file not found in $PATH # k8s.io/kubernetes/vendor/github.com/mindprince/gonvml cgo: exec gcc: exec: \u0026#34;gcc\u0026#34;: executable file not found in $PATH !!! [0515 21:34:42] Call tree: !!! [0515 21:34:42] 1: /mnt/d/src/go_work/src/kubernetes/hack/lib/golang.sh:717 kube::golang::build_some_binaries(...) !!! [0515 21:34:42] 2: /mnt/d/src/go_work/src/kubernetes/hack/lib/golang.sh:861 kube::golang::build_binaries_for_platform(...) !!! [0515 21:34:42] 3: hack/make-rules/build.sh:27 kube::golang::build_binaries(...) !!! [0515 21:34:42] Call tree: !!! [0515 21:34:42] 1: hack/make-rules/build.sh:27 kube::golang::build_binaries(...) !!! [0515 21:34:42] Call tree: !!! [0515 21:34:42] 1: hack/make-rules/build.sh:27 kube::golang::build_binaries(...) [{WIFEXITED(s) \u0026amp;\u0026amp; WEXITSTATUS(s) == 1}], 0, NULL) = 17890 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=17890, si_uid=0, si_status=1, si_utime=0, si_stime=0} --- rt_sigreturn({mask=[]}) = 17890 openat(AT_FDCWD, \u0026#34;/usr/share/locale/C.UTF-8/LC_MESSAGES/make.mo\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, \u0026#34;/usr/share/locale/C.utf8/LC_MESSAGES/make.mo\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, \u0026#34;/usr/share/locale/C/LC_MESSAGES/make.mo\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, \u0026#34;/usr/share/locale-langpack/C.UTF-8/LC_MESSAGES/make.mo\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, \u0026#34;/usr/share/locale-langpack/C.utf8/LC_MESSAGES/make.mo\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, \u0026#34;/usr/share/locale-langpack/C/LC_MESSAGES/make.mo\u0026#34;, O_RDONLY) = -1 ENOENT (No such file or directory) fstat(1, {st_mode=S_IFCHR|0640, st_rdev=makedev(4, 1), ...}) = 0 ioctl(1, TCGETS, {B38400 opost isig icanon echo ...}) = 0 write(1, \u0026#34;Makefile:93: recipe for target \u0026#39;\u0026#34;..., 44Makefile:93: recipe for target \u0026#39;all\u0026#39; failed ) = 44 write(2, \u0026#34;make: *** [all] Error 1\\n\u0026#34;, 24make: *** [all] Error 1 ) = 24 rt_sigprocmask(SIG_BLOCK, [HUP INT QUIT TERM XCPU XFSZ], NULL, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 chdir(\u0026#34;/mnt/d/src/go_work/src/kubernetes\u0026#34;) = 0 close(1) = 0 exit_group(2) = ? +++ exited with 2 +++ 修改后编译问题可以明显看出是哪里\n如尝试增加一种资源类型后编译，这种类型的错误可以根据报错提示进行修改\ntext 1 2 3 4 5 6 7 8 9 +++ [0515 21:47:59] Building go targets for linux/amd64: cmd/kube-apiserver k8s.io/kubernetes/vendor/k8s.io/api/apps/v1 # k8s.io/kubernetes/vendor/k8s.io/api/apps/v1 vendor/k8s.io/api/apps/v1/register.go:48:3: cannot use \u0026amp;StateDeploy{} (type *StateDeploy) as type runtime.Object in argument to scheme.Ad dKnownTypes: *StateDeploy does not implement runtime.Object (missing DeepCopyObject method) !!! [0515 21:48:01] Call tree: !!! [0515 21:48:01] 1: /mnt/d/src/go_work/src/kubernetes/hack/lib/golang.sh:706 k ","permalink":"https://www.161616.top/ch11-code-compile/","summary":"本地构建 选择要构建的版本 text 1 git checkout tags/v1.19.5 将依赖包复制到对应路径下 text 1 cp staging/src/k8s.io vendor/ 调整makefile 在windows上编译的克隆下可能文件编码变了，需要手动修改下文件编码。比如说出现 \\r not found 类似关键词时\n这里转换编码使用了 dos2unix，需要提前安装下\ntext 1 apt install dos2unix 转换原因是因为对于bash 脚本执行识别不了windows的换行\ntext 1 find . -name \u0026#39;*.sh\u0026#39; -exec dos2unix {} \\; 然后将 build/root/ 的文件复制到项目根目录\ntext 1 cp build/root/Makefile* ./ 编译 查看帮助 make help\n编译 make all WHAT=cmd/kube-apiserver GOFLAGS=-v\nWHAT=cmd/kube-apiserver 为仅编译单一组件，all 为所有的组件\n还可以增加其他的一些环境变量 KUBE_BUILD_PLATFORMS= 如编译的平台\n更多的可以 make help 查看帮助\n编译中问题 Makefile:93: recipe for target \u0026lsquo;all\u0026rsquo; failed","title":"如何通过源码编译Kubernetes"},{"content":"APIServer 在kubernetes架构概念层面上，Kubernetes由一些具有不同角色的服务节点组成。而master的控制平面由 Apiserver Controller-manager 和 Scheduler 组成。\nApiserver 从概念上理解可以分为 api 和 object 的集合，api 可以理解为，处理读写请求来修改相应 object 的组件；而 object 可以表示为 kubernetes 对象，如 Pod， Deployment 等 。\n基于声明式的API 在命令式 API 中，会直接发送要执行的命令，例如：运行、停止 等命令。在声明式API 中，将声明希望系统执行的操作，系统将不断将自身状态朝希望状态改变。\n为什么使用声明式 在分布式系统中，任何组件随时都可能发生故障，当组件故障恢复时，需要明白自己需要做什么。在使用命令式时，出现故障的组件可能在异常时错过调用，并且在恢复时需要其他外部组件进行干预。而声明式仅需要在恢复时确定当前状态以确定他需要做什么。\nExternal APIs 在kubernetes中，控制平面是透明的，及没有internal APIs。这就意味着Kubernetes组件间使用相同的API交互。这里通过一个例子来说明外部APIs与声明式的关系。\n例如，创建一个Pod对象，Scheduler 会监听 API来完成创建，创建完成后，调度程序不会命令被分配节点启动Pod。而在kubelet端，发现pod具有与自己相同的一些信息时，会监听pod状态。如改变kubelet则修改状态，如果删除掉Pod（对象资源不存在与API中），那么kubelet则将终止他。\n为什么不使用Internal API 使用External API可以使kubernetes组件都使用相同的API，使得kubernetes具有可扩展性和可组合性。对于kubernetes中任何默认组件，如不足满足需求时，都可以更换为使用相同API的组件。\n另外，外部API还可轻松的使用公共API来扩展kubernetes的功能\nAPI资源 从广义上讲，kubernetes对象可以用任何数据结构来表示，如：资源实例、配置（审计策略）或持久化实体（Pod）；在使用中，常见到的就是对应YAML的资源清单。转换出来就是RESTful地址，那么应该怎么理解这个呢？即，对资源的动作（操作）如图所示。但如果需要了解Kubernetes API需要掌握一些概念才可继续。\nGroup 出于对kubernetes扩展性的原因，将资源类型分为了API组进行独立管理，可以通过 kubectl api-resources查看。在代码部分为 vendor/k8s.io/api\n也可以通过 kubectl xxx -v 6 来查看 kubectl 命令进行了那些API调用\ntext 1 2 3 4 5 6 7 8 9 10 11 $ kubectl get pods -v 6 I0513 21:54:33.250752 38661 round_trippers.go:444] GET http://localhost:8080/api?timeout=32s 200 OK in 1 milliseconds I0513 21:54:33.293831 38661 round_trippers.go:444] GET http://localhost:8080/apis?timeout=32s 200 OK in 0 milliseconds I0513 21:54:33.299741 38661 round_trippers.go:444] GET http://localhost:8080/apis/discovery.k8s.io/v1beta1?timeout=32s 200 OK in 3 milliseconds I0513 21:54:33.301097 38661 round_trippers.go:444] GET http://localhost:8080/apis/autoscaling/v2beta1?timeout=32s 200 OK in 4 milliseconds I0513 21:54:33.301128 38661 round_trippers.go:444] GET http://localhost:8080/apis/authorization.k8s.io/v1beta1?timeout=32s 200 OK in 3 milliseconds I0513 21:54:33.301222 38661 round_trippers.go:444] GET http://localhost:8080/apis/rbac.authorization.k8s.io/v1beta1?timeout=32s 200 OK in 1 milliseconds I0513 21:54:33.301238 38661 round_trippers.go:444] GET http://localhost:8080/apis/authentication.k8s.io/v1beta1?timeout=32s 200 OK in 4 milliseconds I0513 21:54:33.301280 38661 round_trippers.go:444] GET http://localhost:8080/apis/certificates.k8s.io/v1beta1?timeout=32s 200 OK in 4 milliseconds .... No resources found in default namespace. Kind 在kubectl api-resources 中可以看到，有Kind字段，大部分人通常会盲目的 kubectl apply ,这导致了，很多人以为 kind 实际上为资源名称，Pod ，Deployment 等。\n根据 api-conventions.md 的说明，Kind 是对象模式，包含三种类型：\nObject，代表系统中持久化数据资源，如，Service, Namespace, Pod等 List，是一个或多个资源的集合，通常以List结尾，如 DeploymentList 对Object的操作和和非持久化实体，如，当发生错误时会返回“status”类型，并不会持久化该数据。 Object 对象是Kubernetes中持久化的实体，也就是保存在etcd中的数据；如：Replicaset , Configmap 等。这个对象代表的了集群期望状态和实际状态。\n例如：创建了Pod，kubernetes集群会调整状态，直到相应的容器在运行\nKubernetes资源又代表了对象，对象必须定义一些字段：\n所有对象必须具有以下字段： Kind apiVersion metadata spec：期望的状态 status：实际的状态 API Link 前面讲到的 kubectl api-resources 展示的列表不能完整称为API资源，而是已知类型的kubernetes对象，要对展示这个API对象，需要了解其完整的周期。以 kubectl get --raw / 可以递归查询每个路径。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 kubectl get --raw / { \u0026#34;paths\u0026#34;: [ \u0026#34;/api\u0026#34;, \u0026#34;/api/v1\u0026#34;, \u0026#34;/apis\u0026#34;, \u0026#34;/apis/\u0026#34;, \u0026#34;/apis/admissionregistration.k8s.io\u0026#34;, \u0026#34;/apis/admissionregistration.k8s.io/v1\u0026#34;, \u0026#34;/apis/admissionregistration.k8s.io/v1beta1\u0026#34;, \u0026#34;/apis/apiextensions.k8s.io\u0026#34;, \u0026#34;/apis/apiextensions.k8s.io/v1\u0026#34;, ... 对于一个Pod来说，其查询路径就为 /api/v1/namespaces/kube-system/pods/coredns-f9bbb4898-7zkbf\ntext 1 2 3 4 5 6 7 kubectl get --raw /api/v1/namespaces/kube-system/pods/coredns-f9bbb4898-7zkbf|jq kind: Pod apiVersion: v1 metadata: {} spec:{} status: {} 但有一些资源对象也并非这种结构，如 configMap ，因其只是存储的数据，所以没有 spec 和 status\ntext 1 2 3 4 5 6 kubectl get --raw /api/v1/namespaces/kube-system/configmaps/coredns|jq kind apiVersion metadata data API组成 一个API的组成为 一个 API 组Group , 一个版本 Version , 和一个资源 Resource ; 简称为 GVR\n转换为实际的http路径为：\n/api/{version}/namespaces/{namespace_name}/resourcesPlural/{actual_resources_name} /api/v1/namespaces/default/pods/pods123 而GVR中的R代表的是RESTful中的资源，转换为Kubernetes中资源应为 Kind，简称为 GVK，K在URI中表示在：\n/apis/{GROUP}/{VERSION}/namespaces/{namespace}/{KIND} 请求和处理 这里讨论API请求和处理，API的一些数据结构位于 k8s.io/api ，并处理集群内部与外部的请求，而Apiserver 位于 k8s.io/apiserver/pkg/server 提供了http服务。\n那么，当 HTTP 请求到达 Kubernetes API 时，实际上会发生什么？\n首先HTTP请求在 DefaultBuildHandlerChain （可以参考k8s.io/apiserver/pkg/server/config.go）中注册filter chain，过滤器允许并将相应的信息附加至 ctx.RequestInfo; 如身份验证的相应 k8s.io/apiserver/pkg/server/mux 将其分配到对应的应用 k8s.io/apiserver/pkg/server/routes 定义了REST与对应应用相关联 k8s.io/apiserver/pkg/endpoints/groupversion.go.InstallREST() 接收上下文，从存储中传递请求的对象。 Reference Kubernetes API 基础——资源、种类和对象\nKubernetes - 一个 API 统治\nDesign and implementation\nExtending-the-Kubernetes-API\nKubernetes 深入探讨\n","permalink":"https://www.161616.top/ch02-kubernetes-api/","summary":"APIServer 在kubernetes架构概念层面上，Kubernetes由一些具有不同角色的服务节点组成。而master的控制平面由 Apiserver Controller-manager 和 Scheduler 组成。\nApiserver 从概念上理解可以分为 api 和 object 的集合，api 可以理解为，处理读写请求来修改相应 object 的组件；而 object 可以表示为 kubernetes 对象，如 Pod， Deployment 等 。\n基于声明式的API 在命令式 API 中，会直接发送要执行的命令，例如：运行、停止 等命令。在声明式API 中，将声明希望系统执行的操作，系统将不断将自身状态朝希望状态改变。\n为什么使用声明式 在分布式系统中，任何组件随时都可能发生故障，当组件故障恢复时，需要明白自己需要做什么。在使用命令式时，出现故障的组件可能在异常时错过调用，并且在恢复时需要其他外部组件进行干预。而声明式仅需要在恢复时确定当前状态以确定他需要做什么。\nExternal APIs 在kubernetes中，控制平面是透明的，及没有internal APIs。这就意味着Kubernetes组件间使用相同的API交互。这里通过一个例子来说明外部APIs与声明式的关系。\n例如，创建一个Pod对象，Scheduler 会监听 API来完成创建，创建完成后，调度程序不会命令被分配节点启动Pod。而在kubelet端，发现pod具有与自己相同的一些信息时，会监听pod状态。如改变kubelet则修改状态，如果删除掉Pod（对象资源不存在与API中），那么kubelet则将终止他。\n为什么不使用Internal API 使用External API可以使kubernetes组件都使用相同的API，使得kubernetes具有可扩展性和可组合性。对于kubernetes中任何默认组件，如不足满足需求时，都可以更换为使用相同API的组件。\n另外，外部API还可轻松的使用公共API来扩展kubernetes的功能\nAPI资源 从广义上讲，kubernetes对象可以用任何数据结构来表示，如：资源实例、配置（审计策略）或持久化实体（Pod）；在使用中，常见到的就是对应YAML的资源清单。转换出来就是RESTful地址，那么应该怎么理解这个呢？即，对资源的动作（操作）如图所示。但如果需要了解Kubernetes API需要掌握一些概念才可继续。\nGroup 出于对kubernetes扩展性的原因，将资源类型分为了API组进行独立管理，可以通过 kubectl api-resources查看。在代码部分为 vendor/k8s.io/api\n也可以通过 kubectl xxx -v 6 来查看 kubectl 命令进行了那些API调用\ntext 1 2 3 4 5 6 7 8 9 10 11 $ kubectl get pods -v 6 I0513 21:54:33.","title":"深入理解kubernetes API"},{"content":"Overview 进程间是相互保持独立的，内存管理中，就是保护进程的地址空间不被其他进程访问。而进程间通信 ( Inter-process Communication IPC) 用于在一个或多个进程间交换数据\n进程间合作是那些可以影响或受其他过程影响的过程。例如网站包含 JS、H5、Flash，当有一个相应缓慢时，会发生整个网站的布局或其他功能的展示。\n通常情况下进程间合作被允许的原因有：\n信息共享：多个进程需要访问同一个文件。（如管道）\n计算加速：将复杂功能拆分为多个子任务（多处理器时效果更佳），可以更快地解决问题\n模块化：将整体系统架构分为不同功能模块，模块间相互协作\n便利：单用户可以同时多任务处理，如 编辑、编译、打印等\nCommunications model (a) 消息队列（间接通信） \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;(b) 共享内存（直接通信） Message Passing IPC背后关键的一点是消息的传递，即一个进程发消息，一个进程接收消息\n而为了使进程间通信，就必须在进程间建立连接，连接可以是单/双向。连接可以使用直接通信和间接通信来实现\nDirect Communication 直接通信，必须明确声明发送者或接收者的名称，通常定义为：\nSend(P, message)：发送信息到进程 P Receive(Q, message)：接收来自进程 Q 的信息 在直接通信中，一般连接的属性有以下特征：\n一个链路与一对进程相关联 自动建立链接 链接是通用的双向链接 Indirect Communication 间接通信，为异步通信，通常情况下互通信都需要有消息队列；发送者将信息放置消息队列中，接受者从消息队列中取出消息\nSend(P, message)：像消息队列发送消息 Receive(Q, message)：接受消息队列中的消息 每个进程都有唯一ID 共享一个消息队列 在间接通信中，一般连接的属性有以下特征：\n一对进程共享消息队列时，才会在进程之间建立链接 链接可以被许多进程关联 链接可以是单向也可以是双向 每个进程可以有多个链接 \u0026nbsp;\u0026nbsp;直接通信\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;间接通信 Synchronization 从另一方面来讲，消息传递可以是阻塞 Blocking 或非阻塞 Non-Blocking 的；同步 synchronous 会阻塞一个进程，直到发送完成。异步 asynchronous 则是是非阻塞的，发送操作完成后会立即返回不等待返回结果\nBuffer 消息通过队列传递，队列的容量则具有下列三种配置之一：\n0容量：消息不能存储在队列中，因此发送者必须阻塞，直到接收者接受消息 有限容量：队列中有一定的预先约定大小的容量。如果队列已满，发送者必须阻塞，直到队列中有可用空间（反之为空，接受者阻塞），否则可能是阻塞的或非阻塞的 无限容量：具有无限容量的队列，发送者永远不会被迫阻塞 至此整节围绕对消息传递功能的三个方面做了介绍：\n直接或间接通信 同步或异步通信 显式缓冲 Signal 信号是一种有限IPC形式，通常用于Unix、类Unix、POSIX的操作系统。信号是异步的，发送信号后，操作系统会中断进程正常的执行流程来传递信号，如果进程注册过相关信号处理逻辑，则执行对应代码，否则按照POSIX标准执行相关操作。\n信号类似于中断，与中断的区别是，中断是由CPU调解，内核处理，信号是由内核调解，用户程序处理\n接收到信号时会发生什么?\ncatch: 指定信号处理函数被调用 ignore: 依靠操作系统的默认操作(abort，memory dump，suspend or resume process) mask: 屏蔽信号因此不会传送(可能是暂时的，当处理同样类型的信号) 信号的缺点\n不能传输要交换的任何数据 信号的实现：\n应用程序注册信号处理函数，作为系统调用发给操作系统 产生了信号，操作系统返回为信号处理函数入口 通过修改 Stack ，将后续的执行改为信号处理函数的入口 Pipe 管道是用来数据交换的，是进程之间最简单的通讯方式。通过将一个命令的输出，作为另一个命令的输入来实现的\nMessage Queue 消息队列是按FIFO 无界 队列，克服了管道的一些缺点：\n无需父进程建立通道 Pipe是数据流，MQ是一个结构化数据 MQ和Pipe一样是 Bounded Buffer Share Memory 共享内存是一种直接通信方式，对于进程来说，每个进程是私有地址空间；在每个地址空间内，明确地设置了共享内存段。\n优点：共享内存速度快更快，不需要系统调用并且以正常的内存速度进行访问。\n缺点\n设置更复杂\n需要保障进程间同步机制 无法在在多台计算机上正常工作\n如何实现内存共享\n如图所示，可以将共享内存映射到两个不同的进程内存段之间\nOther IPC Socket被定义为通信中的端点，是指一对进程通过网络使用一对套接字进行通信，套接字由连接的端口号和 IP 地址组成，通常为C/S架构。\nUNIX Domain Socket 是基于Socket网络通信架构的基础上发展出的一种IPC机制，相比于Socket，UNIX Domain Socket更高效。\nIP Socket 是可以用在同一台主机的进程间通讯（通过loopback地址127.0.0.1），但 UNIX Domain Socket ，不是使用底层网络协议（不需要打包拆包、计算校验和、维护序号和应答等），所有通信都完全发生在操作系统内核中。通过使用文件系统，是两个进程可以同时打开一个UDS进行通信\n一些其他的IPC\nRPC Remote Procedure Calls 基于Socket的 FTP HTTP等 Reference\nProcesses\nIPC\nUNIX Domain Socket\n","permalink":"https://www.161616.top/ch12-ipc/","summary":"Overview 进程间是相互保持独立的，内存管理中，就是保护进程的地址空间不被其他进程访问。而进程间通信 ( Inter-process Communication IPC) 用于在一个或多个进程间交换数据\n进程间合作是那些可以影响或受其他过程影响的过程。例如网站包含 JS、H5、Flash，当有一个相应缓慢时，会发生整个网站的布局或其他功能的展示。\n通常情况下进程间合作被允许的原因有：\n信息共享：多个进程需要访问同一个文件。（如管道）\n计算加速：将复杂功能拆分为多个子任务（多处理器时效果更佳），可以更快地解决问题\n模块化：将整体系统架构分为不同功能模块，模块间相互协作\n便利：单用户可以同时多任务处理，如 编辑、编译、打印等\nCommunications model (a) 消息队列（间接通信） \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;(b) 共享内存（直接通信） Message Passing IPC背后关键的一点是消息的传递，即一个进程发消息，一个进程接收消息\n而为了使进程间通信，就必须在进程间建立连接，连接可以是单/双向。连接可以使用直接通信和间接通信来实现\nDirect Communication 直接通信，必须明确声明发送者或接收者的名称，通常定义为：\nSend(P, message)：发送信息到进程 P Receive(Q, message)：接收来自进程 Q 的信息 在直接通信中，一般连接的属性有以下特征：\n一个链路与一对进程相关联 自动建立链接 链接是通用的双向链接 Indirect Communication 间接通信，为异步通信，通常情况下互通信都需要有消息队列；发送者将信息放置消息队列中，接受者从消息队列中取出消息\nSend(P, message)：像消息队列发送消息 Receive(Q, message)：接受消息队列中的消息 每个进程都有唯一ID 共享一个消息队列 在间接通信中，一般连接的属性有以下特征：\n一对进程共享消息队列时，才会在进程之间建立链接 链接可以被许多进程关联 链接可以是单向也可以是双向 每个进程可以有多个链接 \u0026nbsp;\u0026nbsp;直接通信\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;间接通信 Synchronization 从另一方面来讲，消息传递可以是阻塞 Blocking 或非阻塞 Non-Blocking 的；同步 synchronous 会阻塞一个进程，直到发送完成。异步 asynchronous 则是是非阻塞的，发送操作完成后会立即返回不等待返回结果\nBuffer 消息通过队列传递，队列的容量则具有下列三种配置之一：\n0容量：消息不能存储在队列中，因此发送者必须阻塞，直到接收者接受消息 有限容量：队列中有一定的预先约定大小的容量。如果队列已满，发送者必须阻塞，直到队列中有可用空间（反之为空，接受者阻塞），否则可能是阻塞的或非阻塞的 无限容量：具有无限容量的队列，发送者永远不会被迫阻塞 至此整节围绕对消息传递功能的三个方面做了介绍：","title":"ch12 进程间通讯"},{"content":"Overview 文件系统和文件 文件系统: 一种用于持久性存储的系统抽象，决定了辅存中的内容如何组织与存储的抽象概念\n文件：文件系统中的一个单元的相关数据在操作系统中的抽象，展现给用户的抽象概念\n文件系统的功能：\n分配文件磁盘空间 管理文件块(哪一块属于哪一个文件) 管理空闲空间(哪一块是空闲的) 分配算法(策略) 管理文件集合 定位文件及其内容 命名：通过名字找到文件的接口 最常见：分层文件系统 文件系统类型(组织文件的不同方式) 提供的便利及特征 保护：分层来保护数据安全 可靠性，持久性：保持文件的持久即使发生崩溃,媒体错误,攻击等 为什么需要文件系统：\n如果将文件放入一个房间中，整个房间都是堆积的文件\n有了文件系统的存在将会改变一切\n空间管理、元数据、数据加密、文件访问控制和数据完整性等等都是文件系统的职责。\n文件属性 文件具有名称和数据，还存储文件创建日期和时间、当前大小、上次修改日期等元信息。所有这些信息都称为文件系统的属性。常见的文件属性有\n名称：它是以人类可理解的形式。 标识符：每个文件都由文件系统中的唯一标记号标识，称为标识符。 位置：设备上的文件位置。 类型：支持各种类型文件的系统需要此属性。 大小：当前文件大小的属性。 保护：分配和控制读、写和执行文件的访问权限。 时间、日期和安全：用于对文件的保护、安全，也用于监控 文件描述符 文件头 File Header；类似于Unix的 inode，在存储元数据中保存了每个文件的信息，保存文件的属性，跟踪哪一块存储块属于逻辑上文件结构的哪个偏移\n文件描述符 （file-descriptor）；是唯一标识操作系统中打开文件的数字（整形），用于用户和内核空间之间的接口，以识别 文件/Socket 资源。因此，当使用 open() 或 socket()（与内核接口的系统调用）时，会得到一个文件描述符，一个整数。因此，如果直接与内核交互，使用系统调用 read(), write() 等 close()。使用的是一个文件描述符句柄。\n在 C 语言中 stdin，stdout、 和 stderr ，在 UNIX 中分别映射到文件描述符 0 1 2\ntext 1 2 3 4 5 f = open(name, flag); ... ... = read(f, ...); ... close(f); 内核跟踪每个进程打开的文件：\n操作系统为每个进程维护一个打开文件表 一个打开文件描述符是这个表中的索引 描述符由唯一的非负整数表示，例如 0、12 或 567。系统上每个打开的文件至少存在一个文件描述符 如果对文件需要更好的管理，就需要元数据来管理打开文件:\n文件指针：指向最近的一次读写位置，每个打开了这个文件的进程都这个指针\n文件打开计数：记录文件打开的次数； 当最后一个进程关闭了文件时，允许将其从打开文件表中移除\n文件磁盘位置：缓存数据访问信息\n访问权限：每个程序访问模式信息\n从用户角度来看：文件是持久的数据结构\n系统访问接口: 字节的集合(UNIX) 系统不会关心你想存储在磁盘上的任何的数据结构 操作系统内部视角： 块的集合（块是逻辑转换单元，而扇区是物理转换单元） 块大小\u0026lt;\u0026gt; 扇区大小：在UNIX中，块的大小是 4KB 空间管理 在系统层面上来看，存储设备在被划分为称为扇区的固定大小的块；扇区是存储设备上的最小存储单元 ，介于 512 bytes 和 4096bytes 之间；所有的操作都是操作磁盘块，读写1bytes也是对整个磁盘块的读写。\n文件系统使用的是块，块是物理扇区的抽象\n用户怎么访问文件：在系统层面需要知道用户的访问模式\n顺序访问：按字节依次读取 几乎所有的访问都是这种方式 随机访问：从中间读写 不常用,但是仍然重要，如：虚拟内存支持文件，内存页存储在文件中； 更加快速，不希望获取文件中间的内容的时候也必须先获取块内所有字节 内容访问：通过特征（索引） 更多是数据库形式的存在；通过index，找到包含关系的文件 文件访问控制 多用户操作系统中的文件共享是很必要的，不是每个人都能够删除或修改他们没有权限的文件。\n有关用户权限和文件所有权的数据在不同的操作系统上有不同的格式：\n类 Unix被存储为访问控制列表 ；Access-Control List （ACL） Windows 上被存储为访问控制条目；Access-Control Entries (ACE) 谁能够获得哪些文件的哪些访问权限\n访问模式: 读,写,执行,删除,列举等 文件访问控制列表(ACL)：\n\u0026lt;文件实体, 权限\u0026gt; UNIX模式：\n\u0026lt;用户|组|物主,读|写|可执行\u0026gt; 用户ID识别用户；表明每个用户所允许的权限及保护模式 组ID允许用户组成组，并指定了组访问权限 多用户下，如何同时访问共享文件：\n和进程同步算法相似\n因磁盘IO和网络延迟而设计简单\n文件共享的一致性语义 Consistency Semantics\nUNIX文件系统语义 Unix Semantics ：\n对打开文件的写入内容立即对其他打开同一文件的其他用户可见\n共享文件指针允许多用户同时读取和写入文件\n会话语义 Session Semantics：\n用户对文件的写入对其他用户不可见。 文件关闭后，更改仅对新会话可见。 锁:\n一些操作系统和文件系统提供该功能 目录 目录是磁盘上的一个位置，是一种特殊的文件；用于对文件分层。文件夹和目录是可互换的术语。\n目录的特点：\n目录是一类特殊的文件：每个目录都包含了一张表 \u0026lt;name, pointer to file header\u0026gt;\n目录和文件的树形结构：早期的文件系统是扁平的(只有一层目录)\n层次名称空间：\n名字解析：逻辑名字转换成物理资源（如文件）的过程:\n在文件系统中：到实际文件的文件名(路径) 遍历文件目录直到找到目标文件 举例：解析 /bin/ls 读取root的文件头(在磁盘固定位置) 读取root的数据块: 搜索bin项 读取bin的文件头 读取bin的数据块: 搜索ls项 读取ls的文件头 当前工作目录：当前目录的内容存储在内存中可以快速的指向一个目录\n每个进程都会指向一个文件目录用于解析文件名 允许用户指定相对路径来代替绝对路径 单级目录 单级目录 Single-Level Directory；单级目录理解起来比较简单，单级目录中所有文件都包含在同一目录中，必须具有唯一的名称\n两级目录 两级目录 Two-Level Directory；两级目录通常为不同用户的自己的目录 User File Director UDF，文件名只需在指定目录下唯一即可。\n树形目录 树形目录 Tree-Structured Directories；通常是指深度大于2的目录，在此结构中，每个文件都有一个唯一的路径\n文件别名 无环图目录 无环图目录 Acyclic-Graph Directories；也被称作为别名 Alias，是指在目录结构中一个文件有多个绝对位置（绝对路径）。UNIX中提供了两种类型的链接来实现该类型：\n硬链接 hard link：硬链接会创建新的文件，指向同一个inode；在删除一个文件时，会删除一个指向底层 inode的链接。当所有指向该 inode 的链接都被删除，inode才会被删除（同一文件系统中的普通文件） 每个文件都有引用计数器，以跟踪当前有多少文件引用该文件。每当删除其中一个引用（有别名的文件）时，计数器就会减少，当它达到零时，可以回收磁盘空间。 符号链接 symbolic link；也称软连接，是一个特殊文件，包含被链接文件的信息，可以是目录或文件。 符号链接在删除或被移动时 找到所有链接并同时修改 符号链接悬空，Dangling Pointer；不在有效 Windows中仅支持符号链接，称为快捷方式\n通用图目录 通用图目录 General Graph Directory ；是一种允许循环的目录；上述无环目录中是会进入无限循环\n通用图目录比其他类型目录更佳灵活，最大问题是计算大小时的难度。通用图目录实现常见的是通过线性列表或哈希表：\n线性列表：将所有文件保存在一个目录中，类似一个单链表。每个文件都包含一个数据块指向指针和目录中的下一个文件。 哈希表：目录中对于每个文件，都会生成一个键值对，将其存入哈希表中。借助文件名的哈希函数，我们可以确定存储在目录中的各个文件 key 和其指针 key points 图：通用图目录结构\n文件系统挂载 在类Unix系统中，VFS 会为每个分区或可移动存储设备分配一个设备 ID 如 dev/disk1s1；然后，创建一个虚拟目录树 ，并将每个设备的内容作为单独的目录放在该目录树下。\n将目录分配给存储设备的行为称为挂载 mount，被分配的目录称为挂载点\n一个文件系统需要先挂载才能被访问 一个未挂载的文件系统被挂载在挂载点上 文件系统种类 磁盘文件系统：文件存储在数据存储设备上,如磁盘; 例如: FAT，NTFS，ext2 3， ISO9660等\n数据库文件系统：文件根据其特征是可被寻址的; 例如: WinFS\n日志文件系统: 记录文件系统的修改,事件; 例如: journaling file system\n网络，分布式文件系统：例如：NFS，SMB，AFS，GFS\n虚拟文件系统\n虚拟文件系统 虚拟文件系统 Virtual File Systems VFS；是对多种类型文件系统提供统一的通用API，使得在上层软件中，可以用单一的方式实现不同底层的文件系统。\n虚拟文件系统的功能：\n提供相同的文件和文件系统接口 管理所有文件和文件系统关联的数据结构 高效查询例程，遍历文件系统 与特定文件系统模块的交互 VFS除了统一的API外，还提供了唯一标识符 vnode；对于类UNIX操作系统中，inode是惟一的，并不会跨网络\nLinux中虚拟文件系统的对象类型：\ninode：代表单个文件 存储文件的元信息（许可，拥有者，大小，数据库位置等） 一个文件对应一个inode，每个inode都由一个inode编号唯一标识 一个inode通常对应于存储在磁盘上的一个文件控制块 file：代表一个打开的文件 存储进程和打开文件之间交互的信息。这个信息只存在于内核空间，不保存在磁盘上。 superblock：卷控制块代表一个文件系统 每个文件系统一个 文件系统详细信息 块，块大小，空余块，计数，指针等 dentry：代表目录项 dirctory entry 用于将一个inode（文件）与一个目录关联 将目录项数据结构及树形布局编码成树形数据结构 指向文件控制块，父节点，项目列表等 数据块缓存 打开文件的数据结构 内存中文件系统结构。(a) 文件打开 \u0026nbsp;\u0026nbsp;\u0026nbsp;(b) 文件读取 由图可知，当一个文件在程序中被访问时（创建、打开），open() 系统调用从磁盘读入FCB信息，并将其存储在系统打开文件表中。一个条目被添加到每个进程的打开文件表中，这个索引引用的是系统范围的打开文件表，并且进程表的索引由 open() 这个系统调用返回。这个索引称为文件描述符， Windows中为文件句柄。\n如果有多个进程打开同一个文件，此时系统表中的计数器会递增。对应打开文件表条目会引用到系统表中\n文件被关闭时，每个进程的打开文件表的条目被释放，系统表中的计数器递减。如果计数器到零，则系统表也被释放。\n打开文件描述：\n每个被打开的文件一个\n文件状态信息\nFCB：目录项，当前文件指针，文件操作设置等\n打开文件表：\n一个进程一个\n一个系统级的\n每个卷控制块也会保存一个列表\n所以如果有文件被打开将不能被卸载\n一些操作系统和文件系统提供该功能\n调节对文件的访问\n强制 - 根据锁保持情况和需求拒绝访问 劝告 - 进程可以查找锁的状态来决定怎么做 文件分配 在文件分配中，存在一些问题\n大多数文件很小\n需要对小文件支持 块的空间不能设置太小 一些文件却很大\n必须支持大文件 大文件需要高效访问 磁盘的分配主要有三种方式：连续 contiguous、链式 linked 、索引 indexed ；分配指标必须遵循 高效的存储利用，与访问速度的表现\n连续分配 连续分配 （Contiguous Allocation）是指文件的所有块连续地保存在一起\n连续分配的特点：\n只需要一个起始块，与长度\n性能非常快，读取同一文件的连续块不需要移动磁头；或仅需要移动一小块到达相邻柱面\n连续分配存在的问题：固定递归存储时不会出现问题，当文件增长或创建时文件的确切大小未知，会出现问题：\n碎片：\n过高估计文件最终的大小会增加外部碎片从而浪费浪费磁盘空间\n文件增长超出其最初分配的空间，过低预估则可能需要移动文件位置或中止进程\n文件增长问题：\n如果一个文件初始分配大小很大，并长期没有写入，在文件填满时，很多空间将变得不可用 链式分配 链式分配（ Linked Allocation ）是指文件按照数据链表方式存储，并以每个链接消耗的存储空间为代价\n链式分配的特点：\n无外部碎片，无需预先知道文件大小，并支持文件的动态增长 链式分配的不足：\n无法实现高效的随机访问；链式访问仅可以串行访问文件，必须从每个位置的列表头开始 可靠性：指针丢失或损坏 索引分配 索引分配（Indexed Allocation）是指将每个文件的索引组合在一个公共块上，每个索引项指向了一个数据块\n索引分配的特点：基本包含链式分配和连续分配的所有优点\n索引分配的不足：会浪费一部分磁盘空间\n无论文件大小，存储索引都有一定开销（文件大小\u0026lt;索引大小） 单个索引块不能保存大文件的所有指针 对于单个索引块不能保存大文件的所有指针这个问题，一般情况下有下述解决方案\n链式索引块模式 Linked Scheme：索引块为一个磁盘块，第一个索引块包含一些头信息、前 N 个块地址；还包含指向其他链接索引块的指针 多级索引 Multi-Level Index：类似于多级页表，第一个索引块包含一组指向二级索引块的指针，而二级索引块又包含指向实际数据块的指针 联合模式 Combined Scheme： 图：索引分配 图：多级索引 Reference\nmultilevel-indexes\n空闲空间管理 空闲空间管理只是磁盘管理中，需要追踪和分配对空闲空间部分的管理，对于空闲空间的管理的表现是位图 bitmap；即通过位向量来标记数据块的使用情况\n其他管理空闲空间的方式：\n链表：链表也可以作为管理空闲块的一种方式 分组：将空闲块存储在第一个空闲块中，前 n-1 个空闲块是空闲的，最后一个包含下一个分组 计数：存储一个磁盘块的地址和之后n个空闲块的数量 Reference\n一些空闲空间管理的测试题\n多磁盘管理 RAID 冗余磁盘阵列 **RAID **（redundant array of independent disks）；是一种小磁盘分区组成大分区的一种解决方案，一种磁盘管理技术\nRAID的工作原理 RAID 的工作原理是将数据分别放置在多个磁盘上，并允许I/O操作以平衡的方式，从而提高磁盘性能\nRAID Controller RAID 控制器是用于管理存储阵列中的硬盘驱动器的设备，是操作系统和物理磁盘中间的一个抽象层，将磁盘表示为逻辑单元。\nRAID控制器分为软RAID和硬RAID\n硬RAID Hardware RAID：物理控制器管理整个磁盘阵列；如主板或RAID卡 软RAID Software RAID：使用硬件资源来管理磁盘阵列；如CPU、内存；软RAID可能无法实现性能提升，反而影响性能 RAID级别 RAID0 RAID0（无容错的条带化磁盘阵列），是将数据体划分为块并将数据块分布在独立磁盘冗余阵列中的多个存储设备中，但不提供容错。\nRAID1 RAID1又称为磁盘镜像 mirroring，将数据复制到两个或更多磁盘，由至少两个数据存储驱动器组成，读性能提升，写性能与单磁盘相同。RAID1提供了全面的数据保护\nRAID5 RAID0（基于奇偶校验块的条带化磁盘阵列）；它使用类似于 RAID0 的分布式数据存储，但通过包含写入不同阵列磁盘的冗余信息（奇偶校验码）来提高数据存储的可靠性。RAID 5 至少需要三个磁盘，但出于性能原因，通常建议使用至少五个磁盘。\nRAID 10 RAID 0+1是结合 RAID 1 和 RAID 0的一种模式，通常称为 RAID 10；RAID10需要至少四个磁盘；通过跨镜像的条带化保存数据，只要每个镜像对中的一个磁盘正常，那么数据就正常\n磁盘调度 磁盘调度是指在操作系统层面重新组织IO请求的顺序，来有效的减少磁盘访问开销。\n在传统磁盘中由多个盘面组成，每个盘面被臂组件上的磁盘头来通过旋转读取，通过所希望的扇区开始\n在操作中，磁盘会告诉旋转例如 7200RPM（revolutions per minute）（实际读写速度120 MB/s ）。数据从磁盘传输到计算机的速率由几个步骤组成：\n寻道时间 seek time：是将磁头从一个柱面移动到另一个柱面所需的时间，以及磁头在移动后稳定下来所需的时间。是过程中最慢的步骤，也是整体传输速率的主要瓶颈。 旋转延迟 rotational latency：扇区从开始处到目的处所需时间 传输速率 transfer rate：数据从磁盘移动到计算机所需的时间 $access\\ time=SK+RL+TR$\n$T_a$ access time 访问时间 $T_s$ seek time 寻址时间 $T_r$ rotational latency 旋转延迟 $T$ transfer time 传输速率 $b$ transfer b bytes 传输位 $N $ bytes per track 每轨存多少字节 $r$ Rotational speed $\\frac{1}{r}$ 旋转一周的时间 $\\frac{1}{2r}$ 平均旋转延迟，旋转一周时间再取一半 $\\frac{b}{rN}$ 数据访问时间 那么 传输速率 $T = \\frac{b}{rN}$\n平均访问时间为 $T_a = T_s + \\frac{1}{2r} + \\frac{b}{rN}$\n如：$T_s = 2ms$，$r=10000rpm$，512B扇区大小，每轨320个扇区， 1.3 MB的文件大小\n转一圈的时间：RPM=10000时，每6毫秒转一圈：$\\frac{1}{r} = \\frac{60}{10000}=0.006s = 6ms$\n一个文件有多少扇区：$\\frac{1.3MB}{512} = \\frac{1300000}{512}=2540$\n一共多少轨道：$2540\\div320=8$\n通过磁道 tracks 访问需要耗时多少？\n一个磁道要传输的数据大小：$b=512\\times320$\n每轨有多少字节：$N=512\\times320$\n$\\frac{1}{r}=6$，$\\frac{1}{2r} = 3$\n$\\frac{b}{rN}=6$ ；因$b=N$ 故$\\frac{b}{rN}=6$\n因 $T_s=2$，$Average T_r=3$，$\\frac{b}{rN}=6$ ；那么 $T_a = T_s + \\frac{1}{2r} + \\frac{b}{rN}=2+3+6=11$\n那么按轨寻址访问的总时间为：$8*11=88ms$\n通过扇区 sectors 访问需要耗时多少？\n每轨多少字节 $N=512\\times320$ 因磁轨转一圈多少时间 $\\frac{1}{r} = 6$，$\\frac{1}{2r} = 3$；每磁道耗时 $\\frac{1}{r} = \\frac{6}{320} = 0.01875ms$ 因 $\\frac{1}{r} = \\frac{6}{320}$ ；$b=512$；$N=512\\times320$；那么 $\\frac{b}{rN}=0.01875$ 那么一个扇区的时间为： 因 $T_s=2$，$Average T_r=3$，$\\frac{b}{rN}=0.01875$ b=512，N=512，$\\frac{1}{r} = 0.01875$，那么，$\\frac{b}{rN}=0.01875$ 那么 $T_a = T_s + aT_r + \\frac{n}{rN} = 2+3+0.01875 = 5.01875$ 那么总共时间为 $2540*5.01875=12748ms$ 那么就可以得出结论：\n寻道方式是性能上区别的原因 如果来回寻道，会导致性能急剧下降 对单个磁盘，会有一个IO请求数目 如果请求是随机的，那么会表现很差 磁盘调度算法 磁盘传输速度主要受寻道时间和旋转延迟的限制。当要处理多个请求时，在等待其他请求被处理时也会有一些固有的延迟。\n带宽是通过传输的数据量除以从发出第一个请求到完成最后一次传输的总时间量来衡量的\n通过以良好的顺序处理请求，可以提高带宽和访问时间。\n磁盘请求包括磁盘地址、内存地址、要传输的扇区数以及请求是读还是写。\nFCFS 先来先服务 First-Come First-Serve：\n按顺序处理请求 公平对待所有进程 在有很多进程的情况下，接近随机调度的性能 可以看到柱面 cylinder从122 到 14 在回到124摆动很巨大：\nSSTF 最短寻道优先 Shortest Seek Time First\n选择从磁臂当前位置需要移动最少的IO请求 总是选择最短寻道时间 可能会导致饥饿 由图可以看出相同的情况下SSTF将柱面移动数减少到 236 个柱面，低于**FCFS **所需的 640 个柱面。\nSCAN SCAN算法，又称电梯算法，磁臂在一个方向上移动，满足所有为完成的请求，直到磁臂到达该方向上最后的磁道，类似于在高层建筑中处理请求的电梯。\nC-SCAN Circular-SCAN，通过循环队列方式来改进 SCAN ：一旦磁臂到达磁盘的末端，它会返回到另一端而不处理任何请求，然后从磁盘的开头重新开始：\nLOOK LOOK是通过待处理请求的队列来改进SCAN，而不是磁盘序列，并且不会将磁头移向磁盘末端的距离超过必要的位置。\nN-Step LOOK or N-Step-SCAN N-Step-SCAN是将请求队列分为成长度为N的子队列，使用SCAN算法进行处理；当在一个队列的处理过程中，如果有一些新的请求到达，那么它们必须被放置在另一个队列中。\n设有从 0 到 199 编号为 200 磁道；请求顺序为， 122、90、160、24、102、89、143、18、67；$N=2$\n子队列有：\n1 = {122, 90}\n2 = {160, 24}\n3 = {102, 89}\n4 = {143, 18}\n5 = {67}\nN-step-SCAN 磁盘调度算法的吞吐量高，平均响应时间低。\nReference\nn-step-scan disk scheduling algorithm\nFSCAN FSCAN Fixed period SCAN，是 N-step-SCAN 的简化版本，FSCAN将磁盘请求队列分成两个子队列，一个是由当前所有请求磁盘I/O的进程所形成的队列，由磁盘调度按SCAN算法进行处理，在扫描期间，将新出现的请求磁盘I/O的进程放入另一个等待处理的请求队列。这样，所有的新请求都被推迟到下一次扫描时处理。\n","permalink":"https://www.161616.top/ch13-file-system/","summary":"Overview 文件系统和文件 文件系统: 一种用于持久性存储的系统抽象，决定了辅存中的内容如何组织与存储的抽象概念\n文件：文件系统中的一个单元的相关数据在操作系统中的抽象，展现给用户的抽象概念\n文件系统的功能：\n分配文件磁盘空间 管理文件块(哪一块属于哪一个文件) 管理空闲空间(哪一块是空闲的) 分配算法(策略) 管理文件集合 定位文件及其内容 命名：通过名字找到文件的接口 最常见：分层文件系统 文件系统类型(组织文件的不同方式) 提供的便利及特征 保护：分层来保护数据安全 可靠性，持久性：保持文件的持久即使发生崩溃,媒体错误,攻击等 为什么需要文件系统：\n如果将文件放入一个房间中，整个房间都是堆积的文件\n有了文件系统的存在将会改变一切\n空间管理、元数据、数据加密、文件访问控制和数据完整性等等都是文件系统的职责。\n文件属性 文件具有名称和数据，还存储文件创建日期和时间、当前大小、上次修改日期等元信息。所有这些信息都称为文件系统的属性。常见的文件属性有\n名称：它是以人类可理解的形式。 标识符：每个文件都由文件系统中的唯一标记号标识，称为标识符。 位置：设备上的文件位置。 类型：支持各种类型文件的系统需要此属性。 大小：当前文件大小的属性。 保护：分配和控制读、写和执行文件的访问权限。 时间、日期和安全：用于对文件的保护、安全，也用于监控 文件描述符 文件头 File Header；类似于Unix的 inode，在存储元数据中保存了每个文件的信息，保存文件的属性，跟踪哪一块存储块属于逻辑上文件结构的哪个偏移\n文件描述符 （file-descriptor）；是唯一标识操作系统中打开文件的数字（整形），用于用户和内核空间之间的接口，以识别 文件/Socket 资源。因此，当使用 open() 或 socket()（与内核接口的系统调用）时，会得到一个文件描述符，一个整数。因此，如果直接与内核交互，使用系统调用 read(), write() 等 close()。使用的是一个文件描述符句柄。\n在 C 语言中 stdin，stdout、 和 stderr ，在 UNIX 中分别映射到文件描述符 0 1 2\ntext 1 2 3 4 5 f = open(name, flag); ... .","title":"ch13 file system"},{"content":"死锁问题 死锁 deadlock；是一组阻塞的进程，每个进程都持有一个资源并等待获取另一个进程持有的资源。\n死锁的示例：交通桥\n如图所示，桥是资源，进程是车辆，两个不同方向的车辆同时占用桥，此时发生谁也过不去的情况（死锁的发生）；\n当死锁发生时，如果一辆车倒车（抢占资源和回滚）就可以解决死锁问题 死锁发生时，可能需要后退多台车辆 饥饿，而饥饿并不一定是死锁 系统模型 在正常情况下，进程必须在使用之前请求资源，并在完成后释放它，顺序如下：\n请求：如果不能立即授予请求，则进程等待，直到它需要的资源变得可用。例如，系统调用 open()、malloc()、new() 、request() 等。\n使用：进程使用资源，例如文件中读取数据；使用硬件。\n释放：进程完成后放弃资源，以便其可用于其他进程。如，close()、free()、delete() 、 release()。\n当在集合中的每个进程都在等待当前分配给集合中另一个进程的资源时，这一组进程就会发生死锁\n资源分配 通过实例来理解死锁，\n一组资源：\n${ R_1,\\ R_2,\\ R_3,\\ \u0026hellip;.,\\ R_N }$；为方形，图形内的点代表资源数量 一组进程：\n${ P_1,\\ P_2,\\ P_3,\\ \u0026hellip;.,\\ P_N }$ 请求边缘 Request Edge：进程需要一些资源，被称为请求边缘；如 $P_i\\ →\\ R_j$\n分配边缘 Assign Edge：当资源已经被分配给进程，被称为分配边缘；如 $R_j\\ →\\ P_i$\n当请求被授予时，可以通过反转方向的线将请求边缘转换为分配边缘\n类型 示意图 Process Resource $P_i$ 请求的 $R_j$ 实例 $P_i$ 持有一个 $R_j$ 的实例\n也可以说 $R_j$ 被 $P_i$ 所持有 资源分配图 资源分配图 如图所示，资源类型为：\nP = ${P_1,\\ P_2,\\ P_3}$ R = ${R_1,\\ R_2,\\ R_3,\\ R_4}$ RE = ${P_1\\ →\\ R_1,\\ P_2\\ →\\ R_3}$ AE = ${R_1\\ →\\ P_2,\\ R_2\\ →\\ P_2,\\ R_2\\ →\\ P_1,\\ R_3\\ →\\ P_3}$ ${P_1}$ 持有 ${R_2}$ 等待 ${R_1}$ ${P_2}$ 持有 ${R_1}$ 和 ${R_2}$ 等待 ${R_3}$ ${P_3}$ 持有 ${R_3}$ 死锁示意图 资源分配图不包含循环，则不会死锁；如果有向图为环形，则为死锁，例如\n环形有向图 死锁示意图资源类型为：\nP = ${P_1,\\ P_2,\\ P_3}$ R = ${R_1,\\ R_2,\\ R_3,\\ R_4}$ RE = ${P_1\\ →\\ R_1,\\ P_2\\ →\\ R_3,\\ P_3\\ →\\ R_2 }$ AE = ${R_1\\ →\\ P_2,\\ R_2\\ →\\ P_2,\\ R_2\\ →\\ P_1,\\ R_3\\ →\\ P_3}$ ${P_1}$ 持有 ${R_2}$ 等待 ${R_1}$ ${P_2}$ 持有 ${R_1}$ 和 ${R_2}$ 等待 ${R_3}$ ${P_3}$ 持有 ${R_3}$ 等待 $R_2$ 这个图出现两个环形，这种情况下会发生死锁\n$P_1\\ →\\ R_1\\ →\\ P_2\\ →\\ R_3\\ →\\ P_3\\ →\\ R_2\\ →\\ P_1$ $P_2\\ →\\ R_3\\ →\\ P_3\\ →\\ R_2\\ →\\ P_2$ 有环但无死锁 有环但无死锁 有环但无死锁意图资源类型为：\nP = ${P_1,\\ P_2,\\ P_3,\\ P_4}$ R = ${R_1,\\ R_2}$ RE = ${P_1\\ →\\ R_1,\\ P_3\\ →\\ R_2 }$ AE = ${R_1\\ →\\ P_2,\\ R_1\\ →\\ P_3,\\ R_2\\ →\\ P_4,\\ R_2\\ →\\ P_1}$ ${P_1}$ 持有 ${R_2}$ 等待 ${R_1}$ ${P_2}$ 持有 ${R_1}$ ${P_3}$ 持有 ${R_1}$ 等待 $R_2$ ${P_4}$ 持有 ${R_2}$ 此图中，$P_4$ 执行完会释放 $R_2$，$R_2$会被分配给 $P_3$，这样就跳出了循环\n结论 如果图中没有循环，则无死锁 如果图中，一个资源仅包含一个实例，必然会发生死锁 如果图中，一个资源包含多个实例，则可能会发生死锁 死锁特性 必要条件 实现死锁需要四个条件： Mutual Exclusion：至少一个资源必须以不可共享的方式持有；如果任何其他进程请求此资源，则该进程必须等待资源被释放。 Hold and Wait：一个进程必须同时持有至少一个资源并等待至少一个当前被其他进程持有的资源。 No preemption：一旦进程持有资源，则该资源不能从该进程中被抢占，直到该进程自愿释放。 Circular Wait ：一组等待的进程 ${P_0,\\ P_1,\\ P_2,\\ \u0026hellip;,\\ P_N}$ 必须存在每个 $P[ i ] $ 都在等待 $P[ ( i + 1 ) % ( N + 1 )]$ 死锁处理方法 确保系统永远不会进入死锁 允许系统进入死锁状态，然后恢复 忽略这个问题，假装系统中从来没有发生死锁，用于大多数操作系统,包括UNIX 死锁预防 限制资源的申请方式\n互斥\n对于只读文件资源来说不会引起死锁 对于只允许单独访问的资源，需要互斥，如打印机 等待：必须确保进程在访问资源时，没有持有其他资源\n要求对所有进程同时请求所有资源。这种情况下当一个资源被占用，导致等待很长时间，这可能会浪费系统资源。 只有当进程能够获得旧的资源以及它请求新的资源，进程才可以执行 这种情况资源利用率低，饥饿 非抢占式：以下情况下可以防止死锁\n如果进程占有某些资源，并请求其他不能被立即分配的资源，则释放当前正占有的资源（可能资源浪费） 当请求资源不可用时，并本身属于阻塞；这时资源会被抢占，添加到进程等待资源列表中 循环等待\n对所有资源进行编号排序，并要求进程仅以严格的递增（递减）顺序请求资源（常见于嵌入式操作系统） 如：为了请求资源 $R_j$，进程必须首先释放所有的 $R_i$，使得 $i \u0026gt;= j$ 挑战性：如何确定不同资源的排序 避免死锁 死锁避免，与死锁预防的区别是，死锁预防是确保可以预防死锁必要条件的其中一个；死锁避免是确保进程不会导致死锁。\n要想防止死锁，就需要更多有关进程的信息，这样的话会导致系统资源利用率低（保守方法），根据不同的调度算法，会得到进程所需的资源的数量，还可以知道以什么顺序进行调度。\n调取器会根据资源分配状态检测将来是否出现死锁\n安全状态 如果系统可以分配所有进程的所有资源，而不进入死锁状态，则被称为安全状态。更严格来讲，存在安全的进程序列，则状态是安全的，（${P_0,\\ P_1,\\ P_2,\\ \u0026hellip;,\\ P_N }$）\n如果 $P_i$ 资源的需求不是立即可用，那么 $P_i$ 可以等到所有 $P_j$ 完成\n当 $i\u0026gt;j$ 时，$P_i$ 要求的资源能够由 $当前可用的资源+所有的\\ P_j\\ 持有的资源$ 来满足；如果 $P_i$ 完成了并释放了所有资源，那么 $P_j$ 也能够完成\n如图所示：如果不存在安全序列，则系统处于不安全状态，这可能会导致死锁。（所有安全状态都是无死锁的，但并非所有不安全状态都会导致死锁。）\n例如：考虑一个具有 12 个磁带驱动器的系统，分配如下。这是一个安全的状态吗？安全顺序是什么？\nProcess Maximum Needs Current Allocation $P_0$ 10 5 $P_1$ 4 2 $P_2$ 9 2 是安全的\n当 $t=0$ 时，$P_0$ 持有5个资源； $P_1$ 持有 2；$P_2$ 持有 2，此时是安全的； 安全顺序为：\n$P_1$ 可以分配所有的资源；$P_1\\ need = Total - Allocation = 12-9=3$，$P_1$ 需要4 ，已分配2，剩余3，可以分配给 $P_1$；当 $P_1$ 执行完成并释放，此时 $available=2+2+1 = 5$ $P_0$，可用5，已分配5，需要5；此时 $P_2$ 也可以执行；释放 $available=5+5=10$ $P_2$，可用10，已分配2，需要9；此时 $P_2$ 也可以执行；释放 $available=2+9+1=12$ 如果进程 P2 请求并被多分配一个磁带资源，会发生什么情况？\n会从安全状态变为不安全状态，$P_2$ 当前分配为3 ，此时 $P_1$ 还是可以执行，执行释放完之后，可用为4，不满足 $P_0$ 所需5 ，$P_2$ 所需的6，死锁 资源分配图 当资源类别仅具有其资源单实例，可以通过资源分配图中循环来检测死锁。这种情况下，使用声明边缘 （claim edge 根据上下文翻译的，不知道对不对），指向了在未来所需要请求的资源，用虚线表示，来避免不安全状态\n声明边缘 claim edge：未来需要请求的资源 请求边缘 request edge：请求的资源 进程在请求资源会从 claim edge 转为 request edge 进程释放资源会从 assignment edge 转为 claim edge 此方法的工作原理是，拒绝会在资源分配图中产生循环的请求，从而使声明边缘生效\n图：当生成的资源分配中会形成一个循环，此资源申请无法授予 Reference\nDeadlocks\nch7 deadlock\n银行家算法 银行家算法 The Banker\u0026rsquo;s algorithm，是一种资源分配和避免死锁的算法，是通过模拟所有资源的一种”预测“最大可能需求来为进程分配资源是否 安全；这个成为 safe state check\n为何被命名为银行家算法？ 银行家算法与银行使用相同的手法来检查资金是否可以批给贷款客户，假设一家银行有 N 个账户持有人，他们存入银行的总金额为 M。在分配任何贷款金额之前，银行会检查在从银行的总金额中减去贷款金额后，剩余的钱是否大于 M，大于 M 则分配这笔贷款是安全的，否则不是。\n而银行家算法是，在一个进程启动时，必须事先声明它可能请求的最大资源分配数，最高可达系统上可用的数量；在发出请求后，调度器确定分配给该进程资源是否会使系统处于不安全状态。如果是，则该进程等待，直到分配后系统处于安全状态。\n银行家算法的数据结构 n ：进程数\nm：资源数\n$Available[ m ]$：当前每种类型有多少资源可用。\n$Max[n][m]$：每个资源的每个进程的最大需求。\n$Allocation[n][m]$：分配给每个进程的每个资源类别的数量。\n$Need[n][m]$：每个进程每个类型所需的剩余资源。\n$Need[ i ][ j ] = Max[ i ][ j ] - Allocation[ i ][ j ]$\n银行家算法需要实现知道每个进程所需的最大资源个数\n银行家算法应用 首先需要一个算法来确定特定状态是否安全；算法会根据以下步骤确定系统的当前状态是否安全：\n⑴ Work 和 Finish 分别是长度为 m 和 n 的向量。 Work 是 available 的副本，将在分析期间进行修改。 finish 是一个布尔值的向量，表示进程是否完成 初始化时，所有 $Work=Available$ ；$Finish = false$ ⑵ 运行时，找到满足 $Finish[i] == false$；$Need[i]\u0026lt;Work[i]$；此时可以分配，如果不存在转4 ⑶ 完成时，设置 $Work = Work + Allocation[i]$，代表释放资源回到 2 ⑷ 如果所有的 $finish[ i ] == true$，则状态是安全的，因为已经完成并有安全序列 有了具体的规范，就需要看银行家算法本身如何执行，算法确定新请求是否安全，当发出请求时，对其做满足推定（假装被授予），查看结果是否安全，安全则批准，不安全则拒绝，如：\n$Request[ n ][ m ]$，代表当前请求需要的资源数，如果 $Request[ i ] \u0026gt; Need[ i ]$ 则拒绝 如果 $Request[ i ] \u0026gt; Available[i]$，那么则需要等待可用。 方法是满足推论，会检查结构是否安全，如果是，授予；如果否，则进程等待，直到请求可以被安全授予 整个过程的计算方式是：\n$Available = Available - Request$ $Allocation = Allocation + Request$ $Need = Need - Request$ Processes Allocation\nA B C Max\nA B C Available\nA B C $P_0$ 1 1 2 4 3 3 2 1 0 $P_1$ 2 1 2 3 2 2 $P_2$ 4 0 1 9 0 2 $P_3$ 0 2 0 7 5 3 $P_4$ 1 1 2 1 1 2 计算每个进程Need，列出矩阵图\n$Need = Max – Allocation$\nProcesses Allocation\nA B C Max\nA B C Available\nA B C Need\nA B C $P_0$ 1 1 2 4 3 3 2 1 0 3 2 1 $P_1$ 2 1 2 3 2 2 1 1 0 $P_2$ 4 0 1 9 0 2 5 0 1 $P_3$ 0 2 0 7 5 3 7 3 3 $P_4$ 1 1 2 1 1 2 0 0 0 这个是否安全？\n$P_0$ 需要 $(3\\ 2\\ 1)$，Available为 $(2\\ 1\\ 0)$；则 $Need \u0026lt;=Available = False$\n$P_1$ 需要 $(1\\ 1\\ 0)$，Available为 $(2\\ 1\\ 0)$；则 ==$Need \u0026lt;= Available = True$==\n$P_1$ 可以授予，执行结束后，$Available = Available +Allocation$ $(2, 1, 0) + (2, 1, 2)$ = $(4, 2, 2)$ $P_2$ 需要 $(5\\ 0\\ 1)$，Available为 $(4\\ 2\\ 2)$；则 $Need \u0026lt;=Available = False$.\n$P_3$ 需要 $(7\\ 3\\ 3)$，Available为 $(4\\ 2\\ 2)$；则 $Need \u0026lt;=Available = False$\n$P_4$ 需要 $(0\\ 0\\ 0)$，Available为 $(4\\ 2\\ 2)$；则 ==$Need \u0026lt;= Available = True$==\n$P_1$ 可以授予，执行结束后，$Available = Available +Allocation$ $(4, 2, 2) + (1, 1, 2)$ = $ (5, 3, 4)$ $P_2$ 需要 $(5\\ 0\\ 1)$，Available为 $(5\\ 3\\ 4)$；则 ==$Need \u0026lt;= Available = True$==.\n$Available = Available +Allocation$ $ (5, 3, 4) + (4, 0, 1)$ = $ (9, 3, 5) $ $P_3$ 需要 $(7\\ 3\\ 3)$，Available为 $(9\\ 3\\ 5)$；则 ==$Need \u0026lt;=Available = True$==\n$Available = Available +Allocation$ $(9, 3, 5) + (0, 2, 0) = (9, 5, 5)$ $P_0$ 需要 $(3\\ 2\\ 1)$，Available为 $(9\\ 5\\ 5)$；则 ==$Need \u0026lt;=Available = True$==\n故序列是安全的，安全序列为 $P_1, P_4, P_2, P_3, P_0$\nhttps://angom.myweb.cs.uwindsor.ca/teaching/cs330/ch7.pdf\n死锁检测 死锁检测是指如果在不能避免死锁的情况下，检测死锁何时发生，并以某些方式恢复。\n对死锁检测会对性能造成影响，除此以外，还必须有策略（算法）来从死锁中恢复，当进程必须被终止或抢占时，那进程工作会丢失\n每个资源类型单一实例 (a) 资源分配图\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;(b) 进程等待图 如图所示：等待图是资料类型的图的变种\n等待图中从 $P_i\\ →\\ P_j$ 的线表示进程 $P_i$ 正在等待进程 $P_j$ 持有的资源。 等待图中的循环表示死锁 算法必须可以做到维护一个等待图，并定期检测死锁循环 死锁检测算法 死锁检测算法与银行家算法基本相同，但有两个差别：\n银行家算法步骤一中，初始将所有 $Finish[i] = false$。而改算法仅 $Allocation[ i ] \\ne 0$，才将 $Finish[ i ] = false$。如果进程分配资源为0，则 $Finish[i] = true$。 假设如果所有其他进程都可以完成，那么这个进程也可以完成。此外，算法会寻找哪些进程涉及死锁情况，没有分配任何资源的进程不能参与死锁。 步骤2, 3 与银行家算法一致 银行家算法中，如果 $Finish[ i ] == true$ 则不存在死锁。该算法中，如果存在 $Finish[ i ] == false$ ，则检测到死锁。 如：有5个进程 $P_0$ ~ $P_4$；三种资源 $A=7$，$B=2$，$C=6$\nProcesses Allocation\nA B C Request\nA B C Available\nA B C $P_0$ 0 1 0 0 0 0 0 0 0 $P_1$ 2 0 0 2 0 2 $P_2$ 3 0 3 0 0 0 $P_3$ 2 1 1 1 0 0 $P_4$ 0 0 2 0 0 2 这个是否安全？\n$P_0$ request $(0\\ 0\\ 0)$，执行释放后\n$Available = Available +Allocation$\n$(0, 0, 0) + (0, 1, 0)$ = $(0, 1, 0)$\n$P_2$ 同 $P_0$\n$(0, 1, 0) + (0, 1, 0)$ = $(3, 1, 3)$ $P_1$ request $(2\\ 0\\ 0)$，执行释放后\n$(3, 1, 3) + (2, 0, 0)$ = $(5, 1, 3)$ $P_3$ request $(2\\ 1\\ 1)$，执行释放后\n$(5, 1, 3) + (2, 1, 1)$ = $(7, 2, 4)$ $P_4$ request $(0\\ 0\\ 2)$，执行释放后\n$(7, 2, 4) + (0, 0, 2)$ = $(7, 2, 6)$ 估是安全的\n检测算法使用 何时，使用什么样的频率来检测依赖于:\n死锁多久可能会发生？ 多少进程需要被回滚？ one for each disjoint cycle 这取决于预期死锁发生的频率，已经发生死锁后的后果，（如果发生死锁没有立即恢复，会越来越多的进程导致死锁后得不到资源阻塞）；通常情况下，常用方法：\n授予的资源分配后进行死锁检测，这样做可以立即检测到死锁；缺点是由于频繁检查死锁而导致性能下降。 仅在可能发生死锁的边缘（进程对资源的请求的边）时才进行死锁检测，这样检查频率就会很低，缺点是无法检测到原来死锁所涉及的进程，会导致死锁复杂化，恢复过程复杂 保留资源分配的历史日志定期检查死锁（如计时器、CPU资源利用率低）；通过追踪日志确定何时发生死锁以及哪些进程导致了最初的死锁 死锁恢复 通常从死锁中恢复有三种方法：\n人工干预；终止所有的死锁进程 终止一个或多个死锁进程，直到死锁消除 抢占资源 进程终止 基本可以恢复死锁的方法：\n终止所有涉及死锁的进程 一个一个的终止进程，直到死锁被消除 这种情况下，很多因素都决定接下来要终止进程的顺序： 优先级 进程运行了多久，还需多久才可完成 进程持有多少资源和什么类型的资源。 进程完成还需多少资源 需要终止多少个进程 进程是交互式的还是批处理 进程是否对任何资源进行了不可逆的更改 资源抢占 选择受害者：选择一个进程进行资源抢占，最小成本 回滚：返回到一些安全状态,重启进程到安全状态 饥饿：同一进程可能一直被选作受害者，包括回滚的数量 使用优先级系统，并在每次进程资源被抢占时增加进程的优先级。最终，获得足够高的优先级，使其不再被抢占。 ","permalink":"https://www.161616.top/ch11-deadlock/","summary":"死锁问题 死锁 deadlock；是一组阻塞的进程，每个进程都持有一个资源并等待获取另一个进程持有的资源。\n死锁的示例：交通桥\n如图所示，桥是资源，进程是车辆，两个不同方向的车辆同时占用桥，此时发生谁也过不去的情况（死锁的发生）；\n当死锁发生时，如果一辆车倒车（抢占资源和回滚）就可以解决死锁问题 死锁发生时，可能需要后退多台车辆 饥饿，而饥饿并不一定是死锁 系统模型 在正常情况下，进程必须在使用之前请求资源，并在完成后释放它，顺序如下：\n请求：如果不能立即授予请求，则进程等待，直到它需要的资源变得可用。例如，系统调用 open()、malloc()、new() 、request() 等。\n使用：进程使用资源，例如文件中读取数据；使用硬件。\n释放：进程完成后放弃资源，以便其可用于其他进程。如，close()、free()、delete() 、 release()。\n当在集合中的每个进程都在等待当前分配给集合中另一个进程的资源时，这一组进程就会发生死锁\n资源分配 通过实例来理解死锁，\n一组资源：\n${ R_1,\\ R_2,\\ R_3,\\ \u0026hellip;.,\\ R_N }$；为方形，图形内的点代表资源数量 一组进程：\n${ P_1,\\ P_2,\\ P_3,\\ \u0026hellip;.,\\ P_N }$ 请求边缘 Request Edge：进程需要一些资源，被称为请求边缘；如 $P_i\\ →\\ R_j$\n分配边缘 Assign Edge：当资源已经被分配给进程，被称为分配边缘；如 $R_j\\ →\\ P_i$\n当请求被授予时，可以通过反转方向的线将请求边缘转换为分配边缘\n类型 示意图 Process Resource $P_i$ 请求的 $R_j$ 实例 $P_i$ 持有一个 $R_j$ 的实例\n也可以说 $R_j$ 被 $P_i$ 所持有 资源分配图 资源分配图 如图所示，资源类型为：","title":"ch11 死锁"},{"content":"Backgound 信号量 semaphores，是操作系统中非常重要的技术，通过使用一个简单的整数值来管理并发进程，信号量只是一个在线程之间共享的整数变量。该变量用于解决临界区问题并实现进程同步。 信号量具有两个原子操作：\nP()：sem减一，如果sem\u0026lt;0，等待；否则继续 V()：sem加一，如果sem≤0，唤醒一个等待的P； Semaphore 信号量的使用 型号量的特点：\n两个类型信号量\n二进制信号量 Binary Semaphore：也称为互斥锁。它只能有两个值0和1。它的值被初始化为1。它用于实现多进程临界区问题的解决。\n计数信号量 Counting Semaphore：值可以跨越一个不受限制的域（可以取任何非负数）。它用于控制对具有多个实例的资源的访问。\n信号量是被保护的变量\n初始化完成后，唯一改变一个信号量的值的办法是通过P() 和 V()\n操作必须是原子\nP() 能够阻塞，V() 不会阻塞\n信号量可以用在2个方面\n互斥\n条件同步(调度约束 —— 一个线程等待另一个线程的事情发生)\n信号量实现的互斥 c 1 2 3 4 5 6 7 mutex = new Semaphore(1); mutex-\u0026gt;P(); // 临界区前p ... critical section ... mutex-\u0026gt;V(); // 临界区后v 信号量实现调度约束 c 1 2 3 4 5 6 7 8 9 10 condition = new Semaphore(0); // Thread A ... condition-\u0026gt;P(); // 等待线程B某一些指令完成之后再继续运行,在此阻塞 ... // Thread B ... condition-\u0026gt;V(); // 线程b执行某程度后，使用信号量增加唤醒线程A 信号量实现有界缓冲 在更复杂的同步场景下，用二进制信号量无法有效的解决问题，此时就需要计数信号量来完成这些功能；例如一个线程等待另一个线程处理事情\n比如生产东西或消费东西(生产者消费者模式)，互斥(锁机制)是不够的\n有界缓冲区的生产者-消费者问题\n一个或者多个生产者产生数据将数据放在一个缓冲区里 单个消费者每次从缓冲区取出数据 在任何一个时间只有一个生产者或消费者可以访问该缓冲区 在这里需要注意一些问题：\n正确性要求\n在任何一个时间只能有一个线程操作缓冲区(互斥)；可多个\n当缓冲区为空时，消费者必须等待生产者(调度，同步约束)\n当缓存区满，生产者必须等待消费者(调度，同步约束)\n每个约束用一个单独的信号量\n一个二进制信号量，互斥 两个计数信号量 一般信号量 fullBuffers 一般信号了 emptyBuffers c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class BoundedBuffer{ mutex = new Semaphore(1); fullBuffers = new Semaphore(0); //说明缓冲区初始为空 emptyBuffers = new Semaphore(n); //同时可以有n个生产者来生产 }; // 生产者 BoundedBuffer::Deposit(c){ emptyBuffers-\u0026gt;P(); // emptyBuff 操作 -1，当EB被阻塞时， mutex-\u0026gt;P(); // 操作buffer时，是互斥操作，需要使用pv Add c to the buffer; // 临界区 mutex-\u0026gt;V();\t// 操作buffer时，是互斥操作，需要使用pv fullBuffers-\u0026gt;V(); // FB初始值为0，通过通知机制可以通知消费者可以开始取数据 } // 消费者 BoundedBuffer::Withdraw(c){ // 消费者先执行，此时BF为0 会阻塞，等待先写后读 // 生产者先执行，初始FB为0，+1 此时会读取数据 fullBuffers-\u0026gt;P(); mutex-\u0026gt;P(); // 操作buffer时，是互斥操作，需要使用pv Remove c from buffer; // 临界区 mutex-\u0026gt;V();\t// 操作buffer时，是互斥操作，需要使用pv emptyBuffers-\u0026gt;V(); // 消费后会+1，使得EB不满，起到通知生产者继续写数据 } 管程 管程是一种解决进程间同步问题的程序结构，英文是 Monitors；管程通过编程语言级别的支持，实现进程间的互斥。管程包含了一些列共享变量，以及针对变量的共享函数的组合，在设计上管程定义了：\n锁 用锁来确保在任何情况下监视器中只有一个进程。 对共享数据提供互斥 0或者多个条件变量，用于管理对共享数据的并发访问 通过使进程进入睡眠状态的同时释放它们的锁，使线程在临界区内进入睡眠状态。 如下图所示：monitor是一种数据结构，用于将所有控制信息、时间信息和共享数据封装为一个整体。这个整体是对信号量的抽象，可以在其中定义互斥的控制语句。\n进入管程需要有队列（entry queue），是互斥的，首先要获得lock\n进入临界区后，执行函数对共享变量进行操作，这是在条件变量中\nlock\nLock::Acquire() 等待直到锁可用,然后抢占锁 Lock::Release() 释放锁,唤醒等待者如果有 Condition Variable\n允许等待状态进入临界区 允许处于等待(睡眠)的线程进入临界区 某个时刻原子释放锁进入睡眠 Wait() operation 暂停对任何条件变量执行等待操作的进程。挂起的进程被放置在该条件变量的块队列中。 Signal() operation(or broadcast() operation) 唤醒等待的进程，需要进程存在 c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Condition{ int numWaiting = 0; WaitQueue q; }; Condition::Wait(lock){ numWaiting++; Add this thread t to q; release(lock); schedule(); // 选择下一个进程执行，选择就绪进程执行 require(lock); } Condition::Signal(){ if(numWaiting \u0026gt; 0){ Remove a thread t from q; wakeup(t); // 唤醒进程，将睡眠进程置为就绪状态 numWaiting--; } } 使用monitor，抽象出一个管程，并用word满足管程的锁和条件变量，word将计时器和控制信息聚合了，只有初始化时的结构才能获得锁：\n**Wait() **：如果定义了成员变量，则等待函数获取互斥锁 **Signal() **：释放获取的锁，以便其他线程可以获取它。 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;math/rand\u0026#34; \u0026#34;strconv\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) // 一个管程 type Monitor interface { Wait() Signal() GetData() []string // 返回整个数组， PutData(string) //原子操作 } // 管程的实现 type Words struct { mutex *sync.Mutex wordsArray []string isInitialized bool } func (m *Words) Wait(p int) { if m.isInitialized { fmt.Printf(\u0026#34;process %d got a lock\\n\u0026#34;, p) m.mutex.Lock() } fmt.Printf(\u0026#34;process %d not get a lock\\n\u0026#34;, p) } func (m *Words) Signal(p int) { if m.isInitialized { fmt.Printf(\u0026#34;process %d release a lock\\n\u0026#34;, p) m.mutex.Unlock() } } func (m *Words) GetData() []string { return m.wordsArray } func (m *Words) PutData(word string, pn int) { m.Wait(pn) fmt.Printf(\u0026#34;start process %d \\n\u0026#34;, pn) // critical section m.wordsArray = append(m.wordsArray, word) time.Sleep(time.Millisecond * time.Duration(rand.Intn(800))) // critical section done fmt.Printf(\u0026#34;process %d done.\\n\u0026#34;, pn) m.Signal(pn) } func (m *Words) Init() { m.mutex = \u0026amp;sync.Mutex{} m.wordsArray = []string{} m.isInitialized = true } func main() { m := \u0026amp;Words{} m.Init() wg := \u0026amp;sync.WaitGroup{} wg.Add(10) for n := 0; n \u0026lt; 10; n++ { go func(i int) { defer wg.Done() m.PutData(\u0026#34;Angad\u0026#34;+strconv.Itoa(rand.Intn(100000)), i) }(n) } wg.Wait() fmt.Println(m.GetData()) } Reference\nmonitor types\nmonitor implement\nhttps://www.cs.utexas.edu/users/lorenzo/corsi/cs372h/07S/notes/Lecture12.pdf\nhttps://medium.com/algorithm-solving/os-synchronization-2-semaphore-and-classical-problems-of-synchronization-fdbbcb027b79\nquestion of synchronize Readers-Writers问题 Readers-Writers Problem 是允许多个进程读临界区，多个写者修改临界区；该问题的约束:\n允许同一时间有多个读者，但在任何时候只有一个写者 没有写者时，读者才能访问数据 没有读者和写者时，写者才能访问数据 在任何时候只能有一个线程可以操作共享变量 读进程\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 wait(mutex); // 修改计数器，因为保证计数器互斥，需要加锁 rc++; if (rc == 1) wait(wrt); // 保证不会有写进入 signal(mutex); // critical section // critical section END wait(mutex); // release rc rc--; if (rc == 0) // 计数器为0则代表已经无读进程， signal (wrt); // 释放读写锁 signal(mutex); 上面代码 mutex 和 wrt 是信号量，rc 是读进程计数器，初始化时为0\n写进程\ntext 1 2 3 wait(wrt); // critical section signal(wrt); 基于管程的实现\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 AR = 0; // # 活跃的读者进程 AW = 0; // # 活跃的写者进程 WR = 0; // # 等待的读者进程 WW = 0; // # 等待的写者进程 Condition okToRead; Condition okToWrite; Lock lock; //writer Public Database::Write(){ //Wait until no readers/writers; StartWrite(); write database; //check out - wake up waiting readers/writers; DoneWrite(); } Private Database::StartWrite(){ lock.Acquire(); // 写优先，存在任意读 写进程都将被阻塞直到满足条件 while((AW + AR) \u0026gt; 0){ WW++; okToWrite.wait(\u0026amp;lock); WW--;\t} AW++; lock.Release(); } Private Database::DoneWrite(){ lock.Acquire(); AW--; if(WW \u0026gt; 0){ okToWrite.signal(); // signal是唤醒一个 } else if(WR \u0026gt; 0){ okToRead.broadcast(); // broadcase是唤醒所有 } lock.Release(); } //reader Public Database::Read(){ //Wait until no writers; StartRead(); read database; //check out - wake up waiting writers; DoneRead(); } Private Database::StartRead(){ lock.Acquire(); while(AW + WW \u0026gt; 0){ //关注等待的writer,体现出写者优先 WR++; okToRead.wait(\u0026amp;lock); WR--; } AR++; lock.Release(); } private Database::DoneRead(){ lock.Acquire(); AR--; if(AR == 0 \u0026amp;\u0026amp; WW \u0026gt; 0){ //只有读者全部没有了,才需要唤醒 okToWrite.signal(); } lock.Release(); } 其他实现方式\n通常情况下为了保证读写问题，一般会通过互斥或信号量来实现。然而，go中提供了读写锁 sync.RWMutex 是为了解决这个问题的一种数据结构。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;math/rand\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) var mutex = new(sync.RWMutex) var cs = []string{} func writer(data string) { mutex.Lock() defer mutex.Unlock() cs = append(cs, fmt.Sprintf(\u0026#34;updated with %s\u0026#34;, data)) // Write to data. } func reader(data string) { fmt.Printf(\u0026#34;%s start execute.\\n\u0026#34;, data) mutex.RLock() defer mutex.RUnlock() fmt.Println(cs) time.Sleep(time.Millisecond * time.Duration(rand.Intn(800))) // Read from data. } func main() { wg := \u0026amp;sync.WaitGroup{} wg.Add(12) for i := 0; i \u0026lt; 2; i++ { go func(i int) { go writer(fmt.Sprintf(\u0026#34;writer thread %d\u0026#34;, i)) wg.Done() time.Sleep(time.Millisecond * time.Duration(rand.Intn(800))) }(i) } for i := 0; i \u0026lt; 10; i++ { go func(i int) { reader(fmt.Sprintf(\u0026#34;reader thread %d\u0026#34;, i)) wg.Done() }(i) } wg.Wait() } 哲学家就餐问题 哲学家就餐问题 dining philosophers problem；有五位哲学家，餐厅中间是一张圆桌，但只有五根筷子，每次吃饭需要两根筷子；当哲学家饿了就会拿起离自己最近的两根筷子；如果可以同时拿起离自己最近的两根筷子吃饭，吃完饭后，放下筷子思考。\n如何设计 如图所示首先，哲学家们处于的状态 思考\u0026mdash;\u0026mdash;拿筷子\u0026mdash;\u0026mdash;吃饭\u0026mdash;\u0026mdash;放下筷子\u0026mdash;\u0026mdash;思考的状态中变化。\n吃就是对临界区的访问，而如何拿筷子才是重点问题，而筷子则是 整个问题中的 Race Condition；可以将每根筷子互斥锁保护的共享对象，而在吃饭前，对其左右筷子进行加锁，两把锁都加成功，视为可以吃饭（访问临界区），吃完饭解锁筷子，进行思考\n共享数据有：\nBowl of rice(data set) Semaphone chopsticks [5] initialized to 1 步骤：\nthink(): pickUpChopsticks()： eating() PutDownChopsticks() cpp 1 2 3 4 5 6 7 8 9 10 11 12 #define N 5 // 哲学家数量 void philosopher(int i) { // 哲学家编号 while(TRUE) { think();\t// 思考 PickUpChopsticks(i); // 拿起左边的筷子 PickUpChopsticks((i+1)%N); // 拿起右边的筷子，筷子保证不大于哲学家数量 eat();\t// 吃饭 PutDownChopsti(i);\t// 放下左边的筷子 PutDownChopsti((i+1)%N); // 放下右边的筷子 } } 这样在哪左边筷子完成时，再拿右边筷子时发现都被占用拿不到，而又不满足吃饭条件，导致死锁。为了防止死锁问题需要进一步的判断\ncpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #define N 5 // 哲学家数量 void philosopher(int i) { // 哲学家编号 while(TRUE) { think();\t// 思考 PickUpChopsticks(i); // 拿起左边的筷子 if (chopsticks((i+1)%N)){ PickUpChopsticks((i+1)%N); // 拿起右边的筷子，筷子保证不大于哲学家数量 break; } else { // 不存在则放下左边筷子，并阻塞 PutDownChopsti(i);\t// 放下左边的筷子 wait() } eat();\t// 吃饭 PutDownChopsti(i);\t// 放下左边的筷子 PutDownChopsti((i+1)%N); // 放下右边的筷子 } } 互斥访问，可以解决但是同时只能一个哲学家就餐；这里将就餐看为临界区，而不是筷子，会造成筷子资源的浪费\ncpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define N 5 // 哲学家数量 void philosopher(int i) { // 哲学家编号 while(TRUE) { p(mutex) think();\t// 思考 PickUpChopsticks(i); // 拿起左边的筷子 PickUpChopsticks((i+1)%N); // 拿起右边的筷子，筷子保证不大于哲学家数量 eat();\t// 吃饭 PutDownChopsticks(i);\t// 放下左边的筷子 PutDownChopsticks((i+1)%N); // 放下右边的筷子 v(mutex) } } 为了防止死锁的发生，可以设置两个条件：\n必须同时拿起左右两根筷子； 只有在两个邻居都没有进餐的情况下才允许进餐。 这种就诞生了一个原则：要么不拿，要么拿两个： step1：thinking step2：Hungry step3：左右邻居正在就餐则等待，否则下一步 step4：拿起两个筷子 step5：eating step6：方向左边筷子 step7：方下右边筷子 step8：to step1 cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #define N 5 // 哲学家数量 #define LEFT (i + N - 1) % N // 左邻居 #define RIGHT (i + 1) % N // 右邻居 #define THINKING 0 #define HUNGRY 1 #define EATING 2 typedef int semaphore; int state[N]; // 跟踪每个哲学家的状态 semaphore mutex = 1; // 临界区的互斥，临界区是 state 数组，对其修改需要互斥 semaphore s[N]; // 同步信号量，每个哲学家一个信号量 void philosopher(int i) { while(TRUE) { think(i); // step1 take_two(i); // step2~4 eat(i);\t// step5 put_two(i); // step6~7 } } void take_two(int i) { P(\u0026amp;mutex); state[i] = HUNGRY; // 饿了 checkChopsticks(i); // 拿筷子 V(\u0026amp;mutex); // 离开临界区 P(\u0026amp;state[i]); // } void put_two(i) { P(\u0026amp;mutex); state[i] = THINKING; // 尝试通知左右邻居，自己吃完了，你们可以开始吃了 checkChopsticks(LEFT); checkChopsticks(RIGHT); V(\u0026amp;mutex); } void eat(int i) { down(\u0026amp;mutex); state[i] = EATING; up(\u0026amp;mutex); } // 检查两个邻居是否都没有用餐，如果是的话，就 up(\u0026amp;s[i])，使得 down(\u0026amp;s[i]) 能够得到通知并继续执行 void checkChopsticks(i) { // 第一个，当前哲学家饿了 // 左边和右边都没有在吃饭 if(state[i] == HUNGRY \u0026amp;\u0026amp; state[LEFT] != EATING \u0026amp;\u0026amp; state[RIGHT] !=EATING) { state[i] = EATING; V(\u0026amp;s[i]); } } 具体的实现：\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 package main import ( \u0026#34;log\u0026#34; \u0026#34;math/rand\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) const ( THINKING = iota HUNGRY EATING ) type chopstick struct { sync.Mutex } type Philosopher struct { Id int Left *chopstick Right *chopstick State int } func init() { log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) } // 哲学家 var ph = []string{\u0026#34;Mark\u0026#34;, \u0026#34;Russell\u0026#34;, \u0026#34;Rocky\u0026#34;, \u0026#34;Haris\u0026#34;, \u0026#34;Root\u0026#34;} // 同时可以吃饭的哲学家数量 var host = make(chan int, int(len(ph)/2)) var wg sync.WaitGroup func (p *Philosopher) think() { log.Printf(\u0026#34;Philosopher %s start thinging.\\n\u0026#34;, ph[p.Id]) time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000))) } func (p *Philosopher) hungry() { log.Printf(\u0026#34;Philosopher %s has hungry.\\n\u0026#34;, ph[p.Id]) time.Sleep(time.Millisecond * time.Duration(rand.Intn(500))) } func (p *Philosopher) pickUpChopsticks() { // 两个筷子被锁，则阻塞 p.Left.Lock() p.Right.Lock() log.Printf(\u0026#34;Philosopher %s pick up two chopsticks.\\n\u0026#34;, ph[p.Id]) } func (p *Philosopher) eating() { // 有两个哲学家在吃，阻塞 host \u0026lt;- p.Id p.State = EATING p.pickUpChopsticks() log.Printf(\u0026#34;Philosopher %s begin eating.\\n\u0026#34;, ph[p.Id]) time.Sleep(time.Millisecond * time.Duration(rand.Intn(10000))) p.putDonwChopsticks() \u0026lt;-host } func (p *Philosopher) putDonwChopsticks() { p.Left.Unlock() p.Right.Unlock() log.Printf(\u0026#34;Philosopher %s put down two chopsticks.\\n\u0026#34;, ph[p.Id]) } func (p *Philosopher) seat() { for n := 0; n \u0026lt; 3; n++ { p.think() p.hungry() p.eating() } wg.Done() } func main() { // 创建五根筷子 ChopSticks := make([]*chopstick, 5) for i := 0; i \u0026lt; 5; i++ { ChopSticks[i] = new(chopstick) } // 创建五个哲学家 philosophers := make([]*Philosopher, len(ph)) for n := 0; n \u0026lt; len(ph); n++ { philosophers[n] = \u0026amp;Philosopher{ Id: n, Left: ChopSticks[n], Right: ChopSticks[(n+1)%len(ph)], State: THINKING, } } // 哲学家就坐 for i := 0; i \u0026lt; 5; i++ { wg.Add(1) go philosophers[i].seat() } wg.Wait() } 可以从执行结果看到，同时满足左右筷子都可以拿起的哲学家才可以进程吃\nReference\nread-write problom\nDining Philosophers Problem\n","permalink":"https://www.161616.top/ch10-semaphore-and-monitors/","summary":"Backgound 信号量 semaphores，是操作系统中非常重要的技术，通过使用一个简单的整数值来管理并发进程，信号量只是一个在线程之间共享的整数变量。该变量用于解决临界区问题并实现进程同步。 信号量具有两个原子操作：\nP()：sem减一，如果sem\u0026lt;0，等待；否则继续 V()：sem加一，如果sem≤0，唤醒一个等待的P； Semaphore 信号量的使用 型号量的特点：\n两个类型信号量\n二进制信号量 Binary Semaphore：也称为互斥锁。它只能有两个值0和1。它的值被初始化为1。它用于实现多进程临界区问题的解决。\n计数信号量 Counting Semaphore：值可以跨越一个不受限制的域（可以取任何非负数）。它用于控制对具有多个实例的资源的访问。\n信号量是被保护的变量\n初始化完成后，唯一改变一个信号量的值的办法是通过P() 和 V()\n操作必须是原子\nP() 能够阻塞，V() 不会阻塞\n信号量可以用在2个方面\n互斥\n条件同步(调度约束 —— 一个线程等待另一个线程的事情发生)\n信号量实现的互斥 c 1 2 3 4 5 6 7 mutex = new Semaphore(1); mutex-\u0026gt;P(); // 临界区前p ... critical section ... mutex-\u0026gt;V(); // 临界区后v 信号量实现调度约束 c 1 2 3 4 5 6 7 8 9 10 condition = new Semaphore(0); // Thread A .","title":"ch10 信号量和监视器"},{"content":"Overview CPU调度 (cpu scheduling )，是决定在一个时间窗口内，哪个进程可以拥有CPU而另外一个个进程会被暂停的过程。CPU调度的作用是为了确保每当CPU空闲时，操作系统至少选择就绪队列中一个可用的进程执行。这个选择过程将由CPU调度器来执行。\n调度程序：挑选就绪进程的内核函数\n调度策略：依据什么挑选进程？ 调度时机：什么时间进行调度？ 进程从运行状态切换到等待状态 进程退出 非抢占式：当前进程主从放弃CPU时， 抢占式：当前进程被抢占 时间片用完 进程从等待切换到就绪（当前就绪进程优先级高于当前运行进程） 调度准则 CPU的调度策略 抢占式调度 抢占式调度（Preemptive）在分配进程时有对应的优先级。而在另一个较低优先级进程之前运行具有较高优先级的进程很重要，即使较低优先级的进程仍在运行。较低优先级的进程扔会等待一段时间，让较高优先级的进程完成执行后恢复。\n抢占式调度主要发生在运行状态切换到就绪或等待状态\n非抢占式调度 非抢占式调度 （Non-Preemptive），在这种类型的调度中，一旦将资源（CPU 周期）分配给一个进程，该进程就会持有CPU使用权，直到它被终止或达到等待状态。\n抢占式调度主要发生在运行状态终止的情况下\n如何确定调度是抢占式还是非抢占式？ 一般来讲，确定调度的方式是通过以下四点来确定的：\n当进程从运行状态切换到等待状态；如I/O请求或调用 wait() 系统调用 当进程从运行状态切换到就绪状态；如响应中断。 当进程从等待状态切换到就绪状态；如在 I/O 完成或从 wait() 返回时。 进程完成执行并终止； 如果调度发生在1 4情况下，则为非抢占式，否则为抢占式\n程序执行模型 需要关注的是进程在计算机系统中运行时存在什么状态？\n几乎所有进程都在一个连续的循环的两种模型之间交替：即CPU突发和I/O突发中交替\n每个调度决定都是关于在下一个CPU突发时将哪个工作交给CPU 在时间分片机制下，线程可能在结束当前CPU突发前被迫放弃CPU CPU突发和 I/O突发的交替序列逻辑\rCPU 突发型的持续时间\r调度指标 在了解评价指标前，需要对CPU调度中的一些术语需要了解\nCPU突发 （Burst Time BT）：进程开始执行的时间，从到达到开始执行花费的时间 到达时间 （Arrival Time AT）：进程到达就绪队列的时间 完成时间（End Time ET 或 Completion Time CT）：进程执行完成的时间 等待时间 （Waiting Time WT）：进程在就绪队列中等待轮到 CPU 占用的时间；$WT = TT - BT$ 周转时间 （Turnaround Time TT）：完成时间和到达时间的差 $TT=CT-AT$ 相应时间（Response Time RT）：开始响应请求所需的时间。第一次请求到相应的时间。 吞吐量 (Throughput)：单位时间内完成的进程数。$Throughput = (Number\\ of\\ processes\\ completed) \\div (Time\\ unit)$ 一般情况下，需要的服务”越快“越好，而快的定义：\n传输文件的高带宽（相应时间） 玩游戏的低延迟（吞吐量） 上述两个因素是互相独立的 需要对低延迟和高带宽做一个平衡\n比较算法的准则 相应时间目标：通过低延迟的调度改善用户的交互体验 减少相应的时间：及时处理用户的输出并且尽快将输出提供给用户 减少平均相应时间的波动：在交互式系统中，可预测性比高差异低平均更重要 相应时间是操作系统的计算延迟 吞吐量目标：操作系统应该保证吞吐量不受用户交互的影响 增加吞吐量，增加系统吞吐量大概可以从两个角度来提出 减少开销（上下文切换） 系统资源的高效利用（CPU，I/O） 减少等待时间 吞吐量是操作系统的计算带宽 公平的目标 在整个操作系统进程管理中的调度挑战在于如何使整个系统尽可能地“高效”与“公平”（efficient\u0026quot; and fair），这受制于不断变化且通常是在动态的条件下，而“高效”和“公平”在某种程度上是主观的，经常会受到不断变化的抢先策略而被影响。\n而公平的定义是可以让每个进程等待相同的时间，如：\n保证每个进程占用相同CPU的时间 如果一个用户比其他用户运行更多的进程的情况下该如何处理？ 而保证公平带来的则通常会增加平均的相应时间\n调度算法 基本调度算法 先到先服务 先来先服务 (First Come First Serve FCFS)，是最简单的调度算法，FCFS是根据进程的到达时间进行调度；即表示，先请求 CPU 的进程先分配CPU。它是通过使用FIFO 队列来实现的。当一个进程进入就绪队列时，该进程的 PCB 会被放置到队列的尾部。当CPU空闲时，会将队列头部。然后从队列中删除正在运行的进程。FCFS 是一种非抢占式调度算法。\n实例：假设有三个进程按以下顺序到达：P1 , P2, P3；CPU突发时间为 24 3 3\nProcess Burst Time P1 24 P2 3 P3 3 那么对应的甘特图是：\n那么衡量指标的信息如：\r等待时间WT为：P1=0；P2=24；P3=27 平均相应AT时间为: $(0 + 24 + 27)\\div3 = 17$ 这里存在一个问题，如果第一个进程执行时间过长，会导致后面所有的进程等待时间过长，这样会拖长整个队列的平均相应时间。这种现象被称为护航效应 （Convoy Effect）。\n护航效应，在调度算法中当CPU密集型（CPU-bound）进程在 I/O密集型（ I/O-bound）进程之前到达系统时，即使 I/O密集型进程需要较少的CPU时间，此时CPU密集型进程也会根据FCFS的策略而获得CPU时间。\n护航效应类比图\r为了更有效的避免护航效应，可以使用抢占式的调度算法，如RR；抢占式调度算法可以使每个进程都有相同的机会使用CPU。而这些较小的进程占用很短的CPU时间，而不必等待很长的CPU时间，从而加快执行速度并减少闲置资源。\n例如假设执行顺序按照：P2 , P3, P1，那么整个的执行队列如下图\n此时平局等待时间WT为： P1 = 6;P2 = 0; P3 = 3\n平均等待时间为：$(6 + 0 + 3)\\div3 = 3$\n缺点:\n平均等待时间波动较大 花费时间少的任务可能排在花费时间长的任务后面 可能导致IO和CPU之间的重叠处理(CPU密集型进程会导致IO设备闲置时，IO密集型进程也在等待) 最短作业优先 最短作业优先 （Shortest Job First SJF），是一种非抢占式的调度算法，SJF会首先调度具有最短CPU Brust的进程。如果两个进程具有相同的BT，则使用FCFS来打破平局。其特点为：\n对每个进程关联其下一个CPU Brust的大小，使用这些长度来排序以最短会被优先调度 SJF最大的优势是，给定一组进程的最短平均等待时间 AT SJF难度在于如何知道下一个 CPU请求的长度 如：假设有三个进程按以下顺序到达：P1 , P2, P3，P4；CPU突发时间为 6 8 7 3，将其排好序后如图\nProcess Burst Time P1 6 P2 8 P3 7 P4 3 gantt\rdateFormat YYYY-MM-DD\raxisFormat %S\rtitle SJF scheduling chart\rP4 :p1, 2014-01-01, 3s\rP1 :p2, after p1, 6s\rP3 :p3, after p2, 7s\rP2 :p4, after p3, 8s 此时平均相应时间为 $(P_{1}+P_2+P_3+P_4)\\div4$ = $(3 + 16 + 9 + 0) \\div 4 = 7$ 。\n此时如果将某一个进程的顺序作为调整，如：P4 调整到 P3之后，会减少对应的平均相应时间吗？对应的gantt如下：\ngantt\rdateFormat YYYY-MM-DD\raxisFormat %S\rtitle SJF scheduling chart\rP1 :p1, 2014-01-01, 6s\rP3 :p2, after p1, 7s\rP4 :p3, after p2, 3s\rP2 :p4, after p3, 8s 此时平均相应时间为 ：（Pi 为进程的运行时间，Ri 为进程的TT）\n$((P_1-R_4)+(P_3-R_4)+2(P_1+P_3-2\\times P_4)+P_2)\\div4$ $((6-3)+(7-3)+2(6+7 - 2\\times3)+8) \\div 4 = 7.25$ SJF存在的问题：\n饥饿 ；饥饿（starvation）又被称为无限阻塞（indefinite blocking）,\n连续的短任务流会使场任务饥饿 短任务可用时的任何长任务的CPU时间都会增加增加整个队列的平均等待时间 需要预知进程的执行时间\n怎么预估下一个CPU突发的持续时间 简单的解决：询问用户 如果用户欺骗就杀死进程 如果不知道怎么办? 如何确定下一个进程的CPU Brust\n只能通过预估的方式确定进程的执行长度，可以通过使用历史的CPU Brust的长度，使用其平均指数来预估，这里就有几个参数：\n$t_n$：第N点的CPU Brust的实际值 $\\tau_{n}$：第N点的CPU Brust的预估值，。 $\\alpha$：是一个因子，控制整个历史相对的权重，$0 \\leq \\alpha \\leq 1$，通常情况下 $\\alpha$ 设置为 ${1 \\over 2}$ $\\alpha = 0$，则 $\\tau^{n+1} = \\tau^n$，初始预估值永远没有变化 $\\alpha = 1$，则 $\\tau^{n+1} = \\tau^n$，初始预估值始终根据第几个进程的实际值的变化而变化 当$\\alpha = {1 \\over 2}$，则历史和最近的权重相同 Τ0：是一个常数或整个系统的平均值。 得到一个公式：\n$\\tau_{n} = \\alpha t_n+(1-\\alpha)\\tau_{n}$\n$\\tau_{n+1} = \\alpha t_n+(1-\\alpha)\\tau_{n-1} + (1-\\alpha)^2\\tau_{n-2}\u0026hellip;.(1-\\alpha)^j\\tau_{n+1}$\n实例题：$\\tau_1=10$，$\\alpha=5$，之前执行的队列为8,7,4,16，接下来预估值是多少？\nA. 9\tB. 8\tC. 7.5\tD. None\n解析：因为是SJF算法，及历史执行的序列为 [4, 7, 8, 16]，并且已知 $\\tau_1=10$ $t_1=4$ $\\alpha=0.5$\n$\\tau_2 = \\alpha \\times t_{n-1} +(1-\\alpha)\\tau_{n-1}$ = $0.5\\times4+0.5\\times 10=7$ 因为$t_1=4$，$\\tau_1=10$\n$\\tau_3 = \\alpha \\times t_{n-1} +(1-\\alpha)\\tau_{n-1}$ = $0.5\\times7+0.5\\times 7=7$\t因为$t_2=7$，$\\tau_2=7$\n$\\tau_4 = \\alpha \\times t_{n-1} +(1-\\alpha)\\tau_{n-1}$ = $0.5\\times 8+0.5\\times 7=7.5$\t因为$t_3=8$，$\\tau_3=7$\nReference\nhandout scheduling\nSJF Quiz\n最高响应比优先 HRRN（Highest Response Ratio Next）调度算法是操作系统中的一种非抢占式调度算法。HRRN是对SJF的改进版，从而减少饥饿问题。\nHRRN的特点：\n非抢占式 对进程增加了关注点：进程等待了多长时间 防止饥饿问题（无限期延后） $RR = (W+S)\\div S$\nRR：响应比，Response Ratio\nW：表示等待时间。\nS：表示服务的时间，即Burst Time或执行时间。\nHRRN是综合考虑了执行时间和等待时间，如：假设有三个进程按以下顺序到达：P1 , P2, P3，P4；CPU突发时间为 6 8 7 3，将其排好序后如图\nProcess Burst Time Arrival Time P1 3 1 P2 6 3 P3 8 5 P4 4 7 P5 5 8 time=0 时，就绪队列为空，0 到 1 为是 CPU 空闲时间。 在 time=1 时，就绪队列仅有P1。因此，进程 P1 一直执行直到完成。 在进程P1之后，在 time=4 时只有进程 P2 到达，故进程 P2 被执行。 在 time=10 时，进程 P3、P4 和 P5 已全达到就绪队列中。故P2之后，需要计算响应率。 P3 $RR=(10-5+8)/8 = 1.625$ P4 $RR=(10-7+4)/4 = 1.75$ P5 $RR=(10-8+5)/5 = 1.4$ 此时P4响应率最高，故P4先执行，执行后需要计算P3和P5的顺序：\nP3 $RR=(14-5+8)/8 = 2.125$ P5 $RR=(14-8+5)/5 = 2.2$ 此时整个队列中的执行周转时间和等待时间如下表：\n$TT=ET-AT$ $WT=TT-BT$ Process AT BT ET TT WT P1 1 3 4 3 0 P2 3 6 10 7 1 P3 5 8 27 22 14 P4 7 4 14 7 3 P5 8 5 19 11 6 这个队列的执行甘特图就如下：\n平均等待时间：$AWT=(0+1+14+3+6)\\div5 = 24\\div5=4.8$\nHRRN的特点 优点：\nHRRN教SJF有更好的性能。 HRRN可以减少较长进程的等待时间，同时也鼓励较短的作业。 增加了吞吐量，避免了饥饿现象 缺点：\nHRRN处于理想状态，因为无法提前预知每个进程的BT。 HRRN会增加CPU的开销。 轮训 轮训调度算法Round-Robin RR，是最简单的调度算法，即每个进程获得相同的CPU时间量子（quantum）并轮流执行\n如：假设有三个进程按以下顺序到达：P1 , P2, P3，P4；CPU突发时间为 53 8 68 24，时间片为20\nProcess BT P1 53 P2 8 P3 68 P4 24 这四个进程轮流占用CPU，那么执行的甘特图如下：\ngantt\rdateFormat YYYY-MM-DD\raxisFormat %j\rtitle RR Gantt chart\rP1 [0,20] :active,p1, 2020-01-01, 20d\rP2 [20,8] :active,p2, after p1, 8d\rP3 [28,48] :active,p3, after p2, 20d\rP4 [48,68] :active,p4, after p3, 20d\rP1 [68,88]\t:active,p5, after p4, 20d\rP3 [88,108]\t:active,p6, after p5, 20d\rP4 [108,112]\t:active,p7, after p6, 4d\rP1 [112,125]\t:active,p8, after p7, 13d\rP3 [125,145]\t:active,p9, after p8, 20d\rP3 [145,153]\t:active,p10, after p9, 8d\ridel [153,]\t:done, p11, after p10, 10d 等待时间 WT：\n$P_1 = (68-20)+(112-88)=72$ $P_2=20-0=20$ $P_3=(28-0)+(88-48)+(125-108)=85$ $P_4=(48-0)+(108-68)=88$ 平均等待时间 AWT：\n$AWT=(72+20+85+88)\\div4=66.25$ 性能 q 较大时，类似于FIFO q较小时，上下文切换此时很多，所以q需要设置的较大些 通常情况下，平均周转率要优于SJF，也不会出现饥饿状态 RR的特点 优点：\n避免了饥饿或护航现象 所有进程获得量子都是公平的 没有优先级 不依赖Brust time 缺点：\n花费更多的时间在上下文切换上 性能取决于量子的大小 不能设置优先级 如何设置一个平均大小的量子非常困难 多级反馈队列 多级反馈队列（Multilevel Feedback Queue）是指，当进程进入系统时被永久分配给相应队列。进程不会在队列之间移动。\n就绪队列被划分为不同的队列：\n前台（交互式 interactive）\n后台（批处理 batch）\n每个队列是对应的调度算法：\n前台 – RR 后台 - FCFS 调度必须在队列之间进行：\n固定的调度优先级：如：先前台，再后台 会存在饥饿现象 时间分片：\n每个队列获得一定的时间分片，并在进程间调度 如 RR - 80%前台，FCFS - 20%后台 多级反馈队列通过历史来预测未来，克服了SJF的预测问题；如果进程过去是I/O密集型的，那么未来也可能发生I/O密集型。通过利用这种行为，调度器可以选择使用最少CPU的进程进行调度。\n拥有不同优先级的队列，如RR 一次结束后，将在下一个高优先级的队列进行作业 循环的时间片随着优先级的降低而增长 优先级的调整：\n进程开始在最高优先级队列中执行 当时间片结束，降低优先级 当时间片没结束（发生CPU上下文切换是I/O事件）则提高优先级，直到最高优先级队列 CPU密集型的优先级下降，I/O密集型的优先级上升 公平共享调度 公平共享调度 Fair-share scheduling，是将处理器的时间平均分配给用户，在用户级别公平分享CPU；如有5个用户同时执行一个进程，调度器会将同等份额的 CPU 周期，分配给每个用户，即每个用户20%\n一些用户组比其他用户组更重要 保证不重要的组无法垄断资源 未使用的资源按照每个组所分配的资源的比例来分配 没有达到资源使用率目标的组获得更高的优先级 实时调度 实时操作系统 RTS，正确性依赖于其时间和功能两方面的一个操作系统；RTS调度算法主要目标是关注满足任务期限，而不是对任务吞吐量、延迟和响应时间等指标。\n举个例子：喂养不同种类的动物，如马和奶牛。\n马，每20分钟喂养一次，一次需要10分钟 奶牛，每50分钟喂养一次，每次25分钟 如果使用rr，第一次喂马10分钟，第二次喂牛25分钟，第三次喂马，此时喂养时间不够，马死了 使用固定时间片喂养的话，喂马10分钟，喂牛10分钟，牛喂养时间不足，牛死了 EDF：截止日期最早优先 喂马10分钟 喂牛10分钟 到达第三次，需要又需要喂马，10分钟 第四次，喂牛15分钟，此时时间为45，满足牛和马的喂养时间，使用EDF，可以保障到牛和马都不会死 性能指标： 时间约束的及时性 deadline 速度和平均性能相对不重要 主要特征：时间约束的可预测性 RTS分类: 硬实时系统 Hard RTS：需要在保证时间内完成重要的任务；必须完成，错过最后期限deadline，将对整个系统产生灾难性的故障。 软实时系统 Soft RTS: 要求重要的进程的优先级更高，尽量完成，并非必须 RTS相关时间参数 $a_j$ ：Arrival Time，工作准备好执行的时间。 $C_j$：Computation (execution) time，任务在处理器不中断的情况下执行所需要的时间 $d_j$：Absolute deadline，绝对截止时间，工作应该完成的时间 $D_j$：Relative deadline，到达时间和绝对截止时间之间的长度 $S_j$：Start Time，进程开始执行的时间 $f_j$：Finishing Time，进程完成执行的时间 $R_j$：Response time，作业到达后执行时间的长度，$R_j=f_j-a_j$ 实时系统要求 决定任务执行的顺序 静态优先级调度 动态优先级调度 EDF Earliest Deadline First (EDF)，截止日期最早优先，EDF使用进程的优先级进行调度。它根据绝对终止期限为进程分配优先级。截止日期最近的任务获得最高优先级。\n有五个进程，如表：\nProcess $a_j$ $C_j$ $d_j$ J1 0 5 6 J2 2 2 8 J3 8 6 20 J4 10 3 14 J5 15 4 22 那么其执行甘特图如下\nEDF特点：\n最佳的动态优先级调度算法 deadline越早优先级越高 先执行deadline最早的任务 RM 速率单调 Rate-Monotonic 算法是指，周期较小的任务具有较高的优先级。（$C_i, T_i, D_i$）\n$T_i$ ：周期任务\nt1 = (1, 6, 6), t2 = (2, 8, 8), t3 = (4, 12, 12) RM特点：\n提前进行优先级排序 适合静态优先调度 周期越短，优先级越高 先执行周期最短的任务 Reference\nReal-Time Scheduling\n多处理器调度 多处理器调度，即存在多个 CPU可用，然而，与单处理器调度相比，多处理器调度更复杂。\n多处理器调度方法 非对称多处理，Asymmetric multiprocessing AMP 一个系统只允许一个CPU执行代码，如一个处理器处理用户代码，其他处理器处理I/O 对称多处理，Symmetrical Multi-Processing SMP， 每个处理器是自调度的，即单独的就绪队列或者公共就绪队列，通过调度器分配要执行的程序 亲和度 亲和度 Affinity 是指，进程在一个处理器上运行时，当发生转移时，需要释放对应的缓存，在新处理器上在加载，此时增加了系统的相应速度，通常情况下，SMP会视图避免进程从一个处理器迁移至另一个处理器上。\n软亲和度，Soft Affinity，是调度器尽可能长时间地将进程保持在同一个 CPU 上。这只是一种尝试；如果不可行，则将进程迁移到另一个处理器 硬亲和度，hard affinity，硬亲和度是利用系统调用 (system call)，强行将进程绑定到指定的CPU上 负载均衡 负载均衡是为了使多个CPU尽可能均衡的处理任务，即出现在SMP的现象，负载均衡可以保持所有处理器之间的工作负载平衡，以充分利用多个处理器的；否则就出现一个或多个处理器将处于空闲状态，而其他处理器具有高工作负载状态。\n负载均衡的通用方法：\n推送迁移：是指操作系统定期检查每个处理器上的负载。如果存在不平衡，一些进程将从一个处理器移动到另一个处理器。 拉取迁移：调度器发现一个处理器的运行队列中没有更多进程时。会从繁忙的处理器中拉出等待的任务 Reference\nLoad Balancing\ncpu affinity\nmultiple-processor-scheduling\nwhat are the types of process affinity\n优先级反转 由于低优先级任务的干扰而导致的任务执行延迟称为优先级反转 priority inversion，优先级反转是1997年火星拓荒者着陆的一个问题。\n如图所示：高优先级任务与低优先级任务共享资源时。当低优先级任务锁定资源时，高优先级任务必须等待，即使高优先级任务有资格运行。\n如图所示，LP任务使用对共享资源加锁，t2时间HP发生抢占，此时因为LP没有对锁释放，HP处于阻塞状态，而MP发生抢占直到t5结束，此时CPU回到LP任务，直到t6结束，释放锁，此时HP才开始执行，即使HP处于高优先级也不会被执行。\n解决的方法：\n优先级继承 Priority Inheritance 低优先级任务持有高优先级请求的资源，则低优先级任务将提高到与高优先级任务相同的优先级 如图所示：具有不同优先级的三个任务共享一个资源。LP首先在时间 t1 获取资源。在t2，MP抢占 LP到 t3，当MP它需要资源时。MP被阻止。此时，LP 提升优先级同MP并恢复执行。HP在 t4抢占 LP 任务。当 HP 访问共享资源时，在 t5 被阻止。此时LP 从 HP 继承其优先级并恢复执行。一旦 LP 完成，它的优先级立即降低到最初分配的级别。 优先级天花板 Priority ceiling 优先级天花板中，每个任务的优先级都是已知的。每个任务所需的资源在执行之前也是已知的。任何时候正在运行的任务的当前优先级上限是当时正在使用的所有资源的最高优先级上限。 Reference\nPriority Inversion\n","permalink":"https://www.161616.top/ch8-cpu-scheduling-algorithms/","summary":"Overview CPU调度 (cpu scheduling )，是决定在一个时间窗口内，哪个进程可以拥有CPU而另外一个个进程会被暂停的过程。CPU调度的作用是为了确保每当CPU空闲时，操作系统至少选择就绪队列中一个可用的进程执行。这个选择过程将由CPU调度器来执行。\n调度程序：挑选就绪进程的内核函数\n调度策略：依据什么挑选进程？ 调度时机：什么时间进行调度？ 进程从运行状态切换到等待状态 进程退出 非抢占式：当前进程主从放弃CPU时， 抢占式：当前进程被抢占 时间片用完 进程从等待切换到就绪（当前就绪进程优先级高于当前运行进程） 调度准则 CPU的调度策略 抢占式调度 抢占式调度（Preemptive）在分配进程时有对应的优先级。而在另一个较低优先级进程之前运行具有较高优先级的进程很重要，即使较低优先级的进程仍在运行。较低优先级的进程扔会等待一段时间，让较高优先级的进程完成执行后恢复。\n抢占式调度主要发生在运行状态切换到就绪或等待状态\n非抢占式调度 非抢占式调度 （Non-Preemptive），在这种类型的调度中，一旦将资源（CPU 周期）分配给一个进程，该进程就会持有CPU使用权，直到它被终止或达到等待状态。\n抢占式调度主要发生在运行状态终止的情况下\n如何确定调度是抢占式还是非抢占式？ 一般来讲，确定调度的方式是通过以下四点来确定的：\n当进程从运行状态切换到等待状态；如I/O请求或调用 wait() 系统调用 当进程从运行状态切换到就绪状态；如响应中断。 当进程从等待状态切换到就绪状态；如在 I/O 完成或从 wait() 返回时。 进程完成执行并终止； 如果调度发生在1 4情况下，则为非抢占式，否则为抢占式\n程序执行模型 需要关注的是进程在计算机系统中运行时存在什么状态？\n几乎所有进程都在一个连续的循环的两种模型之间交替：即CPU突发和I/O突发中交替\n每个调度决定都是关于在下一个CPU突发时将哪个工作交给CPU 在时间分片机制下，线程可能在结束当前CPU突发前被迫放弃CPU CPU突发和 I/O突发的交替序列逻辑\rCPU 突发型的持续时间\r调度指标 在了解评价指标前，需要对CPU调度中的一些术语需要了解\nCPU突发 （Burst Time BT）：进程开始执行的时间，从到达到开始执行花费的时间 到达时间 （Arrival Time AT）：进程到达就绪队列的时间 完成时间（End Time ET 或 Completion Time CT）：进程执行完成的时间 等待时间 （Waiting Time WT）：进程在就绪队列中等待轮到 CPU 占用的时间；$WT = TT - BT$ 周转时间 （Turnaround Time TT）：完成时间和到达时间的差 $TT=CT-AT$ 相应时间（Response Time RT）：开始响应请求所需的时间。第一次请求到相应的时间。 吞吐量 (Throughput)：单位时间内完成的进程数。$Throughput = (Number\\ of\\ processes\\ completed) \\div (Time\\ unit)$ 一般情况下，需要的服务”越快“越好，而快的定义：","title":"ch8 CPU调度算法"},{"content":"Background 多进程作为现代操作系统的重要特性，交互则会引起同时对共享资源的访问，当这些资源访问不正确会出现冲突或产生不适当的输出（冲突、死锁、饥饿）；而在同步的基础上，进程被分为以下两种类型：\n独立进程 Independent Process 不和其他进程共享资源或状态 确定性，输入状态确定结果 可重现，能够重现起始条件，I/O 调度的顺序不重要 协作进程 Cooperative Process； 多进程共享资源或状态 不确定性 probabilistic 不可重现 不确定性和不可重现意味着bug可能是间歇性发生的\nCooperation 进程的互相影响，即进程间的合作（相互或破坏）；最简单的例子就是两个进程使用同一个文件，一个进程读，一个进程写。读进程的结果会被写进程所影响。\n进程需要合作的原因：\n资源共享：多个进程访问相同的数据 一台电脑，多个用户 一个银行存款余额,多台ATM机 嵌入式系统（机器人手臂和收的协调） 计算加速： I/O 和 CPU计算可重叠 多处理器 - 将任务分解为子任务并分布在不同的进程中，它通常可以更快地运行（也需要多个可共享的 CPU） 模块化：复杂的任务组织成单独的子任务，让不同的进程运行 大程序分成小程序 是系统易于扩展 程序可以调用函数fork()来创建一个新的进程\n操作系统需要分配一个新的并且唯一的进程ID 因此在内核中,这个系统调用会运行 new_pid = next_pid++; 翻译成机器指令: Load next_pid Reg1 STORE Reg1 new_pid INC Reg1 STORE Reg1 next_pid 假设两个进程并发执行\n如果next_pid等于100, 那么其中一个进程得到的ID应该是100, 另一个进程的ID应该是101, next_pid应该增加到102 可能在INC前进行了上下文切换, 最终导致两个进程的pid都是100,而next_pid也是101 无论多个线程的指令序列怎样交替执行,程序都必须正常工作\n多线程程序具有不确定性和不可重现的特点 不经过专门设计,调试难度很高 不确定性要求并行程序的正确性\n先思考清楚问题，把程序的行为设计清楚 切忌给予着手编写代码，碰到问题再调试 Race Condition 竞态条件是由操作系统软件中的同步错误。出现在进程试图同时执行两个或多个操作时，这是一种不希望出现的情况。\n怎么样避免竞态?\nAtomic Operator(原子操作)\n原子操作是指一次不存在任何终端或者失败的执行\n该执行成功结束 或者根本没有执行 并且不应发生任何部分执行的状态 假设设计一个程序，A和B两个进程互相竞争，一个进程使counter+1，另外一个进程使counter-1\nc 1 2 3 4 5 6 7 8 while (true) { /* produce an item in next produced */ while (counter == BUFFER_SIZE) ; /* do nothing */ buffer[in] = next_produced; in = (in + 1) % BUFFER_SIZE; counter++; } c 1 2 3 4 5 6 7 8 while (true) { while (counter == 0) ; /* do nothing */ next_consumed = buffer[out]; out = (out + 1) % BUFFER_SIZE; counter--; /* consume the item in next consumed */ } P1和P2指令的执行顺序不同，产生的结果也不同。可能存在P1执行完或P2先执行完，也可能永远执行不完\n临界区：程序中试图访问共享资源并可能导致竞态条件的区域称为临界区 Critical Section 互斥 Mutual Exclusion：如果一个进程在临界区并访问共享资源，则不允许临界区有其他进程处于临界区并访问共享资源。 死锁 Deadlock：两个或两个以上进程，互相等待完成特定任务，而最终没法将自身任务进行下去 有界等待 Bounded Waiting：在一个进程发出进入其临界区的请求后，在该进程的请求被批准之前，有多少个进程可以进入临界区是有限制的。因此，达到限制后，必须有授予权限的进程才能进入其临界区。此条件的目的是确保每个进程都有机会进入其临界区，从而没有进程永远饥饿。 饥饿 Starvation：一个可执行的进程，长期被调度器忽略，以至于虽然处于可执行状态却不被执行。 无忙等待 忙等待 busy-waiting：忙等待是指，进程在继续执行之前等待并不断的检查要满足的条件，例如说循环、锁；一般情况下忙等待分为两种\n消耗处理器的同时不断检查要满足的条件 不消耗处理器，当满足条件时，会被唤醒 在一些操作系统中，忙等待很低效，循环会浪费CPU资源。但通常情况下，解决忙等待的方法就是延迟；例如\ntext 1 2 3 while z is still in use do sleep(900) end 另外一种方式就是信号量的阻塞进程，即处于忙碌等待状态的进程被阻塞并放置在不消耗资源的等待队列中。一旦满足条件，该过程将重新启动并放置在就绪队列中。\nReference synchronization\nrace condition\nbusy waiting\n禁用硬件中断 如何保障临界区操作是原子的，只要不发生上下文切换，那么操作就是原子的，即禁用硬件中断 disable interupt instruction DI instruction\n进入临界区禁用中断 操作临界区代码 离开临界区启用中断 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Lock { int value = FREE; } Lock::Acquire() { Disable interrupts; # 禁用中断 while (value != FREE) { # 等待锁 Enable interrupts; Disable interrupts; } value = BUSY; Enable interrupts; } Lock::Release() { Disable interrupts; value = FREE; # 解锁 Enable interrupts; # 启用中断 } 缺点：\n一旦中断被禁用，线程就无法被停止\n整个系统都会为你停下来 可能导致其他线程处于饥饿状态 要是临界区可以任意长怎么办？\n无法限制响应中断所需的时间(可能存在硬件影响) 要小心使用，适合于较小的操作\n软件解决方案 屏蔽硬件中断简单有效，但受制于临界区执行时间，影响整个系统的效率。\nPeterson 一个满足两个进程进程Pi 和 Pj 之间互斥的经典的基于软件的解决方法(1981年)，Peterson算法；Peterson 算法是基于双进程的互斥访问，需要两个锁：\n一个使用 flag，是一个布尔数组 另外一个使用 turn 的int锁 而这两个锁都有可能出现死锁的情况：如\ntext 1 2 3 4 5 6 7 8 9 int turn = 0 turn = j do { while( turn != i); Critical Section turn = j; remainder section\t} while(1) 另外一个进程\ntext 1 2 3 4 5 6 7 8 9 int turn = 0 turn = i do { while( turn != j); Critical Section turn = i; remainder section\t} while(1) 满足了互斥，没满足progress（想进入临界区的进程），最终只有一个进程可以进入，无法进行流转。\nPeterson 算法基于两个锁的临界区问题的解决方案：\n一个使用 flag，是一个布尔数组；boolean flag[i] 初始化为false，即没有进程有兴趣进入临界区 另外一个使用 turn 的int锁；进入临界区的进程 c 1 2 3 4 5 6 7 8 9 10 int trun; boolean flag[]; do{ flag[i] = true; // 此时i想进入临界区 turn = j // 但是当前是i while(flag[j] \u0026amp;\u0026amp; turn ==j); critical section flag[i] = false; remander section } while(true); Peterson 算法可以解决上述单锁的问题：\n互斥是有保证：任何时候仅有一个进程可以访问临界区 进程有保证：不会阻止临界区外其他进程进入临界区 Peterson 算法的缺点：\n忙等待 仅限于两个进程 Dekker Dekker是另外一种临界区解决方法，Dekker从第五版才完整满足了所有的条件；dekker算法类似于Peterson 算法；下面是算法的实现：\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;math/rand\u0026#34; \u0026#34;time\u0026#34; ) var thread1wantstoenter = false // 进程是否在执行 var thread2wantstoenter = false // 进程是否在执行 var favouredthread int // 进入临界区的进程 var cs = 0 func main() { go thread1() go thread2() time.Sleep(time.Second * 20) } func thread1() { fmt.Printf(\u0026#34;thread %d Start execute\\n\u0026#34;, 1) for { fmt.Printf(\u0026#34;get a lock, %d \\n\u0026#34;, cs) thread1wantstoenter = true for thread2wantstoenter == true { fmt.Printf(\u0026#34;1 get a lock, thread %d also executing.\\n\u0026#34;, 2) if favouredthread == 2 { fmt.Printf(\u0026#34;current cs thread is %d also executing.\\n\u0026#34;, favouredthread) thread1wantstoenter = false for favouredthread == 2 { // 忙等待，一直等到当前临界区进程不为对方 } thread1wantstoenter = true } } fmt.Printf(\u0026#34;not get lock, begin update cs1.\\n\u0026#34;) cs = 1 time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000))) favouredthread = 2 thread1wantstoenter = false fmt.Printf(\u0026#34;thread %d has completed\\n\u0026#34;, favouredthread) fmt.Printf(\u0026#34;cs value with update %d \\n\u0026#34;, cs) } } func thread2() { fmt.Printf(\u0026#34;thread %d Start execute\\n\u0026#34;, 2) for { fmt.Printf(\u0026#34;get a lock, %d \\n\u0026#34;, cs) thread2wantstoenter = true for thread1wantstoenter == true { fmt.Printf(\u0026#34;2 get a lock, thread %d also executing.\\n\u0026#34;, 1) if favouredthread == 1 { fmt.Printf(\u0026#34;current cs thread is %d also executing.\\n\u0026#34;, favouredthread) thread2wantstoenter = false for favouredthread == 1 { // 忙等待，一直等到当前临界区进程不为对方 } thread2wantstoenter = true } } fmt.Printf(\u0026#34;not get lock, begin update cs2.\\n\u0026#34;) cs = 2 favouredthread = 1 time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000))) // 退出，标记着线程2已完成， thread2wantstoenter = false fmt.Printf(\u0026#34;thread %d has completed\\n\u0026#34;, favouredthread) fmt.Printf(\u0026#34;cs value with update %d \\n\u0026#34;, cs) } } 整个for部分是一个锁，如果其他进程没有占用临界区，则可以进入临界区；这样第一个 for保证了互斥，在两个进程都没有被标记时，至少有一个进程可以进入，这样保证了progess。\n再假设，thread1永远卡在thread2wantstoenter == true；最终thread2会退出favouredthread = 1；这样的话不存在死锁，最终会脱离循环，脱离后会将自己设置为true thread1wantstoenter = true；这样的话，只要对方（thread2）为false了，即结束临界区访问；那么下一次循环将退出锁部分，并且可已访问临界区\n如果不对 favouredthread == counterpart 进行判断，那么就会出现饥饿现象。\nReference\ndekker algorithm\nwikipedia\nbakery bakery算法是针对N个进程互斥提出的解决方法之一；\n每当有进程进入临界区时，会被分配一个数 拥有最小数的进程会被选入临界区； 如果进程Pi 和 Pj 被分配相同的数，并且 $i\u0026lt;j$，那么进程Pi 首先进入临界区，进程编号 i j不会重复 定义操作符号 \u0026lt; 判断 $(a,b) \u0026lt; (c,d)$；当 $a\u0026lt;c$即 $(a,b)\u0026lt;(c,d)$; 如果 $a=c$，那么则判断b和d 定义操作函数 max() $max(a_0,\\ \u0026hellip;,\\ a_{n-1})$，是整个序列 $(a_0,\\ \u0026hellip;,\\ a_{n-1})$ 中的一个数 k，使 $k \u0026gt; a_i$； for i=0,... n-1 定义共享数据 boolean choosing[n] int number[n] 初始值分别为false和0 数的分配以递增顺序产生 1 2 3 4 5\u0026hellip;. 需要满足的条件，当一个线程想要进入临界区时，它必须确保它具有最小的数字，但是还需：\n线程状态不为真，即已经完成选号，在进程数组中，并且状态为false 如果线程编号相同，那么最小id的可以进入，即 id和index比谁小 id是当前的id，index是列表中其他的线程 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;math\u0026#34; \u0026#34;math/rand\u0026#34; \u0026#34;time\u0026#34; ) var choosing []bool var number []int var cs int func thread(id int) { var maximum int time.Sleep(time.Millisecond * time.Duration(rand.Intn(500))) // 19-25 线程i开始选择号码，为maximum+1 choosing[id] = true maximum = 0 for i := range number { maximum = int(math.Max(float64(maximum), float64(i))) } number[id] = maximum + 1 choosing[id] = false for i := range number { if i != id { // 此时进程j进入临界区但没有选号完成则i进行忙等待等待选号完成 for choosing[id] { // 忙等待 fmt.Printf(\u0026#34;thread %d busy-waiting 1. \\n\u0026#34;, id) } // 当一个线程想要进入临界区时，必须确保它具有最小的数字（优先级最高） // 当前线程 必须为最小，即number[id] \u0026gt; number[i]需要忙等待 // 如果线程获得相同编号 ，id低的可以抢先 即 (number[id] == number[i] \u0026amp;\u0026amp; id \u0026gt; i) 需要阻塞 for number[i] != 0 \u0026amp;\u0026amp; (number[id] \u0026gt; number[i] || (number[id] == number[i] \u0026amp;\u0026amp; id \u0026gt; i)) { // 忙等待 fmt.Printf(\u0026#34;thread %d busy-waiting 2.\\n\u0026#34;, id) } } } // 即所有的id全部等于0就是没有其他进程抢占。就可以进入临界区 // 临界区 fmt.Printf(\u0026#34;critical section used by thread %d \\n\u0026#34;, id) cs = id fmt.Printf(\u0026#34;critical section has been modified to %d \\n\u0026#34;, cs) // 退出临界区 time.Sleep(time.Millisecond * time.Duration(rand.Intn(100))) number[id] = 0 } func main() { number = make([]int, 6) choosing = make([]bool, 6) for n := 1; n \u0026lt;= 5; n++ { number[n] = n choosing[n] = false go thread(n) } time.Sleep(time.Second * 5) } python的实现\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import threading import random import time class BakeryAlgorithm(): # declaration and initial values of global variables # ticket for threads in line, n - number of threads tickets = [0,1,2,3,4] # True when thread entering in line entering = [False]*5 def lock(self,*args): self.entering[args[0]] = True maximum = 0 for ticket in self.tickets: maximum = max(maximum, ticket) self.tickets[args[0]] = maximum+1 self.entering[args[0]] = False for i in range(len(self.tickets)): if i != args[0]: # Wait until thread j receives its number: while self.entering[i]: print(\u0026#34;waiting %d\u0026#34; % (args[0])) # Wait until all threads with smaller numbers or with the samenumber, but with higher priority, finish their work: while self.tickets[i] != 0 and (self.tickets[args[0]] \u0026gt; self.tickets[i] or (self.tickets[args[0]]==self.tickets[i] and args[0])\u0026gt;i): print(\u0026#34;waiting %d 2\u0026#34; % (args[0])) # The critical section goes here... print(f\u0026#34;critical section used by process{args[0]}\u0026#34;) #exit section self.tickets[args[0]] = 0 def main(self): # Running all the 5 processes using thread module and passing process index as args since Thread supports args and kwargs argument only t1 = threading.Thread(target = self.lock, args = (0,)) t2 = threading.Thread(target = self.lock, args = (1,)) t3 = threading.Thread(target = self.lock, args = (2,)) t4 = threading.Thread(target = self.lock, args = (3,)) t5 = threading.Thread(target = self.lock, args = (4,)) t1.start() t2.start() t3.start() t4.start() t5.start() if __name__ == \u0026#34;__main__\u0026#34;: b = BakeryAlgorithm() b.main() 互斥：有没有可能2个以上进程同时进临界区 最小的id才可进入临界区 如果多个id拿到同样最小号，那么他们的进程id也不一样，进程id最小的可以进入临界区 上述保证了互斥 有界等待：等待的进程不超过$n-1$；可以保证每个进程都能进入临界区 progress：想进入临界区的进程，不想进入的number=0也会被驱逐 Reference\nPython Implementation of Bakery Algorithm\nbakery algorithm\n更高级抽象 硬件提供了一些原语\n像中断禁用, 原子操作指令等 大多数现代体系结构都这样 操作系统提供更高级的编程抽象来简化并行编程\n例如，锁，信号量 从硬件原语中构建 锁是一个抽象的数据结构\n一个二进制状态(锁定,解锁),两种方法 Lock::Acquire() 锁被释放前一直等待,然后得到锁 Lock::Release() 锁释放,唤醒任何等待的进程 使用锁来编写临界区\n前面的例子变得简单起来:\ntext 1 2 3 lock_next_pid-\u0026gt;Acquire(); new_pid = next_pid++; lock_next_pid-\u0026gt;Release(); 大多数现代体系结构都提供特殊的原子操作指令\n通过特殊的内存访问电路 针对单处理器和多处理器 Test-and-Set 测试和置位\n从内存中读取值 测试该值是否为1(然后返回真或假) 内存值设置为1 交换\n交换内存中的两个值 text 1 2 3 4 5 6 7 8 9 10 11 bool TestandSet(bool *target){ bool rv = *target; *target = true; return rv; } void Exchange(bool *a, bool *b){ bool tmp = *a; *a = *b; *b = tmp; } 总结\n锁是更高等级的编程抽象\n互斥可以使用锁来实现 通常需要一定等级的硬件支持 常用的三种实现方法\n禁用中断(仅限于单处理器) 软件方法(复杂) 原子操作指令(单处理器或多处理器均可) 可选的实现内容:\n有忙等待 无忙等待 ","permalink":"https://www.161616.top/ch9-synchronization/","summary":"Background 多进程作为现代操作系统的重要特性，交互则会引起同时对共享资源的访问，当这些资源访问不正确会出现冲突或产生不适当的输出（冲突、死锁、饥饿）；而在同步的基础上，进程被分为以下两种类型：\n独立进程 Independent Process 不和其他进程共享资源或状态 确定性，输入状态确定结果 可重现，能够重现起始条件，I/O 调度的顺序不重要 协作进程 Cooperative Process； 多进程共享资源或状态 不确定性 probabilistic 不可重现 不确定性和不可重现意味着bug可能是间歇性发生的\nCooperation 进程的互相影响，即进程间的合作（相互或破坏）；最简单的例子就是两个进程使用同一个文件，一个进程读，一个进程写。读进程的结果会被写进程所影响。\n进程需要合作的原因：\n资源共享：多个进程访问相同的数据 一台电脑，多个用户 一个银行存款余额,多台ATM机 嵌入式系统（机器人手臂和收的协调） 计算加速： I/O 和 CPU计算可重叠 多处理器 - 将任务分解为子任务并分布在不同的进程中，它通常可以更快地运行（也需要多个可共享的 CPU） 模块化：复杂的任务组织成单独的子任务，让不同的进程运行 大程序分成小程序 是系统易于扩展 程序可以调用函数fork()来创建一个新的进程\n操作系统需要分配一个新的并且唯一的进程ID 因此在内核中,这个系统调用会运行 new_pid = next_pid++; 翻译成机器指令: Load next_pid Reg1 STORE Reg1 new_pid INC Reg1 STORE Reg1 next_pid 假设两个进程并发执行\n如果next_pid等于100, 那么其中一个进程得到的ID应该是100, 另一个进程的ID应该是101, next_pid应该增加到102 可能在INC前进行了上下文切换, 最终导致两个进程的pid都是100,而next_pid也是101 无论多个线程的指令序列怎样交替执行,程序都必须正常工作\n多线程程序具有不确定性和不可重现的特点 不经过专门设计,调试难度很高 不确定性要求并行程序的正确性\n先思考清楚问题，把程序的行为设计清楚 切忌给予着手编写代码，碰到问题再调试 Race Condition 竞态条件是由操作系统软件中的同步错误。出现在进程试图同时执行两个或多个操作时，这是一种不希望出现的情况。\n怎么样避免竞态?","title":"ch9 同步"},{"content":"Overview 进程的描述 进程的状态 State 线程 Thread 进程间通信 Inter-Process Communication 进程互斥与同步 死锁 DeadLock 进程的描述 在操作系统中，通常来说进程 Process 是当前正在执行的东西。因此，一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程，可以称之为进程。\n程序是静态的文件\n进程是程序动态执行的过程\n进程的组成 进程包括 :\n程序的代码 程序处理的数据 程序计数器 (PC) 中的值, 指示下一条将运行的指令 一组通用的寄存器的当前值, 堆 Heap , 栈 Stack 一组系统资源(如打开的文件、内存、网络) 而进程的主要构成如下，\nStack Section Heap Section Data Section Text Section Stack Stack部分包含：\n局部变量 函数和返回地址 main函数 如上图所示，Stack和 heap 以相反的方向增长，如果两者都以相同的方向增长，那么其两者可能会重叠，因此如果它们以相反的方向增长则很好。\n示例：如，调用下列函数时，将存储在Stack部分，一旦函数返回，该函数堆栈部分的值将被删除。\nStack上有一个堆栈帧，其中包含main函数以及局部变量a, b sum 。使用 printf()，创建的帧以及局部变量只能在内存中访问，帧的持续时间在从函数 return 0 后释放。\nc 1 2 3 4 5 6 7 8 int main(void) { int a, b, sum; a = 2; b = 3; sum = a + b; printf(\u0026#34;%d\\n\u0026#34;, sum); return 0 } Stack是一种后进先出 (LIFO) 数据结构，最后一个被推到Stack上的内容就是从顶部弹出的第一个内容。不允许从Stack的中间插入或移除。因此Stack必须至少支持两种操作：push 和 pop ；其他操作也是可以，但不是必需的。\n在Linux中，ulimit -a 是可以获取和设置用户限制的函数\nHeap 当程序在运行时需要内存时，此部分用于提供动态内存。它是从Heap提供的。\n如在C语言中，malloc()和calloc()用于此目的，例如：\nmalloc(4) 将返回Heap区域中 4 BYTE 块的起始地址。 alloca()：从Stack申请内存，因此无需释放. 需要注意的是，在 C 语言中，动态内存需要处理，即在不需要时释放，否则一段时间后堆会变满。\n因此，在 C 程序中使用了free()函数来执行此操作。\n相比于Stack，Heap更为灵活，在Heap中，程序可以在的任何位置分配或释放内存。这种情况下就意味着Heap中间可能有一个“hole”，即未分配的内存被分配的内存包围着\n由图可以看出 ，当程序释放或释放两个相邻的内存块时，Heap区域会将其合并成一个大块。这样做可以让heap更好地满足未来对大块内存的需求。交叉阴影（彩色块大小的两倍）块说明了对大块内存的请求。\ndata data部分包含全局变量和静态局部变量。例如：\nc 1 2 3 4 5 6 7 8 #include\u0026lt;stdio.h\u0026gt; int glbal _var ; // 全局变量将存储到data区 int main() { static int var ; // 静态变量存储到data区 // code statement return 0; } 在保存全局变量和静态变量的内存通常在程序启动时分配。\ntext text部分包含可执行指令、常量和宏，它是只读位置并且是可共享的，因此也可以被其他进程使用。\n通常情况下，Text区域是可共享的，因此对于频繁执行的程序，只需要在内存中保存一个副本。此外，文本段通常是只读的，以防止程序意外修改其指令。\nReference\nlayout of a process\nmemory\nstack vs heap\n程序和进程的关系 进程和程序之间的联系\n程序是产生进程的基础 程序的每次运行构成不同的进程 进程是程序功能的体现 通过多次执行，一个程序可以对应多个进程，通过调用关系，一个进程可包括多个程序。 进程和程序的区别 :\n进程是动态的，程序是静态的：程序是有序代码的集合。进程是程序的执行，进程有核心态/用户态. 进程是暂时的，程序是永久的：进程是一个状态变化的过程，程序可以长久保存。 进程和程序的组成不同：进程的组成包括程序，数据和进程控制块(进程状态信息) 进程的特点 动态性 : 可动态地创建，结果进程; 并发性：进程可以被独立调度并占用处理机运行；(并发:一段, 并行:一时刻) 独立性：不同进程的工作不相互影响；(页表是保障措施之一) 制约性 ：因访问共享数据, 资源或进程间同步而产生制约。 进程控制结构 进程控制块 在操作系统中会同时运行多个进程。每个进程都有一些数据和执行指令。这些指令可以是代码执行或在进程执行期间将用于交互的设备列表（如打印机）。因此，需要一种可以存储进程的每个进程运行时的信息的数据结构，这个数据结构称为进程控制块 ( Process Control block PCB)。\nPCB是进程存在的唯一标准\n进程的创建： 为该进程生成一个PCB 进程的终止： 回收它的PCB 进程的组织管理： 通过对PCB的组织管理来实现 PCB的组成 **PCB有以下三大类信息 **:\n进程标志信息\n进程标志信息：如本进程的标志, 本进程的产生者标志(父进程标志). 用户标志 进程号 (PID)：每个进程的唯一标识号 处理器状态信息保存区：保存进程的运行现场信息 :\n进程结构 Process structuring：进程的子id，或以某种功能方式与当前进程相关的其他进程的id，可以表示为队列 queue 、环 ring 或其他类型的数据结构 程序计数器 ( Program Counter PC)：指向该进程要执行的下一条指令地址 CPU 寄存器 ：存储进程以执行运行状态 CPU调度信息：调度CPU的时间 进程控制信息\n进程调度状态 Process Sheduling State ，指定了进程的状态，如 “ready”、“waiting ”等和一些其他信息，例如优先级、自进程获得 CPU 控制权或自进程获得 CPU 控制权以来经过的时间向量。此外，在暂停进程的情况下，必须为进程正在等待的事件记录事件标识数据。 进程状态 Process State：new, ready, running, waiting, dead 内存管理信息：页表、内存限制、段表 I/O 状态信息：分配给进程的 I/O 设备列表。 进程权限 Privileges：是否允许访问系统资源 进程间通信 IPC：独立进程之间的通信相关的标志、信号和消息 进程的状态 进程生命期管理 进程的生命周期 当一个进程执行时，它会经历不同的状态。这些阶段在不同的操作系统中可能会有所不同，并且这些状态的名称也没有标准化。但一般来将，一个进程一次可以有以下五种状态之一。而进程在执行过程中改变其状态被称为进程的声明周期 process life cycle\nState Descriptio New\u0026amp;Start 进程被首次启动/创建时的初始状态 Ready 表示进程正在等待操作系统分配CPU资源，以便其可以运行。 Running 一旦操作系统将CPU资源分配给进程，进程状态就会设置为Running，并且执行其指令。 Waiting 进程需要等待资源的完成，如：等待用户输入或等待文件变为可用，则进程进入等待状态。 Terminated Exit 当进程完成其执行，或者它被操作系统终止，其状态就会变为Exit状态，等待从主存中删除 进程的创建 引起进程创建的3个主要事件：\n系统初始化 用户请求创建一个新进程 正在运行的进程执行了创建进程的系统调用 进程运行 创建完进程时，不一定会被执行，还需要操作系统内核选择一个可以执行的进程，这种进程称之为就绪进程 Ready，让它占用CPU并执行 (为何选择？如何选择？)\n进程等待(阻塞) 在执行过程中，可能会发生等待，无法完成的事件会发生进程等待(阻塞)，通常下面情况下会触发\n请求并等待系统服务, 无法马上完成（如进程从辅存将数据读入主存，这个时间对于CPU会很慢，此时会发生等待）\n启动某种操作, 无法马上完成（如多进程协同，其他进程没有完成也会等待）\n需要的数据没有到达\n进程等待事件发起只能进程自己阻塞自己，因为只有进程自身才能知道何时需要等待某种事件的发生。\n进程唤醒 进程唤醒是将进程的等待状态转换为就绪态，意味着该进程可以被CPU去调度执行。唤醒进程的原因 :\n被阻塞进程需要的资源可被满足 被阻塞进程等待的事件到达 将该进程的PCB插入到就绪队列 进程只能被别的进程或操作系统唤醒\n进程结束 在以下四种情况下, 进程结束 :\n正常退出（自愿） 错误退出（自愿） 致命错误（强制性） 被其他进程杀死（强制性） 进程的变化模型 两态模型 两态模型 Two state 是指，进程主要有两个状态：\nNot-running：非运行状态是指，进程正在等待执行 Running：运行状态是指当前正在运行的状态 如图可以看出，当操作系统创建进程时，会为该进程初始化PCB；当进程被事件打断，操作系统会将该进程从Running状态转换到到Not-running状态。\n三态模型 三态模型是两态模型的改善，在两态模型中，存在一个主要的缺点。当调度器将一个新进程从非运行态转换为运行态时，该进程可能仍在等待某个事件或 I/O请求。因此，调度器必须遍历队列并从中找到准备好执行但还未运行进程。这样的话效率很低。而且两态模型总体来说不能算满足进程的生命周期。\n为了克服这个问题，三态模型将 Not-running 状态分为两种情况：\n就绪 Ready：一个进程获得了除CPU之外的一切所需资源，等待CPU分配时间片 等待 Waiting（阻塞 Blocked）：进程在等待某一事件而暂停运行；如等待某资源，等待输入/输出完成。 操作系统会为Ready和Waiting维护一个单独的队列。一旦进程的等待的事件完成，进程就会从阻塞态进入就绪态。\n五态模型 五态模型，是在三态模型的基础上，加入了进程的两个新状态**：New** 和 Terminated。\n为什么会有五态\n在之前三态模型中，进程有状态代表的，在主存储器中加载所有进程。很显然这是不可能实现的。所以当创建一个新进程时，程序并不会立即加载到主存中。操作系统仅在主存中存储有关进程的一些信息。当内存有足够可用空间时，长期调度器 long term scheduler 会将程序移动到主存。这样的过程就是New。\n五态模型下的状态 Running：当前正在执行的进程；也可以理解为当前占用CPU时间的进程。 Waiting/Blocked： 等待某些事件的进程，如 I/O，等待其他进程，同步信号等。 Ready：等待执行的进程；也可以理解为等待分配CPU时间的进程。 New：刚创建的新进程。PCB已经初始化完成，但程序尚未加载到主内存中。程序保持在New状态，直到长期调度器 long term scheduler 将进程转换到Ready状态（已在主内存中）。 Terminated/Exit：完成的进程或中止的进程。 五态模型下的状态变化过程\nNULL -\u0026gt; New：新进程的创建过程\nNew -\u0026gt; Ready：当有足够的可用资源时，长期调度程序从辅存中选择一个New状态的进程并将其加载到主存中。该进程现在处于Ready状态，等待分配CPU时间。\nReady -\u0026gt; Running ：短期调度器Short Term Scheduler 或调度器将一个进程从Ready调度到Running，标记着该进程正在被执行。\nRunning -\u0026gt; Exit：当进程完成执行或中止，操作系统将进程从Running移动到Exit。\nRunning -\u0026gt; Ready： 当一个进程运行了一定时间而没有任何中断时，可能会发生这种变化；如，轮训调度算法。另一个常见的情况是，如果处于就绪状态的进程的优先级高于当前正在运行的进程的优先级，操作系统会抢占正在运行的进程并将其改变为就绪状态。\nRunning -\u0026gt; Waiting：进程必须等待某个事件，则将其置于Waitting状态。如：进程可能会请求一些可能不可用的资源或内存事，该进程可能正在等待 I/O 操作，或者该进程正在等待其他进程完成，然后才能继续执行。\nWaiting -\u0026gt; Ready：进程完成了等待的事件，将从Waitting状态进入Ready。\n进程挂起模型 进程挂起 process suspension，是为了合理且充分地利用系统资源。进程在挂起状态时, 意味着进程没有占用内存空间，处在挂起 Suspended 状态的进程把进程放到磁盘上。当一个挂起进程准备好时运行时，它会进入 Ready-Suspend 队列中。因此，挂起状态也分为两个状态，即 阻塞挂起 Blocked Suspend 和 就绪挂起Ready Suspend。\n六态模型 六态模型通常情况下是具有挂起状态的五态模型。在六态模型存在一个缺陷，众所周知处理器比 I/O 设备要快很多。因此，会出现CPU执行速度过快导致所有进程都处于阻塞态而没有进程处于就绪态的情况。此时CPU 处于空闲状态，直到至少有一个进程完成 I/O 操作。这种情况下会导致 CPU 利用率低。\n为了防止这种情况发生，即如果主存中的所有进程都处于阻塞态，操作系统会挂起( Suspended ) 处于阻塞态的进程并将其移动到辅存中。这种过程称为交换。所有处于挂起状态的进程都保存在一个队列中，内存被释放。此时，CPU 可以将一些其他进程带入主存。起到更好的利用CPU资源\n六态模型的状态转换\n在六态模型中，除了五态模型的转换外，还具有下述的几个状态间装换：\nWaiting -\u0026gt; Suspend：如果主存中的所有进程都处于等待状态，操作系统将进程从Waitting转换为到Suspended Suspend -\u0026gt; Ready：当有足够的内存可用时，操作系统将处于Suspended状态的进程移回主内中执行。 Suspend -\u0026gt; Waiting：操作系统从辅存换入到主存的进程仍在等待某个事件的完成。 七态模型 七态模型是将六态模型的阻塞状态又严格的分为 阻塞挂起（Blocked Suspend) 和 就绪挂起 (Ready Suspend )\n阻塞挂起（Blocked Suspend) ：进程在辅存中，尚未Ready。\n就绪挂起 (Ready Suspend )：:进程在辅存中, 但只要进入内存，即可运行。\n七态模型的状态转换\n在内存中会出现的转换情况\nBlocked -\u0026gt; Blocked-Suspend：如果主存中的所有进程都处于Waiting状态，则处理器将至少一个等待进程换回辅存以释放内存使得CPU资源被有效的利用。 Ready -\u0026gt;Ready-Suspend：当具有高优先级发生阻塞等待，操作系统会认为该进程很快会被就绪，此时，操作系统会选择挂起低优先级就绪进程，从Ready移动到Ready-Suspend，以释放主内存用于更高优先级的进程 Running -\u0026gt;Ready-Suspend：对抢先式分时系统，当有高优先级阻塞挂起进程因事件出现而进入就绪挂起时, 系统可能会把运行进程转换为就绪挂起状态。 New -\u0026gt; Ready-Suspend：如果主存使用空间不足，操作系统可能会将新进程移动为Ready-Suspended 状态。 在外存会出现的转换情况\nBlocked-Suspend -\u0026gt; Ready-Suspend： 当有阻塞挂起因相关事件得到满足时，操作系统会把Blocked-Suspend 进程转换为Ready-Suspend。 与挂起相关的状态转换（将进程从外存转入内存）\nReady-Suspend -\u0026gt; Ready：当处于Ready-Suspend的高优先级进程高于Ready的进程，则操作系统会将其与主存中的较低优先级Ready进程交换。 Blocked-Suspend -\u0026gt; Blocked：当主存空间足够时, 系统会把一个高优先级Blocked-Suspend进程(系统认为会很快出现所等待的事件)进程转换为Blocked状态 Reference\nprocess state models\nprocess-scheduling\nprocess suspension\n进程队列 进程队列 (Process Queues) 是操作系统为管理进程的各种状态维护不同类型的队列，PCB会被存放在相同状态的队列中。如果进程状态发送改变，那么PCB也会进行转换到新的状态队列中。\n工作队列 Job queue：保存操作系统中所有的进程，存储在辅存中 就绪队列 Ready queue： 保存在主存中，短期调度器负责分配CPU时间 等待队列 Waiting Queue 或 Device queues：当进程发生阻塞事件，即需要I/O，此时进程会从 Ready转换为 Waitting 状态，进程的上下文 Context（PCB），都将存储在在等待队列中。 Reference\nProcess scheduling\n线程 线程 Thread 是进程中的一条执行流程，线程是CPU独立运行的基本单位，由程序计数器（PC），Stack，和一组寄存器组成。Code、File和Data段等可以在不同的线程共享。\n由上图可以看出，线程的组成比进程要少一些，进程的主要构成是：\nStack Section Heap Section Data Section Text Section 而线程主要构成部分是：\n独立的Stack Heap 与进程共享 Data 与进程共享 Text 与进程共享 线程的控制结构 线程控制块 TCB，是操作系统中的数据结构，每个线程都会维护一个TCB，TCB则是操作系统中线程的表现方式。\n线程的特点 线程的优点 线程减少了上下文切换时间，这有助于管理任务的时间； 各个线程之间可以并发地执行； 各个线程之间可以共享地址空间和文件等资源； 线程的缺点 整个进程过分依赖线程，如单个线程中断，则整个进程中断并阻塞。 安全可靠性无保障：因为共享data（全局变量在线程间共享），这会产生安全问题。 多线程优点大概分为以下四类：\n响应能力： 交互式应用程序的多线程可以允许程序继续运行，即使它的一部分被阻塞或正在执行冗长的操作，从而提高对用户的响应能力。例如，多线程网络浏览器仍然可以允许用户交互\n在另一个线程中加载图像时在一个线程中。\n资源共享：线程共享其所属进程的内存和资源。\n经济：因为线程共享所属进程的资源，线程的创建和上下文切换线程更\n更高效的利用多处理器：每个线程可以在不同的处理器上并行运行，而一个单线程进程只能在一个 CPU 上运行，多线程增加了并发性。\n线程与进程的比较 进程是最小的资源分配单位，线程是最小的CPU调度单位 进程拥有一个完整的资源平台，而线程只独享必不可少的资源，如寄存器和栈； 线程同样具有就绪、阻塞、执行三种基本状态，同样具有状态之间的转换关系； 线程能减少并发执行的时间和空间开销； 创建时间 终止时间 切换（如页表切换、CPU上下文切换） 不同的进程CPU分别执行，不同的线程视为同一个任务 资源的使用（IPC和资源共享） 线程的实现 现代系统中实现了两种类型的线程 用户线程 (User-Level Threads)和内核线程(Kernel-Level Threads)。\n内核线程是操作系统内核支持的线程，由操作系统直接管理。所有现代操作系统都支持内核级线程。 内核知道并管理所有线程。 每个进程一个进程控制块 (PCB)。 系统中每个线程一个线程控制块 (TCB)。 提供系统调用：可以从用户空间创建和管理线程。 用户线程是存与用户空间，并且在没有内核支持的情况下进行管理；内核不知道用户线程。从内核的角度来看，进程是一个不透明的黑匣子。 线程完全由运行时系统 run-time system （用户级线程库）管理。 理想情况下，线程操作应该与函数调用一样快。 内核不知道用户线程的存在，管理用户线程与管理单进程一样。 用户线程模型 通常，可以使用一下四种模型之一来实现用户线程。\n多对一 一对一 多对多 两级 以上所有模型都是将用户级线程映射到内核级线程。而内核线程又类似于非线程（单线程）系统中的进程。内核线程是内核调度在CPU上执行单元。\n多对一 在多对一模型是将所有用户线程都映射到同一个内核线程上执行。该进程一次只能运行一个用户线程。\n从图可以看出，一对一模型的特点如下：\n多个用户线程都映射到单个内核线程上。 线程对内核不透明，线程的管理是由用户空间的线程库处理，效率很高。 如果发生阻塞的系统调用，那么整个进程都会阻塞，即使其他用户线程能够继续执行。 因为单个内核线程只能在单个 CPU 上运行，所以多对一模型不允许将单个进程拆分到多个CPU上。 一对一 在一对一模型中，内核必须提供一个系统调用来创建一个新的内核线程。\n一对一模型的特点：\n在一对一模型中，一个单独的内核线程用户来处理一个用户线程。 一对一模型有效的克服了一对多模型中涉及阻塞式系统调用和跨CPU拆分进程的问题。 管理一对一模型的开销更大，大的开销将减慢系统的调用速度。 该模型中，的大多数实现都对可创建的线程数量进行了限制。 多对多 在多对多模型中，进程将分配了 m 个内核级线程来执行 n 个用户级线程。\n多对多模型的特点：\n可将任意数量的用户程多路复用到数量相等或更少的内核线程上。 用户对创建的线程数没有限制。 阻塞内核级系统调用不会阻塞整个进程。 进程可以跨多个处理器拆分。 两级 两级模型（Two-Level）是严格意义上的多对多模型，可以为单个用户线程专门一对一绑定内核线程的能力的模型\n用户线程缺点 用户级线程与操作系统的集成度不高；如用空闲线程调度进程，阻塞其线程发起 I/O 的进程，即使该进程有其他线程可以运行，以及有锁的线程取消调度进程。\n用户级线程需要非阻塞系统调用，否则，当一个线程阻塞，即使进程中还有可运行的线程，整个进程也会在内核中阻塞。例如，如果一个线程导致页面错误，则进程阻塞。\n用户线程和操作系统内核之间缺乏协调性；无论进程有 1 个线程还是 1000 个线程，都仅能获得一个CPU时间片。由每个线程主动将控制权交给其他线程。\n由于进程时资源分配的最小单位，多线程情况下，每个线程得到的时间片较少，执行会较慢。\n内核级线程优缺点 优点：\n线程对操作系统内核透明，调度程序可以决定给拥有大量线程的进程比拥有少量线程的进程更多的时间。 内核级线程适用于经常阻塞的应用程序。 缺点：\n内核级线程缓慢且效率低下；如，操作内核级线程比用户级线程慢。 由于内核管理和调度线程与进程。每个线程又需要一个完整的TCB来维护有关线程的信息。因此，存在大量开销并增加了内核复杂性。 Reference\nUser-level Thread Library\nimplementing threads\nThreads\n进程控制 上下文切换 Introduction 上下文（Context）是指，进程的CPU占用被移除时，需要存储进程的相关信息，以便稍后获得处理器时，可以从相同的状态继续运行。这个状态数据被称之为上下文。可以理解为为上下文对进程的就如同书签对一本书。\n上下文切换（Context Switch）是将CPU占用的时间片从一个进程或切换到另一个的过程。在这种现象中，处于运行状态的进程的执行会被挂起，而另一个处于就绪态的进程则会占用CPU时间。\n在操作系统中，什么情况下会发生上下文切换？\n当进程终止时 当CPU计时器超时，应该切换到其他进程时 当前进程挂起 当前进程需要一些时间等待I/O事件 当来自计时器之外的中断产生，如优先级高的进程抢占 上下文切换时需要存储什么上下文?\n当进程从一种状态转换到另一种状态时，操作系统必须更新进程PCB信息。PCB是一种数据结构，在操作系统中用于将所有与数据相关的信息存储到进程和上下文中。PCB包含进程，即寄存器、时间片、优先级等。\n进程的上下文包含：\n地址空间、Stack空间、虚拟地址空间 寄存器集，如进程计数器（PC）、Stack指针（SP）、指令寄存器（IR）、程序状态寄存器（PSW） 所有信息都会保存在PCB中\n什么时候会触发上下文切换\n上下文切换只要发生在三种情况下：\n多任务：一个进程从CPU 中换出，下一个可运行进程换入。在抢占式系统只不过，进程可能会被调度器换出。 中断处理：当中断发生时，硬件切换部分上下文。这会自动发生。 用户空间到内核空间：操作系统在用户空间和内核空间之间进行转换时，会发生上下文切换。 上下文切换的过程 又图可以看出P1占用CPU，P2处于就绪状态。如果发生中断或进程发生 I/O 事件，则 P1将会被换出；在更改 P1 的状态前，会将P1 的上下文保存在寄存器中，并将PC保存到 PCB1。之后，将加载PCB2上下文信息，此时PC从就绪状态转变为运行状态。\n类似地，P2发生中断，恢复P1恢复执行。P1 从 PCB1 重新加载到运行状态以在上次停止时间重新执行任务。如信息丢失，并且CPU再次执行该进程时，会从其初始状态开始执行。\nReference\ncontext-switching in os\n进程创建 进程创建是操作系统给用户使用的一个系统调用，用来完成新进程的创建工作。在Unix中进程的创建采用 fork()/exec() 系统调用来完成新进程的创建\nfork() 的功能是创建一个与调用它的进程的几乎完全相同的副本 使用父进程的资源（例如，打开的文件）初始化新进程 PC、SP与父级相同 使用父进程的地址空间的内容作为副本初始化一个新地址空间 fork() 的系统调用会返回两次 父进程会返回子进程的PID 子进程返回0，失败返回-1 exec() 的功能是用新的进程替换当前进程，exec会将程序加载到当前的地址空间内并从头开始执行。 进程的Text、Data、Stack段会被替换成新的 exec()系统调用是使用了新程序替换了当前进程，因此PID没有发生改变 exec()的调用时由调用的进程发起的，被执行的是一个新程序，并不是一个进程，没有创建新进程 exec()系统调用不会返回给调用程序在执行，除非exec() 执行出错。 一个fork()示例：\nc 1 2 3 4 5 6 int main(void) { printf(“Parent (PID = %d)\\n”, getpid()); fork(); printf(“My PID is %d\\n”, getpid() ); return 0; } 一个exec()示例\nc 1 2 3 4 5 6 int main(void) { printf(“before execl\\n”); execl(“/bin/ls”, “/bin/ls”, NULL); printf(“after execl\\n”); return 0; } fork()是如何工作的\n在内核空间内，进程被排列成一个双向链表，称为任务列表。 父进程1234调用fork() PCB会在内核空间内被复制，同时用户空间的代码也会被复制 子进程返回0，父进程返回子进程的PID Reference\nprocess management\n进程加载 进程加载是指，用户程序通过exec()系统调用进行加载。\nexec()系列函数用新的用户程序替换当前的进程的地址空间\n通过exec()更改子进程正在执行的程序代码来转换子进程，切换后的程序从main()开始执行 允许程序加载时指定启动参数；execvp(argv[1], \u0026amp;argv[1]) 当调用成功时 两个为相同的进程，仅仅做了替换，不会生成新的进程 运行的是不同的程序 exec()是如何工作的\n清除局部变量和动态分配的内存 全局的程序（代码）和常量会被替换为新的程序（代码）和常量 全局变量重置为基于新代码的 进程等待与退出 进程的等待 等待和退出实际上是父子进程间的交互，完成子进程的资源回收\nwait()系统调用用于父进程等待子进程的结束 子进程被创建并被执行后，父进程会被挂起，父进程通过wait()系统调用等待子进程返回值并再次获得控制权 子进程调用exit()唤醒父进程，将exit()返回值作为父进程中wait()的返回值 有僵尸子进程等待时，wait()立即返回其中一个值 无子进程存活，wait()立即返回 进程的有序退出 进程在执行结束时调用exit()，完成进程资源回收。exit() 是一个系统调用，有如下功能：\n接受退出状态作为参数传给父进程。 资源回收 关闭所有文件和套接字 释放内存 释放创建出进程相关的数据结构 检查子进程是否存活，保留结果值直到父进程需要，进入zombie状态 否则释放所有数据结构，进程结束 其他进程控制系统调用 上述是对进程模型流的一些进程控制的系统调用，但操作系统还必须包含对进程的特殊控制：\n优先级操作 nice()，指定进程的初始优先级进 在UNIX中，进程优先级会随着进程对CPU的消耗而减少 调试的支持：ptrace()，允许一个进程来控制另一个进程，如设置断点、查看寄存器。 定时：Sleep()可以将进程置于定时器等待队列中，等待数秒 Reference\nProcess Management\n","permalink":"https://www.161616.top/ch7-process-management/","summary":"Overview 进程的描述 进程的状态 State 线程 Thread 进程间通信 Inter-Process Communication 进程互斥与同步 死锁 DeadLock 进程的描述 在操作系统中，通常来说进程 Process 是当前正在执行的东西。因此，一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程，可以称之为进程。\n程序是静态的文件\n进程是程序动态执行的过程\n进程的组成 进程包括 :\n程序的代码 程序处理的数据 程序计数器 (PC) 中的值, 指示下一条将运行的指令 一组通用的寄存器的当前值, 堆 Heap , 栈 Stack 一组系统资源(如打开的文件、内存、网络) 而进程的主要构成如下，\nStack Section Heap Section Data Section Text Section Stack Stack部分包含：\n局部变量 函数和返回地址 main函数 如上图所示，Stack和 heap 以相反的方向增长，如果两者都以相同的方向增长，那么其两者可能会重叠，因此如果它们以相反的方向增长则很好。\n示例：如，调用下列函数时，将存储在Stack部分，一旦函数返回，该函数堆栈部分的值将被删除。\nStack上有一个堆栈帧，其中包含main函数以及局部变量a, b sum 。使用 printf()，创建的帧以及局部变量只能在内存中访问，帧的持续时间在从函数 return 0 后释放。\nc 1 2 3 4 5 6 7 8 int main(void) { int a, b, sum; a = 2; b = 3; sum = a + b; printf(\u0026#34;%d\\n\u0026#34;, sum); return 0 } Stack是一种后进先出 (LIFO) 数据结构，最后一个被推到Stack上的内容就是从顶部弹出的第一个内容。不允许从Stack的中间插入或移除。因此Stack必须至少支持两种操作：push 和 pop ；其他操作也是可以，但不是必需的。","title":"ch7 进程管理"},{"content":"Overviews 功能与目标 实验设置与评价方法 局部页面算法 最优页面置换算法 先进先出算法 最近最久未使用算法 时钟页面置换算法 最不常用置换算法 Belady现象 LRU FIFO Clock对比 全局页面置换算法 工作集模型 工作集页面置换算法 缺页率置换算法 功能与目标 功能 : 当缺页中断发生，需要调入新的页面而内存已满时，选择内存当中哪个物理页面被置换。\n目标 : 尽可能地减少页面的换进换出次数(即缺页中断的次数)。 具体来说，把未来不再使用的或短期内较少使用的页面换出，通常只能在局部性原理指导下依据过去的统计数据来进行预测。\n页面锁定 frame locking：用于描述必须常驻内存的操作系统的关键部分或时间关键（time critical）的应用进程。实现的方式是：在页表中添加锁定标记位(lock bit)。\n实验设置与评价方法 实例：记录一个进程对页访问的一个轨迹\n举例 : 模拟一个实验环境，记录对应的地址访问序列，虚拟地址跟踪(页号, 偏移)\u0026hellip; (3,0) (1,9) (4,1) (2,1) (5,3) (2,0) \u0026hellip; 而offset可以忽略（页不存在才会产生 page fault），生成的页面轨迹 3, 1, 4, 2, 5, 2, 1, \u0026hellip;（替换为，3,1,4,2,5,2,1） 模拟一个页面置换的行为并且记录产生页缺失数的数量\n更少的缺失，更好的性能 局部页面置换算法 最优页面置换算法 基本思路：当一个缺页中断发生时，对于保存在内存当中的每一个逻辑页面，计算在它的下一次访问之前，还需等待多长时间，从中选择等待时间最长的那个，作为被置换的页面。\n这是一种理想情况, 在实际系统中是无法实现的, 因为操作系统无法知道每一个页面要等待多长时间以后才会再次被访问.\n最优页面置换算法（Optimal Page Replacement）可用作其他算法的性能评价的依据，(在一个模拟器上运行某个程序, 并记录每一次的页面访问情况，在第二遍运行时即可使用最优算法)\n在该算法中，会替换在未来最长持续时间内不会使用的页面。如下图所示有 a b c d e五个页，但是只有四个页帧。此时会产生物理页不够，会产生 Page Fault。\n前四次因为a b c d 已经存在物理页帧中，故前四次不会产生缺页中断，第5次请求e不在物理页帧，此时会产生page fault，发生页面置换。可以看出目前最久不会被访问的页面为d，故将d替换出。\n先进先出置换算法 先进先出页面置换算法 First In First Out (FIFO)，这是最简单的页面替换算法。在这个算法中，操作系统在一个队列中跟踪内存中的所有页面，最旧的页面在队列的前面。当一个页面需要被替换时，队列前面的页面被移除，进行置换。\n性能较差, 调出的页面有可能是经常要访问的页面。并且有belady现象，FIFO算法很少单独使用.\nFIFO算法实现起来非常简单。通过对主存储器中的队列来跟踪所有页面。一旦页面进入，我们就会将其放入队列并继续。这样，最旧的页面将始位于于队列中的第一位。\n图是FIFO的伪代码\nReference\nfifo-page-replacement\npage replacement algorithms\n实例，0时刻物理页中存放了 a b c d虚拟页，\n当时刻为5时，此时e不在物理页帧中，触发 page fault进行页面置换，假设 0 时刻时 入栈顺序为 a-b-c-d，此算法将会把a置换出，把e置换入。后续的换入换出也是按照进入队列顺序进行替换\n最近最久未使用页面置换算法 最近最久未使用，Least Recently Used。基本思路是LRU会在一段时间内跟踪页面的使用情况，当发生缺页时，将最长时间未使用的页面替换为新请求的页面。\nLRU是与OPT近似的一个算法，该算法基于程序的局部性原理，即在最近时间内, 被频繁地访问页面, 再将来的一小段时间内，还可能会再一次被频繁地访问。\nLRU 根据历史推测未来 OPT 根据未来推测未来 实例，0时刻物理页中存放了 a b c d虚拟页，\n当访问5时刻时，此时该替换的应该为最久没有被访问的页面，此时c上次访问时间为1，c为最久没有被访问的页面。\n时钟页面置换算法 时钟页面置换算法 The Clock Algorithm，是类似于LRU的一种算法，对FIFO的一种改进\n基本思路 :\n需要用到页表项的访问位，当一个页面被装入内存时，把该位初始化为0。 然后如果这个页面被访问，则把该位置设为1; 把各个页面组织成环形链表(类似钟表面)，把指针指向最老的页面(最先进来的)； 当发生一个缺页中断时，考察指针所指向的最老页面，若它的访问位为0，立即淘汰；若访问位为0，然后指针往下移动一格。如此下去，直到找到被淘汰的页面，然后把指针移动到下一格。 实例：维护一个驻留在内存中的链表，指针指向上一次访问的位置\n使用时钟（或use/reference bit）位来记录页面的访问频率 每当引用页面（被访问）时，都会设置reference bit 为 1 时钟指针扫过页面，寻找 reference bit = 0 的页面，替换时钟扫过一圈未被引用的页面\n实例：0时刻物理页中存放了 a b c d虚拟页，在 1 2 3 4时刻请求时，此时会命中，并且在访问时，将reference bit 设置为1，由下图可见\n当时刻为5时，触发置换条件，此时时钟所有reference bit 都为 1，此时会转到第二圈，由于第一圈全将reference bit 设置为0，故，替换的页为 a，同时指针指向下个位置\n二次机会算法 二次机会置换算法 Second Chance，是对时钟算法的一个改进，具体表现为如下几个方面：\n为每个帧添加一个 drity bit。 当每次引用时，将 该 drity bit 设置为1 ； 这样就为该页面提供了二次机会。 当需要找到被置换出的页面时，请在时钟（维护的帧列表）中循环查找 如 drity bit=1，则将其重置（设置为零）并继续。 如 drity bit=0，则置换出该物理帧中的页面。 增加了 drity bit 如 drity bit=0，此时仅为读操作，在置换时无需做写入操作。这样也被称作，增强时钟算法 Enhance Clock。\n实例：0时刻物理页中存放了 a b c d虚拟页，在 1 2 3 4时刻请求时，此时会命中，并且在访问时，将reference bit 设置为1，并且，区分了读写操作，基于这种方式可以清楚的了解那页可以被置换出。\n因为做了写操作，当时刻为4时，a b 的dirty bit 都为1。在经过两轮后，将00位的页替换出，同时指针指向下一位，则替换出C，并将指针指向下一位\nReference\nSecond Chance Page Replacement Policy\n最不常用置换算法 最不常用置换算法 Least Frequently Used，并不是说算法本身不常用，而是说在该算法中，系统会跟踪内存页的引用次数。当发生 Page Fault时，会置换出使用频率最低的页。\n设计思路， LFU是在每个页表项都有增加一个计数器，对于每次内存引用，MMU 都会递增该计数器。当发生缺页中断时，操作系统选择计数器最小的页作为置换。\n实例，有如下页面，此时物理页帧仅有三个，执行图如下：\n0 1 2 3 0 1 2 3 0 1 2 3 4 5 6 7\nPage Fault = $12 \\div 16 = 75%$\nBelady现象 Belady现象也可以称作Belady异常 beladys anomaly，是在操作系统中，随着增加物理页帧数量会导致``Page fault`数量增加的现象。\n如 : FIFO算法的置换特征与进程访问内存的动态特征是矛盾的，与置换算法的目标是不一致的（即替换较少使用的页面），因此，被他置换出去的页面不一定是进程不会访问的。\n如：\nf 标记位为 缺页中断。\nPage Requests 3 2 1 0 3 2 4 3 2 1 0 4 Newest Page 3f 2f 1f 0f 3f 2f 4f 4 4 1f 0f 0 3 2 1 0 3 2 2 2 4 1 1 Oldest Page 3 2 1 0 3 3 3 2 4 4 示例1：当存在3个物理页面时 Page Fault = 9。\nPage Requests 3 2 1 0 3 2 4 3 2 1 0 4 Newest Page 3f 2f 1f 0f 0 0 4f 3f 2f 1f 0f 4f 3 2 1 1 1 0 4 3 2 1 0 3 2 2 2 1 0 4 3 2 1 Oldest Page 3 3 3 2 1 0 4 3 2 示例2：当存在4个物理页面时 Page Fault = 10。\n如何避免：使用stack 算法\n什么是stack算法 堆栈算法是指，大小为N的集合始终是大小为N+1集合的子集。\n例如：一个大小为N页的集合保存在内存中的页面始终是大小为 N + 1 的帧保存的页面的子集。\n如 具有3 帧的内存中的 {0,1,2} 不是具有 4 帧内存中 {0,1,4,5} 的子集 ，这种情况下基于堆栈的算法的。\n从上面belady现象可以看出，从 4 3 2 1 0 4 开始是违反基于堆栈的算法的属性\nPage Requests 4 3 2 1 0 4 Newest Page 4f 3f 2f 1f 0f 4f 0 4 3 2 1 0 1 0 4 3 2 1 Oldest Page 2 1 0 4 3 2 Page Requests 4 3 2 1 0 4 Newest Page 4f 4 4 1f 0f 0 2 2 2 4 1 1 Oldest Page 3 3 3 2 4 4 Reference\nwhy stack-based cache algorithms avoidbeladys-anomaly\npage replacement algorithms\n为什么stack-based算法不会发生belady现象 基于堆栈的算法不会产生Belady 现象，这是因为这些类型的算法会为页面（用于替换）分配一个优先级，该优先级与页帧的数量没有管理。如 Optimal、LRU 和 LFU。此外，此类算法还具有良好的模拟特性，即通过一次引用可以计算出任意数量的页帧的命中率缺页率。\nPage Requests 1 2 3 4 1 2 5 1 2 3 4 5 Newest Page 1 2 3 4 1 2 5 1 2 3 4 5 1 2 3 4 1 2 5 1 2 3 4 Oldest Page 1 2 3 4 1 2 5 1 2 3 Page Fault F F F F F F F F F F Page Requests 1 2 3 4 1 2 5 1 2 3 4 5 Newest Page 1 2 3 4 1 2 5 1 2 3 4 5 1 2 3 4 1 2 5 1 2 3 4 1 2 3 4 1 2 5 1 2 3 Oldest Page 1 2 3 4 4 4 5 1 2 Page Fault F F F F F F F F 由上述两表可以看出，在LRU算法中，每次引用一个页面时，它都会移动到堆栈的顶部，因此堆栈的顶部的n个页面是最近使用的n个页面*。*即使帧数增加到 n+1，堆栈顶部也会有 n+1 个最近使用的页面。 构成基于堆栈的算法。\nLRU / FIFO 和 Clock 的比较 LRU和FIFO都是先进先出的思路，只不过LRU是针对页面最近访问时间来进行排序，所以需要在每一次页面访问的时候动态地调整各个页面之间的先后顺序(有一个页面的最近访问时间变了)。而FIFO是针对页面进入内存的时间来进行排序，这个时间是固定不变的, 所以各个页面之间的先后顺序是固定的。如果一个页面在进入内存后没有被访问，那么它的最近访问时间就是它进入内存的时间。 换句话说，如果内存当中的所有页面都未曾访问过，那么LRU算法就退化为了FIFO算法。\n例如 : 给进程分配3个物理页面, 逻辑页面的访问顺序是 : 1,2,3,4,5,6,1,2,3 \u0026hellip;\n全局页面置换算法 局部页面置换算法是基于单一程序来说明的，但对于操作系统来讲，执行的程序有很多，如果每个程序使用固定的页面置换算法会产生一定的问题，所以全局页面置换算法就是解决这种问题的。\n存在问题，随着物理页帧的增加，通常情况下会大大减少缺页的次数，而为每个程序分配固定的物理页帧则会大大限制了程序执行的性能；程序是一个动态变化的过程，对内存的需求是可变的。\n工作集模型 如果局部性原理不成立，那么各种页面置换算法就没有说明分别，也没有什么意义。例如：假设进程对逻辑页面的访问顺序是1,2,3,4,5,6,6,7,8,9...，即单调递增，那么在物理页有限的前提下, 不管采用何种置换算法，每次的页面访问都必然导致缺页中断。 如果局部性原理是成立的，那么如何来证明它的存在，如何来对它进行定量地分析? 这就是工作集模型. 什么是工作集：进程当前正在使用的页面集称之为工作集 Working Set，可以用一个二元函数来表示：$W(t, \\tau )$；在区间$[t-\\tau+1..t] $ 内的页数。\nt是当前执行的时刻 $\\tau$ 是工作集窗口 Working-set window，一个定长页面访问的时间窗口 $W(t, \\tau )$ 是工作集的大小，即逻辑页的数量. 如果 Example ($\\tau = 10$ ):\nt1 → WS = {1,2,5,6,7}\nt2 → WS = {3,4}\n由此可知，\n工作集是工作集窗口中的页面集 工作集是窗口是一个移动的窗口，表现形式为对每个内存的引用。 当一个新的引用出现在窗口中，对应的页将被标记位该工作集中的成员 最旧一端的引用将从工作窗口中弹出，相应的页就不会再被标记位工作集中的成员了。 例如：如图所示：一个请求序列，假设 $\\tau = 10$ ，则应该如何计算工作集？\n工作集大小的变化 : 进程开始执行后，随着访问新页面逐步建立较稳定的工作集。当内存访问的局部性区域的位置大致稳定时，工作集大小也大致稳定；局部性区域的位置改变时，工作集快速扩张和收缩过渡到下一个稳定值。\nti WS t1 {1,2,5,6,7} t2 {1,5,6,7} t3 {1,2,5,6,7} t4 {1,2,3,5,6,7} t5 {1,2,3,4,5,6,7} Reference\nComputing the working set\n常驻集 常驻集是指在当前时刻, 进程实际驻留在内存当中的页面集合.\n工作集是进程在运行过程中固有的性质，而常驻集取决于系统分配给进程的物理页面数目，以及所采用的页面置换算法; 如果一个进程的整个工作集都在内存当中，即常驻集 包含 工作集, 那么进程将很顺利地运行，而不会造成太多的缺页中断(直到工作集发生剧烈变动, 从而过渡到另一个状态); 当进程常驻集的大小达到某个数目之后，再给它分配更多的物理页面，缺页率也不会明显下降。 工作集页面置换算法 如图所示，跟踪最后一个 $\\tau$ 参考（不包括断层参考）\n最后一次$\\tau$ 内存访问期间引用的页面是工作集 $\\tau$ 被称为窗口大小 例如，工作集大小为$\\tau=4$ 0时刻，被引用的页面为 a d e 此时 工作集窗口为 {-2, -1, 0}\n时刻1，工作集窗口为 {-2, -1, 0, 1} 工作集为{a c d e}\n时刻2，此时工作集窗口 {-1, 0, 1,2 } 而 工作集为 {a c d} 因为 e已经不在工作集窗口内了。\n时刻4，产生缺页中断，此时将a换出，因为a已经不在工作集窗口内了\n时刻6，产生缺页，将e换入工作集 此时工作集为 {b c e d}\n时刻7，因d不在工作集窗口内，则将d换出，此时工作集为 {b c d}\n缺页率页面置换算法 计算工作集的另一种方法：\n尝试最小化页面错误 当缺页率较高时，增加工作集 当缺页率较低时，减少工作集 缺页率页面置换算法 Page-Fault-Frequency Page Replacment，即可变分配策略：常驻集大小可变。 例如 : 每个进程在刚开始运行的时候, 先根据程序大小给它分配一定数目的物理页面, 然后在进程运行过程中, 再动态地调整常驻集的大小.\n可采用全局页面置换的方式，当发生一个缺页中断时，被置换的页面可以是在其他进程当中，各个并发进程竞争地使用物理页面。 优缺点：性能较好，但增加了系统开销。 具体实现：可以使用缺页率算法来动态调整常驻集的大小. 缺页率： $缺页次数 \\div 内存访问次数$；\n影响因素 :\n页面置换算法 分配给进程的物理页面数目 页面本身的大小 程序的编写方法 缺页集算法实现：保持跟踪缺页发生的概率，当出现缺页异常时，计算并记录从上一次缺页异常起到现在的时间。上次最后一次缺页异常的时间 tlast\n如果两次缺页异常间隔时间 “很大”，则减少工作集\n如果 $t_{current} - t_{last} \u0026gt; \\tau $，则从内存中移除 [$t_{current}$ , $t_{last}$]时间内没有被引用的页。 如果两次缺页异常间隔时间 “很小”，则增加工作集\n如果 $t_{current} - t_{last} \u0026lt; \\tau $，则将缺失的页增加到工作集中。 示例：假设窗口大小为2 $\\tau = 2$\n如果当 $t_{current} - t_{last} \u0026gt; 2$，则移除 [$t_{current}$ , $t_{last}$]时间内没有被引用的页。\n如果当 $t_{current} - t_{last} \u0026lt; 2$，则将缺失的页增加到工作集中\n时刻1：产生缺页异常\n时刻4：产生缺页异常，此时 $t_{current} - t_{last} = 4-3 \u0026gt; 2$，此时工作集窗口为{1,2,3,4}，工作集为 {a,c,d,e}则在工作集窗口内没有被访问到的 a,e 则被从工作集中清除。\n时刻6：产生缺页异常，此时 $t_{current} - t_{last} = 6-4 \\leq 2$，此时工作集窗口为{6,5,4}，工作集为 {b,c,d}；此时增加工作集，将e增加到工作集中\nReference\nPage Fault Frequency\n抖动问题 抖动问题是对工作集与常驻集问题的深入\n工作集：程序在执行过程中对内存访问的固有属性 常驻集：当前程序要访问那些页面放到内存中来 如果分配给一个进程的物理页面太少，不能包含整个的工作集, 即常驻集 属于工作集，那么进程将会造成很多的缺页中断，需要频繁的在内存与外存之间替换页面，从而使进程的运行速度变得很慢，我们把这种状态称为 \u0026ldquo;抖动\u0026rdquo; Thrashing 。\n产生抖动的原因：随着驻留内存的进程数目增加, 分配给每个进程的物理页面数不断就减小, 缺页率不断上升. 所以OS要选择一个适当的进程数目和进程需要的帧数, 以便在并发水平和缺页率之间达到一个平衡.\n更好的负载控制标准：调整MPL，以便：\n平均缺页间隔时间（ means time between page faults MTBF）= 缺页服务时间（ page fault service time PFST）\n$\\sum WS_i = Size of memory$\n","permalink":"https://www.161616.top/ch6-page-replacement-algorithms/","summary":"Overviews 功能与目标 实验设置与评价方法 局部页面算法 最优页面置换算法 先进先出算法 最近最久未使用算法 时钟页面置换算法 最不常用置换算法 Belady现象 LRU FIFO Clock对比 全局页面置换算法 工作集模型 工作集页面置换算法 缺页率置换算法 功能与目标 功能 : 当缺页中断发生，需要调入新的页面而内存已满时，选择内存当中哪个物理页面被置换。\n目标 : 尽可能地减少页面的换进换出次数(即缺页中断的次数)。 具体来说，把未来不再使用的或短期内较少使用的页面换出，通常只能在局部性原理指导下依据过去的统计数据来进行预测。\n页面锁定 frame locking：用于描述必须常驻内存的操作系统的关键部分或时间关键（time critical）的应用进程。实现的方式是：在页表中添加锁定标记位(lock bit)。\n实验设置与评价方法 实例：记录一个进程对页访问的一个轨迹\n举例 : 模拟一个实验环境，记录对应的地址访问序列，虚拟地址跟踪(页号, 偏移)\u0026hellip; (3,0) (1,9) (4,1) (2,1) (5,3) (2,0) \u0026hellip; 而offset可以忽略（页不存在才会产生 page fault），生成的页面轨迹 3, 1, 4, 2, 5, 2, 1, \u0026hellip;（替换为，3,1,4,2,5,2,1） 模拟一个页面置换的行为并且记录产生页缺失数的数量\n更少的缺失，更好的性能 局部页面置换算法 最优页面置换算法 基本思路：当一个缺页中断发生时，对于保存在内存当中的每一个逻辑页面，计算在它的下一次访问之前，还需等待多长时间，从中选择等待时间最长的那个，作为被置换的页面。\n这是一种理想情况, 在实际系统中是无法实现的, 因为操作系统无法知道每一个页面要等待多长时间以后才会再次被访问.\n最优页面置换算法（Optimal Page Replacement）可用作其他算法的性能评价的依据，(在一个模拟器上运行某个程序, 并记录每一次的页面访问情况，在第二遍运行时即可使用最优算法)\n在该算法中，会替换在未来最长持续时间内不会使用的页面。如下图所示有 a b c d e五个页，但是只有四个页帧。此时会产生物理页不够，会产生 Page Fault。","title":"ch6 页面置换算法"},{"content":"Objective 覆盖技术 交换技术 虚拟内存 目标 程序局部性原理 基本概念 基本特征 虚拟页式内存管理 覆盖技术 overlay 在固定分区中的主要遇到的问题是进程的大小受到分区的最大大小的限制，这将意味着一个进程将不能跨越另一个进程。为了解决这个问题，早期使用了称为覆盖(overlay) 的解决方案，覆盖技术是为了在较小的可用内存中运行较大的程序。常用于多道程序系统，与分区存储管理配合使用。这样并非所有模块都需要同时存在于内存中，实现了运行大于物理内存大小的程序的技术。\n覆盖技术的原理： 将程序按照执行逻辑拆分为多个功能上相对独立的部分（overlays）, 那些不会同时执行的模块共享同一块内存区域, 按时间先后来运行（分时）。 必要部分，常驻内存的代码和数据，负责管理，在某个时间片将相应的程序和数据导入或导出内存。 可选部分，在其他程序模块中实现, 平时存放在外存中, 在需要用到时才装入内存; 不存在调用关系的模块不必同时装入到内存, 从而可以相互覆盖, 即这些模块共用一个分区. 覆盖技术实例 覆盖技术说明：\n有一个程序，分位A B C D E F G 六个模块，每一个模块占用了一定空间，程序的覆盖树如图所示。\n问：当满足加载（和运行）该程序所需物理内存中的大小是多少？\n使用覆盖技术，实际上不需要将整个程序放在主内存中。只需要在对应时间片时所需要的部分即可，其逻辑调用关系树可以分为：Root-A-D或者 Root-A-E ; Root-B-F 或 Root-C-G 部分。\nRoot是常驻内存，因为其需要调用A B C D E F G 六个模块，占用2KB\n如图：加载与运行改程序所需的物理内存大小是多少？\n​\t(a) 12 KB\n​\t(b) 14 KB\n​\t(c) 10 KB\n​\t(d) 8 KB\n答：由公式可得，最大运行层所需的物理内存为14KB，即拥有 14KB 大小的分区就可以运行上图任意一个分区\ntext 1 2 3 4 Root+A+D = 2KB + 4KB + 6KB = 12KB Root+A+E = 2KB + 4KB + 8KB = 14KB Root+B+F = 2KB + 6KB + 2KB = 10KB Root+C+G = 2KB + 8KB + 4KB = 14KB 如图所示，Overlay Driver ,也就是root区，是一个由用户负责如何覆盖的代码段，并不是操作系统提供的功能。这就意味着Overlay模式下需要用户自行去管理内存，这就是所谓的Overlay Driver，通俗来讲，Overlay Driver 是帮助整个程序如何换入换出各个部分的代码。\n覆盖技术的优缺点 **优势 **\n减少内存需求 减少时间要求 **坏处 **\n覆盖的关系必须由开发者去指定 开发者需要知道整个程序所需的内存 覆盖的模块必须完全没有交集 交换技术 交换技术(swapping)，是操作系统实现的一种内存交换机制，与覆盖技术最大的不同是，覆盖技术是由开发者在程序内实现的内存交换机制，而swapping则是操作系统实现的内存交换机制。\n实现原理：可将暂时不能运行的程序送到外存，从而获得空闲内存空间。操作系统把一个进程的整个地址空间的内容保存到外存中(换出 swap out)，而将外存中的某个进程的地址空间读入到内存中(换入 swap in)。换入换出内容的大小为整个程序的地址空间。\n![交换](../../../images/ch5 Virtual Memory/Swapping.jpg)\n交换技术存在的问题 交换时机的确定：何时需要发生交换? 只当内存空间不够或有不够的危险时换出; 交换区的大小：必须足够大以存放所有用户进程的所有内存映像的拷贝，必须能够对这些内存映像进行直接存取 程序换入时的重定位：换出后再换入的内存位置一定要在原来的位置上嘛?(可能出现寻址问题) 最好采用动态地址映射的方法 交换技术与覆盖技术的比较 覆盖技术是一种编程方法（换入换出单位为程序内的一个模块），用来解决程序大于计算机主内存的限制，在嵌入式系统中通常会考虑该技术。\n交换技术是操作系统中内存交换的机制，换入换出单位是在内存中一个程序为单位，无需开发者给出各个模块之间的逻辑覆盖结构。\n在内存不够用的情形下, 可以采用覆盖技术和交换技术, 但是 :\n覆盖技术：需要程序要自己把整个程序划分为若干个小的功能模块, 并确定各个模块之间的覆盖关系, 增加了程序员的负担. 交换技术 : 以进程作为交换的单位，需要把进程的整个地址空间都换入换出, 增加了处理器的开销. 虚拟内存技术 Objective 像覆盖技术那样，不用把程序的所有内容都放在内存中，因而能够运行比当前的空闲内存空间还要大的程序。但做的更好，由操作系统自动来完成，无需程序员的干涉。\n像交换技术那样，能够实现进程在内存与外存之间的交换，因而获得更多的空闲内存空间。但做的更好，只对进程的部分内容在内存和外存之间进行交换。\n![虚拟内存](../../../images/ch5 Virtual Memory/virtual_memory.jpg)\n程序局部性原理 程序的局部性 (principle of locality)，是虚拟内存中重要组成的概念，表明了，程序在操作系统中运行期间在任意的时间内只需要访问整个内存中的一小部分。有个这个概念就可以对操作系统的内存进行优化，从而获得更好的整体性能。局部性又可分为：\n时间局部性：时间局部性(Temporal locality)是指，访问过的内存地址很快又会被再次访问，例如：在循环中，循环变量每次迭代期间都会被访问到。 空间局部性：空间局部性(Spatial locality)是指，在特定时间访问过的内存位置，则很可能很快就会引用其附近位置的内存地址，例如：在数组中，常规情况下，访问完数组的第一个元素会直接访问数组的下一个元素。 顺序局部性：所谓顺序局部性(Sequential locality)，是指内存位置按升序或降序顺序方法被访问。 分支局部性：分支局部性( Branch locality)，在计算机中，大多数指令是顺序执行的，这种情况通常发生在分支情况下，当在简单结构或分支中，所需要访问的内存地址仅限于在一个小范围内。 等距局部性：等距局部性(Equidistant locality)位于空间和分支之间，是指如果某个位置被访问，那和它相邻等距离的连续地址极有可能会被访问到。 Reference\nexplame of locality\nlocality of reference\n实例：为什么下列代码有问题?\n页面大小为4k，但会产生4M大小。分配给每个进程的物理页面是1\n在一个进程中, 定义了如下的二维数组 int A[1024][1024]. 该数组按行存放在内存, 每一行放在一个页面中。考虑一下程序的编写方法对缺页率的影响?\ntext 1 2 3 4 5 6 7 8 9 # Option #1 for (j = 0; j \u0026lt; 20; j++) for (i = 0; i \u0026lt; 200; i++) x[i][j] = x[i][j] + 1; ## Option #2 for (i = 0; i \u0026lt; 200; i++) for (j = 0; j \u0026lt; 20; j++) x[i][j] = x[i][j] + 1; option1，按照行访问\na(0,0) a(1,0) a(2,0) \u0026hellip;. a(1023,0) a(0,1) a(1,1) a(2,1) \u0026hellip;. a(1023,1) option #2 按照列访问\na(0,0) a(0,1) a(0,2) \u0026hellip;. a(0,1023) a(1,0) a(1,1) a(1,2) \u0026hellip;. a(1,1023) option #1 每个数据值占用1页，总共$1024X 1024$页，会发生$1024X 1024$ 次page fault\noption #2 总共会发生1024次 page fault\n基本特征\n大的用户空间：通过把物理内存和外存相结合, 提供给用户的虚拟内存空间通常大于实际的物理内存，即实现了这两者的分离。如32位的虚拟地址理论上可以访问4GB，而可能计算机上仅有256M的物理内存，但硬盘容量大于4GB。 部分交换：与交换技术相比较，虚拟存储的调入和调出是对部分虚拟地址空间进行的; 不连续性： 物理内存分配的不连续性，虚拟地址空间使用的不连续性。 虚拟页式内存管理 请求调页 Demand Paging 当用户程序要调入内存运行时，不是将该程序的所有页面都装入内存，而是只装入部分的页面，就可启动程序运行。\n在运行的过程中，如果发现要运行的程序或要访问的数据不再内存，则向系统发出缺页的中断请求，系统在处理这个中断时，将外存中相应的页面调入内存，使得该程序能够继续运行。\n为了能够实现请求调页和页面置换，需要在页表项中增加一些位(bit)，来辅助完成该功能。\n![image-20220320174819945](../../../images/ch5 Virtual Memory/image-20220320174819945.png)\n访问位或引用位（Reference bit）：如果该页被访问过(包括读写操作)，则设置此位，用于页面置换算法。\n修改位（Modified bit (or “dirty bit”)）：表示此页在内存中是否被修改（写）过；当系统回收该物理页面时，根据此位来决定是否把它的内容写回辅存\n保护位 Protection bits (RWX)：能否访问该页,, 如只读, 可读写, 可执行等\n驻留位或存在位 present/absent bit or resident bit ：表示该页是在内存中还是在外存；逻辑页号与物理页帧相对应。\nReference\nPage State\nMapping Pages to Page Frames\n如图所示：\nVirtual memory: 64KB\nPhysical memory: 32KB\nPage size: 4KB\nVirtual memory pages: 16\nPhysical memory pages: 8\n![image-20220320175528794](../../../images/ch5 Virtual Memory/image-20220320175528794.png)\n当虚拟内存页第一项为2，那么代表物理页帧为2的项，公式则为 $vmItme \\times page size= 2\\times4069=8192$\n页式内存管理的处理流程 什么是缺页中断 在操作系统中，进程和内核都会通过页表项(PTE)，来访问一个物理页面的，访问一个目前并未被加载在物理内存中的一个页面时，由MMU引发异常，触发缺页中断 Page Fault。通常情况下，缺页中断不能被准确说为是一种异常错误，而是操作系统内存管理的一种机制，通过这一机制可以实现增加程序可用的内存空间。\n内存管理中的处理流程 ![image-20220320180502614](../../../images/ch5 Virtual Memory/image-20220320180502614.png)\n由图可知：\n第一部，CPU Load内存地址，如果有对页面的引用，首先对该页面的引用将追溯到操作系统（第二步），否则产生 Page Fault 到第四步 操作系统查看页表，请求无效则终止，如不在内存中就进行加载到内存中 第二步，找到空闲帧 第三步，使用页面置换算法，从辅存中调度到物理页帧中，换出前修改标记位 第四步，重置页表项标记位为1 第五步，重启导致 Page Fault 的指令 Reference\nPage Fault\n后备存储 后备存储（有时称为辅助存储）是所有其他存储数据的设备的统称：如硬盘\n一个虚拟地址空间的页面可以被映射到一个文件(在二级存储中)的某个位置 代码段 : 映射到可执行二进制文件 动态加载的共享库程序段 : 映射到动态调用的库文件 其他段 : 可能被映射到交换文件(swap file) 虚拟内存性能计算 effective memory access time ETA 是指有效的内存访问时间，计算EAT的公式如下：\nP：页表命中率\n1-P：缺页率\n$EAT= P \\times hit \\quad memory \\quad time + (1-P) \\times miss \\quad memory \\quad time. $​\n例如：在 TLB 找到页的百分比称为命中率。 80% 的命中率意味着在 80% 的时间在 TLB 中找到所需的页码。 如果搜索 TLB 需要 20 纳秒，访问内存需要 100 纳秒，那么当页码在 TLB 中时，映射内存的访问需要 120 纳秒。\n如果在 TLB 中找不到页号（20 纳秒），那么必须首先访问内存中的页表和帧号（100 纳秒），然后访问内存中所需的字节（100 纳秒），总共 220 纳秒。 为了找到有效的内存访问时间，则需要权衡有效的内存访问的概率：\n$EAT = 0.80 \\times 120 + 0.20 \\times 220 = 140 \\quad nano seconds$​\n例2：已知内存访问时间是10millisecond，缓存访问时间为10microseconds。设，TLB命中率15%，则有效内存访问时间是多少\n$EAT = 0.15\\times(10+0.001)+(1-0.15)\\times(10\\times2 + 0.001)$​\n例3：\n已知TLB命中率为 70%，TLB 访问时间为 30ns，访问主存时间为 90ns，则有效内存访问时间是多少\n$0.7 \\times (30+90) + (1-0.7) \\times (30+90\\times2)$​\n$0.7\\times 120 + 0.3\\times210 = 84+63=170$​\nReference\nETA\ncalculate the effective access time\nhttps://courses.engr.illinois.edu/cs423/sp2018/slides/15-memory.pdf\n","permalink":"https://www.161616.top/ch5-virtual-memory/","summary":"Objective 覆盖技术 交换技术 虚拟内存 目标 程序局部性原理 基本概念 基本特征 虚拟页式内存管理 覆盖技术 overlay 在固定分区中的主要遇到的问题是进程的大小受到分区的最大大小的限制，这将意味着一个进程将不能跨越另一个进程。为了解决这个问题，早期使用了称为覆盖(overlay) 的解决方案，覆盖技术是为了在较小的可用内存中运行较大的程序。常用于多道程序系统，与分区存储管理配合使用。这样并非所有模块都需要同时存在于内存中，实现了运行大于物理内存大小的程序的技术。\n覆盖技术的原理： 将程序按照执行逻辑拆分为多个功能上相对独立的部分（overlays）, 那些不会同时执行的模块共享同一块内存区域, 按时间先后来运行（分时）。 必要部分，常驻内存的代码和数据，负责管理，在某个时间片将相应的程序和数据导入或导出内存。 可选部分，在其他程序模块中实现, 平时存放在外存中, 在需要用到时才装入内存; 不存在调用关系的模块不必同时装入到内存, 从而可以相互覆盖, 即这些模块共用一个分区. 覆盖技术实例 覆盖技术说明：\n有一个程序，分位A B C D E F G 六个模块，每一个模块占用了一定空间，程序的覆盖树如图所示。\n问：当满足加载（和运行）该程序所需物理内存中的大小是多少？\n使用覆盖技术，实际上不需要将整个程序放在主内存中。只需要在对应时间片时所需要的部分即可，其逻辑调用关系树可以分为：Root-A-D或者 Root-A-E ; Root-B-F 或 Root-C-G 部分。\nRoot是常驻内存，因为其需要调用A B C D E F G 六个模块，占用2KB\n如图：加载与运行改程序所需的物理内存大小是多少？\n​\t(a) 12 KB\n​\t(b) 14 KB\n​\t(c) 10 KB\n​\t(d) 8 KB\n答：由公式可得，最大运行层所需的物理内存为14KB，即拥有 14KB 大小的分区就可以运行上图任意一个分区","title":"ch5 虚拟内存"},{"content":"overview Q1: 为什么需要非连续内存分配\n连续内存管理 （contiguous memory allocation）, 即 : 操作系统加载到内存以及程序加载到内存中时, 分配一块连续的内存块. 但这种方式会出现碎片问题，而非连续内存分配（Non-contiguous memory allocation ）可以有效的减少碎片（Fragmentation）的出现。\nQ2: 主要的非连续内存分配的管理方法\n分段（Segmentation） 分页（Paging） 页表 （Page Table） 1.非连续内存分配的必要性 连续内存管理的缺陷：\n内存利用率较低（memory wastage），在程序运行时分配的内存是增长的，但在进程使用为达到分配大小时，分配的块并未使用，并且也不能给其他进程使用，造成了内存的浪费。 分配给一个程序的物理内存必须是连续的。 碎片化问题 不灵活（inflexibility），当进程或文件使用的内存超出预期时（即：超出分配的内存块大小），将停止并抛出异常，例如：No disk space。 非连续内存分配的优点:\n一个程序的物理地址空间是非连续的 更好的内存利用和管理，（减少了内存浪费） 允许共享代码与数据(共享库等\u0026hellip;) 支持动态加载和动态链接 非连续内存分配的缺点：\n建立虚拟地址和物理地址的转换难度大 软件方案 硬件方案(采用硬件方案) : 分段 / 分页 分段（segmentation） 首先 segmentation mechanism需要考虑的问题：\n程序的分段地址空间 分段寻址方案 什么是segmentation 段（segmentation）是一个逻辑单元 (logical unit)，例如：\n主程序 main program 程序（主要是指功能的代码，如一段函数） procedure 函数 function 方法 method 对象 object 局部变量和全局变量 local variables, global variables 公共块 common block 堆 stack 符号表 symbol table 数组 arrays [segmentation的逻辑视图]\r可以看到左边是逻辑地址，右边是不连续的物理地址，中间有一个映射机制将两边建立了一个关联关系（ST Segment Table）。通过映射机制将不同的块（如stack，function..）分别映射到内存中的段中。\n分段寻址方案 一维逻辑地址与分段的物理地址对应的方法，分段寻址 （Segmentation Addressing modes），逻辑地址由两部分组成，segment number (==s==) 和 offset (==d==)。\ns 表示段所需的总位数 d 指定了段大小所需的位数 基于硬件的分段管理机制 Segmention hardware 程序表示为了一个二维地址，但在实际物理内存中是一个一维地址。因此需要将二位地址 （ two-dimensional）映射为一个一维物理地址 (one-dimensional)，这个机制就是段表 （Segment Table）,ST映射了逻辑地址的段号和物理地址的段号。并且段的长度与起始信息也是存放在ST中的。\n段表（ST）中的每个条目都有一个base和一个段 limit。==base== 包含段所在在内存中的起始物理地址，而==limit==指定段的长度。\n如图所示，逻辑地址由两部分组成：s和d。s 作为 ST 的 index 。d必须在0和 limit 之间。当超出limit范围是，CPU会产生异常。当 d 合法时，将其添加到 base 以生成地址物理地址。\n起始物理地址 = d + base\n结束物理地址 = base + limit\n分页(Paging) overview 本章主要分为两部分：\n分页地址空间 页寻址方式 分页 (Paging) 与 分段 (Segmentation) 都是非连续性内存管理的机制，分段允许进程的物理地址空间不连续；而分页则是比分段较有优势的另一种内存管理方案。\n分页的特点:\n将每个块划分为固定的页。 划分物理内存为固定大小( fixed-sized ) 的块 (block) 称作帧 ( frame ） 大小是2的幂, e.g. 512 / 4096 / 8192 逻辑地址相同大小的的块 (blocks) 称作 (Pages) 大小是2的幂, e.g. 512 / 4096 / 8192 每个段需要一个页表。 CPU的内存管理单元需要同时支持 分页和分段。 帧 Frame 物理地址 （Physical Address）空间在划分为若干固定大小的块，称为帧。帧由两部分组成，Frame number(f) 和 Frame offset(==d==)\nFrame number(f): 表示物理地址空间的帧所需的位数。 Frame offset(d): 表示页中物理地址空间的页大小或页或页偏移量的字数所需的位数。 如图所示，物理地址是一个二元组 $(f,d)$，页帧占 $F$ 位，共有 $2^F$ 帧；偏移量 o 占了 $S$ 位（一个帧的大小），共$2^S$ 字节 ，则物理地址 = $2^S\\times f+d$ 。\n地址计算：16bit的物理地址空间，页帧大小为9bit（512byte）\n给出物理地址$(f,d) = (3,6)$，求物理地址的位置 。 $S=9$，$F=7$， $f=3$ ，$d=6$ 套用公式得出，$2^9\\times3+6 = 1542$ 页 Page 逻辑地址相同大小的的块 (blocks) 称作 (Pages)，\n页的偏移的大小=帧内偏移的大小 页号 \u0026lt;\u0026gt; 帧号大小 Page也有两部分组成，页号和页的偏移。Page number(==p==) 和 Page offset(==d==)；\nPage number(p) 逻辑地址空间中页所需的位数 Page offset(d)：逻辑地址空间中页偏移量的字数所需的位数。 如图所示，一个逻辑地址表示为一个二元组 $(p,d)$，页帧占 $P$ 位，共有 $2^P$ 帧；偏移量 o 占了 $S$ 位 (一个页的大小)，共$2^S$ 字节 ，则物理地址 = $2^S\\times p+d$ 。\n页的寻址机制 在图中可以看出，逻辑地址是一个连续的地址空间，并且由一个个Page组成，首先CPU寻址，地址分位两块（一个二元组）$(p,d)$，p作为一个index 去查一个page table (以页号为索引的值为帧号) ，以index与base（基地址）作为查找项查找对应的f，$f + (f)d$ 就找到了对应的物理地址。\n页映射到帧 页是连续的虚拟内存 帧是非连续的物理内存 不是所有的页都有对应的帧 页表 overview 页表（page table）结构：页表虚拟内存统用来存储逻辑地址 (Virtual Address) 到物理地址 ( Physical Address ) 映射关系的一种数据结构。\n页表的特性：\n在页表中，每个映射被称为页表项（Page Table Entries (PTEs)）这个页表项负责逻辑地址到物理地址的转换。 该表存储在页表基址寄存器（PTBR ，``Page-table base register`） 随进程运行状态而动态变化。 页表项（PTE） 的组成：\n物理页号（Physical Page Number PPN）\nDPN（disk page number）的磁盘上的页面\n帧号\n标记位\n脏位/修改位（Dirty Bit (D)）：每个PTE都包含的一个修改位，表示页面自加载后是否已写入。是在操作系统中完成，而不是在硬件中完成的操作。 1：表示数据是\u0026quot;脏的（dirty）\u0026quot;，即已写入。 0：数据是“干净的(clean)”，与加载时相同。 存在位/驻留位 (resident bit (R))：每个PTE都包含的一个驻留位，表示逻辑地址是否有一个物理地址与其相对应。 1：当逻辑页在物理内存中时，该位被设置为1。 0：如果为0，则访问该虚拟页面将导致页面错误。 引用位 (Eviction Bit ，In x86: Reference Bit)，过去一段时间内是否有对其引用（是否访问过页内的某个存储单元）。存在页表的第二位，在每次访问页面时（读/写），Reference Bit设置为1。 1：最近被引用过。 0：从未被引用。 Read/Write Bit NX Bit 页表的转换 （translation process） CPU的内存单元（MMU， memory management unit）根据程序的page的页号的若干位, 计算出索引值index, 在页表中搜索这个index, 得到的是帧号, 帧号和原本的offset组成物理地址.\nQuick Activity\n具有16位地址的计算机系统，物理地址大小32KB，每页大小1024bites，的逻辑地址如何进行转换。\n16位地址是0~15，0~9是页内偏移 d, 10~15是页号。\n页式存储管理机制存在的问题 内存访问性能问题(Performance Issues)：内存访问性能，虚拟地址访问需要2次内存访问 第一次获取页表项 第二次访问数据 页表大小问题：页表可以非常大。 页表可能很大 如图实例，32K内存，每页1K，即32个页表项，如果每项占4byte，则为128byte。 对于具有64位地址和1024字节页的机器，页表的大小是多少？ 页：$2^{64}$，页的大小：$2^{10}$，需要建立页表的大小：$2^{64} \\div 2^{10} = 2^{54}$ 如何解决上述问题(what to do)：\nCaching 缓存，solution：快表，利用缓存机制减少对页表的访问。 Indirection 间接访问，solution：多级页表，通过间接引用的方式来减少页表的长度。 快表 translation look-aside buffers (TLBs) 对于上述的性能问题，有效的解决方法是对页表项（PTE）的高速缓存，成为快表（translation look-aside buffer (TLB)）。快表就是将近期访问过的页表项在CPU内部使用硬件缓存，即将近期访问过的项缓存到CPU中；TLB由内存管理单元（MMU）管理\n[未使用页表的情况]\r[TLB mechanics]\r如图所示，如果TLB命中，则直接可以获取到物理地址，如果TLS未命中，则同时将内容缓存到CPU中的TLB。\n多级页表 多级页表是通过间接引用的方式将页号分成多级。多级页表是树状结构，用于保存页表。\n例子，考虑两个级别的页表再次在具有$2^{12}=4$ KB Page 的32-bit架上，\nQ1：第二级页面表格有多少位？\n$4KB\\div4B = 1024=2^{10}$ Q2：顶级页表有多少位？\n$32-12-10=10$ 10bits 表示0级索引，10bits表示1级索引，12bits表示页面内的偏移量。每一个子页表的开头作为上一个页表的页号物理页号填写到上一级页表当中。\n多级页表计算题 一个32位操作系统中进程的虚拟地址空间可达到4GB，假设用户地址空间为2GB，页面大小为4KB，\nQ1：则一个进程最多可以有（）页。\n$2G = 2\\times 2^{10} \\times 2^{10} \\times 2^{10} = 2^{31} $​ $4KB = 2^2 \\times 2^{10} = 2^{12}$​​ 即 $2^{31}\\div2^{12} = 2^{19}$​​ Q2：若用4个字节表示一页的物理页号，则页表本身就占用（）？\n$4B\\times 2^{19} = 2^2 \\times 2^{19} = 2^{21} = 2M\t$ Q3：即需要（）个页面存放。\n$2M\\div4KB = 2^{21}\\div2^{12} = 2^{9} = 512$ 反置页表 使用页寄存器(Page Registers)，又名反置页表(Inverted page table (IPT))，是为了减少所占用存储空间的一种做法。使用反置页表的原因是在大地址空间(64-bits)的系统上，多级页表会变得繁琐，例如，逻辑地址空间增长速度快于物理空间地址。（以页帧号为索引，页表号为内容 ）\n此时产生一个问题：如何查找？（frame number是索引，pagenumber是内容）。基于关联存储器（associative memory）的方案：\n关联存储器也可以并行的查找，与寄存器可以同时比较，如果找到匹配值，则输出与键对应的值。\n如果帧数较少，页寄存器可以被放置在关联内存中 在关联内存中查找逻辑页号 关联存储器的代价：\n如果帧数较少，页寄存器可以被放置在关联内存中；在关联内存中查找逻辑页号：\n成功，则拿到帧号 失败，也错误异常（page fault） 限制因素：很难在单个时钟周期完成；耗电。\nIPT的具体思路为，不让页表与逻辑地址空间的大小对应，而是让页表与物理地址空间大小相对应，每个帧都与一个寄存器相关联，通常该寄存器包含：\n引用位(Residence bit）：标记该帧(Frame) 是否被进程占用。 占用页号(Occupier)：页帧对应的页号 保护位(Protection bits)：约定页的访问方式(R/W) Quick Activity\n系统有\n物理内存大小：16MB( $4096 \\times 4096 = 4K \\times 4K = 16MB $ ) 页大小为：4KB( $4096bytes = 4KB $​​​​) 页帧数为4K(4096)。 假定每一个页寄存器(page resigter)占8bytes， $4096 \\times 8 = 32Kbytes$。 那么页寄存器占用的开销为： $32Kb\\div 16Mb \\approx 0.2%$​​。此时和虚拟地址没有关联了。创建​的进程的物理地址就与页寄存器对应。\n页寄存器方案的优缺点：\n优点： 页表大小相对于物理内存而言很小 页表大小与逻辑（虚拟）地址无关 缺点： 页表信息对调后，需要帧号可以找到页号 在页寄存器中搜索逻辑地址中的页号 基于hash计算的反向页表 hash table hash table，一个数学计算的方法。通过hash计算输入为page number，输出为 frame number。由图所示\nVirtual Page Number (VPN): p, q Page Frame Number (PFN): r Offset: d Hash Function: h(x) Hashed Page Table with schema (key, VPN, PFN, Pointer to next entry with key) 每一个页表项 hash table方式的工作流\n操作系统从CPU拿到 虚拟内存 p，并执行 h（p） 以获取 same_key。\n操作系统查找哈希页表中的第一个条目，其中 key = same_key，并根据第一个页表的 虚拟内存页号 p。(根据第一个项中的指针来查找第二个项。它知道第二个项具有相同的键 = same_key，因为多级页表工作流是如此。操作系统根据第二项的页表号字段检查p。\n操作系统从第二个条目中获取帧号 r。r 对应于虚拟页号 p 的正确物理帧号；操作系统使用 页帧r + 偏移量 d 在物理内存中查找它想要的物理地址。\n由图可以看出，为了提高效率，可以对hash函数增加一个参数 pid ，可以作为输入，来设计一个hash函数，算出对应的frame number。这种可以很好的解决映射的开销。\n段页式存储管理 段页式存储管理机制 ( Segmented Paging )，是从页式与段式两种技术中获得最佳特性，纯分段不是很流行，并且在许多操作系统中都没有使用。但是，分段可以与分页相结合。段式存储在内存保护方面有优势，页式存储在内存利用和转移到后备存储方面有优势。\n段页式存储管理机制是在段式存储的基础上给每一个段加一级页表。即，主存被划分为可变大小的段，这些段又被进一步划分为固定大小的页，此时就有三个定义词Definitions 段号 **Segment Number ** 页号 **Page Number ** 页偏移 **Page Offset ** 如图所示，通过为每个段创建页表，可以减小页表的大小（要实现这一点，需要硬件支持），CPU提供的地址现在将被划分为段号、页号和偏移量。\n段页式存储机制工作流如图所示：\nCPU生成一个逻辑地址分为两部分：段号和段偏移量。段偏移必须小于段限制。偏移量又分为页码和页偏移。要映射页表中的确切页码，请将页码添加到页表库中。带有页帧号+页偏移组成了实际被映射的物理地址。\n通过只想相同的页表基址，时间进程间的段共享\nhttps://www.cs.utexas.edu/users/witchel/372/lectures/15.VirtualMemory.pdf\nhttps://www.pvpsiddhartha.ac.in/dep_it/lecture%20notes/OS/unit4.pdf\nhttps://www.studytonight.com/operating-system/difference-between-paging-and-segmentation\nhttps://www.utc.edu/sites/default/files/2021-04/2800-lecture8-memeory-management.pdf\nhttps://www.geeksforgeeks.org/paging-in-operating-system/?ref=lbp\nhttps://cw.fel.cvut.cz/old/_media/courses/ae3b33osd/osd-lec6-14.pdf https://www.inf.ed.ac.uk/teaching/courses/os/slides/10-paging16.pdf\n","permalink":"https://www.161616.top/ch4-non-contiguous-memory-allocation/","summary":"overview Q1: 为什么需要非连续内存分配\n连续内存管理 （contiguous memory allocation）, 即 : 操作系统加载到内存以及程序加载到内存中时, 分配一块连续的内存块. 但这种方式会出现碎片问题，而非连续内存分配（Non-contiguous memory allocation ）可以有效的减少碎片（Fragmentation）的出现。\nQ2: 主要的非连续内存分配的管理方法\n分段（Segmentation） 分页（Paging） 页表 （Page Table） 1.非连续内存分配的必要性 连续内存管理的缺陷：\n内存利用率较低（memory wastage），在程序运行时分配的内存是增长的，但在进程使用为达到分配大小时，分配的块并未使用，并且也不能给其他进程使用，造成了内存的浪费。 分配给一个程序的物理内存必须是连续的。 碎片化问题 不灵活（inflexibility），当进程或文件使用的内存超出预期时（即：超出分配的内存块大小），将停止并抛出异常，例如：No disk space。 非连续内存分配的优点:\n一个程序的物理地址空间是非连续的 更好的内存利用和管理，（减少了内存浪费） 允许共享代码与数据(共享库等\u0026hellip;) 支持动态加载和动态链接 非连续内存分配的缺点：\n建立虚拟地址和物理地址的转换难度大 软件方案 硬件方案(采用硬件方案) : 分段 / 分页 分段（segmentation） 首先 segmentation mechanism需要考虑的问题：\n程序的分段地址空间 分段寻址方案 什么是segmentation 段（segmentation）是一个逻辑单元 (logical unit)，例如：\n主程序 main program 程序（主要是指功能的代码，如一段函数） procedure 函数 function 方法 method 对象 object 局部变量和全局变量 local variables, global variables 公共块 common block 堆 stack 符号表 symbol table 数组 arrays [segmentation的逻辑视图]\r可以看到左边是逻辑地址，右边是不连续的物理地址，中间有一个映射机制将两边建立了一个关联关系（ST Segment Table）。通过映射机制将不同的块（如stack，function.","title":"ch4 操作内存管理 - 非连续内存分配"},{"content":"一 计算机体系结构及内存分层体系 1.计算机硬件体系结构大致分为\nCPU，完成程序的执行控制 主存 （main memory），放置程序代码和数据 I/O（外）设备，配合程序工作。 2.内存分层体系（金字塔结构) 什么是内存结构：CPU所访问的指令和数据在什么地方。\n第一类：位于CPU内部，操作操作系统无法直接进行管理的，寄存器，cache；特点，速度快，容量小\n第二类：主存或物理内存，主要用来放置操作系统本身及要运行的代码；其特点是，容量比cache要大很多，单速度交于cache要慢一些。\n第三类：磁盘，永久保存的数据及代码，当对于主存要慢，容量比主存大很多。\n操作系统的作用可以将数据访问的速度（cache与内存）与存储的大小（硬盘）很好的融合在一起\n3.OS管理内存时需要完成的目标\n① 抽象：逻辑地址空间（将物理内存，外设等抽象成逻辑地址空间，只需要访问对应地址空间)\n② 保护（独立）：操作系统完成隔离机制实现，独立地址空间（每段程序执行时，不受其他程序的影响）\n③ 共享：进程间安全，可靠，有效，进行数据传递，访问相同的内存。\n④ 虚拟化：更多的地址空间（利用磁盘的空间) 4.需要完成在操作系统中管理内存的不同方法\n操作系统层面\n程序重定位\n分段\n分页\n虚拟内存\n按需分页虚拟内存\n硬件层面\n必须知道内存架构 MMU(内存管理单元)：处理CPU的内存访问请求 二 地址空间\u0026amp;地址生成 地址空间的定义\n地址生成器\n地址安全检查\n1.内存地址的定义 ① 物理内存地址：硬件支持的地址空间，如主存（内存）和硬盘，由硬件完成管理和控制 ② 逻辑内存地址：一个程序运行时所需要的内存范围。\n两者间的关系：逻辑地址空间最终是一个存在的物理地址空间，两者间的映射关系是由操作系统来管理的\n2.逻辑地址生成过程（把代码转化为计算机能理解语言） 一段代码运行→编译→汇编语言→机器语言→产生链接文件→将硬盘中程序载入到内存当中运行（完成逻辑地址的分配）\n如C中变量的名字，函数的位置，为逻辑地址。\n3.物理地址生成（逻辑地址对应的物理地址的过程） CPU方面 MMU表示映射关系\n① CPU ALU Arithmetic logic unit 发出请求，为逻辑地址 ② CPU MMU Memory management unit 查找逻辑地址映射表，不存在会去内存中找 ③ 控制器提出内存请求（需要的内容，内容即指令） 内存方面\n④ 内存通过bus发送物理内存地址的内容给CPU OS方面 建立逻辑地址和物理地址之间的映射关系（需在前四部前将映射管理建立好）\n4. 内存安全监测：检查运行的内存是否在对应内存空间范围内 操作系统确保程序的有效访问的地址空间，==起始地址==与==长度==（基址寄存器和界限寄存器），也是操作系统所建立和维护的对应的表。\n三、连续式内存分配：内存碎片与分区的动态分配 内存碎片问题 分区动态分配 第一适配 最佳适配 最差适配 压缩式碎片整理 交换式碎片整理 1.内存碎片问题 什么是碎片？ 为程序分配空间后有一些无法被利用的空闲空间，这就是内存碎片。\n碎片的种类：\n外部碎片：在分配单元间未使用的内存（没分配给程序的那块） 内部碎片：在分配单元中未使用的内存（分配给程序之后，程序无法使用） 2.分区的动态分配 操作系统为了管理空闲与非空闲空间，有对应的内存分配算法：\n首次适配 First-Fit 最优适配 Worst Fit 最佳适配 Best Fit sort First-Fit Worst Fit Best Fit overview 空闲分区链首开始查找，直至找到一个能满足其大小要求的空闲分区为止。然后再按 照作业的大小，从该分区中划出一块内存分配给请求者，余下的空闲分区仍留在空闲分区链中。 最坏适应算法与最佳适应算法的排序正好相反，它的队列指针总是指向最大的空闲区，在进行分配时，总是从最大的空闲 区开始查寻；Worst fit 会按大小递减的顺序形成空闲区链，分配时直接从空闲区链的第一个空闲区中分配 寻找整个空间中最适合的的空间块（比分配请求的大小要大，但其差值是最小的） benefit 该算法倾向于使用内存中低地址部分的空闲区，在高地址部分的空闲区很少被利用，从而保留了高地址部分的大空闲 区。显然为以后到达的大作业分配大的内存空间创造了条件。 给文件分配分区后剩下的空闲区不至于太小，产生碎片的几率最小，对中小型文件分配分区操作有利 每次分配给文件的都是最合适该文件大小的分区，避免吧大空间块拆散 defect 不确定性 容易产生外碎片，（低地址部分不断被划分，留下许多难以利用、很小的空闲区，而每次查找又都从低地址部分开始，会增加查找的开销。) 重新分配慢\n产生很多外部碎片\n易于破坏大的空闲块以致大分区无法被分配 缺点：内存中留下许多难以利用的小的空闲区。 require 按地址排序 分配需要找到合适的分区 重新分配需要检查，看是否自由分区能合并于相邻的空间分区。 按尺寸排列的空闲块列表\n分配很快（获得最大的分区）\n重新分配需要合并相邻的空闲分区，然后调整空闲块列表 需要按尺寸排列好空闲块列表分配需要寻找适合的分区重新分配（空闲块利用)需要搜索及合并相邻的空闲分区 四、连续式内存分配 (contiguous memory allocation)：压缩式与交换式碎片化整理 无论采用那种算法，都会产生内碎片internal fragmentation 和外碎片 External fragmentation ，所以需要对这些碎片进行处理使得碎片减少甚至消失。\n紧致算法 compaction：能够调整内存中运行的程序的位置 什么时候做（重定位） 开销 换入换出 swapping：swapping mechanism，是进程可以临时从主存中交换（或移动）到辅存储（磁盘），并使该内存可供其他进程使用。稍后，系统将进程从辅存交换回主存。 English Version: https://www.geeksforgeeks.org/memory-management-in-operating-system/\n","permalink":"https://www.161616.top/ch3-contiguous-memory-allocation/","summary":"一 计算机体系结构及内存分层体系 1.计算机硬件体系结构大致分为\nCPU，完成程序的执行控制 主存 （main memory），放置程序代码和数据 I/O（外）设备，配合程序工作。 2.内存分层体系（金字塔结构) 什么是内存结构：CPU所访问的指令和数据在什么地方。\n第一类：位于CPU内部，操作操作系统无法直接进行管理的，寄存器，cache；特点，速度快，容量小\n第二类：主存或物理内存，主要用来放置操作系统本身及要运行的代码；其特点是，容量比cache要大很多，单速度交于cache要慢一些。\n第三类：磁盘，永久保存的数据及代码，当对于主存要慢，容量比主存大很多。\n操作系统的作用可以将数据访问的速度（cache与内存）与存储的大小（硬盘）很好的融合在一起\n3.OS管理内存时需要完成的目标\n① 抽象：逻辑地址空间（将物理内存，外设等抽象成逻辑地址空间，只需要访问对应地址空间)\n② 保护（独立）：操作系统完成隔离机制实现，独立地址空间（每段程序执行时，不受其他程序的影响）\n③ 共享：进程间安全，可靠，有效，进行数据传递，访问相同的内存。\n④ 虚拟化：更多的地址空间（利用磁盘的空间) 4.需要完成在操作系统中管理内存的不同方法\n操作系统层面\n程序重定位\n分段\n分页\n虚拟内存\n按需分页虚拟内存\n硬件层面\n必须知道内存架构 MMU(内存管理单元)：处理CPU的内存访问请求 二 地址空间\u0026amp;地址生成 地址空间的定义\n地址生成器\n地址安全检查\n1.内存地址的定义 ① 物理内存地址：硬件支持的地址空间，如主存（内存）和硬盘，由硬件完成管理和控制 ② 逻辑内存地址：一个程序运行时所需要的内存范围。\n两者间的关系：逻辑地址空间最终是一个存在的物理地址空间，两者间的映射关系是由操作系统来管理的\n2.逻辑地址生成过程（把代码转化为计算机能理解语言） 一段代码运行→编译→汇编语言→机器语言→产生链接文件→将硬盘中程序载入到内存当中运行（完成逻辑地址的分配）\n如C中变量的名字，函数的位置，为逻辑地址。\n3.物理地址生成（逻辑地址对应的物理地址的过程） CPU方面 MMU表示映射关系\n① CPU ALU Arithmetic logic unit 发出请求，为逻辑地址 ② CPU MMU Memory management unit 查找逻辑地址映射表，不存在会去内存中找 ③ 控制器提出内存请求（需要的内容，内容即指令） 内存方面\n④ 内存通过bus发送物理内存地址的内容给CPU OS方面 建立逻辑地址和物理地址之间的映射关系（需在前四部前将映射管理建立好）","title":"ch3 操作内存管理 - 连续内存分配"},{"content":"在Windows Terminal中WSL无法打开错误代码是 process exited with code 4294967295 (0xffffffff)，但在命令行中 通过 \u0026quot;C:\\Windows\\System32\\wsl.exe\u0026quot; -d ubuntu18 是正常的\n解决方法是：通过修改启动的命令为 wsl.exe ~ -d Ubuntu 中间加一个 ~ 可以很好的解决掉\n这种方法存在一个问题，打开的wsl终端将为根目录而不是当前windows目录\nReference Unable to launch WSL Ubuntu\n","permalink":"https://www.161616.top/wsl-problem-with-windows-terminal/","summary":"在Windows Terminal中WSL无法打开错误代码是 process exited with code 4294967295 (0xffffffff)，但在命令行中 通过 \u0026quot;C:\\Windows\\System32\\wsl.exe\u0026quot; -d ubuntu18 是正常的\n解决方法是：通过修改启动的命令为 wsl.exe ~ -d Ubuntu 中间加一个 ~ 可以很好的解决掉\n这种方法存在一个问题，打开的wsl终端将为根目录而不是当前windows目录\nReference Unable to launch WSL Ubuntu","title":"Windows Terminal无法加载WSL  [process exited with code 4294967295 (0xffffffff)]"},{"content":"Introduction 本文对colly如何使用，整个代码架构设计，以及一些使用实例的收集。\nColly是Go语言开发的Crawler Framework，并不是一个完整的产品，Colly提供了类似于Python的同类产品（BeautifulSoup 或 Scrapy）相似的表现力和灵活性。\nColly这个名称源自 Collector 的简写，而Collector 也是 Colly的核心。\nColly Official Docs，内容不是很多，最新的消息也很就远了，仅仅是活跃在Github\nConcepts Architecture 从理解上来说，Colly的设计分为两层，核心层和解析层，\nCollector ：是Colly实现，该组件负责网络通信，并负责在Collector 作业运行时执行对应事件的回调。 Parser ：这个其实是抽象的，官网并未对此说明，goquery和一些htmlquery，通过这些就可以将访问的结果解析成类Jquery对象，使html拥有了，XPath选择器和CSS选择器 通常情况下Crawler的工作流生命周期大致为\n构建客户端 发送请求 获取响应的数据 将相应的数据解析 对所需数据处理 持久化 而Colly则是将这些概念进行封装，通过将事件注册到每个步骤中，通过事件的方式对数据进行清理，抽象来说，Colly面向的是过程而不是对象。大概的工作架构如图\nevent 通过上述的概念，可以大概了解到 Colly 是一个基于事件的Crawler，通过开发者自行注册事件函数来触发整个流水线的工作\nColly 具有以下事件处理程序：\nOnRequest：在请求之前调用 OnError ：在请求期间发生错误时调用 OnResponseHeaders ：在收到响应头后调用 OnResponse： 在收到响应后调用 OnHTML：如果接收到的内容是 HTML，则在 OnResponse 之后立即调用 OnXML ：如果接收到的内容是 HTML 或 XML，则在 OnHTML 之后立即调用 OnScraped：在 OnXML 回调之后调用 OnHTMLDetach：取消注册一个OnHTML事件函数，取消后，如未执行过得事件将不会再被执行 OnXMLDetach：取消注册一个OnXML事件函数，取消后，如未执行过得事件将不会再被执行 Reference\ngoquery\nhtmlquery\nUtilities 简单使用 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/gocolly/colly\u0026#34; ) func main() { // Instantiate default collector c := colly.NewCollector( // Visit only domains: hackerspaces.org, wiki.hackerspaces.org colly.AllowedDomains(\u0026#34;hackerspaces.org\u0026#34;, \u0026#34;wiki.hackerspaces.org\u0026#34;), ) // On every a element which has href attribute call callback c.OnHTML(\u0026#34;a[href]\u0026#34;, func(e *colly.HTMLElement) { link := e.Attr(\u0026#34;href\u0026#34;) // Print link fmt.Printf(\u0026#34;Link found: %q -\u0026gt; %s\\n\u0026#34;, e.Text, link) // Visit link found on page // Only those links are visited which are in AllowedDomains c.Visit(e.Request.AbsoluteURL(link)) }) // Before making a request print \u0026#34;Visiting ...\u0026#34; c.OnRequest(func(r *colly.Request) { fmt.Println(\u0026#34;Visiting\u0026#34;, r.URL.String()) }) // Start scraping on https://hackerspaces.org c.Visit(\u0026#34;https://hackerspaces.org/\u0026#34;) } 错误处理 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/gocolly/colly\u0026#34; ) func main() { // Create a collector c := colly.NewCollector() // Set HTML callback // Won\u0026#39;t be called if error occurs c.OnHTML(\u0026#34;*\u0026#34;, func(e *colly.HTMLElement) { fmt.Println(e) }) // Set error handler c.OnError(func(r *colly.Response, err error) { fmt.Println(\u0026#34;Request URL:\u0026#34;, r.Request.URL, \u0026#34;failed with response:\u0026#34;, r, \u0026#34;\\nError:\u0026#34;, err) }) // Start scraping c.Visit(\u0026#34;https://definitely-not-a.website/\u0026#34;) } 处理本地文件 word.html\nhtml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 \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;title\u0026gt;Document title\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p\u0026gt;List of words\u0026lt;/p\u0026gt; \u0026lt;ul\u0026gt; \u0026lt;li\u0026gt;dark\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;smart\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;war\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;cloud\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;park\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;cup\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;worm\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;water\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;rock\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;warm\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;footer\u0026gt;footer for words\u0026lt;/footer\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;github.com/gocolly/colly/v2\u0026#34; ) func main() { t := \u0026amp;http.Transport{} t.RegisterProtocol(\u0026#34;file\u0026#34;, http.NewFileTransport(http.Dir(\u0026#34;.\u0026#34;))) c := colly.NewCollector() c.WithTransport(t) words := []string{} c.OnHTML(\u0026#34;li\u0026#34;, func(e *colly.HTMLElement) { words = append(words, e.Text) }) c.Visit(\u0026#34;file://./words.html\u0026#34;) for _, p := range words { fmt.Printf(\u0026#34;%s\\n\u0026#34;, p) } } 使用代理交换器 通过 ProxySwitcher , 可以直接使用一批代理IP池进行访问了，然而这里只有RR，如果需要其他的均衡算法，需要有自己实现了\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package main import ( \u0026#34;bytes\u0026#34; \u0026#34;log\u0026#34; \u0026#34;github.com/gocolly/colly\u0026#34; \u0026#34;github.com/gocolly/colly/proxy\u0026#34; ) func main() { // Instantiate default collector c := colly.NewCollector(colly.AllowURLRevisit()) // Rotate two socks5 proxies rp, err := proxy.RoundRobinProxySwitcher(\u0026#34;socks5://127.0.0.1:1337\u0026#34;, \u0026#34;socks5://127.0.0.1:1338\u0026#34;) if err != nil { log.Fatal(err) } c.SetProxyFunc(rp) // Print the response c.OnResponse(func(r *colly.Response) { log.Printf(\u0026#34;Proxy Address: %s\\n\u0026#34;, r.Request.ProxyURL) log.Printf(\u0026#34;%s\\n\u0026#34;, bytes.Replace(r.Body, []byte(\u0026#34;\\n\u0026#34;), nil, -1)) }) // Fetch httpbin.org/ip five times for i := 0; i \u0026lt; 5; i++ { c.Visit(\u0026#34;https://httpbin.org/ip\u0026#34;) } } 随机延迟 该功能可以对行为设置一种特征，以免被反扒机器人检测，并禁止我们，如速率限制和延迟\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/gocolly/colly\u0026#34; \u0026#34;github.com/gocolly/colly/debug\u0026#34; ) func main() { url := \u0026#34;https://httpbin.org/delay/2\u0026#34; // Instantiate default collector c := colly.NewCollector( // Attach a debugger to the collector colly.Debugger(\u0026amp;debug.LogDebugger{}), colly.Async(true), ) // Limit the number of threads started by colly to two // when visiting links which domains\u0026#39; matches \u0026#34;*httpbin.*\u0026#34; glob c.Limit(\u0026amp;colly.LimitRule{ DomainGlob: \u0026#34;*httpbin.*\u0026#34;, Parallelism: 2, RandomDelay: 5 * time.Second, }) // Start scraping in four threads on https://httpbin.org/delay/2 for i := 0; i \u0026lt; 4; i++ { c.Visit(fmt.Sprintf(\u0026#34;%s?n=%d\u0026#34;, url, i)) } // Start scraping on https://httpbin.org/delay/2 c.Visit(url) // Wait until threads are finished c.Wait() } 多线程请求队列 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/gocolly/colly\u0026#34; \u0026#34;github.com/gocolly/colly/queue\u0026#34; ) func main() { url := \u0026#34;https://httpbin.org/delay/1\u0026#34; // Instantiate default collector c := colly.NewCollector(colly.AllowURLRevisit()) // create a request queue with 2 consumer threads q, _ := queue.New( 2, // Number of consumer threads \u0026amp;queue.InMemoryQueueStorage{MaxSize: 10000}, // Use default queue storage ) c.OnRequest(func(r *colly.Request) { fmt.Println(\u0026#34;visiting\u0026#34;, r.URL) if r.ID \u0026lt; 15 { r2, err := r.New(\u0026#34;GET\u0026#34;, fmt.Sprintf(\u0026#34;%s?x=%v\u0026#34;, url, r.ID), nil) if err == nil { q.AddRequest(r2) } } }) for i := 0; i \u0026lt; 5; i++ { // Add URLs to the queue q.AddURL(fmt.Sprintf(\u0026#34;%s?n=%d\u0026#34;, url, i)) } // Consume URLs q.Run(c) } 异步 默认情况下，Colly的工作模式是同步的。可以使用 Async 函数启用异步模式。在异步模式下，我们需要调用Wait 等待Collector 工作完成。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/gocolly/colly/v2\u0026#34; ) func main() { urls := []string{ \u0026#34;http://webcode.me\u0026#34;, \u0026#34;https://example.com\u0026#34;, \u0026#34;http://httpbin.org\u0026#34;, \u0026#34;https://www.perl.org\u0026#34;, \u0026#34;https://www.php.net\u0026#34;, \u0026#34;https://www.python.org\u0026#34;, \u0026#34;https://code.visualstudio.com\u0026#34;, \u0026#34;https://clojure.org\u0026#34;, } c := colly.NewCollector( colly.Async(), ) c.OnHTML(\u0026#34;title\u0026#34;, func(e *colly.HTMLElement) { fmt.Println(e.Text) }) for _, url := range urls { c.Visit(url) } c.Wait() } 最大深度 深度是在访问这个页面时，其页面还有link，此时需要采集到入口link几层的link？默认1\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/gocolly/colly\u0026#34; ) func main() { // Instantiate default collector c := colly.NewCollector( // MaxDepth is 1, so only the links on the scraped page // is visited, and no further links are followed colly.MaxDepth(1), ) // On every a element which has href attribute call callback c.OnHTML(\u0026#34;a[href]\u0026#34;, func(e *colly.HTMLElement) { link := e.Attr(\u0026#34;href\u0026#34;) // Print link fmt.Println(link) // Visit link found on page e.Request.Visit(link) }) // Start scraping on https://en.wikipedia.org c.Visit(\u0026#34;https://en.wikipedia.org/\u0026#34;) } Reference gocolly\ncolly\n","permalink":"https://www.161616.top/golib-gocolly/","summary":"Introduction 本文对colly如何使用，整个代码架构设计，以及一些使用实例的收集。\nColly是Go语言开发的Crawler Framework，并不是一个完整的产品，Colly提供了类似于Python的同类产品（BeautifulSoup 或 Scrapy）相似的表现力和灵活性。\nColly这个名称源自 Collector 的简写，而Collector 也是 Colly的核心。\nColly Official Docs，内容不是很多，最新的消息也很就远了，仅仅是活跃在Github\nConcepts Architecture 从理解上来说，Colly的设计分为两层，核心层和解析层，\nCollector ：是Colly实现，该组件负责网络通信，并负责在Collector 作业运行时执行对应事件的回调。 Parser ：这个其实是抽象的，官网并未对此说明，goquery和一些htmlquery，通过这些就可以将访问的结果解析成类Jquery对象，使html拥有了，XPath选择器和CSS选择器 通常情况下Crawler的工作流生命周期大致为\n构建客户端 发送请求 获取响应的数据 将相应的数据解析 对所需数据处理 持久化 而Colly则是将这些概念进行封装，通过将事件注册到每个步骤中，通过事件的方式对数据进行清理，抽象来说，Colly面向的是过程而不是对象。大概的工作架构如图\nevent 通过上述的概念，可以大概了解到 Colly 是一个基于事件的Crawler，通过开发者自行注册事件函数来触发整个流水线的工作\nColly 具有以下事件处理程序：\nOnRequest：在请求之前调用 OnError ：在请求期间发生错误时调用 OnResponseHeaders ：在收到响应头后调用 OnResponse： 在收到响应后调用 OnHTML：如果接收到的内容是 HTML，则在 OnResponse 之后立即调用 OnXML ：如果接收到的内容是 HTML 或 XML，则在 OnHTML 之后立即调用 OnScraped：在 OnXML 回调之后调用 OnHTMLDetach：取消注册一个OnHTML事件函数，取消后，如未执行过得事件将不会再被执行 OnXMLDetach：取消注册一个OnXML事件函数，取消后，如未执行过得事件将不会再被执行 Reference\ngoquery\nhtmlquery\nUtilities 简单使用 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.","title":"Go每日一库 - gocolly"},{"content":" Select panel title → Inspect → Panel JSON\nSet “type” to \u0026ldquo;table-old\u0026rdquo;\nApply\nThe visualization should now appear as Table (old) and in the right side will appear Column Styles\nColumn Styles → Options → Name pattern set the name of the column to hide\nType → Hidden\nReference：Hide column in table in v8.0\n","permalink":"https://www.161616.top/grafana-8.0/","summary":"Select panel title → Inspect → Panel JSON\nSet “type” to \u0026ldquo;table-old\u0026rdquo;\nApply\nThe visualization should now appear as Table (old) and in the right side will appear Column Styles\nColumn Styles → Options → Name pattern set the name of the column to hide\nType → Hidden\nReference：Hide column in table in v8.0","title":"grafana v8.0+ 隐藏表格字段"},{"content":"overview kubernetes的设计里面大致上分为3部分：\nAPI驱动型的特点 (API-driven) 控制循环（control loops）与 条件触发 （Level Trigger） API的可延伸性 而正因为这些设计特性，才使得kubernetes工作非常稳定。\n什么是Level Trigger与 Edge trigger 看到网上有资料是这么解释两个属于的：\n条件触发(level-trigger，也被称为水平触发)LT指： 只要满足条件，就触发一个事件(只要有数据没有被获取，就不断通知)。\n边缘触发(edge-trigger)ET: 每当状态变化时，触发一个事件。\n通过查询了一些资料，实际上也不明白这些究竟属于哪门科学中的理论，但是具体解释起来看的很明白。\nLEVEL TRIGGERING：当电流有两个级别，VH 和 VL。代表了两个触发事件的级别。如果将VH 设置为LED在正时钟。当电压为VH时，LED可以在该时间线任何时刻点亮。这称为LEVEL TRIGGERING，每当遇到VH 时间线就会触发事件。事件是在时间内的任何时刻开始，直到满足条件。\nEdge TRIGGERING:\n如图所示，会看到上升线与下降线，当事件在上升/下降边缘触发时（两个状态的交点），称为边缘触发（Edge TRIGGERING:）。\n如果需要打开LED灯，则当时钟从VL转换到VH时才会亮起，而不是一家处在对应的时钟线上，仅仅是在过渡时亮起。\n为什么kubernetes使用Level Trigger而不使用Edge trigger 如图所述，两种不同的设计模式，随着时间形状进行相应，当系统在由高转低，或由低转高时，系统处在关闭或者不可控的异常状态下，应如何触发对应的事件呢。\n换一种方式来来解释，比如说通过 加法运算，如下，i=3，当给I+4作为一个操作触发事件。\ntext 1 2 3 4 5 # let i=3 # let i+=4 # let i # echo $i 7 当为Edge trigger时操作的情况下，将看到 i+4 ,而在 level trigger 时看到的是 i=7。这里将会从``i+4` 一直到下一个信号的触发。\n信号的干扰 通常情况下，两者是没有区别的，但在大规模分布式网络环境中，有很多因素的影响下，任何都是不可靠的，在这种情况下会改变了我们对事件信号的感知。\n如图所示，图为Level Trigger与Edge trigger 的信号发生模拟，在理想情况下，两者间并没有什么不同。\n一次中断场景 由图可知，Edge trigger当在恰当的时间点发生信号中断，会对整个流产生很大的影响，甚至改变了整个状态，对于较少的干扰并不会对有更好的结果，而单次的中断，使Edge trigger错过了从高到低的变化，而 level trigger 基本上保证了整个信号量的所有改变状态。\n两次中断的场景下 由图可看到，信号的上升和下降中如果存在了中断，Edge trigger 丢失了上升的信号，但最终状态是正确的。\n在信号状态的两次变化时发生了两次中断，Level Trigger与Edge trigger 之间的区别很明显，Edge trigger 的信号错过了第一次上升，而Level Trigger 保持了最后观察到的状态，知道拿到了其他状态，这种模式保证了得到的信号基本的正确性，但是发生延迟到中断恢复后。\n通过运算来表示两种模式的变化情况 完整的信号\nbash 1 2 3 4 5 6 7 8 # let i=2 # let i+1 # let i-=1 # let i+1 # echo $i 3 Edge trigger\nbash 1 2 3 4 5 6 7 8 # let i=2 # let i+1 (# let i-=1) miss this # let i+1 # echo $i 4 如何使理想状态和实际状态一样呢？ 在Kubernetes中，不仅仅是观察对象的一个信号，还观察了其他两个信号，集群的期待状态与实际状态，期望的状态是用户期望集群所处的状态，如我运行了2个实例（pod）。在最理想的场景下，集群的实际状态与期待状态是相同的，但这个过程会受到任意的外界因素干扰被影响下，实际状态与理想状态发生偏差。\nKubernetes必须接受实际状态，并将其与所需状态调和。不断地这样做，采取两种状态，确定其之间的差异，并纠正其不断的更改，以使实际状态达到理想状态。\n如图所示，在一个Edge trigger 中，最终的结果很可能会与理想中的结果发生偏差。\n当初始实例为1时，并希望扩展为5个副本，然后再向下缩容到2个副本，则Edge trigger环境下将看到以下状态：系统的实际状态不能立即对这些命令作出反应。正如图所述，当只有3个副本在运行时，它可能会终止3个副本。这就给我们留下了0个副本，而不是所需的2个副本。\ntext 1 2 3 # let replicas=1 # let replicas += 4 # 此时副本数为5，但是这个过程需要时间而不是立即完成至理想状态 # let replicas -= 3 # 当未完成时又接到信号的变化，此时副本数为3，减去3，很可能实际状态为0，与理想状态2发生了偏差 而使用Level Trigger时，会总是比较完整的期望状态和实际状态，直到实际状态与期望状态相同。这大大减少了状态同步间（错误）的产生。\nsummary 每一种触发器的产生一定有其道理，Edge trigger本身并不是很差，只是应用场景的不同，而使用的模式也不同，比如nginx的高性能就是使用了Edge trigger模型，如nginx使用了 Level trigger在大并发下，当发生了变更信号等待返回时，发生大量客户端连接在侦听队列，而Edge trigger模型则不会出现这种情况。\n综上所述，kubernetes在设计时，各个组件需要感知数据的最终理想状态，无需担心错过数据变化的过程。而设计kubernentes系统消息通知机制（或数据实时通知机制），也应满足以下要求：\n实时性（即数据变化时，相关组件感觉越快越好）。消息必须是实时的。在list/watch机制下，每当apiserver资源有状态变化事件时，都会及时将事件推送到客户端，以保证消息的实时性。\n消息序列：消息的顺序也很重要。在并发场景下，客户端可能会在短时间内收到同一资源的多个事件。对于关注最终一致性的kubernetes来说，它需要知道哪个是最新的事件，并保证资源的最终状态与最新事件所表达的一致。kubernetes在每个资源事件中都携带一个resourceVersion标签，这个标签是递增的。因此，客户端在并发处理同一资源的事件时，可以比较resourceVersion，以确保最终状态与最新事件的预期状态一致。\n消息的可靠性，保证消息不丢失或者有可靠的重新获取的机制（比如 kubelet和 kube-apisever之间的网络波动（network flashover ）需要保证kubelet在网络恢复后可以接收到网络故障时产生的消息）。\n正是因为Kubernetes使用了 Level trigger才让集群更加可靠。\nReference nginx-event-driven-architecture\nWhat-is-meant-by-edge-triggering-and-level-triggering\n","permalink":"https://www.161616.top/ch05-listwatch-mechanism/","summary":"overview kubernetes的设计里面大致上分为3部分：\nAPI驱动型的特点 (API-driven) 控制循环（control loops）与 条件触发 （Level Trigger） API的可延伸性 而正因为这些设计特性，才使得kubernetes工作非常稳定。\n什么是Level Trigger与 Edge trigger 看到网上有资料是这么解释两个属于的：\n条件触发(level-trigger，也被称为水平触发)LT指： 只要满足条件，就触发一个事件(只要有数据没有被获取，就不断通知)。\n边缘触发(edge-trigger)ET: 每当状态变化时，触发一个事件。\n通过查询了一些资料，实际上也不明白这些究竟属于哪门科学中的理论，但是具体解释起来看的很明白。\nLEVEL TRIGGERING：当电流有两个级别，VH 和 VL。代表了两个触发事件的级别。如果将VH 设置为LED在正时钟。当电压为VH时，LED可以在该时间线任何时刻点亮。这称为LEVEL TRIGGERING，每当遇到VH 时间线就会触发事件。事件是在时间内的任何时刻开始，直到满足条件。\nEdge TRIGGERING:\n如图所示，会看到上升线与下降线，当事件在上升/下降边缘触发时（两个状态的交点），称为边缘触发（Edge TRIGGERING:）。\n如果需要打开LED灯，则当时钟从VL转换到VH时才会亮起，而不是一家处在对应的时钟线上，仅仅是在过渡时亮起。\n为什么kubernetes使用Level Trigger而不使用Edge trigger 如图所述，两种不同的设计模式，随着时间形状进行相应，当系统在由高转低，或由低转高时，系统处在关闭或者不可控的异常状态下，应如何触发对应的事件呢。\n换一种方式来来解释，比如说通过 加法运算，如下，i=3，当给I+4作为一个操作触发事件。\ntext 1 2 3 4 5 # let i=3 # let i+=4 # let i # echo $i 7 当为Edge trigger时操作的情况下，将看到 i+4 ,而在 level trigger 时看到的是 i=7。这里将会从``i+4` 一直到下一个信号的触发。\n信号的干扰 通常情况下，两者是没有区别的，但在大规模分布式网络环境中，有很多因素的影响下，任何都是不可靠的，在这种情况下会改变了我们对事件信号的感知。\n如图所示，图为Level Trigger与Edge trigger 的信号发生模拟，在理想情况下，两者间并没有什么不同。","title":"理解kubernetes listwatch机制原理"},{"content":"什么是schema schema一词起源于希腊语中的form或figure，但具体应该如何定义schema取决于应用环境的上下文。schema有不同的类型，其含义与数据科学、教育、营销和SEO以及心理学等领域密切相关。\n在维基百科中将schema解释为，图式，在心里学中主要描述一种思维或行为类型，用来组织资讯的类别，以及资讯之间的关系。它也可以被描述为先入为主思想的心理结构，表示世界某些观点的框架，或是用于组织和感知新资讯的系统。\n但在计算机科学中，从很多地方都可以看到 schema 这个名词，例如 database，openldap，programing language等的。这里可以简单的吧schema 理解为 元数据集合 （metadata component）数据模型，主要包含元素及属性的声明，与其他数据结构组成。\n数据库中的schema 在数据库中，schema 就像一个骨架结构，代表整个数据库的逻辑视图。它设计了应用于特定数据库中数据的所有约束。当在数据建模时，就会产生一个schema。在谈到关系数据库]和面向对象数据库时经常使用schema。有时也指将结构或文本的描述。\n数据库中schema描述数据的形状以及它与其他模型、表和库之间的关系。在这种情况下，数据库条目是schema的一个实例，包含schema中描述的所有属性。\n数据库schema通常分为两类：定义数据文件实际存储方式的**物理数据库schema ；和逻辑数据库schema **，它描述了应用于存储数据的所有逻辑约束，包括完整性、表和视图。常见包括\n星型模式（star schema） 雪花模式（snowflake schema） 事实星座模型（fact constellation schema 或 galaxy schema） 星型模式是类似于一个简单的数据仓库图，包括一对多的事实表和维度表。它使用非规范化数据。\n雪花模式是更为复杂的一种流行的数据库模式，在该模式下，维度表是规范化的，可以节省存储空间并最大限度地减少数据冗余。\n事实星座模式远比星型模式和雪花模式复杂得多。它拥有多个共享多个维度表的事实表。\nKubernetes中的schema 通过上面的阐述，大概上可以明白 schema究竟是什么东西了，在Kubernetes中也有schema的概念，通过对kubernetes中资源（GVK）的规范定义、相互关系间的映射等，schema即k8s资源对象元数据。\n而kubernetes中资源对象即 Group Version Kind 这些被定义在 staging/src/k8s.io/api/type.go中，即平时所操作的yaml文件，例如\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: apps/v1 kind: Deployment metadata: name: ngx namespace: default spec: selector: matchLabels: app: ngx template: metadata: labels: app: nginx spec: containers: - name: ngx-schema image: nginx ports: - containerPort: 80 而对应的的即为TypeMeta 、ObjectMeta 和 DeploymentSpec,\nTypeMeta 为 kind 与 apiserver\nObjectMeta 为 Name 、Namespace CreationTimestamp等段。\nDeploymentSpec 则对应了 yaml 中的 spec。\n而整个yaml组成了 一个 k8s的资源对象。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 type Deployment struct { metav1.TypeMeta `json:\u0026#34;,inline\u0026#34;` // Standard object metadata. // +optional metav1.ObjectMeta `json:\u0026#34;metadata,omitempty\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=metadata\u0026#34;` // Specification of the desired behavior of the Deployment. // +optional Spec DeploymentSpec `json:\u0026#34;spec,omitempty\u0026#34; protobuf:\u0026#34;bytes,2,opt,name=spec\u0026#34;` // Most recently observed status of the Deployment. // +optional Status DeploymentStatus `json:\u0026#34;status,omitempty\u0026#34; protobuf:\u0026#34;bytes,3,opt,name=status\u0026#34;` } register.go 则是将对应的资源类型注册到schema中的类\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var ( // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) localSchemeBuilder = \u0026amp;SchemeBuilder AddToScheme = localSchemeBuilder.AddToScheme ) // Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, \u0026amp;Deployment{}, \u0026amp;DeploymentList{}, \u0026amp;StatefulSet{}, \u0026amp;StatefulSetList{}, \u0026amp;DaemonSet{}, \u0026amp;DaemonSetList{}, \u0026amp;ReplicaSet{}, \u0026amp;ReplicaSetList{}, \u0026amp;ControllerRevision{}, \u0026amp;ControllerRevisionList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil } 而 apimachinery 包则是 schema的实现，通过看其内容可以发下，kubernetes中 schema就是 GVK 的属性约束 与 GVR 之间的映射。\n通过示例了解schema 例如在 apps/v1/deployment 这个资源，在代码中表示 k8s.io/api/apps/v1/types.go ，如果需要对其资源进行扩展那么需要怎么做？如，建立一个 StateDeplyment 资源\ntext 1 2 3 4 5 type Deployment struct { metav1.TypeMeta `json:\u0026#34;,inline\u0026#34;` // Standard object metadata. // +optional metav1.ObjectMeta `json:\u0026#34;metadata,omitempty\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=metadata\u0026#34;` 如上述代码所示，Deployment 中的 metav1.TypeMeta 和 metav1.ObjectMeta\n那么我们复制一个 Deployment 为 StateDeployment，注意，因为 Deployment的两个属性， metav1.TypeMeta 和 metav1.ObjectMeta 分别实现了不同的方法，如图所示\n所以在实现方法时，需要实现 DeepCopyinfo ， DeepCopy 和继承接口 Object 的 DeepCopyObject 方法\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StateDeployment) DeepCopyInto(out *StateDeployment) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(\u0026amp;out.ObjectMeta) in.Spec.DeepCopyInto(\u0026amp;out.Spec) in.Status.DeepCopyInto(\u0026amp;out.Status) return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StateDeployment. func (in *StateDeployment) DeepCopy() *StateDeployment { if in == nil { return nil } out := new(StateDeployment) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *StateDeployment) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } 那么扩展一个资源的整个流为：\n资源类型在：k8s.io/api/{Group}/types.go 资料类型的实现接口 k8s.io/apimachinery/pkg/runtime/interfaces.go.Object 其中是基于 Deployment 的类型，metav1.TypeMeta 和 metav1.ObjectMeta metav1.TypeMeta 实现了 GetObjectKind() ；metav1.ObjectMeta 实现了DeepCopyinfo=() ， DeepCopy() ，还需要实现 DeepCopyObject() 最后注册资源到schema中 k8s.io/api/apps/v1/register.go ","permalink":"https://www.161616.top/ch03-kubernetes-schema/","summary":"什么是schema schema一词起源于希腊语中的form或figure，但具体应该如何定义schema取决于应用环境的上下文。schema有不同的类型，其含义与数据科学、教育、营销和SEO以及心理学等领域密切相关。\n在维基百科中将schema解释为，图式，在心里学中主要描述一种思维或行为类型，用来组织资讯的类别，以及资讯之间的关系。它也可以被描述为先入为主思想的心理结构，表示世界某些观点的框架，或是用于组织和感知新资讯的系统。\n但在计算机科学中，从很多地方都可以看到 schema 这个名词，例如 database，openldap，programing language等的。这里可以简单的吧schema 理解为 元数据集合 （metadata component）数据模型，主要包含元素及属性的声明，与其他数据结构组成。\n数据库中的schema 在数据库中，schema 就像一个骨架结构，代表整个数据库的逻辑视图。它设计了应用于特定数据库中数据的所有约束。当在数据建模时，就会产生一个schema。在谈到关系数据库]和面向对象数据库时经常使用schema。有时也指将结构或文本的描述。\n数据库中schema描述数据的形状以及它与其他模型、表和库之间的关系。在这种情况下，数据库条目是schema的一个实例，包含schema中描述的所有属性。\n数据库schema通常分为两类：定义数据文件实际存储方式的**物理数据库schema ；和逻辑数据库schema **，它描述了应用于存储数据的所有逻辑约束，包括完整性、表和视图。常见包括\n星型模式（star schema） 雪花模式（snowflake schema） 事实星座模型（fact constellation schema 或 galaxy schema） 星型模式是类似于一个简单的数据仓库图，包括一对多的事实表和维度表。它使用非规范化数据。\n雪花模式是更为复杂的一种流行的数据库模式，在该模式下，维度表是规范化的，可以节省存储空间并最大限度地减少数据冗余。\n事实星座模式远比星型模式和雪花模式复杂得多。它拥有多个共享多个维度表的事实表。\nKubernetes中的schema 通过上面的阐述，大概上可以明白 schema究竟是什么东西了，在Kubernetes中也有schema的概念，通过对kubernetes中资源（GVK）的规范定义、相互关系间的映射等，schema即k8s资源对象元数据。\n而kubernetes中资源对象即 Group Version Kind 这些被定义在 staging/src/k8s.io/api/type.go中，即平时所操作的yaml文件，例如\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: apps/v1 kind: Deployment metadata: name: ngx namespace: default spec: selector: matchLabels: app: ngx template: metadata: labels: app: nginx spec: containers: - name: ngx-schema image: nginx ports: - containerPort: 80 而对应的的即为TypeMeta 、ObjectMeta 和 DeploymentSpec,","title":"理解kubernetes schema"},{"content":"下载源码 根据kubernetes github 方式可以\ntext 1 2 3 4 5 mkdir -p $GOPATH/src/k8s.io cd $GOPATH/src/k8s.io git clone https://github.com/kubernetes/kubernetes cd kubernetes make 如果有需要可以切换到对应的版本进行学习或者修改，一般kubernetes版本为对应tag\ntext 1 2 3 git fetch origin [远程tag名] git checkout [远程tag名] git branch 配置goland kubernetes本身是支持 go mod 的，但源码这里提供了所有的依赖在 staging/src/k8s.io/ 目录下，可以将此目录内的文件复制到 vendor下。\nbash 1 cp -a staging/src/k8s.io/* vendor/k8s.io/ 对于 k8s.io/kubernetes/pkg/ 发红的（找不到依赖的），可以将手动创建一个目录在 vendor/k8s.io/ 将克隆下来的根目录 pkg 复制到刚才的目录下。\ngoland中，此时不推荐使用go mod模式了，这里goland一定要配置GOPATH的模式。对应的GOPATH加入 {project}/vender即可。 这里可以添加到 goland中 project GOPATH里。\n","permalink":"https://www.161616.top/ch01-k8s-perpare/","summary":"下载源码 根据kubernetes github 方式可以\ntext 1 2 3 4 5 mkdir -p $GOPATH/src/k8s.io cd $GOPATH/src/k8s.io git clone https://github.com/kubernetes/kubernetes cd kubernetes make 如果有需要可以切换到对应的版本进行学习或者修改，一般kubernetes版本为对应tag\ntext 1 2 3 git fetch origin [远程tag名] git checkout [远程tag名] git branch 配置goland kubernetes本身是支持 go mod 的，但源码这里提供了所有的依赖在 staging/src/k8s.io/ 目录下，可以将此目录内的文件复制到 vendor下。\nbash 1 cp -a staging/src/k8s.io/* vendor/k8s.io/ 对于 k8s.io/kubernetes/pkg/ 发红的（找不到依赖的），可以将手动创建一个目录在 vendor/k8s.io/ 将克隆下来的根目录 pkg 复制到刚才的目录下。\ngoland中，此时不推荐使用go mod模式了，这里goland一定要配置GOPATH的模式。对应的GOPATH加入 {project}/vender即可。 这里可以添加到 goland中 project GOPATH里。","title":"k8s开发环境准备 - 如何配置开发环境"},{"content":"What is IPC IPC [Inter-Process Communication] 进程间通信，指至少两个进程或线程间传送数据或信号的一些技术或方法。在Linux/Unix中，提供了许多IPC。Unix七大IPC：\nPipe：无名管道，最基本的IPC，单向通信，仅在父/子进程之间，也就是将一个程序的输出直接交给另一个程序的输入。常见使用为 ps -ef|grep xxx FIFO [(First in, First out)] 或 有名管道（named pipe）:与Pipe不同，FIFO可以让两个不相关的进程可以使用FIFO。单向。 Socket 和 Unix Domain Socket：socket和Unix套接字，双向。适用于网络通信，但也可以在本地使用。适用于不同的协议。 消息队列 Message Queue: SysV 消息队列、POSIX 消息队列。 Signal: 信号，是发送到正在运行的进程通知以触发其事件的特定行为，是IPC的一种有限形式。 Semaphore：信号量，通常用于IPC或同一进程内的线程间通信。他们之间使用队列进行消息传递、控制或内容的传递。（常见SysV 信号量、POSIX 信号量） Shared memory：（常见SysV 共享内存、POSIX 共享内存）。共享内存，是在进程（程序）之间传递数据的有效方式，目的是在其之间提供通信。 每种IPC都有不通的特点，每种方式对资源的使用及性能都是不通的\n管道 I/O是最快的，但为单向通信，需要工作在 父/子 进程关系之间。 UNIX 套接字可以在本地连接不同的进程，并且具有更高的带宽，并且没有固有的消息边界。 TCP/IP套接字可以连接任何进程。并且可以通过网络连接，但是对资源会有更多的开销，同样的没有固定的消息边界。 Reference comparsion Unix/Linux IPC\nWhat is D-Bus 提到，D-Bus就不能不提一下freedesktop，而 D-Bus 仅仅作为freedesktop.org的一部分。\nD-Bus 桌面总线 (Desktop Bus)，的简写，也是Linux- IPC机制，不同于Unix 7大基础IPC的是，D-Bus是在这些IPC类型之上实现的中间件IPC，D-Bus使用了基础IPC中一种过多种，其设计的目的是在Linux桌面环境，提供服务的标准化。但目前并没有合入主线内核中。\n作为中间件IPC，D-Bus的性能较低与其他IPC模式，因为在通信过程中会进行很多上下文切换，如果通过Dbus来发送消息，会先将其发送到内核，然后将其送回D-Bus。AF_BUS 补丁是新的套接字类型，用来减少D-Bus上下文的切换。\n更多可参考：https://en.wikipedia.org/wiki/D-Bus\nD-Bus组成 D-Bus是 一个IPC的实现方式，在架构上分位三层。\nLayer 1 libdbus：freedesktop机构提供的一个免费开源的一个由C语言编写的 low-level API 。是提供dbus功能的库。是高级API绑定的低级API。 Layer 2 dbus daemon：dbus实现的IPC守护进行，随Linux启动，通过不通进程对其的连接，实现了多进程间消息的路由（包含内核、网络、桌面等） Layer 3 Wapper libraries （high-level API）： 对 low-level API libdbus的封装 ，例如 libdbus-qt libdbus-python github.com/godbus/dbus，这些不同编程语言实现的Wapper是不同开发者应该使用的lib，其简化了D-Bus的开发难度。 dbus 基本概念 总线 在 D-Bus 中，bus是一个核心概念。它是应用程序可以进行方法调用、发送信号和侦听信号的通道。有两种预定义的bus：会话总线和系统总线。\n会话总线（Session Bus）：普通进程创建，可同时存在多条。会话总线属于某个进程私有，它用于进程间传递消息。\n系统总线（System Bus）：在引导时就会启动，它由操作系统和后台进程使用，安全性非常好，以使得任意的应用程序不能欺骗系统事件。当然，如果一个应用程序需要接受来自系统总线的消息，他也可以直接连接到系统总线中，但是他能发送的消息是受限的。系统总线最常见的用途是在系统范围事件发生时发送系统范围的通知。添加新的存储设备、网络连接更改事件和关闭相关事件都是系统总线何时更适合通信总线的示例。\n通常情况下只存在一个System Bus，但可以存在多个Session Bus（每个桌面会话一个）。\n总线以dbus-daemon的形式存在与系统中，该进程专门将消息从一个进程传递到另一个进程。该守护进程还将向总线上的所有应用程序转发通知。\nbus name 总线名称 Bus Name，不能单单以字面意思 总线名称 来理解，官方对其解释为**：Connections have one or more bus names associated with them. A connection has exactly one bus name that is a unique connection name.**，可以出bus name其实是用来连接名称。主要是用来标识一个应用和消息总线的连接。总线名称主要分为两类：唯一名称与公共名称。\n唯一连接名称 unique connection names ：以冒号（\u0026rsquo;：\u0026rsquo;）字符开头的 bus name是唯一的连接名称。例如 :1.0。每个连接都有一个唯一名。在一个 消息总线的生命期内，不会有两个连接有相同的唯一名。 公共连接名称 well-known bus names：公共名称是以反向DNS域名（小写）例如：org.fedoraproject.FirewallD1。 如果DNS 域名包含连字符/减号，则应将其替换为下划线，如果包含数字，则应通过添加下划线进行转义。例如： 7-zip.org的bus name应该定义为 org._7_zip.Archiver。 对象路径 对象路径(Object Paths) 是用于引用对象实例的名称（类似于 C++ 或 Java 对象）。从概念上来说，D-Bus在消息交换中每个参与者都有任意个对象实例，如文件系统一样，Dbus中的参与者中的对象实例也会形成一个层次树。如，在CentOS7中 firewalld开发的D-Bus API 使用了/org/fedoraproject/FirewallD1的层次结构。\n在定义一个对象路径时，需要注意以下：\n路径可以是任意长度 路径必须以 ASCII \u0026lsquo;/\u0026rsquo;（整数 47）字符开头，并且必须由以斜杠字符分隔的元素组成。 每个元素只能包含 ASCII 字符 [AZ][az][0-9]_ 不允许出现 空字符串 多个 / 字符不能依次出现。 除非路径是根路径（单个/字符），否则不允许尾随 /字符。 接口名称 interface，在每个 Object Path都包含多个接口，一般情况下接口名称应以==反向 DNS 域名开头==（小写），（同 Java 中的接口名称）。在命名规则上，与bus name相同。\n例如：CentOS7中 firewalld开发的D-Bus API 定义的管理zone的接口 org.fedoraproject.FirewallD1.config.zone。如果DNS名称中包含-，则应将其替换为下划线 _。如果DNS 域名包含紧跟在 . 之后的数字，则接口名称应在数字之前添加一个下划线。例如，如果 7-zip.org 插件定义了一个接口，应该被命名为org._7_zip.Plugin.\n成员方法名称 成员方法名称，Member names ,对于定义了接口后，需要实现其接口的放法，如需要获得firewalld的zone时，就可以调用 org.fedoraproject.FirewallD1.getDefaultZone 。在D-Bus中Member names通常由“驼峰式”（camel-case）命名 。\ndbus 在Linux中，如CentOS dbus包括 dbus daemon及一些cli commad。这些包dbuslib\nD-Bus的消息 最基本的D-Bus协议是一对一的通信协议。与直接使用socket不同，D-Bus是面向消息的协议。 D-Bus的所有功能都是通过在连接上流动的消息完成的。\n而在D-Bus中有四种类型的消息\nMETHOD_CALL 方法调用 METHOD_RETURN 方法返回 ERROR 错误 SIGNAL 信号：与方法调用不同，信号发射没有响应。信号发射只是一个类型为 SIGNAL 的消息。它必须具有三个标头字段：PATH给出发出信号的对象，加上INTERFACE并MEMBER给出信号的完全限定名称。 消息返回的类型\nConventional name 十进制值 说明 INVALID 0 这是个无效类型 METHOD_CALL 1 方法调用，该方法会有提示 METHOD_RETURN 2 方法返回的数据 ERROR 3 错误返回，第一个是其错误的信息 SIGNAL 4 信号的发射 CentOS的dbus服务管理 在CentOS7中，作为systemd的一部分D-BUS会从Systemd获取套接字文件描述符，并使用D-Bus交换当前进程生成的socket信息。而PID 1 不使用 PolicyKit 来控制对特权操作的访问，而是完全依赖于 low-level API D-Bus 。（这样做是为了避免 PolicyKit 和 systemd/PID 1 之间的循环依赖。）而有些特权进程（例如关机/重启/挂起/登陆）可以通过logind进行管理的。\n由此，可以知道在CentOS中，dbus相关的服务大概有 dbus,与 logind。\ndbus包含：\ndbus-daemon：dbus架构中 layer 2的 dbus-damon\ndbus-send: dbus提供的命令行工具，可以用dbus-send来发送消息。\ndbus-monitor: dbus提供的命令行工具，用于监视总线上流动的消息。\ndbus-launch： shell脚本启动消息总线的命令行工具\ndbus配置文件说明 dbus-daemon守护进程，有两个配置文件，一个为 session bus，另外一个为 system bus。\n标准的system bus文件 /usr/local/share/dbus-1/system.conf session bus配置 /usr/local/share/dbus-1/session.conf中配置。在一般情况下，不会操作这两个文件，因其会引入 /etc/dbus-1 中的system.conf 或 session.conf。\n配置文件包含的标签：\n更多的注释可以参考：dbus-daemon\nxml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 # 根元素 \u0026lt;busconfig\u0026gt; \u0026lt;!-- 根据指定的 -system或 -session 来选择的配置文件 --\u0026gt; \u0026lt;type\u0026gt;system\u0026lt;/type\u0026gt; \u0026lt;!-- dbus-daemon运行的用户 --\u0026gt; \u0026lt;user\u0026gt;dbus\u0026lt;/user\u0026gt; \u0026lt;!-- Fork into daemon mode --\u0026gt; \u0026lt;fork/\u0026gt; \u0026lt;!-- We use system service launching using a helper --\u0026gt; \u0026lt;standard_system_servicedirs/\u0026gt; \u0026lt;!-- This is a setuid helper that is used to launch system services --\u0026gt; \u0026lt;servicehelper\u0026gt;//usr/libexec/dbus-1/dbus-daemon-launch-helper\u0026lt;/servicehelper\u0026gt; \u0026lt;!-- Write a pid file --\u0026gt; \u0026lt;pidfile\u0026gt;/run/dbus/messagebus.pid\u0026lt;/pidfile\u0026gt; \u0026lt;!-- Enable logging to syslog --\u0026gt; \u0026lt;syslog/\u0026gt; \u0026lt;!-- 指定授权机制。如果不存在，所有的机制都被允许。 --\u0026gt; \u0026lt;auth\u0026gt;EXTERNAL\u0026lt;/auth\u0026gt; \u0026lt;!-- 总线监听的地址，支持unix socket，tcp，system等 --\u0026gt; \u0026lt;listen\u0026gt;unix:path=/run/dbus/system_bus_socket\u0026lt;/listen\u0026gt; \u0026lt;listen\u0026gt;unix:path=/tmp/foo\u0026lt;/listen\u0026gt; \u0026lt;listen\u0026gt;tcp:host=localhost,port=1234\u0026lt;/listen\u0026gt; \u0026lt;policy context=\u0026#34;default\u0026#34;\u0026gt; \u0026lt;!-- All users can connect to system bus --\u0026gt; \u0026lt;allow user=\u0026#34;*\u0026#34;/\u0026gt; \u0026lt;!-- Holes must be punched in service configuration files for name ownership and sending method calls --\u0026gt; \u0026lt;deny own=\u0026#34;*\u0026#34;/\u0026gt; \u0026lt;deny send_type=\u0026#34;method_call\u0026#34;/\u0026gt; \u0026lt;!-- Signals and reply messages (method returns, errors) are allowed by efault --\u0026gt; \u0026lt;allow send_type=\u0026#34;signal\u0026#34;/\u0026gt; \u0026lt;allow send_requested_reply=\u0026#34;true\u0026#34; send_type=\u0026#34;method_return\u0026#34;/\u0026gt; \u0026lt;allow send_requested_reply=\u0026#34;true\u0026#34; send_type=\u0026#34;error\u0026#34;/\u0026gt; \u0026lt;!-- All messages may be received by default --\u0026gt; \u0026lt;allow receive_type=\u0026#34;method_call\u0026#34;/\u0026gt; \u0026lt;allow receive_type=\u0026#34;method_return\u0026#34;/\u0026gt; \u0026lt;allow receive_type=\u0026#34;error\u0026#34;/\u0026gt; \u0026lt;allow receive_type=\u0026#34;signal\u0026#34;/\u0026gt; \u0026lt;!-- Allow anyone to talk to the message bus --\u0026gt; \u0026lt;allow send_destination=\u0026#34;org.freedesktop.DBus\u0026#34; send_interface=\u0026#34;org.freedesktop.DBus\u0026#34; /\u0026gt; \u0026lt;allow send_destination=\u0026#34;org.fedoraproject.FirewallD1\u0026#34; send_interface=\u0026#34;org.fedorapproject.FirewallD1\u0026#34; /\u0026gt; \u0026lt;allow send_destination=\u0026#34;org.freedesktop.DBus\u0026#34; send_interface=\u0026#34;org.freedesktop.DBus.Introspectable\u0026#34;/\u0026gt; \u0026lt;!-- But disallow some specific bus services --\u0026gt; \u0026lt;deny send_destination=\u0026#34;org.freedesktop.DBus\u0026#34; send_interface=\u0026#34;org.freedesktop.DBus\u0026#34; send_member=\u0026#34;UpdateActivationEnvironment\u0026#34;/\u0026gt; \u0026lt;deny send_destination=\u0026#34;org.freedesktop.DBus\u0026#34; send_interface=\u0026#34;org.freedesktop.DBus.Debug.Stats\u0026#34;/\u0026gt; \u0026lt;deny send_destination=\u0026#34;org.freedesktop.DBus\u0026#34; send_interface=\u0026#34;org.freedesktop.systemd1.Activator\u0026#34;/\u0026gt; \u0026lt;/policy\u0026gt; \u0026lt;!-- Only systemd, which runs as root, may report activation failures. --\u0026gt; \u0026lt;policy user=\u0026#34;root\u0026#34;\u0026gt; \u0026lt;allow send_destination=\u0026#34;org.freedesktop.DBus\u0026#34; send_interface=\u0026#34;org.freedesktop.systemd1.Activator\u0026#34;/\u0026gt; \u0026lt;/policy\u0026gt; \u0026lt;!-- root may monitor the system bus. --\u0026gt; \u0026lt;policy user=\u0026#34;root\u0026#34;\u0026gt; \u0026lt;allow send_destination=\u0026#34;org.freedesktop.DBus\u0026#34; send_interface=\u0026#34;org.freedesktop.DBus.Monitoring\u0026#34;/\u0026gt; \u0026lt;/policy\u0026gt; \u0026lt;!-- If the Stats interface was enabled at compile-time, root may use it. Copy this into system.local.conf or system.d/*.conf if you want to enable other privileged users to view statistics and debug info --\u0026gt; \u0026lt;policy user=\u0026#34;root\u0026#34;\u0026gt; \u0026lt;allow send_destination=\u0026#34;org.freedesktop.DBus\u0026#34; send_interface=\u0026#34;org.freedesktop.DBus.Debug.Stats\u0026#34;/\u0026gt; \u0026lt;/policy\u0026gt; \u0026lt;!-- Include legacy configuration location --\u0026gt; \u0026lt;include ignore_missing=\u0026#34;yes\u0026#34;\u0026gt;/etc/dbus-1/system.conf\u0026lt;/include\u0026gt; \u0026lt;!-- 包含的子配置文件. --\u0026gt; \u0026lt;includedir\u0026gt;system.d\u0026lt;/includedir\u0026gt; \u0026lt;includedir\u0026gt;/etc/dbus-1/system.d\u0026lt;/includedir\u0026gt; \u0026lt;!-- This is included last so local configuration can override what\u0026#39;s in this standard file --\u0026gt; \u0026lt;include ignore_missing=\u0026#34;yes\u0026#34;\u0026gt;/etc/dbus-1/system-local.conf\u0026lt;/include\u0026gt; \u0026lt;include if_selinux_enabled=\u0026#34;yes\u0026#34; selinux_root_relative=\u0026#34;yes\u0026#34;\u0026gt;contexts/dbus_contexts\u0026lt;/include\u0026gt; \u0026lt;/busconfig\u0026gt; 通过命令行发送dbus消息 dbus支持通过命令发送一个dbus消息，如获取可用的dbus 服务。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 dbus-send --session \\ --dest=org.freedesktop.DBus \\ --type=method_call \\ --print-reply \\ /org/freedesktop/DBus \\ org.freedesktop.DBus.ListNames method return time=1631452206.288425 sender=org.freedesktop.DBus -\u0026gt; destination=:1.29 serial=3 reply_serial=2 array [ string \u0026#34;org.freedesktop.DBus\u0026#34; string \u0026#34;org.freedesktop.login1\u0026#34; string \u0026#34;org.freedesktop.systemd1\u0026#34; string \u0026#34;org.fedoraproject.FirewallD1\u0026#34; string \u0026#34;org.freedesktop.PolicyKit1\u0026#34; string \u0026#34;:1.17\u0026#34; string \u0026#34;:1.0\u0026#34; string \u0026#34;:1.29\u0026#34; string \u0026#34;:1.18\u0026#34; string \u0026#34;:1.1\u0026#34; ] 返回org.freedesktop.DBus service\nbash 1 2 3 4 5 6 dbus-send --session \\ --dest=org.freedesktop.DBus \\ --type=method_call \\ --print-reply \\ /org/freedesktop/DBus \\ org.freedesktop.DBus.Introspectable.Introspect 使用dbus api操作linux防火墙 Firewalld是一个基于动态区域的防火墙守护进程，自 2009 年左右开始开发，目前为Fedora 18 以及随后的 RHEL7 和 CentOS 7 中的默认防火墙机制。\nFirewalld被配置为systemd D-Bus 服务。请注意下面的“Type=dbus”指令。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ cat /usr/lib/systemd/system/firewalld.service [Unit] Description=firewalld - dynamic firewall daemon Before=network.target Before=libvirtd.service Before=NetworkManager.service Conflicts=iptables.service ip6tables.service ebtables.service [Service] EnvironmentFile=-/etc/sysconfig/firewalld ExecStart=/usr/sbin/firewalld --nofork --nopid $FIREWALLD_ARGS ExecReload=/bin/kill -HUP $MAINPID # supress to log debug and error output also to /var/log/messages StandardOutput=null StandardError=null Type=dbus BusName=org.fedoraproject.FirewallD1 [Install] WantedBy=basic.target Alias=dbus-org.fedoraproject.FirewallD1.service 知道了firewalld服务是基于D-Bus的，就可以通过D-Bus来操作防火墙。\n查看dbus注册的服务是否包含firewalld，这里需要注意的是，firewalld依赖dbus服务，每次启动firewalld时注册到dbus总线内。所以需要先启动dbus-daemon与 firewalld 服务。\nbash 1 2 dbus-send --system --dest=org.freedesktop.DBus --type=method_call --print-reply \\ /org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep FirewallD 查看得知 org.fedoraproject.FirewallD1 为firewalld接口\n查看接口所拥有的方法、属性、信号等信息\nbash 1 2 dbus-send --system --dest=org.fedoraproject.FirewallD1 --print-reply \\ /org/fedoraproject/FirewallD1 org.freedesktop.DBus.Introspectable.Introspect 获得zone\ntext 1 2 3 4 5 6 7 firewall-cmd --get-zones dbus-send --system \\ --dest=org.fedoraproject.FirewallD1 \\ --print-reply \\ --type=method_call /org/fedoraproject/FirewallD1 \\ org.fedoraproject.FirewallD1.zone.getZones 查看zone内的条目信息\nbash 1 2 3 4 $ firewall-cmd --zone=public --list-all dbus-send --system --dest=org.fedoraproject.FirewallD1 --print-reply --type=method_call \\ /org/fedoraproject/FirewallD1 org.fedoraproject.FirewallD1.getZoneSettings string:\u0026#34;public\u0026#34; Reference bus name\ndbus-tutorial\n","permalink":"https://www.161616.top/what-is-dbus/","summary":"What is IPC IPC [Inter-Process Communication] 进程间通信，指至少两个进程或线程间传送数据或信号的一些技术或方法。在Linux/Unix中，提供了许多IPC。Unix七大IPC：\nPipe：无名管道，最基本的IPC，单向通信，仅在父/子进程之间，也就是将一个程序的输出直接交给另一个程序的输入。常见使用为 ps -ef|grep xxx FIFO [(First in, First out)] 或 有名管道（named pipe）:与Pipe不同，FIFO可以让两个不相关的进程可以使用FIFO。单向。 Socket 和 Unix Domain Socket：socket和Unix套接字，双向。适用于网络通信，但也可以在本地使用。适用于不同的协议。 消息队列 Message Queue: SysV 消息队列、POSIX 消息队列。 Signal: 信号，是发送到正在运行的进程通知以触发其事件的特定行为，是IPC的一种有限形式。 Semaphore：信号量，通常用于IPC或同一进程内的线程间通信。他们之间使用队列进行消息传递、控制或内容的传递。（常见SysV 信号量、POSIX 信号量） Shared memory：（常见SysV 共享内存、POSIX 共享内存）。共享内存，是在进程（程序）之间传递数据的有效方式，目的是在其之间提供通信。 每种IPC都有不通的特点，每种方式对资源的使用及性能都是不通的\n管道 I/O是最快的，但为单向通信，需要工作在 父/子 进程关系之间。 UNIX 套接字可以在本地连接不同的进程，并且具有更高的带宽，并且没有固有的消息边界。 TCP/IP套接字可以连接任何进程。并且可以通过网络连接，但是对资源会有更多的开销，同样的没有固定的消息边界。 Reference comparsion Unix/Linux IPC\nWhat is D-Bus 提到，D-Bus就不能不提一下freedesktop，而 D-Bus 仅仅作为freedesktop.org的一部分。\nD-Bus 桌面总线 (Desktop Bus)，的简写，也是Linux- IPC机制，不同于Unix 7大基础IPC的是，D-Bus是在这些IPC类型之上实现的中间件IPC，D-Bus使用了基础IPC中一种过多种，其设计的目的是在Linux桌面环境，提供服务的标准化。但目前并没有合入主线内核中。\n作为中间件IPC，D-Bus的性能较低与其他IPC模式，因为在通信过程中会进行很多上下文切换，如果通过Dbus来发送消息，会先将其发送到内核，然后将其送回D-Bus。AF_BUS 补丁是新的套接字类型，用来减少D-Bus上下文的切换。\n更多可参考：https://en.wikipedia.org/wiki/D-Bus\nD-Bus组成 D-Bus是 一个IPC的实现方式，在架构上分位三层。\nLayer 1 libdbus：freedesktop机构提供的一个免费开源的一个由C语言编写的 low-level API 。是提供dbus功能的库。是高级API绑定的低级API。 Layer 2 dbus daemon：dbus实现的IPC守护进行，随Linux启动，通过不通进程对其的连接，实现了多进程间消息的路由（包含内核、网络、桌面等） Layer 3 Wapper libraries （high-level API）： 对 low-level API libdbus的封装 ，例如 libdbus-qt libdbus-python github.","title":"Linux高级IPC - DBus"},{"content":"firewalld，一个基于动态区的iptables/nftables守护程序，自2009年左右开始开发，CentOS7基于 firewalld-0.6.3 ， 发布于2018年10月11日。主要的开发人员是托马斯·沃纳，他目前为红帽公司工作。这是因为为Federal 18 的默认防火墙机制， 随后在 Rhel7 和 Centos 7 中使用。\nfirewalld比旧的 iptable 机制有许多优势。值得注意的是，它解决了 iptable 要求每次更改时重新启动防火墙的问题，从而中断了任何状态连接。它还提供了丰富的 D-Bus 方法、信号和属性。\n这里并不是从firewalld操作使用方式来介绍firewalld的改名，想反，是介绍firewalld D-Bus API来检索信息或更改设置。\nfirewalld被配置为系统 D-Bus 服务，注意看 systemd file中的\u0026quot; Type=dbus \u0026ldquo;参数。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ cat /usr/lib/systemd/system/firewalld.service [Unit] Description=firewalld - dynamic firewall daemon Before=network-pre.target Wants=network-pre.target After=dbus.service After=polkit.service Conflicts=iptables.service ip6tables.service ebtables.service ipset.service Documentation=man:firewalld(1) [Service] EnvironmentFile=-/etc/sysconfig/firewalld ExecStart=/usr/sbin/firewalld --nofork --nopid $FIREWALLD_ARGS ExecReload=/bin/kill -HUP $MAINPID # supress to log debug and error output also to /var/log/messages StandardOutput=null StandardError=null Type=dbus BusName=org.fedoraproject.FirewallD1 KillMode=mixed [Install] WantedBy=multi-user.target Alias=dbus-org.fedoraproject.FirewallD1.service 实际上，手动运行 /usr/bin/python2 -Es /usr/sbin/firewalld --nofork --nopid --debug 效果是一样的，这里的注册是通过dbus 高级API操作的。\n此时由于已经了解到了，firewalld 服务 是基于D-Bus接口的，所以需要找到对应的 dbus interface\ntext 1 2 3 dbus-send --system --dest=org.freedesktop.DBus \\ --type=method_call --print-reply \\ /org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep FirewallD org.fedoraproject.FirewallD1 这个就是firewalld注册的dbus interface了。\ndbus-send 命令可以向 D-Bus消息总线发送消息并显示该消息的返回结果。有两个众所周知的消息总线：system bus（Option -System） 和每个用户session bus（ -session）。使用 firewall-cmd 也是通过 dbus interface 进行交互的。在使用dbus-send 时，必须指定其对应的消息接口 -dest，该参数是连接到对应总线上的接口名称，以将消息发送到对应的dbus firewalld-server进行对应iptables规则的翻译。\n现在有了dbus接口，需要了解改接口支持的方法 methods，属性 properties ，信号signals 等信息。\ntext 1 2 3 dbus-send --system --dest=org.fedoraproject.FirewallD1 --print-reply \\ /org/fedoraproject/FirewallD1 \\ org.freedesktop.DBus.Introspectable.Introspect 通过上述输出列出了通过防火墙 D-Bus 接口提供的所有方法、单一和属性。这是基于D-Bus DTD 的输出格式。所有 dbus服务都需要实现 org.freedesktop.DBus.Introspectable.Introspect 方法。\n知道了 方法 属性 信号，就可以直接对firewalld进行一个操作了。现在开始第一个例子。获取默认zone。\ntext 1 2 3 4 5 6 $ firewall-cmd --get-default-zone dbus-send --system --dest=org.fedoraproject.FirewallD1 \\ --print-reply --type=method_call \\ /org/fedoraproject/FirewallD1 \\ org.fedoraproject.FirewallD1.getDefaultZone 通过dbus接口来检索区域列表\ntext 1 2 3 4 5 6 7 $ firewall-cmd --get-zones dbus-send --system \\ --dest=org.fedoraproject.FirewallD1 \\ --print-reply --type=method_call \\ /org/fedoraproject/FirewallD1 \\ org.fedoraproject.FirewallD1.zone.getZones 最常用的命令：查看当前zone所有策略\ntext 1 2 3 4 5 6 7 $ firewall-cmd --zone=public --list-all dbus-send --system \\ --dest=org.fedoraproject.FirewallD1 \\ --print-reply --type=method_call \\ /org/fedoraproject/FirewallD1 \\ org.fedoraproject.FirewallD1.getZoneSettings string:\u0026#34;public\u0026#34; 获得inerface的properties\n其实这里在命令行根本用不到，但是在封装时却会可以用到。\ntext 1 2 3 4 dbus-send --system \\ --print-reply --dest=org.fedoraproject.FirewallD1 \\ /org/fedoraproject/FirewallD1 \\ org.freedesktop.DBus.Properties.GetAll string:\u0026#34;org.fedoraproject.FirewallD1\u0026#34; 还可以通过其他的接口来查看对应的属性值\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 dbus-send --system --print-reply --dest=org.fedoraproject.FirewallD1 \\ /org/fedoraproject/FirewallD1 \\ org.freedesktop.DBus.Properties.Get \\ string:\u0026#34;org.fedoraproject.FirewallD1\u0026#34; \\ string:\u0026#34;version\u0026#34; $ dbus-send --system --print-reply \\ --dest=org.fedoraproject.FirewallD1 \\ /org/fedoraproject/FirewallD1 org.freedesktop.DBus.Properties.Get \\ string:\u0026#34;org.fedoraproject.FirewallD1\u0026#34; \\ string:\u0026#34;interface_version\u0026#34; $ dbus-send --system --print-reply \\ --dest=org.fedoraproject.FirewallD1 \\ /org/fedoraproject/FirewallD1 \\ org.freedesktop.DBus.Properties.Get \\ string:\u0026#34;org.fedoraproject.FirewallD1\u0026#34; \\ string:\u0026#34;state\u0026#34; $ dbus-send --system --print-reply=literal \\ --dest=org.fedoraproject.FirewallD1 \\ /org/fedoraproject/FirewallD1 \\ org.freedesktop.DBus.Properties.Get \\ string:\u0026#34;org.fedoraproject.FirewallD1\u0026#34; \\ string:\u0026#34;state\u0026#34; 查询规则 查询接口\ntext 1 2 3 4 5 6 7 dbus-send --system \\ --dest=org.fedoraproject.FirewallD1 \\ --print-reply \\ --type=method_call \\ /org/fedoraproject/FirewallD1 \\ org.fedoraproject.FirewallD1.zone.getZoneOfInterface \\ string:\u0026#34;eth0\u0026#34; 创建一个新zone\ntext 1 2 3 4 5 6 dbus-send --session \\ --dest=org.freedesktop.DBus \\ --type=method_call \\ --print-reply /org/freedesktop/DBus \\ org.fedoraproject.FirewallD1.config.addZone \\ string:\u0026#34;testapi\u0026#34; 获得一个zone的所有规则（zonesettings）\ntext 1 2 3 4 5 6 dbus-send --system \\ --dest=org.fedoraproject.FirewallD1 \\ --type=method_call \\ --print-reply /org/fedoraproject/FirewallD1 \\ org.fedoraproject.FirewallD1.getZoneSettings \\ string:\u0026#34;public\u0026#34; 添加一个port\ntext 1 2 3 4 5 6 7 8 9 dbus-send --system \\ --dest=org.fedoraproject.FirewallD1 \\ --print-reply --type=method_call \\ /org/fedoraproject/FirewallD1 \\ org.fedoraproject.FirewallD1.zone.addPort \\ string:\u0026#34;public\u0026#34; \\ string:\u0026#34;81\u0026#34; \\ string:\u0026#34;tcp\u0026#34; \\ uint64:300 对应设置firewalld 面板所有属性的命令\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 firewall-cmd --zone=public --change-interface=eth0 firewall-cmd --zone=public --add-masquerade firewall-cmd --zone=public --add-forward-port=port=1122:proto=tcp:toport=22:toaddr=192.168.100.3 firewall-cmd --zone=public --add-forward-port=port=1122:proto=tcp:toport=22:toaddr=10.0.0.3 firewall-cmd --add-protocol=tcp firewall-cmd --add-protocol=udp firewall-cmd --add-icmp-blocks=icmp firewall-cmd --set-target=DROP firewall-cmd --add-icmp-block=redirect firewall-cmd --add-icmp-block=network-unknown firewall-cmd --add-source-port=80/tcp firewall-cmd --add-source-port=100/tcp firewall-cmd --add-source=10.0.0.1 firewall-cmd --add-source=10.0.0.2 firewall-cmd --add-rich-rule=\u0026#39;rule family=ipv4 source address=192.168.1.101/32 service name=telnet limit value=1/m accept\u0026#39; firewall-cmd --add-icmp-block-inversion firewall-cmd --new-zone=123 --permanen 执行远程命令 dbus接口支持远程命令的，通过dbus-send发送时，根据配置dbus的监听来完成远程的操作\ntext 1 DBUS_SESSION_BUS_ADDRESS=tcp:host=10.0.0.3,port=55557 根据上述，参考加上官方文档，了解如何通过D-Bus接口操作FirewallD，虽然此处是使用了 dbus-send，但是也可以通过 qt 或者 其他的来管理 基于 dbus api的应用了。\n","permalink":"https://www.161616.top/firewalld-dbus-interface/","summary":"firewalld，一个基于动态区的iptables/nftables守护程序，自2009年左右开始开发，CentOS7基于 firewalld-0.6.3 ， 发布于2018年10月11日。主要的开发人员是托马斯·沃纳，他目前为红帽公司工作。这是因为为Federal 18 的默认防火墙机制， 随后在 Rhel7 和 Centos 7 中使用。\nfirewalld比旧的 iptable 机制有许多优势。值得注意的是，它解决了 iptable 要求每次更改时重新启动防火墙的问题，从而中断了任何状态连接。它还提供了丰富的 D-Bus 方法、信号和属性。\n这里并不是从firewalld操作使用方式来介绍firewalld的改名，想反，是介绍firewalld D-Bus API来检索信息或更改设置。\nfirewalld被配置为系统 D-Bus 服务，注意看 systemd file中的\u0026quot; Type=dbus \u0026ldquo;参数。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ cat /usr/lib/systemd/system/firewalld.service [Unit] Description=firewalld - dynamic firewall daemon Before=network-pre.target Wants=network-pre.target After=dbus.service After=polkit.service Conflicts=iptables.service ip6tables.service ebtables.service ipset.","title":"使用firewalld dbus接口配置iptables"},{"content":"DBus中也是类似于静态语言，使用了“强类型”数据格式。在DBus上传递的所有数据都需要声明其对应的类型，下面整理了下，DBus中的数据类型，以及在DBus中声明的数据类型是什么意思。\ndbus类型 说明 s string 字符串类型，可以声明 s: a array 数组，可以声明为 a: v variant，variant:: () 结构体，声明时为双括号中间的为类型，可以是多个，例如(ss) 即这个结构体内包含两个字符串属性 b 布尔值 SIGNATURE signature类型 y BYTE d DOUBLE t UINT64 x INT64 u UINT32 i INT32 q uint16 n INT16 {} 词典，这里声明为两个括号，中间为其对应的 key value，例如 {sv} 即 key是字符串类型，value是variant类型。 o OBJECT_PATH 对象路径 a{sv} : 是一个数组，为 一个键值对的词典，里面仅有一个\n(ssssa{ss}as） 为一个结构体， 里面属性有7个 两个词典（数组），五个字符串类型\n(sssbsasa(ss)asba(ssss)asasasasa(ss)b) 这个类型拆开为下：共16个属性\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ( s string s string s string b bool s string as array only one string a(ss) two string type in the array as array only one string b bool a(ssss) four string type in the array as array only one string as array only one string as array only one string as array only one string a(ss) two string type in the array b bool ) 对上述类型，python中就可以很灵活的声明\ntext 1 2 [\u0026#34;\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;\u0026#34;, False, DEFAULT_ZONE_TARGET, [], [], [], False, [], [], [], [], [], [], False] go 中就需要按照对应类型声明为不通的结构体，属性名称可以不为主，顺序需要一致。\n其dbus收到的报文内容为\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 struct { string \u0026#34;\u0026#34; string \u0026#34;Public\u0026#34; string \u0026#34;For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.\u0026#34; boolean false string \u0026#34;default\u0026#34; array [ string \u0026#34;ssh\u0026#34; string \u0026#34;dhcpv6-client\u0026#34; ] array [ struct { string \u0026#34;55555-55557\u0026#34; string \u0026#34;tcp\u0026#34; } ] array [ string \u0026#34;redirect\u0026#34; string \u0026#34;network-unknown\u0026#34; ] boolean true array [ struct { string \u0026#34;1122\u0026#34; string \u0026#34;tcp\u0026#34; string \u0026#34;22\u0026#34; string \u0026#34;10.0.0.3\u0026#34; } ] array [ string \u0026#34;eth0\u0026#34; ] array [ string \u0026#34;10.0.0.1\u0026#34; string \u0026#34;10.0.0.2\u0026#34; ] array [ string \u0026#34;rule family=\u0026#34;ipv4\u0026#34; source address=\u0026#34;192.168.1.101/32\u0026#34; service name=\u0026#34;telnet\u0026#34; accept limit value=\u0026#34;1/m\u0026#34;\u0026#34; ] array [ string \u0026#34;tcp\u0026#34; string \u0026#34;udp\u0026#34; ] array [ struct { string \u0026#34;80\u0026#34; string \u0026#34;tcp\u0026#34; } struct { string \u0026#34;100\u0026#34; string \u0026#34;tcp\u0026#34; } ] boolean false } ao: array，里面元素仅为一个object_path\ngolang 中声明一个 Variant 在go中看到variant类型如下\ntext 1 2 3 4 type Variant struct { sig Signature value interface{} } 可以通过 SignatureOf(\u0026quot;short\u0026quot;)声明一个 Signature\n然后在通过：MakeVariantWithSignature(v interface{}, s Signature) Variant 声明 对应的 Variant\n注：其他数据类型与golang自己的数据类型一致，数组可以使用slice（类似php，python直接用数组替代即可更灵活）\nMore Reference dbus data type\ndbus data type conparision perl\n","permalink":"https://www.161616.top/dbus-data-structure/","summary":"DBus中也是类似于静态语言，使用了“强类型”数据格式。在DBus上传递的所有数据都需要声明其对应的类型，下面整理了下，DBus中的数据类型，以及在DBus中声明的数据类型是什么意思。\ndbus类型 说明 s string 字符串类型，可以声明 s: a array 数组，可以声明为 a: v variant，variant:: () 结构体，声明时为双括号中间的为类型，可以是多个，例如(ss) 即这个结构体内包含两个字符串属性 b 布尔值 SIGNATURE signature类型 y BYTE d DOUBLE t UINT64 x INT64 u UINT32 i INT32 q uint16 n INT16 {} 词典，这里声明为两个括号，中间为其对应的 key value，例如 {sv} 即 key是字符串类型，value是variant类型。 o OBJECT_PATH 对象路径 a{sv} : 是一个数组，为 一个键值对的词典，里面仅有一个\n(ssssa{ss}as） 为一个结构体， 里面属性有7个 两个词典（数组），五个字符串类型\n(sssbsasa(ss)asba(ssss)asasasasa(ss)b) 这个类型拆开为下：共16个属性\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ( s string s string s string b bool s string as array only one string a(ss) two string type in the array as array only one string b bool a(ssss) four string type in the array as array only one string as array only one string as array only one string as array only one string a(ss) two string type in the array b bool ) 对上述类型，python中就可以很灵活的声明","title":"通俗易懂的dbus数据结构"},{"content":" Question1: Similar to pause command in linux\ntext 1 read -n 1 Question2 read : Illegal option -n\n原因为ubuntu 默认的是dash 不是 bash Reference\nQuestion3: How to Compile C programing Language\ntext 1 gcc hello.c -o hello Question4: Segmentation fault (core dumped)\n编译正常执行错误，在linux中使用 strace 查看具体报错。\nReference\n","permalink":"https://www.161616.top/c-complie-record/","summary":"Question1: Similar to pause command in linux\ntext 1 read -n 1 Question2 read : Illegal option -n\n原因为ubuntu 默认的是dash 不是 bash Reference\nQuestion3: How to Compile C programing Language\ntext 1 gcc hello.c -o hello Question4: Segmentation fault (core dumped)\n编译正常执行错误，在linux中使用 strace 查看具体报错。\nReference","title":"C程序编译错误记录"},{"content":"索引管理 创建索引 直接创建索引 PUT newindex1，创建索引可以通过 number_of_shares 和 number_of_replicas 数量来修饰分片和副本的数量。\ntext 1 2 3 4 5 6 7 8 9 PUT newindex { \u0026#34;settings\u0026#34;: { \u0026#34;index\u0026#34; : { \u0026#34;number_of_shares\u0026#34; : 2, \u0026#34;number_of_replicas\u0026#34;: 1 } } } number_of_shares 分片数在创建索引后不能修改\nnumber_of_replicas 副本数可以随时完成修改\n删除索引 DEL index_name\n打开/关闭索引 POST {index_name}/_close\nPOST {index_name}/_open\n关闭的索引无法进行【增删改查】操作\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { \u0026#34;error\u0026#34; : { \u0026#34;root_cause\u0026#34; : [ { \u0026#34;type\u0026#34; : \u0026#34;index_closed_exception\u0026#34;, \u0026#34;reason\u0026#34; : \u0026#34;closed\u0026#34;, \u0026#34;index_uuid\u0026#34; : \u0026#34;3eCslZZ3Q9amlUyDtqTXWA\u0026#34;, \u0026#34;index\u0026#34; : \u0026#34;newindex\u0026#34; } ], \u0026#34;type\u0026#34; : \u0026#34;index_closed_exception\u0026#34;, \u0026#34;reason\u0026#34; : \u0026#34;closed\u0026#34;, \u0026#34;index_uuid\u0026#34; : \u0026#34;3eCslZZ3Q9amlUyDtqTXWA\u0026#34;, \u0026#34;index\u0026#34; : \u0026#34;newindex\u0026#34; }, \u0026#34;status\u0026#34; : 400 } 索引的映射 mapping mapping是定义文档及包含字段的存储与索引方式。可以理解为是elasticsearch的表结构，定义mapping，即在创建index时，自行判断每个字段的类型，而不是有ES自动自动判断每个纬度的类型。这种更贴合业务场景，如分词、存储。\n每个索引仅有一个映射类型（elasticsearch6.x+，之前版本一个索引下有多个类型），它决定了文档将如何被索引。而映射类型分为两部分 meta-fields 与 field of properties\nmeta-fields ：为文档的源数据，如 _index （索引的名称）、_type （文档的类型，7.0+弃用）、_id （索引的ID）和 _source（用于关联文档源数据）字段\nfield of properties：字段属性，包含文档的字段或属性列表。\n字段的数据类型 常见类型 binary：二进制或Base64字符串。 boolean: 布尔值true和false。 keywords：keyword, constant_keyword, 和 wildcard. Numbers：数值类型，例如long和double dates：日期类型，date 和 date_nanos。 alias： 为以有字段定义别名。 对象嵌套类型 object ：JSON对象。 flattened：整个JSON对象作为单个字段值。 nested：嵌套，与子字段之间保留关系的json对象。 join：为同一索引中的文档定义父/子关系。 结构化类型 range：，long_range，double_range， date_range，和ip_range。 ip：IPv4和IPv6地址。 version ：软件版本号。支持 语义化版本号 优先规则。 murmur3：计算并存储值的散列。 汇总数据类型 aggregate_metric_double：预汇总的指标值。 histogram：以直方图形式预汇总的数值。 文字搜寻类型 text：分析的非结构化文本。 annotated-text：包含特殊标记的文本。用于标识命名实体。 completion：用于自动补全建议。 search_as_you_type text类似类型，用于按需输入完成。 token_count：计数令牌。 等等 reference。\n常用字段数据类型参数 更多参数可以参考官方文档：mapping-params\n字段 说明 analyzer 仅text字段支持 analyzer 映射参数。 index 选项控制是否对字段值建立索引。默认为true。未索引的字段不可查询。 index_prefixes 启用术语前缀的索引，以加快前缀搜索的速度 ignore_above 长度大于 ignore_above设置的字符串将不会被索引或存储 映射的元字段 文档的元字段是保证系统正常运转的内置字段，如 _index 索引的字段 _type映射类型（7.0后取消），_id_ 文档主键。\n动态映射 dynamic 在关系型数据库中，需要事先创建好数据库，并在库中插入表。而ES中需要事先创建好索引结构（Mapping），在插入文档到索引中，系统会根据文档内容进行索引的动态映射。自动检测添加新类型和字段，被成为动态映射。\n禁用动态映射：{index_name}/_mapping dynamic 为false\ntext 1 2 3 4 5 6 POST student/_mapping { \u0026#34;dynamic\u0026#34;:\u0026#34;false\u0026#34; } GET student/_mappings 显式映射 实现准备好映射关系，包含文档各字段类型，关系等，这种称之为显式映射 Explicit mapping\n动态模板 动态映射会自动推断数据类型，但这种并不完全符合所有的业务需求，而动态模板可以再动态映射之外更好的控制ES如何映射的数据类型。\n模板的匹配 match_mapping_type 对 Elasticsearch 检测到的数据类型进行操作 match unmatch ： match使用pattern匹配字段名称，unmatch 使用正则排除字段 path_match path_unmatch 使用与match一样，这里为全名称，path指的是多层json的路径。 如，需要将所有数字字段映射为integer而不是long，将所有字符串都映射为text与keyword:\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 { \u0026#34;mappings\u0026#34;: { \u0026#34;dynamic_templates\u0026#34;: [ { \u0026#34;integers\u0026#34;: { \u0026#34;match_mapping_type\u0026#34;: \u0026#34;long\u0026#34;, \u0026#34;mapping\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;integer\u0026#34; } } }, { \u0026#34;strings\u0026#34;: { \u0026#34;match_mapping_type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;mapping\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;text\u0026#34;, \u0026#34;fields\u0026#34;: { \u0026#34;raw\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;keyword\u0026#34;, \u0026#34;ignore_above\u0026#34;: 256 } } } } } ] } } 可以使用正则表达式来匹配字段\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 PUT student12345 { \u0026#34;mappings\u0026#34;: { \u0026#34;dynamic_templates\u0026#34;: [ { \u0026#34;longs_as_strings\u0026#34;: { \u0026#34;match_mapping_type\u0026#34;: \u0026#34;string\u0026#34;, // 捕获的类型 \u0026#34;match\u0026#34;: \u0026#34;pass*\u0026#34;, // 将以pass开头的string类型的映射为integer \u0026#34;unmatch\u0026#34;: \u0026#34;user*\u0026#34;, // 忽略以user开头的 \u0026#34;mapping\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;integer\u0026#34; // 需要映射的类型 } } } ] } } PUT student12345/_doc/1 { \u0026#34;username\u0026#34;: \u0026#34;zhangsan\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;123456\u0026#34; } 查看映射后的类型\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 { \u0026#34;student12345\u0026#34; : { \u0026#34;mappings\u0026#34; : { \u0026#34;dynamic_templates\u0026#34; : [ { \u0026#34;longs_as_strings\u0026#34; : { \u0026#34;match\u0026#34; : \u0026#34;pass*\u0026#34;, \u0026#34;unmatch\u0026#34; : \u0026#34;user*\u0026#34;, \u0026#34;match_mapping_type\u0026#34; : \u0026#34;string\u0026#34;, \u0026#34;mapping\u0026#34; : { \u0026#34;type\u0026#34; : \u0026#34;integer\u0026#34; } } } ], \u0026#34;properties\u0026#34; : { \u0026#34;password\u0026#34; : { \u0026#34;type\u0026#34; : \u0026#34;integer\u0026#34; }, \u0026#34;username\u0026#34; : { \u0026#34;type\u0026#34; : \u0026#34;text\u0026#34;, \u0026#34;fields\u0026#34; : { \u0026#34;keyword\u0026#34; : { \u0026#34;type\u0026#34; : \u0026#34;keyword\u0026#34;, \u0026#34;ignore_above\u0026#34; : 256 } } } } } } } path_match path_unmatch\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 PUT test1 { \u0026#34;mappings\u0026#34;: { \u0026#34;dynamic_templates\u0026#34;: [ { \u0026#34;full_name\u0026#34;: { \u0026#34;path_match\u0026#34;: \u0026#34;document.*\u0026#34;, // 将name下的所有对象复制到外部顶级字段 \u0026#34;path_unmatch\u0026#34;: \u0026#34;*.middlename\u0026#34;, // 这个字段除外 \u0026#34;mapping\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;text\u0026#34;, \u0026#34;copy_to\u0026#34;: \u0026#34;full_name\u0026#34; } } } ] } } PUT test1/_doc/1 { \u0026#34;document\u0026#34;: { \u0026#34;firstname\u0026#34;: \u0026#34;John\u0026#34;, \u0026#34;middlename\u0026#34;: \u0026#34;Winston\u0026#34;, \u0026#34;lastname\u0026#34;: \u0026#34;Lennon\u0026#34; } } 当插入下列时，不成功，这里映射类型为text，与address中的对象类型不匹配所以不成功\ntext 1 2 3 4 5 6 7 8 9 10 11 12 PUT test1/_doc/1 { \u0026#34;document\u0026#34;: { \u0026#34;firstname\u0026#34;: \u0026#34;John\u0026#34;, \u0026#34;middlename\u0026#34;: \u0026#34;Winston\u0026#34;, \u0026#34;address\u0026#34;: { \u0026#34;city\u0026#34;: \u0026#34;beijing\u0026#34;, \u0026#34;province\u0026#34;: \u0026#34;beijing\u0026#34;, \u0026#34;District\u0026#34;: \u0026#34;haidian\u0026#34; } } } 模板变量 {name} {dynamic_type} 占位符，在映射中会替换为字段名和检测到的动态类型\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 PUT student1 { \u0026#34;mappings\u0026#34;: { \u0026#34;dynamic_templates\u0026#34;: [ { \u0026#34;named_analyzers\u0026#34;: { \u0026#34;match_mapping_type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;match\u0026#34;: \u0026#34;*\u0026#34;, \u0026#34;mapping\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;text\u0026#34;, \u0026#34;analyzer\u0026#34;: \u0026#34;{name}\u0026#34; } } }, { \u0026#34;no_doc_values\u0026#34;: { \u0026#34;match_mapping_type\u0026#34;:\u0026#34;*\u0026#34;, \u0026#34;mapping\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;{dynamic_type}\u0026#34;, \u0026#34;doc_values\u0026#34;: false } } } ] } } PUT student1/_doc/1 { \u0026#34;name\u0026#34;: \u0026#34;alex chow\u0026#34;, \u0026#34;age\u0026#34;: 30 } PUT student1/_doc/1 { \u0026#34;english\u0026#34;: \u0026#34;Some English text\u0026#34;, \u0026#34;count\u0026#34;: 5 } reference template-variables\n索引的类型 Elasticsearch7.x中，不在需要document的 type\nschedule_for_removal_of_mapping_types\nElasticSearch在7.x版本之前 index 类似于数据库中的 database，而 type 等同于数据库中的 table\n如：6.x API\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 PUT student # index（库） { \u0026#34;mappings\u0026#34;: { \u0026#34;_doc\u0026#34;: { # type （表） \u0026#34;properties\u0026#34;: { \u0026#34;type\u0026#34;: { # filed (字段) \u0026#34;type\u0026#34;: \u0026#34;keyword\u0026#34; }, \u0026#34;name\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;text\u0026#34; }, \u0026#34;user_name\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;keyword\u0026#34; }, \u0026#34;email\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;keyword\u0026#34; }, \u0026#34;content\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;text\u0026#34; }, \u0026#34;tweeted_at\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;date\u0026#34; } } } } } 而在7.x之后版本可以不需要\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 PUT student { \u0026#34;mappings\u0026#34;: { \u0026#34;properties\u0026#34;: { \u0026#34;type\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;keyword\u0026#34; }, \u0026#34;name\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;text\u0026#34; }, \u0026#34;user_name\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;keyword\u0026#34; }, \u0026#34;email\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;keyword\u0026#34; }, \u0026#34;content\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;text\u0026#34; }, \u0026#34;tweeted_at\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;date\u0026#34; } } } } 在7.0以上版本使用时，必须使用 /{index_name}/_doc/{_id} 进行调用。\n_doc 为路由永久组成，可以理解为 _doc 替换之前的 type\ntext 1 2 3 4 5 6 7 8 9 PUT /student/_doc/1 { \u0026#34;name\u0026#34;: \u0026#34;zhangsan\u0026#34;, \u0026#34;user_name\u0026#34;: \u0026#34;zhangsan\u0026#34;, \u0026#34;email\u0026#34;: \u0026#34;1@gmail.com\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;北京分行、天津分行、河北分行、山西分行、辽宁分行\u0026#34; } GET /student/_doc/1 对于7.0之前的/{index}/{type}/{action} ，的操作如_update 、_search 将紧跟{action}后面。\ntext 1 2 3 4 5 6 7 8 POST /student/_update/1 { \u0026#34;doc\u0026#34;: { \u0026#34;user_name\u0026#34;: \u0026#34;lisi\u0026#34; } } GET /student/_search ","permalink":"https://www.161616.top/elasticsearch-mapping/","summary":"索引管理 创建索引 直接创建索引 PUT newindex1，创建索引可以通过 number_of_shares 和 number_of_replicas 数量来修饰分片和副本的数量。\ntext 1 2 3 4 5 6 7 8 9 PUT newindex { \u0026#34;settings\u0026#34;: { \u0026#34;index\u0026#34; : { \u0026#34;number_of_shares\u0026#34; : 2, \u0026#34;number_of_replicas\u0026#34;: 1 } } } number_of_shares 分片数在创建索引后不能修改\nnumber_of_replicas 副本数可以随时完成修改\n删除索引 DEL index_name\n打开/关闭索引 POST {index_name}/_close\nPOST {index_name}/_open\n关闭的索引无法进行【增删改查】操作\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { \u0026#34;error\u0026#34; : { \u0026#34;root_cause\u0026#34; : [ { \u0026#34;type\u0026#34; : \u0026#34;index_closed_exception\u0026#34;, \u0026#34;reason\u0026#34; : \u0026#34;closed\u0026#34;, \u0026#34;index_uuid\u0026#34; : \u0026#34;3eCslZZ3Q9amlUyDtqTXWA\u0026#34;, \u0026#34;index\u0026#34; : \u0026#34;newindex\u0026#34; } ], \u0026#34;type\u0026#34; : \u0026#34;index_closed_exception\u0026#34;, \u0026#34;reason\u0026#34; : \u0026#34;closed\u0026#34;, \u0026#34;index_uuid\u0026#34; : \u0026#34;3eCslZZ3Q9amlUyDtqTXWA\u0026#34;, \u0026#34;index\u0026#34; : \u0026#34;newindex\u0026#34; }, \u0026#34;status\u0026#34; : 400 } 索引的映射 mapping mapping是定义文档及包含字段的存储与索引方式。可以理解为是elasticsearch的表结构，定义mapping，即在创建index时，自行判断每个字段的类型，而不是有ES自动自动判断每个纬度的类型。这种更贴合业务场景，如分词、存储。","title":"elasticsearch mapping"},{"content":" question: How use golang Copy one struct to another where structs have same members and different types\n此时需要的库\ngithub.com/ulule/deepcopier github.com/jinzhu/copier E.g.\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/ulule/deepcopier\u0026#34; ) // Model type User struct { // Basic string field Name string // Deepcopier supports https://golang.org/pkg/database/sql/driver/#Valuer Email sql.NullString } func (u *User) MethodThatTakesContext(ctx map[string]interface{}) string { // do whatever you want return\u0026#34;hello from this method\u0026#34; } // Resource type UserResource struct { //copy from field\u0026#34;Name\u0026#34; DisplayName string `deepcopier:\u0026#34;field:Name\u0026#34;` //this will be skipped in copy SkipMe string `deepcopier:\u0026#34;skip\u0026#34;` //this should call method named MethodThatTakesContext MethodThatTakesContext string `deepcopier:\u0026#34;context\u0026#34;` Email string `deepcopier:\u0026#34;force\u0026#34;` } func main() { user := \u0026amp;User{ Name:\u0026#34;gilles\u0026#34;, Email: sql.NullString{ Valid: true, String:\u0026#34;gilles@example.com\u0026#34;, }, } resource := \u0026amp;UserResource{} deepcopier.Copy(user).To(resource) //copied from User\u0026#39;s Name field fmt.Println(resource.DisplayName)//output: gilles fmt.Println(resource.Email) //output: gilles@example.com fmt.Println(resource.MethodThatTakesContext) //output: hello from this method } Reference copy different struct\n","permalink":"https://www.161616.top/golib-deepcopier/","summary":"question: How use golang Copy one struct to another where structs have same members and different types\n此时需要的库\ngithub.com/ulule/deepcopier github.com/jinzhu/copier E.g.\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.","title":"Go每日一库 - deepcopier"},{"content":" 工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 该文整理一些常用的shell用法，及语法，并非介绍如何使用\n变量 变量可分为两类：环境变量ENV（全局）和局部变量。\nbash环境变量\n变量名 含义 _= 上一条命令的最后一个参数 BASH_VERSION=\u0026ldquo;4.1.2(1)-release\u0026rdquo; 当前bash实例的版本号 COLORS=\u0026quot;/etc/DIR_COLORS\u0026quot; COLUMNS=80 设置该变量就给shell编辑模式和选择命令定义了编辑窗口的宽度 CVS_RSH=\u0026ldquo;ssh\u0026rdquo; DIRSTACK 代表目录栈当前的内容 EUID=0 为在shell启动时被初始化的当前用户的有效ID G_BROKEN_FILENAMES=1 GROUPS=() 当前用户所属组 HISTFILE=/root/.bash_history 历史记录文件的全路径 HISTFILESIZE=50 历史文件能包含的最大行数 HISTSIZE=50 记录在命令行历史文件中的命令行数 HOME=/root 当前用户家目录 HOSTNAME= 当前主机机器名称 HOSTTYPE=x86_64 IFS=$\u0026rsquo;\\t\\n' 内容字段分隔符，一般是空格符、制表符、和换行符，用于由命令替换，循环结构中的表和读取的输入产生的词的字段划分。 INPUTRC=/etc/inputrc readline启动文件的文件名。取代默认的~/.inputrc JAVA_HOME=/app/jdk1.6 KDENIR=/usr KDE IS PRELINKED=1 LANG=zh_CN.GB18030 LESSONPEN LINES=36 LONGNAME=root 登陆的用户名 LS_COLORS=xx MACHTYPE=x86_64-redhat-linux-gnu 包含一个描述正在运行bash的系统串 MAILCHECK=60 这个参数定义shell将隔多长时间（以秒为单位检查一次由参数MAILPATH或MAILFILE）指定的文件，看看是否有邮件到达。默认600秒 MAIL=/var/spool/mail/root 邮件全路径 OLDPWD=/root 前一个当前工作目录 OPTERR=1 如果设置为1，秒年十时毫，来自getopts内置命令的错误信息。 OPTIND=1 下一个有getopts内置命令处理的参数序号 OSTYPE=linux-gnu 自动设置称一个串，该串标书正在运行bash的操作系统，默认值有系统决定 PATH 全局PATH路径。命令搜索路径。一个有冒号分隔的目录列表，shell用它来搜索命令。默认路径有系统决定，并且由安装bash的管理员设置。 PIPESTATUS=([0]=0 [1]=1) 一个数组，包含一列最进在管道执行的前台作业的进程退出状态值。 PPID=1112 父进程的进程ID PS1=[\\u@\\h \\W]$ 主提示符串，默认值是$ PS2= \u0026gt; 次提示符串，默认值是\u0026gt; PS4=+ 当开启追踪时使用的调试提示符串，默认值是+，追踪可用set-x开启。 PWD 当前用户家目录。 SHELL=/bin/bash SHLVL=1 每启动一个bash实例就将其加1 TMOUT=3600 退出前等待超时的秒数。 UID=0 当前用户的UID，在shell启动时初始化。 USER=root 当前用户的用户名，在shell启动时初始化。 自定义环境变量 export\n默认的环境变量 env（printenv）或set\n消除本地变量和环境变量 unset 定义变量：习惯：数字不加引号，其他默认加双引号\n引号 名称 解释 单引 号 可以说是所见即所得：即将单引号内的所有内容都原样输出，或者描述为单引号里面看到的是什么就会输出什么。 双引号 把双引号内的所有内容都输出出来；如果内容中有命令（要反引下）、变量、特殊转义符等，会先把变量、命令解析出结果，然后再输出最终内容来。 无引号 把内容输出出来前，会将含有空格的字符串视为一个整体输出，如果内容中有命令、变量等，会先把变量、命令解析出结果，然后在输出最终内容来，如果字符串中带有空格等特殊字符，则不能完整的输出，需要改加双引号，一般连续的字符串，数字，路径等可以不加任何引号，不过无引号的情况最好用双引号替代之。 变量的命名规范 变量名要统一，使用全部大写字母，如APCHE_ERR_NUM；语义要清晰，能够正确表达变量内容含义，过长的英文单词可采用前几个字符代替，多个单词连接用“_”连接，引用时，最好以${APACHE_ERR_NUM}或\u0026quot;${APACHE_ERR_NUM}\u0026ldquo;的方式引用变量。\n避免无意义字符或数字：例如下面的COUNT，并不知道其确切含义\n范例1：COUNT的不确切定义 COUNT=$(grep keywords file)\n全局变量和局部变量命名\n脚本中的全局变量定义，如USER_HOME或USERHOME，在变量使用时，使用 { }将变量括或\u0026rdquo;${APACHE_ERR_NUM}\u0026quot;了；变量后还有字符串隔不开的情况下，用大括号扩一下 ${金庸}新著作 脚本中局部变量定义：存在于脚本函数（function）中的变量称为局部变量，要以local方式进行生命，使之只在本函数作用域内有效，防止变量在函数中的命名于变量外部程序中变量重名造成程序异常。下面是函数中的变量定义例子： 特殊变量 No 位置变量 $0 当前执行的shell脚本的文件名，如果执行脚本带路径则包括脚本路径。 $n 当前执行的shell脚本的第n个参数值，n=1..9，当n为0时表示脚本文件名，如果n大于9用大括号括起来${10}. $# 当前执行的shell脚本后面接的参数的总个数 $* 当前shell的所有传参的参数，将所有的参数视为单个字符串，相当于“$1$2$3..”注意$与#的区别 $@ 这个程序的所有参数“$1” “$​2” “$3” ....”，这是将参数传递给其他程序的最佳方式，因为会保留所有内嵌在每个参数里的任何空白。 进程状态变量 $$ 获得当前shell脚本的进程号（PID） $? 执行上一个指令的返回值（0为成功，非0为失败） $! 执行上一个指令的PID $_ 在此之前执行的命令或脚本的最后一个参数。 $? 返回值参考\nno 意思 0 表示允许成功 2 权限拒绝 1~125 表示运行失败，脚本命令、系统命令错误或参数传递错误 126 找到该命令了，但是无法运行 127 为找打要运行的命令$ zhangsan-bash: zhangsan: command not found $ echo $? 127 \u0026gt;128 命令被系统强制结束$ sleep 100000^C $ echo $?130 变量子串 表达式 说明 ${#string} 返回$string的长度 ${string:position} 在$string中，从位置$position之后开始提取子串 ${string:position:length} 在$string中，从位置position之后开始提取长度为length的子串 ${string#sub} 从变量string开头开始删除最短匹配sub子串 ${string##sub} 从变量开头开始删除最长匹配子串 ${string%sub} 从变量string结尾开始删除最短匹配sub子串 ${string%%sub} 从变量string结尾开始删除最长匹配sub子串 ${string/sub/rep} 使用rep，来代替第一个匹配的sub ${string/#sub/rep} 如果string前缀匹配sub就用rep代替匹配sub 变量替换 运算符号 替换 ${value:-word} 如果变量名存在且非null，则返回变量的值。否则，返回word字符串。用途：如果变量未定义，则返回默认值。范例：${value:-word}，如果value未定义，则表达式的值为word ${value:=word} 如果变量名存在且非null，则返回变量值。否则，设置这个变量值未word，并返回其值。用途：如果变量未定义，则设置变量为默认值，并返回默认值。范例：${value:=word}，如果value未定义，则设置value的值为word，返回表达式的值也为word。 ${value:?\u0026quot;not defined\u0026quot;} 如果变量名存在且非null，则返回变量的值。否则显示变量名：msg，并退出当前的命令或脚本。用途：用于捕捉由于变量未定义而导致的错误，并退出程序。范例：${value:?\u0026quot;not defined\u0026quot;}如果value未定义，则显示-bash:value:not defined并退出。 ${value:+word} 如果变量名存在且非null，则返回word。否则返回null。用途：测试变量是否存在。范例：${value:+word} 如果value已经定义，返回word（也是就是真）。 数值（整数）计算 (( )) 如果要执行简单的整数运算，只需将特定的算数表达式用 (( 和 )) 括起来即可。\nshell的算数运算符号常置于$(( 和 ))的语法中。这一语法如同双引号用能，除了内嵌双引号无需转义。\n运算符 意义 ++ \u0026ndash; 增加及减少，可前置也可放在结尾 + - ！~ 一元的正号与负号；逻辑与位的取反 * / % 乘法、除法、与取余 + - 加法、减法 \u0026lt; \u0026lt;= \u0026gt; \u0026gt;= 比较负号 == != 相等与不相等，一个“=”赋值 \u0026laquo; \u0026raquo; 向左位移 向右位移 \u0026amp; 位的AND ^ 位的异或 | 位的或 \u0026amp;\u0026amp; 逻辑的AND（make \u0026amp;\u0026amp; mak install） || 逻辑的OR（make || make install） ?: 条件表达式 = += -= *= /= \u0026amp;= ^= \u0026laquo;= \u0026raquo;= |= 赋值运算符 a+=1都相当a=a+1 ** 为幂运算：% 为取模运算（就是除法当中取余数）。 上面涉及到的参数变量必须位整数（==整型==）。不能是小数（浮点数）或者字符串。后面的bc命令可以进行浮点数运算，但一般较少用到。 echo $((a++)) 和 $((a--)) 表示先输出a自身的值，然后在进行 ++-- 的运算，echo$((++a)) 和 echo$((--a)) 表示先进行 ++-- 的运算，在输出a自身的值。 记忆方法：变量在前，先输出变量值，变量在后，就是先运算后输出变量的值\nlet let赋值表达式，【注】let赋值表达式功能等同于((赋值表达式)) ，例如 let i=i+8\nexpr expr（evaluate（求值）expressions（表达式））命令：\nexpr命令一般用于整数值，但也可用于字符串，用来求表达式变量的值，同时expr是一个手工命令行计算器。\nexpr 2 + 2 expr 2 - 1 expr 2 * 1 expr 2 \\* 1 expr 3 % 2 expr$[$a+$b]表达式形式\nbash 1 2 3 4 5 6 7 8 9 10 11 12 # expr $[2+3] 5 # expr $[2**3] 8 # echo $[2**3] 8 2、 # a=1 # b=2 # expr $[$a+$b] 3 expr 将其后的串解释为表达式计算其值，运算符前后需有空格 bc bc是UNXI下的计算器，它也可以用在命令行中，bc支持科学计算，所以这种方法功能非常强大\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 # 一般工作中不这么用 $ bc bc 1.06.95 Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc. This is free software with ABSOLUTELY NO WARRANTY. For details type `warranty\u0026#39;. 1*5 5 1/5 0 5/3 1 10+2 12 # $ echo 5+10|bc 15 $ echo 5*20|bc 100 $ echo 10%3|bc 1 $ echo 10.5+3.1|bc 13.6 # 与expr的区别 $ echo `expr 1+1` 1+1 $ echo `expr 1 + 1` 2 $ echo `expr 1 + 1.2` expr: 参数数目错误 $ echo 1+1|bc 2 $ echo 1 + 1|bc 2 # 保留小数位数 $ echo \u0026#34;scale=2;10.45246/2.2315\u0026#34;|bc 4.68 $ echo \u0026#34;10.45246/2.2315\u0026#34;|bc 4 $ echo 10.45246/2.2315|bc 4 # 进制转换 $ echo \u0026#34;obase=2;2\u0026#34;|bc 10 $ echo \u0026#34;obase=10;10\u0026#34;|bc 10 $ echo \u0026#34;obase=8;10\u0026#34;|bc 12 范例：通过命令输出1+2+3+4..+10=XX的表达式，并计算出结果 $ echo `seq -s \u0026#34;+\u0026#34; 10`=`seq -s \u0026#39;+\u0026#39; 10|bc` 1+2+3+4+5+6+7+8+9+10=55 $ echo `seq -s \u0026#34;+\u0026#34; 10`=$((`seq -s \u0026#34;+\u0026#34; 10`)) 1+2+3+4+5+6+7+8+9+10=55 $ echo `seq -s \u0026#34;+\u0026#34; 10`=`seq -s \u0026#39; + \u0026#39; 10|xargs expr` 1+2+3+4+5+6+7+8+9+10=55 $ echo {1..10}|tr \u0026#34; \u0026#34; \u0026#34;+\u0026#34; 1+2+3+4+5+6+7+8+9+10 $ echo {1..10}|tr \u0026#34; \u0026#34; \u0026#34;+\u0026#34;|bc 55 $[] bash 1 2 3 4 # echo $[2+3] 5 # echo $[ 2 * 3 ] 6 条件测试 ​\n测试语句 语法 说明 语法1：test \u0026lt;测试表达式\u0026gt; 利用test命令进行条,test后有一个空格 语法2：[ \u0026lt;测试表达式\u0026gt; ] 通过单中括号进行,单中括号中的内容前后都有一个空格 语法3：[[ \u0026lt;测试表达式\u0026gt; ]] 通过双中括号进行,双中括号中的内容前后都有一个空格 语法4：((\u0026lt;测试表达式\u0026gt;)) 通过双小括号进行,双小括号中的内容前后无空格 在[[ ]]中可以使用通配符进行模式匹配。\u0026amp;\u0026amp; || \u0026gt; \u0026lt;等操作符可以应用于[[ ]]中，不能应用于[ ]中。[]中一般用-a、-o、-gt 等替代对整数进行关系运算，也可以使用Shell的算数运算符 (( ))\n文件测试操作符 测试操作符 说明 -f 文件file 若文件存在且为普通文件则真 -d 文件 directory 若文件存在且为目录文件则真 -s 文件 size 若文件存在切不为空（文件大小非0）则真 -e 文件 exist 若文件存在则真，要区别-f -r 文件 read 若文件存在且可读则真 -w 文件write 若文件存在且可写则真 -x 文件 excute 若文件存在且可执行则真 -L 文件link 若文件存在且为链接文件则真 f1 -nt f2 never than 若文件f1比文件f2新则真 f1 -ot f2 older than 若文件f1比文件f2旧则真 f1 -ef f2 两个文件具有同样的设备号和i结点号 -k file 文件是否設置了粘着位(Sticky Bit)，如果是，則返回 true。 [ -k $file ] 返回 false。 -u file 文件是否設置了 SUID 位，如果是，則返回 true。 [ -u $file ] 返回 false。 -x file 文件是否可執行，如果是，則返回 true。 -p file 文件是否是有名管道，如果是，則返回 true。 [ -p $file ] 返回 false。 -w file 文件是否可寫，如果是，則返回 true。 [ -w $file ] 返回 true。 字符串测试操作符 常用字符串测试操作符 说明 -z \u0026ldquo;string\u0026rdquo; 若串长度为0则真，-z可以理解为zero -n \u0026ldquo;string\u0026rdquo; 若长度不为0则真，-n可以理解为no zero \u0026ldquo;string1\u0026rdquo;=\u0026ldquo;string2\u0026rdquo; 若串1等于串2则真，可使用“==”代替“=” \u0026ldquo;string1\u0026rdquo; != \u0026ldquo;string2\u0026rdquo; 若串1不等于串2则真。但不能用“!==”代替“!=” 二元比较操作符 在[]中使用的比较符 在[[ ]]中使用的比较符 说明 -eq == equal的缩写，相等返回真 -ne != not equal的缩写，不相等返回真 -gt \u0026gt; 大于greater than -ge \u0026gt;= 大于等于 greate equal -lt \u0026lt; 小于类似less than -le \u0026lt;= 小于等于less equal 逻辑操作符 在[ ]中使用的比较符 在[[ ]]中使用的比较符 说明 -a \u0026amp;\u0026amp; and 与，两端都为真，则真 -o || or 或，两端有一个为真则真 ! ! not 非，相反则为真 字体颜色 颜色范围：30-37\nbash 1 2 3 4 5 6 7 8 echo -e \u0026#34;\\033[30m 黑字体 test \\033[0m\u0026#34; echo -e \u0026#34;\\033[31m 红字体 test \\033[0m\u0026#34; echo -e \u0026#34;\\033[32m 绿字体 test \\033[0m\u0026#34; echo -e \u0026#34;\\033[33m 黄字体 test \\033[0m\u0026#34; echo -e \u0026#34;\\033[34m 蓝字体 test \\033[0m\u0026#34; echo -e \u0026#34;\\033[35m 紫字体 test \\033[0m\u0026#34; echo -e \u0026#34;\\033[36m 天蓝字 test \\033[0m\u0026#34; echo -e \u0026#34;\\033[37m 白色字 test \\033[0m\u0026#34; 40-47\nbash 1 2 3 4 5 6 7 8 9 echo -e \u0026#34;\\033[40;37m 黑底白字 welcome \\033[0m\u0026#34; echo -e \u0026#34;\\033[41;37m 红底白字 welcome \\033[0m\u0026#34; echo -e \u0026#34;\\033[42;37m 绿底白字 welcome \\033[0m\u0026#34; echo -e \u0026#34;\\033[43;37m 黄底白字 welcome \\033[0m\u0026#34; echo -e \u0026#34;\\033[44;37m 蓝底白字 welcome \\033[0m\u0026#34; echo -e \u0026#34;\\033[45;37m 紫底白字 welcome \\033[0m\u0026#34; echo -e \u0026#34;\\033[46;37m 天蓝底白字 welcome \\033[0m\u0026#34; echo -e \u0026#34;\\033[47;37m 白底白字 welcome \\033[0m\u0026#34; echo -e \u0026#34;\\033[47;30m 白底黑字 welcome \\033[0m\u0026#34; 通过定义变量方式给字体加颜色 bash 1 2 3 4 5 6 7 8 9 10 #!/bin/bash red=\u0026#39;\\033[31m\u0026#39; green=\u0026#39;\\033[32m\u0026#39; yellow=\u0026#39;\\033[33m\u0026#39; blue=\u0026#39;\\033[34m\u0026#39; pink=\u0026#39;\\E[1;35m\u0026#39; end=\u0026#39;\\E[0m\u0026#39; echo -e \u0026#34;${red} ======red======${end}\u0026#34; echo -e \u0026#34;${yellow} =====yellow=====${end}\u0026#34; 循环 当型循环和直到型循环 bash 1 2 3 4 5 6 while条件句 语法： while 条件 do 指令... done until\nbash 1 2 3 4 until 条件. do 指令... done for循环\ntext 1 2 3 4 for varName in 变量取值列表 do 指令... done 读取文件 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 1. cat.log|while read line do done 2. while read line do done\u0026lt;a.log 3) exec \u0026lt;a.log while read line do done linux产生随机数的 系统环境变量$RANDAM 范围 ==0-32767==\n随机数01-99之间的数字\nbash 1 $[RANDOM%99+1] # 一个数和一个数取余这个数，这个数一定小于这个数 openssl: openssl rand -base64 8\n通过时间获得随机数（date）: date +%s%N\n/dev/random设备：/dev/random设备，存储着系统当前运行的环境的实时数据。它可以看作是系统某个时候，唯一值数据，因此可以用作随机数元数据。我们可以通过文件读取方式，读得里面数据。我们可以通过文件读取方式，读得里面数据。/dev/urandom这个设备数据与random里面一样。只是，他是非阻塞的随机数发生器，读取操作不会产生阻塞。\nUUID：cat /proc/sys/kernel/random/uuid\nmkpasswd -l 8\n数组 Shell 数组用==括号==来表示，元素用==\u0026ldquo;空格\u0026rdquo;==符号分割开：array=(value1 value2 ... valuen)\n使用下标来定义数组: array[0]=value0 读取数组：${array[index]} 数组中的所有元素：${array[*]}\u0026quot; 或 ${array[@]}\u0026quot; 数组的长度： ${#array[*]} 或 ${#array[@]} ","permalink":"https://www.161616.top/awesome-bash-shell/","summary":"工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 该文整理一些常用的shell用法，及语法，并非介绍如何使用\n变量 变量可分为两类：环境变量ENV（全局）和局部变量。\nbash环境变量\n变量名 含义 _= 上一条命令的最后一个参数 BASH_VERSION=\u0026ldquo;4.1.2(1)-release\u0026rdquo; 当前bash实例的版本号 COLORS=\u0026quot;/etc/DIR_COLORS\u0026quot; COLUMNS=80 设置该变量就给shell编辑模式和选择命令定义了编辑窗口的宽度 CVS_RSH=\u0026ldquo;ssh\u0026rdquo; DIRSTACK 代表目录栈当前的内容 EUID=0 为在shell启动时被初始化的当前用户的有效ID G_BROKEN_FILENAMES=1 GROUPS=() 当前用户所属组 HISTFILE=/root/.bash_history 历史记录文件的全路径 HISTFILESIZE=50 历史文件能包含的最大行数 HISTSIZE=50 记录在命令行历史文件中的命令行数 HOME=/root 当前用户家目录 HOSTNAME= 当前主机机器名称 HOSTTYPE=x86_64 IFS=$\u0026rsquo;\\t\\n' 内容字段分隔符，一般是空格符、制表符、和换行符，用于由命令替换，循环结构中的表和读取的输入产生的词的字段划分。 INPUTRC=/etc/inputrc readline启动文件的文件名。取代默认的~/.inputrc JAVA_HOME=/app/jdk1.6 KDENIR=/usr KDE IS PRELINKED=1 LANG=zh_CN.GB18030 LESSONPEN LINES=36 LONGNAME=root 登陆的用户名 LS_COLORS=xx MACHTYPE=x86_64-redhat-linux-gnu 包含一个描述正在运行bash的系统串 MAILCHECK=60 这个参数定义shell将隔多长时间（以秒为单位检查一次由参数MAILPATH或MAILFILE）指定的文件，看看是否有邮件到达。默认600秒 MAIL=/var/spool/mail/root 邮件全路径 OLDPWD=/root 前一个当前工作目录 OPTERR=1 如果设置为1，秒年十时毫，来自getopts内置命令的错误信息。 OPTIND=1 下一个有getopts内置命令处理的参数序号 OSTYPE=linux-gnu 自动设置称一个串，该串标书正在运行bash的操作系统，默认值有系统决定 PATH 全局PATH路径。命令搜索路径。一个有冒号分隔的目录列表，shell用它来搜索命令。默认路径有系统决定，并且由安装bash的管理员设置。 PIPESTATUS=([0]=0 [1]=1) 一个数组，包含一列最进在管道执行的前台作业的进程退出状态值。 PPID=1112 父进程的进程ID PS1=[\\u@\\h \\W]$ 主提示符串，默认值是$ PS2= \u0026gt; 次提示符串，默认值是\u0026gt; PS4=+ 当开启追踪时使用的调试提示符串，默认值是+，追踪可用set-x开启。 PWD 当前用户家目录。 SHELL=/bin/bash SHLVL=1 每启动一个bash实例就将其加1 TMOUT=3600 退出前等待超时的秒数。 UID=0 当前用户的UID，在shell启动时初始化。 USER=root 当前用户的用户名，在shell启动时初始化。 自定义环境变量 export","title":"bash shell常用示例"},{"content":" 进入后，找到linux16 开头的一行！将ro改为 rw init=/sysroot/bin/sh\n查看passwd和 shadow 发现用户并没有锁，于是想到，应该是pam的设置。\ntext 1 pam_tally2.so deny=6 onerr=fail unlock_time=120 默认log在： /var/log/tallylog\ntext 1 2 3 4 chroot /sysroot # 使用pam_tally2命令解锁 pam_tally2 --user=root --reset rw init=/sysroot/bin/sh Reference Centos7.x破解密码\npam_tally2锁用户\n","permalink":"https://www.161616.top/account-locked-due-to-10-failed-logins/","summary":"进入后，找到linux16 开头的一行！将ro改为 rw init=/sysroot/bin/sh\n查看passwd和 shadow 发现用户并没有锁，于是想到，应该是pam的设置。\ntext 1 pam_tally2.so deny=6 onerr=fail unlock_time=120 默认log在： /var/log/tallylog\ntext 1 2 3 4 chroot /sysroot # 使用pam_tally2命令解锁 pam_tally2 --user=root --reset rw init=/sysroot/bin/sh Reference Centos7.x破解密码\npam_tally2锁用户","title":"Account locked due to 10 failed logins"},{"content":"import 规范\n引入了三种类型的包，标准库包，第三方包，程序内部包，建议采用如下方式进行组织你的包：\n有顺序的引入包，不同的类型采用空格分离，\n第一种标准库 第二是第三方包 第三是项目包。 在项目中不要使用相对路径引入包，在goland中可以使用如下设置自动格式化为引入标准\n打开设置：Editor \u0026gt; Code Style \u0026gt; Go，选择import标签，将排序改为goimports, 剩下的按照自己喜好进行修改即可\nReference goimports-group\n","permalink":"https://www.161616.top/go-mod-specification/","summary":"import 规范\n引入了三种类型的包，标准库包，第三方包，程序内部包，建议采用如下方式进行组织你的包：\n有顺序的引入包，不同的类型采用空格分离，\n第一种标准库 第二是第三方包 第三是项目包。 在项目中不要使用相对路径引入包，在goland中可以使用如下设置自动格式化为引入标准\n打开设置：Editor \u0026gt; Code Style \u0026gt; Go，选择import标签，将排序改为goimports, 剩下的按照自己喜好进行修改即可\nReference goimports-group","title":"goland设置import规范"},{"content":"D-Bus是Linux使用的进程间通信机制，允许各个进程互相访问，而不需要为每个其他组件实现自定义代码。即使对于系统管理员来说，这也是一个相当深奥的主题，但它确实有助于解释linux的另一部分是如何工作的。\n这里主要介绍 dbus-send 与 GDbus cli工具，其他的还有QtDbus , d-feet\u0026hellip;\n命令行工具dbus-send ，是freedesktoop提供的dbus包配套的命令客户端工具，可用于发送dbus消息。\nGDbus GLib实现的dbus工具。较与 dbus-send，拥有更完整的功能。\nd-feet: 可以处理所有D-Bus服务的GUI应用程序。\ndbus-send dbus有两种消息总线 （message bus）：system bus 和 session bus，通过使用 --system和 --session 选项来通过dbus-send 向系统总线或会话总线发送消息。如果两者都未指定，默认为**session bus*.\n借此，顺道聊下 system bus 和 session bus：\nSystem Bus:\n在桌面上，为所有用户提供一条总线. 专用于系统服务。 有关于低级时间，例如 网络连接，USB设备。 在嵌入式Linux系统中，system bus是唯一D-Bus类型。 Session Bus:\n每个用户会话一个实例 为用户应用提供那个桌面服务。 连接到 X-session 参数选项 参数 说明 --dest=NAME 这个是必选的参数，指定要接收消息的接口名称。例如 org.freedesktop.ExampleName --print-reply 打印回复消息 --print-reply=literal 如选项一样，打印回复正文。如有特殊字符，如对象或 object 则按字面打印，没有标点符号、转义字符等。 --reply-timeout= 可选参数，等待回复的超时时长，单位为 毫秒。 --system --session --type=method_call signal 必须始终指定要发送的消息的对象路径和名称。以下参数（如果有）是消息内容（消息参数）。这些值作为类型指定的值给出，可能包括如下所述的容器（数组、dict和变体）。\n支持参数 dbus-send 发送的消息，在调用方法需要传参数时，必须将这些值给出。dbus-send 支持传入的参数的类型，并不为D-Bus支持的所有的数据类型，仅为一些简单的类型：如\nType: 这里type 仅仅为简单的数据类型，即 type:content ,支持的内容如下： string | int16 | uint16 | int32 | uint32 | int64 | uint64 | double | byte | boolean | objpath。 数组：array = array:\u0026lt;type\u0026gt;:\u0026lt;value\u0026gt;[,\u0026lt;value\u0026gt;...] 词典: dict = dict:\u0026lt;type\u0026gt;:\u0026lt;type\u0026gt;:\u0026lt;key\u0026gt;,\u0026lt;value\u0026gt;[,\u0026lt;key\u0026gt;,\u0026lt;value\u0026gt;...]。 变体：variant = variant:\u0026lt;type\u0026gt;:\u0026lt;value\u0026gt;。 根据官网的解析出来后如上述集中数据类型，更详细的描述可以根据官方 dbus-send 进行参考。\n可以通过一张图来理解 dbus-send 发送一个消息所需的几个必须参数\n通过简单的命令，来了解一个 dbus-send 命令如何传入参数\nbash 1 2 3 4 5 6 7 8 dbus-send --dest=org.freedesktop.ExampleName \\ # service /org/freedesktop/sample/object/name \\ # object org.freedesktop.ExampleInterface.ExampleMethod \\ # interface.method int32:47 string:\u0026#39;hello world\u0026#39; double:65.32\t\\ # param int array:string:\u0026#34;1st item\u0026#34;,\u0026#34;next item\u0026#34;,\u0026#34;last item\u0026#34; \\ # param array dict:string:int32:\u0026#34;one\u0026#34;,1,\u0026#34;two\u0026#34;,2,\u0026#34;three\u0026#34;,3 \\ # param dict variant:int32:-8 \\ # param variant objpath:/org/freedesktop/sample/object/name # param object_path 使用案例 如列出所有总线接口\nshell 1 2 3 4 5 6 dbus-send --session \\ --dest=org.freedesktop.DBus \\ --type=method_call \\ --print-reply \\ /org/freedesktop/DBus \\ org.freedesktop.DBus.ListNames 查看对方总线所支持的对象接口，org.freedesktop.DBus.Introspectable 、org.freedesktop.DBus.Properties 和 org.freedesktop.PowerManagement。每个接口实现一些方法和信号。这些是你可以与之互动的东西。\nbash 1 2 3 4 5 6 dbus-send --session \\ --type=method_call \\ --print-reply \\ --dest=org.freedesktop.DBus \\ / \\ org.freedesktop.DBus.Introspectable.Introspect dbus-send，也支持调用远程总线接口，通过默认通过 DBUS_SESSION_BUS_ADDRESS 或 DBUS_SYSTEM_BUS_ADDRESS，来指定远程的总线。\ntext 1 2 3 4 5 6 7 DBUS_SESSION_BUS_ADDRESS=\u0026#34;\u0026#34; dbus-send --session \\ --type=method_call \\ --print-reply \\ --dest=org.freedesktop.DBus \\ / \\ org.freedesktop.DBus.Introspectable.Introspect dbus-monitor dbus-monitor 是一个可以监控 D-Bus 消息的命令行工具，。它可以调试和分析 D-Bus 通信间的数据包。\n监视所有消息 要在控制台上监视所有 D-Bus 消息，可以使用以下命令：\nbash 1 dbus-monitor 监视某个特定接口 go 1 dbus-monitor interface=\u0026#34;org.freedesktop.NetworkManager\u0026#34; 监视某个特定路径下的对象 bash 1 dbus-monitor path=\u0026#34;/org/freedesktop/NetworkManager\u0026#34; 监视某个特定接口和路径下的对象 bash 1 2 dbus-monitor interface=\u0026#34;org.freedesktop.NetworkManager\u0026#34; \\ path=\u0026#34;/org/freedesktop/NetworkManager\u0026#34; 抓包输出到 Wireshark text 1 dbus-monitor --pcap \u0026gt; 1.pcap ​\ngdbus gdbus是 GLib实现的dbus工具。较与 dbus-send，拥有更完整的功能。\nintrospect : 可以打印出对象的接口和属性值。对应对象的所有者需要实现org.freedesktop.DBus.Introspectable 的接口。使用 --xml选项，将打印返回的xml 格式。--recurse 选项可将其子级等打印，--only 选项仅打印具有属性的接口。\nmonitor: 类似于 dbus-monitor\ncall: 调用一个方法，传入的必须为 GVariant ,而相应的也为GVariant。\nemit: 发出信号。信号中包含的每个参数除字符串外都必须序列化为GVariant。\n使用案例\ntext 1 2 3 4 5 6 gdbus introspect --system \\ --dest org.freedesktop.UPower \\ --object-path \\ / \\ --recurse \\ --only-properties 通过call 来向一个dbus service发送信息\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 gdbus call --session \\ --dest org.freedesktop.Notifications \\ --object-path /org/freedesktop/Notifications \\ --method org.freedesktop.Notifications.Notify \\ my_app_name \\ 42 \\ gtk-dialog-info \\ \u0026#34;The Summary\u0026#34; \\ \u0026#34;Here\u0026#39;s the body of the notification\u0026#34; \\ [] \\ {} \\ 5000 (uint32 12,) 监听一个服务的对象\ntext 1 2 3 4 gdbus monitor \\ --system \\ --dest org.freedesktop.NetworkManager \\ --object-path /org/freedesktop/NetworkManager/AccessPoint/4141 发送信号\ntext 1 2 3 gdbus emit --session \\ --object-path /foo \\ --signal org.bar.Foo \u0026#34;[\u0026#39;foo\u0026#39;, \u0026#39;bar\u0026#39;, \u0026#39;baz\u0026#39;]\u0026#34; 想特定进程发送信号，`\u0026ndash;dest 为指定进程。\ntext 1 2 3 4 5 gdbus emit \\ --session \\ --object-path /bar \\ --signal org.bar.Bar someString \\ --dest :1.42 检查配置文件语法 CentOS 7 (dbus 1.10)，命令会启动一个dbus来验证配置文件的正确与否\nbash 1 dbus-launch --sh-syntax --config-file=/path/to/config \u0026gt; output.txt higher\nbash 1 dbus-daemon --config-file=/path/to/config --print-syntax ","permalink":"https://www.161616.top/dbus-client-tutorial/","summary":"D-Bus是Linux使用的进程间通信机制，允许各个进程互相访问，而不需要为每个其他组件实现自定义代码。即使对于系统管理员来说，这也是一个相当深奥的主题，但它确实有助于解释linux的另一部分是如何工作的。\n这里主要介绍 dbus-send 与 GDbus cli工具，其他的还有QtDbus , d-feet\u0026hellip;\n命令行工具dbus-send ，是freedesktoop提供的dbus包配套的命令客户端工具，可用于发送dbus消息。\nGDbus GLib实现的dbus工具。较与 dbus-send，拥有更完整的功能。\nd-feet: 可以处理所有D-Bus服务的GUI应用程序。\ndbus-send dbus有两种消息总线 （message bus）：system bus 和 session bus，通过使用 --system和 --session 选项来通过dbus-send 向系统总线或会话总线发送消息。如果两者都未指定，默认为**session bus*.\n借此，顺道聊下 system bus 和 session bus：\nSystem Bus:\n在桌面上，为所有用户提供一条总线. 专用于系统服务。 有关于低级时间，例如 网络连接，USB设备。 在嵌入式Linux系统中，system bus是唯一D-Bus类型。 Session Bus:\n每个用户会话一个实例 为用户应用提供那个桌面服务。 连接到 X-session 参数选项 参数 说明 --dest=NAME 这个是必选的参数，指定要接收消息的接口名称。例如 org.freedesktop.ExampleName --print-reply 打印回复消息 --print-reply=literal 如选项一样，打印回复正文。如有特殊字符，如对象或 object 则按字面打印，没有标点符号、转义字符等。 --reply-timeout= 可选参数，等待回复的超时时长，单位为 毫秒。 --system --session --type=method_call signal 必须始终指定要发送的消息的对象路径和名称。以下参数（如果有）是消息内容（消息参数）。这些值作为类型指定的值给出，可能包括如下所述的容器（数组、dict和变体）。\n支持参数 dbus-send 发送的消息，在调用方法需要传参数时，必须将这些值给出。dbus-send 支持传入的参数的类型，并不为D-Bus支持的所有的数据类型，仅为一些简单的类型：如","title":"Linux dbus命令行套件"},{"content":"What is Views drf提供了两个基类，五个视图扩展类，9个视图集\ndrf提供了一个Django中view的子类APIView ,主要变动大概为以下：\n重新封装了Request 与 Response实例。 使用了独有的Request与Response对象，并且提供了专有的解析器 Parser 可以根据HTTP Content-Type 指明的请求数据进行解析。 增加了自有的鉴权/节流 在django中dispatch() 分发前，会对请求进行身份认证、权限检查、流量控制。 异常捕获 APIException。 APIView implement\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @classmethod def as_view(cls, **initkwargs): .... # 调用父类的方法，Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx view = super(APIView, cls).as_view(**initkwargs) view.cls = cls # 并且生成一个新的request view.initkwargs = initkwargs # Note: session based authentication is explicitly CSRF validated, # all other authentication is CSRF exempt. return csrf_exempt(view) ## 父类的view会执行dispatch分配为对应的handle memory，通过method获得对应的方法处理请求 def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn\u0026#39;t exist, # defer to the error handler. Also defer to the error handler if the # request method isn\u0026#39;t on the approved list. if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs) What is GenericAPIView GenericAPIView 是继承与 APIView的子类，在 APIView 的基础上增加了对于视图的通用支持方法，用来简化用户代码的编写。主要增加了 QuerySet 与 Serializers\nGenericAPIView implement\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class GenericAPIView(views.APIView): queryset = None serializer_class = None lookup_url_kwarg = None def get_queryset(self): ... assert self.queryset is not None, ( \u0026#34;\u0026#39;%s\u0026#39; should either include a `queryset` attribute, \u0026#34; \u0026#34;or override the `get_queryset()` method.\u0026#34; % self.__class__.__name__ ) queryset = self.queryset if isinstance(queryset, QuerySet): # Ensure queryset is re-evaluated on each request. queryset = queryset.all() return queryset How to Use Reference APIView\nGenericAPIView\n使用APIView与使用View类似，像往常一样，请求会根据不同的方法被dispatch到对应的处理逻辑方法，例如.get()or .post()\n引入\npython 1 2 from rest_framework.views import APIView from rest_framework.response import Response 使用GenericAPIView 是 APIView 的子类，是实现了APIView 的常用行为的一个类。一般情况下会与引入\nqueryset：对象查询集，使用GenericAPIView 必须设置该属性，或者重写 get_queryset() 方法 serializer_class: 序列化器类，必须设置该属性或重写get_serializer_class()方法。 lookup_field: 查库时使用的条件字段，一般为传入的值，默认为pk pagination_class ：分页 python 1 2 3 4 5 6 7 8 9 10 11 12 from rest_framework import generics class BookViewSet(generics.GenericAPIView): queryset = Book.objects.all() serializer_class = BookModelSerializer def get(self, request): book_list = self.get_queryset() book_serializers = self.get_serializer(book_list, many=True) return Response(book_serializers.data) def delete(self, reques, pk): book = self.get_object().delete() return Response({\u0026#34;message\u0026#34;:\u0026#34;success\u0026#34;, \u0026#34;status\u0026#34;:100}) 五个视图扩展 Mixin类：DRF提供的通用的增删改查行为，Mixin一般与generics.GenericAPI 混用，可以组成灵活的视图。\nCreateModelMixin: 保存新对象实例 创建成功返回201与序列化后的列表，失败则返回400与错误的详细信息 UpdateModelMixin ：对现有对象实例进行更新 与创建相同，成功返回200，失败返回400 DestroyModelMixin：删除对象实例 成功删除返回204 错误将返回一个404 ListModelMixin：列出实例列表 查询成功返回200，需要设置queryset，相应数据可以设置分页 RetrieveModelMixin: 只读操作单个对象 九个视图集 在路由确定用于请求的控制器之后，您的控制器负责理解请求并产生适当的输出。\n— Ruby on Rails 文档Django REST 框架允许您将一组相关视图的逻辑组\n视图集 ViewSet 是DRF基于view使用视图集ViewSet，可以将一系列逻辑相关的动作放到一个类中，如.get()或.post()则不在提供了，换为.list()和.create()的具体逻辑动作。\n","permalink":"https://www.161616.top/python-django-restframework-view-set/","summary":"What is Views drf提供了两个基类，五个视图扩展类，9个视图集\ndrf提供了一个Django中view的子类APIView ,主要变动大概为以下：\n重新封装了Request 与 Response实例。 使用了独有的Request与Response对象，并且提供了专有的解析器 Parser 可以根据HTTP Content-Type 指明的请求数据进行解析。 增加了自有的鉴权/节流 在django中dispatch() 分发前，会对请求进行身份认证、权限检查、流量控制。 异常捕获 APIException。 APIView implement\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @classmethod def as_view(cls, **initkwargs): .... # 调用父类的方法，Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx view = super(APIView, cls).as_view(**initkwargs) view.cls = cls # 并且生成一个新的request view.initkwargs = initkwargs # Note: session based authentication is explicitly CSRF validated, # all other authentication is CSRF exempt.","title":"python drf之viewset"},{"content":"What is serializers？ serializers主要作用是将原生的Python数据类型（如 model querysets ）转换为web中通用的JSON，XML或其他内容类型。\nDRF 提供了一个Serializer类，它为您提供了种强大的通用方法来控制响应的输出，以及一个ModelSerializer 类，它为创建处理 model instance 和 serializers 提供了一个序列化的快捷方式。\nReference drf serializers manual\nHow to Declaring Serializers? 序列化一个django model\npython 1 2 3 4 5 6 7 class Comment: def __init__(self, email, content, created=None): self.email = email self.content = content self.created = created or datetime.now() comment = Comment(email=\u0026#39;leila@example.com\u0026#39;, content=\u0026#39;foo bar\u0026#39;) 声明Serializers，可以用来序列化与反序列化对象 Comment的属性及值。\npython 1 2 3 4 5 6 from rest_framework import serializers class CommentSerializer(serializers.Serializer): email = serializers.EmailField() # 属性名称与类Comment名校相同 content = serializers.CharField(max_length=200) created = serializers.DateTimeField() 序列化及反序列化 序列化 python 1 2 3 4 5 6 7 8 9 10 11 12 13 from rest_framework import serializers class CommentSerializer(serializers.Serializer): email = serializers.EmailField() content = serializers.CharField(max_length=200) created = serializers.DateTimeField() # 上面类似于如下python中的操作 from rest_framework.renderers import JSONRenderer json = JSONRenderer().render(serializer.data) json # b\u0026#39;{\u0026#34;email\u0026#34;:\u0026#34;leila@example.com\u0026#34;,\u0026#34;content\u0026#34;:\u0026#34;foo bar\u0026#34;,\u0026#34;created\u0026#34;:\u0026#34;2016-01-27T15:17:10.375877\u0026#34;}\u0026#39; 反序列化 反序列化是将json数据流解析为python的数据类型，后映射至对象\npython 1 2 3 4 5 6 7 8 9 10 11 import io from rest_framework.parsers import JSONParser stream = io.BytesIO(json) data = JSONParser().parse(stream) serializer = CommentSerializer(data=data) serializer.is_valid() # True serializer.validated_data # {\u0026#39;content\u0026#39;: \u0026#39;foo bar\u0026#39;, \u0026#39;email\u0026#39;: \u0026#39;leila@example.com\u0026#39;, \u0026#39;created\u0026#39;: datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)} 数据的落地 如果需要对经过认证的数据进行保存入库，需要实现对应 serializer的 create() 和 update() 方法\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 class CommentSerializer(serializers.Serializer): email = serializers.EmailField() content = serializers.CharField(max_length=200) created = serializers.DateTimeField() def create(self, validated_data): # validate_data 实际与 Comment一致，打散后为 return Comment(**validated_data) def update(self, instance, validated_data): # drf serializer实现了对应的实例，instance是该serializer，vilidated是对应的属性 instance.email = validated_data.get(\u0026#39;email\u0026#39;, instance.email) instance.content = validated_data.get(\u0026#39;content\u0026#39;, instance.content) instance.created = validated_data.get(\u0026#39;created\u0026#39;, instance.created) return instance text 1 2 3 4 5 serializer = CommentSerializer(data={\u0026#39;email\u0026#39;: \u0026#39;foobar\u0026#39;, \u0026#39;content\u0026#39;: \u0026#39;baz\u0026#39;}) serializer.is_valid() # False serializer.errors # {\u0026#39;email\u0026#39;: [\u0026#39;Enter a valid e-mail address.\u0026#39;], \u0026#39;created\u0026#39;: [\u0026#39;This field is required.\u0026#39;]} save() save() 可以创建或更新一个实例（实例是值库中的行）。\npython 1 2 3 4 5 # .save() will create a new instance. serializer = CommentSerializer(data=data) # .save() will update the existing `comment` instance. serializer = CommentSerializer(comment, data=data) How to Use validate? validate是值在反序列化数据时，需要对数据进行验证（如，长度，值，类型），即在数据落地前，对其制定的规则进行验证。\npython 1 2 3 4 5 serializer = CommentSerializer(data={\u0026#39;email\u0026#39;: \u0026#39;foobar\u0026#39;, \u0026#39;content\u0026#39;: \u0026#39;baz\u0026#39;}) serializer.is_valid() # False serializer.errors # {\u0026#39;email\u0026#39;: [\u0026#39;Enter a valid e-mail address.\u0026#39;], \u0026#39;created\u0026#39;: [\u0026#39;This field is required.\u0026#39;]} .is_valid() 是对数据的验证。raise_exception 是一个可选参数，如果 serializers.ValidationError如果存在验证错误，将引发异常。异常由 REST framework 提供的默认异常处理程序自动处理，并HTTP 400 Bad Request默认返回响应。\npython 1 2 # Return a 400 response if the data was invalid. serializer.is_valid(raise_exception=True) 字段的验证 自定义验证 单字段验证\n通过子类 .validate_\u0026lt;field_name\u0026gt; 方法进行自定义验证方式，该方法需要返回验证的值或触发serializers.ValidationError. 例如：\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 from rest_framework import serializers class BlogPostSerializer(serializers.Serializer): title = serializers.CharField(max_length=100) content = serializers.CharField() def validate_title(self, value): \u0026#34;\u0026#34;\u0026#34; Check that the blog post is about Django. \u0026#34;\u0026#34;\u0026#34; if \u0026#39;django\u0026#39; not in value.lower(): raise serializers.ValidationError(\u0026#34;Blog post is not about Django\u0026#34;) return value 类级别验证\n如果需要对多个字段进行验证验证，需要在类中实现validate() 方法。该方法仅单个参数 data, 为验证的字段的字典。例如\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 from rest_framework import serializers class EventSerializer(serializers.Serializer): description = serializers.CharField(max_length=100) start = serializers.DateTimeField() finish = serializers.DateTimeField() def validate(self, data): \u0026#34;\u0026#34;\u0026#34; Check that start is before finish. \u0026#34;\u0026#34;\u0026#34; if data[\u0026#39;start\u0026#39;] \u0026gt; data[\u0026#39;finish\u0026#39;]: raise serializers.ValidationError(\u0026#34;finish must occur after start\u0026#34;) return data 忽略验证 注意：如在Serializer 的\u0026lt;field_name\u0026gt; 声明了参数required=False 则该字段不会进行验证。\n指定验证器 Serializer 的\u0026lt;field_name\u0026gt; 还可以声明 validator，例如，\npython 1 2 3 4 5 6 7 def multiple_of_ten(value): if value % 10 != 0: raise serializers.ValidationError(\u0026#39;Not a multiple of ten\u0026#39;) class GameRecord(serializers.Serializer): score = IntegerField(validators=[multiple_of_ten]) ... validator Reference\nvalidator\nModelSerializer ModelSerializer，是drf为了方便实现好的可以直接用的Serializer。实现为：\n将根据模型自动为您生成一组字段。 将自动为Serializer程序生成validator，例如 unique_together 验证器。 包括简单的实现默认的.create()和.update()。 ModelSerializer的声明 python 1 2 3 4 class AccountSerializer(serializers.ModelSerializer): class Meta: model = Account fields = [\u0026#39;id\u0026#39;, \u0026#39;account_name\u0026#39;, \u0026#39;users\u0026#39;, \u0026#39;created\u0026#39;] Meta的说明 Meta 类，如名称可知，这是设置Serializer的一些元数据。包含Model,Filed, Validator等信息，例如声明一个Meta类。\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class EventSerializer(serializers.Serializer): name = serializers.CharField() room_number = serializers.IntegerField(choices=[101, 102, 103, 201]) date = serializers.DateField() class Meta: # 通过在内部Meta类中声明validators来包含，如下所示： validators = [ UniqueTogetherValidator( queryset=Event.objects.all(), fields=[\u0026#39;room_number\u0026#39;, \u0026#39;date\u0026#39;] ) ] # 通过在内部Meta类中声明model来包含对应使用model，如下所示： model = User # fields 可以指定要序列化的字段，\u0026#39;__all__\u0026#39;为model中的所有字段 fields = [\u0026#39;username\u0026#39;, \u0026#39;email\u0026#39;, \u0026#39;profile\u0026#39;] exclude=[\u0026#39;username\u0026#39;] # exclude是要排除的字段 注：从 3.3.0 版开始，必须提供以下属性之一fields或exclude.\nSerializer会在Meta中拿取自己对应的属性进行使用，例如\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 meta = getattr(self, \u0026#39;Meta\u0026#39;, None) validators = getattr(meta, \u0026#39;validators\u0026#39;, None) # assert \u0026lt;condition\u0026gt;,(..error message) # 可以看到Meta和Meta.model必须要设置 assert hasattr(self, \u0026#39;Meta\u0026#39;), ( \u0026#39;Class {serializer_class} missing \u0026#34;Meta\u0026#34; attribute\u0026#39;.format( serializer_class=self.__class__.__name__ ) ) assert hasattr(self.Meta, \u0026#39;model\u0026#39;), ( \u0026#39;Class {serializer_class} missing \u0026#34;Meta.model\u0026#34; attribute\u0026#39;.format( serializer_class=self.__class__.__name__ ) ) if model_meta.is_abstract_model(self.Meta.model): raise ValueError( \u0026#39;Cannot use ModelSerializer with Abstract Models.\u0026#39; ) 其他用法 设置只读字段：字段属性中添加 read_only=True, 或者在Meta类中添加属性 中指定字段 read_only_fields 为列表。\nreadonly-field\nSerializer的字段与字段属性属性 Reference\nfields\n字段属性 read_only 在创建或更新时改属性True字段都被忽略 write_only 仅为创建或更新时使用，序列化时不操作该字段 required 默认情况下，在反序列化时未提供字段会引发错误，如果不需要可以设置为False source： 用于序列化时，填充替代对应字段名称的作用 URLField(source='get_absolute_url') 可以跨表 可以执行对象内方法。 Many: 可以返回多个对象，而非一个，在objects.all时使用 字段类型 BooleanField() CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True) 文本字段 EmailField(max_length=None, min_length=None, allow_blank=False) email字段 RegexField(regex, max_length=None, min_length=None, allow_blank=False) 正则表达式 IPAddressField(protocol='both', unpack_ipv4=False, **options) IP地址 SerializerMethodField(method_name=None) 通过方法序列化，只读字段 method_name 序列化时通过方法的名称。默认为get_\u0026lt;field_name\u0026gt;. ","permalink":"https://www.161616.top/python-django-restframework-serializers/","summary":"What is serializers？ serializers主要作用是将原生的Python数据类型（如 model querysets ）转换为web中通用的JSON，XML或其他内容类型。\nDRF 提供了一个Serializer类，它为您提供了种强大的通用方法来控制响应的输出，以及一个ModelSerializer 类，它为创建处理 model instance 和 serializers 提供了一个序列化的快捷方式。\nReference drf serializers manual\nHow to Declaring Serializers? 序列化一个django model\npython 1 2 3 4 5 6 7 class Comment: def __init__(self, email, content, created=None): self.email = email self.content = content self.created = created or datetime.now() comment = Comment(email=\u0026#39;leila@example.com\u0026#39;, content=\u0026#39;foo bar\u0026#39;) 声明Serializers，可以用来序列化与反序列化对象 Comment的属性及值。\npython 1 2 3 4 5 6 from rest_framework import serializers class CommentSerializer(serializers.","title":"python drf之Serializer"},{"content":"路由匹配 django中默认匹配页 text 1 url(r\u0026#39;^$\u0026#39;, views.login), django中404匹配 text 1 url(r\u0026#39;^$\u0026#39;, views.login), # 需要放置最后，不过一般不推荐，都是通过异常捕获处理 named group 名称组 https://docs.djangoproject.com/en/1.11/topics/http/urls/#named-groups\ntext 1 url(r\u0026#39;^test[0-9]{4}\u0026#39;,views.login) 反向解析 别名不能出现冲突\ntext 1 2 from django.shortcuts import reverse reverse(xxx) 名称组反向解析 无名分组\npython 1 2 3 4 5 6 # 路由部分 url(r\u0026#39;^index/(\\d+)/\u0026#39;, views.home, name=\u0026#39;xxx\u0026#39;) # 前端 \u0026lt;a href=\u0026#34;{% url \u0026#39;id\u0026#39; obj.id %}\u0026#34; class=\u0026#34;btn btn-primary btn-xs\u0026#34;\u0026gt;remove\u0026lt;/a\u0026gt;s # 后端 print reverse(\u0026#39;id\u0026#39;, args=(id,)) 有名称分组\npython 1 2 3 4 5 6 7 8 # 路由部分 url(r\u0026#34;^userdel/(?P\u0026lt;id\u0026gt;\\d+)/\u0026#34;,views.UserDelete, name=\u0026#39;id\u0026#39;), # 前端 \u0026lt;a href=\u0026#34;{% url \u0026#39;id\u0026#39; obj.id %}\u0026#34; class=\u0026#34;btn btn-primary btn-xs\u0026#34;\u0026gt;remove\u0026lt;/a\u0026gt; # or \u0026lt;a href=\u0026#34;{% url \u0026#39;id\u0026#39; id=obj.id %}\u0026#34; class=\u0026#34;btn btn-primary btn-xs\u0026#34;\u0026gt;remove\u0026lt;/a\u0026gt;s # 后端 print reverse(\u0026#39;id\u0026#39;, kwargs={\u0026#34;id\u0026#34;:id}) 路由分发 路由分发中，并不能识别出，名称分组并不能准确识别出对应的分组，这里需要增加namespace概念\npython 1 2 3 4 5 6 7 from django.conf.urls import url, include from django.contrib import admin from memberserver import urls as member_urls urlpatterns = [ url(r\u0026#39;member\u0026#39;, include(member_urls)), ] 路由分发中，并不能识别出，名称分组并不能准确识别出对应的分组，这里需要增加namespace概念\npython 1 2 3 4 5 6 7 8 9 10 11 12 from django.conf.urls import url, include from django.contrib import admin from memberserver import urls as member_urls urlpatterns = [ url(r\u0026#39;member\u0026#39;, include(member_urls, namespace=\u0026#34;member\u0026#34;)), ] # 在后端映射可以使用 reverse(\u0026#34;member:id\u0026#34;) ## 来获得对应的路由 # 在前端可以使用 来获得对应的路由 {% url \u0026#39;id\u0026#39; id=obj.id %} 伪静态\n虚拟环境\n","permalink":"https://www.161616.top/python-django/","summary":"路由匹配 django中默认匹配页 text 1 url(r\u0026#39;^$\u0026#39;, views.login), django中404匹配 text 1 url(r\u0026#39;^$\u0026#39;, views.login), # 需要放置最后，不过一般不推荐，都是通过异常捕获处理 named group 名称组 https://docs.djangoproject.com/en/1.11/topics/http/urls/#named-groups\ntext 1 url(r\u0026#39;^test[0-9]{4}\u0026#39;,views.login) 反向解析 别名不能出现冲突\ntext 1 2 from django.shortcuts import reverse reverse(xxx) 名称组反向解析 无名分组\npython 1 2 3 4 5 6 # 路由部分 url(r\u0026#39;^index/(\\d+)/\u0026#39;, views.home, name=\u0026#39;xxx\u0026#39;) # 前端 \u0026lt;a href=\u0026#34;{% url \u0026#39;id\u0026#39; obj.id %}\u0026#34; class=\u0026#34;btn btn-primary btn-xs\u0026#34;\u0026gt;remove\u0026lt;/a\u0026gt;s # 后端 print reverse(\u0026#39;id\u0026#39;, args=(id,)) 有名称分组\npython 1 2 3 4 5 6 7 8 # 路由部分 url(r\u0026#34;^userdel/(?","title":"python django使用"},{"content":"https://www.cnblogs.com/Dominic-Ji/p/11516152.html\n对象关系映射（Object Relational Mapping，简称ORM）模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。\n简单的说，ORM是通过使用描述对象和数据库之间映射的元数据，将程序中的对象自动持久化到关系数据库中。\nORM在业务逻辑层和数据库层之间充当了桥梁的作用。\ndjango中仅测试ORM 导入model，然后直接使用对应对象进行ORM操作。\ntext 1 2 3 4 5 6 7 import os if __name__ == \u0026#34;__main__\u0026#34;: os.environ.setdefault(\u0026#34;DJANGO_SETTINGS_MODULE\u0026#34;, \u0026#34;app.settings\u0026#34;) import django django.setup() from xxx import models models.User.objects.all() 连接数据库 django配置数据库\npython 1 2 3 4 5 6 7 8 9 10 DATABASES = { \u0026#39;default\u0026#39;: { \u0026#39;ENGINE\u0026#39;: \u0026#39;django.db.backends.mysql\u0026#39;, \u0026#39;USER\u0026#39;: \u0026#39;root\u0026#39;, \u0026#39;PASSWORD\u0026#39;: \u0026#39;111\u0026#39;, \u0026#39;HOST\u0026#39;:\u0026#39;127.0.0.1\u0026#39;, \u0026#39;NAME\u0026#39;: \u0026#39;book\u0026#39;, \u0026#39;CHARSET\u0026#39;: \u0026#39;utf8\u0026#39; } } 可选：pymysql 使用模块连接MySQL数据库:：在项目中__init__.py 文件中添加配置：\npython 1 2 import pymysql pymysql.install_as_MySQLdb() 创建（表）对象 ORM中，O (Object) 代表\u0026quot;对象\u0026quot;，而R(Relational) 则代表\u0026quot;关系\u0026quot;。所以创建表即创建一个类，字段则是类的属性。类的每个实例则对应表中的一条记录。\n在Django中model就是你数据来源。通常，一个model映射到一个数据库表，一般情况下基本满足：\n每个model（表）都是一个Python类，它是django.db.models.Model的子类即继承models.Model。\n类的每个属性都代表一个字段（字段）。\n实例化出的对象，代表表中的记录。\n例如\npython 1 2 3 4 class User(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() registration_time = models.DateField() 字段类型 Reference 字段类型说明\n字段选项说明\n必需掌握字段类型说明：\nBigIntegerField or BigAutoField: 1~9223372036854775807 的64位自增int\nCharField: 用于存储字符串，对应MySQL中varchar\nDateField: python中的datetime.date.today() 即 Y-m-d\nDateTimeField：timezone.now - django.utils.timezone.now()为YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]，相当于Python中的datetime.datetime()\nDecimalField：小数，使用：models.DecimalField(..., max_digits=5, decimal_places=2)\nBooleanField: 代表一个true/false的布尔值。\nAutoField: int类型的自增列，必须填入参数primary_key=True 。当model中如果没有自增列，则自动会创建一个列名为id的列。\n字段参数 Reference\n字段选项说明\n必须掌握的字段选项：\nnull：表示该字段是否允许空值，如果为true，django将在数据库中将空值存储为null。默认为false。使用：models.CharField(null=True) db_index：是否对字段创建索引，如果为true，将为此字段创建数据库索引。 default：字段的默认值。这可以是值或可调用的对象。如果可调用它将每次创建新对象时调用它。 primary_key：primary_key=True，该字段为主键。 时间字段特殊参数：\nauto_now_add: auto_now_add=True，仅在创建对象时将当前时间插入到数据库中。如果不设置 auto_now: auto_now=True，每次更新数据记录的时候会更新该字段。 增 save() Python对象中表示数据库表数据，模型类表示数据库表，该类的实例表示数据库表中的记录。\n要创建一个对象，然后调用save()将其保存到数据库中。\nsave() 直到调用时，才操作数据库，并且没有返回值。\nsave() 必须实例化后调用\npython 1 2 b = models.User(name=\u0026#34;zhangsan\u0026#34;,age=18) b.save() create() 创建对象并且保存\npython 1 models.User.objects.create(name=\u0026#34;lisi\u0026#34;, age=19) 删 delete() delete() 在查询集中的所有行上执行SQL DELETE，并返回删除的对象数量和每个对象类型的删除次数的字典。\ndelete()无法对QuerySet 上调用delete()\npython 1 models.User.objects.filter(pk=1).delete() 查 查询必会的方法 返回值为QuerySet对象的方法有\nall() 查询所有数据 filter() 带有过滤条件的查询 exclude() 排除数据，exclude('xxx=xxx') order_by() 排序 降序 models.User.objects.order_by('-age') reverse() 反转，反转的数据必须是 order_by()后的数据 distinct() 去重，主键是唯一值，需要过滤主键\n返回值为特殊的QuerySet\nvalues() 返回一个可迭代的字典序列。（列表套字典） values_list() 返回一个可迭代的元祖序列。（列表套QuerySet）\n返回值为具体对象\nget() first() last()\n返回值布尔值：\nexists()\n返回值为数字\ncount() 统计当前数据个数\ndjango 查看原生SQL的方法 ``QuerySet 可以使用models.User.objects.values_list().query`\n终端打印，在setting.py中配置下列\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 LOGGING = { \u0026#39;version\u0026#39;: 1, \u0026#39;disable_existing_loggers\u0026#39;: False, \u0026#39;handlers\u0026#39;: { \u0026#39;console\u0026#39;:{ \u0026#39;level\u0026#39;:\u0026#39;DEBUG\u0026#39;, \u0026#39;class\u0026#39;:\u0026#39;logging.StreamHandler\u0026#39;, }, }, \u0026#39;loggers\u0026#39;: { \u0026#39;django.db.backends\u0026#39;: { \u0026#39;handlers\u0026#39;: [\u0026#39;console\u0026#39;], \u0026#39;propagate\u0026#39;: True, \u0026#39;level\u0026#39;:\u0026#39;DEBUG\u0026#39;, }, } } 条件查询 基于双下划线的查询 查询大于的18的用户 models.User.objects.filter(age__gt=12)\n查询年龄为18,19,20岁的用户 models.User.objects.filter(age__in=[18,19,20])\n查询90后用户 models.User.objects.filter(age__range=[22,31])\n模糊查询：查询名字包含l 的用户：models.User.objects.filter(name__contains='li')\n模糊查询：忽略大小写查询：models.User.objects.filter(name__icontains='li') 查询注册时间为 2020 7月份数据：models.User.objects.filter(registration_time__month=7)\n多表操作 在django中外键的存在使得ORM框架在处理表关系的时候异常的强大。在Django中，外键类定义为：class ForeignKey(to,on_delete,**options) 。可以看到外键的参数大致分为：\nto：引用那个model（表）。 on_delete：当使用了外键引用model（表）的数据被删除后的操作。 定义一个外键：\n在关系数据库中外键的作用是在于将表彼此关联起来。Django提供了定义三种最常见的数据库关系类型的方法：多对一、多对多和一对一。\n而关系型字段分为：\n关系型字段 对应关系 ForeignKey 多对一 ManyToManyField 多对多 OneToOneField 一对一 如下：\n一个作者可以写多本书，但一本书只能由一个出版社出版，使用 ForeignKey 可以直接使用Book实例中通过 Press 属性来操作对应的Press模型。 一本书 可以由多个 Author 编写，也可以由一个作者 Author 编写，但一个作者( Author)也可以编写多本书 Book。 一般情况下，出版社仅记录Author的一个联系方式，也就是 Author 与 AuthorDetail 为一对一关系。 python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=5, decimal_places=2) publishData = models.DateField(auto_now_add=True) press = models.ForeignKey(to=\u0026#34;Press\u0026#34;) author = models.ManyToManyField(to=\u0026#34;Author\u0026#34;) class Press(models.Model): name = models.CharField(max_length=32,null=True) address = models.CharField(max_length=32) email = models.EmailField() class Author(models.Model): name = models.CharField(max_length=32,null=True) age = models.IntegerField() authorDetail = models.OneToOneField(to=\u0026#34;AuthorDetail\u0026#34;) class AuthorDetail(models.Model): phoneNumber = models.BigIntegerField() address = models.CharField(max_length=32) 外键的基本操作 添加外键关系：\npython 1 2 3 bookobj = models.Book.objects.filter(pk=1).first() bookobj.author.add(1) # 给主键为1的书籍绑定一个主键1的作者 bookobj.author.add([1,2,3]) # 给主键为1的书籍绑定多个作者 移除关系：bookobj.author.remove(1)\n修改关系：bookobj.author.set(2)\n清空该关系：bookobj.author.clear() # 清除所有这个作者的书\n正反向概念 正向查询：在子表中，查询父表（外键所在表）的信息 反向查询：通过父表，查询子表的信息\n多表查询 查询口诀：正向查询按外键字段，反向查询按表名（model）\nall() 当结果为多个时，需要使用.all() 如多对多，一对多\n查询书籍1的出版社 正向查询\ntext 1 2 book = models.Book.objects.filter(pk=1).first() print book.press.name 查询书籍1的作者 正向查询\ntext 1 2 book = models.Book.objects.filter(pk=1).first() print book.author.all() 查询作者1的电话\ntext 1 2 auther = models.Author.objects.filter(pk=1).first() print auther.authorDetail.phoneNumber 查询出版社拥有的书 反向查询\npython 1 2 press = models.Press.objects.filter(pk=1).first() books = press.book_set.all() 查询作者Phoenix写的书\npython 1 2 auther = models.Author.objects.filter(name=\u0026#34;Phoenix\u0026#34;).first() print auther.book_set.all() 根据手机号查询作者\npython 1 2 phone = models.AuthorDetail.objects.filter(phoneNumber=1511111111).first() print phone.author.name 基于下划线的查询 根据名称查询手机号\npython 1 2 3 models.Author.objects.filter(name=\u0026#34;Phoenix\u0026#34;).values(\u0026#34;authorDetail__phoneNumber\u0026#34;) # 获取两个表中的字段 models.Author.objects.filter(name=\u0026#34;Phoenix\u0026#34;).values(\u0026#34;authorDetail__phoneNumber\u0026#34;,\u0026#34;name\u0026#34;) 查询书籍1的作者名称\npython 1 2 3 4 # 正向 models.Book.objects.filter(pk=1).values(\u0026#34;author__name\u0026#34;) # 反向 models.AuthorDetail.objects.filter(author__name=\u0026#34;Phoenix\u0026#34;).values(\u0026#34;phoneNumber\u0026#34;,\u0026#34;author__name\u0026#34;) 查询书籍1的作者手机号\ntext 1 models.Book.objects.filter(pk=1).values(\u0026#34;author__authorDetail__phoneNumber\u0026#34;) 聚合查询 (aggregate) aggregate() 是 QuerySet 的一个终止子句，意思是说，它返回一个包含一些键值对的字典。键的名称是聚合值的标识符，值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。\n使用聚合查询需要引入具体的类 from django.db.models import Avg,Max,Min,Count,Sum\n获取书的总数 Book.objects.count()\n对数据进行聚合查询：aggregate(别名 = 聚合函数名[avg,max..](\u0026quot;属性名称\u0026quot;))\n分组查询（annotate） 分组查询一般会与聚合函数一起使用，使用前也许引入具体类：from django.db.models import Avg,Max,Min,Count,Sum\n返回值：\n分组后，用 values 取值，则返回值是 QuerySet 数据类型里面为一个个字典； 分组后，用 values_list 取值，则返回值是 QuerySet 数据类型里面为一个个元组。 分组位置 annotate：\nvalues or values_list 在 annotate 前：values 或者 values_list 是声明以什么字段分组，annotate 执行分组。 values or values_list 在annotate后： annotate 表示直接以当前表的pk执行分组，values 或者 values_list 表示查询哪些字段， 并且要将 annotate 里的聚合函数起别名，在 values 或者 values_list 里写其别名。 统计每本书的作者有几个\npython 1 models.Book.objects.annotate(autherNum=Count(\u0026#39;author__id\u0026#39;)).values(\u0026#39;autherNum\u0026#39;,\u0026#39;title\u0026#39;) 统计出版社最便宜书的价格\npython 1 models.Press.objects.annotate(minPrice=Min(\u0026#39;book__price\u0026#39;)).values(\u0026#34;name\u0026#34;, \u0026#34;minPrice\u0026#34;) 统计不止一个作者的书\npython 1 models.Book.objects.annotate(autherCount=Count(\u0026#34;author__id\u0026#34;)).filter(autherCount__gt=1).values(\u0026#34;title\u0026#34;,\u0026#34;autherCount\u0026#34;) 统计作者出书的总价\npython 1 models.Author.objects.annotate(bookPrice=Sum(\u0026#34;book__price\u0026#34;)).values(\u0026#34;name\u0026#34;,\u0026#34;bookPrice\u0026#34;) 根据指定字段分组\nF\u0026amp;Q查询 F查询 F 可以在对Model字段值的转换时，无需从数据库中将值加载到内存中，进行操作后再save()。\n例如。通常情况下，在更新数据时需先从数据库里将原数据加载到内存里，编辑后最后提交。\npython 1 2 3 order = Order.objects.get(orderid=\u0026#39;1\u0026#39;) order.amount += 1 order.save() 而F 可以直接对值进行运行而不必将数据从库中拉到内存中。例如\n卖出大于库存的书籍\npython 1 models.Book.objects.filter(sell__gt=F(\u0026#39;stock\u0026#39;)) 对所有书籍价格增加100\npython 1 models.Book.objects.update(price=F(\u0026#39;price\u0026#39;)+100) Q查询 ORM filter() 等方法中的关键字参数查询都是一起进行 AND 的。 如需要执行更复杂的查询（例如OR语句），你可以使用Q对象。\nQ是对查询条件进行字符串拼接，故可以组合 \u0026amp; 和| 等操作符以及使用括号进行分组来编写任意复杂的Q对象。同时，Q 对象可以使用~ 操作符取反。\nQ 对象允许组合正常的查询和取反(NOT) 查询。\n如**：查询作者是的Radamandis和Phoenix**\npython 1 models.Book.objects.filter(Q(authors__name=\u0026#34;Phoenix\u0026#34;)|Q(authors__name=\u0026#34;Radamandis\u0026#34;)) 查询作者不是Phoenix的书\npython 1 models.Book.objects.filter(~Q(author__name=\u0026#34;Phoenix\u0026#34;)) 也可以进行组合查询: 查询作者不是Phoenix 并且价格大于700\ntext 1 models.Book.objects.filter(~Q(author__name=\u0026#34;Phoenix\u0026#34;) \u0026amp; Q(publishData__gt=\u0026#34;2021-08-04\u0026#34;)) Q的第二种使用方法\npython 1 2 3 4 5 6 7 query = Q() query.connector = \u0026#39;OR\u0026#39; #默认为and query.children.append((\u0026#39;id\u0026#39;, 1)) query.children.append((\u0026#39;id\u0026#39;, 2)) query.children.append((\u0026#39;id\u0026#39;, 3)) models.Book.objects.filter(query) 事务 在操作多表，或多次变更数据时，这些数据的修改应该是一个整体事务，即要么一起成功，要么一起失败。Django 默认的事务行为是自动提交，即每执行一次则会自动提交到数据库。\n在django中事务的使用是通过django.db.transaction模块提供的atomic来定义事务。所以使用事务需要先引入from django.db import transaction\n事务的使用可以通过装饰器 或 with语句。\n通过装饰器方式（全局事务），在整个函数内为一个事务，要么一起成功，要么一起失败。\npython 1 2 3 4 5 6 7 8 @transaction.atomic def test(): models.Press.objects.create(name=\u0026#34;Yasgot\u0026#34;,address=\u0026#34;Nordische Botschaften\u0026#34;, email=\u0026#34;yasgot.com@gamil.com\u0026#34;) print \u0026#34;insert ok.\u0026#34; time.sleep(10) models.Press.objects.create(name=\u0026#34;Yasgot\u0026#34;,address=\u0026#34;Nordische Botschaften\u0026#34;, email=\u0026#34;yasgot.com@gamil.com\u0026#34;) book = models.Press.objects.all() print book 通过with方式（局部事务），在函数中，使用 with transaction.atomic(): 代码块内的为一个事务。\npython 1 2 3 def viewfunc(request): with transaction.atomic(): # 这部分代码会在事务中执行 事务的异常处理 保存点 保存点（savepoint），在事务中可以做到部分回滚，而不是整个事务。\natomic() 为开启一个事务，而回滚是通过，transaction.rollback() 执行的完全回滚。而django也推荐仅使用atomic()。\nsavepoint(*using=None*)：创建新的保存点，返回保存点ID (sid) 。\nsavepoint_commit(*sid*, *using=None*)：释放保存点 sid 。如回滚等将不在保证之前的保存点的数据而是整个事务。\nsavepoint_rollback(*sid*, *using=None*):回滚事务 sid 。\n下面是官方的一个例子：example-to-savepoint\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from django.db import transaction # open a transaction @transaction.atomic def viewfunc(request): a.save() # transaction now contains a.save() sid = transaction.savepoint() b.save() # transaction now contains a.save() and b.save() if want_to_keep_b: transaction.savepoint_commit(sid) # open transaction still contains a.save() and b.save() else: transaction.savepoint_rollback(sid) # open transaction now contains only a.save() 执行原生SQL Reference raw-sql\nraw() :执行原生语句 django.db.models.query.RawQuerySet\ndjango.db.connection()：；连接多个库 from django.db import connection\n自定义字段类 Reference custom-filed\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 class FixedCharField(models.Field): \u0026#34;\u0026#34;\u0026#34; 自定义的 char 类型的字段类 \u0026#34;\u0026#34;\u0026#34; def __init__(self, max_length, *args, **kwargs): self.max_length = max_length super(FixedCharField, self).__init__(max_length=max_length, *args, **kwargs) def db_type(self, connection): \u0026#34;\u0026#34;\u0026#34; 限定生成数据库表的字段类型为 char，长度为 max_length 指定的值 \u0026#34;\u0026#34;\u0026#34; return \u0026#39;char(%s)\u0026#39; % self.max_length Reference transactions\n","permalink":"https://www.161616.top/django-orm/","summary":"https://www.cnblogs.com/Dominic-Ji/p/11516152.html\n对象关系映射（Object Relational Mapping，简称ORM）模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。\n简单的说，ORM是通过使用描述对象和数据库之间映射的元数据，将程序中的对象自动持久化到关系数据库中。\nORM在业务逻辑层和数据库层之间充当了桥梁的作用。\ndjango中仅测试ORM 导入model，然后直接使用对应对象进行ORM操作。\ntext 1 2 3 4 5 6 7 import os if __name__ == \u0026#34;__main__\u0026#34;: os.environ.setdefault(\u0026#34;DJANGO_SETTINGS_MODULE\u0026#34;, \u0026#34;app.settings\u0026#34;) import django django.setup() from xxx import models models.User.objects.all() 连接数据库 django配置数据库\npython 1 2 3 4 5 6 7 8 9 10 DATABASES = { \u0026#39;default\u0026#39;: { \u0026#39;ENGINE\u0026#39;: \u0026#39;django.db.backends.mysql\u0026#39;, \u0026#39;USER\u0026#39;: \u0026#39;root\u0026#39;, \u0026#39;PASSWORD\u0026#39;: \u0026#39;111\u0026#39;, \u0026#39;HOST\u0026#39;:\u0026#39;127.0.0.1\u0026#39;, \u0026#39;NAME\u0026#39;: \u0026#39;book\u0026#39;, \u0026#39;CHARSET\u0026#39;: \u0026#39;utf8\u0026#39; } } 可选：pymysql 使用模块连接MySQL数据库:：在项目中__init__.py 文件中添加配置：\npython 1 2 import pymysql pymysql.","title":"django ORM"},{"content":" phenomenon： Specified key was too long; max key length is 3072 bytes\n在修改一个数据库字段时，字段容量被限制为了表前缀的大小而不是本身的容量大小\n查了一下innodb_large_prefix究竟是什么？\n动态行格式DYNAMIC row format 支持最大的索引前缀(3072)。由变量innodb_large_prefix进行控制。\nBy default, the index key prefix length limit is 767 bytes. See Section 13.1.13, “CREATE INDEX Statement”. For example, you might hit this limit with a column prefix index of more than 255 characters on a TEXT or VARCHAR column, assuming a utf8mb3 character set and the maximum of 3 bytes for each character. When the innodb_large_prefix configuration option is enabled, the index key prefix length limit is raised to 3072 bytes for InnoDB tables that use the DYNAMIC or COMPRESSED row format.\n官方上说在 utf8mb3（most bytes n）如果设置为 varchar(255)时，索引前缀将大于767，可以扩展为3072，但是实际上 varchar的size可以为65535，这个就限制了整个alter table 的操作\n因为建表是时索引没设置大小，默认是超过255的，后面开启了前缀限制，大小会为3072，此时无法做表修改\nReference\nUtf8mb4 / ERROR 1709 (HY000): Index column size too large. The maximum column size is 767 bytes MySQL8 innodb large prefix\ninnodb_file_format settings backwards compatible\ninnodb limits\n","permalink":"https://www.161616.top/mysql5.6-innodb_large_prefix-abnormal/","summary":"phenomenon： Specified key was too long; max key length is 3072 bytes\n在修改一个数据库字段时，字段容量被限制为了表前缀的大小而不是本身的容量大小\n查了一下innodb_large_prefix究竟是什么？\n动态行格式DYNAMIC row format 支持最大的索引前缀(3072)。由变量innodb_large_prefix进行控制。\nBy default, the index key prefix length limit is 767 bytes. See Section 13.1.13, “CREATE INDEX Statement”. For example, you might hit this limit with a column prefix index of more than 255 characters on a TEXT or VARCHAR column, assuming a utf8mb3 character set and the maximum of 3 bytes for each character.","title":"mysql5.6 innodb_large_prefix引起的一个异常"},{"content":" sence：python中使用subprocess.Popen(cmd, stdout=sys.STDOUT, stderr=sys.STDERR, shell=True) ，stdout, stderr 为None.\n在错误中执行是无法捕获 stderr的内容，后面将上面的改为 subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True),发现是可以拿到 stderr, 但是会遇到大量任务hanging，造成线上事故。\n为此特意查询subprocess的一些参数的说明。\nstdin stdout stderr 如果这些参数为 PIPE, 此时会为一个文件句柄，而传入其他（例如 sys.stdout 、None 等）的则为None\n正如这里介绍的一样，subprocess 。\n而使用 PIPE，却导致程序 hanging。一般来说不推荐使用 stdout=PIPE stderr=PIPE，这样会导致一个死锁，子进程会将输入的内容输入到 pipe，直到操作系统从buffer中读取出输入的内容。\n查询手册可以看到确实是这个问题 Refernce\nWarning This will deadlock when using stdout=PIPE and/or stderr=PIPE and the child process generates enough output to a pipe such that it blocks waiting for the OS pipe buffer to accept more data. Use communicate() to avoid that.\n而在linux中 PIPE 的容量（capacity）是内核中具有固定大小的一块缓冲区，如果用来接收但不消费就会阻塞，所以当用来接收命令的输出基本上100% 阻塞所以会导致整个任务 hanging。（ -Linux2.6.11 ，pipe capacity 和system page size 一样（如， i386 为 4096 bytes ）。 since Linux 2.6.11+，pipe capacity 为 65536 bytes。）\n关于更多的信息可以参考：pipe\n所以如果既要拿到对应的输出进行格式化，又要防止程序hang，可以自己创建一个缓冲区，这样可以根据需求控制其容量，可以有效的避免hanging。列如：\npython 1 2 3 4 cmd = \u0026#34;this is complex command\u0026#34; outPipe = tempfile.SpooledTemporaryFile(bufsize=10*10000) fileno = outPipe.fileno() process = subprocess.Popen(cmd,stdout=fileno,stderr=fileno,shell=True) 另外，几个参数设置的不通的区别如下：\nstdout=None 为继承父进程的句柄，通俗来说为标准输出。\nstderr=STDOUT 重定向错误输出到标准输出\nstdout=PIPE 将标准输出到linux pipe\nReference subprocess\nsubprocess stderr/stdout field is None\nsubprocess-popen-hanging\npipe size\n","permalink":"https://www.161616.top/pipe-size-problem/","summary":"sence：python中使用subprocess.Popen(cmd, stdout=sys.STDOUT, stderr=sys.STDERR, shell=True) ，stdout, stderr 为None.\n在错误中执行是无法捕获 stderr的内容，后面将上面的改为 subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True),发现是可以拿到 stderr, 但是会遇到大量任务hanging，造成线上事故。\n为此特意查询subprocess的一些参数的说明。\nstdin stdout stderr 如果这些参数为 PIPE, 此时会为一个文件句柄，而传入其他（例如 sys.stdout 、None 等）的则为None\n正如这里介绍的一样，subprocess 。\n而使用 PIPE，却导致程序 hanging。一般来说不推荐使用 stdout=PIPE stderr=PIPE，这样会导致一个死锁，子进程会将输入的内容输入到 pipe，直到操作系统从buffer中读取出输入的内容。\n查询手册可以看到确实是这个问题 Refernce\nWarning This will deadlock when using stdout=PIPE and/or stderr=PIPE and the child process generates enough output to a pipe such that it blocks waiting for the OS pipe buffer to accept more data. Use communicate() to avoid that.","title":"由PIPE size 引起的线上故障"},{"content":"数据结构 数据类型总结 Go语言将数据类型分为四类：基础类型、复合类型、引用类型和接口类型。\n基础数据类型包括：\n基础类型： 布尔型、整型、浮点型、复数型、字符型、字符串型、错误类型。 复合数据类型包括： 指针、数组、切片、字典、通道、结构体、接口。 什么是反射 在计算机科学领域，反射是指一类应用，它们能够自描述和自控制。\n在go中，编译时不知道类型的情况下，可更新变量、运行时查看值、调用方法以及直接对他们的布局进行操作的机制，称为反射。\n场景：无法透视一个未知类型的时候，这时候就需要有反射来帮忙你处理，反射使用TypeOf和ValueOf函数从接口中获取目标对象的信息，轻松完成目的。\nrune与byte的区别 byte是uint8、rune为uint32，一个仅限于ascii码的值，一个支持更多的值。rune比byte能表达更多的数。 golang默认使用utf8编码，一个中文占用3字节，一个utf8数字占用1字节，utf8字母占用1字节 切片 切片的扩容：切片扩容，一般方式：上一次容量的2倍，超过1024字节，每次扩容上一次的1/4\n切片的截取：在截取时，capacity 不能超过原slice的 capacity\nnew() 与 make() 的区别 new(T) 和 make(T, args) 是Go语言内建函数，用来分配内存，但适用的类型不用。\nnew函数用于分配指定类型的零值对象，并返回指向其内存地址的指针。例如，new(int)将分配一个类型为int且值为0的对象，并返回一个指向该地址的指针。可以使用*运算符访问指针指向的值。 make函数用于创建和初始化内置类型（如map、slice、channel）的数据结构，并返回其指针。它比new函数更加复杂很多，因为它需要知道类型的大小和结构，以便为其分配内存并初始化其字段或元素。例如，make(map[string]int)将创建一个空的map。它有一个string类型的键和一个int类型的值。 nil切片和空切片指向的地址一样吗？ nil切片和空切片指向的地址==不一样==。nil空切片引用数组指针地址为0（无指向任何实际地址） 空切片的引用数组指针地址是有的，且固定为一个值 什么是Receiver Golang的Receiver是绑定function到特定type成为其method的一个参数，即一个function加了receiver就成为一个type的method。\n构体方法跟结构体指针方法的区别（Receiver和指针Receiver的区别） T 的方法集仅拥有 T Receiver。 *T 方法集则包含全部方法 (Receiver + *Receiver)。 sync.once 是 Golang package 中使方法只执行一次的对象实现，作用与 init 函数类似。但也有所不同\ninit 函数是在文件包首次被加载的时候执行，且只执行一次\nsync.Onc 是在代码运行中需要的时候执行，且只执行一次\n当一个函数不希望程序在一开始的时候就被执行的时候，我们可以使用 sync.Once\n实现：sync.Once 的源码实现非常简单，采用的是双重检测锁机制 (Double-checked Locking)，是并发场景下懒汉式单例模式的一种实现方式\n首先判断 done 是否等于 0，等于 0 则表示回调函数还未被执行 加锁，确保并发安全 在执行函数前，二次确认 done 是否等于 0，等于 0 则执行 将 done 置 1，同时释放锁 疑问一: 为什么不使用乐观锁 CAS 简单的来说就是 f() 的执行结果最终可能是不成功的，所以你会看到现在采用的是双重检测锁机制来实现，同时需要等 f() 执行完成才修改 done 值 疑问二: 为什么读取 done 值的方式没有统一 比较 done 是否等于 0，为什么有的地方用的是 atomic.LoadUint32，有的地方用的却是 o.done。主要原因是 atomic.LoadUint32 可以保证原子读取到 done 值，是并发安全的，而在 doSlow 中，已经加锁了，那么临界区就是并发安全的，使用 o.done 就可以来读取值就可以了 原子操作和互斥锁的区别 文档：https://zhuanlan.zhihu.com/p/147618421\nGMP模型 文档：https://zhuanlan.zhihu.com/p/261590663 文档：https://juejin.cn/post/6844904104343388168\nG：goroutine\nM：Machine，内核线程\nP：Logical Processor，处理器；代表了M所需要的上下文环境\nruntime.GOMAXPROCS (numLogicalProcessors)可以设置多少个处理器，go 1.5开始，默认是CPU核数； 实际运行时P和CPU核心数并无任何关联，P最大不超过256；P可以理解为并行度的多少，也就是说当前最多只能有P个线程在运行；(是不是很像线程池) P一旦初始化了，就不能修改了 三者关系 M的数量和P不一定匹配，可以设置很多M，M和P绑定后才可运行，多余的M处于休眠状态。\nP包含一个LRQ（Local Run Queue）本地运行队列，这里面保存着P需要执行的协程G的队列。\n除了每个P自身保存的G的队列外，调度器还拥有一个全局的G队列GRQ（Global Run Queue），这个队列存储的是所有未分配的协程G。\ngo func()执行流程 创建一个G对象，加入到本地队列或全局队列； 如果还有空闲的P，则创建一个M； M会启动一个底层线程，并结合P，循环执行G； P执行G的顺序是，先从本地队列找，没有则到全局队列找（一次性转移[全局G个数/P个数]），再到其他P中找（一次性转移一半）； G是执行顺序是按照队列顺序的； P管理着G队列，但是G要运行，还需要M的绑定； runtime.GOMAXPROCS只会影响P的数量，不会影响M的数量； P和M的关系，就好比用户线程和内核线程的N：M模型； 没有足够的M关联P时，会创建M；在runtime执行系统监控或垃圾回收等任务的时候也会导致新的M的创建。 所以，runtime.GOMAXPROCS只是类似线程池的大小设置而已； 当然，go也可以通过runtime/debug.SeMaxThreads限制操作系统线程数；SetMaxThreads主要用于限制程序无限制的创造线程导致的灾难。目的是让程序在干掉操作系统之前，先干掉它自己。 goroutine是按照抢占式调度的，一个goroutine最多执行10ms就会换作下一个； 死锁 死锁是：多个进（线）程是相互竞争的关系，并且互持资源，相互等待，这样产生的永久阻塞的现象称为死锁。\n死锁产生的原因：\n互斥 占有且等待 不可抢占 循环等待 死锁如何解决：死锁的发生很难通过人为干预来解决，只能避免（打破死锁产生的条件）\n互斥：线程安全是通过互斥来实现的（无法干预） 占有且等待：申请资源时获取所有所需资源 不可抢占：占用资源的进程在进一步申请其他资源时，如申请不到主动释放已占有的资源 循环等待：按需预防，对所需资源进行排序，按照大小依次申请 Refer deadlock\nGo中产生死锁的原因：\n无缓冲；解决：缓冲或先读后写 缓冲已满（只写不读）；解决，需要有消费端 读写互斥；（读写加锁导致一段阻塞变成死锁） 未初始化的channel（读，写，关闭） 多线程只要保证个线程的执行，可以允许死锁 如主进程只读不写造成阻塞，这种情况在没有子线程情况下是死锁 slice和map区别 slice是有序的，map是无序的，在每次迭代时，无法确定其顺序 slice有容量，map没有容量，map是由go内部控制的数据结构 slice可以使用appen()，map不可以 slice和map都是引用类型，当作为参数传递时共享相同地址 如何复制slice、map和interface？ 这些类型的变量是内存引用类型，可以使用内置函数 copy() 来完成复制 Refer to\ngo 1 2 3 4 5 6 a := []int{1, 2} b := []int{3, 4} check := a copy(a, b) fmt.Println(a, b, check) // Output: [3 4] [3 4] [3 4] 什么是goroutine go的多线程是包含在运行时内的一种机制，用的模型是两级，即runtime帮助申请和释放线程，而这个gorutine可以为一对一，也可以为一对多，即操作系统中的 “两级模型”（Two-Level）是严格意义上的多对多模型，可以为单个用户线程专门一对一绑定内核线程的能力的模型\n用户线程的缺点：\n用户级线程与操作系统的集成度不高；如用空闲线程调度进程，阻塞其线程发起 I/O 的进程，即使该进程有其他线程可以运行，以及有锁的线程取消调度进程。 用户级线程需要非阻塞系统调用，否则，当一个线程阻塞，即使进程中还有可运行的线程，整个进程也会在内核中阻塞。例如，如果一个线程导致页面错误，则进程阻塞。 用户线程和操作系统内核之间缺乏协调性；无论进程有 1 个线程还是 1000 个线程，都仅能获得一个CPU时间片。由每个线程主动将控制权交给其他线程。 由于进程时资源分配的最小单位，多线程情况下，每个线程得到的时间片较少，执行会较慢。 C语言的线程和Goroutine的区别主要表现在以下几个方面 实现方式不同：C语言的线程是由操作系统内核来实现的，而Goroutine则是通过Go语言的runtime来实现的。 内存分配方式不同：C语言的线程需要在内存中分配一定的栈空间，而Goroutine则通过自动扩展栈来实现。 调度方式不同：C语言的线程是由操作系统内核来调度的，而Goroutine是通过Go语言的runtime自己实现的调度器来调度的。 轻量级：Goroutine是轻量级的线程，一个Goroutine只需要2KB的栈空间，而C语言的线程需要占用更多的内存空间。 效率高：由于Goroutine是由Go语言的runtime来实现的，因此它具有非常高的执行效率和并发性能，比C语言的线程更加高效。 总的来说，Goroutine是Go语言的并发特性中非常重要的一部分，通过Goroutine可以非常方便地实现高效的并发程序。而C语言的线程则更多地是由操作系统来实现，对于一些需要最大化利用机器性能的场景会更为适合。\n应用 Go语言创建TCP连接 conn.dial conn.write/read ","permalink":"https://www.161616.top/interview-go/","summary":"数据结构 数据类型总结 Go语言将数据类型分为四类：基础类型、复合类型、引用类型和接口类型。\n基础数据类型包括：\n基础类型： 布尔型、整型、浮点型、复数型、字符型、字符串型、错误类型。 复合数据类型包括： 指针、数组、切片、字典、通道、结构体、接口。 什么是反射 在计算机科学领域，反射是指一类应用，它们能够自描述和自控制。\n在go中，编译时不知道类型的情况下，可更新变量、运行时查看值、调用方法以及直接对他们的布局进行操作的机制，称为反射。\n场景：无法透视一个未知类型的时候，这时候就需要有反射来帮忙你处理，反射使用TypeOf和ValueOf函数从接口中获取目标对象的信息，轻松完成目的。\nrune与byte的区别 byte是uint8、rune为uint32，一个仅限于ascii码的值，一个支持更多的值。rune比byte能表达更多的数。 golang默认使用utf8编码，一个中文占用3字节，一个utf8数字占用1字节，utf8字母占用1字节 切片 切片的扩容：切片扩容，一般方式：上一次容量的2倍，超过1024字节，每次扩容上一次的1/4\n切片的截取：在截取时，capacity 不能超过原slice的 capacity\nnew() 与 make() 的区别 new(T) 和 make(T, args) 是Go语言内建函数，用来分配内存，但适用的类型不用。\nnew函数用于分配指定类型的零值对象，并返回指向其内存地址的指针。例如，new(int)将分配一个类型为int且值为0的对象，并返回一个指向该地址的指针。可以使用*运算符访问指针指向的值。 make函数用于创建和初始化内置类型（如map、slice、channel）的数据结构，并返回其指针。它比new函数更加复杂很多，因为它需要知道类型的大小和结构，以便为其分配内存并初始化其字段或元素。例如，make(map[string]int)将创建一个空的map。它有一个string类型的键和一个int类型的值。 nil切片和空切片指向的地址一样吗？ nil切片和空切片指向的地址==不一样==。nil空切片引用数组指针地址为0（无指向任何实际地址） 空切片的引用数组指针地址是有的，且固定为一个值 什么是Receiver Golang的Receiver是绑定function到特定type成为其method的一个参数，即一个function加了receiver就成为一个type的method。\n构体方法跟结构体指针方法的区别（Receiver和指针Receiver的区别） T 的方法集仅拥有 T Receiver。 *T 方法集则包含全部方法 (Receiver + *Receiver)。 sync.once 是 Golang package 中使方法只执行一次的对象实现，作用与 init 函数类似。但也有所不同\ninit 函数是在文件包首次被加载的时候执行，且只执行一次\nsync.Onc 是在代码运行中需要的时候执行，且只执行一次\n当一个函数不希望程序在一开始的时候就被执行的时候，我们可以使用 sync.Once\n实现：sync.Once 的源码实现非常简单，采用的是双重检测锁机制 (Double-checked Locking)，是并发场景下懒汉式单例模式的一种实现方式\n首先判断 done 是否等于 0，等于 0 则表示回调函数还未被执行 加锁，确保并发安全 在执行函数前，二次确认 done 是否等于 0，等于 0 则执行 将 done 置 1，同时释放锁 疑问一: 为什么不使用乐观锁 CAS 简单的来说就是 f() 的执行结果最终可能是不成功的，所以你会看到现在采用的是双重检测锁机制来实现，同时需要等 f() 执行完成才修改 done 值 疑问二: 为什么读取 done 值的方式没有统一 比较 done 是否等于 0，为什么有的地方用的是 atomic.","title":"go面试题收集"},{"content":"Kubernetes概念 Ingress和LoadBalancer的区别 Ingress通常用于将HTTP(S)流量路由到Kubernetes群集内部的服务，支持复杂路径路由和负载均衡算法 LB则是通过提供商提供一种外部流量引入到集群内的组件，通常为2 3层 Ingress本身是基于service的，引入流量时依赖 kube-proxy LB则是独立的组件，最小接入单元也是service，而通过2 3层的广播等功能可以提供多节点的引入 功能：Ingress是一个规范，LB则是一种实现 实现方式：ingress通过扩展Kubernetes API+controller, 而LB除此外还需要外部设备提供（软硬件，云组件） kubernetes之最小单元 Pod最小可调度单元，最小部署单元 容器：容器是最小的执行单元 Namespace：最小隔离单元 Service：最小接入单元 etcd用的什么算法，简单解释一下 raft算法 强一致性 同一时间只能有一个leader,所有的操作都在leader上。\nPod 的生命周期 Pod 状态始终处于一下几个状态之一:\nPending: 部署 Pod 事务已被集群受理，但当前容器镜像还未下载完或现有资源无法满足 Pod 的资源需求 Running: 所有容器已被创建，并被部署到节点上 Successed: Pod 成功退出，并不会被重启 Failed: Pod 中有容器被终止 Unknown: 未知原因，如 kube-apiserver 无法与 Pod 进行通讯 Kubernetes有哪些不同类型的服务？ cluster ip Node Port Load Balancer Extrenal Name 什么是ETCD？ Etcd是用Go编程语言编写的，是一个分布式键值存储，用于协调分布式工作。因此，Etcd存储Kubernetes集群的配置数据，表示在任何给定时间点的集群状态。\n什么是Ingress网络，它是如何工作的？ Ingress网络是一组规则，充当Kubernetes集群的入口点。这允许入站连接，可以将其配置为通过可访问的URL，负载平衡流量或通过提供基于名称的虚拟主机从外部提供服务。因此，Ingress是一个API对象，通常通过HTTP管理集群中服务的外部访问，是暴露服务的最有效方式。\n什么是Headless Service？ Headless Service类似于“普通”服务，但没有群集IP。此服务使您可以直接访问pod，而无需通过代理访问它。\n什么是集群联邦？ 在联邦集群的帮助下，可以将多个Kubernetes集群作为单个集群进行管理。因此，您可以在数据中心/云中创建多个Kubernetes集群，并使用联邦来在一个位置控制/管理它们。\n联合集群可以通过执行以下两项操作来实现此目的。请参考下图。\nkube-proxy的作用 kube-proxy运行在所有节点上，它监听apiserver中service和endpoint的变化情况，创建路由规则以提供服务IP和负载均衡功能。简单理解此进程是Service的透明代理兼负载均衡器，其核心功能是将到某个Service的访问请求转发到后端的多个Pod实例上。\nkube-proxy iptables的原理 Kubernetes从1.2版本开始，将iptables作为kube-proxy的默认模式。iptables模式下的kube-proxy不再起到Proxy的作用，其核心功能：通过API Server的Watch接口实时跟踪Service与Endpoint的变更信息，并更新对应的iptables规则，Client的请求流量则通过iptables的NAT机制“直接路由”到目标Pod。\nkube-proxy ipvs的原理 IPVS在Kubernetes1.11中升级为GA稳定版。IPVS则专门用于高性能负载均衡，并使用更高效的数据结构（Hash表），允许几乎无限的规模扩张，因此被kube-proxy采纳为最新模式。\n在IPVS模式下，使用iptables的扩展ipset，而不是直接调用iptables来生成规则链。iptables规则链是一个线性的数据结构，ipset则引入了带索引的数据结构，因此当规则很多时，也可以很高效地查找和匹配。\n可以将ipset简单理解为一个IP（段）的集合，这个集合的内容可以是IP地址、IP网段、端口等，iptables可以直接添加规则对这个“可变的集合”进行操作，这样做的好处在于可以大大减少iptables规则的数量，从而减少性能损耗。\nkube-proxy ipvs和iptables的异同 iptables与IPVS都是基于Netfilter实现的，但因为定位不同，二者有着本质的差别：iptables是为防火墙而设计的；IPVS则专门用于高性能负载均衡，并使用更高效的数据结构（Hash表），允许几乎无限的规模扩张。\n与iptables相比，IPVS拥有以下明显优势：\n为大型集群提供了更好的可扩展性和性能； 支持比iptables更复杂的复制均衡算法（最小负载、最少连接、加权等）； 支持服务器健康检查和连接重试等功能； 可以动态修改ipset的集合，即使iptables的规则正在使用这个集合。 Kubernetes镜像的下载策略 Kubernetes的镜像下载策略有三种：Always、Never、IFNotPresent。\nAlways：镜像标签为latest时，总是从指定的仓库中获取镜像。 Never：禁止从仓库中下载镜像，也就是说只能使用本地镜像。 IfNotPresent：仅当本地没有对应镜像时，才从目标仓库中下载。默认的镜像下载策略是：当镜像标签是latest时，默认策略是Always；当镜像标签是自定义时（也就是标签不是latest），那么默认策略是IfNotPresent。 简述Kubernetes Scheduler使用哪两种算法将Pod绑定到worker节点 Kubernetes Scheduler根据如下两种调度算法将 Pod 绑定到最合适的工作节点：\n预选（Predicates）：输入是所有节点，输出是满足预选条件的节点。kube-scheduler根据预选策略过滤掉不满足策略的Nodes。如果某节点的资源不足或者不满足预选策略的条件则无法通过预选。如“Node的label必须与Pod的Selector一致”。 优选（Priorities）：输入是预选阶段筛选出的节点，优选会根据优先策略为通过预选的Nodes进行打分排名，选择得分最高的Node。例如，资源越富裕、负载越小的Node可能具有越高的排名。 有了解过qos吗？ 怎么实现的？ 服务质量 Quality of Service有三种 Guaranteed, Burstable, and Best-Effort，它们的QoS级别依次递减。\nGuaranteed：确保的，只设置 limits 或者 requests 与 limits 为相同时则为该等级 Burstable：可突发的，只设置 requests 或 requests 低于 limits 的场景 Best-effort： 默认值，如果不设置则为这个等级 node资源不足时会按qos级别驱逐pod。 最先驱逐的是Best-Effort ,重要组件一定要设置limit和request.\n驱逐顺序根据 BestEffort ==》Burstable ==》Guaranteed 进行驱逐\nKubernetes 开发 资源和类型 Kind：实体的类型\nresources：resources是，restful中的资源，标识一组HTTP端点（paths），可以理解为kind的实例化。\n例如：Pod是etcd中的数据，而resources对应的 path上的resources\nResources和kinds区别 Resources与HTTP paths关联， Resources始终是API Group和Version的一部分。 kind是这些endpoint返回并接收的objects的类型，并持久存在于etcd中。 Kubernetes OOP Kind Class Resource Object Kind 其实就是一个类，用于描述对象的；而 Resource 就是具体的 Kind，可以理解成类已经实例化成对象了。\nGVR与GVK有什么区别？ GVR = Group Version Resources GVK = Group Version Kind 每个kind都属于一个Group和Version中，通过GVK标识，GVR是GVK对外提供服务的入口，GVK与GVR之间的映射过程交 REST mapping\nclient-go 客户端类型有哪些？ RestClient：是最基础的客户端，其作用是将http client进行封装成rest api格式。位于rest目录 ClientSet：基于RestClient进行封装对 Resource 与 version 管理集合， DiscoverySet：RestClient进行封装，可动态发现kube-apiserver所支持的GVR（Group Version Resource）。 ：基于RestClient，包含动态的客户端，可以对Kubernetes所支持的 API对象进行操作，包括CRD。 kubernetes 调度过程 kubernetes调度过程就是调度上下文，而调度上下文就是执行 scheduleOne 这个函数中线性执行的。\n调度上下文的过程分为两阶段，调度和绑定\n调度：指的是SchedulePod -\u0026gt; findNodesThatFitPod 这是 prefilter 阶段，如果通过 prefilter\nprefilter 做预检查动作，如获取node列表，检查提名node是否满足，满足则评估，不满足则从PreFilterPlugins获取node list，满足所有条件执行 filter plugin；PreFilterPlugins返回的是一组Node name\nfilter 做过滤操作，满足的条件是至少配置了一个filter plugin，filter是线性的，如一个扩展不满足则标记为不可用\npostFilter 是抢占的触发条件，即filter阶段没有FN时被触发，满足条件是需要配置至少一个该类型plugin\n这里存在 Unschedulable 不可调用则执行postfilter，否则都不可调用 preScore, score 会在 prioritizeNodes 中执行对应的 插件\n接下来是 Reserve, 为了避免Pod在绑定到节点前时，调度一个新的Pod，使节点使用资源超过可用资源情况。\nPremit ，阻止或延迟 Pod 的绑定\n绑定：绑定操作时异步进行的，即通过了打分阶段，基本上等于调度成功\n这个异步线程会从延迟队列中拿到调度的pod，即Premit 的延迟 这里关联的上面的reserve 与 premit，如果不可调用则调用unreserved回滚，否则会调用 bind prebind 与 bind 的失败都会放入到回滚队列中 bind 当 执行了unreserve 则忘记这个pod 否则成功的绑定了node client-go的架构 Reflector deltafifo的生产者 就是将 （监控）Etcd 里面的数据反射到本地存储（DeltaFIFO）中\ndeltaFIFO， Delta 表示的是变化的资源对象存储 先进先出的队列\nworkqueue kubernetes中使用的队列，即每次触发的事件都被塞入到queue中进行处理\n去重 delay：如 cronjob 依赖延迟队列实现定时功能 限速： Indexer deltaFIFO消费者，是Informer的本地存储。\nworkqueue算法实现 已知workqueue主要作用是为了去重，延迟，限速，那么他是通过什么算法实现的呢？\n延迟主要使用了 Binary Heap 数据类型实现的延迟，而这种queue称为优先级队列 Heap是一个二叉树数据结构，即每个节点包含的元素大于或等于该节点子节点的元素，如果新元素的值大于父元素，将新元素与父元素交换，直到达到新元素到根，这个过程叫向上调整 元素被放置在结构中时，按照优先级排列 优先级最高的元素最先离开 限速队列时在延迟队列的基础上扩展的，使用的令牌桶和漏桶算法实现的 kube-proxy作用 kube-proxy作用是位于工作节点上，通过ipvs提供service功能，本质上来说是一个controller，通过监听 node, endpoints， service等资源的变动从而生成proixer的规则\n什么是endpointslice Endpoints 与 EndpointSlices 均是为service提供端点的 Service规模越大，那么Endpoints中的 Pod 数量越大，传输的 EndPoints 对象就越大。集群中 Pod 更改的频率越高，也意味着传输在网络中发生的频率就越高 Endpoints 对象在大规模集群场景下存在下列问题： 增加网络流量（etcd最大请求大小为 1.5 MiB），隐性增加对控制平面的影响，service的可扩展性将降低 超大规模的 service 理论上会无法存储 该 Endpoints 处理Endpoints资源的 worker 会消耗更多的计算资源 Endpointslices 解决了： 部分更新，更少的网络流量 Worker 处理 Endpoints 更新所需的资源更少 减少对控制平面的影响，提升的性能和 service 规模 Kubernetes controller 原理 如下图所示：\nReflector \u0026mdash; 连接 Kubernetes API Server（集群大脑）与本地世界的桥梁。通过 List + Watch 持续感知变化。把“远端状态”同步到本地 DeltaFIFO \u0026mdash; “变化的缓冲河流”；(Delta 通常被称为增量、差异或变化量），会合并、压缩变化，里面的内容是 event。总结：DeltaFIFO 不关心“世界是什么样”，只关心“世界发生了什么变化”。 **Informer ** \u0026mdash; 调度中枢，整个系统的分发中心。他负责更新缓存 (Indexer) 和分发事件 (Handler)。总结，informer 既是数据的“落地者”，也是事件的“广播者”。 Indexer \u0026mdash; 本地的最终状态存储 **Workqueue ** \u0026mdash; 任务缓冲队列，Workqueue 是 controller 的待办任务列表。总结，workqueue 是把事件转化为“可控的任务节奏”。 operator 并发控制 在開發 Operator（基於 Controller-runtime）時，如何正確處理 CR（Custom Resource）的狀態（Status）更新？為什麼強烈建議使用 UpdateStatus 而不是直接調用 Update？如果更新時遇到 Conflict (HTTP 409) 錯誤，底層原理是什麼，該如何優雅處理？\n答案解析：\n答案解析：\nSubresource 隔離： 必須在 CRD 定義中開啟 /status subresource。調用 UpdateStatus 時，APIServer 只會校驗和更新對象的 Status 欄位，完全忽略對 Spec 的修改。這實現了權責分離（用戶控制 Spec，Controller 控制 Status），防止 Controller 的狀態更新意外覆蓋了用戶剛剛提交的 Spec 變更。 Conflict 原理： K8s 採用基於 resourceVersion 的樂觀併發控制（Optimistic Concurrency Control）。當 Controller 嘗試更新一個對象時，如果提交的 RV 與 etcd 中最新的 RV 不一致（說明在讀取和寫入期間，對象被其他組件修改過），APIServer 就會拒絕寫入並返回 409 Conflict。 優雅處理： 絕對不能強行覆蓋。標準做法是利用 client-go/util/retry 包中的 RetryOnConflict 函數。在發生衝突時，強制從 APIServer（或本地緩存）重新 Get 最新版本的對象，基於最新狀態重新計算需要更新的數據，然後重試提交。 Status 的定位\nSpec 是使用者告訴系統「我想要什麼」；Status 是 Operator 告訴使用者「現在變成什麼樣了」。\ntext 1 2 spec: # 使用者期望狀態 status: # controller 觀察到的實際狀態 所以在 Operator 裡，Controller 應該：然後只更新 status 子資源。\n為什麼要用 Status().Update，而不是 Update？\n在 controller-runtime 裡，推薦這樣：\ntext 1 err := r.Status().Update(ctx, cr) Update() 是更新整個物件，包括：\ntext 1 2 3 metadata spec status Conflict / HTTP 409 是什麼原理？\nKubernetes 每個物件都有一個：metadata.resourceVersion，当在读取 CR 時，這時其他人先更新了它,你再拿舊物件去更新，API Server 發現版本過期，就會返回：409\ntext 1 2 3 4 5 Get CR，resourceVersion = 100 CR 變成 resourceVersion = 101 Update resourceVersion = 100 如何優雅處理 Conflict？\n重新 Get 最新物件，重新計算 Status，再重試更新。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import ( apierrors \u0026#34;k8s.io/apimachinery/pkg/api/errors\u0026#34; \u0026#34;k8s.io/client-go/util/retry\u0026#34; ) err := retry.RetryOnConflict(retry.DefaultRetry, func() error { latest := \u0026amp;appv1.MyApp{} if err := r.Get(ctx, req.NamespacedName, latest); err != nil { return err } latest.Status.Phase = \u0026#34;Ready\u0026#34; latest.Status.ObservedGeneration = latest.Generation return r.Status().Update(ctx, latest) }) if err != nil { return ctrl.Result{}, err } Admission Webhook 是什麼 Admission Webhook 是 Kubernetes API Server 在資源寫入 etcd 之前，調用外部服務做校驗或修改的擴展點。\ntext 1 2 MutatingAdmissionWebhook 修改請求物件 ValidatingAdmissionWebhook 校驗請求是否合法 面试回答：Admission Webhook 是 API Server 寫入資源前的一道「可編程關卡」，Mutating 負責塑形，Validating 負責守門。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 kubectl / controller ↓ Kube API Server ↓ Authentication ↓ Authorization ↓ Admission Chain ↓ MutatingAdmissionWebhook ↓ ValidatingAdmissionWebhook ↓ etcd Admission Webhook 开发执行顺序 開發 Admission Webhook 時，Mutating Webhook 和 Validating Webhook 的執行順序是什麼？如果一個 Mutating Webhook 修改了對象，會不會導致其他已經執行過的 Webhook 驗證失效？在生產環境中，如何避免 Webhook 成為卡死整個 K8s 集群的單點故障？\n答案解析：\n執行順序與重入機制（Re-invocation）： APIServer 會先並行執行所有匹配的 Mutating Webhooks，然後再執行 Validating Webhooks。如果某個 Mutating Webhook 修改了對象，為了保證一致性，APIServer 會重新觸發（Re-invoke） 那些已經執行過的 Mutating Webhooks，確保所有的修改最終收斂，然後才進入 Validating 階段。 防範單點故障： FailurePolicy： 非關鍵鏈路的 Webhook 應將 failurePolicy 設置為 Ignore（默認為 Fail），確保 Webhook 服務宕機時不會阻塞常規的 Pod 創建。 超時控制： 合理設置 timeoutSeconds（通常 1-3 秒），避免 Webhook 處理過慢耗盡 APIServer 的線程。 命名空間豁免： 必須在 MutatingWebhookConfiguration 中配置 namespaceSelector，嚴格避開 kube-system 命名空間。否則，一旦 Webhook 宕機，可能導致 CoreDNS 或 CNI 插件無法重啟，引發全局災難。 标准回答：\nWebhook Server 多副本\ntext 1 replicas: 2 设置合理的 timeoutSeconds\ntext 1 timeoutSeconds: 2 配置合理的 failurePolicy\ntext 1 2 3 failurePolicy: Fail // Webhook 失敗則拒絕請求; 安全、合規、強校驗 // failurePolicy: Ignore // Webhook 失敗則放行; 非關鍵注入、可降級邏輯 避免攔截自己\n按照 ns 隔离 webhook\ntext 1 2 3 4 5 6 namespaceSelector: matchExpressions: - key: admission-webhook operator: NotIn values: - disabled 請簡述 Scheduler Framework 的架構 目前 K8s 官方推薦使用 Scheduler Framework 而不是從頭實現一個自定義調度器（Custom Scheduler）。請簡述 Scheduler Framework 的架構，並說明如果你要實現一個「優先將 Pod 調度到 GPU 溫度較低的節點，且拒絕調度到溫度超過 85°C 的節點」的插件，需要實現在哪些擴展點（Extension Points）？\n答案解析：\n架構特點： Scheduler Framework 將調度流程拆分為 Scheduling Cycle（同步執行，選擇節點）和 Binding Cycle（異步執行，綁定節點）。開發者只需要實現特定的接口並編譯進默認調度器中，無需維護整個調度邏輯。 擴展點實作： 拒絕高溫節點（硬性過濾）： 實現 Filter 擴展點。當檢查到節點 GPU 溫度 \u0026gt; 85°C 時，返回 Unschedulable，該節點將被直接剔除出候選列表。 優先低溫節點（軟性打分）： 實現 Score 擴展點。對通過 Filter 的所有節點進行打分，溫度越低分數越高（例如 0-100 分）；同時可能需要實現 NormalizeScore 擴展點，將分數歸一化，確保與其他打分插件（如資源利用率）的權重兼容。 请从代码角度描述 Pod的调度上下文 Kubernetes Scheduler 本质上是一个不断从调度队列中取出 Pending Pod，然后通过一系列调度插件为它选择最合适 Node，并最终 Bind 的控制循环。\n核心代码调度\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 Run() ↓ SchedulingQueue.NextPod() ↓ scheduleOne() ↓ SchedulePod() ↓ Framework 插件链 ↓ AssumePod() ↓ Bind() 调度流程大致是：\ntext 1 2 3 4 5 6 7 8 9 10 11 QueueSort PreFilter Filter PostFilter PreScore Score Reserve Permit PreBind Bind PostBind 调度流程大致是：\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 取 Pod ↓ PreFilter：预处理 ↓ Filter：过滤不可用节点 ↓ PreScore：打分前准备 ↓ Score：节点打分 ↓ Reserve：资源预留 ↓ Permit：准入等待 ↓ PreBind：绑定前处理 ↓ Bind：绑定到 Node ↓ PostBind：绑定后处理 调度步骤所负责的内容\nQueueSort：哪个 Pod 先被调度 PreFilter： 预计算阶段，“跟 Node 无关”的准备工作，结果写入CycleState，链后面复用这个结果 Filter：节点过滤， Node 能不能跑这个 Pod？ 资源够不够（CPU/Memory） nodeSelector / nodeAffinity taint / toleration volume 是否可挂载 端口冲突 PostFilter：调度失败后的补救，例如抢占（Preemption）, 调度的兜底机制（failback） PreScore: 打分前准备 计算 topology 信息 统计 Node 分布 Score：节点打分，每个插件打一个分（0~100）： Reserve： 资源预留，乐观锁 / 本地资源占用 Permit：调度“闸门” PreBind：绑定前处理 绑定 PVC（VolumeBinding） 写 annotation side effect 操作 Bind： 真正绑定 Pod，调用 API PostBind：绑定成功后的收尾 记录事件（Event） metrics 日志 Kubernetes Scheduler 通过 Scheduling Framework 将调度过程拆分为多个阶段：QueueSort 决定调度顺序，PreFilter 做预计算，Filter 过滤可行节点，Score 对节点打分并选出最优节点。当调度失败时 PostFilter 可以触发抢占。调度成功后通过 Reserve 做本地资源预留，Permit 控制调度节奏，PreBind 做绑定前准备，Bind 完成 Pod 到 Node 的绑定，最后 PostBind 做收尾工作。这种分阶段设计使调度器具备高度的可扩展性和可定制能力。\nCNI CNI 原理剖析 CNI 的本質不是一個守護進程（Daemon），而是一套二進制可執行文件和標準化的 JSON 規範。它的核心職責只有兩個：將容器加入網絡（分配 IP、配置路由），以及將容器從網絡中移除\n調用鏈路 (Kubelet -\u0026gt; CRI -\u0026gt; CNI)\n當 Pod 被調度到節點，Kubelet 通過 gRPC 調用 CRI（如 containerd）。 containerd 先調用底層的 runc 創建一個 Network Namespace（網絡命名空間）。 containerd 讀取機器的 /etc/cni/net.d/ 目錄下的配置，拉起指定的 CNI 插件二進制文件（如 calico、flannel 或 cilium）。 CNI 的核心執行流 (二進制調用) RI 啟動 CNI 插件時，是通過環境變量和標準輸入 (stdin) 傳遞參數的，執行 ADD 或 DEL 操作。\n階段一：IPAM (IP Address Management) 主插件首先調用 IPAM 插件（如 host-local 或 calico-ipam）。 IPAM 負責從節點的 PodCIDR 網段中分配一個可用的 IP，並將結果返回給主插件。 階段二：網絡構建 (以傳統 Veth Pair 為例) 主插件在宿主機和容器的 Netns 之間創建一對 veth pair 虛擬網卡。 將網卡的一端移入容器 Netns，重命名為 eth0，配置剛分配的 IP，並設置默認路由。 將另一端接入宿主機的網橋（如 cni0）或進行 BGP 路由發佈。 Aliyun terway 和 flannel 区别 Flannel\n基于 VXLAN / Overlay\nPod IP 是虚拟的\n跨节点通信需要封包 + 解包（隧道）\nTerway\n基于 ENI（弹性网卡） Pod 直接使用 VPC IP 没有 overlay Pod IP = VPC真实IP 无隧道 → 高性能、低延迟 可以直接被 SLB / RDS / ECS 访问 支持安全组绑定到 Pod aws 的 VPC CNI 本质：AWS CNI 本质是“把 Pod 当 EC2 的附属 IP”\nVPC CNI（ENI 模式）= Pod 用真实 VPC IP 的区别 每个 Node（EC2）挂多个 ENI 每个 ENI 分配多个 secondary IP Pod 直接绑定这些 IP Pod IP = VPC IP 可以直接访问 AWS 服务（RDS / ELB） 图：VPC CNI示意\rSource：https://medium.com/k8s-ebpf-tips/mastering-aws-vpc-cni-benefits-best-practices-troubleshooting-for-kubernetes-networking-56afddfa3b0f\n","permalink":"https://www.161616.top/interview-kubernetes/","summary":"Kubernetes概念 Ingress和LoadBalancer的区别 Ingress通常用于将HTTP(S)流量路由到Kubernetes群集内部的服务，支持复杂路径路由和负载均衡算法 LB则是通过提供商提供一种外部流量引入到集群内的组件，通常为2 3层 Ingress本身是基于service的，引入流量时依赖 kube-proxy LB则是独立的组件，最小接入单元也是service，而通过2 3层的广播等功能可以提供多节点的引入 功能：Ingress是一个规范，LB则是一种实现 实现方式：ingress通过扩展Kubernetes API+controller, 而LB除此外还需要外部设备提供（软硬件，云组件） kubernetes之最小单元 Pod最小可调度单元，最小部署单元 容器：容器是最小的执行单元 Namespace：最小隔离单元 Service：最小接入单元 etcd用的什么算法，简单解释一下 raft算法 强一致性 同一时间只能有一个leader,所有的操作都在leader上。\nPod 的生命周期 Pod 状态始终处于一下几个状态之一:\nPending: 部署 Pod 事务已被集群受理，但当前容器镜像还未下载完或现有资源无法满足 Pod 的资源需求 Running: 所有容器已被创建，并被部署到节点上 Successed: Pod 成功退出，并不会被重启 Failed: Pod 中有容器被终止 Unknown: 未知原因，如 kube-apiserver 无法与 Pod 进行通讯 Kubernetes有哪些不同类型的服务？ cluster ip Node Port Load Balancer Extrenal Name 什么是ETCD？ Etcd是用Go编程语言编写的，是一个分布式键值存储，用于协调分布式工作。因此，Etcd存储Kubernetes集群的配置数据，表示在任何给定时间点的集群状态。\n什么是Ingress网络，它是如何工作的？ Ingress网络是一组规则，充当Kubernetes集群的入口点。这允许入站连接，可以将其配置为通过可访问的URL，负载平衡流量或通过提供基于名称的虚拟主机从外部提供服务。因此，Ingress是一个API对象，通常通过HTTP管理集群中服务的外部访问，是暴露服务的最有效方式。\n什么是Headless Service？ Headless Service类似于“普通”服务，但没有群集IP。此服务使您可以直接访问pod，而无需通过代理访问它。\n什么是集群联邦？ 在联邦集群的帮助下，可以将多个Kubernetes集群作为单个集群进行管理。因此，您可以在数据中心/云中创建多个Kubernetes集群，并使用联邦来在一个位置控制/管理它们。\n联合集群可以通过执行以下两项操作来实现此目的。请参考下图。\nkube-proxy的作用 kube-proxy运行在所有节点上，它监听apiserver中service和endpoint的变化情况，创建路由规则以提供服务IP和负载均衡功能。简单理解此进程是Service的透明代理兼负载均衡器，其核心功能是将到某个Service的访问请求转发到后端的多个Pod实例上。\nkube-proxy iptables的原理 Kubernetes从1.","title":"kubernetes面试题收集"},{"content":"Linux 基础 进程 如何查找特定进程消耗多少内存 bash 1 ps aux --sort=-%mem 按住 ctrl + c 会发生什么 按下 Ctrl + C 时，会向当前正在运行的前台进程发送一个信号，具体来说是 SIGINT 信号（Interrupt Signal，中断信号）。这个信号的作用是请求进程中断执行，通常会导致进程终止。信号编号是 2。\n如果程序有处理信号，那么将不会发生任何事情。\n守护、僵⼫、孤⼉进程的概念终端 【答】\n守护进程：运⾏在后台的⼀种特殊进程，独⽴于控制终端并周期性地执⾏某些任务。 僵⼫进程：⼀个进程 fork ⼦进程，⼦进程退出，⽽⽗进程没有 wait/waitpid⼦进程，那么⼦进程的进程描述符仍保存在系统中，这样的进程称为僵⼫进程。 孤⼉进程：⼀个⽗进程退出，⽽它的⼀个或多个⼦进程还在运⾏，这些⼦进程称为孤⼉进程。（孤⼉进程将由 init 进程收养并对它们完成状态收集⼯作） 进程间通讯方式有哪些？ Pipe：无名管道，最基本的IPC，单向通信，仅在父/子进程之间，也就是将一个程序的输出直接交给另一个程序的输入。常见使用为 ps -ef|grep xxx FIFO [(First in, First out)] 或 有名管道（named pipe）:与Pipe不同，FIFO可以让两个不相关的进程可以使用FIFO。单向。 Socket 和 Unix Domain Socket：socket和Unix套接字，双向。适用于网络通信，但也可以在本地使用。适用于不同的协议。 消息队列 Message Queue: SysV 消息队列、POSIX 消息队列。 Signal: 信号，是发送到正在运行的进程通知以触发其事件的特定行为，是IPC的一种有限形式。 Semaphore：信号量，通常用于IPC或同一进程内的线程间通信。他们之间使用队列进行消息传递、控制或内容的传递。（常见SysV 信号量、POSIX 信号量） Shared memory：（常见SysV 共享内存、POSIX 共享内存）。共享内存，是在进程（程序）之间传递数据的有效方式，目的是在其之间提供通信。 BASH和DOS控制台之间的主要区别在于3个方面： 答案：\nBASH命令区分大小写，而DOS命令则不区分; 在BASH下，/ character是目录分隔符，\\ 作为转义字符。在DOS下，/ 用作命令参数分隔符，\\ 是目录分隔符 DOS遵循命名文件中的约定，即8个字符的文件名后跟一个点，扩展名为3个字符。BASH没有遵循这样的惯例。 Linux 中进程有哪几种状态？在 ps 显示出来的信息中，分别用什么符号表示的？ 答案：\nR runnable (on run queue) 运行 (正在运行或在运行队列中等待)\nS 中断 sleeping(休眠中, 受阻, 在等待某个条件的形成或接受到信号)\nD 不可中断 uninterruptible sleep (usually IO)(收到信号不唤醒和不可运行, 进程必须等待直到有中断发生)\nZ 僵尸 a defunct (”zombie”) process (进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放)\nT 停止 traced or stopped (进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行运行)\n系统目前有许多正在运行的任务，在不重启机器的条件下，有什么方法可以把所有正在运行的进程移除呢？ 答案：使用linux命令 ’disown -r ’可以将所有正在运行的进程移除。\nLinux 粘滞位作用 答案：\n粘滞位(sticky bit)权限是针对目录的，对文件无效，设置了sticky位表示这个目录里的文件只能被owner和root删除。\n什么是inode？ 答案：\ninode是Linux(Unix)操作系统中文件系统的一个概念。inode的全称为index node，也就是索引节点。那么inode是用来索引什么的呢？其实inode表示的是一个文件，它是用来索引文件数据的。\n磁盘报错”No space left on device”，但是通过命令df –h查看磁盘空间没有满，请问为什么？ 答案：\n该磁盘的inode数量被用尽，无法再写入文件。 企业工作中邮件临时队列 /var/spool/clientmquene或/var/spool/postfix/maildrop这里很容易被大量小文件占满导致No space left on device的错误。clientmquene目录只有安装了sendmail服务，才会有，是sendmail的临时队列。\n一个100M的磁盘分区，分别写入1K的文件，及写入1M的文件，分别可以写多少个？ 答案：\n在linux文件系统中，iNode用来存放文件的属性信息，而Block用来存放文件实际内容，默认大小1K(boot)或4K(非系统分区默认为4k)。\n写入1M文件的数量为100/1，且不会存在磁盘浪费情况（这也说明了一般情况下，inode和block的数量都是足够的）；\n而写入1K文件时，inode和block同时被消耗，但一般block数量远大于inode的数量，因此写入的数量就是inode的数量，并且这样会浪费3/4的磁盘容量。\nnohup nohup 执行会忽略信号 SIGHUP，并将 stdout/stderr 重定向到文件 nohup.out。以便shell在关闭或注销后命令可以在后台继续运行 。nohup做的工作就是让 nohup 后的命令不在是当前 shell 的子命令。而是PPID=1的进程（进程的PPID=1）。这种情况下不能被带回到前台。\nc 1 2 3 4 signal (SIGHUP, SIG_IGN); // 忽略信号SIGHUP char **cmd = argv + optind; execvp (*cmd, cmd); // 在执行这个命令，而不是当前shell 对于输出的重定向，对于STDOUT/STDERR会忽略，然后写入到 nohup.out\nc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ignoring_input = isatty (STDIN_FILENO); redirecting_stdout = isatty (STDOUT_FILENO); stdout_is_closed = (!redirecting_stdout \u0026amp;\u0026amp; errno == EBADF); redirecting_stderr = isatty (STDERR_FILENO); /* If standard input is a tty, replace it with /dev/null if possible. Note that it is deliberately opened for *writing*, to ensure any read evokes an error. */ if (ignoring_input) { if (fd_reopen (STDIN_FILENO, \u0026#34;/dev/null\u0026#34;, O_WRONLY, 0) \u0026lt; 0) error (exit_internal_failure, errno, _(\u0026#34;failed to render standard input unusable\u0026#34;)); if (!redirecting_stdout \u0026amp;\u0026amp; !redirecting_stderr) error (0, 0, _(\u0026#34;ignoring input\u0026#34;)); } /* If standard output is a tty, redirect it (appending) to a file. First try nohup.out, then $HOME/nohup.out. If standard error is a tty and standard output is closed, open nohup.out or $HOME/nohup.out without redirecting anything. */ if (redirecting_stdout || (redirecting_stderr \u0026amp;\u0026amp; stdout_is_closed)) { char *in_home = NULL; char const *file = \u0026#34;nohup.out\u0026#34;; int flags = O_CREAT | O_WRONLY | O_APPEND; mode_t mode = S_IRUSR | S_IWUSR; mode_t umask_value = umask (~mode); out_fd = (redirecting_stdout ? fd_reopen (STDOUT_FILENO, file, flags, mode) : open (file, flags, mode)); Referece what is the function of the nohup command\n","permalink":"https://www.161616.top/interview-linux/","summary":"Linux 基础 进程 如何查找特定进程消耗多少内存 bash 1 ps aux --sort=-%mem 按住 ctrl + c 会发生什么 按下 Ctrl + C 时，会向当前正在运行的前台进程发送一个信号，具体来说是 SIGINT 信号（Interrupt Signal，中断信号）。这个信号的作用是请求进程中断执行，通常会导致进程终止。信号编号是 2。\n如果程序有处理信号，那么将不会发生任何事情。\n守护、僵⼫、孤⼉进程的概念终端 【答】\n守护进程：运⾏在后台的⼀种特殊进程，独⽴于控制终端并周期性地执⾏某些任务。 僵⼫进程：⼀个进程 fork ⼦进程，⼦进程退出，⽽⽗进程没有 wait/waitpid⼦进程，那么⼦进程的进程描述符仍保存在系统中，这样的进程称为僵⼫进程。 孤⼉进程：⼀个⽗进程退出，⽽它的⼀个或多个⼦进程还在运⾏，这些⼦进程称为孤⼉进程。（孤⼉进程将由 init 进程收养并对它们完成状态收集⼯作） 进程间通讯方式有哪些？ Pipe：无名管道，最基本的IPC，单向通信，仅在父/子进程之间，也就是将一个程序的输出直接交给另一个程序的输入。常见使用为 ps -ef|grep xxx FIFO [(First in, First out)] 或 有名管道（named pipe）:与Pipe不同，FIFO可以让两个不相关的进程可以使用FIFO。单向。 Socket 和 Unix Domain Socket：socket和Unix套接字，双向。适用于网络通信，但也可以在本地使用。适用于不同的协议。 消息队列 Message Queue: SysV 消息队列、POSIX 消息队列。 Signal: 信号，是发送到正在运行的进程通知以触发其事件的特定行为，是IPC的一种有限形式。 Semaphore：信号量，通常用于IPC或同一进程内的线程间通信。他们之间使用队列进行消息传递、控制或内容的传递。（常见SysV 信号量、POSIX 信号量） Shared memory：（常见SysV 共享内存、POSIX 共享内存）。共享内存，是在进程（程序）之间传递数据的有效方式，目的是在其之间提供通信。 BASH和DOS控制台之间的主要区别在于3个方面： 答案：\nBASH命令区分大小写，而DOS命令则不区分; 在BASH下，/ character是目录分隔符，\\ 作为转义字符。在DOS下，/ 用作命令参数分隔符，\\ 是目录分隔符 DOS遵循命名文件中的约定，即8个字符的文件名后跟一个点，扩展名为3个字符。BASH没有遵循这样的惯例。 Linux 中进程有哪几种状态？在 ps 显示出来的信息中，分别用什么符号表示的？ 答案：","title":"操作系统类面试题收集"},{"content":"Prometheus的四种数据类型 Counter(计数器类型) Counter类型的指标的工作方式和计数器一样，只增不减 Gauge(仪表盘类型) Gauge是可增可减的指标类，通常用于反应当前应用的状态。 Histogram 主要用于表示一段时间范围内对数据进行采样 Summary(摘要类型) Summary类型和Histogram类型相似，主要用于表示一段时间内数据采样结果 Prometheus 的局限 Prometheus 是基于 Metric 的监控，不适用于日志（Logs）、事件(Event)、调用链(Tracing)。 Prometheus 默认是 Pull 模型，合理规划你的网络，尽量不要转发。对于集群化和水平扩展，官方和社区都没有银弹，需要合理选择 Federate、Cortex、Thanos等方案。 监控系统一般情况下可用性大于一致性，容忍部分副本数据丢失，保证查询请求成功。这个后面说Thanos 去重的时候会提到。 Prometheus 不一定保证数据准确，这里的不准确一是指 rate、histogram_quantile 等函数会做统计和推断，产生一些反直觉的结果，这个后面会详细展开。二来查询范围过长要做降采样，势必会造成数据精度丢失，不过这是时序数据的特点，也是不同于日志系统的地方 采集组件 All IN One Prometheus 体系中 Exporter 都是独立的，每个组件各司其职，如机器资源用 Node-Exporter，Gpu 有Nvidia Exporter等等。但是 Exporter 越多，运维压力越大，尤其是对 Agent做资源控制、版本升级。我们尝试对一些Exporter进行组合，方案有二：\n通过主进程拉起N个 Exporter 进程，仍然可以跟着社区版本做更新、bug fix。 用Telegraf来支持各种类型的 Input，N 合 1。另外，Node-Exporter 不支持进程监控，可以加一个Process-Exporter，也可以用上边提到的Telegraf，使用 procstat 的 input来采集进程指标。 合理选择黄金指标 采集的指标有很多，我们应该关注哪些？Google 在 “Sre Handbook” 中提出了“四个黄金信号”：延迟、流量、错误数、饱和度。实际操作中可以使用 Use 或 Red 方法作为指导，Use 用于资源，Red 用于服务。\nUse 方法：Utilization、Saturation、Errors。如 Cadvisor 数据 Red 方法：Rate、Errors、Duration。如 Apiserver 性能指标 Prometheus 采集中常见的服务分三种：\n在线服务：如 Web 服务、数据库等，一般关心请求速率，延迟和错误率即 RED 方法 离线服务：如日志处理、消息队列等，一般关注队列数量、进行中的数量，处理速度以及发生的错误即 Use 方法 批处理任务：和离线任务很像，但是离线任务是长期运行的，批处理任务是按计划运行的，如持续集成就是批处理任务，对应 K8S 中的 job 或 cronjob， 一般关注所花的时间、错误数等，因为运行周期短，很可能还没采集到就运行结束了，所以一般使用 Pushgateway，改拉为推。 如何采集 LB 后面的 RS 的 Metric 假如你有一个负载均衡 LB，但网络上 Prometheus 只能访问到 LB 本身，访问不到后面的 RS，应该如何采集 RS 暴露的 Metric？\nRS 的服务加 Sidecar Proxy，或者本机增加 Proxy 组件，保证 Prometheus 能访问到。 LB 增加 /backend1 和 /backend2请求转发到两个单独的后端，再由 Prometheus 访问 LB 采集。 Prometheus 大内存问题 随着规模变大，Prometheus 需要的 CPU 和内存都会升高，内存一般先达到瓶颈，这个时候要么加内存，要么集群分片减少单机指标。这里我们先讨论单机版 Prometheus 的内存问题。\n原因：\nPrometheus 的内存消耗主要是因为每隔2小时做一个 Block 数据落盘，落盘之前所有数据都在内存里面，因此和采集量有关。 加载历史数据时，是从磁盘到内存的，查询范围越大，内存越大。这里面有一定的优化空间。 一些不合理的查询条件也会加大内存，如 Group 或大范围 Rate。 我的指标需要多少内存：作者给了一个计算器，设置指标量、采集间隔之类的，计算Prometheus 需要的理论内存值：计算公式\n以我们的一个 Prometheus Server为例，本地只保留 2 小时数据，95 万 Series，大概占用的内存如下：\n有什么优化方案：\nSample 数量超过了 200 万，就不要单实例了，做下分片，然后通过 Victoriametrics，Thanos， Trickster 等方案合并数据。 评估哪些 Metric 和 Label 占用较多，去掉没用的指标。2.14 以上可以看 Tsdb 状态 查询时尽量避免大范围查询，注意时间范围和 Step 的比例，慎用 Group。 如果需要关联查询，先想想能不能通过 Relabel 的方式给原始数据多加个 Label，一条Sql 能查出 来的何必用Join，时序数据库不是关系数据库。 Prometheus 内存占用分析：\n通过 pprof分析 1.X 版本的内存 相关 issue： https://groups.google.com/forum/#!searchin/prometheus-users/memory%7Csort:date/prometheus-users/q4oiVGU6Bxo/uifpXVw3CwAJ https://github.com/prometheus/prometheus/issues/5723 https://github.com/prometheus/prometheus/issues/1881 Prometheus 容量规划 容量规划除了上边说的内存，还有磁盘存储规划，这和你的 Prometheus 的架构方案有关。\n如果是单机Prometheus，计算本地磁盘使用量。 如果是 Remote-Write，和已有的 Tsdb 共用即可。 如果是 Thanos 方案，本地磁盘可以忽略（2H)，计算对象 Prometheus 每2小时将已缓冲在内存中的数据压缩到磁盘上的块中。包括Chunks、Indexes、Tombstones、Metadata，这些占用了一部分存储空间。一般情况下，Prometheus中存储的每一个样本大概占用1-2字节大小（1.7Byte）。可以通过Promql来查看每个样本平均占用多少空间：\n如果大致估算本地磁盘大小，可以通过以下公式：$磁盘大小=保留时间每秒获取样本数样本大小$\n保留时间(retention_time_seconds)和样本大小(bytes_per_sample)不变的情况下，如果想减少本地磁 盘的容量需求，只能通过减少每秒获取样本数(ingested_samples_per_second)的方式。\n查看当前每秒获取的样本数：\ntext 1 rate(prometheus_tsdb_head_samples_appended_total[1h]) 有两种手段，一是减少时间序列的数量，二是增加采集样本的时间间隔。考虑到 Prometheus 会对时间 序列进行压缩，因此减少时间序列的数量效果更明显。\n举例说明：\n采集频率 30s，机器数量1000，Metric种类6000，1000600026024 约 200 亿，30G 左右磁盘。 只采集需要的指标，如 match[], 或者统计下最常使用的指标，性能最差的指标。 以上磁盘容量并没有把 wal 文件算进去，wal 文件(Raw Data)在 Prometheus 官方文档中说明至少会保存3个 Write-Ahead Log Files，每一个最大为128M(实际运行发现数量会更多)。\n因为我们使用了 Thanos 的方案，所以本地磁盘只保留2H 热数据。Wal 每2小时生成一份Block文件，Block文件每2小时上传对象存储，本地磁盘基本没有压力。\n","permalink":"https://www.161616.top/interview-monitor/","summary":"Prometheus的四种数据类型 Counter(计数器类型) Counter类型的指标的工作方式和计数器一样，只增不减 Gauge(仪表盘类型) Gauge是可增可减的指标类，通常用于反应当前应用的状态。 Histogram 主要用于表示一段时间范围内对数据进行采样 Summary(摘要类型) Summary类型和Histogram类型相似，主要用于表示一段时间内数据采样结果 Prometheus 的局限 Prometheus 是基于 Metric 的监控，不适用于日志（Logs）、事件(Event)、调用链(Tracing)。 Prometheus 默认是 Pull 模型，合理规划你的网络，尽量不要转发。对于集群化和水平扩展，官方和社区都没有银弹，需要合理选择 Federate、Cortex、Thanos等方案。 监控系统一般情况下可用性大于一致性，容忍部分副本数据丢失，保证查询请求成功。这个后面说Thanos 去重的时候会提到。 Prometheus 不一定保证数据准确，这里的不准确一是指 rate、histogram_quantile 等函数会做统计和推断，产生一些反直觉的结果，这个后面会详细展开。二来查询范围过长要做降采样，势必会造成数据精度丢失，不过这是时序数据的特点，也是不同于日志系统的地方 采集组件 All IN One Prometheus 体系中 Exporter 都是独立的，每个组件各司其职，如机器资源用 Node-Exporter，Gpu 有Nvidia Exporter等等。但是 Exporter 越多，运维压力越大，尤其是对 Agent做资源控制、版本升级。我们尝试对一些Exporter进行组合，方案有二：\n通过主进程拉起N个 Exporter 进程，仍然可以跟着社区版本做更新、bug fix。 用Telegraf来支持各种类型的 Input，N 合 1。另外，Node-Exporter 不支持进程监控，可以加一个Process-Exporter，也可以用上边提到的Telegraf，使用 procstat 的 input来采集进程指标。 合理选择黄金指标 采集的指标有很多，我们应该关注哪些？Google 在 “Sre Handbook” 中提出了“四个黄金信号”：延迟、流量、错误数、饱和度。实际操作中可以使用 Use 或 Red 方法作为指导，Use 用于资源，Red 用于服务。\nUse 方法：Utilization、Saturation、Errors。如 Cadvisor 数据 Red 方法：Rate、Errors、Duration。如 Apiserver 性能指标 Prometheus 采集中常见的服务分三种：","title":"监控类面试题"},{"content":"域名相关 什么是DNS劫持 DNS劫持就是通过劫持了DNS服务器，通过某些手段取得某域名的解析记录控制权，进而修改此域名的解析结果，导致对该域名的访问由原IP地址转入到修改后的指定IP，其结果就是对特定的网址不能访问或访问的是假网址，从而实现窃取资料或者破坏原有正常服务的目的。DNS劫持通过篡改DNS服务器上的数据返回给用户一个错误的查询结果来实现的。\n通俗来讲：DNS劫持就是指用户访问一个被标记的地址时，DNS服务器故意将此地址指向一个错误的IP地址的行为。范例，网通、电信、铁通的某些用户有时候会发现自己打算访问一个地址，却被转向了各种推送广告等网站，这就是DNS劫持。\nDNS劫持症状：在某些地区的用户在成功连接宽带后，首次打开任何页面都指向ISP提供的“电信互联星空”、“网通黄页广告”等内容页面。还有就是曾经出现过用户访问Google域名的时候出现了百度的网站。这些都属于DNS劫持。\n解决：对于DNS劫持，可以采用使用国外免费公用的DNS服务器解决。例如OpenDNS（208.67.222.222）或GoogleDNS（8.8.8.8）。\n什么是DNS污染 DNS污染是指在DNS服务器中修改DNS解析结果的过程，以便将用户重定向到恶意网站或欺骗性网站，而不是所期望的目标网站。\n攻击者可以通过多种方式进行DNS污染攻击。最常见的手段是在用户的网络中添加一个恶意DNS服务器，或者在受感染的计算机上运行一个恶意DNS服务器。当用户试图连接到互联网上的某个网站时，计算机将查询DNS服务器以查找目标网站的IP地址。如果攻击者控制的恶意DNS服务器已将相应的IP地址修改为攻击者的网站，则用户将被重定向到恶意网站或欺骗性网站。\nDNS污染发生在用户请求的第一步上，直接从协议上对用户的DNS请求进行干扰。 DNS污染症状：目前一些被禁止访问的网站很多就是通过DNS污染来实现的，例如YouTube、Facebook等网站。\n解决：\n可靠的DNS服务器，但这种方式效果不佳 手动修改Hosts文件 使用VPN或域名远程解析 加密通信：VPN可以加密整个通信过程，这意味着攻击者无法窃取UDP数据包中的任何信息，包括DNS查询请求和响应。这样可以避免DNS查询被篡改的风险。 虚拟IP地址：VPN会给每个用户分配其所连接的虚拟IP地址，使用户的真实IP地址不会暴露在公共互联网中。这样，DNS服务器只能看到VPN服务器的IP地址，而无法识别用户的IP地址。这意味着攻击者无法跟踪用户的访问历史，从而减少遭受DNS污染攻击的风险。 总结：\nDNS污染，指的是用户访问一个地址，国内的服务器(非DNS)监控到用户访问的已经被标记地址时，服务器伪装成DNS服务器向用户发回错误的地址的行为。范例，访问Youtube、Facebook之类网站等出现的状况。\n什么是域名被墙 这种情况一般出现在解析为国外地址的域名上，假如域名下的网站非法信息多，敏感，又不整改，会直接被G.F.W墙掉，就是通常所说的被封锁、被屏蔽、被和谐，结果就是访问域名是打不开的，但是解析是正常的。此时域名在国内是无法使用的，国外可以访问和使用。\n主要有以下几种情况：\nip 被墙 解决：换 ip 。 域名被 url 重置（访问时出现ERR_CONNECTION_RESET 或 “连接重置” 换域名 做301跳转，(有专门服务商)，域名通过解析到国内301服务商，重定向到真是国外IP，以减少流量和权重的丢失。 上 https或域名备案，智能解析分国内，国外 。 可以使用HTTPS；一般来讲解析到国外的IP的域名，有敏感词会被重置，GFW可以进行敏感词检测（http为明文），使用https加密GFW无法检测数据包内容 ，（客户端与服务端默认会有公钥私钥，而GFW没有）。 域名被国家出口 dns 污染，解决：用国内 dns ，备案回国。 域名被省级 dns 污染，解决：能做到这个这里可能为内部或对应运营商被黑（只能进行dns清洗，一般大流量域名了） 什么是DNS清洗？ DNS清洗是一项旨在阻止访问特定网站和域名的措施，在该措施中，Internet服务提供商（ISP）通过其服务器筛选特定网站的DNS查询，并将查询重定向到一个错误的IP地址（通常是一个不存在的地址），从而防止用户访问该网站。这种措施通常是由政府、公司或组织实施，旨在防止用户接触到不适当、危险或非法的内容。\n网络攻击相关 什么是TCP的SYN攻击？如何预防？ TCP SYN攻击是一种利用TCP协议三次握手机制的攻击。攻击者发送大量伪造的TCP SYN请求（数据包），然后在TCP三次握手建立连接的第二步时停止（使服务器不断地向攻击者发送SYN-ACK确认，但攻击者不回复ACK确认），从而导致服务器等待客户端的确认信号很长时间，最终占用服务器的资源而无法处理新的请求。\n为了预防TCP SYN攻击，可以采取以下措施：\n服务器操作系统的设置：可以设置TCP的连接数和时间等参数，限制每个IP地址的连接数，设置连接超时时间。 防火墙设置：可以设置防火墙规则，根据IP地址、端口等信息对连接进行过滤，控制IP地址的访问等。 加强网络监测：使用入侵检测系统（IDS）对流量进行实时监控，并对异常流量进行报警处理。 使用SYN Cookies：SYN cookies是一种可以防止SYN攻击的技术，它通过特殊的算法对TCP连接进行加密，并保存在服务器端，当客户端发送响应时，服务器端可以对连接进行识别和验证，从而防止SYN攻击。 增加硬件设备：可以增加具有流量分析和过滤功能的硬件设备来协助防御SYN攻击，这种设备可以通过分析流量实现精细化的流量分析和识别。 ddos攻击的类型 DDoS攻击（Distributed Denial of Service）是一种利用许多计算机和网络设备构成的“僵尸网络”对一个或多个目标服务器发起攻击，从而占用大量的网络资源，耗尽系统资源，导致服务拒绝的攻击方式。DDoS攻击的类型可以分为以下几种：\n带宽攻击（Bandwidth-based Attack）：利用大量的数据流或报文，通过消耗目标系统的网络带宽使其服务不能正常传输。 应用层攻击（Application-Layer Attack）：利用正常流量模拟合法用户的请求，通过消耗服务器CPU和内存资源使其无法处理合法请求。 反射式攻击（Reflection Attack）：使用伪造的IP地址向网络中的一个或多个服务器发起请求，这些服务器会响应请求，但响应信息将被发回目标服务器，从而形成了一次反射式攻击。 慢速攻击（Slowloris Attack）：利用HTTP协议的设计漏洞，向目标服务器发送大量不完整请求，从而占用目标服务器处理请求的线程资源。 IoT攻击（IoT Attack）：通过侵入大量的物联网设备，如路由器、摄像头、智能家居等，利用这些设备来发起攻击，构建大规模的“僵尸网络”。 DNS Amplification攻击：攻击者向域名服务器发送请求，利用伪造的IP地址和请求报文，让服务器向目标主机发送大量的DNS解析响应数据包，从而使目标系统在短时间内遭受网络拥塞。 NTP Amplification攻击：攻击者伪造IP地址，向其余互联网上安装有网络时间服务器（Network Time Protocol，NTP）软件的服务器发送请求，从而获取大量NTP响应包，最终将其转发到目标IP地址，从而占用目标系统的网络带宽。 SYN Flood攻击：攻击者向目标服务器发送大量的TCP SYN请求，但却不发送客户端的应答确认，造成服务器长时间处于等待状态，无法接受正常的TCP连接请求。 HTTP Flood攻击：攻击者利用HTTP叠加攻击、HTTP POST攻击等手段，向目标系统发送大量HTTP请求和数据包，造成目标系统资源的耗尽，从而导致服务不可用。 ICMP Flood攻击：攻击者向目标系统发送大量的ICMP数据包，造成目标系统CPU和内存资源的消耗，从而导致系统缓慢或崩溃。 什么是反射式攻击 反射式攻击（Reflection Attack）是一种利用网络协议的设计缺陷进行攻击的方式。攻击者通常会利用一些可以进行源地址欺骗或反射的协议，例如Domain Name System（DNS），Simple Network Management Protocol（SNMP），和Network Time Protocol（NTP）等。攻击者利用这些协议在网络中进行广播，构造一些请求消息，伪造源IP地址为目标IP地址，将请求消息发送给网络上的服务器，要求其向目标IP地址回送响应。这样攻击者就可以通过伪造的IP地址对目标系统进行攻击，占用它的网络带宽和资源，极大地降低了目标系统的可用性。\n反射式攻击的原理：\n攻击者使用一个随机IP地址，向一个有可能反射请求的服务器发送一个请求包。 攻击者伪造请求包的源IP地址为目标IP地址。 服务器收到请求包后，会根据请求包中的信息回复一个响应包到目标IP地址。 攻击者的随机IP地址会收到一个错误的响应包，而目标IP地址会收到大量的响应包，引起服务器的网络拥塞和系统负载过高。 反射式攻击的特点是可以发动大规模的攻击，难以追踪攻击者的真实身份。 为了避免反射式攻击带来的影响，应加强对网络设备的安全性监管，限制端口映射或在系统中配置反射式攻击防御机制等。\n什么是CC攻击 CC攻击是一种网络攻击，也称为HTTP CC攻击。\nCC是英文“Challenge Collapsar”的缩写，意思是“挑战式崩溃”。这种攻击通常是攻击者使用大量的机器在同一时间，对目标网站发送数以万计的HTTP请求，耗费网站的带宽和Web服务器资源，从而使得目标网站难以提供正常的服务。\nCC 攻击的原理：\n攻击者使用大量的机器，在短时间内对目标网站发送大量的请求，即HTTP GET和HTTP POST请求。 攻击者使用IP地址欺骗进行伪造，以避免被目标网站发现。 目标网站在处理大量请求的同时，网络带宽和服务器资源被消耗，导致无法正常处理合法用户的请求。 这种攻击方式是一种专门针对Web应用的攻击，能够对目标网站造成极大的破坏。 CC攻击具有隐蔽性强、攻击目标精准、攻击规模大的特点，对于商业网站和金融机构等重要场所，尤其危害性较高。为了防范CC攻击，应采用一系列的安全措施，包括但不限于：IP限制、用户访问控制、流量清洗系统、高级请求监控和识别等。\ncc攻击和ddos攻击有什么区别 CC攻击和DDoS攻击都是一种网络攻击方式，但它们具有不同的特点和目的，可以通过以下几个维度进行区分：\n发起机制：DDoS攻击通常使用由许多受感染的计算机组成的僵尸网络向目标服务器发起攻击；而CC攻击则利用大量的请求浪费目标网站的资源，其通常由单个或少量主机发起。 对服务器的影响：DDoS攻击通常通过占用服务器的网络带宽和处理能力消耗服务器资源，从而使服务器无法处理合法的请求。而CC攻击通常会利用大量的HTTP请求消耗目标服务器的网络带宽和Web服务器资源，从而使目标网站无法提供正常的服务。 目的不同：DDoS攻击的目的通常是摧毁或瘫痪目标网站，其目的往往是为了实现利润或危害竞争对手。而CC攻击通常用于使目标网站的网络带宽和服务器资源不可用，以达到一定的干扰或损坏目的。 防御方式：DDoS攻击通常需要综合多种防护手段，包括入侵检测系统、防火墙、流量清洗系统等；而CC攻击通常可以通过增加带宽、配置IP限制、使用反垃圾过滤系统等简单的措施进行防御。 总的来说，CC攻击主要针对Web应用程序和网站，其攻击手段比DDoS攻击更加简单和直接，而DDoS攻击则涉及到更多的计算机协调，其对目标服务器造成的影响也更为广泛和严重。\nDBus攻击 DBus（Desktop Bus）是用于在Linux系统中应用程序之间进行通信的一种机制。DBus通信协议是所有Linux桌面环境（如GNOME和KDE）的核心组件。DBus协议本身并没有安全问题，但是由于应用程序在通信时可能会使用明文传输敏感信息（例如登录密码），因此DBus协议仍然存在被黑客攻击的风险。\nDBus攻击主要有以下几种方式\n端口监听攻击：DBus默认使用unix-socket，如果未被正确配置只能在特定用户之间使用。如果DBus暴露在公网上，并启用了TCP/IP支持，则可能会受到端口监听攻击。 消息劫持攻击：假冒攻击者可以伪造DBus通信的结构头和消息体，并将其发送到目标程序。这样可以使目标程序对不良消息进行响应并以不合适的方式执行命令或泄漏敏感信息。 执行命令攻击：攻击者可以通过发送DBus消息来请求目标程序执行特定的操作。如果目标程序没有执行严格的访问控制，那么攻击者可能会成功执行恶意操作。 为了防止DBus攻击，可以采取以下预防措施：\n使用策略控制：通过在DBus配置文件中设置安全策略，在文件中定义一系列规则，以控制哪些应用程序可以访问DBus总线，以及哪些应用程序可以以哪种方式访问DBus总线。 使用加密通信：可以使用SSL或TLS等安全协议对DBus通信进行加密，以确保DBus通信传输的数据不会被窃取或篡改。 调用API实现过滤和审核：可以通过DBus接口的API调用来检查和验证DBus消息中的请求和响应是否合法，如果失败，则拒绝执行DBus请求命令。 更新操作系统和软件：可以定期更新Linux操作系统和DBus相关软件的版本，以修复可能出现的安全漏洞，从而提高DBus协议的安全性。 综上所述，DBus协议是任何Linux桌面环境中的核心组件，因此必须采取一系列的安全预防措施防止攻击者利用漏洞对DBus协议进行攻击。\n网络相关 OSI模型和TCP/IP模型有什么区别？ OSI模型和TCP/IP模型都是网络通信时使用的通信协议模型\nOSI模型通常表示一个网络请求的完整路径，而TCP/IP模型通常表示Linux 内核网络栈中的模型\nOSI模型包括7个层次：物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。\nTCP/IP模型则是由4个层次组成：网络接口层、网络层、传输层和应用层\n简述TCP重传时的ACK机制？ TCP协议中的重传机制是保证数据传输可靠性的关键，它使用一个称为确认（ACK）的机制来标记接收到的数据段。TCP准确地检测丢失或重复数据包，并要求发送方重发丢失的数据包，直到接收方确认收到该数据包。\n当TCP发送方发现某个数据包未收到确认时，会启动重传机制。它会重发此数据包并设置一个计时器，在计时器超时前等待接收方的确认，并在接收方回复确认后将计时器停止。如果在计时器超时之前，没有收到确认，则认为此数据包丢失，就会重发相同的数据包，并随之设置新的计时器。\n每次成功发送数据包后，TCP发送方将等待接收方发回一个相应的确认，以确认数据包已经被正确接收。接收方会在ACK中包含一个确认号（ACK number），该数值表示收到的最后一个正确的数据包的编号，这表明它准备接收下一数据包并告诉发送端可以继续发送数据。\n总之，TCP重传时的ACK机制，是通过发送数据包、等待接收方确认、超时时间检测等多步操作来检查数据包传输是否能够成功到达，同时保证数据的可靠传输。\n常见的TCP连接报错有哪些? Connection reset by peer：通常是由于远程端重置连接造成的，发送的是 RST 强制中断连接\n这类问题主要发生在连接关闭的情况下，在tcp连接中，双方是对等连接 peer，当数据包从连接的一端发送，但另一端无法识别到连接时，会返回设置了RST位的数据包，用来强制关闭连接。\n通常情况下，会立即中断连接，并且绕过了正常关闭的2MSL时间。例如：\n客户端关闭了连接，而服务器还在给客户端发送数据。 服务器超载，服务端强制关闭掉一些连接。 Connection timeout：般为建立连接在最大超时时间后没有收到来自服务端的相应，此时客户端将收到错误信息。\nConnection refused：通常出现在连接某一服务的端口时发生（拒绝机制是 TCP RST 标志），通常情况下包含一下情况：\n目标主机端口未开放 端口开放了，但处理其连接堆积或已满。 客户端与服务端防火墙阻碍其连接。（客户端和服务端防火墙均会发生） Port Unavailable：通常由于TCP连接的目标端口已经被占用, 或是网络路由器的设置引起的。\nHost Unreachable：主机不可达错误通常由于网络路由故障引起的，它表示目标主机无法被连接，有可能是由于网络故障或是DNS解析错误引起的。\n什么是TCP Reset（RST） 当收到意外数据包（非正常3次握手进行的连接）到达主机时，此时会重置连接，重置的数据包是设置了RST位的数据包。\n常见的有以下情况：\n一个初始数据包 (初始数据包SYN) 尝试向一个没有任何进程的监听地址发起连接时，会出现次状态（如：curl localhost $non-listen-port） 在之前建立的连接，并且本地已经关闭socket或退出。 防火墙的拦截 数据包到达先前建立的TCP连接，但本地应用程序已关闭其套接字或退出，并且操作系统已关闭该套接字。\nTCP原理总结 一问二答三确认，双方状态才成立。\nSYN SYN+ACK ACK 一方说停，对方确认；对方再停，我再确认。\nFIN ACK FIN2 ACK 常见TCP的连接状态有哪些？ 三次握手期间：\nCLOSED：初始状态。\nLISTEN：服务器处于监听状态。\nSYN_SEND：客户端socket执行CONNECT连接，发送SYN包，进入此状态。\nSYN_RECV：服务端收到SYN包并发送服务端SYN包，进入此状态。\nESTABLISH：表示连接建立。客户端发送了最后一个ACK包后进入此状态，服务端接收到ACK包后 进入此状态。\n四次挥手期间：\nFIN_WAIT_1：终止连接的一方（通常是客户机）发送了FIN报文后进入。等待对方FIN。 CLOSE_WAIT：（假设服务器）接收到客户机FIN包之后等待关闭的阶段。在接收到对方的FIN包之 后，自然是需要立即回复ACK包的，表示已经知道断开请求。但是本方是否立即断开连接（发送FIN 包）取决于是否还有数据需要发送给客户端，若有，则在发送FIN包之前均为此状态。 FIN_WAIT_2：此时是半连接状态，即有一方要求关闭连接，等待另一方关闭。客户端接收到服务器 的ACK包，但并没有立即接收到服务端的FIN包，进入FIN_WAIT_2状态。 LAST_ACK：服务端发动最后的FIN包，等待最后的客户端ACK响应，进入此状态。 TIME_WAIT：客户端收到服务端的FIN包，并立即发出ACK包做最后的确认，为此之后的2MSL时间 客户端为TIME_WAIT状态。 CLOSE_WAIT状态的产生、危害、如何避免？ 客户端TCP状态迁移：\ntext 1 CLOSED-\u0026gt;SYN_SENT-\u0026gt;ESTABLISHED-\u0026gt;FIN_WAIT_1-\u0026gt;FIN_WAIT_2-\u0026gt;TIME_WAIT-\u0026gt;CLOSED 服务器TCP状态迁移：\ntext 1 CLOSED-\u0026gt;LISTEN-\u0026gt;SYN_RECV-\u0026gt;ESTABLISHED-\u0026gt;CLOSE_WAIT-\u0026gt;LAST_ACK-\u0026gt;CLOSED 【答】：\n产生: TCP连接的两端都可以发起关闭连接的请求，若对端发起了关闭连接，但本地没有进行后续的关闭连接操作，那么该链接就会处于CLOSE_WAIT状态。\n在某种情况下应用关闭了socket连接,但是服务端忙于读或者写，没有关闭连接。 在被动关闭连接情况下，在已经接收到FIN，但是还没有发送自己的FIN的时刻，连接处于CLOSE_WAIT状态。通常来讲，CLOSE_WAIT状态的持续时间应该很短，正如SYN_RCVD状态。但是在一些特殊情况下，就会出现连接长时间处于CLOSE_WAIT状态的情况。 危害：CLOSE_WAIT 会使连接处于假死状态，连接本身占用的资源不会被释放。网络服务器程序要同时管理大量连接，所以很有必要保证无用连接完全断开，否则大量僵死的连接会浪费许多服务器资源。\n如何避免：\n更详细分析可以看：https://www.cnblogs.com/shengs/p/4495998.html\nTCP的关闭 TCP 支持两种类型的连接释放：\n优雅关闭：正常的四次挥手\n突然关闭：发送RST段，即TCP Reset\n当为不存在的 TCP 连接接收到非 SYN 段时 在已打开的连接中，某些 TCP 实现会在收到具有无效标头的段时会发送 RST 段。 这将通过关闭相应的连接来防止攻击。 当某些实现需要关闭现有的 TCP 连接时，它们会发送一个 RST 段。 将立即关闭现有的 TCP 连接： 缺乏支持连接的资源 远程主机现在无法访问并且已停止响应 TIME_WAIT 状态的产生、危害、如何避免？ 【答】：\n产生：TCP在关闭连接的四次挥手中，为了应对最后一个ACK 丢失的情况，Client(主动关闭的一方)需要维持TIME_WAIT状态，并停留2MSL的时间。\n危害：\n浪费系统资源：大量的TIME_WAIT状态会占用系统资源（用户的文件句柄 如端口） 降低系统性能： 如何避免：在 /etc/sysctl.conf文件中开启 net.ipv4.tcp_tw_reuse重用，和net_ipv4.tcp.tw_recycle 快速回收。\nTIME_WAIT 和 CLOSE_WAIT区别 主动关闭的一端是 TIME_WAIT；即客户端主动关闭TIME_WAIT出现在客户端，服务端主动关闭 TIME_WAIT出现在服务端 被动关闭的一端是 CLOSE_WAIT；通常情况下，服务端会维护大量的CLOSE_WAIT 服务端出现 CLOSE_WAIT则客户端永远不会出现 TIME_WAIT CLOSE_WAIT 一般发生在服务在接收到关闭信号后没有正确关闭连接 TIME_WAIT 会随着2MSL的时长而积压 服务端 在 发送 两次 ACK 与 FIN后，即收到FIN后客户端（主动断开端）才会转变为 TIME_WAIT 结束CLOSE_WAIT状态，对应的应用程序必须显式关闭打开套接字（或退出） ss命令可以强制关闭套接字 ss \u0026ndash;tcp state CLOSE-WAIT \u0026ndash;kill 关闭对应状态的连接 ss \u0026ndash;tcp state CLOSE-WAIT \u0026lsquo;( dport = 22 or dst 1.1.1.1 )\u0026rsquo; \u0026ndash;kill 过滤操作\n2MSL的值是多少？ 2MSL (Maximum Segment Lifetime) 作为TCP 连接的“关闭”过程的一部分，是在 RFC 793(TCP)，Linux内核的默认MSL设置为 60 秒，2MSL为120s，可以通过cat /proc/sys/net/ipv4/tcp_fin_timeout查看。\n在 Check Point VPN网关中，R80.20 及更高版本中，默认的 TCP 超时5秒。\nReference\n2MSL\n为什么客户端要等2MSL TCP / IP协议中使用2MSL来等待连接中的可能尚未到达的遗留数据包。当一个TCP连接关闭时，每个端口必须等待2MSL时间，以确保对方端口正确接收了所有数据段。以下是对不同情况下等待时间的解释：\n如果等待时间小于1MSL，可能会有数据包丢失，从而导致通信不完整。 如果等待时间等于1MSL，TCP连接完成后，存在某些可能尚未到达的数据包，这些数据包在此时间段内将没有机会重传，因此可能会导致通信不完整。 如果等待时间大于2MSL，虽然可以确保数据包被完全接收并处理，但是长时间的等待可能会影响网络效率并占用网络资源。 因此，2MSL时间是一个标准和安全的等待时间，可确保TCP连接关闭时没有未接收的数据包。\n《UNIX网络编程(卷1)》提到，TIME_WAIT的作用大概是下述两部分：\n实现可靠地TCP连接终止 为了可靠的终止TCP的全双工连接，当客户端发送的最后一个ACK丢失，服务端会重传FIN，为了接收超时并重传FIN，客户端就需要一个TIME_WAIT，如果RTO（Retransmission Timeout）小于MSL，那么TIME_WAIT的大小为MSL就足够了，如果RTO大于2MSL，则TIME_WAIT大小为2MSL已经不够了，所以只有TIME_WAIT状态介于 MSL与2MSL之间，才实现可靠地TCP连接终止。通常情况下RTO要比MSL小很多，但是考虑到最糟糕的情况，RTO是2MSL 允许旧的重复段在网络中过期 为了保证在这个连接期间产生的所有数据包都从网络中消失，即保证在建立新的TCP连接时，来自该连接的旧的重复数据包已经在网络中消失了； 此时存在一个问题：当客户端回复最后一个ACK后，用一个MSL时间就可以断开双方的连接（所有的数据包都消失）。为什么需要2MSL才可以？ 这是因为假设在客户端发送ACK刚刚过了一个MSL时间，而服务端在收到这个ACK之前一瞬间刚好启动超时重传FIN，所以要等这个FIN也消失，就是2MSL了。文中所指的另一个方向的应答应该就是这个超时重传的FIN。 Reference\nwhy time wait state need to be 2msl long\nin the tcp state transition diagram why do we have\nRTO和MSL RTO和MSL是两个完全独立的定义，它们之间没有特定的依赖关系。RTO是一个时间间隔，在未收到对方确认的情况下触发重传，并根据网络连接的延迟和拥塞情况进行调整。而MSL则是一个固定的时间，在TCP连接关闭后，用于保持最后的ACK数据包保持活动状态的时间。\n尽管RTO和MSL没有直接关联，但是RTO和MSL都涉及到TCP数据包的传输和处理。RTO值需要在某些情况下限制其最小值，以避免过度等待，例如在快速重传和快速恢复等TCP机制中。而MSL在TCP数据包传输网络中扮演着非常重要的角色，以确保在TCP连接关闭后遗留的数据包都能达到其目的地，同时避免后续的混淆与冲突。\nRTO和MSL是TCP / IP协议中的两个超时机制，这些机制都与传输控制协议（TCP）的数据包传输相关。以下是它们之间的区别：\nRTO (Retransmission Time Out)：是指在TCP / IP网络中，当一个数据包发送后，期望在多长时间内收到对方回应的时间。如果在该时间内没有收到回应，则认为该数据包已丢失，发起重传。RTO时间间隔通常根据TCP窗口大小调整，一般都比MSL的时间间隔短。 MSL (Maximum Segment Lifetime)：是指在TCP网络中，一个数据包在网络中传输的最长时间。当超过这个时间后，该数据包将被认为已经过期并被丢弃。通常，MSL是30秒，是一个相对不变的时间阈值，不会随着网络的突发变化而发生变化。 总的来说，RTO和MSL都是与TCP传输相关的超时机制，但它们的目的和应用场景略有区别：RTO用于控制未收到响应时的重传时间间隔，而MSL用于控制关闭连接后等待可能未到达的数据包的最长时间。\nTCP 和 UDP 的区别？ TCP是稳定、可靠、⾯向连接的传输层协议，它在传递数据前要三次握⼿建⽴连接，在数据传递时，有确认机制、重传机制、流量控制、拥塞控制等，可以保证数据的正确性和有序性。 UDP是⽆连接的数据传输协议，端与端之间不需要建⽴连接，且没有类似TCP的那些机制，会发⽣丢包、乱序等情况。\nTCP是数据流模式，⽽UDP是数据报模式。\n为什么 TCP 叫数据流模式？ UDP 叫数据报模式？ 【答】：最大的一个区别就是，TCP包头允许数据的分段，会携带每段的编号，而UDP只携带数据长度及校验码\n所谓的“流模式”，是指TCP发送端发送⼏次数据和接收端接收⼏次数据是没有必然联系的，⽐如你通过 TCP 连接给另⼀端发送数据，你只调⽤了⼀次 write，发送了100个字节，但是对⽅可以分10次收完，每次10个字节；你也可以调⽤10次 write，每次10个字节，但是对⽅可以⼀次就收完。\n原因：这是因为TCP是⾯向连接的，⼀个 socket 中收到的数据都是由同⼀台主机发出，且有序地到达，所以每次读取多少数据都可以。\n所谓的“数据报模式”，是指UDP发送端调⽤了⼏次 write，接收端必须⽤相同次数的 read 读完。UDP 是基于报⽂的，在接收的时候，每次最多只能读取⼀个报⽂，报⽂和报⽂是不会合并的，如果缓冲区⼩于报⽂⻓度，则多出的部分会被丢弃。\n原因：这是因为UDP是⽆连接的，只要知道接收端的 IP 和端⼝，任何主机都可以向接收端发送数据。 这时候， 如果⼀次能读取超过⼀个报⽂的数据， 则会乱套\nTCP建⽴连接为什么需要三次？断开连接⼜为什么需要四次？ 【答】：\n“三次握⼿”的主要⽬的是为了防⽌已失效的连接请求报⽂段突然⼜传送到了服务端，因⽽产⽣错误。\n例如：client发出的第⼀个连接请求报⽂段并没有丢失，⽽是在某个⽹络结点⻓时间的滞留了，以致延误到连接释放以后的某个时间才到达server。本来这是⼀个早已失效的报⽂段。但server收到此失效的连接请求报⽂段后，就误认为是client再次发出的⼀个新的连接请求。于是就向client发出确认报⽂段，同意建⽴连接。假设不采⽤“三次握⼿”，那么只要server发出确认，新的连接就建⽴了。由于现在client并没有发出建⽴连接的请求，因此不会理睬server的确认，也不会向server发送ack包。\n“四次挥⼿”主要是为了确保数据能够完成传输。\n因为TCP连接是全双⼯的(即数据可在两个⽅向上同时传递)，关闭连接时，当收到对⽅的FIN报⽂通知时，它仅仅表示对⽅没有数据发送给你了；但未必你所有的数据都全部发送给对⽅了，所以你可以未必会⻢上会关闭SOCKET,也即你可能还需要发送⼀些数据给对⽅之后，再发送FIN报⽂给对⽅来表示你同意现在可以关闭连接了，所以它这⾥的ACK报⽂和FIN报⽂多数情况下都是分开发送的。\nTCP协议如何提供可靠性的？ TCP的可靠，因为它使用校验和进行错误检测，尝试通过重新传输、确认策略和计时器来恢复丢失或损坏的数据包。它使用字节数和序列号以及确认号等特性来确保可靠性。\nTCP Flags TCP有6个Flag\nSYN (synchronize)，初始化一个连接的标识 ACK (acknowledgment)；用于确认数据包已收到，用于确认建立连接和关闭连接的 RST (reset)；表示连接已关闭，或者服务可能不接受请求 FIN (finish)；表示正在断开连接。发送方和接收方都发送 FIN 数据包以优雅地终止连接 PSH (push)；表示传入的数据应该直接传递给应用程序，而不是被缓冲 URG (urgent)；表示数据包所携带的数据应立即由 TCP 堆栈处理 DNS使⽤什么协议？ DNS服务器间进⾏域传输的时候使⽤ TCP 53；\n客户端查询DNS服务器时使⽤ UDP 53，但当DNS查询超过512字节，TC标志出现时，使⽤TCP发送。\n这是因为以太⽹(Ethernet)数据帧的⻓度必须在46-1500字节之间，这是由以太⽹的物理特性决定的。这个数据帧⻓度被称为链路层的MTU（最⼤传输单元）—— 实际Internet上的标准MTU值为576字节，也就是说链路层的数据区（不包括链路层的头部和尾部）被限制在576字节，所以这也就是⽹络层IP数据报的⻓度限制。\n因为IP数据报的⾸部为20字节，所以IP数据报的数据区⻓度最⼤为556字节。⽽这个556字节就是⽤来放TCP报⽂段或UDP数据报的。我们知道UDP数据报的⾸部8字节，所以UDP数据报的数据区最⼤⻓度为548字节。—— 如果UDP数据报的数据区⼤于这个⻓度，那么总的IP数据包就会⼤于MTU，这个时候发送⽅IP层就需要分⽚(fragmentation)，把数据报分成若⼲⽚，使每⼀⽚都⼩于MTU，⽽接收⽅IP层则需要进⾏数据报的重组。由于UDP的特性，当某⼀⽚数据传送中丢失时，接收⽅将⽆法重组数据报，从⽽导致丢弃整个UDP数据报。所以通常UDP的最⼤报⽂⻓度就限制为512字节或更⼩。\nTCP的拥塞控制机制是什么？请简单说说。 IP报文中TTL字段作用 可用于防止数据包循环\n什么是TCP RTO？ TCP RTO是TCP数据包在发送后到接收ACK之间的超时时间。如果在这个时间内没有收到ACK，则认为数据包已经丢失，需要进行重传。RTO的值是根据网络延迟、带宽和网络拥堵等多种因素计算得出的。\n如何调整TCP RTO？ TCP RTO的调整需要根据实际的网络环境进行，可以通过以下几种方法进行：\n内核参数调整：可以通过调整内核参数来改变TCP RTO的计算公式，从而适应不同的网络环境； 使用拥塞控制算法：可以通过改变TCP的拥塞控制算法来影响RTO的调整； 优化网络拓扑结构：通过优化网络拓扑结构，如改变路由器的位置、增加网络带宽等，可以有效地降低RTO的值。 面试题：用户第一次访问网站的基本流程 问题：假设用户在浏览器中输入 www.example.com，并且这是用户第一次访问该网站（即没有缓存）。请详细描述从输入 URL 到网页内容显示在浏览器上的完整流程，特别关注 DNS 解析和网络请求的细节。\n答案（口述版本）：\n用户输入 URL 并触发请求 用户在浏览器地址栏输入 www.example.com 并按下回车。浏览器会将这个请求分解为几个部分：协议（默认推测为 https）、主机名（www.example.com）和路径（默认 /）。然后，浏览器开始解析主机名对应的 IP 地址。\nDNS 解析：查找 IP 地址\n因为是第一次访问，浏览器和操作系统中都没有缓存，系统需要通过 DNS 解析获取\nwww.example.com\n的 IP 地址。这个过程在 Linux 系统中是这样进行的：\n检查 NSS 配置：操作系统读取 /etc/nsswitch.conf 文件中的 hosts 行，例如 hosts: files dns，决定解析顺序。 查询 /etc/hosts：首先检查本地的 /etc/hosts 文件，看是否有静态映射。如果有 www.example.com 的记录（比如 192.0.2.1 www.example.com），直接返回 IP 地址，解析结束。 本地缓存服务：如果文件中没有记录，且配置了 resolve（如使用 systemd-resolved），会查询本地 DNS 缓存。由于是第一次访问，缓存为空。 查询 DNS 服务器：系统读取 /etc/resolv.conf，找到指定的 DNS 服务器（比如 nameserver 8.8.8.8）。如果使用了 systemd-resolved，可能会先通过 127.0.0.53 转发请求。 递归解析：DNS 请求从本地发送到递归解析器，依次查询根域名服务器（.）、顶级域名服务器（.com），最后到达权威域名服务器（example.com 的 NS 记录），获取 www.example.com 的 A 记录（例如 192.0.2.1）。 返回 IP：解析完成后，IP 地址返回给操作系统和浏览器。 建立 TCP 连接\n浏览器拿到 IP 地址（192.0.2.1）后，发起与目标服务器的 TCP 连接：\n三次握手：浏览器发送 SYN 包，服务器回复 SYN-ACK，浏览器再发送 ACK，建立连接。\n连接跟踪：如果系统中启用了 conntrack（如 NAT 或防火墙），内核会记录这个连接的状态（NEW → ESTABLISHED）。\n端口分配：本地随机分配一个临时端口（来自 ip_local_port_range），与服务器的 443 端口（HTTPS 默认）通信。\n发送 HTTPS 请求\nTLS 握手：建立 TCP 连接后，浏览器发起 TLS 握手，与服务器协商加密协议、交换证书并验证身份（证书由 CA 签名）。\nHTTP 请求\n：完成 TLS 后，浏览器发送 HTTP 请求，例如：\ntext\n","permalink":"https://www.161616.top/interview-network/","summary":"域名相关 什么是DNS劫持 DNS劫持就是通过劫持了DNS服务器，通过某些手段取得某域名的解析记录控制权，进而修改此域名的解析结果，导致对该域名的访问由原IP地址转入到修改后的指定IP，其结果就是对特定的网址不能访问或访问的是假网址，从而实现窃取资料或者破坏原有正常服务的目的。DNS劫持通过篡改DNS服务器上的数据返回给用户一个错误的查询结果来实现的。\n通俗来讲：DNS劫持就是指用户访问一个被标记的地址时，DNS服务器故意将此地址指向一个错误的IP地址的行为。范例，网通、电信、铁通的某些用户有时候会发现自己打算访问一个地址，却被转向了各种推送广告等网站，这就是DNS劫持。\nDNS劫持症状：在某些地区的用户在成功连接宽带后，首次打开任何页面都指向ISP提供的“电信互联星空”、“网通黄页广告”等内容页面。还有就是曾经出现过用户访问Google域名的时候出现了百度的网站。这些都属于DNS劫持。\n解决：对于DNS劫持，可以采用使用国外免费公用的DNS服务器解决。例如OpenDNS（208.67.222.222）或GoogleDNS（8.8.8.8）。\n什么是DNS污染 DNS污染是指在DNS服务器中修改DNS解析结果的过程，以便将用户重定向到恶意网站或欺骗性网站，而不是所期望的目标网站。\n攻击者可以通过多种方式进行DNS污染攻击。最常见的手段是在用户的网络中添加一个恶意DNS服务器，或者在受感染的计算机上运行一个恶意DNS服务器。当用户试图连接到互联网上的某个网站时，计算机将查询DNS服务器以查找目标网站的IP地址。如果攻击者控制的恶意DNS服务器已将相应的IP地址修改为攻击者的网站，则用户将被重定向到恶意网站或欺骗性网站。\nDNS污染发生在用户请求的第一步上，直接从协议上对用户的DNS请求进行干扰。 DNS污染症状：目前一些被禁止访问的网站很多就是通过DNS污染来实现的，例如YouTube、Facebook等网站。\n解决：\n可靠的DNS服务器，但这种方式效果不佳 手动修改Hosts文件 使用VPN或域名远程解析 加密通信：VPN可以加密整个通信过程，这意味着攻击者无法窃取UDP数据包中的任何信息，包括DNS查询请求和响应。这样可以避免DNS查询被篡改的风险。 虚拟IP地址：VPN会给每个用户分配其所连接的虚拟IP地址，使用户的真实IP地址不会暴露在公共互联网中。这样，DNS服务器只能看到VPN服务器的IP地址，而无法识别用户的IP地址。这意味着攻击者无法跟踪用户的访问历史，从而减少遭受DNS污染攻击的风险。 总结：\nDNS污染，指的是用户访问一个地址，国内的服务器(非DNS)监控到用户访问的已经被标记地址时，服务器伪装成DNS服务器向用户发回错误的地址的行为。范例，访问Youtube、Facebook之类网站等出现的状况。\n什么是域名被墙 这种情况一般出现在解析为国外地址的域名上，假如域名下的网站非法信息多，敏感，又不整改，会直接被G.F.W墙掉，就是通常所说的被封锁、被屏蔽、被和谐，结果就是访问域名是打不开的，但是解析是正常的。此时域名在国内是无法使用的，国外可以访问和使用。\n主要有以下几种情况：\nip 被墙 解决：换 ip 。 域名被 url 重置（访问时出现ERR_CONNECTION_RESET 或 “连接重置” 换域名 做301跳转，(有专门服务商)，域名通过解析到国内301服务商，重定向到真是国外IP，以减少流量和权重的丢失。 上 https或域名备案，智能解析分国内，国外 。 可以使用HTTPS；一般来讲解析到国外的IP的域名，有敏感词会被重置，GFW可以进行敏感词检测（http为明文），使用https加密GFW无法检测数据包内容 ，（客户端与服务端默认会有公钥私钥，而GFW没有）。 域名被国家出口 dns 污染，解决：用国内 dns ，备案回国。 域名被省级 dns 污染，解决：能做到这个这里可能为内部或对应运营商被黑（只能进行dns清洗，一般大流量域名了） 什么是DNS清洗？ DNS清洗是一项旨在阻止访问特定网站和域名的措施，在该措施中，Internet服务提供商（ISP）通过其服务器筛选特定网站的DNS查询，并将查询重定向到一个错误的IP地址（通常是一个不存在的地址），从而防止用户访问该网站。这种措施通常是由政府、公司或组织实施，旨在防止用户接触到不适当、危险或非法的内容。\n网络攻击相关 什么是TCP的SYN攻击？如何预防？ TCP SYN攻击是一种利用TCP协议三次握手机制的攻击。攻击者发送大量伪造的TCP SYN请求（数据包），然后在TCP三次握手建立连接的第二步时停止（使服务器不断地向攻击者发送SYN-ACK确认，但攻击者不回复ACK确认），从而导致服务器等待客户端的确认信号很长时间，最终占用服务器的资源而无法处理新的请求。\n为了预防TCP SYN攻击，可以采取以下措施：\n服务器操作系统的设置：可以设置TCP的连接数和时间等参数，限制每个IP地址的连接数，设置连接超时时间。 防火墙设置：可以设置防火墙规则，根据IP地址、端口等信息对连接进行过滤，控制IP地址的访问等。 加强网络监测：使用入侵检测系统（IDS）对流量进行实时监控，并对异常流量进行报警处理。 使用SYN Cookies：SYN cookies是一种可以防止SYN攻击的技术，它通过特殊的算法对TCP连接进行加密，并保存在服务器端，当客户端发送响应时，服务器端可以对连接进行识别和验证，从而防止SYN攻击。 增加硬件设备：可以增加具有流量分析和过滤功能的硬件设备来协助防御SYN攻击，这种设备可以通过分析流量实现精细化的流量分析和识别。 ddos攻击的类型 DDoS攻击（Distributed Denial of Service）是一种利用许多计算机和网络设备构成的“僵尸网络”对一个或多个目标服务器发起攻击，从而占用大量的网络资源，耗尽系统资源，导致服务拒绝的攻击方式。DDoS攻击的类型可以分为以下几种：\n带宽攻击（Bandwidth-based Attack）：利用大量的数据流或报文，通过消耗目标系统的网络带宽使其服务不能正常传输。 应用层攻击（Application-Layer Attack）：利用正常流量模拟合法用户的请求，通过消耗服务器CPU和内存资源使其无法处理合法请求。 反射式攻击（Reflection Attack）：使用伪造的IP地址向网络中的一个或多个服务器发起请求，这些服务器会响应请求，但响应信息将被发回目标服务器，从而形成了一次反射式攻击。 慢速攻击（Slowloris Attack）：利用HTTP协议的设计漏洞，向目标服务器发送大量不完整请求，从而占用目标服务器处理请求的线程资源。 IoT攻击（IoT Attack）：通过侵入大量的物联网设备，如路由器、摄像头、智能家居等，利用这些设备来发起攻击，构建大规模的“僵尸网络”。 DNS Amplification攻击：攻击者向域名服务器发送请求，利用伪造的IP地址和请求报文，让服务器向目标主机发送大量的DNS解析响应数据包，从而使目标系统在短时间内遭受网络拥塞。 NTP Amplification攻击：攻击者伪造IP地址，向其余互联网上安装有网络时间服务器（Network Time Protocol，NTP）软件的服务器发送请求，从而获取大量NTP响应包，最终将其转发到目标IP地址，从而占用目标系统的网络带宽。 SYN Flood攻击：攻击者向目标服务器发送大量的TCP SYN请求，但却不发送客户端的应答确认，造成服务器长时间处于等待状态，无法接受正常的TCP连接请求。 HTTP Flood攻击：攻击者利用HTTP叠加攻击、HTTP POST攻击等手段，向目标系统发送大量HTTP请求和数据包，造成目标系统资源的耗尽，从而导致服务不可用。 ICMP Flood攻击：攻击者向目标系统发送大量的ICMP数据包，造成目标系统CPU和内存资源的消耗，从而导致系统缓慢或崩溃。 什么是反射式攻击 反射式攻击（Reflection Attack）是一种利用网络协议的设计缺陷进行攻击的方式。攻击者通常会利用一些可以进行源地址欺骗或反射的协议，例如Domain Name System（DNS），Simple Network Management Protocol（SNMP），和Network Time Protocol（NTP）等。攻击者利用这些协议在网络中进行广播，构造一些请求消息，伪造源IP地址为目标IP地址，将请求消息发送给网络上的服务器，要求其向目标IP地址回送响应。这样攻击者就可以通过伪造的IP地址对目标系统进行攻击，占用它的网络带宽和资源，极大地降低了目标系统的可用性。","title":"网络基础面试题收集"},{"content":"如何优化 Linux系统（笼统） 不用root，添加普通用户，通过sudo授权管理 更改默认的远程连接SSH服务端口及禁止root用户远程连接 定时自动更新服务器时间 配置国内yum源 关闭selinux及iptables（iptables工作场景如果有外网IP一定要打开，高并发除外） 调整文件描述符的数量 精简开机启动服务（crond rsyslog network sshd） 内核参数优化（/etc/sysctl.conf） 更改字符集，支持中文，但建议还是用英文字符集，防止乱码 锁定关键系统文件 清空/etc/issue，去除系统及内核版本登录前的屏幕显示 基础命令 ps aux 中的VSZ代表什么意思，RSS代表什么意思 VSZ:虚拟内存集,进程占用的虚拟内存空间 RSS:物理内存集,进程战用实际物理内存空间 shell下32位随机密码生成 text 1 cat /dev/urandom | head -1 | md5sum | head -c 32 \u0026gt;\u0026gt; /pass 将生成的32位随机数 保存到/pass文件里了\n统计出nginx的access.log中访问量最多的5个IP text 1 cat access_log | awk \u0026#39;{print $1}\u0026#39; | sort | uniq -c | sort -n -r | head -5 web与lb 讲述一下LVS三种模式的工作过程 LVS负载的原理，和Nginx负载有啥区别 VS/NAT：（Virtual Server via Network Address Translation）\n也就是网络地址翻译技术实现虚拟服务器，当用户请求到达调度器时，调度器将请求报文的目标地址（即虚拟IP地址）改写成选定的Real Server地址，同时报文的目标端口也改成选定的Real Server的相应端口，最后将报文请求发送到选定的Real Server。在服务器端得到数据后，Real Server返回数据给用户时，需要再次经过负载调度器将报文的源地址和源端口改成虚拟IP地址和相应端口，然后把数据发送给用户，完成整个负载调度过程。\n可以看出，在NAT方式下，用户请求和响应报文都必须经过Director Server地址重写，当用户请求越来越多时，调度器的处理能力将称为瓶颈。\nVS/TUN ：即（Virtual Server via IP Tunneling）\n也就是IP隧道技术实现虚拟服务器。它的连接调度和管理与VS/NAT方式一样，只是它的报文转发方法不同，VS/TUN方式中，调度器采用IP隧道技术将用户请求转发到某个Real Server，而这个Real Server将直接响应用户的请求，不再经过前端调度器，此外，对Real Server的地域位置没有要求，可以和Director Server位于同一个网段，也可以是独立的一个网络。因此，在TUN方式中，调度器将只处理用户的报文请求，集群系统的吞吐量大大提高。\nVS/DR： 即（Virtual Server via Direct Routing）\n也就是用直接路由技术实现虚拟服务器。它的连接调度和管理与VS/NAT和VS/TUN中的一样，但它的报文转发方法又有不同，VS/DR通过改写请求报文的MAC地址，将请求发送到Real Server，而Real Server将响应直接返回给客户，免去了VS/TUN中的IP隧道开销。这种方式是三种负载调度机制中性能最高最好的，但是必须要求Director Server与Real Server都有一块网卡连在同一物理网段上。\n回答负载调度算法，IPVS实现在八种负载调度算法，我们常用的有四种调度算法（轮叫调度、加权轮叫调度、最少链接调度、加权最少链接调度）。一般说了这四种就够了，也不会需要你详细解释这四种算法的。你只要把上面3种负载均衡技术讲明白面试官就对这道问题很满意了。\nlvs与nginx的区别： LVS的优点：\n抗负载能力强、工作在第4层仅作分发之用，没有流量的产生，这个特点也决定了它在负载均衡软件里的性能最强的；无流量，同时保证了均衡器IO的性能不会受到大流量的影响； 工作稳定，自身有完整的双机热备方案，如LVS+Keepalived和LVS+Heartbeat； 应用范围比较广，可以对所有应用做负载均衡； 配置性比较低，这是一个缺点也是一个优点，因为没有可太多配置的东西，所以并不需要太多接触，大大减少了人为出错的几率。 LVS的缺点：\n软件本身不支持正则处理，不能做动静分离，这就凸显了Nginx/HAProxy+Keepalived的优势。 如果网站应用比较庞大，LVS/DR+Keepalived就比较复杂了，特别是后面有Windows Server应用的机器，实施及配置还有维护过程就比较麻烦，相对而言，Nginx/HAProxy+Keepalived就简单一点 Nginx的优点：\n工作在OSI第7层，可以针对http应用做一些分流的策略。比如针对域名、目录结构。它的正则比HAProxy更为强大和灵活； Nginx对网络的依赖非常小，理论上能ping通就就能进行负载功能，这个也是它的优势所在； Nginx安装和配置比较简单，测试起来比较方便； 可以承担高的负载压力且稳定，一般能支撑超过几万次的并发量； Nginx可以通过端口检测到服务器内部的故障，比如根据服务器处理网页返回的状态码、超时等等，并且会把返回错误的请求重新提交到另一个节点； Nginx不仅仅是一款优秀的负载均衡器/反向代理软件，它同时也是功能强大的Web应用服务器。LNMP现在也是非常流行的web环境，大有和LAMP环境分庭抗礼之势，Nginx在处理静态页面、特别是抗高并发方面相对apache有优势； Nginx的缺点：\nNginx不支持url来检测。 Nginx仅能支持http和Email，这个它的弱势。 Nginx的Session的保持，Cookie的引导能力相对欠缺。 apache工作模式 查看apache工作模式 apachectl -l|sed -n '/worker\\|prefork/p'\nprefork使用的是多个子进程，而每个子进程只有一个线程，每个进程在某个确定的时间只能维持一个连接.\nworker模式是Apache2.X新引进来的模式，是线程与进程的结合，在worker模式下会有多个子进程，每个进程又会有多个线程。每个线程在某个确定的时间只能维持一个连接。\nevent模式：event和 worker模式很像，最大的区别在于，它解决了keep-alive场景下 ，长期被占用的线程的资源浪费问题。\nevent MPM中，会有一个专门的线程来管理这些keep-alive类型的线程，当有真实请求过来的时候，将请求传递给服务线程，执行完毕后，又允许它释放。这样，一个线程就能处理几个请求了，实现了异步非阻塞。\nevent MPM在遇到某些不兼容的模块时，会失效，将会回退到worker模式，一个工作线程处理一个请求。官方自带的模块，全部是支持eventMPM的。\n优缺点\n优点：内存占用比prefork模式低，适合高并发高流量HTTP服务。\n缺点：假如一个线程崩溃，整个进程就会连同其任何线程一起“死掉”.由于线程贡献内存空间，所以一个程序在运行时必须被系统识别为“每个线程都是安全的”服务稳定性不如prefork模式。\nhttps://github.com/PlutoaCharon/LiunxNotes/blob/master/Liunx%E9%9D%A2%E8%AF%95%E7%AC%94%E8%AE%B0/Linux%E6%9C%8D%E5%8A%A1%E7%AE%A1%E7%90%86%E7%B1%BB/Apache.md\nkubernetes Kubernetes有哪些不同类型的服务？ cluster ip Node Port Load Balancer Extrenal Name 什么是ETCD？ Etcd是用Go编程语言编写的，是一个分布式键值存储，用于协调分布式工作。因此，Etcd存储Kubernetes集群的配置数据，表示在任何给定时间点的集群状态。\n什么是Ingress网络，它是如何工作的？ Ingress网络是一组规则，充当Kubernetes集群的入口点。这允许入站连接，可以将其配置为通过可访问的URL，负载平衡流量或通过提供基于名称的虚拟主机从外部提供服务。因此，Ingress是一个API对象，通常通过HTTP管理集群中服务的外部访问，是暴露服务的最有效方式。\n什么是Headless Service？ Headless Service类似于“普通”服务，但没有群集IP。此服务使您可以直接访问pod，而无需通过代理访问它。\n什么是集群联邦？ 在联邦集群的帮助下，可以将多个Kubernetes集群作为单个集群进行管理。因此，您可以在数据中心/云中创建多个Kubernetes集群，并使用联邦来在一个位置控制/管理它们。\n联合集群可以通过执行以下两项操作来实现此目的。请参考下图。\nkube-proxy的作用 kube-proxy运行在所有节点上，它监听apiserver中service和endpoint的变化情况，创建路由规则以提供服务IP和负载均衡功能。简单理解此进程是Service的透明代理兼负载均衡器，其核心功能是将到某个Service的访问请求转发到后端的多个Pod实例上。\nkube-proxy iptables的原理 Kubernetes从1.2版本开始，将iptables作为kube-proxy的默认模式。iptables模式下的kube-proxy不再起到Proxy的作用，其核心功能：通过API Server的Watch接口实时跟踪Service与Endpoint的变更信息，并更新对应的iptables规则，Client的请求流量则通过iptables的NAT机制“直接路由”到目标Pod。\nkube-proxy ipvs的原理 IPVS在Kubernetes1.11中升级为GA稳定版。IPVS则专门用于高性能负载均衡，并使用更高效的数据结构（Hash表），允许几乎无限的规模扩张，因此被kube-proxy采纳为最新模式。\n在IPVS模式下，使用iptables的扩展ipset，而不是直接调用iptables来生成规则链。iptables规则链是一个线性的数据结构，ipset则引入了带索引的数据结构，因此当规则很多时，也可以很高效地查找和匹配。\n可以将ipset简单理解为一个IP（段）的集合，这个集合的内容可以是IP地址、IP网段、端口等，iptables可以直接添加规则对这个“可变的集合”进行操作，这样做的好处在于可以大大减少iptables规则的数量，从而减少性能损耗。\nkube-proxy ipvs和iptables的异同 iptables与IPVS都是基于Netfilter实现的，但因为定位不同，二者有着本质的差别：iptables是为防火墙而设计的；IPVS则专门用于高性能负载均衡，并使用更高效的数据结构（Hash表），允许几乎无限的规模扩张。\n与iptables相比，IPVS拥有以下明显优势：\n为大型集群提供了更好的可扩展性和性能； 支持比iptables更复杂的复制均衡算法（最小负载、最少连接、加权等）； 支持服务器健康检查和连接重试等功能； 可以动态修改ipset的集合，即使iptables的规则正在使用这个集合。 Kubernetes镜像的下载策略 Kubernetes的镜像下载策略有三种：Always、Never、IFNotPresent。\nAlways：镜像标签为latest时，总是从指定的仓库中获取镜像。 Never：禁止从仓库中下载镜像，也就是说只能使用本地镜像。 IfNotPresent：仅当本地没有对应镜像时，才从目标仓库中下载。默认的镜像下载策略是：当镜像标签是latest时，默认策略是Always；当镜像标签是自定义时（也就是标签不是latest），那么默认策略是IfNotPresent。 简述Kubernetes Scheduler使用哪两种算法将Pod绑定到worker节点 Kubernetes Scheduler根据如下两种调度算法将 Pod 绑定到最合适的工作节点：\n预选（Predicates）：输入是所有节点，输出是满足预选条件的节点。kube-scheduler根据预选策略过滤掉不满足策略的Nodes。如果某节点的资源不足或者不满足预选策略的条件则无法通过预选。如“Node的label必须与Pod的Selector一致”。 优选（Priorities）：输入是预选阶段筛选出的节点，优选会根据优先策略为通过预选的Nodes进行打分排名，选择得分最高的Node。例如，资源越富裕、负载越小的Node可能具有越高的排名。 zabbix 简述Zabbix-proxy使用场景 监控远程位置，解决跨机房 监控主机多，性能跟不上，延迟大 解决网络不稳定 zabbix 是怎么实施监控的 agentd需要安装到被监控的主机上，它负责定期收集各项数据，并发送到zabbix server端，zabbix server将数据存储到数据库中，zabbix web根据数据在前端进行展现和绘图。这里agentd收集数据分为主动和被动两种模式：\n主动：agent请求server获取主动的监控项列表，并主动将监控项内需要检测的数据提交给server/proxy\n被动：server向agent请求获取监控项的数据，agent返回数据。\n主动模式被动模式：默认为zabbix-agent被动模式\n1.被动模式（zabbix-server轮询检测zabbix-agent）\n2.主动模式（zabbix-agent主动上报给zabbix-server）优\n1.当（Queue）队列中有大量的延迟监控项\n2.当监控主机超过300+ ,建议使用主动模式\nzabbix 怎么开启自定义监控 1、写一个脚本用于获取待监控服务的一些状态信息。\n2、在zabbix客户端的配置文件zabbix_agentd.conf中添加上自定义的“UserParameter”，目的是方便zabbix调用我们上面写的那个脚本去获取待监控服务的信息。\n3、在zabbix服务端使用zabbix_get测试是否能够通过第二步定义的参数去获取zabbix客户端收集的数据。\n4、在zabbix服务端的web界面中新建模板，同时第一步的脚本能够获取什么信息就添加上什么监控项，“键值”设置成前面配置的“UserParameter”的值。\n5、数据显示图表，直接新建图形并选择上一步的监控项来生成动态图表即可。\niptables iptables四表五链（必问题） 表：\nnat 用于网络地址解析 mangle mangle包， 特定数据包的更改，好比head,content filter default表，用于过滤包 raw 优先级最高，用于pretouting和output ,使用raw表，能够跳过NAT表和ip_conntrack处理,即再也不作地址转换和数据包的连接跟踪处理了 链：\nprerouting,\ninput\nforword\noutput\npostrouting\niptables和firewalld的基本区别是什么呢？ 在linux中，防火墙的概念是 Linux 内核网络堆栈中的网络数据包进行操作的规则集合，而iptables则是这些用来操作这些规则的用户空间命令行工具，通过将规则格式化为内核数据结构转发为内核。\nfirewalld是fedora的一个开源项目，是iptables/nftables命令的封装，实现了更多动态防火墙的概念，并且加入更多用户鉴权，GUI管理等。\n类似于http是tcp的wapper，firewalld则是iptables的wapper。\n什么是iptables中的目标值（能被指定为目标），他们有什么用 下面是在iptables中可以指定为目标的值：\nACCEPT : 接受包 QUEUE : 将包传递到用户空间 (应用程序和驱动所在的地方) DROP : 丢弃包 RETURN : 将控制权交回调用的链并且为当前链中的包停止执行下一调用规则 ","permalink":"https://www.161616.top/interview-om/","summary":"如何优化 Linux系统（笼统） 不用root，添加普通用户，通过sudo授权管理 更改默认的远程连接SSH服务端口及禁止root用户远程连接 定时自动更新服务器时间 配置国内yum源 关闭selinux及iptables（iptables工作场景如果有外网IP一定要打开，高并发除外） 调整文件描述符的数量 精简开机启动服务（crond rsyslog network sshd） 内核参数优化（/etc/sysctl.conf） 更改字符集，支持中文，但建议还是用英文字符集，防止乱码 锁定关键系统文件 清空/etc/issue，去除系统及内核版本登录前的屏幕显示 基础命令 ps aux 中的VSZ代表什么意思，RSS代表什么意思 VSZ:虚拟内存集,进程占用的虚拟内存空间 RSS:物理内存集,进程战用实际物理内存空间 shell下32位随机密码生成 text 1 cat /dev/urandom | head -1 | md5sum | head -c 32 \u0026gt;\u0026gt; /pass 将生成的32位随机数 保存到/pass文件里了\n统计出nginx的access.log中访问量最多的5个IP text 1 cat access_log | awk \u0026#39;{print $1}\u0026#39; | sort | uniq -c | sort -n -r | head -5 web与lb 讲述一下LVS三种模式的工作过程 LVS负载的原理，和Nginx负载有啥区别 VS/NAT：（Virtual Server via Network Address Translation）","title":"运维类面试题收集"},{"content":"在使用 wsl 时，总是需要执行 windows 的 cmd，但是windows命令行对于大多数人使用起来还是不习惯，微软提供了在 windows 中Linux与Windows的命令互通，即可以使用cmd shell执行Linux命令，也可以使用bash shell来执行windows命令。\nWSL可对 Windows 与 Linux 之间的集成操作：\n从 Linux shell（如 Ubuntu）运行 Windows 工具（任意 .exe）。 从 Windows shell（即 PowerShell or cmd ）运行 Linux 命令（如 cd ls grep）。 在 WSL与windows之间共享环境变量。 （版本 17063+） 满足上述要求，可以很好地使用windows的软件在WSL中畅快的操作，即空WSL环境拥有了python解析器 docker等操作。\n如何在 WSL和 Windows 之间共享环境变量 从Build 17063 开始，可以利用 WSLENV 来增强 Win/WSL 之间的环境变量互操作。\n什么是WSLENV WSLENV 是一个以冒号分隔的环境变量列表，当从 WSL 启动 WSL进程或 Win进程时包含的变量 每个变量都可以以斜杠作为后缀，后跟标识位以指定它的转换方式 WSLENV 可以在 WSL 和 Win32 之间转换的路径 WSLENV。在WSL中，是以冒号分隔的列表。在Win中，是以分号分隔的列表 可以在.bashrc或者windows自定义环境变量中设置WSLENV 例如：一个WSLENV应该设置为\ntext 1 WSLENV=GOPATH/l:USERPROFILE/w:SOMEVAR/wp 在17063之前，WSL访问Windows环境变量唯一方法是使用全路径（可以使用全路径从WSL下启动Win32可执行文件）。但是没有办法在WSL中设置环境变量，调用Win进程，并期望将该变量传送到进程。\n在17063之后，引入一个名为WSLENV的特殊环境变量，以帮助WSL和Win之间的共享。 WSLENV存在于两个环境中。用户可以将WSLENV的值设置为耦合值与环境变量串联，每个都以 \\ 为标志，以指定应该如何解析该变量。例如：\n/p /p 表示应在WSL和Win32之间转换path。例如。在WSL中设置变量，将其添加到WSLENV设置/p 标志，然后在win环境cmd.exe中读取变量，该值会随着rootfs的转变而转换为对应的值。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 $ /mnt/d# export TRANSLATABLE=`pwd` $ /mnt/d# echo $TRANSLATABLE /mnt/d $ /mnt/d# export WSLENV=TRANSLATABLE\\p $ /mnt/d# export WSLENV=TRANSLATABLE/p $ /mnt/d# echo $WSLENV TRANSLATABLE/p $ /mnt/d# cmd.exe Microsoft Windows [版本 10.0.19043.1052] (c) Microsoft Corporation。保留所有权利。 D:\\\u0026gt;set TRANSLATABLE # 在windows中查看环境变量 TRANSLATABLE=D:\\ /l /l 表示该值是路径列表（如Linux的PATH）。在Linux中，是以冒号分隔的路径列表。在Win中，是以分号分隔的路径列表。/l 可以将路径列表适当对不通系统进行转换。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 $ /mnt/d# export TEMPORARY=/usr/local/go/bin:/usr/local/python/bin $ /mnt/d# WSLENV=$WSLENV:TEMPORARY/l $ /mnt/d# echo $WSLENV TRANSLATABLE/p:TEMPORARY/l $ /mnt/d# cmd.exe Microsoft Windows [版本 10.0.19043.1052] (c) Microsoft Corporation。保留所有权利。 D:\\\u0026gt;set TEMPORARY TEMPORARY=\\\\wsl$\\ubuntu1\\usr\\local\\go\\bin;\\\\wsl$\\ubuntu1\\usr\\local\\python\\bin /u /u 表示仅在Linux（WSL）中调用变量的值为 Win 类型的变量值，及windows向Linux传递环境变量，但格式不变\nbash 1 2 3 4 5 6 7 8 9 10 D:\\compose\u0026gt;set zhangsan=D:\\compose D:\\compose\u0026gt;set zhangsan zhangsan=D:\\compose D:\\compose\u0026gt;set WSLENV=zhangsan/u D:\\compose\u0026gt;wsl -d ubuntu1 $ /mnt/d/compose# echo $zhangsan D:\\compose 如需要自动适应转换，则需要 使用/up\n/w /w 表示仅在从Win调用WSL环境变量是的值，该参数并不会自动转换，如需转换一样需要使用 /wp 。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ /mnt/d/compose# export FROMWSL=/mnt/d/compose $ /mnt/d/compose# export WSLENV=FROMWSL/w $ /mnt/d/compose# cmd.exe Microsoft Windows [版本 10.0.19043.1052] (c) Microsoft Corporation。保留所有权利。 D:\\compose\u0026gt;set FROMWSL FROMWSL=/mnt/d/compose D:\\compose\u0026gt;exit $ /mnt/d/compose# export WSLENV=FROMWSL/wp $ /mnt/d/compose# cmd.exe Microsoft Windows [版本 10.0.19043.1052] (c) Microsoft Corporation。保留所有权利。 D:\\compose\u0026gt;set FROMWSL FROMWSL=D:\\compose 使用脚本传递变量 如果需要BASH脚本传递对应的变量到windows程序执行，例如\nbash 1 2 3 4 5 #!/bin/bash export MYPATH=/mnt/c/Users/ WSLENV=$WSLENV:MYPATH/p cmd.exe /c set MYPATH 通过WSL shell环境执行，可以得到windows程序处理的结果，并且可以拿到环境变量\nbash 1 2 $ /mnt/d/compose# bash 1.sh MYPATH=C:\\Users\\ 实例：设置一个开发环境，使其共享环境变量 例如，希望在WSL中设置DEV环境。使用WSLENV VAR，将其配置为在WSL和Win之间共享GoPath。\n安装golang 首先，我们需要安装两个平台。要在Windows与WSL安装，步骤不说了。（如果是python等解析语言，可以使用alias直接使用windows的解析器则不需要安装了）\n设置项目 接下来，需要配置的GO项目。该项目需要在Windows文件系统下。在PowerShell中发出以下命令：(这里在桌面配置的)\nbash 1 2 3 mkdir $env:USERPROFILE\\desktop\\goProject cd $env:USERPROFILE\\desktop\\goProject New-Item hello.go 配置环境变量，然后将gopath添加到WSLENV，此时，两个文件系统间，会使用同一个GOPATH\nbash 1 2 setx GOPATH \u0026#34;$env:USERPROFILE\\desktop\\goProject\u0026#34; setx WSLENV \u0026#34;$env:WSLENV:GOPATH\u0026#34;/p 需要事项\nWSL（通过.profile或其他）中的定义将在通过WSL访问时覆盖默认WSLENV中定义的值。 在关闭WSL后，WSLENV不会持久化，需要修改相应的配置文件（.profile，.bash_rc等）。 WSL可以设置任何值。如果仅设置当前文件系统变量，则不会自动转换。通过WSLENV可以自动翻译成两种不通的文件系统下的环境变量。 题外话cmd.exe 跨文件系统常用参数 options describe /C 使用cmd.exe运行一个命令并终止，类似于 bash -c Reference 更多cmd.exe帮助参考\ncmd_helps\nWSL备份及windows Docker安装\nWSL安装维护\n","permalink":"https://www.161616.top/wsl-share-to-win/","summary":"在使用 wsl 时，总是需要执行 windows 的 cmd，但是windows命令行对于大多数人使用起来还是不习惯，微软提供了在 windows 中Linux与Windows的命令互通，即可以使用cmd shell执行Linux命令，也可以使用bash shell来执行windows命令。\nWSL可对 Windows 与 Linux 之间的集成操作：\n从 Linux shell（如 Ubuntu）运行 Windows 工具（任意 .exe）。 从 Windows shell（即 PowerShell or cmd ）运行 Linux 命令（如 cd ls grep）。 在 WSL与windows之间共享环境变量。 （版本 17063+） 满足上述要求，可以很好地使用windows的软件在WSL中畅快的操作，即空WSL环境拥有了python解析器 docker等操作。\n如何在 WSL和 Windows 之间共享环境变量 从Build 17063 开始，可以利用 WSLENV 来增强 Win/WSL 之间的环境变量互操作。\n什么是WSLENV WSLENV 是一个以冒号分隔的环境变量列表，当从 WSL 启动 WSL进程或 Win进程时包含的变量 每个变量都可以以斜杠作为后缀，后跟标识位以指定它的转换方式 WSLENV 可以在 WSL 和 Win32 之间转换的路径 WSLENV。在WSL中，是以冒号分隔的列表。在Win中，是以分号分隔的列表 可以在.bashrc或者windows自定义环境变量中设置WSLENV 例如：一个WSLENV应该设置为\ntext 1 WSLENV=GOPATH/l:USERPROFILE/w:SOMEVAR/wp 在17063之前，WSL访问Windows环境变量唯一方法是使用全路径（可以使用全路径从WSL下启动Win32可执行文件）。但是没有办法在WSL中设置环境变量，调用Win进程，并期望将该变量传送到进程。","title":"WSL与Windows环境共享"},{"content":"因为在拷贝web站点时，也会存在更新，需要定期覆盖新的内容，就是上次覆盖的时间和到这次时间内修改过的文件都复制。\n实现命令xcopy\ntext 1 2 3 4 5 6 xcopy src dest $ xcopy D:\\WWW\\back1\\* D:\\WWW\\back4 /D:05-22-2018 /F /E /y D:\\WWW\\back1\\db_qbe.php -\u0026gt; D:\\WWW\\back4\\db_qbe.php D:\\WWW\\back1\\docs.css -\u0026gt; D:\\WWW\\back4\\docs.css D:\\WWW\\back1\\test\\changelog.php -\u0026gt; D:\\WWW\\back4\\test\\changelog.php 复制了 3 个文件 /D:mm-dd-yyyy\n/F 打印复制过程\n/E 递归复制目录和子目录包括空目录\n/Y 禁止提示\n","permalink":"https://www.161616.top/recursive-replication-with-dos/","summary":"因为在拷贝web站点时，也会存在更新，需要定期覆盖新的内容，就是上次覆盖的时间和到这次时间内修改过的文件都复制。\n实现命令xcopy\ntext 1 2 3 4 5 6 xcopy src dest $ xcopy D:\\WWW\\back1\\* D:\\WWW\\back4 /D:05-22-2018 /F /E /y D:\\WWW\\back1\\db_qbe.php -\u0026gt; D:\\WWW\\back4\\db_qbe.php D:\\WWW\\back1\\docs.css -\u0026gt; D:\\WWW\\back4\\docs.css D:\\WWW\\back1\\test\\changelog.php -\u0026gt; D:\\WWW\\back4\\test\\changelog.php 复制了 3 个文件 /D:mm-dd-yyyy\n/F 打印复制过程\n/E 递归复制目录和子目录包括空目录\n/Y 禁止提示","title":"windows递归复制指定时间后修改过的文件"},{"content":"venv模块支持使用自己的站点目录创建轻量级“虚拟环境”，可选择与系统站点目录隔离。每个虚拟环境都有自己的Python二进制文件（与用于创建此环境的二进制文件的版本相匹配），并且可以在其站点目录中拥有自己独立的已安装 Python 软件包集。\n3.6 版后已移除: pyvenv 是 Python 3.3 和 3.4 中创建虚拟环境的推荐工具，不过 在 Python 3.6 中已弃用。\n在 3.5 版更改: 现在推荐使用 venv 来创建虚拟环境。\n创建venv虚拟环境 如果使用python2，则需要安装virtualenv模块\ntext 1 2 pip install virtualenv python -m virtualenv {name} python3内置了 venv 模块，可以直接使用\ntext 1 python3 -m venv {name} 进入虚拟环境\nlinux\ntext 1 venv\\Scripts\\activate windows\ntext 1 venv\\Scripts\\activate.bat 退出环境\ntext 1 2 venv\\Scripts\\deactivate.bat venv\\Scripts\\deactivate 使用venv环境安装软件报错 Could not fetch URL https://pypi.org/simple/pip/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host=\u0026lsquo;pypi.org\u0026rsquo;, port=443): Max retries exceeded with url: /simple/pip/ (Caused by SSLError(SSLEOFError(8, u\u0026rsquo;EOF occurred in violation of protocol (_ssl.c:727)\u0026rsquo;),)) - skipping\n查询很多都无法解决，最后发现有文章提到这是因为开启了climb wall软件导致的，关闭后恢复正常\n","permalink":"https://www.161616.top/python-venv/","summary":"venv模块支持使用自己的站点目录创建轻量级“虚拟环境”，可选择与系统站点目录隔离。每个虚拟环境都有自己的Python二进制文件（与用于创建此环境的二进制文件的版本相匹配），并且可以在其站点目录中拥有自己独立的已安装 Python 软件包集。\n3.6 版后已移除: pyvenv 是 Python 3.3 和 3.4 中创建虚拟环境的推荐工具，不过 在 Python 3.6 中已弃用。\n在 3.5 版更改: 现在推荐使用 venv 来创建虚拟环境。\n创建venv虚拟环境 如果使用python2，则需要安装virtualenv模块\ntext 1 2 pip install virtualenv python -m virtualenv {name} python3内置了 venv 模块，可以直接使用\ntext 1 python3 -m venv {name} 进入虚拟环境\nlinux\ntext 1 venv\\Scripts\\activate windows\ntext 1 venv\\Scripts\\activate.bat 退出环境\ntext 1 2 venv\\Scripts\\deactivate.bat venv\\Scripts\\deactivate 使用venv环境安装软件报错 Could not fetch URL https://pypi.org/simple/pip/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host=\u0026lsquo;pypi.","title":"python使用虚拟环境venv"},{"content":" 工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 ##AWK运算符\n运算符 说明 赋值运算符 = += -= *= /= %= ^= **= 逻辑运算符 || 逻辑或 \u0026amp;\u0026amp; 逻辑与 正则运算符 ~ !~ 匹配正则表达式和不匹配正则表达式 关系运算符 \u0026lt; \u0026lt;= \u0026gt; \u0026gt;= != == 关系运算符 算术运算符 + - 加，减 *** / \u0026amp;** 乘，除与求余 + - ! 一元加，减和逻辑非 ^ *** 求幂 ++ \u0026ndash; 增加或减少，作为前缀或后缀 其他运算符 $ 字段引用 空格 字符串链接符 ?: 三目运算符 In 数组中是否存在某键值 内置变量 变量名 属性 $0 当前记录 1 n 当前记录的第 n 个字段 FS 输入字段分隔符 默认是空格 RS 输入记录分割符 默认为换行符 NF 当前记录中的字段个数，就是有多少列 NR 已经读出的记录数，就是行号，从 1 开始 OFS 输出字段分隔符 默认也是空格 ORS 输出的记录分隔符 默认为换行符 特殊模式 - - BEGIN awk 将在读取任何输入行之前立即执行BEGIN 中指定的动作 END awk 将在它正式退出前执行 END中指定的动作 用法 去掉空白：awk 'NF' file\n统计行数： awk 'END{print NR}' file (END)\n偶数行：awk 'NR%2==0 {print $n}' file\n奇数行：awk 'a=!a' file\n指定分隔符： awk -F \u0026quot;:\u0026quot; '{print $1}' file\n使用正则： awk '/^tecmint.com/ { counter+=1 ; printf \u0026quot;%s\\n\u0026quot;, counter ; }' file\n打印多列：awk -F \u0026quot;:\u0026quot; '{print $1 $2 .. $(NF-1) $NF}' /etc/passwd\n多分隔符的用法：echo i am a protester,myqq is 1112222|awk -F '[, ]' '{print $4 \u0026quot; \u0026quot; $7}'\n多个分隔符使用正则：awk -F\u0026quot;/|=\u0026quot; '{print $3, $5, $NF}' file\n使用[]作为分隔符：awk -F '[][]' '{print $3;}' data (这里[ ] 分别占用两列 2个$)\n获取以 []内的值： awk -F '[][*:]' '{print $8}'\ntext 1 echo \u0026#34;[Remote_ip:10.41.58.88] [Remote_user:-] [Querytime:12/Nov/2021:15:50:11 +0800] [Request_url:POST /zeusweb-1/index.php?r=task/ws\u0026amp;ws=1 HTTP/1.1] [Request_status:200] [Request_byte_B:1080] [Request_time_s:4.375] [Http_referer:-] [Http_agent:PHP-SOAP/5.4.25]\u0026#34;|awk -F \u0026#39;[][*:]\u0026#39; \u0026#39;{print $8}\u0026#39; 也可以使用 `awk -F '[][*:]' '{print $8}'`\rtext 1 2 3 [ Remote_ip:10.41.58.88 ] [ Remote_user: - ] [ Querytime : 12/Nov/2021:15:50:11 +0800 ] ^ ^^^^^^^^^ ^^^^^^^^^^^ ^ ^^^^^^^^^^^ ^ ^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ 1 2 3 4 5 6 7 8 9 10 ","permalink":"https://www.161616.top/awesome-awk-command/","summary":"工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 ##AWK运算符\n运算符 说明 赋值运算符 = += -= *= /= %= ^= **= 逻辑运算符 || 逻辑或 \u0026amp;\u0026amp; 逻辑与 正则运算符 ~ !~ 匹配正则表达式和不匹配正则表达式 关系运算符 \u0026lt; \u0026lt;= \u0026gt; \u0026gt;= != == 关系运算符 算术运算符 + - 加，减 *** / \u0026amp;** 乘，除与求余 + - ! 一元加，减和逻辑非 ^ *** 求幂 ++ \u0026ndash; 增加或减少，作为前缀或后缀 其他运算符 $ 字段引用 空格 字符串链接符 ?","title":"awk常用案例"},{"content":"P2P 概述 相比于 C/S B/S 架构来说， P2P 是由 Peer 组成。每个Peer同时是客户端也是服务器。这意味着，P2P网络中的peer点为每个其他的peer提供服务，所有节点直接相互通信，没有中心节点，并共享资源源相互联系。\nP2P有结构化的P2P网络和非结构化P2P网络。如TomP2P (Java的一个框架，一个分布式哈希表，提供去中心化的键/值基础设施）。而Gnutella (第一个分散式P2P文件共享网络)，是非结构化 （unstructured）P2P网络的。另外还有两种类型的P2P网络，即集中式（centralized）P2P网络（Napster）和混合（hybrid p2p）peer网络（如Skype）。\nNAT网络 由于NAT的网络模型，破坏了主机 Peer之间的端到端连接，因此P2P网络需要穿过NAT网络，而穿过NAT网络是目前为P2P技术面临的一个很大的挑战。\n网络地址转换，NAT （Network Address Translation）是一种模糊指明的机制，可以将两个IP连接在一起，一个NAT设备总是拥有至少两个IP地址，（公网IP，私网IP），NAT就是将数据包上的IP地址在传输时，将私网IP转换为公网IP。每个NAT设备会维护一个NAT表，该表存储了所有活动的连接。\n在创建网络映射后，NAT将源IP地址和端口更改为外部源IP地址和端口。NAT保留端口，不将新端口分配给外部源。一旦创建了映射，只要映射存在，与之联系的设备就能够发回消息。不存在NAT映射的所有来自外部的通信请求都是无法穿越NAT。因此，在P2P环境中，如果两个peer位于在NAT之后，两者都无法直接联系，因为它们之间不知道外部IP地址和源端口，NAT表中并没有其所维护的映射信息。所以NAT穿越中的主要问题之一是网络地址转换问题。\nNAT网络类型 一般来讲， NAT网络可以分为四种类型: nat type\n全锥型(Full Cone) 受限锥型(Restricted Cone)， 或者说是IP受限锥型 端口受限锥型(Port Restricted Cone), 或者说是IP + PORT受限锥型 对称型(Symmetric) Full Cone NAT 全锥形 全锥形网络（Full Cone NAT） 的工作原理类似于 IP 地址一对一映射。 内部 IP 和端口映射到相同的外部地址和端口。 之后，任何外部源都可以通过向外部地址发送数据包来访问内部主机。 这意味着，一旦创建了映射，任何外部主机都可以联系内部主机。如下图\n地址受限形的锥形NAT Address Restricted Cone NAT 地址受限形锥形 NAT是，仅当内部主机先联系外部主机时，受限锥形 NAT 才会为相应的内部主机分配外部IP。 外部主机然后能够通过分配的外部地址联系内部主机。\n对称型 Symmetric nat 对称 NAT 是最难穿过的NAT，因为对称将随机端口分配给映射。 如果外部主机首先与内部主机连接，则外部主机只能知道外部映射。 在 P2P 场景中， 如果两个Peer（主机）位于 NAT 后面，则它们无法互相通信以让另一个对等体知道所使用的映射。 此外，对称 NAT 几乎不可能知道分配的端口以通过打孔（hole Punching）建立连接。\n反向连接 Reverse Connection 反向连接（Reverse Connection）是一种通讯机制，它允许不使用防火墙或 NAT 设备的Peer使用中继（或服务器）连接到另一个使用防火墙（或不带 UPnP、NAT-PmP 或类似机制的 NAT 设备）的Peer建立直接连接。该机制的工作原理如下。设 A 和 B 为网络设备。 A 没有使用任何类型的防火墙，而 B 使用防火墙。假设也是服务器（或中继）S\n假设 A 和 B 为网络设备。 A 没有使用任何类型的防火墙，而 B 使用防火墙（或也是服务器、中继）S。A要连接到B。A首先向S发送连接建立请求，然后S使用与A已经建立的连接将此消息转发给 B, 一旦 B 与 S 建立连接，S 开始帮助B连接到 A。在 A 和 B 都完成连接后，尽管 B 使用防火墙（或 NAT 设备），但 A 已经能够与B形成了 Peer to Peer直接通信。\n反向连接设置的优点是两个网络设备即使使用了防火墙（或 NAT 设备）也能够进行通信。但是这种机制有各种限制。首先，仅允许两个设备中的一个使用防火墙（或 NAT 设备）。其次，防火墙后面的设备（或 NAT 设备）必须像中继一样连接到外部主机 或服务器。第三，不使用任何防火墙的设备（或NAT设备）需要知道防火墙后面设备的地址，并且需要能够连接到已经连接到防火墙后面设备（或NAT设备）的主机）。\n仅当所有前面提到的限制都适用时，反向连接才可用。但是，由于当今许多家庭宽带都在使用 NAT 和防火墙，因此这不是连接位于 NAT 设备或防火墙后面的 P2P 网络的两个Peer的可靠的选择。\nUPnP 在当今的家庭宽带网络中，在很多情况下，两端的Peer都使用了 NAT 设备，无法建立连接。 而 UPnP 通用即插即用（Universal Plug and Play），即使为了解决此网络环境下的一种网络架构，从而可以使用P2P网络。简而言之，UPnP 技术的工作过程如下：\n寻址 （Addressing）：使每个设备都获得一个 IP 地址。 发现 （Discovery）：UPnP Control Point，通知所有设备或其他所有设备通知其他们的存在。 描述（Description）：UPnP Control Point 学习其他设备的能力。 控制（Control）：UPnP Control Point 向设备发送命令。 事件（Eventing）：UPnP Control Point 监听设备的状态的变化。 展示（Presentation）：UPnP Control Point 显示设备的用户界面。 UPnP特点：\n能够在任何类型的 NAT 或防火墙阻止的情况下进行通信 可能无法在路由器和服务器上启用 不提供身份验证机制 不支持多层 NAT NAT端口映射协议 NAT端口映射协议（NAT Port Mapping Protocol，简写**NAT-PMP**）是苹果公司于 2005 年开发的 NAT 。该协议是 NAT 设备的扩展。\n打洞 Hole Punching 打洞（Hole Punching）是可以使NAT 后面的两个网络设备通过让另一个设备知道它们的私有断定与公共端点之间的映射信息来相互联系。 因此，它属于基于的NAT打洞机制。 必要条件是需要存在一个第三方网络设备，该设备可以获得两设备间的信息，并进行信息交换的服务器。 Hole Puching的工作原理如下:\nHost A 和 Host B 都向Host C(中继器) 发送 UDP 数据包。当数据包通过它们的 NAT 时，NAT 将源 IP 地址重写为其公网可访问的 IP 地址。它也可能重写源端口号，在这种情况下，UDP 打孔几乎是不可能的。 C 记录来自 Host A 和Host B 的传请求的 IP 与端口。 C 将两者（Host A、B）信息交换 （告诉A B的IP端口，告诉B A的IP端口） Host A 和Host B 的第一个数据包在进入彼此的 NAT设备 时被拒绝。然而，当数据包从 A 的 NAT 在端口X传递到 B 的 NAT 时，NAT A 注意到了它，因此在其防火墙上打了一个洞，以允许来自 B 的 NAT 的 IP 的传入数据包，从端口 X . B 的 NAT 也会发生同样的情况，它制定了一个规则，允许来自 A 的 NAT 的 IP 地址的数据包从端口 Y传入。 完成时，当 Host A 和 Host B 相互发送数据包时，此时会被接受，即完成了P2P网络。 Reference 打洞\nP2P技术详解(三)：P2P中的NAT穿越(打洞)方案详解(进阶分析篇)\n","permalink":"https://www.161616.top/understand-hole-punchine-mechnism/","summary":"P2P 概述 相比于 C/S B/S 架构来说， P2P 是由 Peer 组成。每个Peer同时是客户端也是服务器。这意味着，P2P网络中的peer点为每个其他的peer提供服务，所有节点直接相互通信，没有中心节点，并共享资源源相互联系。\nP2P有结构化的P2P网络和非结构化P2P网络。如TomP2P (Java的一个框架，一个分布式哈希表，提供去中心化的键/值基础设施）。而Gnutella (第一个分散式P2P文件共享网络)，是非结构化 （unstructured）P2P网络的。另外还有两种类型的P2P网络，即集中式（centralized）P2P网络（Napster）和混合（hybrid p2p）peer网络（如Skype）。\nNAT网络 由于NAT的网络模型，破坏了主机 Peer之间的端到端连接，因此P2P网络需要穿过NAT网络，而穿过NAT网络是目前为P2P技术面临的一个很大的挑战。\n网络地址转换，NAT （Network Address Translation）是一种模糊指明的机制，可以将两个IP连接在一起，一个NAT设备总是拥有至少两个IP地址，（公网IP，私网IP），NAT就是将数据包上的IP地址在传输时，将私网IP转换为公网IP。每个NAT设备会维护一个NAT表，该表存储了所有活动的连接。\n在创建网络映射后，NAT将源IP地址和端口更改为外部源IP地址和端口。NAT保留端口，不将新端口分配给外部源。一旦创建了映射，只要映射存在，与之联系的设备就能够发回消息。不存在NAT映射的所有来自外部的通信请求都是无法穿越NAT。因此，在P2P环境中，如果两个peer位于在NAT之后，两者都无法直接联系，因为它们之间不知道外部IP地址和源端口，NAT表中并没有其所维护的映射信息。所以NAT穿越中的主要问题之一是网络地址转换问题。\nNAT网络类型 一般来讲， NAT网络可以分为四种类型: nat type\n全锥型(Full Cone) 受限锥型(Restricted Cone)， 或者说是IP受限锥型 端口受限锥型(Port Restricted Cone), 或者说是IP + PORT受限锥型 对称型(Symmetric) Full Cone NAT 全锥形 全锥形网络（Full Cone NAT） 的工作原理类似于 IP 地址一对一映射。 内部 IP 和端口映射到相同的外部地址和端口。 之后，任何外部源都可以通过向外部地址发送数据包来访问内部主机。 这意味着，一旦创建了映射，任何外部主机都可以联系内部主机。如下图\n地址受限形的锥形NAT Address Restricted Cone NAT 地址受限形锥形 NAT是，仅当内部主机先联系外部主机时，受限锥形 NAT 才会为相应的内部主机分配外部IP。 外部主机然后能够通过分配的外部地址联系内部主机。\n对称型 Symmetric nat 对称 NAT 是最难穿过的NAT，因为对称将随机端口分配给映射。 如果外部主机首先与内部主机连接，则外部主机只能知道外部映射。 在 P2P 场景中， 如果两个Peer（主机）位于 NAT 后面，则它们无法互相通信以让另一个对等体知道所使用的映射。 此外，对称 NAT 几乎不可能知道分配的端口以通过打孔（hole Punching）建立连接。","title":"P2P打洞技术"},{"content":"什么是信号 信号（signal）\u0026ndash; 进程间通讯的一种方式，也可作为一种软件中断的方法。一个进程一旦接收到信号就会打断原来的程序执行来按照信号进行处理。\n简化术语，信号是一个事件，用于中断运行功能的执行。信号始终在主Python线程中执行。对于信号，这里不做详细介绍。\nPython封装了操作系统的信号功能的库 singal 的库。singal 库可以使我们在python程序中中实现信号机制。\nhttps://zh.wikipedia.org/wiki/Unix%E4%BF%A1%E5%8F%B7)\nPython的信号处理 首先需要了解Python为什么要提供 signal Library。信号库使我们能够使用信号处理程序，以便当接收信号时都可以执行自定义任务。\nMission：当接收到信号时执行信号处理方法\n可以通过使用 signal.singal() 函数来实现此功能\nPython对信号的处理 通常情况下Python 信号处理程序总是会在主 Python 主解析器的主线程中执行，即使信号是在另一个线程中接收的。 这意味着信号不能被用作线程间通信的手段。 你可以改用 threading 模块中的同步原语。\nPython信号处理流程，需要对信号处理程序（signal handling ）简要说明。signal handling 是一个任务或程序，当检测到特定信号时，处理函数需要两个参数，即信号id signal number （Linux 中 1-64），与堆栈帧 frame。通过相应信号启动对应 signal handling ，signal.signal() 将为信号分配 处理函数。\n如：当运行一个脚本时，取消，此时是捕获到一个信号，可以通过捕获信号方式对程序进行异步的优雅处理。通过将信号处理程序注册到应用程序中：\npy 1 2 3 4 5 6 7 8 9 10 11 import signal import time def handler(a, b): # 定义一个signal handling print(\u0026#34;Signal Number:\u0026#34;, a, \u0026#34; Frame: \u0026#34;, b) signal.signal(signal.SIGINT, handler) # 将handle分配给对应信号 while True: print(\u0026#34;Press ctrl + c\u0026#34;) time.sleep(10) 如果不对对应信号进行捕获处理时，python将会抛出异常。\ntext 1 2 3 4 5 $ python signal.py ^CTraceback (most recent call last): File \u0026#34;signal.py\u0026#34;, line 3, in \u0026lt;module\u0026gt; while True: KeyboardInterrupt 信号枚举 信号的表现为一个int，Python的信号库有对应的信号枚举成员\n其中常用的一般有，\nSIGINT control+c\nSIGTERM 终止进程 软件终止信号\nSIGKILL 终止进程 杀死进程\nSIGALRM 超时\n信号 说明 SIG_DFL SIG_IGN 标准信号处理程序，它将简单地忽略给定的信号 SIGABRT SIGIOT 来自 abort 的中止信号。\nabort 导致异常进程终止。通常由检测内部错误或严重破坏约束的库函数调用。例如，如果堆的内部结构被堆溢出损坏，malloc()将调用abort() SIGALRMSIGVTALRM SIGPROF 如果你用 setitimer 这一类的报警设置函数设置了一个时限，到达时限时进程会接收到 SIGALRM, SIGVTALRM 或者 SIGPROF。但是这三个信号量的含义各有不同，SIGALRM 计时的是真实时间，SIGVTALRM计时的是进程使用了多少CPU时间，而 SIGPROF 计时的是进程和代表该进程的内核用了多少时间。 SIGBUS 总线发生错误时，进程接收到一个SIGBUS信号。举例来说，存储器访问对齐或者或不存在对应的物理地址都会产生SIGBUS信号。 SIGCHLD 当子进程终止、被中断或被中断后恢复时，SIGCHLD信号被发送到进程。该信号的一个常见用法是指示操作系统在子进程终止后清理其使用的资源，而不显式调用等待系统调用。 SIGILL 非法指令。当进程试图执行非法、格式错误、未知或特权指令时，SIGILL信号被发送到该进程。 SIGKILL 发送SIGKILL信号到一个进程可以使其立即终止(KILL)。与SIGTERM和SIGINT相不同的是，这个信号不能被捕获或忽略，接收过程在接收到这个信号时不能执行任何清理。 以下例外情况适用: SIGINT 来自键盘的中断 (CTRL + C)。KeyboardInterrupt SIGPIPE 当一个进程试图写入一个没有连接到另一端进程的管道时，SIGPIPE信号会被发送到该进程。 **SIGTERM ** 终结信号。 KILL -15 |KILL SIGUSR1\nSIGUSR2 用户自定义信号 SIGWINCH 终端窗口大小已变化 SIGHUP 在控制终端上检测到挂起或控制进程的终止。 Reference：[signal-wikipedia](\n信号函数 Python的信号库中也有很多常用的函数\nsignal.alarm(time) 创建一个 SIGALRM 类型的信号，time为预定的时间，设置为0时取消先前设置的定时器\nsignal.pause() 可以使代码逻辑处理过程睡眠，直到收到信号，然后调用对应的handler。\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 import signal import os import time def do_exit(sig, stack): raise SystemExit(\u0026#39;Exiting\u0026#39;) signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGUSR1, do_exit) print(\u0026#39;My PID:\u0026#39;, os.getpid()) signal.pause() 在执行时，忽略了ctrl + c的信号，对USR1做退出操作\nsignal.setitimer(which, seconds, interval) which： signal.ITIMER_REAL，signal.ITIMER_VIRTUAL 或 signal.ITIMER_PROF\nseconds：多少秒后触发which。seconds设置为0可以清除which的计时器。\ninterval：每隔interval秒后触发一次\nos.getpid() 获得当前执行程序的pid\nWindows下信号的使用 在Linux中，可以通过任何可接受的信号枚举值作为信号函数的参数。在Windows中，SIGABRT, SIGFPE, SIGINT, SIGILL, SIGSEGV, SIGTERM, SIGBREAK。\n当signal handling需要参数怎么办 在一些时候，signal handling的操作需要对应主进程传递进来一些函数，而在整个项目中执行过程中的变量与 signal handling不处于一个作用域中，而signal.signal() 不能传递其他的参数，这个时候可以使用 partial 创建一个闭包来解决这个问题。\n例如：\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import signal import os import sys import time from functools import partial \u0026#34;\u0026#34;\u0026#34; 这里signal frame默认参数需要放到最后 \u0026#34;\u0026#34;\u0026#34; def signal_handler(test_parameter1, test_parameter2, signal_num, frame): print \u0026#34;signal {} exit. {} {}\u0026#34;.format(signal_num, test_parameter1, test_parameter2) sys.exit(1) a=1 b=2 signal.signal(signal.SIGINT, partial(signal_handler, a, b) ) print(\u0026#39;My PID:\u0026#39;, os.getpid()) signal.pause() 忽略信号 signal定义了忽略接收信号的方法。为了实现信号的处理，需要使用signal.signal() 将默认的信号与signal.SIG_IGN 注册，即可忽略对应的信号中断，kill -9 不可忽略 。\npython 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import signal import os import time def receiveSignal(signalNumber, frame): print(\u0026#39;Received:\u0026#39;, signalNumber) raise SystemExit(\u0026#39;Exiting\u0026#39;) return if __name__ == \u0026#39;__main__\u0026#39;: # register the signal to be caught signal.signal(signal.SIGUSR1, receiveSignal) # register the signal to be ignored signal.signal(signal.SIGINT, signal.SIG_IGN) # output current process id print(\u0026#39;My PID is:\u0026#39;, os.getpid()) signal.pause() 常用的信号 python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import signal import os import time import sys def readConfiguration(signalNumber, frame): print (\u0026#39;(SIGHUP) reading configuration\u0026#39;) return def terminateProcess(signalNumber, frame): print (\u0026#39;(SIGTERM) terminating the process\u0026#39;) sys.exit() def receiveSignal(signalNumber, frame): print(\u0026#39;Received:\u0026#39;, signalNumber) return signal.signal(signal.SIGHUP, readConfiguration) signal.signal(signal.SIGINT, receiveSignal) signal.signal(signal.SIGQUIT, receiveSignal) signal.signal(signal.SIGILL, receiveSignal) signal.signal(signal.SIGTRAP, receiveSignal) signal.signal(signal.SIGABRT, receiveSignal) signal.signal(signal.SIGBUS, receiveSignal) signal.signal(signal.SIGFPE, receiveSignal) #signal.signal(signal.SIGKILL, receiveSignal) signal.signal(signal.SIGUSR1, receiveSignal) signal.signal(signal.SIGSEGV, receiveSignal) signal.signal(signal.SIGUSR2, receiveSignal) signal.signal(signal.SIGPIPE, receiveSignal) signal.signal(signal.SIGALRM, receiveSignal) signal.signal(signal.SIGTERM, terminateProcess) ","permalink":"https://www.161616.top/python-signal-handle/","summary":"什么是信号 信号（signal）\u0026ndash; 进程间通讯的一种方式，也可作为一种软件中断的方法。一个进程一旦接收到信号就会打断原来的程序执行来按照信号进行处理。\n简化术语，信号是一个事件，用于中断运行功能的执行。信号始终在主Python线程中执行。对于信号，这里不做详细介绍。\nPython封装了操作系统的信号功能的库 singal 的库。singal 库可以使我们在python程序中中实现信号机制。\nhttps://zh.wikipedia.org/wiki/Unix%E4%BF%A1%E5%8F%B7)\nPython的信号处理 首先需要了解Python为什么要提供 signal Library。信号库使我们能够使用信号处理程序，以便当接收信号时都可以执行自定义任务。\nMission：当接收到信号时执行信号处理方法\n可以通过使用 signal.singal() 函数来实现此功能\nPython对信号的处理 通常情况下Python 信号处理程序总是会在主 Python 主解析器的主线程中执行，即使信号是在另一个线程中接收的。 这意味着信号不能被用作线程间通信的手段。 你可以改用 threading 模块中的同步原语。\nPython信号处理流程，需要对信号处理程序（signal handling ）简要说明。signal handling 是一个任务或程序，当检测到特定信号时，处理函数需要两个参数，即信号id signal number （Linux 中 1-64），与堆栈帧 frame。通过相应信号启动对应 signal handling ，signal.signal() 将为信号分配 处理函数。\n如：当运行一个脚本时，取消，此时是捕获到一个信号，可以通过捕获信号方式对程序进行异步的优雅处理。通过将信号处理程序注册到应用程序中：\npy 1 2 3 4 5 6 7 8 9 10 11 import signal import time def handler(a, b): # 定义一个signal handling print(\u0026#34;Signal Number:\u0026#34;, a, \u0026#34; Frame: \u0026#34;, b) signal.","title":"python中的signal"},{"content":"记录一下，接了一个python2 django1.x的项目，很老了导致很多扩展无法安装\nos version：macos catalina python version: 2.7.18\n而django后端使用sqllite以外需要对应客户端引擎，而安装时编译依赖C客户端即实际mysql组件。\n使用的数据库后端。 内建的数据库后端有：\n\u0026lsquo;django.db.backends.postgresql\u0026rsquo; \u0026lsquo;django.db.backends.mysql\u0026rsquo; \u0026lsquo;django.db.backends.sqlite3\u0026rsquo; \u0026lsquo;django.db.backends.oracle\u0026rsquo;\n并且修改配置实例\ntext 1 2 3 4 5 6 7 8 9 10 DATABASES = { \u0026#39;default\u0026#39;: { \u0026#39;ENGINE\u0026#39;: \u0026#39;django.db.backends.mysql\u0026#39;, \u0026#39;USER\u0026#39;: \u0026#39;mydatabaseuser\u0026#39;, \u0026#39;NAME\u0026#39;: \u0026#39;mydatabase\u0026#39;, \u0026#39;TEST\u0026#39;: { \u0026#39;NAME\u0026#39;: \u0026#39;mytestdatabase\u0026#39;, }, }, } brew unlink mysql\nerror: command \u0026lsquo;gcc\u0026rsquo; failed with exit status 1 text 1 2 3 4 5 6 creating build/temp.macosx-10.9-x86_64-2.7 gcc -fno-strict-aliasing -fno-common -dynamic -arch x86_64 -g -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -Dversion_info=(1,2,5,\u0026#39;final\u0026#39;,1) -D__version__=1.2.5 -I/usr/local/Cellar/mysql@5.7/5.7.32/include/mysql -I/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7 -c _mysql.c -o build/temp.macosx-10.9-x86_64-2.7/_mysql.o gcc -bundle -undefined dynamic_lookup -arch x86_64 -g build/temp.macosx-10.9-x86_64-2.7/_mysql.o -L/usr/local/Cellar/mysql@5.7/5.7.32/lib -lmysqlclient -lssl -lcrypto -o build/lib.macosx-10.9-x86_64-2.7/_mysql.so ld: library not found for -lssl clang: error: linker command failed with exit code 1 (use -v to see invocation) error: command \u0026#39;gcc\u0026#39; failed with exit status 1 解决方法：\ntext 1 2 3 4 5 6 # Required for mysqlclient, see brew info openssl echo \u0026#39;export PATH=\u0026#34;/usr/local/opt/openssl/bin:$PATH\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bash_profile export LDFLAGS=\u0026#34;-L/usr/local/opt/openssl/lib\u0026#34; export CPPFLAGS=\u0026#34;-I/usr/local/opt/openssl/include\u0026#34; pip install MySQL-python Reference：not-found-for-lssl\nwindows安装\ntext 1 pip install mysqlclient-1.3.12-cp36-cp36m-win_amd64.whl Reference https://www.lfd.uci.edu/~gohlke/pythonlibs/\nubuntu安装\ntext 1 2 apt-get install libmysqld-dev pip install MySQL-python my_config.h file not found text 1 2 3 4 5 6 7 creating build/temp.macosx-10.9-x86_64-2.7 gcc -fno-strict-aliasing -fno-common -dynamic -arch x86_64 -g -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -Dversion_info=(1,2,5,\u0026#39;final\u0026#39;,1) -D__version__=1.2.5 -I/usr/local/Cellar/mysql/8.0.23_1/include/mysql -I/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7 -c _mysql.c -o build/temp.macosx-10.9-x86_64-2.7/_mysql.o _mysql.c:44:10: fatal error: \u0026#39;my_config.h\u0026#39; file not found #include \u0026#34;my_config.h\u0026#34; ^~~~~~~~~~~~~ 1 error generated. error: command \u0026#39;gcc\u0026#39; failed with exit status 1 解决：网上找了很多版本均无法解决，最后发现实际上与linux处理思路是一样的。\ntext 1 2 3 4 5 6 7 brew install mysql # 可以加版本 如 brew install mysq@5.7 brew unlink mysql brew install mysql-connector-c # 这个是客户端 ln -snvf /usr/local/Cellar/mysql\\@5.7/5.7.32/bin/mysql_config /usr/local/bin/ # 做个软连接，位置可能不一致 sed -i -e \u0026#39;s/libs=\u0026#34;$libs -l \u0026#34;/libs=\u0026#34;$libs -lmysqlclient -lssl -lcrypto\u0026#34;/g\u0026#39; /usr/local/bin/mysql_config pip install MySQL-python sh: mysql_config: command not found sh: mysql_config: command not found 这个与上面类似，可以看到也是在PATH中找mysql_config\n","permalink":"https://www.161616.top/mac-mysqlapi/","summary":"记录一下，接了一个python2 django1.x的项目，很老了导致很多扩展无法安装\nos version：macos catalina python version: 2.7.18\n而django后端使用sqllite以外需要对应客户端引擎，而安装时编译依赖C客户端即实际mysql组件。\n使用的数据库后端。 内建的数据库后端有：\n\u0026lsquo;django.db.backends.postgresql\u0026rsquo; \u0026lsquo;django.db.backends.mysql\u0026rsquo; \u0026lsquo;django.db.backends.sqlite3\u0026rsquo; \u0026lsquo;django.db.backends.oracle\u0026rsquo;\n并且修改配置实例\ntext 1 2 3 4 5 6 7 8 9 10 DATABASES = { \u0026#39;default\u0026#39;: { \u0026#39;ENGINE\u0026#39;: \u0026#39;django.db.backends.mysql\u0026#39;, \u0026#39;USER\u0026#39;: \u0026#39;mydatabaseuser\u0026#39;, \u0026#39;NAME\u0026#39;: \u0026#39;mydatabase\u0026#39;, \u0026#39;TEST\u0026#39;: { \u0026#39;NAME\u0026#39;: \u0026#39;mytestdatabase\u0026#39;, }, }, } brew unlink mysql\nerror: command \u0026lsquo;gcc\u0026rsquo; failed with exit status 1 text 1 2 3 4 5 6 creating build/temp.macosx-10.9-x86_64-2.7 gcc -fno-strict-aliasing -fno-common -dynamic -arch x86_64 -g -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -Dversion_info=(1,2,5,\u0026#39;final\u0026#39;,1) -D__version__=1.","title":"macos python安装mysqlapi集合"},{"content":"VMware Tools描述 VMware Tools 中包含一系列服务和模块，可在 VMware 产品中实现多种功能，从而使用户能够更好地管理客户机操作系统，以及与客户机系统进行无缝交互。\n在Linux虚拟机中安装VMware Tools 安装前准备\n虚拟机必须打开cd/dvd驱动器，否则安装VMware Tools的选项无法选择 VMware Tools安装程序是使用Perl编写的，必须确认操作系统中安装Perl。 安装步骤\n在虚拟机菜单中右键单击虚拟机，然后单击客户机 \u0026gt; 安装/升级 VMware Tools。\n要创建一个挂载点 mkdir /mnt/cdrom\n要装载 CDROM，mount /dev/cdrom /mnt/cdrom\n要将安装文件文件复制到临时目录：cp /mnt/cdrom/VMwareTools*.tar.gz /tmp/；其中，* 部分是 VMware Tools 软件包的版本号，故以替代*。\n解压文件：cd /tmp \u0026amp;\u0026amp; tar -zxvf VMwareTools*.tar.gz\n运行PERL脚本以安装VMware Tools：cd vmware-tools-distrib \u0026amp;\u0026amp; ./vmware-install.pl，若要求选择，一路回车即可。\n安装完成后清理 rm -fr {/tmp/VMwareTools*,/tmp/vmware-tools-distrib} ; yum remove perl -y，如不需要perl可以卸载\n命令集合\nbash 1 2 3 4 5 mkdir /mnt/cdrom mount /dev/cdrom /mnt/cdrom cp /mnt/cdrom/VMwareTools*.tar.gz /tmp/ cd /tmp \u0026amp;\u0026amp; tar -xf VMwareTools*.tar.gz cd vmware-tools-distrib \u0026amp;\u0026amp; ./vmware-install.pl 安装后清理\nbash 1 2 rm -fr {/tmp/VMwareTools*,/tmp/vmware-tools-distrib} yum remove perl -y # 选择性执行 VMware Tools服务 VMware Tools守护进程在后台运行。它在 Windows 客户机操作系统中名为 vmtoolsd.exe，在 Mac OS X 客户机操作系统中名为 vmware-tools-daemon，在 Linux、FreeBSD 和 Solaris 客户机操作系统中名为 vmtoolsd。\n安装完成后，VMware Tools守护进程并未开机启动，可以设置开机启动，该守护进程在主机和客户机操作系统之间传递信息。\ntext 1 2 3 systemctl enable vmtoolsd systemctl start vmtoolsd systemctl status vmtoolsd 配置虚拟机与宿主机系统之间的时间同步 启用时间同步时，VMware Tools会将虚拟机操作系统的时间设置为与宿主机的时间相同。\n注意：无论 VMware Tools时间同步是否打开，在执行以下操作后都会进行时间同步：\n当启动 VMware Tools守护进程时，例如重新引导或打开电源操作过程中。 在从某个挂起操作恢复虚拟机时 恢复到快照后 压缩磁盘后 命令\n操作系统 程序名称 Windows VMwareToolboxCmd.exe Linux、Solaris 和 FreeBSD vmware-toolbox-cmd MAC OS X vmware-tools-cli text 1 vmware-toolbox-cmd timesync enable|disable Reference 配置客户机与主机操作系统之间的时间同步\n在 Linux 虚拟机中安装 VMware Tools (1018414)\nVMware Tools 产品文档\n","permalink":"https://www.161616.top/vmvare-tool-explain/","summary":"VMware Tools描述 VMware Tools 中包含一系列服务和模块，可在 VMware 产品中实现多种功能，从而使用户能够更好地管理客户机操作系统，以及与客户机系统进行无缝交互。\n在Linux虚拟机中安装VMware Tools 安装前准备\n虚拟机必须打开cd/dvd驱动器，否则安装VMware Tools的选项无法选择 VMware Tools安装程序是使用Perl编写的，必须确认操作系统中安装Perl。 安装步骤\n在虚拟机菜单中右键单击虚拟机，然后单击客户机 \u0026gt; 安装/升级 VMware Tools。\n要创建一个挂载点 mkdir /mnt/cdrom\n要装载 CDROM，mount /dev/cdrom /mnt/cdrom\n要将安装文件文件复制到临时目录：cp /mnt/cdrom/VMwareTools*.tar.gz /tmp/；其中，* 部分是 VMware Tools 软件包的版本号，故以替代*。\n解压文件：cd /tmp \u0026amp;\u0026amp; tar -zxvf VMwareTools*.tar.gz\n运行PERL脚本以安装VMware Tools：cd vmware-tools-distrib \u0026amp;\u0026amp; ./vmware-install.pl，若要求选择，一路回车即可。\n安装完成后清理 rm -fr {/tmp/VMwareTools*,/tmp/vmware-tools-distrib} ; yum remove perl -y，如不需要perl可以卸载\n命令集合\nbash 1 2 3 4 5 mkdir /mnt/cdrom mount /dev/cdrom /mnt/cdrom cp /mnt/cdrom/VMwareTools*.tar.gz /tmp/ cd /tmp \u0026amp;\u0026amp; tar -xf VMwareTools*.","title":"Linux VMware Tools详解"},{"content":"什么是网络策略 在Kubernetes平台中，要实现零信任网络的安全架构，Calico与istio是在Kubernetes集群中构建零信任网络必不可少的组件。\n而建立和维护整个集群中的“零信任网络”中，网络策略的功能在操作上大致可以总结为使用资源配置模板来管理控制平面数据流。说白了讲网络策略就是用来控制Pod间流量的规则。\n在Calico中如何编写网络策略 要使用网络策略就需要先了解Calico功能**：NetworkPolicy和GlobalNetworkPolicy**。\nNetworkPolicy资源，简称np；是命名空间级别资源。规则应用于与标签选择器匹配的endpoint的集合。\nGlobalNetworkPolicy资源，简称 gnp/gnps与NetworkPolicy功能一样，是整个集群级别的资源。\nGlobalNetworkPolicy 与 NetworkPolicy资源的管理也与calico的部署方式有关，使用etcd作为存储时，资源的管理只能使用 calicoctl进行管理\nNetworkPolicy与GlobalNetworkPolicy的构成 yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 apiVersion: projectcalico.org/v3 kind: NetworkPolicy metadata: name: allow-tcp-90 spec: selector: app == \u0026#39;envoy\u0026#39; # 应用此策略的endpoint types: # 应用策略的流量方向 - Ingress - Egress ingress: # 入口的流量规则 - action: Allow # 流量的行为 protocol: ICMP # 流量的协议 notProtocol: TCP # 匹配流量协议不为 值 的流量 source: # 流量的来源 src与dst的匹配关系为 与，所有的都生效即生效 nets: # 有效的来源IP selector: # 标签选择器 namespaceSelector: # 名称空间选择器 ports: # 端口 - 80 # 单独端口 - 6040:6050\t# 端口范围 destination: # 流量的目标 egress: # 出口的流量规则 - action: Allow serviceAccountSelector: # 使用与此规则的serviceAccount NetworkPolicy使用 实例：允许6379流量可以被 role=frontend的pod访问\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 apiVersion: projectcalico.org/v3 kind: NetworkPolicy metadata: name: allow-tcp-6379 namespace: production spec: selector: role == \u0026#39;database\u0026#39; types: - Ingress - Egress ingress: - action: Allow metadata: annotations: from: frontend to: database protocol: TCP source: selector: role == \u0026#39;frontend\u0026#39; destination: ports: - 6379 egress: - action: Allow 实例：禁止ICMP流量\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: projectcalico.org/v3 kind: NetworkPolicy metadata: name: allow-tcp-90 spec: selector: app == \u0026#39;netbox\u0026#39; types: - Ingress - Egress ingress: - action: Deny protocol: ICMP egress: - action: Deny protocol: ICMP 实例：禁止访问指定服务\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: projectcalico.org/v3 kind: NetworkPolicy metadata: name: allow-tcp-90 spec: selector: app == \u0026#39;netbox\u0026#39; types: - Ingress - Egress ingress: - action: Allow egress: - action: Deny destination: selector: app == \u0026#39;envoy\u0026#39; GlobalNetworkPolicy GlobalNetworkPolicy与NetworkPolicy使用方法基本一致，只是作用域的不同，并且可以应用很多高级的网络策略：\n转发流量 防御DoS \u0026hellip;. GlobalNetworkPolicy 中提供了一个preDNAT的功能，是kube-proxy对Node port的端口和IP的流量DNAT到所对应的Pod中的时候，为了既允许正常的ingress流量，又拒绝其他的ingress流量，这个时候必须要在DNAT前生效，这种情况需要使用preDNAT。\npreDNAT 适用的条件是，流量仅为ingress并且在DNAT之前。\nReference NetworkPolicy.spec\nNetworkPolicy.spec.ingress|egress\nNetworkPolicy.spec.ingress.src|dst\nglobalnetworkpolicy\n","permalink":"https://www.161616.top/calico-network-policy/","summary":"什么是网络策略 在Kubernetes平台中，要实现零信任网络的安全架构，Calico与istio是在Kubernetes集群中构建零信任网络必不可少的组件。\n而建立和维护整个集群中的“零信任网络”中，网络策略的功能在操作上大致可以总结为使用资源配置模板来管理控制平面数据流。说白了讲网络策略就是用来控制Pod间流量的规则。\n在Calico中如何编写网络策略 要使用网络策略就需要先了解Calico功能**：NetworkPolicy和GlobalNetworkPolicy**。\nNetworkPolicy资源，简称np；是命名空间级别资源。规则应用于与标签选择器匹配的endpoint的集合。\nGlobalNetworkPolicy资源，简称 gnp/gnps与NetworkPolicy功能一样，是整个集群级别的资源。\nGlobalNetworkPolicy 与 NetworkPolicy资源的管理也与calico的部署方式有关，使用etcd作为存储时，资源的管理只能使用 calicoctl进行管理\nNetworkPolicy与GlobalNetworkPolicy的构成 yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 apiVersion: projectcalico.org/v3 kind: NetworkPolicy metadata: name: allow-tcp-90 spec: selector: app == \u0026#39;envoy\u0026#39; # 应用此策略的endpoint types: # 应用策略的流量方向 - Ingress - Egress ingress: # 入口的流量规则 - action: Allow # 流量的行为 protocol: ICMP # 流量的协议 notProtocol: TCP # 匹配流量协议不为 值 的流量 source: # 流量的来源 src与dst的匹配关系为 与，所有的都生效即生效 nets: # 有效的来源IP selector: # 标签选择器 namespaceSelector: # 名称空间选择器 ports: # 端口 - 80 # 单独端口 - 6040:6050\t# 端口范围 destination: # 流量的目标 egress: # 出口的流量规则 - action: Allow serviceAccountSelector: # 使用与此规则的serviceAccount NetworkPolicy使用 实例：允许6379流量可以被 role=frontend的pod访问","title":"calico网络策略"},{"content":"开始前准备 确定calico数据存储\nCalico同时支持kubernetes api和etcd数据存储。官方给出的建议是在本地部署中使用K8S API，仅支持Kubernetes模式。而官方给出的etcd则是混合部署（Calico作为Kubernetes和OpenStack的网络插件运行）的最佳数据存储。\n使用etcd作为calico数据存储的好处：\n允许多平台混用calico，如Kubernetes OpenStack上运行Calico Kubernetes资源与Calico资源分离 一个Calico群集，该群集不仅仅包含一个Kubernetes群集，如可与多个kubernetes集群互通。 坏处：\n安装步骤繁琐 无法使用Kubernetes RBAC对calico资源的控制 无法使用Kubernetes资源对calico进行管理 下载calico部署清单 text 1 curl https://docs.projectcalico.org/manifests/calico-etcd.yaml -o calico.yaml 修改Pod CIDR Calico默认的Pod CIDR使用的是192.168.0.0/16，这里一般使用与controller-manager中的--cluster-cidr 保持一,取消资源清单内的 CALICO_IPV4POOL_CIDR变量的注释，并将其设置为与所选Pod CIDR相同的值。\ncalico的IP分配范围 Calico IPAM从ipPool分配IP地址。修改Pod的默认IP范围则修改清单calico.yaml中的CALICO_IPV4POOL_CIDR\n配置Calico的 IP in IP 默认情况下，Calico中的IPIP已经禁用，这里使用的v3.17.2 低版本默认会使用IPIP\n要开启IPIP mode则需要修改配置清单内的 CALICO_IPV4POOL_IPIP 环境变量改为 always\n修改secret yaml 1 2 3 4 5 6 7 8 9 10 11 # Populate the following with etcd TLS configuration if desired, but leave blank if # not using TLS for etcd. # The keys below should be uncommented and the values populated with the base64 # encoded contents of each file that would be associated with the TLS data. # Example command for encoding a file contents: cat \u0026lt;file\u0026gt; | base64 -w 0 # etcd的ca etcd-ca: # 填写上面命令编码后的值 # etcd客户端key etcd-key: # 填写上面命令编码后的值 # etcd客户端访问证书 etcd-cert: # 填写上面命令编码后的值 修改configMap yaml 1 2 3 4 5 6 etcd_endpoints: \u0026#34;https://10.0.0.6:2379\u0026#34; # If you\u0026#39;re using TLS enabled etcd uncomment the following. # You must also populate the Secret below with these files. etcd_ca: \u0026#34;/calico-secrets/etcd-ca\u0026#34; etcd_cert: \u0026#34;/calico-secrets/etcd-cert\u0026#34; etcd_key: \u0026#34;/calico-secrets/etcd-key\u0026#34; 开始安装 text 1 kubectl apply -f calico.yaml 安装出错 /calico-secrets/etcd-cert: permission denied\ntext 1 2 2021-02-08 02:15:10.485 [INFO][1] main.go 88: Loaded configuration from environment config=\u0026amp;config.Config{LogLevel:\u0026#34;info\u0026#34;, WorkloadEndpointWorkers:1, ProfileWorkers:1, PolicyWorkers:1, NodeWorkers:1, Kubeconfig:\u0026#34;\u0026#34;, DatastoreType:\u0026#34;etcdv3\u0026#34;} 2021-02-08 02:15:10.485 [FATAL][1] main.go 101: Failed to start error=failed to build Calico client: could not initialize etcdv3 client: open /calico-secrets/etcd-cert: permission denied 找到资源清单内的对应容器（calico-kube-controllers）的配置。在卷装载中设置440将解决此问题\nyaml 1 2 3 4 5 6 7 volumes: # Mount in the etcd TLS secrets with mode 400. # See https://kubernetes.io/docs/concepts/configuration/secret/ - name: etcd-certs secret: secretName: calico-etcd-secrets defaultMode: 0400 # 改为0440 修改calicoctl的数据源 使用单独的etcd作为calico数据存储还需要修改calicoctl数据存储访问配置\ncalicoctl 在默认情况下，查找配置文件的路径为/etc/calico/calicoctl.cfg上。可以使用--config覆盖此选项默认配置（使用中测试不成功，官方给出有这个方法）。\n如果calicoctl无法获得配置文件，将检查环境变量。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: projectcalico.org/v3 kind: CalicoAPIConfig metadata: spec: datastoreType: etcdv3 etcdEndpoints: \u0026#34;https://10.0.0.6:2379\u0026#34; etcdCACert: | # 这里填写etcd ca证书文件的内容，无需转码base64 etcdCert: | # 这里填写etcd client证书文件的内容，无需转码base64 etcdKey: | # 这里填写etcd client秘钥文件的内容，无需转码base64 reference：\nSecret permission denied\nconfiguration calicoctl\ncalico installation\n","permalink":"https://www.161616.top/calico-deploy-on-hybrid-cloud/","summary":"开始前准备 确定calico数据存储\nCalico同时支持kubernetes api和etcd数据存储。官方给出的建议是在本地部署中使用K8S API，仅支持Kubernetes模式。而官方给出的etcd则是混合部署（Calico作为Kubernetes和OpenStack的网络插件运行）的最佳数据存储。\n使用etcd作为calico数据存储的好处：\n允许多平台混用calico，如Kubernetes OpenStack上运行Calico Kubernetes资源与Calico资源分离 一个Calico群集，该群集不仅仅包含一个Kubernetes群集，如可与多个kubernetes集群互通。 坏处：\n安装步骤繁琐 无法使用Kubernetes RBAC对calico资源的控制 无法使用Kubernetes资源对calico进行管理 下载calico部署清单 text 1 curl https://docs.projectcalico.org/manifests/calico-etcd.yaml -o calico.yaml 修改Pod CIDR Calico默认的Pod CIDR使用的是192.168.0.0/16，这里一般使用与controller-manager中的--cluster-cidr 保持一,取消资源清单内的 CALICO_IPV4POOL_CIDR变量的注释，并将其设置为与所选Pod CIDR相同的值。\ncalico的IP分配范围 Calico IPAM从ipPool分配IP地址。修改Pod的默认IP范围则修改清单calico.yaml中的CALICO_IPV4POOL_CIDR\n配置Calico的 IP in IP 默认情况下，Calico中的IPIP已经禁用，这里使用的v3.17.2 低版本默认会使用IPIP\n要开启IPIP mode则需要修改配置清单内的 CALICO_IPV4POOL_IPIP 环境变量改为 always\n修改secret yaml 1 2 3 4 5 6 7 8 9 10 11 # Populate the following with etcd TLS configuration if desired, but leave blank if # not using TLS for etcd.","title":"基于混合云模式的calico部署"},{"content":"在Linux中，发现每次系统启动时，都会将（169.254.0.0/16）路由启动并将其添加到路由表中。但是并不知道这条路由具有什么功能和它到底来自于哪里？\ntext 1 2 3 4 5 6 $ route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 10.0.0.2 0.0.0.0 UG 0 0 0 eth0 10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0 要想搞清楚路由（169.254.0.0/16）究竟来自哪里并且它的作用是什么？首先需要搞明白两个概念\nzeroconf “zeroconf”或“Zero Configuration Networking” 是一种无需额外配置即可自动创建IP地址网络的技术。也被称为 “Automatic Private IP Addressing”（APIPA）。\nzeroconf规范的提出者是Apple公司，其目的是让非专业用户也可以便捷的连接各种网络设备，例如计算机，打印机等。整个搭建网络的过程都是自动实现。如果没有“zeroconf”，用户必须手动，或者利用对应的服务（例如DHCP、DNS）对网络进行配置。这些过程对非技术用户和新用户们来说是很一件难的事情。\nzeroconf的出现是问了解决三个问题：\n为网络设备自动分配可用IP地址 解析计算机主机名 自动发现网络服务（如打印机等） zeroconf的地址选用 对于Link-local address，IPv4使用的特殊保留地址169.254.0.0/16，在RFC3927中所描述。作用是当DHCP客户端在超时和重试后扔找不到对应的DHCP服务器，它将随机从该网络(｀169.254.0.0/16｀)中获取地址。这样可以与无法获取DHCP地址的主机进行通信。\n如何禁用zeroconf 要在系统引导期间禁用zeroconf路由，需要编辑/etc/sysconfig/network文件，配置以下内容\ntext 1 2 3 NETWORKING=YES HOSTNAME=localhost.localdomain NOZEROCONF=yes 169.254.0.0/16的应用 在calico中就使用了这个地址169.254.0.0/16\nbash 1 2 default via 169.254.1.1 dev eth0 169.254.1.1 dev eth0 scope link 这个IP地址169.254.1.1是默认的网关，但是整个网络中中没有一张网卡是这个地址。那么为何是这个地址？\n如一个网络中设备D1 (192.168.0.2/24) 与设备D2 (192.168.1.2/24)，D1和D2在相互通信时，D1先发送了ARP广播，请求D2的mac地址，但是由于两个设备处于不同网，也就是说D1的ARP请求会被R1拦截到，然后R1会封装自己的mac地址为目的地址发送一个ARP回应数据报给R1（善意的欺骗），然后R1就会代替D1去访问D2。\n在Kubernetes Calico网络中，当一个数据包的目的地址不是本网络时，会先发起ARP广播，网关设置即169.254.1.1收到会将自己的mac地址返回给发送端，后续的请求由这个veth对进行完成，使用代理arp做了arp欺骗。这样做抑制了arp广播攻击，并且通过代理arp也可以进行跨网络的访问。\n在容器内可以使用ethtool -S 来查看对端。这里使用的容器为cylon/netbox，集成了常用的网络命令，作为网络故障排除容器使用。\n实验：查找calico网络中169.254.1.1的应用 当从netbox02（10.244.140.69/32） ping netbox01（10.244.241.74/32）发现ARP包被抑制在容器内部，相应的mac地址为eth0的对端\n而后根据路由交有tun0隧道进行IPIP封装，tun0为其隧道，隧道解包后发往对应的设备，而calico网络中会生成路由到对应的workload之上。\nReference wiki_zero-configuration-network ","permalink":"https://www.161616.top/linux-1692540024/","summary":"在Linux中，发现每次系统启动时，都会将（169.254.0.0/16）路由启动并将其添加到路由表中。但是并不知道这条路由具有什么功能和它到底来自于哪里？\ntext 1 2 3 4 5 6 $ route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 10.0.0.2 0.0.0.0 UG 0 0 0 eth0 10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0 要想搞清楚路由（169.254.0.0/16）究竟来自哪里并且它的作用是什么？首先需要搞明白两个概念\nzeroconf “zeroconf”或“Zero Configuration Networking” 是一种无需额外配置即可自动创建IP地址网络的技术。也被称为 “Automatic Private IP Addressing”（APIPA）。\nzeroconf规范的提出者是Apple公司，其目的是让非专业用户也可以便捷的连接各种网络设备，例如计算机，打印机等。整个搭建网络的过程都是自动实现。如果没有“zeroconf”，用户必须手动，或者利用对应的服务（例如DHCP、DNS）对网络进行配置。这些过程对非技术用户和新用户们来说是很一件难的事情。\nzeroconf的出现是问了解决三个问题：\n为网络设备自动分配可用IP地址 解析计算机主机名 自动发现网络服务（如打印机等） zeroconf的地址选用 对于Link-local address，IPv4使用的特殊保留地址169.254.0.0/16，在RFC3927中所描述。作用是当DHCP客户端在超时和重试后扔找不到对应的DHCP服务器，它将随机从该网络(｀169.254.0.0/16｀)中获取地址。这样可以与无法获取DHCP地址的主机进行通信。\n如何禁用zeroconf 要在系统引导期间禁用zeroconf路由，需要编辑/etc/sysconfig/network文件，配置以下内容\ntext 1 2 3 NETWORKING=YES HOSTNAME=localhost.","title":"Linux中169.254.0.0/24的路由来自哪里"},{"content":"本文转自博客： 我的小米粥分你一半\n我一直在负责维护的PaaS平台引入了Kubernetes作为底层支持, 可以借助Kubernetes的生态做更多的事情, 这篇博客主要介绍如何为用户提供dashboard功能, 以及一些可以扩展的想法. 希望读者有一定的kubernetes使用经验, 并且了解rbac的功能。\nDashboard功能 Kubernetes原生提供了Web界面, 也就是Dashboard, 具体的参考可以见官方文档:\n​\t安装完成后, 我们一般是通过token来使用的, 不同的token有着不同的权限.\n​\t上面所说的token是Bearer Token, 除了在界面上输入之外, 你可以这么来用, 通过添加header即可.\ntext 1 curl -H \u0026#34;Authorization: Bearer ${TOKEN}\u0026#34; https://{dashboard}/api/myresource PaaS平台使用Dashboard简要讨论 需求分析 Dashboard本身的功能是十分强大的, 但是给所有人admin权限显然是不现实的. 对于一个普通用户来讲, PaaS平台的将他的应用(代码)部署好并运行, 他所需要关注的就只有属于他自己的项目, 平台也需要做好权限控制, 避免一个用户操作了另一个用户的应用.\n权限系统设计 基于以上的需求讨论, 平台需要做的操作就是为每个用户创建属于自己的权限提供, 并限制可以访问到的资源. 考虑这样的情况:\n我们有一个用户A, 他拥有自己的一个应用群组(G), 群组中部署了一系列应用程序(a1, a2…). 在Kubernetes中, 这样的群组概念我们将其映射为namespace, 群组(G) \u0026lt;=\u0026gt; 用户空间(NS), 我们需要控制的权限控制策略就变成了用户A在用户空间NS的权限控制.\ntoken分发策略 拥有了权限控制后, 所需要打就是将token分发给用户, 当然这是一种极度不安全的做法, Kubernetes中的token创建之后一般是不会改变的, 分发这样的token会有很大的安全风险, 有两个方面:\n1. 用户A将token保存了下来, 那么他就能不经过平台登录Dashboard, 这样不利于审计工作,\r2. token一旦泄露, PaaS平台很难做到反应(因为token脱离了平台的控制, 无法判断究竟是什么时候发生了泄露, 也无法马上吊销这个token), 安全风险比较高.\r因此, 最好的做法就是不把token交给用户, 用户每次想要登录dashboard, 从平台进行跳转, 跳转时携带安全信息, 在dashboard登录时, 由平台自己的程序请求token, 避免经手用户.\n如果到这里, 你没有理解上面的内容, 建议回去再看一次需求, 如果还是理解不了, 就不要往下看了, 下面只是介绍具体的实现方案.\nKubernetes权限限制 Kubernetes本身有着比较复杂的权限控制系统, 设计时没必要纠结过多, 按照可以给用户和不能给用户的权限进行区分就好了. 我直接贴一下我的权限控制策略吧, 并不一定适合每个人, 只是可以做个参考.\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: xxx:xxx-group-yyy namespace: xxx-group-yyy rules: # 可以查看当前NS下面的service, pod, logs, events - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;services\u0026#34;, \u0026#34;pods\u0026#34;, \u0026#34;pods/log\u0026#34;, \u0026#34;events\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;] # 可以使用exec命令进入容器 - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods/exec\u0026#34;] verbs: [\u0026#34;create\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;] # 可以查看deployments和replicasets - apiGroups: [\u0026#34;extensions\u0026#34;, \u0026#34;apps\u0026#34;] resources: [\u0026#34;deployments\u0026#34;, \u0026#34;replicasets\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;] # 可以查看job, cronjob以及ingress - apiGroups: [\u0026#34;batch\u0026#34;, \u0026#34;extensions\u0026#34;] resources: [\u0026#34;cronjobs\u0026#34;, \u0026#34;jobs\u0026#34;, \u0026#34;ingresses\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;] 正如上面的注释一样, 尽可能只给用户只读的权限, 也许你已经发现了, 甚至不需要给用户namespace的查看权限, 这也是为了安全, 避免用户得知其他人的namespace.\n分发Token以及安全性保证 这是本篇博客的核心内容: 如何使得用户可以无感知的登录到dashboard(对用户隐藏token).\n该方案用到的方法是: 添加一层访问控制的网关, 用于处理token获取的操作, 具体的流程图如下.\n需要注意的有几点:\nPaaS给出的secret_code是有时效性的, 不允许用户一直用同一个secret_code进行访问 网关与PaaS平台间的通信应该加密, 网关必须是PaaS平台可信的 网关不应该长期保存token 网关的访问最好添加OpenID校验, 确保网关可以精确定位到每个用户的每次访问 体验优化 首先, 第2步到第3步, secret_code获取之后, 可以以302重定向的方式跳转至网关入口 网关可以临时性的保存secret_code与token的映射关系, 既能够提升用户体验, 也能有效减缓PaaS平台的压力 dashbaord的webshell功能是基于websocket支持的, 所以请确保你的网关可以通过websocket请求, 否则终端连接后几分钟就断了, websocket可以持续几个小时那么久 跳转到网关时, 可以携带更多的信息, 比如携带某个pod的id, 网关就可以直接跳转到对应的pod, 用户打开webshell就很方便了 网关的实现我不做过多的说明了, 只有一点建议, secret_code在跳转到网关后, 马上进行校验. 由于dashboard的前端路由实现问题, secret_code最好在校验后加密放置到cookie中, 实现方面的问题其他可以发邮件与我讨论.\n总结 这篇博客主要介绍了一种允许普通用户使用dashboard的功能. 在实现策略上, 利用了kubernetes的权限限制, token隐藏的方案, 该方案目前我已经加入到了我负责的PaaS平台中, 稳定性方面可以满足工作需求, 安全性正如博客中介绍, 大家可以自行斟酌.\n","permalink":"https://www.161616.top/pass-base-dashboard-k8s/","summary":"本文转自博客： 我的小米粥分你一半\n我一直在负责维护的PaaS平台引入了Kubernetes作为底层支持, 可以借助Kubernetes的生态做更多的事情, 这篇博客主要介绍如何为用户提供dashboard功能, 以及一些可以扩展的想法. 希望读者有一定的kubernetes使用经验, 并且了解rbac的功能。\nDashboard功能 Kubernetes原生提供了Web界面, 也就是Dashboard, 具体的参考可以见官方文档:\n​\t安装完成后, 我们一般是通过token来使用的, 不同的token有着不同的权限.\n​\t上面所说的token是Bearer Token, 除了在界面上输入之外, 你可以这么来用, 通过添加header即可.\ntext 1 curl -H \u0026#34;Authorization: Bearer ${TOKEN}\u0026#34; https://{dashboard}/api/myresource PaaS平台使用Dashboard简要讨论 需求分析 Dashboard本身的功能是十分强大的, 但是给所有人admin权限显然是不现实的. 对于一个普通用户来讲, PaaS平台的将他的应用(代码)部署好并运行, 他所需要关注的就只有属于他自己的项目, 平台也需要做好权限控制, 避免一个用户操作了另一个用户的应用.\n权限系统设计 基于以上的需求讨论, 平台需要做的操作就是为每个用户创建属于自己的权限提供, 并限制可以访问到的资源. 考虑这样的情况:\n我们有一个用户A, 他拥有自己的一个应用群组(G), 群组中部署了一系列应用程序(a1, a2…). 在Kubernetes中, 这样的群组概念我们将其映射为namespace, 群组(G) \u0026lt;=\u0026gt; 用户空间(NS), 我们需要控制的权限控制策略就变成了用户A在用户空间NS的权限控制.\ntoken分发策略 拥有了权限控制后, 所需要打就是将token分发给用户, 当然这是一种极度不安全的做法, Kubernetes中的token创建之后一般是不会改变的, 分发这样的token会有很大的安全风险, 有两个方面:\n1. 用户A将token保存了下来, 那么他就能不经过平台登录Dashboard, 这样不利于审计工作,\r2. token一旦泄露, PaaS平台很难做到反应(因为token脱离了平台的控制, 无法判断究竟是什么时候发生了泄露, 也无法马上吊销这个token), 安全风险比较高.","title":"基于Kubernetes的PaaS平台提供dashboard支持的一种方案"},{"content":"什么是arp 地址解析协议，Address Resolution Protoco，使用ARP协议可实现通过IP地址获得对应主机的的物理地址（MAC）\n在TCP/IP的网络环境下，每个互联网的主机都会被分配一个32位的IP地址，这种互联网地址是在网际范围标识主机的一种逻辑地址。为了让报文在物理网路上传输，还补习要知道对方目的主机的物理地址才行。这样就存在把IP地址变换成物理地址的地址转换问题。\n在以太网环境，为了正确地向目的主机传送报文，必须把目的主机的32为IP地址转换成为目的主机48位以太网地址(MAC),这个就需要在互联层有一个服务或功能将IP地址转换为相应的物理地址(MAC)，这个服务就是ARP协议.\n所谓的\u0026quot;地址解析\u0026quot;，就是主机在发送帧之前将目标IP地址转换成目标MAC地址的过程。ARP协议的基本功能就是通过目标设备的IP地址，查询目标设备的MAC地址，以保证主机间互相通信的顺利进行.\nARP协议和DNS有相像之处。不同点是：DNS实在域名和IP之间解析，另外ARP协议不需要配置服务，而DNS要配置服务才行。\nARP缓存表 在每台安装有TCP/IP协议的设备都会有一个ARP缓存表（windows命令提示符里输入arp -a即可）， 表里的IP地址与MAC地址是一一对应的。\nbash 1 2 3 4 5 6 7 8 9 10 C:\\Users\\CM\u0026gt;arp -a 接口: 192.168.1.103 --- 0x3 Internet 地址 物理地址 类型 192.168.1.1 3c-46-d8-5d-53-87 动态 192.168.1.255 ff-ff-ff-ff-ff-ff 静态 224.0.0.22 01-00-5e-00-00-16 静态 224.0.0.251 01-00-5e-00-00-fb 静态 224.0.0.252 01-00-5e-00-00-fc 静态 239.11.20.1 01-00-5e-0b-14-01 静态 239.255.255.250 01-00-5e-7f-ff-fa 静态 arp常用命令 arp -a 查看所有记录\narp -d 清除arp表\narp -s $ip $mac 将绑定IP和MAC\narp -n 不解析名称打印arp表\nARP缓存是把双刃剑 主机有了arp缓存表，可以加快arp的解析速度，减少局域网内广播风暴。\n正是有了arp缓存表，给恶意黑客带来了攻击服务器主机的风险，这个就是arp欺骗攻击\n切换路由器，负载均衡器等设备时，可能会导致短时网络中断（发送广播）。\n为什么要使用ARP协议 OSI模型把网络工作分为七层，彼此不直接打交道，只通过接口(layer interface)。IP地址工作在第三层，MAC地址工作在第二层。在局域网中，当主机或其它三层网络设备有数据要发送给另一台主机或三层网络设备时，它需要知道对方的网络层地址（即IP地址）。但是仅有IP地址是不够的，因为IP报文必须封装成帧才能通过物理网络发送，因此发送方还需要知道接收方的物理地址（即MAC地址），但又不能跨第二、三层，所以需要用ARP协议服务，来帮助获取到目的节点的MAC地址。ARP可以实现将IP地址解析为MAC地址。主机或三层网络设备上会维护一张ARP表，用于存储IP地址和MAC地址的关系。一般ARP表项包括动态ARP表项和静态ARP表项。\n模拟一个环境抓包分析arp数据包的内容 text 1 2 3 4 5 6 [Huawei] 系统视图 \u0026lt;Huawei\u0026gt; 用户视图，开机命令行进入的就是用户视图 system-view 用户视图切换系统视图 interface g0/0/0 选择接口 display arp all 查看arp表 arp-porxy enable 开启代理ARP功能 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 \u0026lt;Huawei\u0026gt;dis this # return # 设置g0/0/0接口ip [Huawei-GigabitEthernet0/0/0]ip a 192.168.0.1 24 Jan 19 2021 17:56:33-08:00 Huawei %%01IFNET/4/LINK_STATE(l)[0]:The line protocol IP on the interface GigabitEthernet0/0/0 has entered the UP state. [Huawei-GigabitEthernet0/0/0] [Huawei-GigabitEthernet0/0/0]dis this [V200R003C00] # interface GigabitEthernet0/0/0 ip address 192.168.0.1 255.255.255.0 # return # 设置g0/0/1 ip [Huawei-GigabitEthernet0/0/0]int g0/0/1 [Huawei-GigabitEthernet0/0/1]ip a 192.168.1.1 24 Jan 19 2021 17:57:02-08:00 Huawei %%01IFNET/4/LINK_STATE(l)[1]:The line protocol IP on the interface GigabitEthernet0/0/1 has entered the UP state. 此时查看pc与路由器的arp表\ntext 1 2 3 4 5 $ arp -a Internet Address Physical Address Type $ ping另外一台设备 192.168.2.2\ntext 1 2 3 4 5 6 7 8 9 10 11 12 $ ping 192.168.1.2 Ping 192.168.1.2: 32 data bytes, Press Ctrl_C to break Request timeout! From 192.168.1.2: bytes=32 seq=2 ttl=127 time=15 ms From 192.168.1.2: bytes=32 seq=3 ttl=127 time=16 ms --- 192.168.1.2 ping statistics --- 3 packet(s) transmitted 2 packet(s) received 33.33% packet loss round-trip min/avg/max = 0/15/16 ms 另外一段抓包可以看到对应收到的广播\n以太网arp数据包段说明\n目的mac地址 源mac地址 帧类型 硬件类型 上层协议类型 mac地址长度 ip地址长度 操作类型 源mac地址 源ip地址 目的mac地址 目的ip地址 HuaweiTe_c7:73:db (54:89:98:c7:73:db) HuaweiTe_58:37:e8 (00:e0:fc:58:37:e8) ARP Ethernet IPv4 6 4 2 HuaweiTe_58:37:e8 (00:e0:fc:58:37:e8) 192.168.0.1 HuaweiTe_c7:73:db (54:89:98:c7:73:db) 192.168.0.2 arp广播是通过网关进行传递的，本机arp表缓存的为网关的mac地址\ntext 1 2 3 4 $ arp -a Internet Address Physical Address Type 192.168.1.1 00-E0-FC-58-37-E9 dynamic op操作类型说明\n代码 说明 1 arp请求 2 arp应答 3 rarp请求 4 rarp应答 动态ARP表项 动态ARP表项由ARP协议通过ARP报文自动生成和维护，可以被老化，可以被新的ARP报文更新，也可以被静态ARP表项所覆盖。当到达老化时间或接口关闭时会删除相应的动态ARP表项。\n静态ARP表项 静态ARP表项通过手工配置（通过对应设备的IP地址与MAC地址绑定命定进行）和维护。不会被老化，也不会被动态ARP表项覆盖。配置静态ARP表项可以增加通信的安全性，因为静态ARP可以限定和指定IP地址的设备通信时只使用指定的MAC地址（也就是我们通常所说的IP地址和MAC地址的绑定），此时攻击报文无法修改此表项的IP地址和MAC地址的映射关系，从而保护了本设备和指定设备间正常通信。静态ARP表项又分为短静态ARP表项和长静态ARP表项\n短静态ARP表项 在配置短静态ARP表项时，只需要配置IP地址和MAC地址项。如果出接口是三层以太网接口，短静态ARP表项可以直接用于报文转发；如果出接口是VLAN虚接口，短静态ARP表项不能直接用于报文转发，当要发送IP数据包时，先发送ARP请求报文，如果收到的相应报文中的源IP地址和源MAC地址与所配置的IP地址和MAC地址相同，则将接受ARP响应报文的接口加入该静态表项中，之后就可以用于IP数据包的转发了。\n长静态ARP表项 在配置长静态ARP表项时，除了配置IP地址和MAC地址项外，还必须配置该ARP表所对应的VLAN（虚拟局域网）和出接口。也就是长静态ARP表项同事绑定了IP地址、MAC地址、VLAN和端口，可以直接用于报文转发。\napr欺骗 ARP病毒，ARP欺骗\n高可用服务器对之间切换时要考虑ARP缓存问题\n路由器等设备无缝迁移时要考虑ARP缓存的问题，例如：更换办公室的路由器.\nARP欺骗原理 ARP攻击就是通过伪造IP地址和MAC地址对实现ARP欺骗的，如果一台主机中了ARP病毒，那么它就能够在网络中产生大量的ARP通信量（它会以很快的频率进行广播），以至于使网络阻塞，攻击者只要持续不断的发出伪造ARP响应包就能更改局域网中目标主机ARP缓存中的IP-MAC条目，造成网络中断或中间人攻击。\nARP攻击主要是存在于局域网网络中，局域网中若有一个人感染ARP木马，则感染该ARP木马的系统将会试图通过“ARP欺骗”手段截获所在网络内其他计算机的通信故障。\n服务器切换ARP问题\n当网络中一台提供服务的机器宕机后，当在其他运行正常的机器添加宕机的机器的IP时，会因为客户端的ARP table cache的地址解析还是宕机的机器的MAC地址。从而导致，即使在其他运行正常的机器添加宕机的机器的IP，也会发生客户依然无法访问的情况。\n解决方法是：当宕机时，IP地址迁移到其他机器上时，需要通过arping命令来通知所有网络内机器清除其本地的ARP table cache，从而使得客户机访问时重新广播获取MAC地址.\n几乎所有的高可用软件都会考虑这个问题。\nARP广播而进行新的地址解析。\nlinux下具体命令：\ntext 1 2 arping -I eth0 -c 3 -s 10.0.0.162 10.0.0.253 arping -U -I eth0 10.0.0.162 proxy arp 代理ARP的原理就是当出现跨网段的ARP请求时，路由器将自己的MAC返回给发送ARP广播请求发送者，实现MAC地址代理（善意的欺骗），最终使得主机能够通信。\n如一个网络中设备D1 (192.168.0.2/24) 与设备D2 (192.168.1.2/24)，D1和D2在相互通信时，D1先发送了ARP广播，请求D2的mac地址，但是由于两个设备处于不同网，也就是说D1的ARP请求会被R1拦截到，然后R1会封装自己的mac地址为目的地址发送一个ARP回应数据报给R1（善意的欺骗），然后R1就会代替D1去访问D2。\n如上述arp抓包图，首先广播收到回复为R1192.168.0.1\n如果R1关闭了arp的代理功能，那么R1再访问R3的时候，R2并不会把自己的mac地址给R1，那么R1和R3之间就无法通信。默认情况下，思科的设备是开启了arp代理功能，也就是说，R2会作为中间代理实现R1和R3之间跨网段通信。\n实验：通过命名空间模拟容器网络的代理arp数据包 实验前所需掌握的知识接口作用域 interface scope与链路本地地址（Link-local address)\n接口作用域 路由的接口作用域，这个配置可以解释为路由的范围会影响源数据（源地址）请求的选择。当主机存在多个网络接口和地址时，route scope控制ip数据寻址和广播的范围。\nglobal：如果来自不同的端口（可以理解为网卡等）可以转发。\nlink：仅在此设备有效，即只有来自这个网络接口设备的流量才走这条路由（发送和接收为同一端口）\nhost：本地回环，仅用于在主机内部进行通信。\nsite：ipv6独有。\nreference http://linux-ip.net/html/tools-ip-address.html\n链路本地地址 169.254.0.0/16 保留地址块，在169.254.1.0 ~ 169.254.254.255 中随机选择一个地址进行ARP广播，如果收到回复，表示IP地址已经使用。\n在Kubernetes Calico网络中，当一个数据包的目的地址不是本网络时，会先发起ARP广播，网关设置即169.254.1.1收到会将自己的mac地址返回给发送端，后续的请求由这个veth对进行完成，使用代理arp做了arp欺骗。这样做抑制了arp广播攻击，并且通过代理arp也可以进行跨网络的访问。\n实验目的：模拟calico网络，使用代理arp欺骗完成网络的跨网段通信\n在准备的两个主机进行相应的设置\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 # 添加网络名称空间 ip netns add net1 # 创建一个虚拟以太网对 ip link add veth1 type veth peer name eth1 # 将一端关联至网络名称空间内 ip link set eth1 netns net1 # 设置一个IP地址 ip netns exec net1 ip addr add 192.168.1.10/24 dev eth1 # 启动这个网卡 ip netns exec net1 ip link set eth1 up # 添加一个自动寻址IP，作为网关 ip netns exec net1 ip route add 169.254.1.1 dev eth1 scope link # 所有的流量都通过这个网关进行进出 ip netns exec net1 ip route add default via 169.254.1.1 dev eth1 ip netns exec net1 ip route ip netns exec net1 ip link set eth1 up # 设置主机端的网络对 ip link set veth1 up # 所有通过192.168.1.10的数据包进出都走veth1 ip route add 192.168.1.10 dev veth1 scope link # 通往192.168.2.10的数据的下一挑是10.0.0.3（对端主机IP） ip route add 192.168.2.10 via 10.0.0.3 dev eth0 ## 设置另外一台设备 ip netns add net1 ip link add veth1 type veth peer name eth1 ip link set eth1 netns net1 ip netns exec net1 ip addr add 192.168.2.10/24 dev eth1 ip netns exec net1 ip link set eth1 up ip netns exec net1 ip route add 169.254.1.1 dev eth1 scope link ip netns exec net1 ip route add default via 169.254.1.1 dev eth1 ip netns exec net1 ip route ip link set veth1 up ip route add 192.168.2.10 dev veth1 scope link ip route add 192.168.1.10 via 10.0.0.4 dev eth0 开启对应内核设置\nbash 1 2 3 4 5 6 # 代理arp echo 1 \u0026gt; /proc/sys/net/ipv4/conf/veth1/proxy_arp # 专用VLAN代理arp。基本上允许代理arp回复到同一接口 echo 1 \u0026gt; /proc/sys/net/ipv4/conf/veth1/proxy_arp_pvlan # 内核转发 echo 1 \u0026gt; /proc/sys/net/ipv4/ip_forward reference\nlinux proxy arp\n169.254-ip-address\n​\n","permalink":"https://www.161616.top/arp-proxy-and-arp/","summary":"什么是arp 地址解析协议，Address Resolution Protoco，使用ARP协议可实现通过IP地址获得对应主机的的物理地址（MAC）\n在TCP/IP的网络环境下，每个互联网的主机都会被分配一个32位的IP地址，这种互联网地址是在网际范围标识主机的一种逻辑地址。为了让报文在物理网路上传输，还补习要知道对方目的主机的物理地址才行。这样就存在把IP地址变换成物理地址的地址转换问题。\n在以太网环境，为了正确地向目的主机传送报文，必须把目的主机的32为IP地址转换成为目的主机48位以太网地址(MAC),这个就需要在互联层有一个服务或功能将IP地址转换为相应的物理地址(MAC)，这个服务就是ARP协议.\n所谓的\u0026quot;地址解析\u0026quot;，就是主机在发送帧之前将目标IP地址转换成目标MAC地址的过程。ARP协议的基本功能就是通过目标设备的IP地址，查询目标设备的MAC地址，以保证主机间互相通信的顺利进行.\nARP协议和DNS有相像之处。不同点是：DNS实在域名和IP之间解析，另外ARP协议不需要配置服务，而DNS要配置服务才行。\nARP缓存表 在每台安装有TCP/IP协议的设备都会有一个ARP缓存表（windows命令提示符里输入arp -a即可）， 表里的IP地址与MAC地址是一一对应的。\nbash 1 2 3 4 5 6 7 8 9 10 C:\\Users\\CM\u0026gt;arp -a 接口: 192.168.1.103 --- 0x3 Internet 地址 物理地址 类型 192.168.1.1 3c-46-d8-5d-53-87 动态 192.168.1.255 ff-ff-ff-ff-ff-ff 静态 224.0.0.22 01-00-5e-00-00-16 静态 224.0.0.251 01-00-5e-00-00-fb 静态 224.0.0.252 01-00-5e-00-00-fc 静态 239.11.20.1 01-00-5e-0b-14-01 静态 239.255.255.250 01-00-5e-7f-ff-fa 静态 arp常用命令 arp -a 查看所有记录\narp -d 清除arp表\narp -s $ip $mac 将绑定IP和MAC\narp -n 不解析名称打印arp表\nARP缓存是把双刃剑 主机有了arp缓存表，可以加快arp的解析速度，减少局域网内广播风暴。","title":"ARP与ARP Proxy"},{"content":"概述 BGP Border Gateway Protocol 边界网关协议,，是一种运行于TCP上的自治系统AS（Autonomous System）之间的路由可达，并选择最佳路由的距离矢量路由协议。\n早期发布的三个版本分别是BGP-1、BGP-2和BGP-3，1994年开始使用BGP-4，2006年之后单播IPv4网络使用的版本是BGP-4，其他网络（如IPv6等）使用的版本是MP-BGP。\nMP-BGP是对BGP-4进行了扩展，来达到在不同网络中应用的目的，BGP-4原有的消息机制和路由机制并没有改变。MP-BGP在IPv6单播网络上的应用称为BGP4+，在IPv4组播网络上的应用称为MBGP（Multicast BGP）。\n历史 为方便管理规模不断扩大的网络，网络被分成了不同的自治系统。1982年，外部网关协议EGP（Exterior Gateway Protocol）被用于实现在AS之间动态交换路由信息。但是EGP设计得比较简单，只发布网络可达的路由信息，而不对路由信息进行优选，同时也没有考虑环路避免等问题，很快就无法满足网络管理的要求。\nBGP是为取代最初的EGP而设计的另一种外部网关协议。不同于最初的EGP，BGP能够进行路由优选、避免路由环路、更高效率的传递路由和维护大量的路由信息。\n虽然BGP用在AS之间传递路由信息，但并非所有AS之间传递路由信息都要运行BGP。如数据中心上行到Internet的出口上，为了避免Internet海量路由对数据中心内部网络影响，设备采用静态路由代替BGP与外部网络通信。\n受益\nBGP从多方面保证了网络的安全性、灵活性、稳定性、可靠性和高效性：\nBGP采用认证和GTSM的方式，保证了网络的安全性。\nBGP提供了丰富的路由策略，能够灵活的进行路由选路，并且能指导邻居按策略发布路由。\nBGP提供了路由聚合和路由衰减功能用于防止路由振荡，有效提高了网络的稳定性。\nBGP使用TCP作为其传输层协议（端口号为179），并支持BGP与BFD联动、BGP Tracking和BGP GR和NSR，提高了网络的可靠性。\n在邻居数目多、路由量大且大多邻居有相同出口策略场景下，BGP用按组打包技术极大提高了BGP打包发包性能。\nBGP相关名词说明 名词 说明 BGP 边界网关协议（Border Gateway Protocol）是互联网上一个核心的去中心化自治路由协议，它通过维护IP路由表或前缀表来实现自治系统（AS）之间的可达性大多数ISP使用BGP来与其他ISP创建路由连接，特大型的私有IP网络也可以使用BGPBGP的通信对端（对等实体，Peer）通过TCP（端口179）会话交换数据，BGP路由器会周期地发送19字节的保活消息来维护连接。在路由协议中，只有BGP使用TCP作为传输层协议 IBGP 内部边界网关协议。同一个AS内部的两个或多个对等实体之间运行的BGP被称为IBGP IGP 内部网关协议。同一AS内部的对等实体（路由器）之间使用的协议，它存在可扩容性问题：1. 一个IGP内部应该仅有数十（最多小几百）个对等实体2. 对于端点数，也存在限制，一般在数百（最多上千）个Endpoint级别IBGP和IGP都是处理AS内部路由的，仍然需要IGP的原因是：1. IBGP之间是TCP连接，也就意味着IBGP邻居采用的是逻辑连接的方式，两个IBGP连接不一定存在实际的物理链路。所以需要有IGP来提供路由，以完成BGP路由的递归查找2. BGP协议本身实际上并不发现路由，BGP将路由发现的工作全部移交给了IGP协议，它本身着重于路由的控制 EBGP 外部边界网关协议。归属不同的AS的对等实体之间运行的BGP称为EBGP AS 自治系统（Autonomous system），一个组织（例如ISP）管辖下的所有IP网络和路由器的整体参与BGP路由的每个AS都被分配一个唯一的自治系统编号（ASN）。对BGP来说ASN是区别整个相互连接的网络中的各个网络的唯一标识。64512到65535之间的ASN编号保留给专用网络使用 RouteReflector 同一AS内如果有多个路由器参与BGP路由，则它们之间必须配置成全连通的网状结构——任意两个路由器之间都必须配置成对等实体。由于所需要TCP连接数是路由器数量的平方，这就导致了巨大的TCP连接数为了缓解这种问题，BGP支持两种方案：Route Reflector、Confederations路由反射器（Route Reflector）是AS内的一台路由器，其它所有路由器都和RR直接连接，作为RR的客户机。RR和客户机之间建立BGP连接，而客户机之间则不需要相互通信RR的工作步骤如下：1. 从非客户机IBGP对等实体学到的路由，发布给此RR的所有客户机2. 从客户机学到的路由，发布给此RR的所有非客户机和客户机3. 从EBGP对等实体学到的路由，发布给所有的非客户机和客户机RR的一个优势是配置方便，因为只需要在反射器上配置 工作负载 Workload，即运行在Calico节点上的虚机或容器 全互联 全互联网络（Full node-to-node Mesh）是指任何两个Calico节点都进行配对的L3连接模式 BGP 应用 国内IDC机房需要在 CNNIC (中国互联网信息中心)或 APNIC (亚太网络信息中心)申请自己的IP地址段和AS号，然后将自己的IP地址广播到其它网络运营商的AS中，并通过BGP协议将多个AS进行连接，从而实现可自动跨网访问。此时，当用户发出访问请求后，将根据BGP协议的机制自动在已建立连接的多个AS之间为用户提供最佳路由，从而实现不同网络运营商用户的高速访问同一机房资源。\nBGP的运行 BGP使用TCP为传输层协议，TCP端口号179。路由器之间的BGP会话基于TCP连接而建立。运行BGP的路由器被称为BGP发言者（BGP Speaker），或BGP路由器。两个建立BGP会话的路由器互为对等体（或称通信对端/对等实体，peer）。BGP对等体之间交换BGP路由表。\nBGP路由器只发送增量的BGP路由更新，或进行触发更新（不会周期性更新）。\nBGP具有丰富的路径属性和强大的路由策略工具。\nBGP能够承载大批量的路由前缀，用于大规模的网络中。\nIBGP And EBGP 同一个AS自治系统中的两个或多个对等实体之间运行的BGP被称为iBGP（Internal/Interior BGP）。归属不同的AS的对等实体之间运行的BGP称为eBGP（External/Exterior BGP）。在AS边界上与其他AS交换信息的路由器被称作边界路由器（border/edge router），边界路由器之间互为eBGP对端。\niBGP和eBGP的区别主要在于转发路由信息的行为。例如，从eBGP peer获得的路由信息会分发给所有iBGP peer和eBGP peer，但从iBGP peer获得的路由信息仅会分发给所有eBGP peer。所有的iBGP peer之间需要全互联。\n总结\nIBGP（Internal BGP）：位于相同自治系统的BGP路由器之间的BGP邻接关系。\n两台路由器之间要建立IBGP对等体关系，必须满足两个条件：\n两个路由器所属AS需相同（也即AS号相同）。\n在配置BGP时，Peer命令所指定的对等体IP地址要求路由可达，并且TCP连接能够正确建立\nEBGP（External BGP）：位于不同自治系统的BGP路由器之间的BGP邻接关系。\n两台路由器之间要建立EBGP对等体关系，必须满足两个条件：\n两个路由器所属AS不同（也即AS号不同）。\n在配置BGP时，Peer命令所指定的对等体IP地址要求路由可达，并且TCP连接能够正确建立.\n实验：配置BGP R1、R2及R3属于AS 123，R4属于AS 400；\nAS123内的R1、R2及R3运行OSPF，通告各自直连接口（包括三台设备的Loopback0接口），注 意OSPF域的工作范围；所有设备的Loopback0接口地址为 x.x.x.x/32，其中x为设备编号（如R1的接口地址为 1.1.1.1/32）。\nR3与R4之间建立EBGP对等体关系，R2暂时不运行BGP，R1与R3之间建立IBGP对等体关系， 所有的BGP对等体关系基于直连接口建立；R4将直连路由4.4.4.4/32通告到BGP，要求R1能学习到 BGP路由4.4.4.4/32；\n修改BGP配置，使得R1与R3基于Loopback0接口建立IBGP对等体关系\n[eNSP BGP实验](https://cdn.jsdelivr.net/gh/cylonchau/blogs@img/img/basic bgp.zip)\n配置ospf\nbash 1 2 3 4 5 6 7 8 9 10 11 ospf router-id 1.1.1.1 area 0 # 这里声明的路由为单独的，否则声明全部的会使用ospf学到对应的路由 network 10.0.0.0 255.255.255.0 # 这里通过的L0接口进行bgp链接的，所以需要声明该路由。否则ospf学习不到无法链接 network 1.1.1.1 255.255.255.255 [R1-ospf-1-area-0.0.0.0]dis this [V200R003C00] # area 0.0.0.0 network 0.0.0.0 255.255.255.255 配置bgp\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bgp 123 # 设置route-id router-id 1.1.1.1 # 自治系统号码为123 peer 3.3.3.3 as-number 123 # 建立链接的接口使用的L0，如不指定，默认使用出接口做连接。 peer 3.3.3.3 connect-interface LoopBack0 # 设置另外一个路由器 bgp 123 router-id 3.3.3.3 peer 1.1.1.1 as-number 123 peer 1.1.1.1 connect-interface LoopBack0 # 声明一个bgp路由 network 33.33.33.33 255.255.255.255 此时可以看到对应的路由已经学习到了\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 [R1]dis bgp routing-table BGP Local router ID is 1.1.1.1 Status codes: * - valid, \u0026gt; - best, d - damped, h - history, i - internal, s - suppressed, S - Stale Origin : i - IGP, e - EGP, ? - incomplete Total Number of Routes: 2 Network NextHop MED LocPrf PrefVal Path/Ogn i 4.4.4.4/32 10.2.0.2 0 100 0 400i *\u0026gt;i 33.33.33.33/32 3.3.3.3 0 100 0 i 配置一个ebgp\nbash 1 2 3 4 5 6 7 8 9 10 # 这是两个路由器间的bgp配置。因为L0没有对应的互通接口所以使用默认出接口进行链接。 peer 10.2.0.1 as-number 123 # 与123 as里的bgp形成一个对等实体 peer 10.2.0.2 as-number 400 # 与400 as里的bgp形成一个对等实体 # 在R4上声明一个路由 network 4.4.4.4 255.255.255.255 dis bgp ip routing-table\t# 在R3上，可以看到通过eBGP学习到的到4.4.4.4的路由 [R3-bgp]dis ip routing-table 4.4.4.4/32 EBGP 255 0 D 10.2.0.2 GigabitEthernet0/0/1 refresh bgp external import 刷新BGP\nBGP的RR 由于IBGP水平分割的存在，为了保证所有的BGP路由器都能学习到完整的BGP路由，就必须在AS内实现IBGP全互联，这就导致AS内部需要维护大量的BGP连接，从而影响网络性能，路由反射器（Route Reflector，RR）可以“放宽”水平分割原则，解决该问题。\n为保证IBGP对等体之间的连通性，需要在IBGP对等体之间建立全连接关系。假设在一个AS内部有n台设备，那么建立的IBGP连接数就为n(n-1)/2。当设备数目很多时，设备配置将十分复杂，而且配置后网络资源和CPU资源的消耗都很大。在IBGP对等体间使用路由反射器可以解决以上问题。\nBGP反射规则\nBGP RR在接收到BGP路由时\n如果该路由学习自非Client IBGP对等体，则反射给自己所有的Client；\n如果路由学习自Client，则反射给所有非Client IBGP对等体和除了该Client之外的所有Client（华为设备可通过命令关闭RR在Client之间的路由反射行为）；\n如果路由学习自EBGP对等体，则发送给所有Client和非Client IBGP对等体。\nBGP RR的配置\n[BGP RouteReflector.zip](https://cdn.jsdelivr.net/gh/cylonchau/blogs@img/img/BGP RouteReflector.zip)\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 进入系统视图 system-view # 设置路由器的名称 sysname R2 # 进入g0/0/0接口 in g0/0/0 ip address 10.0.0.2 24 # 配置l0口 in l0 ip address 2.2.2.2 32 # 只有R1需要配置次步骤 in g0/0/1 ip address 10.1.0.1 24 # 查看接口的信息 dis ip in b\t配置ospf\ntext 1 2 3 4 5 ospf router-id 1.1.1.1 area 0 network 1.1.1.1 0.0.0.0 network 10.0.0.0 0.0.0.255 network 10.1.0.0 0.0.0.255 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 bgp 123 router-id 1.1.1.1 peer 2.2.2.2 as-number 123 peer 2.2.2.2 connect-interface LoopBack0 peer 3.3.3.3 as-number 123 peer 3.3.3.3 connect-interface LoopBack0 # ipv4-family unicast undo synchronization peer 2.2.2.2 enable peer 2.2.2.2 reflect-client peer 3.3.3.3 enable peer 3.3.3.3 reflect-client 配置路由反射器反射客户端\nbash 1 2 3 4 # 此处是1.1.1.1的配置 # 以1.1.1.1 为中心 指定2.2.2.2为反射客户端 peer 2.2.2.2 reflect-client peer 3.3.3.3 reflect-client ","permalink":"https://www.161616.top/dynamic-routing-bgp/","summary":"概述 BGP Border Gateway Protocol 边界网关协议,，是一种运行于TCP上的自治系统AS（Autonomous System）之间的路由可达，并选择最佳路由的距离矢量路由协议。\n早期发布的三个版本分别是BGP-1、BGP-2和BGP-3，1994年开始使用BGP-4，2006年之后单播IPv4网络使用的版本是BGP-4，其他网络（如IPv6等）使用的版本是MP-BGP。\nMP-BGP是对BGP-4进行了扩展，来达到在不同网络中应用的目的，BGP-4原有的消息机制和路由机制并没有改变。MP-BGP在IPv6单播网络上的应用称为BGP4+，在IPv4组播网络上的应用称为MBGP（Multicast BGP）。\n历史 为方便管理规模不断扩大的网络，网络被分成了不同的自治系统。1982年，外部网关协议EGP（Exterior Gateway Protocol）被用于实现在AS之间动态交换路由信息。但是EGP设计得比较简单，只发布网络可达的路由信息，而不对路由信息进行优选，同时也没有考虑环路避免等问题，很快就无法满足网络管理的要求。\nBGP是为取代最初的EGP而设计的另一种外部网关协议。不同于最初的EGP，BGP能够进行路由优选、避免路由环路、更高效率的传递路由和维护大量的路由信息。\n虽然BGP用在AS之间传递路由信息，但并非所有AS之间传递路由信息都要运行BGP。如数据中心上行到Internet的出口上，为了避免Internet海量路由对数据中心内部网络影响，设备采用静态路由代替BGP与外部网络通信。\n受益\nBGP从多方面保证了网络的安全性、灵活性、稳定性、可靠性和高效性：\nBGP采用认证和GTSM的方式，保证了网络的安全性。\nBGP提供了丰富的路由策略，能够灵活的进行路由选路，并且能指导邻居按策略发布路由。\nBGP提供了路由聚合和路由衰减功能用于防止路由振荡，有效提高了网络的稳定性。\nBGP使用TCP作为其传输层协议（端口号为179），并支持BGP与BFD联动、BGP Tracking和BGP GR和NSR，提高了网络的可靠性。\n在邻居数目多、路由量大且大多邻居有相同出口策略场景下，BGP用按组打包技术极大提高了BGP打包发包性能。\nBGP相关名词说明 名词 说明 BGP 边界网关协议（Border Gateway Protocol）是互联网上一个核心的去中心化自治路由协议，它通过维护IP路由表或前缀表来实现自治系统（AS）之间的可达性大多数ISP使用BGP来与其他ISP创建路由连接，特大型的私有IP网络也可以使用BGPBGP的通信对端（对等实体，Peer）通过TCP（端口179）会话交换数据，BGP路由器会周期地发送19字节的保活消息来维护连接。在路由协议中，只有BGP使用TCP作为传输层协议 IBGP 内部边界网关协议。同一个AS内部的两个或多个对等实体之间运行的BGP被称为IBGP IGP 内部网关协议。同一AS内部的对等实体（路由器）之间使用的协议，它存在可扩容性问题：1. 一个IGP内部应该仅有数十（最多小几百）个对等实体2. 对于端点数，也存在限制，一般在数百（最多上千）个Endpoint级别IBGP和IGP都是处理AS内部路由的，仍然需要IGP的原因是：1. IBGP之间是TCP连接，也就意味着IBGP邻居采用的是逻辑连接的方式，两个IBGP连接不一定存在实际的物理链路。所以需要有IGP来提供路由，以完成BGP路由的递归查找2. BGP协议本身实际上并不发现路由，BGP将路由发现的工作全部移交给了IGP协议，它本身着重于路由的控制 EBGP 外部边界网关协议。归属不同的AS的对等实体之间运行的BGP称为EBGP AS 自治系统（Autonomous system），一个组织（例如ISP）管辖下的所有IP网络和路由器的整体参与BGP路由的每个AS都被分配一个唯一的自治系统编号（ASN）。对BGP来说ASN是区别整个相互连接的网络中的各个网络的唯一标识。64512到65535之间的ASN编号保留给专用网络使用 RouteReflector 同一AS内如果有多个路由器参与BGP路由，则它们之间必须配置成全连通的网状结构——任意两个路由器之间都必须配置成对等实体。由于所需要TCP连接数是路由器数量的平方，这就导致了巨大的TCP连接数为了缓解这种问题，BGP支持两种方案：Route Reflector、Confederations路由反射器（Route Reflector）是AS内的一台路由器，其它所有路由器都和RR直接连接，作为RR的客户机。RR和客户机之间建立BGP连接，而客户机之间则不需要相互通信RR的工作步骤如下：1. 从非客户机IBGP对等实体学到的路由，发布给此RR的所有客户机2. 从客户机学到的路由，发布给此RR的所有非客户机和客户机3. 从EBGP对等实体学到的路由，发布给所有的非客户机和客户机RR的一个优势是配置方便，因为只需要在反射器上配置 工作负载 Workload，即运行在Calico节点上的虚机或容器 全互联 全互联网络（Full node-to-node Mesh）是指任何两个Calico节点都进行配对的L3连接模式 BGP 应用 国内IDC机房需要在 CNNIC (中国互联网信息中心)或 APNIC (亚太网络信息中心)申请自己的IP地址段和AS号，然后将自己的IP地址广播到其它网络运营商的AS中，并通过BGP协议将多个AS进行连接，从而实现可自动跨网访问。此时，当用户发出访问请求后，将根据BGP协议的机制自动在已建立连接的多个AS之间为用户提供最佳路由，从而实现不同网络运营商用户的高速访问同一机房资源。\nBGP的运行 BGP使用TCP为传输层协议，TCP端口号179。路由器之间的BGP会话基于TCP连接而建立。运行BGP的路由器被称为BGP发言者（BGP Speaker），或BGP路由器。两个建立BGP会话的路由器互为对等体（或称通信对端/对等实体，peer）。BGP对等体之间交换BGP路由表。\nBGP路由器只发送增量的BGP路由更新，或进行触发更新（不会周期性更新）。\nBGP具有丰富的路径属性和强大的路由策略工具。","title":"动态路由- BGP"},{"content":"Calico针对容器、虚拟机的开源网络和网络安全解决方案。是纯三层的数据中心网络方案。\nCalico在每一个计算节点利用Linux Kernel实现了一个高效的虚拟路由器vRouter来负责数据转发，而每个vRouter通过BGP协议负责把自己上运行的workload的路由信息向整个Calico网络内传播。（小规模部署可以直接互联 BGP full mesh，大规模下可通过指定的BGP route reflector来完成）。 这样保证最终所有的workload之间的数据流量都是通过IP路由的方式完成互联的。Calico节点组网可以直接利用数据中心的网络结构（无论是L2或者L3），不需要额外的NAT，隧道或者Overlay Network。\nCalico还基于iptables还提供了丰富而灵活的网络Policy，保证通过各个节点上的ACLs来提供Workload的多租户隔离、安全组以及其他可达性限制等功能。\ncalico组件 在Kubernetes平台之上calico/node容器会通过DaemonSet部署到每个节点，并运行三个守护程序：\nFelix：用于管理路由规则，负责状态上报。 BIRD：BGP的客户端，用于将Felix的路由信息加载到内核中，同时负责路由信息在集群中的分发。 confd：用于监视Calico存储（etcd）中的配置变更并更新BIRD的配置文件。 calicoctl使用问题\ntext 1 Failed to create Calico API client: invalid configuration: no configuration has been provided 默认情况下，calicoctl 将使用位于的默认KUBECONFIG从 Kubernetes APIServer 读取$(HOME)/.kube/config 。\n如果默认的 KUBECONFIG 不存在，或者想从指定的存储访问信息，则需要单独配置。\nbash 1 2 3 export DATASTORE_TYPE=kubernetes export DATASTORE_TYPE=etcdv3 export KUBECONFIG=~/.kube/config reference for\ncalico 安装配置 开始前准备\n确定calico数据存储\nCalico同时支持kubernetes api和etcd数据存储。官方给出的建议是在本地部署中使用K8S API，仅支持Kubernetes模式。而官方给出的etcd则是混合部署（Calico作为Kubernetes和OpenStack的网络插件运行）的最佳数据存储。\n使用kubernetes api作为数据存储的安装\ntext 1 2 curl https://docs.projectcalico.org/manifests/calico.yaml -O kubectl apply -f calico.yaml 修改Pod CIDR\nCalico默认的Pod CIDR使用的是192.168.0.0/16，这里一般使用与controller-manager中的--cluster-cidr 保持一,取消资源清单内的 CALICO_IPV4POOL_CIDR变量的注释，并将其设置为与所选Pod CIDR相同的值。\ncalico的IP分配范围\nCalico IPAM从ipPool分配IP地址。修改Pod的默认IP范围则修改清单calico.yaml中的 CALICO_IPV4POOL_CIDR\n配置Calico的 IP in IP\n默认情况下，Calico中的IPIP已经禁用，这里使用的v3.17.2 低版本默认会使用IPIP\n要开启IPIP mode则需要修改配置清单内的 CALICO_IPV4POOL_IPIP 环境变量改为 always\n修改secret\nyaml 1 2 3 4 5 6 7 8 9 10 11 # Populate the following with etcd TLS configuration if desired, but leave blank if # not using TLS for etcd. # The keys below should be uncommented and the values populated with the base64 # encoded contents of each file that would be associated with the TLS data. # Example command for encoding a file contents: cat \u0026lt;file\u0026gt; | base64 -w 0 # etcd的ca etcd-ca: # 填写上面命令编码后的值 # etcd客户端key etcd-key: # 填写上面命令编码后的值 # etcd客户端访问证书 etcd-cert: # 填写上面命令编码后的值 修改configMap\nyaml 1 2 3 4 5 6 etcd_endpoints: \u0026#34;https://10.0.0.6:2379\u0026#34; # If you\u0026#39;re using TLS enabled etcd uncomment the following. # You must also populate the Secret below with these files. etcd_ca: \u0026#34;/calico-secrets/etcd-ca\u0026#34; etcd_cert: \u0026#34;/calico-secrets/etcd-cert\u0026#34; etcd_key: \u0026#34;/calico-secrets/etcd-key\u0026#34; 在卷装载中设置440将解决此问题\n/calico-secrets/etcd-cert: permission denied\ntext 1 2 2021-02-08 02:15:10.485 [INFO][1] main.go 88: Loaded configuration from environment config=\u0026amp;config.Config{LogLevel:\u0026#34;info\u0026#34;, WorkloadEndpointWorkers:1, ProfileWorkers:1, PolicyWorkers:1, NodeWorkers:1, Kubeconfig:\u0026#34;\u0026#34;, DatastoreType:\u0026#34;etcdv3\u0026#34;} 2021-02-08 02:15:10.485 [FATAL][1] main.go 101: Failed to start error=failed to build Calico client: could not initialize etcdv3 client: open /calico-secrets/etcd-cert: permission denied 找到资源清单内的对应容器（calico-kube-controllers）的配置。\nyaml 1 2 3 4 5 6 7 volumes: # Mount in the etcd TLS secrets with mode 400. # See https://kubernetes.io/docs/concepts/configuration/secret/ - name: etcd-certs secret: secretName: calico-etcd-secrets defaultMode: 0400 # 改为0440 使用单独的etcd作为calico数据存储还需要修改calicoctl数据存储访问配置\ncalicoctl 在默认情况下，查找配置文件的路径为/etc/calico/calicoctl.cfg上。可以使用--config覆盖此选项默认配置。\n如果calicoctl无法获得配置文件，将检查环境变量。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: projectcalico.org/v3 kind: CalicoAPIConfig metadata: spec: datastoreType: etcdv3 etcdEndpoints: \u0026#34;https://10.0.0.6:2379\u0026#34; etcdCACert: | # 这里填写etcd ca证书文件的内容，无需转码base64 etcdCert: | # 这里填写etcd client证书文件的内容，无需转码base64 etcdKey: | # 这里填写etcd client秘钥文件的内容，无需转码base64 reference：\nSecret permission denied\nconfiguration calicoctl\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 eb 7 21:25:13 master01 etcd: recognized environment variable ETCD_NAME, but unused: shadowed by corresponding flag Feb 7 21:25:13 master01 etcd: unrecognized environment variable ETCD_SERVER_NAME=hk.etcd Feb 7 21:25:13 master01 etcd: recognized environment variable ETCD_DATA_DIR, but unused: shadowed by corresponding flag Feb 7 21:25:13 master01 etcd: recognized environment variable ETCD_LISTEN_CLIENT_URLS, but unused: shadowed by corresponding flag Feb 7 21:25:13 master01 etcd: etcd Version: 3.3.11 Feb 7 21:25:13 master01 etcd: Git SHA: 2cf9e51 Feb 7 21:25:13 master01 etcd: Go Version: go1.10.3 Feb 7 21:25:13 master01 etcd: Go OS/Arch: linux/amd64 Feb 7 21:25:13 master01 etcd: setting maximum number of CPUs to 2, total number of available CPUs is 2 Feb 7 21:25:13 master01 etcd: the server is already initialized as member before, starting as etcd member... Feb 7 21:25:13 master01 etcd: peerTLS: cert = /etc/etcd/pki/peer.crt, key = /etc/etcd/pki/peer.key, ca = , trusted-ca = /etc/etcd/pki/ca.crt, client-cert-auth = true, crl-file = Feb 7 21:25:13 master01 etcd: listening for peers on https://10.0.0.5:2380 Feb 7 21:25:13 master01 etcd: listening for client requests on 10.0.0.5:2379 Feb 7 21:25:13 master01 etcd: panic: freepages: failed to get all reachable pages (page 3471766746605708656: out of bounds: 1633) 集群节点损坏\ntext 1 panic: freepages: failed to get all reachable pages (page 3471766746605708656: out of bounds: 1633) 这是k8s不支持当前calico版本的原因, calico版本与k8s版本支持关系可到calico官网查看:\nbash 1 error: unable to recognize \u0026#34;calico.yaml\u0026#34;: no matches for kind \u0026#34;PodDisruptionBudget\u0026#34; in version \u0026#34;policy/v1\u0026#34; 配置SW\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 system-view sysname SW1 vlan batch 10 20 30 interface GigabitEthernet0/0/1 port link-type trunk port trunk allow-pass vlan 10 20 30 interface GigabitEthernet0/0/2 port link-type trunk port trunk allow-pass vlan 10 20 30 interface GigabitEthernet0/0/3 port link-type trunk port trunk allow-pass vlan 10 20 30 配置路由器间的ospf\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface l0 ip address 1.1.1.1 32 quit ospf router-id 1.1.1.1 area 0 network 1.1.1.1 0.0.0.0 network 10.0.0.253 0.0.0.0 dis this interface l0 ip address 2.2.2.2 32 quit ospf router-id 2.2.2.2 area 0 network 2.2.2.2 0.0.0.0 network 10.0.0.254 0.0.0.0 dis this 配置两个k8s节点与路由器之间的bgp\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 system-view sysname R1 interface GigabitEthernet0/0/0 ip address 10.0.0.253 24 dis this quit bgp 64512 router-id 10.0.0.253 peer 10.0.0.5 as-number 64512 peer 10.0.0.5 reflect-client dis ip interface brief system-view sysname R2 interface GigabitEthernet0/0/0 ip address 10.0.0.254 24 dis this quit bgp 63400 router-id 10.0.0.254 peer 10.0.0.6 as-number 63400 peer 10.0.0.6 reflect-client dis ip interface brief bgp 64512 router-id 10.0.0.253 peer 2.2.2.2 as-number 63400 bgp 63400 router-id 10.0.0.254 peer 1.1.1.1 as-number 64512 ","permalink":"https://www.161616.top/calico-network-cni/","summary":"Calico针对容器、虚拟机的开源网络和网络安全解决方案。是纯三层的数据中心网络方案。\nCalico在每一个计算节点利用Linux Kernel实现了一个高效的虚拟路由器vRouter来负责数据转发，而每个vRouter通过BGP协议负责把自己上运行的workload的路由信息向整个Calico网络内传播。（小规模部署可以直接互联 BGP full mesh，大规模下可通过指定的BGP route reflector来完成）。 这样保证最终所有的workload之间的数据流量都是通过IP路由的方式完成互联的。Calico节点组网可以直接利用数据中心的网络结构（无论是L2或者L3），不需要额外的NAT，隧道或者Overlay Network。\nCalico还基于iptables还提供了丰富而灵活的网络Policy，保证通过各个节点上的ACLs来提供Workload的多租户隔离、安全组以及其他可达性限制等功能。\ncalico组件 在Kubernetes平台之上calico/node容器会通过DaemonSet部署到每个节点，并运行三个守护程序：\nFelix：用于管理路由规则，负责状态上报。 BIRD：BGP的客户端，用于将Felix的路由信息加载到内核中，同时负责路由信息在集群中的分发。 confd：用于监视Calico存储（etcd）中的配置变更并更新BIRD的配置文件。 calicoctl使用问题\ntext 1 Failed to create Calico API client: invalid configuration: no configuration has been provided 默认情况下，calicoctl 将使用位于的默认KUBECONFIG从 Kubernetes APIServer 读取$(HOME)/.kube/config 。\n如果默认的 KUBECONFIG 不存在，或者想从指定的存储访问信息，则需要单独配置。\nbash 1 2 3 export DATASTORE_TYPE=kubernetes export DATASTORE_TYPE=etcdv3 export KUBECONFIG=~/.kube/config reference for\ncalico 安装配置 开始前准备\n确定calico数据存储\nCalico同时支持kubernetes api和etcd数据存储。官方给出的建议是在本地部署中使用K8S API，仅支持Kubernetes模式。而官方给出的etcd则是混合部署（Calico作为Kubernetes和OpenStack的网络插件运行）的最佳数据存储。\n使用kubernetes api作为数据存储的安装\ntext 1 2 curl https://docs.projectcalico.org/manifests/calico.yaml -O kubectl apply -f calico.","title":"calico network cni网络方案"},{"content":"在因特网中，网络连接设备用来控制网络流量和保证网络数据传输质量。常见的网络连接设备有集线器（Hub）、网桥（Bridge）、交换机（Switch）和路由器（Router）。\n路由器是一种典型的网络连接设备，用来进行路由选择和报文转发。路由器根据收到报文的目的地址选择一条合适的路径（包含一个或多个路由器的网络），然后将报文传送到下一个路由器，路径终端的路由器负责将报文送交目的主机。\n路由（routing）就是报文从源地址传输到目的地址的活动。路由发生在OSI网络参考模型中的第三层即网络层。当报文从路由器到目的网段有多条路由可达时，路由器可以根据路由表中最佳路由进行转发。最佳路由的选取与发现此路由的路由协议的优先级、路由的度量有关。当多条路由的协议优先级。\n路由是数据通信网络中最基本的要素。路由信息就是指导报文发送的路径信息，路由的过程就是报文转发的过程。\n根据路由目的地的不同，路由可划分为：\n网段路由：目的地为网段，IPv4地址子网掩码长度小于32位或IPv6地址前缀长度小于128位。\n主机路由：目的地为主机，IPv4地址子网掩码长度为32位或IPv6地址前缀长度为128位。\n根据目的地与该路由器是否直接相连，路由又可划分为：\n直连路由：目的地所在网络与路由器直接相连。\n间接路由：目的地所在网络与路由器非直接相连。\n根据目的地址类型的不同，路由还可以分为：\n单播路由：表示将报文转发的目的地址是一个单播地址。\n组播路由：表示将报文转发的目的地址是一个组播地址。\n路由的优先级 对于相同的目的地，不同的路由协议（包括静态路由）可能会发现不同的路由，但这些路由并不都是最优的。事实上，在某一时刻，到某一目的地的当前路由仅能由唯一的路由协议来决定。为了判断最优路由，各路由协议（包括静态路由）都被赋予了一个优先级，当存在多个路由信息源时，具有较高优先级（取值较小）的路由协议发现的路由将成为最优路由，并将最优路由放入本地路由表中。\n路由协议 优先级 DIRECT 0 OSPF 10 IS-IS IS-IS Level1 15\nIS-IS Level 2 由网关加入的路由 50 路由器发现的路由 55 静态路由 60 UNR（User Network Route） DHCP（Dynamic Host Configuration Protocol）：60AAA-Download：60IP Pool：61Frame：62Host：63NAT（Network Address Translation）：64IPSec（IP Security）：65NHRP（Next Hop Resolution Protocol）：65PPPoE（Point-to-Point Protocol over Ethernet）：65 Berkeley RIP 100 点对点接口聚集的路由 110 OSPF的扩展路由 140 OSPF ASE 150 OSPF NSSA 150 BGP 170 EGP 200 IBGP 255 EBGP 255 其中，0表示直接连接的路由，255表示任何来自不可信源端的路由；数值越小表明优先级越高。 除直连路由（DIRECT）外，各种路由协议的优先级都可由用户手工进行配置。另外，每条静态路由的优先级都可以不相同。 路由器根据路由转发数据包，路由可通过手动配置和使用动态路由算法计算产生，其中手动配置的路由就是静态路由。\n静态路由比动态路由使用更少的带宽，并且不占用CPU资源来计算和分析路由更新。但是当网络发生故障或者拓扑发生变化后，静态路由不会自动更新，必须手动重新配置。静态路由有5个主要的参数：目的地址、掩码、出接口、下一跳、优先级。\n目的地址和掩码: IPv4的目的地址为点分十进制格式，掩码可以用点分十进制表示，也可用掩码长度（即掩码中连续‘1’的位数）表示。当目的地址和掩码都为零时，表示静态缺省路由。\n出接口和下一跳地址: 在配置静态路由时，根据不同的出接口类型，指定出接口和下一跳地址。\n对于点到点类型的接口，只需指定出接口。因为指定发送接口即隐含指定了下一跳地址，这时认为与该接口相连的对端接口地址就是路由的下一跳地址。\n对于NBMA（Non Broadcast Multiple Access）类型的接口（如ATM接口），配置下一跳IP地址。因这类接口支持点到多点网络，除了配置静态路由外，还需在链路层建立IP地址到链路层地址的映射，这种情况下，不需要指定 出接口。\n对于广播类型的接口（如以太网接口）和VT（Virtual-template）接口，必须指定通过该接口发送时对应的下一跳地址。因为以太网接口是广播类型的接口，而VT接口下可以关联多个虚拟访问接口（Virtual Access Interface），这都会导致出现多个下一跳，无法唯一确定下一跳。\n静态路由优先级 对于不同的静态路由，可以为它们配置不同的优先级，优先级数字越小优先级越高。配置到达相同目的地的多条静态 路由，如果指定相同优先级，则可实现负载分担；如果指定不同优先级，则可实现路由备份。\n实验： 在eNsp实现静态路由配置 通信是双向的，因此要留意往返流量（的路由）。\n路由的行为是逐跳的，因此需保证沿途的每一台路由器都有路由\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 \u0026lt;Huawei\u0026gt;system-view [Huawei]sysname ar1 [ar1]interface g0/0/0 [ar1-GigabitEthernet0/0/0]ip address 192.168.10.1 24 Jan 23 2021 20:52:30-08:00 ar1 %%01IFNET/4/LINK_STATE(l)[0]:The line protocol IP on the interface GigabitEthernet0/0/0 has entered the UP state. [ar1-GigabitEthernet0/0/0]dis this [V200R003C00] # interface GigabitEthernet0/0/0 ip address 192.168.10.1 255.255.255.0 # return [Huawei-GigabitEthernet0/0/1]display ip interface brief *down: administratively down ^down: standby (l): loopback (s): spoofing The number of interface that is UP in Physical is 3 The number of interface that is DOWN in Physical is 0 The number of interface that is UP in Protocol is 3 The number of interface that is DOWN in Protocol is 0 Interface IP Address/Mask Physical Protocol GigabitEthernet0/0/0 192.168.20.1/24 up up GigabitEthernet0/0/1 192.168.30.1/24 up up NULL0 unassigned up up(s) # 查看所有路由 dis ip routing-table # 删除命令 undo ip address 192.168.10.1 255.255.255.0 # 查看bgp协议路由 dis ip routing-table # 设置静态路由 ip routing-static 192.168.0.0 24 192.168.1.1 # 保存配置命令 \u0026lt;用户模式\u0026gt; save即可保存 eNSP实验拓扑：静态路由.zip\nLinux中的路由 Linux系统的route 与ip route 命令用于显示和操作IP路由表（show/manipulate the IP routing table)。要实现两个不同的子网之间的通信，需要一台连接两个网络的路由器，或者同时位于两个网络的网关来实现。在Linux系统中，设置路由通常是为了解决以下问题：该Linux系统在一个局域网中，局域网中有一个网关，能够让机器访问internet，那么就需要将网关地址设置为该Linux机器的默认路由。需要注意的是，命令行执行的route操作不会持久化，当网卡重启或者机器重启之后，该路由就失效了；可以在/etc/rc.local中添加route命令来保证该路由设置永久有效。\nroute -n 命令显示的字段说明\n字段 说明 Destination 目标网段或是主机 Gateway 网关地址 [*标识目标是本机所属的网络，不需要路由] Genmask 网络掩码 flags 标记[可选如下]U - 路由是活动的H - 目标是一个主机G - 路由指向网关R - 恢复动态路由产生的表项D - 由动态路由后台程序动态的安装M - 由路由的后台程序修改! - 拒绝路由 Metric 路由距离，到达指定网络需要的中转数[Linux内核中没有引用] Ref 路由项引用册数[Linux内核中没有引用] Use 次路由项被路由软件查找的次数 Iface 该路由表项输出的路由接口 Linux开启IP转发功能，Linux主机可以是一个路由器 sysctl -w net.ipv4.ip_forward=1\n","permalink":"https://www.161616.top/static-routing/","summary":"在因特网中，网络连接设备用来控制网络流量和保证网络数据传输质量。常见的网络连接设备有集线器（Hub）、网桥（Bridge）、交换机（Switch）和路由器（Router）。\n路由器是一种典型的网络连接设备，用来进行路由选择和报文转发。路由器根据收到报文的目的地址选择一条合适的路径（包含一个或多个路由器的网络），然后将报文传送到下一个路由器，路径终端的路由器负责将报文送交目的主机。\n路由（routing）就是报文从源地址传输到目的地址的活动。路由发生在OSI网络参考模型中的第三层即网络层。当报文从路由器到目的网段有多条路由可达时，路由器可以根据路由表中最佳路由进行转发。最佳路由的选取与发现此路由的路由协议的优先级、路由的度量有关。当多条路由的协议优先级。\n路由是数据通信网络中最基本的要素。路由信息就是指导报文发送的路径信息，路由的过程就是报文转发的过程。\n根据路由目的地的不同，路由可划分为：\n网段路由：目的地为网段，IPv4地址子网掩码长度小于32位或IPv6地址前缀长度小于128位。\n主机路由：目的地为主机，IPv4地址子网掩码长度为32位或IPv6地址前缀长度为128位。\n根据目的地与该路由器是否直接相连，路由又可划分为：\n直连路由：目的地所在网络与路由器直接相连。\n间接路由：目的地所在网络与路由器非直接相连。\n根据目的地址类型的不同，路由还可以分为：\n单播路由：表示将报文转发的目的地址是一个单播地址。\n组播路由：表示将报文转发的目的地址是一个组播地址。\n路由的优先级 对于相同的目的地，不同的路由协议（包括静态路由）可能会发现不同的路由，但这些路由并不都是最优的。事实上，在某一时刻，到某一目的地的当前路由仅能由唯一的路由协议来决定。为了判断最优路由，各路由协议（包括静态路由）都被赋予了一个优先级，当存在多个路由信息源时，具有较高优先级（取值较小）的路由协议发现的路由将成为最优路由，并将最优路由放入本地路由表中。\n路由协议 优先级 DIRECT 0 OSPF 10 IS-IS IS-IS Level1 15\nIS-IS Level 2 由网关加入的路由 50 路由器发现的路由 55 静态路由 60 UNR（User Network Route） DHCP（Dynamic Host Configuration Protocol）：60AAA-Download：60IP Pool：61Frame：62Host：63NAT（Network Address Translation）：64IPSec（IP Security）：65NHRP（Next Hop Resolution Protocol）：65PPPoE（Point-to-Point Protocol over Ethernet）：65 Berkeley RIP 100 点对点接口聚集的路由 110 OSPF的扩展路由 140 OSPF ASE 150 OSPF NSSA 150 BGP 170 EGP 200 IBGP 255 EBGP 255 其中，0表示直接连接的路由，255表示任何来自不可信源端的路由；数值越小表明优先级越高。 除直连路由（DIRECT）外，各种路由协议的优先级都可由用户手工进行配置。另外，每条静态路由的优先级都可以不相同。 路由器根据路由转发数据包，路由可通过手动配置和使用动态路由算法计算产生，其中手动配置的路由就是静态路由。","title":"静态路由"},{"content":" 本文是关于深入理解Kubernetes网络原理系列第2章 深入理解Kubernetes Pod网络原理 - 网络名称空间 深入理解Kubernetes Pod网络原理 - Linux虚拟网络技术 深入理解Kubernetes Pod网络原理 - CNI 深入理解Kubernetes Pod网络原理 - 跟随 flannel 学习CNI原理 深入理解Kubernetes Pod网络原理 - 跟随 flannel + multus 剖析 Chained Plugins 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 1 (Shell) 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 2 (libcni) 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 1 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 2 深入理解Kubernetes Pod网络原理 - Pod网络排错思路 Overview 本文将介绍Kubernetes中使用的相关虚拟网络功能，目的是为了任何无相关网络背景经历的人都可以了解这些技术在kubernetes中式如何应用的。\nVLAN VLAN (Virtual local area networks)是逻辑上的LAN而不受限于同一物理网络交换机上。同样的VLAN也可以将同一台交换机/网桥下的设备/划分为不同的子网。\nVLAN 区分的广播域的标准是VLAN ID，此功能是Linux内核3.8中引入的\n在Linux中创建一个VLAN\nbash 1 ip link add link eth0 name eth0.2 type vlan id 2 VETH [1] VETH (Virtual Ethernet device)，是一个本地的以太网隧道，创建出的设备是成对的，通常会存在两个名称空间内，例如在docker中创建出的设备一端在root名称空间内，一端被挂在到容器的名称空间内。\n图：veth topology Source：https://medium.com/@arpitkh96/basics-of-container-networking-with-linux-part-1-3a3cdc64c87a\nbash 1 ip link add \u0026lt;p1-name\u0026gt; type veth peer name \u0026lt;p2-name\u0026gt; Bridge Linux bridge（又称为网桥、VLAN交换机）是Linux内核中集成的功能，用来做tcp/ip做二层协议交换的设备，虽然是软件实现的，但它与普通的二层物理交换机功能一样。bridge 就是为了解决虚拟机网卡连接问题。可以添加若干个网络设备到 bridge 上作为其接口，添加到 bridge 上的设备被设置为只接受二层数据帧并且转发所有收到的数据包到 bridge 中。\n由于 Linux bridge 是二层设备，故数据包是根据MAC地址而不是IP转发的。因此所有协议都可以透明地通过网桥。Linux bridge 广泛用于虚拟机，名称空间等。\n图：Linux Bridge Source：https://kbespalov.medium.com/linux-linux-bridge-7e0e887edd01\nMACVLAN [2] MACVLAN 允许在一个物理接口创建多个子接口，并且每个子接口都拥有一个随机生成的MAC地址，与IP地址。\nMACVLAN 中子接口不能与与父接口直接通讯，例如在虚拟化环境中，容器是不能ping通宿主机的IP，如果子接口需要和父接口进行通讯，需要将子接口分配给父接口。\n图：MACVLAN Source：https://hicu.be/bridge-vs-macvlan\n从 Linux 内核3.0起，MAC已经是linux内核中的一部分。\n查看内核是否加载 lsmod | grep macvlan\n加载macvlan模块 modprobe macvlan\n如果需要每次启动时都加载该模块，echo \u0026quot;macvlan\u0026quot; \u0026gt;\u0026gt; /etc/modules\nMACVLAN的限制 限制：\n使用MACVLAN技术需要开启网卡的混杂模式 授予NIC支持的MAC数量限制，超出限制会影响性能 MACVLAN不能工作在wireless网络环境中 [3] MACVLAN的模式 MACVLAN又五种类型：\nPrivate VEPA Bridge Passthru Source Private mode Private模式下的同一物理接口下的子接口将不允许通讯，即使物理设备支持hairpin也不行。\n图：MACVLAN Private mod Source：https://hicu.be/bridge-vs-macvlan\nhairpin 有多个名称，U-turn NAT, NAT loopback，实际上就是一种网络转换，在一个网络中的两个设备使用外部IP进行通讯。在MACVLAN这个例子中，veth1发往veth2的流量通过外部设备switch进行一个回转，这个行为称为为 hairpin turn。从上图也可以看出，是一个U形的转换。\nVPEA mode VPEA 将会将来自子接口的通过父接口转发，这将要求物理交换机需要支持 hairpin\n图：MACVLAN VPEA mode Source：https://hicu.be/bridge-vs-macvlan\nBridge mode Bridge 是指父接口与所有子接口是通过网桥相连的，因为连接在网桥上，不需要学习MAC地址。这也是容器网络中常用到的模式\n图：MACVLAN bridge mode Source：https://hicu.be/bridge-vs-macvlan\nPassthru passthru 是 pass through，由名字也可以得知，是指允许单个VM与物理接口连接。\n图：MACVLAN passthru mode Source：https://hicu.be/bridge-vs-macvlan\nSource 这个模式是Linux中的一个 Patch，是流量仅允许被允许的MAC地址列表\nIPVLAN IPVLAN与MACVLAN类似，只有一个不同的地方是，IPVLAN所有的MAC地址都是一样的，就是使用相同的MAC地址去创建子接口。\n图：IPVLAN Source：https://hicu.be/bridge-vs-macvlan\nIPVLAN modes 在使用IPVLAN时，只能选择下面两种模式中的一种，选择后，所有的子节接口将工作在这个模式下。\nL2 IPVLAN L2模式与 MACVLAN 的 brigde 模式工作原理很相似，父接口是作为交换机的角色，同一个物理接口上的子接口可以通过父接口来转发数据，而如果要发送数据到其他的网络，报文则会通过父接口的路由转发出去。\n图：IPVLAN L2 Source：https://hicu.be/bridge-vs-macvlan\nNotes: IPVLAN 是 linux kernel 比较新的特性，linux kernel 3.19 开始支持 ipvlan，但是比较稳定推荐的版本是 \u0026gt;=4.2\nL3 在 L3 模式下也就是 物理接口 是作为路由器，这种情况下就要求子接口之间需要处在不同子网中才可以。因为广播域是 L2 的，所以 IPVLAN L3 不支持多播和广播。\n图：IPVLAN L3 Source：https://hicu.be/bridge-vs-macvlan\nIPVLAN与MACVLAN如何选择 在于MACVLAN在wireless环境中的不友好，wireless选择 IPVLAN 对于外部设备有MAC地址限制的，或者混杂模式限制NIC性能 VxLAN VxLAN Introduction VxLAN (Virtual eXtensible Local Area Network) 是一种 MAC-over-IP 或者称为 UDP隧道机制，本质上来说是使用网络隧道技术将L3网络扩展为一个L2网络。\n为什么将 VxLAN 视为L2网络呢？ 虽说 VxLAN 是将L2的以太网帧封装到UDP报文中（L2 over L4）中，并在L3网络中传输。但是 VxLAN 最终是基于 MAC 地址的二层网络（可以无需路由设备），而不像 IPIP 的隧道技术网络通讯是基于路由的。\n下图是一个使用 VxLAN 技术的网络拓扑，整个篮筐部分可以视为一个二层的虚拟交换机，连接的各设备都可以直连。\n图：VXLAN tunneling technology Source：https://support.huawei.com/enterprise/en/doc/EDOC1100086966\nVNI VNI (VXLAN Network Identifier) 是类似于VLAN ID的标识符，不同的VNI 之间不能进行二层通讯\nVEPT VTEP (VxLAN tunnel endpoint) 是指数据包封包与解包的实体，VTEP 既可以是一台独立的网络设备，也可以是一个基于软件的虚拟交换机。当源服务器发出包时，会在 VTEP 上封装成 VxLAN 格式的报文；当传送到对端时，会在对端的 VTEP 进行解包\n下图是一个VxLAN网络拓扑图，其中建立隧道的两端就是 VTEP，这里的 VTEP 是两台 TOR 交换机，通过两个 VTEP 来对数据包的解封装。\n图：VXLAN network model Source：https://support.huawei.com/enterprise/en/doc/EDOC1100086966\n下图是Linux VxLAN 类型的网络拓扑，VTEP 可以理解为是一种网络接口，通过该接口与内核功能进行解封包，而实际的流量也是通过物理设备进行传输。\n图：VxLAN in Linux Preliminary knowledge 三层分级网络 [4] 三层分级网络? 思科的三层分级网络(three-layer hierarchical model) 包含三层：\n核心层 (Core)：网络骨干，提供不同分布层之间的高速连接和最优传送路径 分布层 (distribution) 将连接接入层到核心层，并且实施安全，流量负载和路由相关的策略 接入层 (access) 为用户终端初始网络的接入点。 图：three-layer hierarchical model Source：https://community.fs.com/blog/how-to-choose-the-right-distribution-switch.html\n为什么需要VxLAN [5] 下图是一个 CSP (Cloud Service Provider ) 的 DC 网络，\n接入层：48口交换机20个 分布层：两台分布式交换机，共同组成一个虚拟化交换机。默认网关位于分布式交换机中。 核心层：两个核心交换机 Notes：工作在分布层的交换机被称为分布式交换机 (Distribution Switch)，分布式交换机会将来自访问层的流量转发至核心层，并提供一些连接策略。\n每台接入交换机连接48台物理服务器。这些服务器中的每一个都包含五个不同的租户，它们拥有自己的虚拟路由 (VRF)。一个租户由三个广播域组成：Presiontation， Application 和 Database ，每层都是互备的。在管理租户时，可以定义 VLAN ID、虚拟机的 MAC 地址和 IP 地址。虚拟机时可移动的，存在如下信息：\n图：CSP DC network Source：https://nwktimes.blogspot.com/2018/02/vxlan-part-i-why-vxlan-is-needed.html\n通过上述信息可以得知有如下：\n服务器：$20\\times48=960$, 20为ToR交换机数量，48为每个ToR的接口 虚拟机/MAC地址/ARP：28800个虚拟机（每个物理机30个虚拟机） 广播域：每个租户+每个租户的VLAN+所拥有的物理机，$5\\times3\\times960=14400$ VRF：4800，5个租户+960个虚拟机 在这种网络中存在的挑战如下：\nVLAN ID的限制，通常来说VLAN ID只有12位，4096个，这意味着不够用 多租户，多租户场景下，广播域等都是用户自己定义的，此时可能发生客户定义的ID为相同的 MAC表大小限制，在一个租户下有28800个机器，意味着交换机MAC地址表存放28800个MAC地址。会出现老化过程。（Notes：Cisco Nexus 9500/9300 系列支持90000个MAC地址表） APR表大小限制，分布式交换机中存在超过28800条MAC-IP的数量（ Notes：Cisco Nexus 9500系列交换机支持 60,000 个 IPv4 ARP 和 30,000 个 IPv6 ND） 生成树协议，在这种网络拓扑结构下，由于 STP 不支持链路之间的负载均衡，因此某些链路可能不会用于流量传输，这种情况下使带宽利用率下降。 由于 VxLAN 通过L3建立隧道，因此不需要生成树协议。在基于 VxLAN 技术的 DC 中，VLAN将不再具有意义，因为 VLAN 是交换机甚至交换机端口特定的。\n在基于 VxLAN Leaf-Spine 的网络架构中，通过将网络压缩为一个L2的网络架构，所有的节点在访问其他节点时，都将仅需要两部，因此除了Leaf交换机之外，其余并不清楚虚拟机的MAC地址。\n下图是Leaf-Spine网络拓扑图， 在该架构中，每个较低级别的接入（leaf）交换机都以全网状连接到每个较高级别的核心（spine）交换机。\n图：spine-leaf network Source：https://www.datacenterdynamics.com/en/marketwatch/spine-and-leaf-network-architecture-explained/\nVxLAN in Linux Linux 对 VxLAN 协议的支持时间并不久，2012 年 Stephen Hemminger 才把相关的工作合并到 kernel 中，并最终出现在 kernel 3.7.0 版本。为了稳定性和很多的功能，你可以会看到某些软件推荐在 3.9.0 或者 3.10.0 以后版本的 kernel 上使用 vxlan。\nlinux实现VxLAN网络 两台机器构成一个VxLAN网络，每台机器上有一个 VTEP，VTEP 通过它们的 IP 互相通信。\n图：VxLAN in Linux 这个图创建的VxLAN0设备模拟了VTEP隧道端点，实现了一个大二层域，突破了虚拟化网络的物理界限。\nnode01\nbash 1 2 3 ip link add vxlan1 type vxlan id 1 remote 10.0.0.3 dstport 4789 dev ens33 ip link set vxlan1 up ip addr add 192.168.100.1/24 dev vxlan1 node02\ntext 1 2 3 ip link add vxlan1 type vxlan id 1 remote 10.0.0.14 dstport 4789 dev eth0 ip link set vxlan1 up ip addr add 192.168.100.2/24 dev vxlan1 上述命令创建了一个类型为vxlan，名为vxlan1的网络接口，期后面的为配置这个网络设备的内容：\nid 1 类似CE设备的vxlan vni 10 设置的桥接域，只有相同的VNI之间可以直接进行二层通信。 dstport VTEP 通信的端口，这里会监听一个udp端口 remote 10.0.0.3 类似vni 10 head-end peer-list 2.2.2.2 用来设置隧道对端的 VTEP 地址，因为这里使用的为单播模式。 local 10.0.0.4 与 dev eth0 类似于 source 1.1.1.1 配置源VTEP的IP地址。 bash 1 2 3 4 5 6 7 8 9 10 $ tcpdump -np -i vxlan1 -vv tcpdump: listening on vxlan1, link-type EN10MB (Ethernet), snapshot length 262144 bytes 05:07:20.589942 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 192.168.100.1 tell 192.168.100.2, length 28 05:07:20.589963 ARP, Ethernet (len 6), IPv4 (len 4), Reply 192.168.100.1 is-at ca:21:c6:01:f9:d5, length 28 05:07:20.590509 IP (tos 0x0, ttl 64, id 26921, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.100.2 \u0026gt; 192.168.100.1: ICMP echo request, id 1225, seq 1, length 64 05:07:20.590525 IP (tos 0x0, ttl 64, id 1264, offset 0, flags [none], proto ICMP (1), length 84) 192.168.100.1 \u0026gt; 192.168.100.2: ICMP echo reply, id 1225, seq 1, length 64 05:07:21.593791 IP (tos 0x0, ttl 64, id 27468, offset 0, flags [DF], proto ICMP (1), length 84) 抓包查看对应的数据包 [vxlan_linux.cap](......\\images\\vxlan in linux\\vxlan_linux.cap)\n清理数据\ntext 1 2 ip link set vxlan1 down ip link delete vxlan1 多播模式的 vxlan “多播”即“多点传送”(multicast)，也就是一台主机发出的包可以同时被其他多个有资格的主机接收，这台主机和那些有资格的主机就形成了一个组，他们在组内的通信是广播式的。多播的工作原理是，将一个网络上的某些主机的网卡设置成多播传送工作模式，指定其不过滤以某一个多播传送地址作为目的物理地址的数据帧，这样，这些主机的驱动程序中就可以同时接收以该多播传送地址作为目的物理地址的数据帧，而其他主机的驱动程序却接收不到，这些主机在逻辑上便形成了一个“多播”组。采用这种技术，相对广播而言，可有效减轻网络上“多播”组之外的其他主机的负担，因为发送给“多播”组的数据不会被传送到它们的驱动程序中去处理，避免资源的无谓浪费。\n多播的IP范围为：从224.0.0.0到239.255.255.255。能够接收发往一个特定多播组地址数据的主机集合称为主机组 (host group)。一个主机组可跨越多个网络。主机组中成员可随时加入或离开主机组。主机组中对主机的数量没有限制，同时不属于某一主机组的主机可以向该组发送信息。\n239.1.1.1 IIANA保留地址用于多播（多点传送）的IP，其mac地址为 01:00:5e:01:01:01(参考：组播地址)\n要组成同一个 vxlan 网络，vtep 必须能感知到彼此的存在。多播组本来的功能就是把网络中的某些节点组成一个虚拟的组。\n实验使用的为多播组组成一个虚拟的整体，通过多播组，组成可容纳多个主机组成 vxlan 网络\n图：muticast VxLAN in Linux bash 1 2 3 ip link add vxlan2 type vxlan id 10 group 239.1.1.1 dstport 4789 dev eth0 ip link set vxlan2 up ip addr add 192.168.100.10/24 dev vxlan2 node01\nbash 1 2 3 ip link add vxlan2 type vxlan id 10 group 239.1.1.1 dstport 4789 dev eth0 ip link set vxlan2 up ip addr add 192.168.100.20/24 dev vxlan2 FDB 是 Linux 网桥维护的一个二层转发表，用于保存远端虚拟机/容器的 MAC地址，远端 VTEP IP，以及 VNI 的映射关系，可以通过 bridge fdb 命令来对 FDB 表进行操作：\nvxlan接口在创建后，fdb只有一个表项，就是所有vxlan2的流量都发往多播组\ntext 1 2 3 4 5 $ bridge fdb 33:33:00:00:00:01 dev eth0 self permanent 01:00:5e:00:00:01 dev eth0 self permanent 01:00:5e:01:01:01 dev eth0 self permanent 00:00:00:00:00:00 dev vxlan2 dst 239.1.1.1 via eth0 self permanent 组播路由方式过程\n当发送ping 192.168.100.10时在同一个局域网内会先发送ARP广播，为组播方式，node1与node2（10.0.0.4）均受到广播，而node3（10.0.0.6）未受到 ARP报文要获得的内容为vxlan的mac地址，目的地址为全1的广播地址 vxlan隧道封装VNI=10，因为不知道目的地址，所以会发送多播报文 受到报文后进行解包，取出真实的报文，如果发现是自己的，经由隧道封装后传递 vtep 通过源报文学习到了 vtep 所在的主机，因此会直接单播发送给目的 vtep。发送方主机根据 VNI 把报文转发给 vtep，vtep 解包取出 ARP 应答报文，添加 arp 缓存到内核。并根据报文学习到目的 vtep 所在的主机地址，添加到 fdb 表中 而没在多播组中的同网段主机没有受到对应的ARP广播\n而在加入多播组中会受到多播的信息，确定不是自己后没有reply\n此实验的报文内容\n[192.168.10.30 加入同多播组](......\\images\\vxlan in linux\\10.30.cap)\n[192.168.10.20 发起端](......\\images\\vxlan in linux\\10.20.cap)\n[192.168.10.30 不在多播组内的报文](......\\images\\vxlan in linux\\10.30 exit multicast.cap)\n清除配置\nbash 1 2 ip link set vxlan2 down ip link delete vxlan2 实验一：Linux Bridge[L2] 该实验包含 veth, vlan, Linux bridge 方面的\n图：L2 vn topology\n加载vlan模块\nbash 1 2 3 4 5 6 modprobe 8021q ## 查看核心是否提供VLAN 功能 dmesg | grep -i 802 [ 1.592802] pci 0000:00:15.0: PME# supported from D0 D3hot D3cold [ 1755.995461] 8021q: 802.1Q VLAN Support v1.8 [ 1755.995485] 8021q: adding VLAN 0 to HW filter on device eth0 安装命令\nbash 1 2 yum install vconfig -y apt install vlan 创建vlan\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # 创建bridge brctl addbr vlan10 brctl show ip link add veth01 type veth peer name eth01 ip link add veth02 type veth peer name eth02 # 将veth对的一端加入网桥 brctl addif vlan10 veth01 brctl addif vlan10 veth02 # 启动对应设备 ip link set dev vlan10 up ip link set dev veth01 up ip link set dev veth02 up ip link set dev eth01 up ip link set dev eth02 up # 创建ns ip netns add net1 ip netns add net2 # 将veth关联到对应名称空间内 ip link set eth01 netns net1 ip link set eth02 netns net2 网络名称空间net1内的操作，在vlan一端添加接口，与关联到该名称空间内的 veth 关联\ntext 1 2 3 4 5 6 7 8 9 vconfig add eth01 3001 vconfig add eth01 3002 ip link set eth01 up ip link set eth01.3001 up ip link set eth01.3002 up ip addr add 192.168.100.1/24 dev eth01.3001 ip addr add 192.168.100.2/24 dev eth01.3002 网络名称空间net2与net1的类似\ntext 1 2 3 4 5 6 7 8 9 vconfig add eth02 3001 vconfig add eth02 3002 ip link set dev eth02 up ip link set dev eth02.3001 up ip link set dev eth02.3002 up ip addr add 192.168.100.10/24 dev eth02.3001 ip addr add 192.168.100.11/24 dev eth02.3002 验证连通性，可以看到发送的包带有tag的标签\n实验二：IPVLAN L2 实验结果，通过namespace模拟Pod的网络，做到各Pod间的网络通讯。\nip netns list 查看网络命名空间\nip netns add net2 创建一个网络命名空间\nip link add \u0026lt;name\u0026gt; link eth0 type ipvlan mode l2 在当前名称空间创建一个类型为IPVLAN L2模式的接口，将该接口关联至父接口eth0上。\nip link set $name netns $nsName 将接口加入到对应网络名称空间内\nip netns exec $nsName $cmd 在对应的网络名称空间内运行命令\n创建两个网络名称空间\nbash 1 2 3 4 5 $ ip netns add net1 $ ip netns add net2 $ ip netns list net2 net1 创建 IPVLAN 接口\nbash 1 2 3 4 5 $ ip link add ipvlan01 link eth0 type ipvlan mode l2 $ ip link add ipvlan02 link eth0 type ipvlan mode l2 $ ip link set ipvlan01 netns net1 $ ip link set ipvlan02 netns net2 给对应接口添加IP地址\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ip netns exec net1 ifconfig ipvlan01 192.168.0.1/24 up ip netns exec net2 ifconfig ipvlan02 192.168.0.2/24 up # 这两个名称空间内的mac地址是一样的 $ ip netns exec net2 ifconfig ipvlan02: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 192.168.0.2 netmask 255.255.255.0 broadcast 192.168.0.255 inet6 fe80::da78:c800:27a:fb26 prefixlen 64 scopeid 0x20\u0026lt;link\u0026gt; ether da:78:c8:7a:fb:26 txqueuelen 1000 (Ethernet) RX packets 39007 bytes 2394577 (2.2 MiB) RX errors 0 dropped 27 overruns 0 frame 0 TX packets 11 bytes 866 (866.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 $ ip netns exec net1 ifconfig ipvlan01: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 192.168.0.1 netmask 255.255.255.0 broadcast 192.168.0.255 inet6 fe80::da78:c800:17a:fb26 prefixlen 64 scopeid 0x20\u0026lt;link\u0026gt; ether da:78:c8:7a:fb:26 txqueuelen 1000 (Ethernet) RX packets 132823 bytes 8184548 (7.8 MiB) RX errors 0 dropped 93 overruns 0 frame 0 TX packets 12 bytes 936 (936.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 测试两个名称空间是否互通\n结论：可以看到两个名称空间内的子接口 (sub-interface) 通过其父接口 (parent-interface) 可以达到互通。子接口与父接口之间的不互通。IPVLAN L2模式仅限于子接口之间的互通\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ ip netns exec net1 ping 192.168.0.2 PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data. 64 bytes from 192.168.0.2: icmp_seq=1 ttl=64 time=0.285 ms 64 bytes from 192.168.0.2: icmp_seq=2 ttl=64 time=0.077 ms ^C --- 192.168.0.2 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1027ms rtt min/avg/max/mdev = 0.077/0.181/0.285/0.104 ms $ ip netns exec net2 ping 192.168.0.1 PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data. 64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=0.051 ms 64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=0.059 ms ^C --- 192.168.0.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1056ms rtt min/avg/max/mdev = 0.051/0.055/0.059/0.004 ms 遇到问题\nRTNETLINK answers: Operation not supported： CentOS 7默认内核版本为3.10 IPVLAN 3.19开始支持，推荐内核为4.2+\n子接口ping父接口不通，源MAC与目标MAC是一致，而mac接口是mac地址与接口绑定，因为三个接口的mac地址都相同，此时区分不了是哪个接口。\nping公网地址不通，查看路由表中没有对外的路由，手动添加即可\nbash 1 2 ip netns exec net1 route -n ip netns exec net1 route add -net 0.0.0.0/0 gw 10.0.0.2 IPVLAN L2模式中，父接口是可以没有IP地址的。不影响子接口的使用\n清除所有网络名称空间\nbash 1 for n in $(ip netns list|awk \u0026#39;{print $1}\u0026#39;);do ip netns del $n;done 实验三：IPVLAN L3 先创建两个用做测试的 network namespace\nbash 1 2 ip netns add net3 ip netns add net4 创建出 IPVLAN 的虚拟网卡接口，创建 IPVLAN 虚拟接口的命令和 MACVLAN 格式相同：\nbash 1 2 ip link add ipvl01 link ens33 type ipvlan mode l3 ip link add ipvl02 link ens33 type ipvlan mode l3 把 IPVLAN 接口放到前面创建好的 namespace 中\nbash 1 2 3 4 5 6 7 8 ip link set ipvl01 netns net3 ip link set ipvl02 netns net4 # 给对应设备设置IP地址 ip netns exec net3 ifconfig ipvl01 192.168.10.1/24 up ip netns exec net4 ifconfig ipvl02 192.168.20.1/24 up # 设置对应的路由 ip netns exec net4 route add -net 192.168.10.0/24 dev ipvl02 ip netns exec net3 route add -net 192.168.20.0/24 dev ipvl01 结果是可以通的\nbash 1 2 3 4 5 6 7 8 $ ip netns exec net3 ping 192.168.20.1 PING 192.168.20.1 (192.168.20.1) 56(84) bytes of data. 64 bytes from 192.168.20.1: icmp_seq=1 ttl=64 time=0.026 ms 64 bytes from 192.168.20.1: icmp_seq=2 ttl=64 time=0.119 ms ^C --- 192.168.20.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1028ms rtt min/avg/max/mdev = 0.026/0.072/0.119/0.046 ms 实验四：MACVLAN 创建两个名称空间\nbash 1 2 ip netns add net1 ip netns add net2 创建两个 MACVLAN 接口\nbash 1 2 3 4 ip link add link eth0 name macv1 type macvlan mode bridge ip link add link eth0 name macv2 type macvlan mode bridge ## 持久化创建 echo \u0026#34;ip link add eth0 eth0.1 address 52:54:00:cc:ee:aa link enp0s31f6 type macvlan\u0026#34; \u0026gt; /sbin/ifup-pre-local2 把 MACVLAN 接口放到前面创建好的 namespace 中\nbash 1 2 3 4 5 6 7 8 ip link set macv1 netns net1 ip link set macv2 netns net2 # 给对应设备设置IP地址 ip netns exec net1 ifconfig ipvl01 192.168.10.1/24 up ip netns exec net2 ifconfig ipvl02 192.168.20.1/24 up # 设置对应的路由 ip netns exec net1 route add -net 192.168.10.0/24 dev ipvl02 ip netns exec net2 route add -net 192.168.20.0/24 dev ipvl01 可以看到两个网卡的MAC地址是不同的\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ ip netns exec net2 ifconfig macv2: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 192.168.10.1 netmask 255.255.255.0 broadcast 192.168.10.255 inet6 fe80::a830:c9ff:fe9a:7c33 prefixlen 64 scopeid 0x20\u0026lt;link\u0026gt; ether aa:30:c9:9a:7c:33 txqueuelen 1000 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 7 bytes 586 (586.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 $ ip netns exec net1 ifconfig macv1: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 192.168.10.1 netmask 255.255.255.0 broadcast 192.168.10.255 inet6 fe80::d448:c5ff:fec7:76a3 prefixlen 64 scopeid 0x20\u0026lt;link\u0026gt; ether d6:48:c5:c7:76:a3 txqueuelen 1000 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 8 bytes 656 (656.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 Reference [1] man veth\n[2] macvlan\n[3] how to configure macvlan interface for getting the IP?\n[4] distribution switch\n[5] why vxlan is needed\n[6] distribution switch\n","permalink":"https://www.161616.top/virtual-networking/","summary":"本文是关于深入理解Kubernetes网络原理系列第2章 深入理解Kubernetes Pod网络原理 - 网络名称空间 深入理解Kubernetes Pod网络原理 - Linux虚拟网络技术 深入理解Kubernetes Pod网络原理 - CNI 深入理解Kubernetes Pod网络原理 - 跟随 flannel 学习CNI原理 深入理解Kubernetes Pod网络原理 - 跟随 flannel + multus 剖析 Chained Plugins 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 1 (Shell) 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 2 (libcni) 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 1 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 2 深入理解Kubernetes Pod网络原理 - Pod网络排错思路 Overview 本文将介绍Kubernetes中使用的相关虚拟网络功能，目的是为了任何无相关网络背景经历的人都可以了解这些技术在kubernetes中式如何应用的。\nVLAN VLAN (Virtual local area networks)是逻辑上的LAN而不受限于同一物理网络交换机上。同样的VLAN也可以将同一台交换机/网桥下的设备/划分为不同的子网。","title":"深入理解Kubernetes Pod网络原理 - Linux虚拟网络技术"},{"content":" 实验文件： [calico BGP.zip](https://cdn.jsdelivr.net/gh/cylonchau/blogs@img/img/calico BGP.zip)\nR1\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 system-view sysname R1 interface l0 ip address 1.1.1.1 32 interface g0/0/0 ip address 10.1.0.1 24 interface g0/0/1 ip address 10.3.0.1 24 bgp 100 router-id 1.1.1.1 peer 10.1.0.2 as-number 123 peer 10.3.0.2 as-number 456 dis this dis ip interface b R2\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 system-view sysname R2 interface l0 ip address 2.2.2.2 32 interface g0/0/0 ip address 10.2.0.1 24 interface g0/0/1 ip address 10.4.0.1 24 bgp 200 router-id 2.2.2.2 peer 10.4.0.2 as-number 123 peer 10.2.0.2 as-number 456 dis ip interface b 从下至上配置\nR3\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 system-view sysname R3 interface l0 ip address 3.3.3.3 32 interface g0/0/0 ip address 10.1.0.2 24 interface g0/0/1 ip address 10.5.0.1 24 vlan 2 int vlan 2 ip address 10.6.0.1 24 in e0/0/0 port link-type access port default vlan 2 dis ip interface brief vlan 3 int vlan 3 ip address 10.4.0.2 24 in e0/0/1 port link-type access port default vlan 3 dis ip interface brief ospf router-id 3.3.3.3 area 0 network 10.1.0.0 0.0.0.255 network 10.5.0.0 0.0.0.255 network 3.3.3.3 0.0.0.0 network 10.4.0.0 0.0.0.255 network 10.6.0.0 0.0.0.255 dis this bgp 123 router-id 3.3.3.3 peer 5.5.5.5 as-number 123 peer 5.5.5.5 connect-interface l0 peer 6.6.6.6 as-number 123 peer 6.6.6.6 connect-interface l0 dis this peer 5.5.5.5 reflect-client peer 6.6.6.6 reflect-client peer 10.1.0.1 as-number 100 peer 10.4.0.1 as-number 200 dis this R5\n注意这里OSPF宣告的路由，OSPF优先级高于BGP，此处不能宣告0.0.0.0 255.255.255.255\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 system-view sysname R5 interface l0 ip address 5.5.5.5 32 quit vlan 2 int vlan 2 ip address 10.5.0.2 24 in e0/0/0 port link-type access port default vlan 2 dis ip interface brief ospf router-id 5.5.5.5 area 0 network 5.5.5.5 0.0.0.0 network 10.5.0.0 0.0.0.255 dis this bgp 123 router-id 5.5.5.5 peer 3.3.3.3 as-number 123 peer 3.3.3.3 connect-interface l0 dis this R6\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 system-view sysname R6 interface l0 ip address 6.6.6.6 32 quit vlan 2 int vlan 2 ip address 10.6.0.2 24 in e0/0/0 port link-type access port default vlan 2 dis ip interface brief ospf router-id 6.6.6.6 area 0 network 6.6.6.6 0.0.0.0 network 10.6.0.0 0.0.0.255 dis this bgp 123 router-id 6.6.6.6 peer 3.3.3.3 as-number 123 peer 3.3.3.3 connect-interface l0 dis this R4\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 system-view sysname R4 interface l0 ip address 4.4.4.4 32 interface g0/0/0 ip address 10.2.0.2 24 interface g0/0/1 ip address 10.7.0.1 24 vlan 2 int vlan 2 ip address 10.8.0.1 24 in e0/0/0 port link-type access port default vlan 2 dis ip interface brief vlan 3 int vlan 3 ip address 10.3.0.2 24 in e0/0/1 port link-type access port default vlan 3 dis ip interface brief ospf router-id 4.4.4.4 area 0 network 10.2.0.0 0.0.0.255 network 10.3.0.0 0.0.0.255 network 4.4.4.4 0.0.0.0 network 10.7.0.0 0.0.0.255 network 10.8.0.0 0.0.0.255 dis this bgp 456 router-id 4.4.4.4 peer 7.7.7.7 as-number 456 peer 7.7.7.7 connect-interface l0 peer 8.8.8.8 as-number 456 peer 8.8.8.8 connect-interface l0 dis this peer 7.7.7.7 reflect-client peer 8.8.8.8 reflect-client peer 10.3.0.1 as-number 100 peer 10.2.0.1 as-number 200 dis this R7\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 system-view sysname R7 interface l0 ip address 7.7.7.7 32 quit vlan 2 int vlan 2 ip address 10.7.0.2 24 in e0/0/0 port link-type access port default vlan 2 dis ip interface brief ospf router-id 7.7.7.7 area 0 network 7.7.7.7 0.0.0.0 network 10.7.0.0 0.0.0.255 dis this bgp 456 router-id 7.7.7.7 peer 4.4.4.4 as-number 456 peer 4.4.4.4 connect-interface l0 dis this R8\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 system-view sysname R8 interface l0 ip address 8.8.8.8 32 interface g0/0/0 ip address 10.8.0.2 24 dis ip interface brief ospf router-id 8.8.8.8 area 0 network 8.8.8.8 0.0.0.0 network 10.8.0.0 0.0.0.255 dis this bgp 456 router-id 8.8.8.8 peer 4.4.4.4 as-number 456 peer 4.4.4.4 connect-interface l0 dis this 在R7与R5各添加一条路由\ntext 1 2 3 4 5 6 7 8 9 10 11 interface L11 ip address 77.77.77.77 32 quit bgp 456 network 77.77.77.77 255.255.255.255 interface L11 ip address 55.55.55.55 32 quit bgp 123 network 55.55.55.55 255.255.255.255 可以看到R1 R2 与 R6 R8都通过对应的bgp协议学习到相应的路由。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 [R2]dis bgp routing-table BGP Local router ID is 2.2.2.2 Status codes: * - valid, \u0026gt; - best, d - damped, h - history, i - internal, s - suppressed, S - Stale Origin : i - IGP, e - EGP, ? - incomplete Total Number of Routes: 4 Network NextHop MED LocPrf PrefVal Path/Ogn *\u0026gt; 55.55.55.55/32 10.4.0.2 0 123i * 10.2.0.2 0 456 100 123i *\u0026gt; 77.77.77.77/32 10.2.0.2 0 456i * 10.4.0.2 0 123 100 456i [R6-bgp]dis bgp routing-table BGP Local router ID is 6.6.6.6 Status codes: * - valid, \u0026gt; - best, d - damped, h - history, i - internal, s - suppressed, S - Stale Origin : i - IGP, e - EGP, ? - incomplete Total Number of Routes: 2 Network NextHop MED LocPrf PrefVal Path/Ogn *\u0026gt;i 55.55.55.55/32 5.5.5.5 0 100 0 i *\u0026gt;i 77.77.77.77/32 10.1.0.1 100 0 100 456i [R6-bgp] 遇到问题：\nvlan未启动，需要查看对应绑定的端口是否正确\nospf配置错误，undo ospf 1 重启ospf\n","permalink":"https://www.161616.top/ensp-calico-bgp/","summary":"实验文件： [calico BGP.zip](https://cdn.jsdelivr.net/gh/cylonchau/blogs@img/img/calico BGP.zip)\nR1\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 system-view sysname R1 interface l0 ip address 1.1.1.1 32 interface g0/0/0 ip address 10.1.0.1 24 interface g0/0/1 ip address 10.3.0.1 24 bgp 100 router-id 1.1.1.1 peer 10.1.0.2 as-number 123 peer 10.3.0.2 as-number 456 dis this dis ip interface b R2\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 system-view sysname R2 interface l0 ip address 2.","title":"使用eNSP构建calico BGP网络"},{"content":"隧道技术概要 隧道技术（Tunneling）是网络基础设置在网络之间传递数据的方式，使用隧道技术传递可以是不同协议的数据包，隧道协议将这些其他协议的数据包重新封装在新的包头中发送。被封装的数据包在隧道的两个端点之间通过网络进行路由，被封装数据包在网络上传递时所经历的逻辑路径称为隧道。\n简单来说，隧道技术是一类网络协议，是将一个数据包封装在另一个数据包中进行传输的技术；**使用隧道的原因是在不兼容的网络上传输数据，或在不安全网络上提供一个安全路径。**通过网络隧道技术，可以使隧道两端的网络组成一个更大的内部网络。（把不支持的协议数据包打包成支持的协议数据包之后进行传输）。\n隧道协议 要创建隧道，隧道的客户机和服务器双方必须使用相同的隧道技术，隧道协议有二层隧道协议与三层隧道协议两类。\n二层隧道协议对应OSI模型中数据链路层，使用 帧 作为数据交换单位，PPTP、L2TP、L2F都属于二层隧道协议。是将数据封装在点对点协议的帧中通过互联网络发送。\n三层隧道协议对应OSI模型中网络层，使用 包 作为数据交换单位，GRE、IPSec 都属于三层隧道协议。都是数据包封装在附加的IP包头中通过IP网络传送。\n在例如VxLAN，工作在传输层和网络层之间。具体来说，将运行在用户数据报协议 (UDP) 和网络数据报协议 (IP) 之间，以便在网络中建立安全的通信通道。\n网络隧道技术应用 隧道在Linux 中应用 IP隧道是指一种可在两网络间进行通信的通道。在该通道里，会先封装其他网络协议的数据包，之后再传输信息。\nLinux原生共支持5种IPIP隧道：\nipip: 普通的IPIP隧道，就是在报文的基础上再封装成一个IPv4报文 gre: 通用路由封装（Generic Routing Encapsulation），定义了在任意一种网络层协议上封装其他任意一种网络层协议的机制，所以对于IPv4和IPv6都适用 sit: sit模式主要用于IPv4报文封装IPv6报文，即IPv6 over IPv4 isatap: 站内自动隧道寻址协议（Intra-Site Automatic Tunnel Addressing Protocol），类似于sit也是用于IPv6的隧道封装 vti: 即虚拟隧道接口（Virtual Tunnel Interface），是一种IPsec隧道技术 像IPVS/LVS中的 Virtual Server via IP Tunneling，就是使用了IPIP隧道\nSSH隧道技术 SSH提供了一个重要功能，称为转发 forwarding 或者称为隧道传输tunneling，它可以通过加密频道将明文流量导入隧道中，在创建SSH隧道时， SSH客户端要设置并转交一个特定本地端口号到远程机器上；一旦SSH隧道创建，用户可以连到指定的本地端口号以访问网络服务。本地端口号不用与远地端口号一样。\nSSH隧道主要使用场景一般为 规避防火墙、加密网络流量\n规避防火墙，SSH隧道可以使一个被防火墙阻挡的协议可被包在另一个没被防火墙阻挡的协议里，这技巧可用来逃避防火墙政策。而这种操作符合“数据包封装在另一个数据包中进行传输的技术”，故称为SSH隧道技术。\nSSH隧道类型 在ssh连接的基础上，指定 ssh client 或 ssh server 的某个端口作为源地址，所有发至该端口的数据包都会透过ssh连接被转发出去；至于转发的目标地址，目标地址既可以指定，也可以不指定，如果指定了目标地址，称为定向转发，如果不指定目标地址则称为动态转发：\n定向转发\n定向转发把数据包转发到指定的目标地址。目标地址不限定是ssh client 或 ssh server，既可以是二者之一，也可以是二者以外的其他机器。\n动态转发\n动态转发不指定目标地址，数据包转发的目的地是动态决定的。\n本地端口转发 本地转发中的本地是指将本地的某个端口(1024-65535)通过SSH隧道转发至其他主机的套接字，这样当我们的程序连接本地的这个端口时，其实间接连上了其他主机的某个端口，当我们发数据包到这个端口时数据包就自动转发到了那个远程端口上了\n远程端口转发 远程转发和本地很相似，原理也差不多，但是不同的是，本地转发是在本地主机指定的一个端口，而远程转发是由SSH服务器经由SSH客户端转发，连接至目标服务器上。本质一样，区别在于需要转发的端口是在远程主机上还是在本地主机上\n现在SSH就可以把连接从（39.104.112.253:80）转发到（10.0.0.10:85）。\n动态端口转发 定向转发（包括本地转发和远程转发）的局限性是必须指定某个目标地址，如果需要借助一台中间服务器访问很多目标地址，一个一个地定向转发显然不是好办法，这时就要用的是ssh动态端口转发，它相当于建立一个SOCKS服务器。各种应用经由SSH客户端转发，经过SSH服务器，到达目标服务器，不固定端口。\nSSH隧道的本质 SSH隧道可以被认为是一种应用层隧道，与其他隧道类型（如IPIP, VxLAN）不同的是，SSH隧道是基于SSH协议的一种应用，而IPIP, VxLAN这种，则是基于IP协议，UDP协议的一种封包机制。\nSSH（Secure Shell）是一种网络协议，支持远程登录和其他安全网络服务的加密通信。SSH隧道属于SSH协议中的一种应用场景，用于在SSH加密连接上建立通信隧道。SSH隧道允许用户通过加密终端 (SSH客户端) 和远程服务之间的连接，在不暴露底层网络协议的信息（例如IP地址、端口号等）的情况下，传输数据。\nSSH隧道工作方式如下：\n首先，在本地主机和目标服务器之间建立SSH连接，SSH连接是一条安全加密的连接管道，连接过程中对数据进行加密传输。 连接建立后，通过SSH隧道在本地主机和目标服务器之间建立一个TCP连接，并将本地主机上的数据通过SSH隧道加密传输到目标服务器，目标服务器接收数据，解密后将数据传输到最终目的地。 同样，当接收数据时，目标服务器会将数据加密再通过SSH隧道传输回本地主机。 由于SSH隧道在SSH连接上建立通信隧道，因此可以将其视为应用层隧道。应用层隧道是在应用层协议上建立的隧道，用于将应用程序传输的数据加密传输到目标地址。SSH隧道给用户提供了一种安全的数据通信方法，在安全性上比普通TCP/IP连接更具有优势。\nSSH隧道也可以成为一种代理模式，常用于越过不可访问的网络时使用\n图：SSH隧道应用图解\rSource：https://infosecwriteups.com/bypass-the-firewall-with-ssh-tunnelling-711fa78ea97f\n其他隧道协议 对于隧道，上面也提到了，隧道就是网络数据包封包一种协议，那就是说很多常见的协议其实都是隧道技术\n工作与数据链路层的隧道技术： PPP隧道协议（Point-to-Point Protocol）：PPP隧道协议是一种在两个点之间建立可靠连接的协议，它能够在一条串行线路上同时传输多种网络层协议。PPP隧道协议通过在两个点之间建立隧道，将其他协议的数据封装起来进行传输。 L2TP协议（Layer 2 Tunnel Protocol）：L2TP协议是一种在不安全的公共网络上传输数据的加密协议，常用于建立VPN（Virtual Private Network）隧道。L2TP协议将PPP协议属性和L2TP控制消息封装在IP（Internet Protocol）数据报中。 PPTP协议（Point-to-Point Tunneling Protocol）：PPTP协议是一种在不安全的公共网络上传输数据的加密协议，也常用于建立VPN隧道。PPTP协议通过在数据包中添加PPTP头和PPP协议数据负载来传输数据。 GRE协议（Generic Routing Encapsulation）：GRE协议是一种通用路由封装协议，它可以将其他协议的数据封装在IP数据报中进行传输。GRE协议主要用于连接不同类型的网络，通常用于建立VPN隧道。 工作与网络层的隧道协议： 负载均衡协议 (LBP) 是一种在网络层以上实现的协议，用于在二层 (链路层) 上实现数据包的转发。LBP 可以将数据包转发到多个服务器上，从而实现负载均衡。LBP 可以用于实现网站负载均衡、存储集群等功能。 协议映射协议 (PMP) 是一种在网络层以下实现的协议，用于在网络层以上实现数据包的映射。PMP 可以将一个数据包映射到另一个数据包中，从而实现数据包的转发。PMP 可以用于实现虚拟专用网络 (Virtual Private Network,VPN) 和防火墙等功能。 虚拟隧道协议 (Virtual Tunneling Protocol,VTP) 是一种在网络层以下实现的协议，用于在网络中创建和管理隧道。VTP 可以将一个网络中的多个子网互联，使得数据包可以在这些子网之间传输。VTP 可以用于实现数据包的路由、负载均衡和安全性等方面。 工作与应用层的隧道技术： HTTP隧道：HTTP隧道通过HTTP连接创建隧道，将其他协议的数据封装在HTTP报文中，传输到目标地址。HTTP隧道通常用于访问受限制的服务器，如防火墙后的服务器。 SSL/TLS隧道：SSL/TLS隧道也是基于加密传输的应用层隧道。通过SSL/TLS加密传输，将通信数据封装在加密连接中，传输到目标服务器。SSL/TLS隧道通常用于保护Web应用程序中传输的机密数据。 SOCKS代理隧道：SOCKS代理隧道是一种应用层代理协议，用于将流量转发到目标地址并代理转发返回数据。SOCKS代理隧道通常用于隐藏客户端的真实IP地址和身份。 DNS隧道：DNS隧道是通过将数据封装在DNS请求或响应中来传输数据的应用层隧道。DNS隧道通常被用于绕过安全防护措施或访问受限制的服务器。 常提到的“非法信道”中的“信道”和“隧道”是一样的吗？ 首先，“信道”和“隧道” 是两种不同的概念，常见的表示形式如下：\n意义不同：信道是指物理媒介或虚拟路径，用于数据的传输，例如网络电缆或无线信道。隧道则是一种逻辑隧道，通过在底层通信协议的基础上创建加密通道来传输数据。 位置不同：信道通常是指在通信的物理媒介上的传输路径，而隧道则是在信道之上的 OSI 模型层协上创建加密通道的逻辑概念。 传输方式不同：信道是直接用于传输数据的物理媒介，信号通过信道进行传输；隧道则是在传输数据时，将数据封装成新的协议格式，通过信道进行加密传输。 使用场景不同：信道常用于介质访问控制、传输层控制、传输介质选择等方面，例如在局域网中使用以太网电缆传送数据。隧道则通常用于保障企业内部网络安全、建立虚拟专用网络、跨越防火墙等隧道服务需求。 技术特点不同：信道是一种物理层或数据链路层技术，而隧道是一种应用层或数据链路层技术。 ","permalink":"https://www.161616.top/network-tunnel-technology/","summary":"隧道技术概要 隧道技术（Tunneling）是网络基础设置在网络之间传递数据的方式，使用隧道技术传递可以是不同协议的数据包，隧道协议将这些其他协议的数据包重新封装在新的包头中发送。被封装的数据包在隧道的两个端点之间通过网络进行路由，被封装数据包在网络上传递时所经历的逻辑路径称为隧道。\n简单来说，隧道技术是一类网络协议，是将一个数据包封装在另一个数据包中进行传输的技术；**使用隧道的原因是在不兼容的网络上传输数据，或在不安全网络上提供一个安全路径。**通过网络隧道技术，可以使隧道两端的网络组成一个更大的内部网络。（把不支持的协议数据包打包成支持的协议数据包之后进行传输）。\n隧道协议 要创建隧道，隧道的客户机和服务器双方必须使用相同的隧道技术，隧道协议有二层隧道协议与三层隧道协议两类。\n二层隧道协议对应OSI模型中数据链路层，使用 帧 作为数据交换单位，PPTP、L2TP、L2F都属于二层隧道协议。是将数据封装在点对点协议的帧中通过互联网络发送。\n三层隧道协议对应OSI模型中网络层，使用 包 作为数据交换单位，GRE、IPSec 都属于三层隧道协议。都是数据包封装在附加的IP包头中通过IP网络传送。\n在例如VxLAN，工作在传输层和网络层之间。具体来说，将运行在用户数据报协议 (UDP) 和网络数据报协议 (IP) 之间，以便在网络中建立安全的通信通道。\n网络隧道技术应用 隧道在Linux 中应用 IP隧道是指一种可在两网络间进行通信的通道。在该通道里，会先封装其他网络协议的数据包，之后再传输信息。\nLinux原生共支持5种IPIP隧道：\nipip: 普通的IPIP隧道，就是在报文的基础上再封装成一个IPv4报文 gre: 通用路由封装（Generic Routing Encapsulation），定义了在任意一种网络层协议上封装其他任意一种网络层协议的机制，所以对于IPv4和IPv6都适用 sit: sit模式主要用于IPv4报文封装IPv6报文，即IPv6 over IPv4 isatap: 站内自动隧道寻址协议（Intra-Site Automatic Tunnel Addressing Protocol），类似于sit也是用于IPv6的隧道封装 vti: 即虚拟隧道接口（Virtual Tunnel Interface），是一种IPsec隧道技术 像IPVS/LVS中的 Virtual Server via IP Tunneling，就是使用了IPIP隧道\nSSH隧道技术 SSH提供了一个重要功能，称为转发 forwarding 或者称为隧道传输tunneling，它可以通过加密频道将明文流量导入隧道中，在创建SSH隧道时， SSH客户端要设置并转交一个特定本地端口号到远程机器上；一旦SSH隧道创建，用户可以连到指定的本地端口号以访问网络服务。本地端口号不用与远地端口号一样。\nSSH隧道主要使用场景一般为 规避防火墙、加密网络流量\n规避防火墙，SSH隧道可以使一个被防火墙阻挡的协议可被包在另一个没被防火墙阻挡的协议里，这技巧可用来逃避防火墙政策。而这种操作符合“数据包封装在另一个数据包中进行传输的技术”，故称为SSH隧道技术。\nSSH隧道类型 在ssh连接的基础上，指定 ssh client 或 ssh server 的某个端口作为源地址，所有发至该端口的数据包都会透过ssh连接被转发出去；至于转发的目标地址，目标地址既可以指定，也可以不指定，如果指定了目标地址，称为定向转发，如果不指定目标地址则称为动态转发：\n定向转发\n定向转发把数据包转发到指定的目标地址。目标地址不限定是ssh client 或 ssh server，既可以是二者之一，也可以是二者以外的其他机器。","title":"网络隧道技术"},{"content":" 工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 理解ldap - OpenLDAP客户端命令行使用 Overview 作为系统管理员或程序员，经常需要诊断分析和解决网络问题，而配置、监控与保护网络有助于发现问题并在事情范围扩大前得意解决，并且网络的性能与安全也是管理与诊断网络的重要部分。本文将总结常用与Linux网络管理的命令与使用示例，保持长期更新与更正。\nIP iproute2 包含网络、路由、ARP缓存等的管理与配置的ip命令，用来取代传统的 ifconfig 与 route；ip 使用第二个参数，指定在对象执行的操作（例如，add delete show）。\nip 命令是配置网络接口的强大工具，任何 Linux 系统管理员都应该知道。它用于启动或关闭接口、分配和删除地址和路由、管理 ARP 缓存等等。\nip 常用的子命令有：\nlink (l) 网络接口管理 address (a) IP地址管理 route (r) 路由表管理 neigh (n) arp表管理 各系统下的包名与安装\nUbuntu/Debian: iproute2 ；apt install iproute2 CentOS/Fedora: iproute2 ；yum install -y iproute2 Apline：iproute2 ；apk add iproute2 ip link ip link 用于管理和显示网络接口\n获取网络接口信息ip link show 查看特定设备信息\nbash 1 ip link show dev [device] 查看所有网络接口的统计信息（如传输或丢弃的数据包，错误等等）：\nbash 1 ip -s link 查看单个网络接口的类似信息：\nbash 1 ip -s link ls [interface] 例如\ntext 1 2 3 4 5 6 7 $ ip -s link ls eth0 2: eth0: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc fq state UP mode DEFAULT group default qlen 1000 link/ether da:78:c8:7a:fb:26 brd ff:ff:ff:ff:ff:ff RX: bytes packets errors dropped overrun mcast 38626072259 324723879 0 347316 0 0 TX: bytes packets errors dropped carrier collsns 13404948080 6829250 0 0 0 0 如果需要显示更多的详情，可以再添加一个 -s\nbash 1 ip -s -s link ls [interface] 仅查看启动（运行）的接口列表\nbash 1 ip link ls up 修改网络接口信息 ip link set 查看 ip link 的帮助\nbash 1 ip link help 启动/关闭网络接口\nbash 1 ip link set [interface] up/down ip link 可以修改设备传输队列的长度\nbash 1 ip link set txqueuelen [number] dev [interface] 设置 MTU (Maximum Transmission Unit) 来提高网络性能\nbash 1 ip link set mtu [number] dev [interface] ###查看与管理IP地址 ip addr\n显示所有设备\nbash 1 ip addr 列出网络接口与IP地址\nbash 1 ip addr show 查看单个网络设备的信息\nbash 1 ip addr show dev [interface] 列出 IPv4/IPv6 地址\nbash 1 2 ip -4 addr ip -6 addr 在Linux中添加网络地址\nbash 1 ip addr add [ip_address] dev [interface] 添加广播地址\nbash 1 ip addr add brd [ip_address] dev [interface] 删除接口上的网络地址\nbash 1 ip addr del [ip_address] dev [interface] 管理路由表 ip route 显示路由表 ip route list bash 1 2 ip route ip route list 选择范围；上述命令列出内核内所有路由条目，如果想要缩小范围可以使用选择器 SELECTOR\n语法：ip route list SELECTOR\nSELECTOR:\nroot：[ local | main | default | all | NUMBER ]\nmatch：\n[ match PREFIX ]\nbash 1 ip route list match 10 exact： [ exact PREFIX ]\nTABLE\n[ table TABLE_ID ] [ local | main | default | all | NUMBER ]\nbash 1 2 3 4 5 6 7 8 9 ip route list table local broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1 local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1 local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1 broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1 broadcast 195.133.10.0 dev eth0 proto kernel scope link src 195.133.11.43 local 195.133.11.43 dev eth0 proto kernel scope host src 195.133.11.43 broadcast 195.133.11.255 dev eth0 proto kernel scope link src 195.133.11.43 PROTO\n[ proto RTPROTO ] [ kernel | boot | static | NUMBER ]\ntext 1 ip route list proto static TYPE\n[ type TYPE ] { unicast | local | broadcast | multicast | throw |unreachable | prohibit | blackhole | nat }\ntext 1 ip route list type multicast SCOPE\n[ scope SCOPE ] [ host | link | global | NUMBER ]\ntext 1 2 3 4 ip route list scope link 169.254.0.0/16 dev eth0 metric 1002 172.16.0.0/20 dev eth0 proto kernel src 172.16.0.2 修改路由表 ip route add/del 在指定设备上添加路由条目\nbash 1 ip route add [ip_address] dev [interface] 通过网关添加新路由\nbash 1 ip route add [ip_address] via [gatewayIP] 通过本地网关为所有地址添加默认路由\nbash 1 2 3 ip route add default [ip_address] dev [device] ip route add default [network/mask] via [gatewayIP] 删除已经存在的路由表\nbash 1 2 3 ip route del [ip_address] ip route del default ip route del [ip_address] dev [interface] ARP地址表管理 ip neighbor 显示arp 条目 ip neigh show 显示系统中设备的MAC地址及其状态。设备存在的状态：\n状态 说明 REACHABLE 在超时过期之前有效且可访问的条目 PERMANENT 管理员才能删除的永久条目 STALE 有效但无法访问的条目；为了检查它的状态，内核在第一次传输时检查 例如\nbash 1 2 3 4 5 6 7 8 9 10 ip neigh show 192.168.10.1 dev eth0 lladdr 00:1f:ce:72:bd:8c REACHABLE 46.17.40.155 dev eth0 lladdr c4:71:fe:f1:9f:3f STALE 2a00:b700:3::1 dev eth0 lladdr 00:1f:ce:72:bd:8c router STALE fe80::f0c5:a5ff:fee8:2aa4 dev eth0 lladdr f2:c5:a5:e8:2a:a4 router STALE fe80::a48a:1eff:fe35:c2f7 dev eth0 lladdr a6:8a:1e:35:c2:f7 router STALE fe80::4c4d:b3ff:fe44:fd58 dev eth0 lladdr 4e:4d:b3:44:fd:58 router STALE fe80::4c33:dfff:fe92:9f2f dev eth0 lladdr 4e:33:df:92:9f:2f router STALE fe80::21f:ceff:fe72:bd8c dev eth0 lladdr 00:1f:ce:72:bd:8c router STALE 修改arp条目 ip neigh add/del bash 1 2 3 ip neigh add [ip_address] dev [interface] ip neigh del [ip_address] dev [interface] traceroute traceroute 可以追踪数据传输是如何从本地传输到远程的。一个典型的例子是网页的访问。在互联网上加载一个网页需要数据流经一个网络和许多路由器。traceroute 可以显示所采用的路由以及网络上路由器的IP和主机名。它可以应用于排查网络延迟或诊断网络问题。\n各系统下的包名与安装\nUbuntu/Debian: traceroute ；apt install traceroute -y CentOS/Fedora: traceroute ；yum install -y traceroute Apline：busybox ；apk add busybox 追踪网络主机的路由 traceroute host bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 traceroute baidu.com traceroute to baidu.com (220.181.38.148), 30 hops max, 60 byte packets 1 * 9.31.61.129 (9.31.61.129) 1.795 ms * 2 9.31.123.98 (9.31.123.98) 0.907 ms 1.179 ms 1.416 ms 3 10.196.18.109 (10.196.18.109) 0.866 ms 10.196.18.125 (10.196.18.125) 1.085 ms * 4 10.162.33.5 (10.162.33.5) 1.297 ms 10.200.16.169 (10.200.16.169) 0.774 ms 10.196.92.109 (10.196.92.109) 1.218 ms 5 10.162.32.145 (10.162.32.145) 1.539 ms 1.431 ms 10.162.32.149 (10.162.32.149) 1.310 ms 6 * * * 7 58.63.249.45 (58.63.249.45) 7.320 ms * 121.14.50.25 (121.14.50.25) 7.859 ms 8 * * 113.96.4.121 (113.96.4.121) 4.887 ms 9 202.97.22.149 (202.97.22.149) 32.481 ms 202.97.22.153 (202.97.22.153) 32.676 ms 10 36.110.245.206 (36.110.245.206) 36.928 ms 36.110.247.54 (36.110.247.54) 37.593 ms 36.110.245.82 (36.110.245.82) 41.254 ms 11 36.110.245.161 (36.110.245.161) 33.749 ms * 37.905 ms 12 * * * 13 * * 220.181.182.170 (220.181.182.170) 42.998 ms 14 * * * 15 * * * 16 * * * 17 * * * 18 * * * 19 * * * 20 * * * 21 * * * 22 * * * 23 * * * 24 * * * 25 * * * 26 * * * 27 * * * 28 * * * 29 * * * 30 * * * 第一行显示要访问的主机名和ip、traceroute将尝试到主机的最大跃点数以及要发送的字节数据包的大小。\n每行列出到达目的地的一个跳跃点。给出主机名与主机名的ip，然后是数据包到达主机并返回发起计算机所需的时间。默认情况下，traceroute 为每个主机发送三个数据包，因此列出了三个响应时间。\n星号 * 表示丢失的数据包。这意味着网络中断、大量流量导致网络拥塞或防火墙丢弃流量。\n追踪IPv6协议 bash 1 traceroute -6 ipv6.google.com 忽略主机名与IP的映射 使用-n选项在traceroute中禁用IP地址映射。\nbash 1 2 3 4 5 traceroute -n qq.com traceroute to qq.com (183.3.226.35), 30 hops max, 60 byte packets 1 9.31.61.129 0.908 ms 1.159 ms 1.537 ms 2 9.31.122.210 1.061 ms 0.837 ms 1.421 ms 设置相应等待时间 使用 -w 选项在traceroute 中配置响应等待时间，支持指定等待对探测的响应的时间（秒为单位）。\nbash 1 traceroute -w 1 -n qq.com 使用特定的网络接口 使用-i选项设置traceroute应使用的网络接口，如果未设置，则根据路由表选择接口。\nbash 1 traceroute -w 1 -n -i eth0 qq.com ping Ping是一种简单、广泛使用的跨平台网络工具，用于测试主机是否可以在Internet协议（IP）网络上访问。它的工作原理是向目标主机发送网络控制消息协议Internet Control Message Protocol (ICMP) ECHO_REQUEST，目标节点等待并回复 ECHO_RESPONSE。\n可以使用ping 测试两节点间的网络通信，可以做到：\n目标主机是否可用， 测量数据包到达目标主机并返回计算机所需的时间（与目标主机通信的往返时间（rtt）），以及数据包丢失的百分比。 各系统下的安装\nUbuntu/Debian: iputils-ping ；apt install iputils-ping CentOS/Fedora: iputils ；yum install -y iputils Apline：iputils ；apk add iputils 使用参数\n参数 说明 -c 指定发送ECHO_REQUEST的请求数 -i 设置包与包之间的间隔 ping -i 3 -c 5 www.google.com -f flood ping，检测高负载下的响应，需要有root权限 -b 允许ping一个广播地址 -t 限制ping遍历的网络跳跃数（TTL Time-to-live），收到数据包的每个路由器从计数中至少减去 1，如果大于 0，路由器会将数据包转发到下一跳，否则它会丢弃它并将 ICMP 响应返回。 -s 设置ping时的数据包大小（单位 bytes），这将导致提供的总数据包大小加上ICMP头的8个额外字节。 -l 发送预加载数据包（先发不等待回复的数据包），大于3需要root权限 -W 设置等待相应时间，单位秒 -w 设置超时时间，超时退出，单位秒 -d debug模式 -v 显示详细输出 -A 更快的在两节点间包往返的时间，非特权用户最小为200ms hping hping一个具有可嵌入tcl脚本功能的 TCP/IP包伪造工具。，主要用于创建或生成网络数据包以测试网络、服务或系统性能。 hping 是由不同实体开发的旧工具，并以 hping2 或 hping3 等新版本命名。 在大多数情况下，您可以使用操作系统提供的命令，可以是 hping 或 hping2 或 hping3。 hping 名称源自 ping 命令名称。hping3 是另一种用于扫描网络的工具。它在kali linux中默认是DOS攻击软件之一。\nhping支持TCP、UDP、ICMP、raw-IP等协议用于不同的用例。通过使用hping，可以创建具有不同选项的不同协议包。hping主要可以用作。\n创建原始IP数据包 生成指定数量的数据包 设置包发送间隔 指定传输网络接口 创建和生成TCP数据包 创建和生成UDP数据包 创建和生成IP数据包 创建和生成ICMP数据包 设置MTU值 设置碎片并创建碎片或未碎片的数据包 设置数据包的有效负载或数据大小 hping的常用场景\n模拟DOS和DDOS攻击 测试防火墙和TCP、UDP、IP等协议的防火墙配置 TCP和UDP端口扫描 测试网络设备的配置，如碎片、MTU等。 用于列出中间主机的高级跟踪路由 远程操作系统指纹识别和检测 远程正常运行时间决策 TCP/IP协议实现与栈测试审计 各系统下的安装\nUbuntu/Debian: hping3 ；apt install hping3 CentOS/Fedora: hping3 ；yum install epel-release \u0026amp;\u0026amp; yum install -y hping3 Apline：hping3 ；apk add hping3 --update-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing 参数说明 基础参数\n参数选项 参数说明 -c --count [count] 发送数据包的次数 关于countreached_timeout 可以在hping2.h里编辑 -i --interval 每个包发送间隔时间(单位是毫秒) 缺省时间是1秒,此功能在增加传输率上很重要。\n-i 1 为1s -i u1 为1us （微秒） 即每秒发送1000000包 --fast 为 -i u10000 的别名，即1秒发送10个包 --faster 为 -i u1 的别名，但实际上发送的包取决于计算机的速度 --flood 尽可能快速的发送包，不关注收到的恢复，要比 -i u0 快 -I --interface [interface name] 指定默认的路由接口，在linux中，hping3使用默认路由接口。可以使用 -I 接网络接口的完整名称，如 eth0 -q -quiet 安静输出。除了启动时和完成时的摘要信息外，不输出任何内容。 -n -nmeric 数字化输出主机地址 协议选项\n默认情况下，hping使用的为tcp协议\n选项 说明 -0 --rawip 原始IP模式，此模式下，hping3将发送IP头。 -1 --icmp ICMP模式，默认情况下hping3将发送ICMP回显请求。 -2 --udp UDP模式，默认情况下，hping3将向目标主机的0端口发送UDP -8 --scan 端口扫描，在该模式下，需要提供一组端口，如 1,2,3 端口组以 , 分隔\n端口范围：start-end 如 1000-2000 特殊字符：all 表示所有端口；know ：包含 /etc/services 中的所有端口\n组合写法：hping --scan 1-1000,8888,known -S www.baidu.com -9 --listen signature 监听模式，此模式下 hping3 等待包含签名的数据包并从签名端转储到数据包的结尾处。 IP相关选项\n参数 说明 -a --spoof hostname 此选项可以伪造源IP地址，可确保目标不会获得真实IP地址，必然性的响应将被发送到伪造的地址处。 --rand-source 此选项开启随机源模式。hping将发送带有随机源地址的数据包。 --rand-dest 此选项开启随机目标模式。hping将数据包发送到随机目标地址如，当使用随机目标地址时，可以使用x 作为范围，所有出现的 x 都将呗替换为0-255之间的随机数。如10.0.0.x。可以使用--debug 选项查看生成的随机地址。注意：使用此选项，hping无法检测数据包的正确传出接口，应使用 -I 选项指定网络接口。 -t --ttl 此选项可以设置传出数据包的TTL（生存时间） -N id 设置IP字段的随机值 -H --ipproto 在RAW IP模式中设置IP协议 -r --rel ip id等增量 -m –mtu 设置虚拟最大传输单元 icmp选项\n参数 说明 -C --icmptype type 设置icmp类型，默认为icmp echo reques。 --icmp-ipver 设设置包含在ICMP数据中的IP头的IP版本，默认值为4。 --icmp-ipproto 设置包含在ICMP数据中的IP头的IP协议，默认为TCP。 TCP/UDP选项\n参数 说明 -s --baseport [src port] 随机源端口 -p --destport [dest port] 设置目标端口\n+ 目标端口将随着收到的每个回复而增加\n++ 目标端口每发送数据包都会增加 \u0026ndash;keep 保持源端口不边 -w \u0026ndash;win 设置tcp窗口大小，默认64 -F \u0026ndash;fin 设置 tcp fin标记 -S \u0026ndash;syn 设置 tcp SYN标记 -R \u0026ndash;rst 设置 tcp rst标记 -P \u0026ndash;push 设置 tcp PUSH标记 -A \u0026ndash;ack 设置 tcp ACK标记 -U \u0026ndash;urg 设置 tcp URG标记 -X \u0026ndash;xmas 设置 tcp Xmas标记 -Y \u0026ndash;ymas 设置 tcp Ymas标记 常用参数\n参数 说明 -d --data 设置数据包主体大小。 使用 --data 40 hping将在 protocol_header 增加40 字节。 -E --file [filename] 使用文件名内容填充数据包的数据 -j --dump 以16进制导出数据包 -J --print 导出可打印的数据包 -u --end 如果使用 ``\u0026ndash;file filename` 选项，何时为EOF。 -T --traceroute traceroute 模式。此选项将在接收ttl来尝试追踪。 --tr-keep-ttl 保持ttl的固定，用于监视某一跳 –tr-stop traceroute 下收到第一个不是ICMP时退出 \u0026hellip;. 输出格式\nhping的一个标准的TCP/UDP格式如下，UDP字段含义与TCP的相同。\nbash 1 2 3 4 5 # tcp len=46 ip=192.168.1.1 flags=RA DF seq=0 ttl=255 id=0 win=0 rtt=0.4 ms # udp len=46 ip=192.168.1.1 seq=0 ttl=64 id=0 rtt=6.0 ms len：len是从数据链路层捕获的数据的大小（字节），不包括数据链路头大小。 ip： ip 为请求的ip flags：flags为TCP的标记，如 R RESET S SYN A ACK F FIN P PUSH U URGENT X 不标准的 0x40 Y 不标准的 0x80 seq：seq是数据包的序列号，使用TCP/UDP数据包的源端口获得 id 是IP ID字段。 win TCP 窗口大小 rtt 往返时间 （round trip time），单位毫秒 以下是使用-V参数后的字段 tos 是IP标头的服务类型字段。 iplen ip的总长度 seq 和 ack 是TCP标头中的序列号和32位确认号 是TCP标头校验和值。 urp TCP紧急指针值。 ICMP的输出格式\nbash 1 ICMP Port Unreachable from ip=192.168.1.1 name=nano.marmoc.net 在此格式中，ip 为 ICMP 错误的 IP 地址，name为解析的名称或者为UNKNOWN，而其他的参数含义与TCP/UDP大致相同。\n端口扫描 hping可以自由地创建原始IP、TCP、UDP和ICMP数据包。可以利用此功能生成 TCP SYN 扫描。TCP-SYN 扫描是最简单的将数据包发送到主机/IP端口的方法。这里 扫描的为110.242.68.4:80\n启动经典的扫描的最简单方法是将TCP-SYN数据包发送到主机/ip上的端口。下面的命令将扫描IP 192.168.8.223上的端口80。从输出中，可以看到 flags=SA SYN和ACK标记，代表一个开放端口。\nbash 1 hping3 -S 110.242.68.4 -p 80 -c 2 扫描一个范围的端口可以使用 ++\nbash 1 hping3 -S 110.242.68.4 -p ++80 也可以使用如下方式\nbash 1 2 3 4 5 6 7 8 9 10 hping3 -8 80-86 -S 110.242.68.4 Scanning 110.242.68.4 (110.242.68.4), port 80-86 7 ports to scan, use -V to see all the replies +----+-----------+---------+---+-----+-----+-----+ |port| serv name | flags |ttl| id | win | len | +----+-----------+---------+---+-----+-----+-----+ 80 http : .S..A... 128 60936 64240 46 All replies received. Done. Not responding ports: (81 ) (82 xfer) (83 mit-ml-dev) (84 ctf) (85 ) (86 mfcobol) 通过Hping3跟踪路由到指定端口： hping3支持一个很实用功能，可以追踪路由到一个指出的端口，查看你的数据包被阻塞的地方。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 hping3 --traceroute -p 80 -V -1 www.google.com using eth0, addr: 195.133.11.43, MTU: 1500 HPING www.google.com (eth0 142.250.150.104): icmp mode set, 28 headers + 0 data bytes hop=1 TTL 0 during transit from ip=195.133.10.1 name=gateway hop=1 hoprtt=3.1 ms hop=2 TTL 0 during transit from ip=10.11.12.37 name=UNKNOWN hop=2 hoprtt=10.0 ms hop=3 TTL 0 during transit from ip=62.140.243.62 name=msk-m9-b1-ae30-vlan449.fiord.net hop=3 hoprtt=1.9 ms hop=4 TTL 0 during transit from ip=62.140.239.113 name=msk-m9-b6-ae1-vlan12.fiord.net hop=4 hoprtt=9.8 ms hop=5 TTL 0 during transit from ip=72.14.222.198 name=UNKNOWN hop=5 hoprtt=4.2 ms hop=6 TTL 0 during transit from ip=108.170.250.33 name=UNKNOWN hop=6 hoprtt=3.8 ms hop=7 TTL 0 during transit from ip=108.170.250.51 name=UNKNOWN hop=7 hoprtt=2.5 ms hop=8 TTL 0 during transit from ip=142.251.49.158 name=UNKNOWN hop=8 hoprtt=34.7 ms hop=9 TTL 0 during transit from ip=108.170.235.204 name=UNKNOWN hop=9 hoprtt=18.2 ms hop=10 TTL 0 during transit from ip=142.250.209.35 name=UNKNOWN hop=10 hoprtt=17.1 ms .... 不同类型的ICMP bash 1 2 3 4 5 6 7 8 hping3 -c 5 -V -1 -C 17 110.242.68.4 using eth0, addr: 10.0.0.4, MTU: 1500 HPING 110.242.68.4 (eth0 110.242.68.4): icmp mode set, 28 headers + 0 data bytes --- 110.242.68.4 hping statistic --- 5 packets transmitted, 0 packets received, 100% packet loss round-trip min/avg/max = 0.0/0.0/0.0 ms 通过hping3进行TCP FIN扫描\n在TCP连接中，FIN标志用于开始请求关闭连接。万一没有得到答复，那说明端口是开放的。通常防火墙会再次发送Rst+ack数据包，以指示该端口已关闭。\n通过hping3 进行ACK扫描 有些情况下，主机可能禁止PING ICMP，此时使用ACK扫描可以用于检查主机是否处于活动状态。如果主机活跃，会相应RST标记，在hping中是为 flags=R。\nbash 1 2 3 4 5 6 7 hping3 -c 2 -V -p 80 -A 110.242.68.4 using eth0, addr: 10.0.0.4, MTU: 1500 HPING 110.242.68.4 (eth0 110.242.68.4): A set, 40 headers + 0 data bytes len=46 ip=110.242.68.4 ttl=128 id=2391 tos=0 iplen=40 sport=80 flags=R seq=0 win=32767 rtt=0.6 ms seq=1165126080 ack=0 sum=c0ba urp=0 UDP扫描 使用参数 -2 可以让hping工作于UDP模式，可以进行UDP扫描\nbash 1 hping3 -2 8.8.4.4 -V -p 53 -c 10 操作系统识别 使用-Q或-seqnum可以让hping 收集了ISN。\nbash 1 2 3 4 5 6 7 hping3 127.0.0.1 -Q -p 22 -V -S using lo, addr: 127.0.0.1, MTU: 65536 HPING 127.0.0.1 (lo 127.0.0.1): S set, 40 headers + 0 data bytes 893247485 +893247485 2568100167 +1674852682 2600543427 +32443260 内容探测 可以使用hping的监听模式，来抓取通过网络接口的所有流量，以及捕获对应的内容。例如抓取通过谷歌搜索的流量包\nbash 1 hping3 -9 \u0026#34;www.google.com\u0026#34; --beep -I eth0hping2 listen mode[main] memlockall(): SuccessWarning: can\u0026#39;t disable memory paging!Accept: */*.hk/url?sa=p\u0026amp;hl=zh-CN\u0026amp;pref=hkredirect\u0026amp;pval=yes\u0026amp;q=http://www.google.com.hk/\u0026amp;ust=1624605433464983\u0026amp;usg=AOvVaw2THxd5w15lxgX3_KA19GWLCache-Control: privateContent-Type: text/html; charset=UTF-8P3P: CP=\u0026#34;This is not a P3P policy! See g.co/p3phelp for more info.\u0026#34;Date: Fri, 25 Jun 2021 07:16:43 GMTServer: gwsContent-Length: 370X-XSS-Protection: 0X-Frame-Options: SAMEORIGINSet-Cookie: 1P_JAR=2021-06-25-07; expires=Sun, 25-Jul-2021 07:16:43 GMT; path=/; domain=.google.com; SecureSet-Cookie: NID=217=PdQLBtU-tTavgvb4BW9ouB3nAr1OKNK6I_kn9u2Qa2eTgLA_qLyGv2G_2t2G_PRNVrKu2SOEm-e7ED17ljnx3uFBweBjQWOyRvHrJ6jhC5_J3yaBK0r8mikUrqHNjDez5F3rCleFQDurBEfnqECDFXNkvvO_-Wn4ahGJeid01TM; expires=Sat, 25-Dec-2021 07:16:43 GMT; path=/; domain=.google.com; HttpOnly\u0026lt;HTML\u0026gt;\u0026lt;HEAD\u0026gt;\u0026lt;meta http-equiv=\u0026#34;content-type\u0026#34; content=\u0026#34;text/html;charset=utf-8\u0026#34;\u0026gt;\u0026lt;TITLE\u0026gt;302 Moved\u0026lt;/TITLE\u0026gt;\u0026lt;/HEAD\u0026gt;\u0026lt;BODY\u0026gt;\u0026lt;H1\u0026gt;302 Moved\u0026lt;/H1\u0026gt;The document has moved\u0026lt;A HREF=\u0026#34;http://www.google.com.hk/url?sa=p\u0026amp;amp;hl=zh-CN\u0026amp;amp;pref=hkredirect\u0026amp;amp;pval=yes\u0026amp;amp;q=http://www.google.com.hk/\u0026amp;amp;ust=1624605433464983\u0026amp;amp;usg=AOvVaw2THxd5w15lxgX3_KA19GWL\u0026#34;\u0026gt;here\u0026lt;/A\u0026gt;.\u0026lt;/BODY\u0026gt;\u0026lt;/HTML\u0026gt;.hk/\u0026amp;ust=1624605433464983\u0026amp;usg=AOvVaw2THxd5w15lxgX3_KA19GWL HTTP/1.1User-Agent: curl/7.29.0Host: www.google.com.hkAccept: */*.hk/Cache-Control: privateContent-Type: text/html; charset=UTF-8P3P: CP=\u0026#34;This is not a P3P policy! See g.co/p3phelp for more info.\u0026#34;Date: Fri, 25 Jun 2021 07:16:43 GMTServer: gwsContent-Length: 222X-XSS-Protection: 0Set-Cookie: 1P_JAR=2021-06-25-07; e 网络后门 可以通过hping3的监听模式，创建一个简单的后门(backdoor)，通过管道来执行脚本\nbash 1 hping3 -I eth1 -9 secret | /bin/shhping3 -R 192.168.1.100 -e secret -E commands_file -d 100 -c 1 nslookup nslookup（name server lookup）用于在Linux中执行DNS查找的工具。用于显示DNS详细信息，例如计算机的IP地址、域的MX记录或域的NS服务器。\nnslookup 可以在两种模式下运行：交互式和非交互式。交互模式可以查询名称服务器以获取有关各种主机和域的信息或打印域中的主机列表。非交互模式仅打印主机或域的名称和请求的信息。\n各系统下的安装\nUbuntu/Debian: knot-dnsutils ；apt install knot-dnsutils CentOS/Fedora: bind-utils | dnsutils ；yum install -y bind-utils Apline：bind-tools ；apk add bind-tools 简单查询 nslookup后跟域名将显示域名的“A记录”（IP地址）,nslookup命令的默认输出比dig命令的默认输出相对整洁些。\nbash 1 nslookup redhat.com 执行反向DNS查找：\nbash 1 nslookup 208.117.229.88 查询MX记录 MX（ Mail Exchange ）记录将域名映射到该域的邮件服务器列表。MX记录表明发到 @qq.com 的所有邮件都应该路由到该域中的邮件服务器。\nbash 1 2 3 4 5 6 7 8 9 10 11 nslookup -query=mx qq.com Server: 183.60.83.19 Address: 183.60.83.19#53 Non-authoritative answer: qq.com mail exchanger = 20 mx2.qq.com. qq.com mail exchanger = 30 mx1.qq.com. qq.com mail exchanger = 10 mx3.qq.com. Authoritative answers can be found from: Authoritative Answer与Non-Authoritative Answer\n可以注意到注意到上面输出中的关键字 Authoritative 和 Non-Authoritative Answer。任何来自DNS服务器的答复都称为Authoritative Answer，该服务器具有域可用的完整区域文件信息。在许多情况下，DNS服务器将不具备给定域的完整区域文件信息。相反，它维护一个缓存文件，该文件包含过去执行的所有查询的结果，并已获得权威响应。当给出一个DNS查询时，它搜索缓存文件，并以 Non-Authoritative Answer 的形式返回可用的信息。\n查询NS记录 NS ( Name Server ) 记录将域名映射到该域的授权DNS服务器列表。它将输出与给定域关联的名称服务。\nbash 1 nslookup -type=ns qq.comServer: 183.60.83.19Address: 183.60.83.19#53Non-authoritative answer:qq.com nameserver = ns1.qq.com.qq.com nameserver = ns2.qq.com.qq.com nameserver = ns3.qq.com.qq.com nameserver = ns4.qq.com.Authoritative answers can be found from: 查询SOA记录 SOA ( start of authority )记录\\，提供关于域的权威信息、域管理员的电子邮件地址、域序列号等。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 nslookup -type=soa qq.com Server: 183.60.83.19 Address: 183.60.83.19#53 Non-authoritative answer: qq.com origin = ns1.qq.com mail addr = webmaster.qq.com serial = 1330914143 refresh = 3600 retry = 300 expire = 86400 minimum = 300 Authoritative answers can be found from: mail addr–指定域管理员的邮件地址 serial 一种版本编号系统。标准惯例是使用 YYYYMMYNN 格式 2012-07-16.01如果在同一天进行了多个编辑，则将递增） refresh 指定从DNS服务何时轮询主DNS以查看序列号是否已增加（以秒为单位）。如果增加，从DNS服务器将发出复制新区域文件的新请求。 retry 指定与主DNS重新连接的间隔 expire 指定辅助DNS保持缓存区域文件有效的时间 minimum 指定从DNS应缓存区域文件的时间 查看可用的DNS记录 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 nslookup -type=any qq.com Server: 183.60.83.19 Address: 183.60.83.19#53 Non-authoritative answer: Name: qq.com Address: 61.129.7.47 Name: qq.com Address: 183.3.226.35 Name: qq.com Address: 203.205.254.157 Name: qq.com Address: 123.151.137.18 qq.com mail exchanger = 10 mx3.qq.com. qq.com mail exchanger = 20 mx2.qq.com. qq.com mail exchanger = 30 mx1.qq.com. Authoritative answers can be found from: 使用指定DNS查询 可以指定特定的DNS来解析域名，而不是使用默认DNS进行查询。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 nslookup www.qq.com 8.8.8.8 Server: 8.8.8.8 Address: 8.8.8.8#53 Non-authoritative answer: www.qq.com canonical name = news.qq.com.edgekey.net. news.qq.com.edgekey.net canonical name = e6156.dscf.akamaiedge.net. Name: e6156.dscf.akamaiedge.net Address: 23.219.132.75 Name: e6156.dscf.akamaiedge.net Address: 2600:1417:76:494::180c Name: e6156.dscf.akamaiedge.net Address: 2600:1417:76:480::180c 使用特殊的dns端口 默认情况下，DNS使用端口号为53。可以使用-port选项指定端口号。\nbash 1 nslookup -port 56 qq.com 设置超时时间 可以使用 -timeout 选项来指定超时时间\nbash 1 nslookup -timeout=10 qq.com 启用调试模式 -debug 选项打开/关闭调试\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 nslookup -debug qq.com Server: 183.60.83.19 Address: 183.60.83.19#53 ------------ QUESTIONS: qq.com, type = A, class = IN ANSWERS: -\u0026gt; qq.com internet address = 183.3.226.35 ttl = 92 -\u0026gt; qq.com internet address = 203.205.254.157 ttl = 92 -\u0026gt; qq.com internet address = 61.129.7.47 ttl = 92 -\u0026gt; qq.com internet address = 123.151.137.18 ttl = 92 AUTHORITY RECORDS: ADDITIONAL RECORDS: ------------ Non-authoritative answer: Name: qq.com Address: 183.3.226.35 Name: qq.com Address: 203.205.254.157 Name: qq.com Address: 61.129.7.47 Name: qq.com Address: 123.151.137.18 ------------ QUESTIONS: qq.com, type = AAAA, class = IN ANSWERS: AUTHORITY RECORDS: -\u0026gt; qq.com origin = ns1.qq.com mail addr = webmaster.qq.com serial = 1330914143 refresh = 3600 retry = 300 expire = 86400 minimum = 300 ttl = 296 ADDITIONAL RECORDS: ------------ dig dig（Domain Information Groper) 执行DNS查找。默认情况下，dig查询通过 resolver ( /etc/resolv.conf ) 中列出的DNS地址，除非指定特定的name server。\n语法 bash 1 dig @server name type 解析IP地址 dig 通常不带参数地用于获取提供的DNS名称的IP地址。默认使用系统提供的DNS服务器用于DNS解析。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 dig www.qq.com ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.3 \u0026lt;\u0026lt;\u0026gt;\u0026gt; www.qq.com ;; global options: +cmd ;; Got answer: ;; -\u0026gt;\u0026gt;HEADER\u0026lt;\u0026lt;- opcode: QUERY, status: NOERROR, id: 40004 ;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;www.qq.com. IN A ;; ANSWER SECTION: www.qq.com. 132 IN CNAME ins-r23tsuuf.ias.tencent-cloud.net. ins-r23tsuuf.ias.tencent-cloud.net. 60 IN A 109.244.236.76 ins-r23tsuuf.ias.tencent-cloud.net. 60 IN A 109.244.236.65 ;; Query time: 11 msec ;; SERVER: 183.60.83.19#53(183.60.83.19) ;; WHEN: Tue Jun 22 21:39:33 CST 2021 ;; MSG SIZE rcvd: 108 dig命令输出包括以下部分：\nHEADER：显示dig命令的版本号、dig命令使用的全局选项，以及一些附加的Header信息。 QUESTION SECTION：显示dig像DNSserver发出的请求。即你请求的域名。这里使用dig命令获取qq.com使用的默认类型（A记录） ANSWER SECTION：显示从DNS接收到的应答。将显示qq.com 的A记录 ADDITIONAL SECTION：显示ADDITIONAL SECTION 中列出的DNS服务器的ip地址。 底部的Stats部分显示一些dig命令统计信息，包括执行此查询所用的时间 仅显示应答部分 在大多数情况下，我们只需要查看dig的 ANSWER SECTION。可以仅打印该部分。\n+nocomments 不显示注释行 +noauthority 不显示authority部分 +noadditional 不显示 additional 部分 +nostats 不显示统计信息 stats +noanswer 关掉ANSWER 部分，这里一般为想要的结果 bash 1 2 3 4 5 6 7 8 9 10 11 12 dig www.qq.com \\ +nocomments \\ +noquestion \\ +noauthority \\ +noadditional \\ +nostats ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.3 \u0026lt;\u0026lt;\u0026gt;\u0026gt; www.qq.com +nocomments +noquestion +noauthority +noadditional +nostats ;; global options: +cmd www.qq.com. 180 IN CNAME ins-r23tsuuf.ias.tencent-cloud.net. ins-r23tsuuf.ias.tencent-cloud.net. 31 IN A 109.244.236.65 ins-r23tsuuf.ias.tencent-cloud.net. 31 IN A 109.244.236.76 也可以使用 +noal 禁用所有不需要的部分，当然也会关掉 answer ，然后+answer 只显示 answer部分，这样看起来简洁些。\nbash 1 2 3 dig www.qq.com \\ +noall \\ +answer 查询MX记录 将MX作为参数，可以查询mx记录，可以使用 -t 增加类型\nbash 1 2 3 4 5 6 7 dig qq.com MX +noall +answer ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.3 \u0026lt;\u0026lt;\u0026gt;\u0026gt; qq.com MX +noall +answer ;; global options: +cmd qq.com. 4969 IN MX 10 mx3.qq.com. qq.com. 4969 IN MX 20 mx2.qq.com. qq.com. 4969 IN MX 30 mx1.qq.com. 查询NS记录 bash 1 dig qq.com NS +noall +answer 查询所有记录 查看所有记录类型（A、MX、NS等），可以使用ANY作为类型。\nbash 1 2 3 4 5 6 7 8 dig qq.com ANY +noall +answer ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.3 \u0026lt;\u0026lt;\u0026gt;\u0026gt; qq.com ANY +noall +answer ;; global options: +cmd qq.com. 83 IN A 183.3.226.35 qq.com. 83 IN A 203.205.254.157 qq.com. 83 IN A 123.151.137.18 qq.com. 83 IN A 61.129.7.47 仅查看记录的IP 有些场景下，仅需要域名的ip地址（即a记录），可以使用 +short 选项。\nbash 1 2 3 4 5 dig qq.com +short 123.151.137.18 203.205.254.157 183.3.226.35 61.129.7.47 +short 也可指定类型\nbash 1 2 3 4 5 6 7 8 9 10 dig qq.com a +short 111.30.144.71 112.53.26.232 dig qq.com mx +short 10 mx3.qq.com. 20 mx2.qq.com. 30 mx1.qq.com. 反向查找 可以使用dig-x 进行ip地址反向查找DNS，场景：如果只有一个外部ip地址，并且希望知道属于它的网站时。当然过了CDN的域名，只会显示对应CNAME\nbash 1 dig -x 203.205.254.157 +short 使用指定DNS来进行查询 默认情况下，dig 使用 /etc/resolv.conf 文件中定义的DNS。如果要使用其他DNS执行查询，使用 @dnsserver。\nbash 1 2 3 4 5 dig @8.8.8.8 www.qq.com +short ins-r23tsuuf.ias.tencent-cloud.net. 109.244.236.76 109.244.236.65 批量查询 进行批量查询时可以不用通过shell循环查询了，dig提供了批量查询的功能。使用dig -f 从文件内进行批量DNS查询。\nbash 1 2 3 4 5 6 7 8 9 10 11 echo www.qq.com \u0026gt; dns.txt echo www.baidu.com \u0026gt;\u0026gt; dns.txt dig -f dns.txt +noall +answer www.baidu.com. 678 IN CNAME www.a.shifen.com. www.a.shifen.com. 106 IN A 14.215.177.39 www.a.shifen.com. 106 IN A 14.215.177.38 www.qq.com. 60 IN CNAME ins-r23tsuuf.ias.tencent-cloud.net. ins-r23tsuuf.ias.tencent-cloud.net. 60 IN A 109.244.236.65 ins-r23tsuuf.ias.tencent-cloud.net. 60 IN A 109.244.236.76 也可以在命令行直接根多个域名即可，这样查询结果相比于shell循环查询会简洁很多。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 dig qq.com mx +noall +answer baidu.org ns +noall +answer ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.3 \u0026lt;\u0026lt;\u0026gt;\u0026gt; qq.com mx +noall +answer baidu.org ns +noall +answer ;; global options: +cmd qq.com. 4223 IN MX 10 mx3.qq.com. qq.com. 4223 IN MX 20 mx2.qq.com. qq.com. 4223 IN MX 30 mx1.qq.com. baidu.org. 300 IN NS ns4.brandshelter.net. baidu.org. 300 IN NS ns3.brandshelter.info. baidu.org. 300 IN NS ns2.brandshelter.de. baidu.org. 300 IN NS ns5.brandshelter.us. baidu.org. 300 IN NS ns1.brandshelter.com. 设置dig默认选项 如别名 alias 一样，在查询中不想输入过多的 +noall +answer 之类，可以在 $HOME/.digrc 设置dig 的默认参数，这样只需和平时一样使用 dig domain 即可。\nbash 1 cat \u0026lt;\u0026lt;EOF \u0026gt;${HOME}/.digrc+noall +answerEOFdig www.qq.comwww.qq.com. 247 IN CNAME ins-r23tsuuf.ias.tencent-cloud.net.ins-r23tsuuf.ias.tencent-cloud.net. 67 IN A 109.244.236.76ins-r23tsuuf.ias.tencent-cloud.net. 67 IN A 109.244.236.6 curl curl 是Linux命令行工具，可以使用任何可支持的协议（如HTTP、FTP、IMAP、POP3、SCP、SFTP、SMTP、TFTP、TELNET、LDAP或FILE）在服务器之间传输数据。\n在Linux下，curl是由 libcurl 提供驱动封装的cli客户端，在 libcurl 驱动下，curl可以一次传输多个文件。而PHP中的cURL函数，也是基于libcurl驱动的。\n各系统下的安装\nUbuntu/Debian: curl； apt install curl CentOS/Fedora: curl ； yum install -y curl Apline：curl | wget ； apk add --no-cache curl cURL常用参数 参数 说明 -i 默认隐藏响应头，此选项打印响应头与 -I/\u0026ndash;head 仅显示响应头 -o 将相应内容保存指定路径下 -O 将相应内容保存在当前工作目录下 -C 断点续传，在 crtl + c终端后，可以从中断后部分开始 -v 显示请求头与响应头 -x 使用代理 -X 指定请求方法，POST GET PUT DELETE等 -d 如GET/POST/PUT/DELETE 需要传的表单参数，如JSON格式 -u username:password 当使用ftp有用户名可以使用-u，ftp允许匿名用户访问可以忽略 –-limit-rate 2000B 限速 -T/\u0026ndash;upload-file \u0026lt;file\u0026gt; 上传一个文件 -c/\u0026ndash;cookie-jar \u0026lt;file name\u0026gt; 将cookie下载到文件内 -k/\u0026ndash;insecure 允许执行不安全的ssl连接，即调过SSL检测 --header 'Host: targetapplication.com' 使用请求头 -L/\u0026ndash;location 接受服务端redirect的请求 -F 上传二进制文件 限制下载速率 bash 1 curl --limit-rate 100K http://yourdomain.com/yourfile.tar.gz -O 使用代理访问 bash 1 curl --proxy yourproxy:port https://yoururl.com 限速访问 bash 1 curl www.baidu.com --limit-rate 1k 存储cookie和使用cookie text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 $ curl --cookie-jar cnncookies.txt https://www.baidu.com/index.html -O -s -v * About to connect() to www.baidu.com port 443 (#0) * Trying 14.215.177.39... * Connected to www.baidu.com (14.215.177.39) port 443 (#0) * Initializing NSS with certpath: sql:/etc/pki/nssdb * CAfile: /etc/pki/tls/certs/ca-bundle.crt CApath: none * SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 * Server certificate: * subject: CN=baidu.com,O=\u0026#34;Beijing Baidu Netcom Science Technology Co., Ltd\u0026#34;,OU=service operation department,L=beijing,ST=beijing,C=CN * start date: Apr 02 07:04:58 2020 GMT * expire date: Jul 26 05:31:02 2021 GMT * common name: baidu.com * issuer: CN=GlobalSign Organization Validation CA - SHA256 - G2,O=GlobalSign nv-sa,C=BE \u0026gt; GET /index.html HTTP/1.1 \u0026gt; User-Agent: curl/7.29.0 \u0026gt; Host: www.baidu.com \u0026gt; Accept: */* \u0026lt; HTTP/1.1 200 OK \u0026lt; Accept-Ranges: bytes \u0026lt; Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform \u0026lt; Connection: keep-alive \u0026lt; Content-Length: 2443 \u0026lt; Content-Type: text/html \u0026lt; Date: Wed, 26 May 2021 12:14:41 GMT \u0026lt; Etag: \u0026#34;58860402-98b\u0026#34; \u0026lt; Last-Modified: Mon, 23 Jan 2017 13:24:18 GMT \u0026lt; Pragma: no-cache \u0026lt; Server: bfe/1.0.8.18 * Added cookie BDORZ=\u0026#34;27315\u0026#34; for domain baidu.com, path /, expire 1622117681 \u0026lt; Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/ \u0026lt; { [data not shown] * Connection #0 to host www.baidu.com left intact text 1 # Netscape HTTP Cookie File# http://curl.haxx.se/docs/http-cookies.html# This file was generated by libcurl! Edit at your own risk..baidu.com TRUE / FALSE 1622117681 BDORZ 27315 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 $ curl --cookie cnncookies.txt https://www.baidu.com -s -v -o /dev/null * About to connect() to www.baidu.com port 443 (#0) * Trying 14.215.177.39... * Connected to www.baidu.com (14.215.177.39) port 443 (#0) * Initializing NSS with certpath: sql:/etc/pki/nssdb * CAfile: /etc/pki/tls/certs/ca-bundle.crt CApath: none * SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 * Server certificate: * subject: CN=baidu.com,O=\u0026#34;Beijing Baidu Netcom Science Technology Co., Ltd\u0026#34;,OU=service operation department,L=beijing,ST=beijing,C=CN * start date: Apr 02 07:04:58 2020 GMT * expire date: Jul 26 05:31:02 2021 GMT * common name: baidu.com * issuer: CN=GlobalSign Organization Validation CA - SHA256 - G2,O=GlobalSign nv-sa,C=BE \u0026gt; GET / HTTP/1.1 \u0026gt; User-Agent: curl/7.29.0 \u0026gt; Host: www.baidu.com \u0026gt; Accept: */* \u0026gt; Cookie: BDORZ=27315 \u0026lt; HTTP/1.1 200 OK \u0026lt; Accept-Ranges: bytes \u0026lt; Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform \u0026lt; Connection: keep-alive \u0026lt; Content-Length: 2443 \u0026lt; Content-Type: text/html \u0026lt; Date: Wed, 26 May 2021 12:23:27 GMT \u0026lt; Etag: \u0026#34;58860402-98b\u0026#34; \u0026lt; Last-Modified: Mon, 23 Jan 2017 13:24:18 GMT \u0026lt; Pragma: no-cache \u0026lt; Server: bfe/1.0.8.18 * Replaced cookie BDORZ=\u0026#34;27315\u0026#34; for domain baidu.com, path /, expire 1622118207 \u0026lt; Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/ # 这里可以看到设置的cookie \u0026lt; { [data not shown] * Connection #0 to host www.baidu.com left intact 使用代理 bash 1 curl -x socks5://127.0.0.1:10808 https://www.google.com 使用application/x-www-form-urlencoded表单类型 这里使用的为application/x-www-form-urlencoded\nbash 1 curl -d \u0026#34;option=value\u0026amp;something=anothervalue\u0026#34; -X POST https://{hostname}/ 使用json格式作为body bash 1 2 curl -H \u0026#34;Content-Type: application/json\u0026#34; -X POST https://host.com/ \\ -d \u0026#39; { \u0026#34;option\u0026#34;: \u0026#34;value\u0026#34;, \u0026#34;something\u0026#34;: \u0026#34;anothervalue\u0026#34; }\u0026#39; 使用curl 上传文件 bash 1 2 curl {host}/api/v1/upimg -F \u0026#34;file=@/Users/fungleo/Downloads/401.png\u0026#34; \\ -H \u0026#34;token: 222\u0026#34; -v 也可以指定MIME类型。如：\nbash 1 curl -F \u0026#39;file=@photo.png;type=image/png\u0026#39; https://{host}/api/v1/upimg curl输出的格式变量 curl -w参数提供了一些格式变量，可以达到紧紧获取某些数据\n仅获取http状态码 bash 1 curl -w %{http_code} www.baidu.com -o /dev/null -s 获取整个请求的时间 获取整个请求的耗时，单位秒，显示单位 毫秒\nbash 1 curl -w %{time_total} www.baidu.com -o /dev/null -s 获取域名解析时间 bash 1 curl -w %{time_namelookup} www.baidu.com -o /dev/null -s 获取TCP连接耗时 bash 1 curl -w %{time_connect} www.baidu.com -o /dev/null -s 获取SSL/SSH握手到远程主机耗时 bash 1 curl -w %{time_appconnect} https://www.baidu.com -o /dev/null -s -v 获取所有重定向的耗时 这里是从查找、连接、传输整个事务的完成到开始传送数据之前的耗时\nbash 1 curl -w %{time_redirect} www.baidu.com -o /dev/null -s 获得下载的总字节数 这里是http相应的body长度，而不是加上头部的大小\nbash 1 curl -w %{size_download} www.baidu.com -o /dev/null -s text 1 2 $ curl -w %{size_download} www.baidu.com -o /dev/null -s 2381 获得请求体送字节数 bash 1 curl -w %{size_request} www.baidu.com -o /dev/null -s 获得传输中的连接数 bash 1 curl -w %{num_connects} www.baidu.com -o /dev/null -s 获得重定向次数 bash 1 curl -w %{num_redirects} www.360buy.com -o /dev/null -s -L 获得SSL验证结果 0 表示是成功的\nbash 1 curl -w %{ssl_verify_result} https://www.baidu.com -o /dev/null -s -L 获得重定向的地址 当没有指定-L时，会返回被重定向后的地址\nbash 1 curl -w %{redirect_url} https://www.360buy.com -o /dev/null -s 获得上传和下载速度 bash 1 2 curl -w %{speed_download} https://www.360buy.com -o /dev/null -s curl -w %{speed_upload} https://www.360buy.com -o /dev/null -s 根据自己需要拼接特定格式 bash 1 2 3 4 curl -w \u0026#34;总共请求时长：%{time_total}\\n总跳转次数：%{num_redirects}\\n\u0026#34; \\ www.360buy.com -o /dev/null -s 总共请求时长：1.338总跳转次数：3 wget wget 用于从web下载文件的命令行程序。wget，可以使用 HTTP、HTTPS和 FTP 协议下载文件。wget还允许下载多个文件、断点续传、限速、递归下载、后台下载、镜像网站等等。\n各系统下的安装\nUbuntu/Debian: wget ； apt install wget CentOS/Fedora: wget ； yum install -y wget Apline： wget ； apk add --no-cache wget 简单使用 使用wget最简单的方法是为它提供通过HTTP下载的文件的位置。如，下载文件。\nbash 1 wget http://website.com/files/file.zip 该操作会将文件下载到工作目录中。\n下载文件并保存为指定名称 bash 1 wget –O [file_name] [URL] 将文件下载到指定目录 默认情况下，wget下载的文件保存在用户所在工作目录中。使用 –P 可以将文件保存到指定路径。\nbash 1 wget –P [wanted_directory] [URL] 设置下载速度 在下载时可以设置下载时最大使用带宽，这样就不会使用主机全部的可用带宽。下载速度以 k 和 m 定义单位。\nbash 1 2 3 wget --limit-rate [wanted_speed] [URL] wget --limit-rate 1m http://us.download.nvidia.com/tesla/396.37/nvidia-diag-driver-local-repo-ubuntu1710-396.37_1.0-1_amd64.deb 断点续传 如果在下载时取消，wget提供了可以在中断前停止的地方继续下载。当下载文件时连接丢失时，这个非常有用。\nbash 1 wget –c [URL] 下载多个文件 wget也提供了下载多个文件的方法：\n方法1：将需要下载的文件地址保存在文件中 使用 -i 指定文件，每个URL 单独占一行\nbash 1 wget –i [file_name] 下载网页（网站镜像） 使用 –m 下载URL中包含的所有连接，结果会保存为一个文件夹\nbash 1 wget –m [URL] FTP下载 wget也可以下载FTP文件，当需要认证时，可以指定FTP的用户名和密码，然后接FTP地址：\nbash 1 wget --ftp-user=[ftp_username] --ftp-password=[ftp_password] ftp://... 后台下载 当下载文件很大时，wget也支持后台下载文件，在网络不稳定命令行断开时很实用。\nbash 1 wget –b [URL] 可以使用命令 tail –f wget –log 来检查下载状态\n中断重试次数 当网络中断后，wget也支持设置在网络中断后尝试下载文件的次数：\nbash 1 wget --tries=[number_of_tries] [URL] 忽略证书验证 默认情况下，wget会验证服务端SSL/TLS证书是否有效。如果识别到无效的证书，它将拒绝下载。当在访问自签名证书时，可以使用--no-check-certificate 忽略验证\nbash 1 wget --no-check-certificate [URL] 自定义User-Agent 当服务端阻止了特定的 User-Agent 时，可以进行自定义 User-Agent 设置。\nbash 1 wget --user-agent=”User Agent Here” “[URL]” 实用技巧-下载内容到标准输出stdout 如在下载一个tar包时，一般都是wget 后 在tar 解压到对应目录，可以使用 -O - 将其下载到标准输出，-q 静默方式，通过管道直接解压到对应的路径下。\nbash 1 wget -q -O - \u0026#34;http://wordpress.org/latest.tar.gz\u0026#34; | tar -xzf - -C /var/www ss ss (socket statistics) 命令行工具，用于在Linux系统上显示与网络套接字相关的信息。\n各系统下的安装\nss Ubuntu/Debian: iproute2 ；apt install iproute2 CentOS/Fedora: iproute ；yum install -y iproute Apline：iproute ；apk add --no-cache iproute 查看所有连接 没有任何选项的ss命令只列出所有连接。\n查看Listening 与 Non-listening Ports bash 1 ss -a 查看监听 套接字列表 这里列出所有监听套接字，不关其是服务监听还是客户端请求占用\nbash 1 ss -l 查看所有TCP连接 这里只所有的tcp连接， 包含客户端与服务端\nbash 1 ss -t 查看所有监听类型的tcp连接 bash 1 ss -lt 查看所有udp连接 bash 1 ss -ua 查看监听类型的UDP连接 bash 1 ss -lu 显示socket的pid进程id bash 1 ss -p 显示连接摘要信息 bash 1 ss -s 显示ipv6或ipv4 连接 bash 1 2 ss -4 ss -6 筛选连接 语法\nbash 1 ss [ OPTIONS ] [ STATE-FILTER ] [ ADDRESS-FILTER ] ss命令还提供了筛选方法，过滤套接字端口或地址。例如，要显示具有ssh服务的源端口与目标端口（即监听与客户端连接）。\ntext 1 ss -at \u0026#39;( dport = :22 or sport = :22 )\u0026#39; 也可以通过服务名称进行过滤\nbash 1 ss -at \u0026#39;( dport = :ssh or sport = :ssh )\u0026#39; 仅显示所有处于 established 状态的Ipv4 tcp套接字。\nbash 1 2 3 4 5 6 7 8 9 10 ss -t4 state established -n Recv-Q Send-Q Local Address:Port Peer Address:Port 0 0 172.16.0.2:22 61.50.248.5:22005 0 0 172.16.0.2:42930 169.254.0.55:5574 0 0 172.16.0.2:22 61.50.248.5:22008 0 0 172.16.0.2:22 61.50.248.5:22003 0 0 172.16.0.2:40652 94.130.12.30:443 0 36 172.16.0.2:22 61.50.248.5:22012 0 0 172.16.0.2:22 61.50.248.5:22004 列出状态为time wait的套接字\nbash 1 ss -t4 state time-wait -n 这里状态可以为下面的任意一种\nestablished syn-sent syn-recv fin-wait-1 fin-wait-2 time-wait closed close-wait last-ack closing all 上面所有状态 connected 除listen和closed之外的所有状态 synchronized 除syn-sent之外的所有连接状态 显示状态，这些被维护为mini sockets，即 time-wait 与 syn-recv big 与bucket选项相反 过滤地址\nbash 1 ss -nt dst 74.125.236.178 还可以过滤网段\nbash 1 ss -nt dst 74.125.236.178/16 ip和端口的组合\nbash 1 ss -nt dst 74.125.236.178:80 源地址为127.0.0.1，且源端口大于5000\nbash 1 ss -nt src 127.0.0.1 sport gt :5000 源端口为25的smtp套接字\nbash 1 ss -ntlp sport eq :smtp 端口号大于25\nbash 1 ss -nt sport gt :1024 远程端口小于100的套接字\nbash 1 ss -nt dport \\\u0026lt; :100 连接到远程80端口的\nbash 1 ss -nt state connected dport = :80 不解析主机名 可以通过 -n 选项阻止ss 将ip解析为主机名，来达到更快地获得输出，但这也无法进行到端口号的解析。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ss -at \u0026#39;( dport = :22 or sport = :22 )\u0026#39; State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:ssh *:* ESTAB 0 0 172.16.0.2:ssh 111.206.214.55:49374 ESTAB 0 0 172.16.0.2:ssh 61.50.248.5:optohost005 ESTAB 0 36 172.16.0.2:ssh 61.50.248.5:22008 ESTAB 0 0 172.16.0.2:ssh 61.50.248.5:optohost003 ESTAB 0 0 172.16.0.2:ssh 61.50.248.5:optohost004 LISTEN 0 128 [::]:ssh [::]:* ss -at \u0026#39;( dport = :22 or sport = :22 )\u0026#39; -n State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:22 *:* ESTAB 0 0 172.16.0.2:22 111.206.214.55:49374 ESTAB 0 0 172.16.0.2:22 61.50.248.5:22005 ESTAB 0 36 172.16.0.2:22 61.50.248.5:22008 ESTAB 0 0 172.16.0.2:22 61.50.248.5:22003 ESTAB 0 0 172.16.0.2:22 61.50.248.5:22004 LISTEN 0 128 [::]:22 [::]:* 仅显示监听套接字 bash 1 ss -ltn 要列出所有侦听的udp连接，请将t替换为u\nbash 1 ss -lun 显示时间信息 可以使用 -o 选项，来获得每个连接的时间信息。通过timer得知\nbash 1 2 3 4 5 6 7 8 9 10 11 ss -tn -o State Recv-Q Send-Q Local Address:Port Peer Address:Port ESTAB 0 0 172.16.0.2:22 61.50.248.5:22005 timer:(keepalive,40min,0) ESTAB 0 0 172.16.0.2:42930 169.254.0.55:5574 ESTAB 0 0 172.16.0.2:22 61.50.248.5:22008 timer:(keepalive,64min,0) ESTAB 0 0 172.16.0.2:44900 169.254.0.55:80 timer:(keepalive,13sec,0) ESTAB 0 0 172.16.0.2:22 61.50.248.5:22003 timer:(keepalive,40min,0) ESTAB 0 36 172.16.0.2:22 61.50.248.5:22012 timer:(on,347ms,0) ESTAB 0 0 172.16.0.2:39316 94.130.12.30:443 timer:(keepalive,50sec,0) ESTAB 0 0 172.16.0.2:22 61.50.248.5:22004 timer:(keepalive,40min,0) netstat netstat (network statistics) 命令行工具，用于监视传入和传出的网络连接，以及查看路由表、接口统计等。netstat在所有类似Unix的操作系统上都可用，在Windows操作系统上也可用，是最基本的网络服务调试工具。\n不过，现在netstat 命令早已被弃用，取而代之的是 iproute 套件中的 ss。ss 比起 netstat，ss 能够显示有关网络连接的详细信息，并且速度更快。netstat 从 /proc 文件收集信息，当有大量连接要打印时，netstat 效率很低。而ss 是直接从内核空间获取信息。并且ss命令在使用起来与netstat 非常相似，用户几乎可以无缝切换。\n各系统下的安装\nss Ubuntu/Debian: iproute2 ；apt install iproute2 CentOS/Fedora: iproute ；yum install -y iproute Apline：iproute ；apk add --no-cache iproute netstat Ubuntu/Debian: net-tools ；apt install net-tools CentOS/Fedora: net-tools ；yum install -y net-tools Apline：net-tools ；apk add --no-cache net-tools 列出所有tcp与udp的连接的所有端口 netstat -a 仅列出tcp (Transmission Control Protocol) 端口的连接 netstat -at 仅列出udp (User Datagram Protocol ) 端口的连接 netstat -au 列出所有活动监听端口连接 netstat -l 列出TCP监听端口 netstat -lt 列出udp监听端口 netstat -lu 列出unix socket 监听端口 netstat -lx 显示统计信息 netstat -s 显示tcp的统计信息 netstat -st 显示udp的统计信息 netstat -su 显示服务名与PID号 netstat -tp 显示混杂模式，类似watch 每5s 刷新 netstat -ac 5 | grep tcp 显示内核路由表，类似 route -n 命令；netstat -r 显示网络接口数据包事务，包括传输和接收MTU大小的数据包。 netstat -i 显示内核接口表，类似 ifconfig 命令。 netstat -ie 显示IPv4和IPv6的广播信息。netstat -g 混杂模式，间隔时间打印netstat命令的信息 netstat -c [second] -ltnp 显示原始网络信息统计 netstat --statistics --raw lsof lsof (LiSt Open Files)，主要用来找出哪个进程打开了哪些文件。众所周知，Linux是一个基于文件的操作系统（管道、套接字、目录、设备等）。使用lsof也可以排查一些网络问题。如未关闭的文件不能被移动或删除，网络端口使用的文件等，都可以通过lsof快速定位。\n各系统下的安装\nUbuntu/Debian: lsof ；apt install lsof CentOS/Fedora: lsof ；yum install -y lsof Apline：lsof ；apk add lsof --no-cache 列出所有打开的文件 不带任何参数的情况下运行lsof，可以列出所有打开的文件\nbash 1 lsof 列出用户进程使用的文件 lsof 可以查看特定用户进程使用的哪些文件，使用-u\nbash 1 lsof -u root 根据网络地址查找文件 bash 1 lsof -i 4 按照程序名称列出所打卡的文件 这里不必使用完整的程序名，会列出所有以 name开头的进程应用使用的文件\nbash 1 lsof -u nginx 列出进程使用的文件 使用 -p [pid] k可以显示进程打开的文件，可以通过 ^ 来排除特定的PID。\nbash 1 2 lsof -p [pid] lsof -p [^pid] 找到使用文件的进程 使用 -t 餐食可以找到哪些进程使用了该文件\nbash 1 lsof -t [file_name] 列出目录中所有打开的文件 +D 餐食可以对目录的所有打开实例（包括它包含的所有文件和目录）进行搜索。\nbash 1 lsof +D [dir] 列出网络文件 -i 侦听特定端口号的进程或应用程序，如检查了哪个程序进程正在使用端口80。\nbash 1 lsof -i:80 还可以根据端口范围进程查找\nbash 1 lsof -i:1-1024 根据网络连接类型来查找文件 lsof还可以根据连接的类型列出文件。例如，TCP使用的文件\nbash 1 lsof -i tcp 拿到进程的父进程ID lsof -R 可以拿到进程的父进程IP输出中列出父进程标识（PPID Parent Process IDentification）。\nbash 1 lsof -p [] -R 查看用户的网络连接 结合使用 -i 和 -u 命令行选项，我们可以搜索Linux用户的所有网络连接。可以按照需要检查一个被黑客攻击的系统，如我们检查用户root的所有网络活动：\nbash 1 lsof -a -i -u root 列出所有内存映射文件 bash 1 lsof -d mem route 在Linux中，route命令用于处理IP/内核路由表。主要用于通过网络接口建立到主机/IP的静态路由。它用于显示或更新IP/内核路由表。\n各系统下的安装\nUbuntu/Debian: net-tools ；apt install net-tools CentOS/Fedora: net-tools ；yum install -y net-tools Apline：net-tools ；apk add net-tools --no-cache route命令不加任何参数，默认情况下将显示内核路由表条目的详细信息。当包在这个路由IP范围内发送时，通过ARP协议找到目的地的MAC地址，包将被发送到MAC地址。\n当在路由条目中找不到对应的路由信息，数据包将被转发到默认网关，该网关决定该数据包的进一步路由。\nroute命令不加参数，会在输出时显示为主机名，这时解析会影响性能。可以使用 -n 选项请求不显示主机名。\nbash 1 route -n 添加默认网关 可以使用 route add 命令添加一个默认网关。\nbash 1 route add default gw 10.0.0.1 添加一条路由 这里添加一条，将通过10.0.0.0/24的流量由eth0设备通过 添加一条路由，如下所示。\nbash 1 route add -net 10.0.0.0 netmask 255.255.255.0 dev eth0 -net 目标网络\ndev 将规则和设备关联在一起\n添加一个目标主机\nbash 1 route add -host 12.123.0.10 gw 192.168.1.1 enp0s3 列出内核路由表信息 内核维护了路由缓存以更快地路由数据包。可以使用 -C 来打印内核的路由缓存信息。\nbash 1 2 3 4 5 6 7 route -Cn Kernel IP routing cache Source Destination Gateway Flags Metric Ref Use Iface 10.0.0.4 10.0.0.1 10.0.0.1 0 1 0 eth0 10.0.0.1 10.0.0.4 10.0.0.4 il 0 0 44 lo 10.0.0.1 10.0.0.255 10.0.0.255 ibl 0 0 7 lo 拒绝路由到特定的主机 有些场景下，可能需要拒绝数据包路由到特定的主机/网络。\nbash 1 route add -host 192.168.1.51 reject 可以看到路由已经不会路由该流量了\nbash 1 2 ping 10.0.0.2 connect: No route to host 如果需要拒绝整个网络可以这样\nbash 1 route add -net 192.168.1.0 netmask 255.255.255.0 reject 删除一条路由 text 1 2 3 4 # 删除默认路由 route del default # 删除刚才添加的拒绝路由 route del -host 10.0.0.2 reject ncat \u0026amp; netcat(nc) \u0026amp; nmap netcat（简称nc）是一款功能强大的网络命令行工具，用于在Linux中执行与TCP、UDP或UNIX域套接字相关的任何操作。netcat可以用于端口扫描、端口重定向，作为端口监听器（用于传入连接）；它还可以用来打开远程连接和其他许多事情。此外，还可以将其用作访问目标服务器的后门。netcat还因此被称为TCP/IP的“瑞士军刀”。\n各系统下的安装\nUbuntu/Debian: netcat ；apt install netcat CentOS/Fedora: nc ；yum install -y nc Apline：netcat-openbsd ；apk add --no-cache netcat-openbsd 端口扫描 netcat可以用于端口扫描：了解哪些端口是开放的，并且在目标机器上运行服务。它可以扫描单一或多个开防的端口。如示例，-z 选项将nc设置为只扫描监听守护进程，而不实际向它们发送任何数据。-v 选项启用详细模式，-w 为无法建立连接时超时时间。\nbash 1 2 3 4 5 nc -v -w 10 -z 195.133.11.43 22 Ncat: Version 7.50 ( https://nmap.org/ncat ) Ncat: Connected to 195.133.11.43:22. Ncat: 0 bytes sent, 0 bytes received in 0.25 seconds. 也可以扫描一个范围\nbash 1 nc -v -n -z -w 1 127.0.0.1 1-1000 在服务器间传送文件 netcat可以在两台服务器之间传输文件，这两个系统都必须安装nc。例如，要将ISO映像文件从一台计算机复制到另一台计算机并监视传输进度（使用pv），请在发送方/接收端上运行以下命令。\n将以netcat 的监听模式 -l。\nbash 1 tar -zcf - debian-10.0.0-amd64-xfce-CD-1.iso | pv | nc -l -p 3000 -q 5 在接受端运行命令\nbash 1 nc 192.168.1.4 3000 | pv | tar -zxf - 使用netcat实现一个命令行聊天服务器 可以使 netcat 创建一个简单的命令行消息服务器，前提条件是nc必须安装在两个系统上。在服务端，运行命令来创建监听端口5555的聊天服务器。\nbash 1 nc -l -vv -p 5000 在客户端上，运行命令连接到服务端进行聊天会话。\nbash 1 nc {ip} 5000 使用nc创建一个web服务器 使用nc -l 选项可以创建一个基础的不安全的web服务器，需要一个静态html文件。然后可以通过 while 保持netcat命令不退出。正常情况下，netcat在连接断开时退出。\nbash 1 while : ; do ( echo -ne \u0026#34;HTTP/1.1 200 OK\\r\\n\u0026#34; ; cat 1.html; ) | nc -l -p 8080 ; done text 1 2 3 4 5 6 7 8 9 $ while : ; do ( echo -ne \u0026#34;HTTP/1.1 200 OK\\r\\n\u0026#34; ; cat 1.html; ) | nc -l -p 8080 ; done GET / HTTP/1.1 Host: ip:8080 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6 网络故障排查 netcat 主要常用的一个方面时排查网络连接故障，可以使用 netcat 来验证服务器正在发送哪些数据以响应客户端发出的命令。\n使用命令的可以输出包括web服务器发送的标头，这些标头可用于故障排除。也可以使用 curl 等命令进行同样的操作。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 printf \u0026#34;GET / HTTP/1.0\\r\\n\\r\\n\u0026#34; | nc baidu.com 80 HTTP/1.1 200 OK Date: Mon, 28 Jun 2021 12:16:55 GMT Server: Apache Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT ETag: \u0026#34;51-47cf7e6ee8400\u0026#34; Accept-Ranges: bytes Content-Length: 81 Cache-Control: max-age=86400 Expires: Tue, 29 Jun 2021 12:16:55 GMT Connection: Close Content-Type: text/html \u0026lt;html\u0026gt; \u0026lt;meta http-equiv=\u0026#34;refresh\u0026#34; content=\u0026#34;0;url=http://www.baidu.com/\u0026#34;\u0026gt; \u0026lt;/html\u0026gt; 查找端口上运行的服务 使用 netcat 可以获取服务监听端口的信息，单一般情况下，仅常见公共服务会这样，一些服务并不会相应对应的应用名称。。-n 标志表示禁用DNS或服务查找。\nbash 1 2 3 4 nc -v -n 195.133.11.43 22 Ncat: Version 7.50 ( https://nmap.org/ncat ) Ncat: Connected to 195.133.11.43:22. SSH-2.0-OpenSSH_7.4 网络后门 一般情况下，黑客将 netcat 当作网络后门来运行，通过反弹式shell以获取远程命令。要充当后门。-e 在目标系统上运行的命令。\n如监听一个端口，并将所有传入的输入传递给bash命令，结果将传送于客户端。\ntext 1 2 3 4 5 # linux nc -l -p -v 3001 -e /bin/bash # windows nc -l -p 3001 -e cmd.exe 检查一个udp端口 -z：无法进行I/O ，仅报告连接状态\n-u：使用udp协议\nbash 1 nc -vz -u tcpdump [1] tcpdump网络嗅探器，将强大和简单结合到一个单一的命令行界面中，能够将网络中的报文抓取，输出到屏幕或者记录到文件中。\n各系统下的安装\nUbuntu/Debian: tcpdump ；apt-get install -y tcpdump CentOS/Fedora: tcpdump ；yum install -y tcpdump Apline：tcpdump ；apk add tcpdump --no-cache 查看指定接口上的所有通讯\n语法\n参数 说明 -i [interface] 接口名 -p --no-promiscuous-mode 抓包内容为非混杂模式下的包 -w [flle] 保存原始的包到文件中 -n 不转换 IP 为 DNS 名称 -N 将端口解析为数字格式而不是服务名 -A 以 ASCII 格式打印内容（不包含标头） -XX 打印数据为==数据包的标头==与以十六进制和 ASCII 格式打印数据包的数据。 -v/-vv/-vvv 详细信息；-v, -vv 将打印更多信息 -r 读取文件而不是实时抓包 -e 增加连接级标头每个输出行前，例如，打印 MAC地址等 关键字\n参数 关键字 type host, net, port, portrange direction src, dst, src or dst , src and ds protocol ether, ip, arp, tcp, udp, wlan 捕获所有网络接口 bash 1 tcpdump -D 按IP查找流量 最常见的查询之一 host，可以看到来往于 1.1.1.1 的流量。\nbash 1 tcpdump host 1.1.1.1 按源/目的 地址过滤 如果只想查看来自/向某方向流量，可以使用 src 和 dst。\nbash 1 tcpdump src|dst 1.1.1.1 通过网络查找数据包 使用 net 选项，来要查找出/入某个网络或子网的数据包。\nbash 1 tcpdump net 1.2.3.0/24 使用十六进制输出数据包内容 hex 可以以16进制输出包的内容\nbash 1 tcpdump -c 1 -X icmp 查看特定端口的流量 使用 port 选项来查找特定的端口流量。\nbash 1 2 tcpdump port 3389 tcpdump src port 1025 查找端口范围的流量 bash 1 tcpdump portrange 21-23 过滤包的大小 如果需要查找特定大小的数据包，可以使用以下选项。你可以使用 less，greater。\nbash 1 2 3 tcpdump less 32 tcpdump greater 64 tcpdump \u0026lt;= 128 捕获流量输出为文件 -w 可以将数据包捕获保存到一个文件中以便将来进行分析。这些文件称为PCAP（PEE-cap）文件，它们可以由不同的工具处理，包括 Wireshark 。\nbash 1 tcpdump port 80 -w capture_file 组合条件 tcpdump也可以结合逻辑运算符进行组合条件查询\nAND and or \u0026amp;\u0026amp;\nOR or or ||\nEXCEPT not or !\nbash 1 2 3 4 tcpdump -i eth0 -nn host 220.181.57.216 and 10.0.0.1 # 主机之间的通讯 tcpdump -i eth0 -nn host 220.181.57.216 or 10.0.0.1 # 获取10.0.0.1与 10.0.0.9或 10.0.0.1 与10.0.0.3之间的通讯 tcpdump -i eth0 -nn host 10.0.0.1 and \\(10.0.0.9 or 10.0.0.3\\) 原始输出 并显示人类可读的内容进行输出包（不包含内容）。\ntcpdump tcpdump -ttnnvvS -i eth0 IP到端口 让我们查找从某个IP到端口任何主机的某个端口所有流量。\nbash 1 tcpdump -nnvvS src 10.5.2.3 and dst port 3389 去除特定流量 可以将指定的流量排除，如这显示所有到192.168.0.2的 非ICMP的流量。\nbash 1 tcpdump dst 192.168.0.2 and src net and not icmp 来自非指定端口的流量，如，显示来自不是SSH流量的主机的所有流量。\nbash 1 tcpdump -vv src mars and not dst port 22 选项分组 在构建复杂查询时，必须使用单引号 '。单引号用于忽略特殊符号 () ，以便于使用其他表达式（如host, port, net等）进行分组。\n### tcpdump \u0026#39;src 10.0.2.4 and (dst port 3389 or 22)\u0026#39; 过滤TCP标记位 TCP RST\nThe filters below find these various packets because tcp[13] looks at offset 13 in the TCP header, the number represents the location within the byte, and the !=0 means that the flag in question is set to 1, i.e. it’s on.\nbash 1 2 tcpdump \u0026#39;tcp[13] \u0026amp; 4!=0\u0026#39; tcpdump \u0026#39;tcp[tcpflags] == tcp-rst\u0026#39; TCP SYN\nbash 1 2 tcpdump \u0026#39;tcp[13] \u0026amp; 2!=0\u0026#39; tcpdump \u0026#39;tcp[tcpflags] == tcp-syn\u0026#39; 同时忽略SYN和ACK标志的数据包\nbash 1 tcpdump \u0026#39;tcp[13]=18\u0026#39; TCP URG\nbash 1 2 tcpdump \u0026#39;tcp[13] \u0026amp; 32!=0\u0026#39; tcpdump \u0026#39;tcp[tcpflags] == tcp-urg\u0026#39; TCP ACK\nbash 1 2 tcpdump \u0026#39;tcp[13] \u0026amp; 16!=0\u0026#39; tcpdump \u0026#39;tcp[tcpflags] == tcp-ack\u0026#39; TCP PSH\nbash 1 2 tcpdump \u0026#39;tcp[13] \u0026amp; 8!=0\u0026#39; tcpdump \u0026#39;tcp[tcpflags] == tcp-push\u0026#39; TCP FIN\nbash 1 2 tcpdump \u0026#39;tcp[13] \u0026amp; 1!=0\u0026#39; tcpdump \u0026#39;tcp[tcpflags] == tcp-fin\u0026#39; 查找http包 查找 user-agent 信息\nbash 1 tcpdump -vvAls0 | grep \u0026#39;User-Agent:\u0026#39; 查找只是 GET 请求的流量\nbash 1 tcpdump -vvAls0 | grep \u0026#39;GET\u0026#39; 查找http客户端IP\nbash 1 tcpdump -vvAls0 | grep \u0026#39;Host:\u0026#39; 查询客户端cookie\nbash 1 tcpdump -vvAls0 | grep \u0026#39;Set-Cookie|Host:|Cookie:\u0026#39; 查找DNS流量 bash 1 tcpdump -vvAs0 port 53 查找对应流量的明文密码 bash 1 tcpdump port http or port ftp or port smtp or port imap or port pop3 or port telnet -lA | egrep -i -B5 \u0026#39;pass=|pwd=|log=|login=|user=|username=|pw=|passw=|passwd= |password=|pass:|user:|username:|password:|login:|pass |user \u0026#39; arp ARP “地址解析协议” (Address Resolution Protoco)，是一种将IP地址映射到局域网上物理MAC地址的协议。\n为什么我们需要MAC地址？\n任何本地通信都将使用MAC地址，而不是IP地址。当一台计算机想在不同的网络上与另一台计算机通信时，将使用IP地址。IP地址就像你的邮寄收货地址，而MAC地址就像你的名字。在TCP/IP网络上，每台计算机都被分配IP地址，一些本地服务器的IP地址也被分配给网络客户主机。因此其在第2层（数据链路层）和第3层（网络层）之间工作。\n在一个本地网络中，客户主机尝试在连接另外一个主机时（这里为同一网络，即同一广播域中），首先客户端会检查ARP缓存表（缓存IP于MAC的关系）。而 arp 命令可以管理系统的arp缓存表。它允许完全转储ARP缓存。\n各系统下的安装\nUbuntu/Debian: net-tools ；apt install net-tools CentOS/Fedora: net-tools ；yum install -y net-tools Apline：net-tools ；apk add --no-cache net-tools 查看arp条目 arp 命令在没有任何选项的情况下将显示arp缓存表的内容。\nbash 1 2 3 4 5 arp Address HWtype HWaddress Flags Mask Iface correspond.fsddsfk.cn ether c4:71:fe:f1:9f:3f C eth0 gateway ether 00:1f:ce:72:bd:8c C eth0 显示一个特定的arp条目 当arp缓存表很大不利于查看，并且仅需要拿到特定IP的条目的话，可以在 -a 后加具体IP地址来获取一个特定的条目。\nbash 1 2 arp -a 46.17.40.155 correspond.faaaaa.cn (10.17.40.1) at c4:71:fe:f1:9f:3f [ether] on eth0 显示指定接口的arp缓存表 如果仅希望显示一个接口的arp条目，可以通过 arp 命令 -i选项后跟接口名称。\nbash 1 2 3 4 5 arp -i bond0 Address HWtype HWaddress Flags Mask Iface usartdb02.exmpl.c ether 17:a9:9b:f5:1a:7e C bond0 usartdb02.exmpl.c ether f8:db:77:f2:5a:a2 C bond0 删除一个arp条目 从arp缓存表中删除一个ip条目，可以使用 arp 命令-d选项，后跟IP地址。当一旦执行arp命令，ARP缓存表就会被刷新。\nbash 1 arp -d 192.168.188.2 向arp缓存添加一个条目 永久添加一个条目到arp缓存中，使用 -s 选项，需要指定IP地址和MAC地址外，还需要指定将条目添加到哪个接口。如：\nbash 1 arp -s 192.168.188.133 -i eth0 00:0c:29:f6:1d:81 清空arp缓存表 某些场景下，需要清空arp缓存，而 arp 命令并没有清空缓存表的操作，这时可以使用 ip neigh 而且ip-route2 最小化安装；基础容器等场景下也会存在。\nbash 1 ip neigh flush dev eth0s 显示格式 使用 -e 选项以linux标准格式输出arp缓存表\n其输出格式中，ARP缓存表中的每个完整条目都将被标记为 C Complete entry。M （Permanent entry）表示永久条目；P （Published entry.）表示已发布条目标记。\nbash 1 2 3 4 5 6 7 8 9 arp -ae Address HWtype HWaddress Flags Mask Iface correspond.fsddsfk.cn ether c4:71:fe:fe:9f:2f C eth0 arp -aen Address HWtype HWaddress Flags Mask Iface 36.17.40.111 ether c4:71:fe:fe:9f:2f C eth0 arp-scan 网络扫描是渗透测试的步骤之一。有不同的和流行的工具来扫描网络线masscan，nmap等。Arp扫描。\narp-scan 是专门设计用来扫描二层（网络层）的mac, arp数据包的工具（也可称为ARP Sweep 或 MAC Scanner ）；是一个非常快速的ARP包扫描程序，它可以显示子网中所有活动的IPv4设备。由于ARP是不可路由的，这种类型的扫描仪只能在本地LAN（本地子网或网段）上工作。\narp-scan 显示所有活动设备，即使它们有防火墙。设备不能像躲避Ping一样躲避ARP包。\n各系统下的安装\nUbuntu/Debian: arp-scan ；apt install arp-scan CentOS/Fedora: arp-scan (epel) ；yum install -y arp-scan Apline：arp-scan ；apk add --no-cache arp-scan 扫描本地网络 arp-scan 的最基本使用方法是扫描本地网络，使用-l 或 --localnet 可以扫描整个本地网络，但需要root权限\nbash 1 2 3 4 5 6 7 8 9 arp-scan --localnet Interface: eth0, type: EN10MB, MAC: da:78:c8:7a:fb:26, IPv4: 195.133.11.43 Starting arp-scan 1.9.7 with 512 hosts (https://github.com/royhills/arp-scan) 195.133.10.1 00:1f:ce:72:bd:8c QTECH LLC 195.133.10.2 56:85:8e:2b:cf:11 (Unknown: locally administered) 195.133.10.5 de:58:c6:5b:b5:c2 (Unknown: locally administered) 195.133.10.7 de:ed:ae:4b:7a:c8 (Unknown: locally administered) 195.133.10.6 d2:a6:f4:4c:f0:4b (Unknown: locally administered) 设置源mac 在apr扫描的过程中，会使用现有的mac地址进行请求，这样会留下网络痕迹，arp-scan 提供了修改源mac的功能。使用 --destaddr 或 -T 。\n###指定特殊vlan\n在网络设备中，一个接口可以实现多个网络，这使用了虚拟局域网VLAN（Virtual Local Area Network）的多路复用协议。如果接口是 trunk ，意味接口承载多个VLAN，我们可能需要指定VLAN id，可以使用 --vlan 或 -Q指定vlan id\nbash 1 arp-scan -i eth0 -Q 10 发现网络冲突 arp-scan 也可用于发现IP冲突与识别设备等操，只需一个命令 arp scan -l。可以通过 -i 指定端口。\n这里 192.168.1.39 冲突，因为出现了两次并且指定了不同的mac地址。\nbash 1 2 3 4 5 6 7 8 9 10 arp-scan –I eth0 -l 192.168.1.10 00:1b:a9:63:a2:4c BROTHER INDUSTRIES, LTD. 192.168.1.30 00:1e:8f:58:ec:49 CANON INC. 192.168.1.33 00:25:4b:1b:10:20 Apple, Inc 192.168.1.37 10:9a:dd:55:d7:95 Apple Inc 192.168.1.38 20:c9:d0:27:8d:56 (Unknown) 192.168.1.39 d4:85:64:4d:35:be Hewlett Packard 192.168.1.39 00:0b:46:e4:8e:6d Cisco (DUP: 2) 192.168.1.40 90:2b:34:18:59:c0 (Unknown) ethtool ethtool 命令用于 显示, 配置以太网设备。可以在Linux中使用此工具更改网卡速度, 自动协商, LAN唤醒设置, 双工模式。\n各系统下的安装\nUbuntu/Debian: ethtool ；apt install ethtool CentOS/Fedora: ethtool (epel) ；yum install -y ethtool Apline：ethtool ；apk add --no-cache ethtool 列出以太网设备属性 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ethtool eth0 Settings for eth0: Supported ports: [ ] Supported link modes: Not reported Supported pause frame use: No Supports auto-negotiation: No Supported FEC modes: Not reported Advertised link modes: Not reported Advertised pause frame use: No Advertised auto-negotiation: No Advertised FEC modes: Not reported Speed: Unknown! Duplex: Unknown! (255) Port: Other PHYAD: 0 Transceiver: internal Auto-negotiation: off Link detected: yes 查看网络接口于容器内接口的对应关系 可以通过ethtool查看容器的网卡对，通过-S 加接口设备名，-S 为统计信息\n在宿主机上 使用命令查看，其中peer_ifindex: 767 对应容器内 ip link 的编号\nbash 1 ethtool -S veth45562ed 容器内\ntext 1 2 3 4 5 $ ip link 1: lo: \u0026lt;LOOPBACK,UP,LOWER_UP\u0026gt; mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 767: eth0: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc noqueue state UP mode DEFAULT group default link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff nsenter nsenter是一款可以进入进程的名称空间中。例如，如果一个容器以非 root 用户身份运行，而使用 docker exec 进入其中后，但该容器没有安装 sudo 或未 netstat ，并且您想查看其当前的网络属性，如开放端口，这种场景下将如何做到这一点？nsenter 就是用来解决这个问题的。\nnsenter (namespace enter) 可以在容器的宿主机上使用 nsenter 命令进入容器的命名空间，以容器视角使用宿主机上的相应网络命令进行操作。==当然需要拥有 root 权限==\nNote: 通常情况下最小系统已经安装好了，包含容器，所以不需要特殊安装。\n各系统下的安装 [4]\nUbuntu/Debian: util-linux ；apt-get install -y util-linux CentOS/Fedora: util-linux ；yum install -y util-linux Apline：util-linux ；apk add util-linux --no-cache nsenter 的使用语法为，nsenter -t pid -n \u0026lt;commond\u0026gt;，-t 接 进程ID号，-n 表示进入名称空间内，\u0026lt;commond\u0026gt; 为执行的命令。更多的内容可以参考 [3]\n实例：如我们有一个Pod进程ID为30858，进入该Pod名称空间内执行 ifconfig ，如下列所示\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $ ps -ef|grep tail root 17636 62887 0 20:19 pts/2 00:00:00 grep --color=auto tail root 30858 30838 0 15:55 ? 00:00:01 tail -f $ nsenter -t 30858 -n ifconfig eth0: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1480 inet 192.168.1.213 netmask 255.255.255.0 broadcast 192.168.1.255 ether 5e:d5:98:af:dc:6b txqueuelen 0 (Ethernet) RX packets 92 bytes 9100 (8.8 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 92 bytes 8422 (8.2 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo: flags=73\u0026lt;UP,LOOPBACK,RUNNING\u0026gt; mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 loop txqueuelen 1000 (Local Loopback) RX packets 5 bytes 448 (448.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 5 bytes 448 (448.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 net1: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 10.1.0.201 netmask 255.255.255.0 broadcast 10.1.0.255 ether b2:79:f9:dd:2a:10 txqueuelen 0 (Ethernet) RX packets 228 bytes 21272 (20.7 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 216 bytes 20272 (19.7 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 如何定位Pod名称空间 首先需要确定Pod所在的节点名称\nbash 1 2 3 4 $ kubectl get pods -owide |awk \u0026#39;{print $1,$7}\u0026#39; NAME NODE netbox-85865d5556-hfg6v master-machine netbox-85865d5556-vlgr4 node01 如果Pod不在当前节点还需要用IP登录则还需要查看IP（可选）\nbash 1 2 3 4 $ kubectl get pods -owide |awk \u0026#39;{print $1,$6,$7}\u0026#39; NAME IP NODE netbox-85865d5556-hfg6v 192.168.1.213 master-machine netbox-85865d5556-vlgr4 192.168.0.4 node01 接下来，登录节点，获取容器lD，如下列所示，每个pod默认有一个 pause 容器，其他为用户yaml文件中定义的容器，理论上所有容器共享相同的网络命名空间，排查时可任选一个容器。\nbash 1 2 3 $ docker ps |grep netbox-85865d5556-hfg6v 6f8c58377aae f78dd05f11ff \u0026#34;tail -f\u0026#34; 45 hours ago Up 45 hours k8s_netbox_netbox-85865d5556-hfg6v_default_4a8e2da8-05d1-4c81-97a7-3d76343a323a_0 b9c732ee457e registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1 \u0026#34;/pause\u0026#34; 45 hours ago Up 45 hours k8s_POD_netbox-85865d5556-hfg6v_default_4a8e2da8-05d1-4c81-97a7-3d76343a323a_0 接下来获得获取容器在节点系统中对应的进程号，如下所示\nbash 1 2 $ docker inspect --format \u0026#34;{{ .State.Pid }}\u0026#34; 6f8c58377aae 30858 最后就可以通过 nsenter 进入容器网络空间执行命令了\npaping paping 命令可对目标地址指定端口以TCP协议进行连续ping，通过这种特性可以弥补 ping ICMP协议，以及 nmap , telnet 只能进行一次操作的的不足；通常情况下会用于测试端口连通性和丢包率\npaping download：paping\npaping 还需要安装以下依赖，这取决于你安装的 paping 版本\nRedHat/CentOS：yum install -y libstdc++.i686 glibc.i686 Ubuntu/Debian：最小化安装无需依赖 bash 1 2 3 4 5 6 7 8 9 10 11 $ paping -h paping v1.5.5 - Copyright (c) 2011 Mike Lovell Syntax: paping [options] destination Options: -?, --help display usage -p, --port N set TCP port N (required) --nocolor Disable color output -t, --timeout timeout in milliseconds (default 1000) -c, --count N set number of checks to N mtr mtr 是一个跨平台的网络诊断工具，将 traceroute 和 ping 的功能结合到一个工具。与 traceroute 不同的是 mtr 显示的信息比起 traceroute 更加丰富：通过 mtr 可以确定网络的条数，并且可以同时打印响应百分比以及网络中各跳跃点的响应时间。\n各系统下的安装\nUbuntu/Debian: mtr ；apt-get install -y mtr CentOS/Fedora: mtr ；yum install -y mtr Apline：mtr ；apk add mtr --no-cache 简单的使用示例 最简单的示例，就是后接域名或IP，这将跟踪整个路由\nbash 1 2 3 4 5 6 7 8 9 10 11 $ mtr google.com Start: Thu Jun 28 12:10:13 2018 HOST: TecMint Loss% Snt Last Avg Best Wrst StDev 1.|-- 192.168.0.1 0.0% 5 0.3 0.3 0.3 0.4 0.0 2.|-- 5.5.5.211 0.0% 5 0.7 0.9 0.7 1.3 0.0 3.|-- 209.snat-111-91-120.hns.n 80.0% 5 7.1 7.1 7.1 7.1 0.0 4.|-- 72.14.194.226 0.0% 5 1.9 2.9 1.9 4.4 1.1 5.|-- 108.170.248.161 0.0% 5 2.9 3.5 2.0 4.3 0.7 6.|-- 216.239.62.237 0.0% 5 3.0 6.2 2.9 18.3 6.7 7.|-- bom05s12-in-f14.1e100.net 0.0% 5 2.1 2.4 2.0 3.8 0.5 -n 强制 mtr 打印 IP地址而不是主机名\nbash 1 2 3 4 5 6 7 8 9 10 11 $ mtr -n google.com Start: Thu Jun 28 12:12:58 2018 HOST: TecMint Loss% Snt Last Avg Best Wrst StDev 1.|-- 192.168.0.1 0.0% 5 0.3 0.3 0.3 0.4 0.0 2.|-- 5.5.5.211 0.0% 5 0.9 0.9 0.8 1.1 0.0 3.|-- ??? 100.0 5 0.0 0.0 0.0 0.0 0.0 4.|-- 72.14.194.226 0.0% 5 2.0 2.0 1.9 2.0 0.0 5.|-- 108.170.248.161 0.0% 5 2.3 2.3 2.2 2.4 0.0 6.|-- 216.239.62.237 0.0% 5 3.0 3.2 3.0 3.3 0.0 7.|-- 172.217.160.174 0.0% 5 3.7 3.6 2.0 5.3 1.4 -b 同时显示IP地址与主机名\nbash 1 2 3 4 5 6 7 8 9 10 11 $ mtr -b google.com Start: Thu Jun 28 12:14:36 2018 HOST: TecMint Loss% Snt Last Avg Best Wrst StDev 1.|-- 192.168.0.1 0.0% 5 0.3 0.3 0.3 0.4 0.0 2.|-- 5.5.5.211 0.0% 5 0.7 0.8 0.6 1.0 0.0 3.|-- 209.snat-111-91-120.hns.n 0.0% 5 1.4 1.6 1.3 2.1 0.0 4.|-- 72.14.194.226 0.0% 5 1.8 2.1 1.8 2.6 0.0 5.|-- 108.170.248.209 0.0% 5 2.0 1.9 1.8 2.0 0.0 6.|-- 216.239.56.115 0.0% 5 2.4 2.7 2.4 2.9 0.0 7.|-- bom07s15-in-f14.1e100.net 0.0% 5 3.7 2.2 1.7 3.7 0.9 -c 跟一个具体的值，这将限制 mtr ping的次数，到达次数后会退出\nbash 1 $ mtr -c5 google.com 如果需要指定次数，并且在退出后保存这些数据，使用 -r flag\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ mtr -r -c 5 google.com \u0026gt; 1 $ cat 1 Start: Sun Aug 21 22:06:49 2022 HOST: xxxxx.xxxxx.xxxx.xxxx Loss% Snt Last Avg Best Wrst StDev 1.|-- gateway 0.0% 5 0.6 146.8 0.6 420.2 191.4 2.|-- 212.xx.21.241 0.0% 5 0.4 1.0 0.4 2.3 0.5 3.|-- 188.xxx.106.124 0.0% 5 0.7 1.1 0.7 2.1 0.5 4.|-- ??? 100.0 5 0.0 0.0 0.0 0.0 0.0 5.|-- 72.14.209.89 0.0% 5 43.2 43.3 43.1 43.3 0.0 6.|-- 108.xxx.250.33 0.0% 5 43.2 43.1 43.1 43.2 0.0 7.|-- 108.xxx.250.34 0.0% 5 43.7 43.6 43.5 43.7 0.0 8.|-- 142.xxx.238.82 0.0% 5 60.6 60.9 60.6 61.2 0.0 9.|-- 142.xxx.238.64 0.0% 5 59.7 67.5 59.3 89.8 13.2 10.|-- 142.xxx.37.81 0.0% 5 62.7 62.9 62.6 63.5 0.0 11.|-- 142.xxx.229.85 0.0% 5 61.0 60.9 60.7 61.3 0.0 12.|-- xx-in-f14.1e100.net 0.0% 5 59.0 58.9 58.9 59.0 0.0 默认使用的是 ICMP 协议 -i ，可以指定 -u, -t 使用其他协议\nbash 1 mtr --tcp google.com -m 指定最大的跳数\nbash 1 mtr -m 35 216.58.223.78 -s 指定包的大小\nmtr输出的数据 colum describe last 最近一次的探测延迟值 avg 探测延迟的平均值 best 探测延迟的最小值 wrst 探测延迟的最大值 stdev 标准偏差。越大说明相应节点越不稳定 丢包判断 任一节点的 Loss%（丢包率）如果不为零，则说明这一跳网络可能存在问题。导致相应节点丢包的原因通常有两种。\n运营商基于安全或性能需求，人为限制了节点的ICMP发送速率，导致丢包。 节点确实存在异常，导致丢包。可以结合异常节点及其后续节点的丢包情况，来判定丢包原因。 Notes:\n如果随后节点均没有丢包，则通常说明异常节点丢包是由于运营商策略限制所致。可以忽略相关丢包。 如果随后节点也出现丢包，则通常说明节点确实存在网络异常，导致丢包。对于这种情况，如果异常节点及其后续节点连续出现丢包，而且各节点的丢包率不同，则通常以最后几跳的丢包率为准。如链路测试在第5, 6, 7跳均出现了丢包。最终丢包情况以第7跳作为参考。 延迟判断 由于链路抖动或其它因素的影响，节点的 Best 和 Worst 值可能相差很大。而 Avg（平均值）统计了自链路测试以来所有探测的平均值，所以能更好的反应出相应节点的网络质量。而 StDev（标准偏差值）越高，则说明数据包在相应节点的延时值越不相同（越离散）。所以标准偏差值可用于协助判断 Avg 是否真实反应了相应节点的网络质量。例如，如果标准偏差很大，说明数据包的延迟是不确定的。可能某些数据包延迟很小（例如：25ms），而另一些延迟却很大（例如：350ms），但最终得到的平均延迟反而可能是正常的。所以此时 Avg 并不能很好的反应出实际的网络质量情况。\n这就需要结合如下情况进行判断：\n如果 StDev 很高，则同步观察相应节点的 Best 和 wrst，来判断相应节点是否存在异常。 如果StDev 不高，则通过Avg来判断相应节点是否存在异常。 brctl [5] brctl 是用于创建和操作 Linux Bridge 的命令行工具，\n各系统下的安装\nUbuntu/Debian: bridge-utils ；apt-get install -y bridge-utils CentOS/Fedora: bridge-utils ；yum install -y bridge-utils Apline：bridge-utils ；apk add bridge-utils --no-cache 显示所有启用的网桥的设备 使用 brctl show 查看当前节点可用的 bridge\nbash 1 2 3 4 5 $ brctl show bridge name bridge id STP enabled interfaces br0 8000.000d3a8a7868 no eth1 vnet0 virbr0 8000.5254005098ae yes virbr0-nic 创建网桥 使用 brctl addbr \u0026lt;name\u0026gt; 可以创建出一个Linux Bridge\nbash 1 brctl addbr dev 在网桥上添加接口 使用 brctl addif \u0026lt;bridge_name\u0026gt; \u0026lt;interface_name\u0026gt; 可以在一个已经存在的 bridge 上添加一个接口\nbash 1 $ brctl addif br0 eth1 指定多个 \u0026lt;interface_name\u0026gt; 会向网桥上添加多个接口\nbash 1 $ brctl addif br0 enp0s3 enp1s3 删除网桥 使用 brctl delbr \u0026lt;bridge_name\u0026gt; 可以删除一个网桥\nbash 1 $ brctl delbr br0 检查网桥 STF 信息 使用 brctl showstp \u0026lt;bridge_name\u0026gt; 可以查看网桥的STF信息\nbash 1 2 3 4 5 6 7 8 9 10 11 12 $ brctl showstp br0 br0 bridge id 8000.000000000000 designated root 8000.000000000000 root port 0 path cost 0 max age 20.00 bridge max age 20.00 hello time 2.00 bridge hello time 2.00 forward delay 15.00 bridge forward delay 15.00 ageing time 300.00 hello timer 0.00 tcn timer 0.00 topology change timer 0.00 gc timer 0.00 flags 从网桥上删除接口 使用 brctl delif \u0026lt;bridge_name\u0026gt; \u0026lt;interface_name\u0026gt; 可以从网桥上删除接口。\nbash 1 $ brctl delif br0 enp0s3 开启/关闭生成树协议 使用 brctl stp \u0026lt;bridge_name\u0026gt; on/off 可以选择开启或关闭生成树协议\nbash 1 2 $ brctl stp br0 off $ brctl stp br0 on 查看网桥上学到的MAC地址 使用 brctl showmacs \u0026lt;bridge_name\u0026gt; 可以查看网桥上学到的所有设备的MAC地址。\nbash 1 2 3 $ brctl showmacs br0 port no mac addr is local? ageing timer 1 00:74:16:87:14:de yes 0.00 修改STP值 如果需要修改 STP 参数值，可以通过 brctl setageing \u0026lt;bridge_name\u0026gt; \u0026lt;value\u0026gt; 来修改\nbash 1 $ brctl setageing br0 100 bridge [6] bridge 是一款用于展示和操作网桥地址和设备的命令，通常情况下\n各系统下的包名与安装\nUbuntu/Debian: iproute2 ；apt install iproute2 CentOS/Fedora: iproute2 ；yum install -y iproute2 Apline：iproute2 ；apk add iproute2 bridge 命令可以完成三种类型的配置管理：\n端口配置 FDB管理 VLAN 配置 大多数人都会使用 brctl 命令替代，而 bridge 通常只是来完成 FDB (forwarding database management ) 的相关操作。。也可以使用 bridge 命令创建和删除VLAN。\nbridge vs brct [7] BRIDGE MANAGEMENT 动作 BRCTL BRIDGE 创建bridge brctl addbr \u0026lt;bridge\u0026gt; 删除bridge brctl delbr \u0026lt;bridge\u0026gt; 为bridge添加接口 brctl addif \u0026lt;bridge\u0026gt; \u0026lt;ifname\u0026gt; 删除bridge上的接口 brctl delbr \u0026lt;bridge\u0026gt; FDB MANAGEMENT ACTION BRCTL BRIDGE 显示 FDB 中的 MAC 列表 brctl showmacs \u0026lt;bridge\u0026gt; bridge fdb show 设置 FDB 条目老化时间 brctl setageingtime \u0026lt;bridge\u0026gt; \u0026lt;time\u0026gt; 设置 FDB 垃圾回收间隔 brctl setgcint \u0026lt;brname\u0026gt; \u0026lt;time\u0026gt; 添加FDB 条目(add) bridge fdb add dev \u0026lt;interface\u0026gt; [dst, vni, port, via] 追加FDB条目(append) bridge fdb append (parameters same as for fdb add) 删除 FDB 条目 bridge fdb delete (parameters same as for fdb add) STP MANAGEMENT ACTION BRCTL BRIDGE 打开/关闭 STP brctl stp \u0026lt;bridge\u0026gt; \u0026lt;state\u0026gt; 设置网桥优先级 brctl setbridgeprio \u0026lt;bridge\u0026gt; \u0026lt;priority\u0026gt; 设置网桥转发延迟 brctl setfd \u0026lt;bridge\u0026gt; \u0026lt;time\u0026gt; 设置bridge “hello”时间 brctl sethello \u0026lt;bridge\u0026gt; \u0026lt;time\u0026gt; 设置网桥最大消息年龄 brctl setmaxage \u0026lt;bridge\u0026gt; \u0026lt;time\u0026gt; 设置桥上端口开销 brctl setpathcost \u0026lt;bridge\u0026gt; \u0026lt;port\u0026gt; \u0026lt;cost\u0026gt; bridge link set dev \u0026lt;port\u0026gt; cost \u0026lt;cost\u0026gt; 设置网桥端口优先级 brctl setportprio \u0026lt;bridge\u0026gt; \u0026lt;port\u0026gt; \u0026lt;priority\u0026gt; bridge link set dev \u0026lt;port\u0026gt; priority \u0026lt;priority\u0026gt; 是否应端口处理 STP BDPU bridge link set dev \u0026lt;port \u0026gt; guard [on, off] 网桥是否应该在接收到的端口上发送流量 bridge link set dev \u0026lt;port\u0026gt; hairpin [on,off] 在端口上启用/禁用 fastleave 选项 bridge link set dev \u0026lt;port\u0026gt; fastleave [on,off] 设置 STP 端口状态 bridge link set dev \u0026lt;port\u0026gt; state \u0026lt;state\u0026gt; VLAN MANAGEMENT ACTION BRCTL BRIDGE 创建新的 VLAN 过滤器条目 bridge vlan add dev [vid, pvid, untagged, self, master] 删除 VLAN 过滤器条目 bridge vlan delete dev (parameters same as for vlan add) 列出 VLAN 配置 bridge vlan show Reference [1] tcpdump\n[2] linux mtr command\n[3] man nsenter\n[4] How to install nsenter\n[5] examples for ethernet network bridge\n[6] man bridge\n[7] comparison of brctl and bridge\n","permalink":"https://www.161616.top/linux-network-command/","summary":"工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 理解ldap - OpenLDAP客户端命令行使用 Overview 作为系统管理员或程序员，经常需要诊断分析和解决网络问题，而配置、监控与保护网络有助于发现问题并在事情范围扩大前得意解决，并且网络的性能与安全也是管理与诊断网络的重要部分。本文将总结常用与Linux网络管理的命令与使用示例，保持长期更新与更正。\nIP iproute2 包含网络、路由、ARP缓存等的管理与配置的ip命令，用来取代传统的 ifconfig 与 route；ip 使用第二个参数，指定在对象执行的操作（例如，add delete show）。\nip 命令是配置网络接口的强大工具，任何 Linux 系统管理员都应该知道。它用于启动或关闭接口、分配和删除地址和路由、管理 ARP 缓存等等。\nip 常用的子命令有：\nlink (l) 网络接口管理 address (a) IP地址管理 route (r) 路由表管理 neigh (n) arp表管理 各系统下的包名与安装\nUbuntu/Debian: iproute2 ；apt install iproute2 CentOS/Fedora: iproute2 ；yum install -y iproute2 Apline：iproute2 ；apk add iproute2 ip link ip link 用于管理和显示网络接口","title":"长期总结 - Linux网络命令合集"},{"content":"sidecar 介绍 在istio的流量管理等功能，都需要通过下发的配置应用到应用运行环境执行后生效，负责执行配置规则的组件在service mesh中承载应用代理的实体被称为side-car\nIstio对流量策略管理等功能无须对应用程序做变动，\n这种对应用服务完全非侵入的方式完全依赖于Side-car，应用的流量有Sidecar拦截并进行认证、鉴权、策略执行等治理功能。在Kubernetes平台中，Side-car容器于应用容器在同一个Pod中并共享网络名词控件，因此Side-car容易可以通过iptables规则拦截进出流量进行管理。\nsidecar的注入 sidecar是service mesh无侵入式架构的应用模式，在使用sidecar部署服务网格时，无需再每个应用节点运行服务代理，但是需要在每个应用程序中部署一个sidecar容器，来接管所有进出流量。\nSidecar会将额外容器注入到 Pod 模板中。Istio中的数据平面组件所需的容器有：\nistio-init 用于设置容器的iptables规则，目的是为了接管进出流量。在应用容器前启动并运行完成其生命周期，多个init容器按顺序依次完成。 istio-proxy 基于envoy的sidecar的代理。 sidecar被注入的方式 手动注入，使用 istioctl 修改容器模板并添加前面提到的两个容器的配置。不论是手动注入还是自动注入，Istio 都从 istio-sidecar-injector 和的 istio 两个 Configmap 对象中获取配置。 refer-istio-sidecar-injector\n自动注入，在部署应用是，istio自动将sidecar注入到pod。这是istio推荐的方法，Istio在基于Kubernetes平台之上，需要在部署应用之前，对要标记部署应用程序的名称空间标记 kubectl label namespace default istio-injection=enabled 这个操作是对名称空间级别生效的。而后所部署的Pod中会注入sidecar执行上面sidecar容器的操作。\nistio injector 注入原理 Sidecar Injector是Istio中实现自动注入Sidecar的组件，它是以Kubernetes准入控制器 AdmissionController 的形式运行的。Admission Controller 的基本工作原理是拦截Kube-apiserver的请求，在对象持久化之前、认证鉴权之后进行拦截。\nAdmission Controller有两种：一种是内置的，另一种是用户自定义的。\nKubernetes允许用户以Webhook的方式自定义准入控制器，Sidecar Injector就是这样一种特殊的MutatingAdmissionWebhook。\nSidecar Injector只在创建Pod时进行Sidecar容器注入，在Pod的创建请求到达 Kube-apiserver 后，首先进行认证鉴权，然后在准入控制阶段，Kube-apiserver以REST的方式同步调用Sidecar Injector Webhook服务进行init与istio-proxy容器的注入，最后将Pod对象持久化存储到etcd中。\nsidecar容器 sidecar容器内部运行着 pilot-agent 与 envoy\nPilot-agent：基于kubernetesAPI资源对象为envoy初始化可用的bootstrap配置进行启动，在运行后管理envoy运行状态，如配置变更，出错重启等。\nenvoy：数据平面的执行层，由 pilot-agent 所启动的，从xDS API动态获取配置信息。Envoy并通过流量拦截机制处理入栈及出栈的流量。\nenvoy的listener 在运行在Kubernetes平台之上的istio，Envoy是通过Pilot将Kubernetes CRD资源 DestnationRule VirtualService Gateway等资源提供的配置，生成对应的Envoy配置。\n每个sidecar中的envoy都会生成众多的配置，这些配置在每一个网格中会对对应的流量进行拦截管理。\n通过envoy admin interface查看对应生产的envoy资源配置信息\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 $ kubectl exec reviews-v1-6549ddccc5-k995p -c istio-proxy -- curl -s 127.0.0.1:15000/listeners 97591f7a-0cbf-469a-a5f2-c9a76a3d0ced::0.0.0.0:15090 e523c9a4-da71-439f-885f-020f70349f21::0.0.0.0:15021 10.96.56.243_10251::10.96.56.243:10251 10.0.0.6_10250::10.0.0.6:10250 10.244.0.51_8443::10.244.0.51:8443 10.0.0.5_10250::10.0.0.5:10250 10.97.190.96_10252::10.97.190.96:10252 10.96.0.1_443::10.96.0.1:443 10.96.0.10_53::10.96.0.10:53 10.105.226.65_80::10.105.226.65:80 10.105.226.65_82::10.105.226.65:82 0.0.0.0_80::0.0.0.0:80 10.0.0.6_4194::10.0.0.6:4194 10.105.226.65_443::10.105.226.65:443 10.105.226.65_81::10.105.226.65:81 10.0.0.5_4194::10.0.0.5:4194 10.102.134.81_14250::10.102.134.81:14250 10.107.39.113_9090::10.107.39.113:9090 0.0.0.0_10255::0.0.0.0:10255 0.0.0.0_20001::0.0.0.0:20001 0.0.0.0_9090::0.0.0.0:9090 10.96.0.10_9153::10.96.0.10:9153 10.102.134.81_14268::10.102.134.81:14268 0.0.0.0_9411::0.0.0.0:9411 10.101.93.145_8000::10.101.93.145:8000 10.99.79.173_443::10.99.79.173:443 10.105.226.65_8080::10.105.226.65:8080 0.0.0.0_9080::0.0.0.0:9080 10.108.161.174_3000::10.108.161.174:3000 virtualOutbound::0.0.0.0:15001 virtualInbound::0.0.0.0:15006 .. istio使用的端口\nPort Protocol Description Pod-internal only 15000 TCP envoy管理端口 Yes 15001 TCP envoy出站端口 No 15006 TCP envoy入站端口 No 15008 TCP envoy隧道端口 No 15020 HTTP 从istio-agent envoy 应用 合并prometheus遥测 No 15021 HTTP 健康检查端口 No 15090 HTTP Envoy Prometheus telemetry No envoy的listener通过绑定与IP Socket或者Unix Domain Socket，接收转发来的数据。\nVirtualOutboundListener | VirtualIntboundListener 通过一个端口接收所有出/入站流量，并根据目标端口来区分使用哪个Listener进行处理。\niptables通过对应规则拦截发往所在Pod的流量并转发到对应的Envoy接收端口（如入站流量为15006），该Listener通过配置将请求转发到请求原目标地址关联的Listener之上。envoy原始目标地址\n若不存在对应的listener则根据全局配置选项进行处理\nALLOW_ANY：允许外发至任何服务的请求，没有匹配到目标Listener的流量则由tcp_proxy过滤器指向的PassthroughCluster。 REGISTRY_ONLY：仅允许外发请求至注册过的服务，没有匹配到目标Listener的流量，则由tcp_proxy通过BlackHoleCluster丢弃。 监听端口配置参数bind_to_port的值为false，意味着该Listener并未真正绑定套接字上，而是通过对应的入站/出站Linstener接收兵转发流量。\nsidecar 流量的拦截 sidecar通过注入到Pod中的init，执行在 pod 命名空间中设置 iptable 规则来完成流量捕获并管理。\n通过查看nsenter 命令可以查看对应实现的内容。\niptables在NAT表中新建了4条链，ISTIO_INBOUND、ISTIO_IN_REDIRECT、ISTIO_OUTPUT 和 ISTIO_REDIRECT\n当进入Pod的流量会被PREROUTING 拦截处理。根据规则将数据包转发到ISOTIO_INBOUND。-A PREROUTING -p tcp -j ISTIO_INBOUND ISTIO_INBOUND处理对应的规则，当遇到15008 , 22 15090 15021 15020 不做处理，return给上一个链。 关于iptables return 剩余流量从ISTIO_INBOUND 转交给 ISTIO_IN_REDIRECT -A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT ISTIO_IN_REDIRECT 将流量交给15006端口应用处理。 Envoy根据数据包的目的地址查看 Inbound方向的监听器配置，根据监听器及路由、Cluster、 Endpoint等配置，决定是否将数据包转发到应用。 OUTPUT的流量由规则 -A OUTPUT -p tcp -j ISTIO_OUTPUT 由 ISTIO_OUTPUT ` 链处理。 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 $ nsenter -t `ps -ef|grep details|grep envoy|awk \u0026#39;{print $2}\u0026#39;` -n iptables -t nat -S -P PREROUTING ACCEPT -P INPUT ACCEPT -P OUTPUT ACCEPT -P POSTROUTING ACCEPT -N ISTIO_INBOUND -N ISTIO_IN_REDIRECT -N ISTIO_OUTPUT -N ISTIO_REDIRECT -A PREROUTING -p tcp -j ISTIO_INBOUND -A OUTPUT -p tcp -j ISTIO_OUTPUT -A ISTIO_INBOUND -p tcp -m tcp --dport 15008 -j RETURN -A ISTIO_INBOUND -p tcp -m tcp --dport 22 -j RETURN -A ISTIO_INBOUND -p tcp -m tcp --dport 15090 -j RETURN -A ISTIO_INBOUND -p tcp -m tcp --dport 15021 -j RETURN -A ISTIO_INBOUND -p tcp -m tcp --dport 15020 -j RETURN -A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT # 到此入栈流量处理完成 -A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006 # 这里开始执行出站流量 # 原地址为127.0.0.6/32 不做处理 -A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN # 默认目标127.0.0.1/32--uid-owner 1337的由ISTIO_IN_REDIRECT处理 -A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT -A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN -A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN -A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT # 这些流量都不做处理。继续默认操作 -A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN -A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN -A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN # 默认所有ISTIO_OUTPUT链的流量都由ISTIO_REDIRECT -A ISTIO_OUTPUT -j ISTIO_REDIRECT # ISTIO_REDIRECT 的流量 默认有envoy进行处理转发到对应的应用 -A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001 Istio中目前有两种流量拦截模式：REDIRECT模式和TPROXY模式。\nTPROXY transparent proxy 透明代理，操作的是mangle表，同时需要原始客户端socket设置 IP_TRANSPARENT选项，此模式同时保留源IP地址和目标IP地址和端口。\nREDIRECT ：端口重定向，将流量NAT至envoy，此模式会丢失源IP。\nsidecar资源配置 Sidecar这个资源清单描述了Sidecar代理的配置，从而管理他所连接的workload实例的流量。\nworkloadSelector：选择sidecar应用此配置的Pod，如省略则为该名称空间的所有workload lables: 配置pod的标签 ingress：用于指定连接workload的入站流量的sidecar配置，如省略，则从默认获取。 port：Listener关联的端口 bind：Listener绑定的IP，ingress不允许unix套接字 captureMode： 流量捕获模式，仅Listener绑定到IP时适用。 DEFAULT 环境定义默认配置 IPTABLES ：使用IPtables重定向流量 NONE 不捕获。 egress: 用于指定workload的出站流量的sidecar配置，如省略，则继承默认值 port：Listener关联的端口 bind：Listener绑定的IP或套接字 captureMode： 流量捕获模式，仅Listener绑定到IP时适用。 DEFAULT 环境定义默认配置 IPTABLES ：使用IPtables重定向流量 NONE 不捕获。 hosts：目标地址，namespace/dnsName格式显示的一台或多台服务主机 outboundTrafficPolicy：出站流量策略的配置，不指定则继承默认值。 mode： REGISTRY_ONLY：仅允许注册到pilot的服务通过。 ALLOW_ANY：没有配置的，允许流量的出站。 声明一个出站流量 下面的示例在Sidecar名为的根命名空间中声明了全局默认配置istio-config，该配置在所有命名空间中配置了sidecars，以仅允许将流量发送到同一命名空间中的其他workload以及istio-system命名空间中的服务 。\nyaml 1 2 3 4 5 6 7 8 9 10 apiVersion: networking.istio.io/v1alpha3 kind: Sidecar metadata: name: default namespace: istio-config spec: egress: - hosts: - \u0026#34;./*\u0026#34; - \u0026#34;istio-system/*\u0026#34; 以下示例配置一个应用的出站与入站规则；在名称空间prod-us1中声明所有app: ratings 属于该prod-us1/ratings 带有标签的Pod 。workload在端口9080上接受入站HTTP通信。然后，将通信转发到侦听Unix套接字的附加workload实例。在出口流量，除了允许发给istio-system 名称空间任何端口任何Pod的流量，如果是9080端口，允许发送给prod-us1名称空间下的所有的服务。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 apiVersion: networking.istio.io/v1alpha3 kind: Sidecar metadata: name: ratings namespace: prod-us1 spec: workloadSelector: labels: app: ratings ingress: - port: number: 9080 protocol: HTTP name: somename defaultEndpoint: unix:///var/run/someuds.sock egress: - port: number: 9080 protocol: HTTP name: egresshttp hosts: - \u0026#34;prod-us1/*\u0026#34; - hosts: - \u0026#34;istio-system/*\u0026#34; ","permalink":"https://www.161616.top/istio-sidecar-mechnisim/","summary":"sidecar 介绍 在istio的流量管理等功能，都需要通过下发的配置应用到应用运行环境执行后生效，负责执行配置规则的组件在service mesh中承载应用代理的实体被称为side-car\nIstio对流量策略管理等功能无须对应用程序做变动，\n这种对应用服务完全非侵入的方式完全依赖于Side-car，应用的流量有Sidecar拦截并进行认证、鉴权、策略执行等治理功能。在Kubernetes平台中，Side-car容器于应用容器在同一个Pod中并共享网络名词控件，因此Side-car容易可以通过iptables规则拦截进出流量进行管理。\nsidecar的注入 sidecar是service mesh无侵入式架构的应用模式，在使用sidecar部署服务网格时，无需再每个应用节点运行服务代理，但是需要在每个应用程序中部署一个sidecar容器，来接管所有进出流量。\nSidecar会将额外容器注入到 Pod 模板中。Istio中的数据平面组件所需的容器有：\nistio-init 用于设置容器的iptables规则，目的是为了接管进出流量。在应用容器前启动并运行完成其生命周期，多个init容器按顺序依次完成。 istio-proxy 基于envoy的sidecar的代理。 sidecar被注入的方式 手动注入，使用 istioctl 修改容器模板并添加前面提到的两个容器的配置。不论是手动注入还是自动注入，Istio 都从 istio-sidecar-injector 和的 istio 两个 Configmap 对象中获取配置。 refer-istio-sidecar-injector\n自动注入，在部署应用是，istio自动将sidecar注入到pod。这是istio推荐的方法，Istio在基于Kubernetes平台之上，需要在部署应用之前，对要标记部署应用程序的名称空间标记 kubectl label namespace default istio-injection=enabled 这个操作是对名称空间级别生效的。而后所部署的Pod中会注入sidecar执行上面sidecar容器的操作。\nistio injector 注入原理 Sidecar Injector是Istio中实现自动注入Sidecar的组件，它是以Kubernetes准入控制器 AdmissionController 的形式运行的。Admission Controller 的基本工作原理是拦截Kube-apiserver的请求，在对象持久化之前、认证鉴权之后进行拦截。\nAdmission Controller有两种：一种是内置的，另一种是用户自定义的。\nKubernetes允许用户以Webhook的方式自定义准入控制器，Sidecar Injector就是这样一种特殊的MutatingAdmissionWebhook。\nSidecar Injector只在创建Pod时进行Sidecar容器注入，在Pod的创建请求到达 Kube-apiserver 后，首先进行认证鉴权，然后在准入控制阶段，Kube-apiserver以REST的方式同步调用Sidecar Injector Webhook服务进行init与istio-proxy容器的注入，最后将Pod对象持久化存储到etcd中。\nsidecar容器 sidecar容器内部运行着 pilot-agent 与 envoy\nPilot-agent：基于kubernetesAPI资源对象为envoy初始化可用的bootstrap配置进行启动，在运行后管理envoy运行状态，如配置变更，出错重启等。\nenvoy：数据平面的执行层，由 pilot-agent 所启动的，从xDS API动态获取配置信息。Envoy并通过流量拦截机制处理入栈及出栈的流量。\nenvoy的listener 在运行在Kubernetes平台之上的istio，Envoy是通过Pilot将Kubernetes CRD资源 DestnationRule VirtualService Gateway等资源提供的配置，生成对应的Envoy配置。","title":"istio sidecar流量处理机制及配置"},{"content":"端口绑定无权限 创建Gateway，提示绑定端口无权限。\ntext 1 2 3 2020-12-27T12:25:30.974288Z\twarning\tenvoy config\tgRPC config for type.googleapis.com/envoy.config.listener.v3.Listener rejected: Error adding/updating listener(s) 0.0.0.0_90: cannot bind \u0026#39;0.0.0.0:90\u0026#39;: Permission denied 问题原因：容器内默认权限不能使用非特权端口（\u0026lt;1024\n导出部署清单部署失败 bash 1 istioctl manifest generate \u0026gt; generated-manifest.yaml 如果尝试使用来安装和管理Istio istioctl manifest generate，请注意以下警告：\nIstio名称空间（istio-system默认情况下）必须手动创建。 istioctl install 会从Kubernetes上下文中自动检测特定于环境的设置。如需手动安装需要执行如下步骤： --set values.global.jwtPolicy=third-party-jwt --set values.global.jwtPolicy=first-party-jwt refer\ntoken\nGenerate a manifest\n","permalink":"https://www.161616.top/istio-deployment-qa/","summary":"端口绑定无权限 创建Gateway，提示绑定端口无权限。\ntext 1 2 3 2020-12-27T12:25:30.974288Z\twarning\tenvoy config\tgRPC config for type.googleapis.com/envoy.config.listener.v3.Listener rejected: Error adding/updating listener(s) 0.0.0.0_90: cannot bind \u0026#39;0.0.0.0:90\u0026#39;: Permission denied 问题原因：容器内默认权限不能使用非特权端口（\u0026lt;1024\n导出部署清单部署失败 bash 1 istioctl manifest generate \u0026gt; generated-manifest.yaml 如果尝试使用来安装和管理Istio istioctl manifest generate，请注意以下警告：\nIstio名称空间（istio-system默认情况下）必须手动创建。 istioctl install 会从Kubernetes上下文中自动检测特定于环境的设置。如需手动安装需要执行如下步骤： --set values.global.jwtPolicy=third-party-jwt --set values.global.jwtPolicy=first-party-jwt refer\ntoken\nGenerate a manifest","title":"istio部署问题Q\u0026A"},{"content":"Open Shortest Path First OSPF，开放的最短路径优先协议，是IETF组织开发的一个基于链路状态的内部网关协议，它的使用不受任何厂商限制，所有人都可以使用，所以称为开放的，而最短路径优先（SPF）只是OSPF的核心思想，其使用的算法是Dijkstra算法，最短路径优先并没有太多特殊的含义，并没有任何一个路由协议是最长路径优先的，所有协议，都会选最短的。\nOSPF针对IPv4协议使用的是OSPF Version 2（RFC2328）；针对IPv6协议使用OSPF Version 3（RFC2740）\n目的：\n在OSPF出现前，网络上广泛使用RIP（Routing Information Protocol）作为内部网关协议。\n由于RIP是基于距离矢量算法的路由协议，存在着收敛慢、路由环路、可扩展性差等问题，所以逐渐被OSPF取代。\nOSPF作为基于链路状态的协议，能够解决RIP所面临的诸多问题。此外，OSPF还有以下优点：\nOSPF采用组播形式收发报文，这样可以减少对其它不运行OSPF路由器的影响。 OSPF支持无类型域间选路（CIDR）。 OSPF支持对等价路由进行负载分担。 OSPF支持报文加密。 由于OSPF具有以上优势，使得OSPF作为优秀的内部网关协议被快速接收并广泛使用。\nOSPF协议特点：\nOSPF把自治系统AS（Autonomous System）划分成逻辑意义上的一个或多个区域； OSPF通过LSA（Link State Advertisement）的形式发布路由； OSPF依靠在OSPF区域内各设备间交互OSPF报文来达到路由信息的统一； OSPF报文封装在IP报文内，可以采用单播或组播的形式发送。 OSPF工作流程 寻找邻居\nOSPF协议运行后，先寻找网络中可与自己交互链路状态信息的周边路由器，可以交互链路状态信息的路由器互为邻居\n建立邻居关系\n邻接关系可以想象为一条点到点的虚链路，他是在一些邻居路由器之间构成的。只有建立了可靠邻接关系的路由器才相互传递链路状态信息。\n链路状态信息传递\nOSPF路由器将建立描述网络链路状态的LSA Link State Advertisement，链路状态公告，建立邻接关系的OSPF路由器之间将交互LSA，最终形成包含网络完整链路状态的配置信息。\n计算路由\n获得了完整的LSBD后，OSPF区域内的每个路由器将会对该区域的网络结构有相同的认识，随后各路由器将依据LSDB的信息用SPF算法独立计算出路由。\nRouter ID OSPF Router-ID用于在OSPF domain中唯一地表示一台OSPF路由器，从OSPF网络设计的角度，我们要求全OSPF域内，禁止出现两台路由器拥有相同的Router-ID。\nOSPF Router-ID的设定可以通过手工配置的方式，或者通过协议自动选取的方式。当然，在实际网络部署中，强烈建议手工配置OSPF的Router-ID，因为这关系到协议的稳定。\n实验：单区域OSPF配置 配置两台路由器\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 [Huawei]sysname R2 [R2]interface lo0 [R2-LoopBack0]ip add 2.2.2.2 32 [R2-LoopBack0]dis this [V200R003C00] # interface LoopBack0 ip address 2.2.2.2 255.255.255.255 # return [R2-LoopBack0]quit [R2]interface g0/0/0 [R2-GigabitEthernet0/0/0]ip a 20.0.0.1 24 Jan 24 2021 22:01:22-08:00 R2 %%01IFNET/4/LINK_STATE(l)[0]:The line protocol IP on the interface GigabitEthernet0/0/0 has entered the UP state. [R2-GigabitEthernet0/0/0]dis this [V200R003C00] # interface GigabitEthernet0/0/0 ip address 20.0.0.1 255.255.255.0 # return [R2]dis ip routing-table Route Flags: R - relay, D - download to fib ------------------------------------------------------------------------------ Routing Tables: Public Destinations : 8 Routes : 8 Destination/Mask Proto Pre Cost Flags NextHop Interface 2.2.2.2/32 Direct 0 0 D 127.0.0.1 LoopBack0 20.0.0.0/24 Direct 0 0 D 20.0.0.1 GigabitEthernet 0/0/0 20.0.0.1/32 Direct 0 0 D 127.0.0.1 GigabitEthernet 0/0/0 20.0.0.255/32 Direct 0 0 D 127.0.0.1 GigabitEthernet 0/0/0 127.0.0.0/8 Direct 0 0 D 127.0.0.1 InLoopBack0 127.0.0.1/32 Direct 0 0 D 127.0.0.1 InLoopBack0 127.255.255.255/32 Direct 0 0 D 127.0.0.1 InLoopBack0 255.255.255.255/32 Direct 0 0 D 127.0.0.1 InLoopBack0 整理配置\ntext 1 2 3 4 5 6 7 8 9 sysname R2 interface lo0 ip add 2.2.2.2 32 [R2-LoopBack0]dis this quit interface g0/0/0 ip a 20.0.0.1 24 dis this dis ip routing-table 配置ospf协议\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 配置route-id ospf router-id 2.2.2.2 # 选择区域 # OSPF实施了两层的分层： # 1.骨干区域（也就是area0） # 2.连接到骨干的区域（area1~65535） area 0 # 声明一个路由，子网掩码为反向的 network 2.2.2.2 0.0.0.0 network 10.0.0.0 0.0.0.255 # 打印ospf对的简要信息 dis ospf peer brief # 显示ospf路由表 dis ospf routing 可以看到对应的已经学习到动态的路由\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [R1]dis ip routing-table Route Flags: R - relay, D - download to fib ------------------------------------------------------------------------------ Routing Tables: Public Destinations : 9 Routes : 9 Destination/Mask Proto Pre Cost Flags NextHop Interface 1.1.1.1/32 Direct 0 0 D 127.0.0.1 LoopBack0 2.2.2.2/32 OSPF 10 1 D 10.0.0.2 GigabitEthernet 0/0/0 10.0.0.0/24 Direct 0 0 D 10.0.0.1 GigabitEthernet 0/0/0 10.0.0.1/32 Direct 0 0 D 127.0.0.1 GigabitEthernet 0/0/0 10.0.0.255/32 Direct 0 0 D 127.0.0.1 GigabitEthernet 0/0/0 127.0.0.0/8 Direct 0 0 D 127.0.0.1 InLoopBack0 127.0.0.1/32 Direct 0 0 D 127.0.0.1 InLoopBack0 127.255.255.255/32 Direct 0 0 D 127.0.0.1 InLoopBack0 255.255.255.255/32 Direct 0 0 D 127.0.0.1 InLoopBack0 配置完成后可以看到对应的报文与状态\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [R1-ospf-1-area-0.0.0.0] Jan 24 2021 22:12:19-08:00 R1 %%01OSPF/4/NBR_CHANGE_E(l)[0]:Neighbor changes eve nt: neighbor status changed. (ProcessId=256, NeighborAddress=2.0.0.10, NeighborE vent=HelloReceived, NeighborPreviousState=Down, NeighborCurrentState=Init) [R1-ospf-1-area-0.0.0.0] Jan 24 2021 22:12:19-08:00 R1 %%01OSPF/4/NBR_CHANGE_E(l)[1]:Neighbor changes eve nt: neighbor status changed. (ProcessId=256, NeighborAddress=2.0.0.10, NeighborE vent=2WayReceived, NeighborPreviousState=Init, NeighborCurrentState=ExStart) [R1-ospf-1-area-0.0.0.0] Jan 24 2021 22:12:19-08:00 R1 %%01OSPF/4/NBR_CHANGE_E(l)[2]:Neighbor changes eve nt: neighbor status changed. (ProcessId=256, NeighborAddress=2.0.0.10, NeighborE vent=NegotiationDone, NeighborPreviousState=ExStart, NeighborCurrentState=Exchan ge) [R1-ospf-1-area-0.0.0.0] Jan 24 2021 22:12:19-08:00 R1 %%01OSPF/4/NBR_CHANGE_E(l)[3]:Neighbor changes eve nt: neighbor status changed. (ProcessId=256, NeighborAddress=2.0.0.10, NeighborE vent=ExchangeDone, NeighborPreviousState=Exchange, NeighborCurrentState=Loading) [R1-ospf-1-area-0.0.0.0] Jan 24 2021 22:12:20-08:00 R1 %%01OSPF/4/NBR_CHANGE_E(l)[4]:Neighbor changes eve nt: neighbor status changed. (ProcessId=256, NeighborAddress=2.0.0.10, NeighborE vent=LoadingDone, NeighborPreviousState=Loading, NeighborCurrentState=Full) [R1-ospf-1-area-0.0.0.0] ospf的八种状态 在OSPF网络中，为了交换路由信息，邻居设备之间首先要建立邻接关系，邻居（Neighbors）关系和邻接（Adjacencies）关系是两个不同的概念。\n邻居关系：OSPF设备启动后，会通过OSPF接口向外发送Hello报文，收到Hello报文的OSPF设备会检查报文中所定义的参数，如果双方一致就会形成邻居关系，两端设备互为邻居。\n邻接关系：形成邻居关系后，如果两端设备成功交换DD报文和LSA，才建立邻接关系。\nOSPF共有8种状态机，分别是：Down、Attempt、Init、2-way、Exstart、Exchange、Loading、Full。\nDown：邻居会话的初始阶段，表明没有在邻居失效时间间隔内收到来自邻居路由器的Hello数据包。 Attempt：该状态仅发生在NBMA网络中，表明对端在邻居失效时间间隔（dead interval）超时前仍然没有回复Hello报文。此时路由器依然每发送轮询Hello报文的时间间隔（poll interval）向对端发送Hello报文。 Init：收到Hello报文后状态为Init。 2-way：收到的Hello报文中包含有自己的Router ID，则状态为2-way；如果不需要形成邻接关系则邻居状态机就停留在此状态，否则进入Exstart状态。 Exstart：开始协商主从关系，并确定DD的序列号，此时状态为Exstart。 Exchange：主从关系协商完毕后开始交换DD报文，此时状态为Exchange。 Loading：DD报文交换完成即Exchange done，此时状态为Loading。 Full：LSR重传列表为空，此时状态为Full 实验 多播网络ospf关系 在广播多路访问网络（Multi Access）中，所有的路由器的接口都是相同网段，这些接口两两建立OSPF邻居关系，这就意味着，网络中共有：n(n-1)/2。维护如此多的邻居关系不仅额外消耗资源，更增加了网络中LSA的泛洪数量。\n为减小多路访问网络中的 OSPF 流量，OSPF 会在每一个MA网络（多路访问网络）选举一个指定路由器 (DR)和一个备用指定路由器 (BDR)。\nDR选举规则：最高OSPF接口优先级拥有者被选作DR，如果优先级相等（默认为1），具有最高的OSPF Router-ID的路由器被选举成DR，并且DR具有非抢占性。\n指定路由器 (DR)：DR 负责使用该变化信息更新其它所有 OSPF 路由器（DR Rother）。备用指定路由器 (BDR)：BDR 会监控 DR 的状态，并在当前 DR 发生故障时接替其角色。 注意OSPF为“接口敏感型协议”，DR及BDR的身份状态是基于OSPF接口的。\nMA网络中，所有的DRother路由器均只与DR和BDR建立邻接关系，DRother间不建立全毗邻邻接关系。如此一来，该多路访问网络中设备需要维护的OSPF邻居关系大幅减小：M= (n-2)×2+1，LSA的泛洪问题也可以得到一定的缓解。\n可以查看到对应两种状态的ospf中的角色\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 [R3]dis ospf peer brief OSPF Process 1 with Router ID 10.10.10.10 Peer Statistic Information ---------------------------------------------------------------------------- Area Id Interface Neighbor id State 0.0.0.0 GigabitEthernet0/0/0 30.30.30.30 Full 0.0.0.0 GigabitEthernet0/0/0 5.5.5.5 Full 0.0.0.0 GigabitEthernet0/0/0 6.6.6.6 Full 0.0.0.0 GigabitEthernet0/0/0 7.7.7.7 Full ---------------------------------------------------------------------------- [R7-ospf-1-area-0.0.0.0]dis ospf peer brief OSPF Process 1 with Router ID 7.7.7.7 Peer Statistic Information ---------------------------------------------------------------------------- Area Id Interface Neighbor id State 0.0.0.0 GigabitEthernet0/0/0 10.10.10.10 Full 0.0.0.0 GigabitEthernet0/0/0 30.30.30.30 Full 0.0.0.0 GigabitEthernet0/0/0 5.5.5.5 2-Way 0.0.0.0 GigabitEthernet0/0/0 6.6.6.6 2-Way ---------------------------------------------------------------------------- # 除了dr 与 bdr 任何机器值只与dr和bdr形成关系 [R3]dis ospf peer OSPF Process 1 with Router ID 10.10.10.10 Neighbors Area 0.0.0.0 interface 192.168.0.10(GigabitEthernet0/0/0)\u0026#39;s neighbors Router ID: 30.30.30.30 Address: 192.168.0.11 State: Full Mode:Nbr is Master Priority: 1 DR: 192.168.0.10 BDR: 192.168.0.11 MTU: 0 Dead timer due in 40 sec Retrans timer interval: 5 Neighbor is up for 00:57:22 Authentication Sequence: [ 0 ] Router ID: 5.5.5.5 Address: 192.168.0.12 State: Full Mode:Nbr is Slave Priority: 1 DR: 192.168.0.10 BDR: 192.168.0.11 MTU: 0 Dead timer due in 40 sec Retrans timer interval: 5 Neighbor is up for 00:56:31 Authentication Sequence: [ 0 ] Router ID: 6.6.6.6 Address: 192.168.0.13 State: Full Mode:Nbr is Slave Priority: 1 DR: 192.168.0.10 BDR: 192.168.0.11 MTU: 0 Dead timer due in 40 sec Retrans timer interval: 5 Neighbor is up for 00:56:08 Authentication Sequence: [ 0 ] 实验：多区域ospf 对两个区域的路由器设置对应的配置\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 system-view sysname R10 ip address 10.0.0.1 24 in l0 ip address 1.1.1.1 32 ospf router-id 4.4.4.4 area 0 network 0.0.0.0 255.255.255.255 [R10]dis ospf peer brief OSPF Process 1 with Router ID 4.4.4.4 Peer Statistic Information ---------------------------------------------------------------------------- Area Id Interface Neighbor id State 0.0.0.0 GigabitEthernet0/0/0 5.5.5.5 Full ---------------------------------------------------------------------------- 对边界路由设置双区域\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 system-view sysname R11 ip address 10.0.0.2 24 in l0 ip address 2.2.2.2 32 interface g0/0/1 ip address 10.1.0.1 255.255.255.0 ospf router-id 5.5.5.5 area 0 network 10.0.0.0 0.0.0.255 network 2.2.2.2 0.0.0.0 area 1 network 10.1.0.0 0.0.0.255 [R11-ospf-1-area-0.0.0.1]dis ospf peer brief OSPF Process 1 with Router ID 5.5.5.5 Peer Statistic Information ---------------------------------------------------------------------------- Area Id Interface Neighbor id State 0.0.0.0 GigabitEthernet0/0/0 4.4.4.4 Full 0.0.0.1 GigabitEthernet0/0/1 3.3.3.3 Full ---------------------------------------------------------------------------- 可以看到已经学习到对应的路由了\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 [R10]dis ip routing-table Route Flags: R - relay, D - download to fib ------------------------------------------------------------------------------ Routing Tables: Public Destinations : 11 Routes : 11 Destination/Mask Proto Pre Cost Flags NextHop Interface 1.1.1.1/32 Direct 0 0 D 127.0.0.1 LoopBack0 2.2.2.2/32 OSPF 10 1 D 10.0.0.2 GigabitEthernet 0/0/0 3.3.3.3/32 OSPF 10 2 D 10.0.0.2 GigabitEthernet 0/0/0 10.0.0.0/24 Direct 0 0 D 10.0.0.1 GigabitEthernet 0/0/0 10.0.0.1/32 Direct 0 0 D 127.0.0.1 GigabitEthernet 0/0/0 10.0.0.255/32 Direct 0 0 D 127.0.0.1 GigabitEthernet 0/0/0 10.1.0.0/24 OSPF 10 2 D 10.0.0.2 GigabitEthernet 0/0/0 127.0.0.0/8 Direct 0 0 D 127.0.0.1 InLoopBack0 127.0.0.1/32 Direct 0 0 D 127.0.0.1 InLoopBack0 127.255.255.255/32 Direct 0 0 D 127.0.0.1 InLoopBack0 255.255.255.255/32 Direct 0 0 D 127.0.0.1 InLoopBack0 [ospf-test.zip](......\\images\\Dynamic Routing - OSPF\\ospf-test.zip)\n","permalink":"https://www.161616.top/dynamic-routing-ospf/","summary":"Open Shortest Path First OSPF，开放的最短路径优先协议，是IETF组织开发的一个基于链路状态的内部网关协议，它的使用不受任何厂商限制，所有人都可以使用，所以称为开放的，而最短路径优先（SPF）只是OSPF的核心思想，其使用的算法是Dijkstra算法，最短路径优先并没有太多特殊的含义，并没有任何一个路由协议是最长路径优先的，所有协议，都会选最短的。\nOSPF针对IPv4协议使用的是OSPF Version 2（RFC2328）；针对IPv6协议使用OSPF Version 3（RFC2740）\n目的：\n在OSPF出现前，网络上广泛使用RIP（Routing Information Protocol）作为内部网关协议。\n由于RIP是基于距离矢量算法的路由协议，存在着收敛慢、路由环路、可扩展性差等问题，所以逐渐被OSPF取代。\nOSPF作为基于链路状态的协议，能够解决RIP所面临的诸多问题。此外，OSPF还有以下优点：\nOSPF采用组播形式收发报文，这样可以减少对其它不运行OSPF路由器的影响。 OSPF支持无类型域间选路（CIDR）。 OSPF支持对等价路由进行负载分担。 OSPF支持报文加密。 由于OSPF具有以上优势，使得OSPF作为优秀的内部网关协议被快速接收并广泛使用。\nOSPF协议特点：\nOSPF把自治系统AS（Autonomous System）划分成逻辑意义上的一个或多个区域； OSPF通过LSA（Link State Advertisement）的形式发布路由； OSPF依靠在OSPF区域内各设备间交互OSPF报文来达到路由信息的统一； OSPF报文封装在IP报文内，可以采用单播或组播的形式发送。 OSPF工作流程 寻找邻居\nOSPF协议运行后，先寻找网络中可与自己交互链路状态信息的周边路由器，可以交互链路状态信息的路由器互为邻居\n建立邻居关系\n邻接关系可以想象为一条点到点的虚链路，他是在一些邻居路由器之间构成的。只有建立了可靠邻接关系的路由器才相互传递链路状态信息。\n链路状态信息传递\nOSPF路由器将建立描述网络链路状态的LSA Link State Advertisement，链路状态公告，建立邻接关系的OSPF路由器之间将交互LSA，最终形成包含网络完整链路状态的配置信息。\n计算路由\n获得了完整的LSBD后，OSPF区域内的每个路由器将会对该区域的网络结构有相同的认识，随后各路由器将依据LSDB的信息用SPF算法独立计算出路由。\nRouter ID OSPF Router-ID用于在OSPF domain中唯一地表示一台OSPF路由器，从OSPF网络设计的角度，我们要求全OSPF域内，禁止出现两台路由器拥有相同的Router-ID。\nOSPF Router-ID的设定可以通过手工配置的方式，或者通过协议自动选取的方式。当然，在实际网络部署中，强烈建议手工配置OSPF的Router-ID，因为这关系到协议的稳定。\n实验：单区域OSPF配置 配置两台路由器\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 [Huawei]sysname R2 [R2]interface lo0 [R2-LoopBack0]ip add 2.","title":"动态路由 - OSPF"},{"content":" 本文是关于深入理解Kubernetes网络原理系列第1章 深入理解Kubernetes Pod网络原理 - 网络名称空间 深入理解Kubernetes Pod网络原理 - Linux虚拟网络技术 深入理解Kubernetes Pod网络原理 - CNI 深入理解Kubernetes Pod网络原理 - 跟随 flannel 学习CNI原理 深入理解Kubernetes Pod网络原理 - 跟随 flannel + multus 剖析 Chained Plugins 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 1 (Shell) 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 2 (libcni) 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 1 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 2 深入理解Kubernetes Pod网络原理 - Pod网络排错思路 Linux namespace namespace是Linux内核的一项功能，该功能对内核资源进行分区，以使一组进程看到一组资源，而另一组进程看到另一组资源。该功能通过为一组资源和进程具有相同的名称空间而起作用，但是这些名称空间引用了不同的资源。资源可能存在于多个空间中。\nLinux namespaces 是对全局系统资源的一种封装隔离，使得处于不同 namespace 的进程拥有独立的全局系统资源，改变一个namespace中的系统资源只会影响当前 namespace 里的进程，对其他 namespace 中的进程没有影响。\n每一个进程在其对应的 /proc/[pid]/ns 下都有其 namespace 信息\n查看当前进程的namespace ll /proc/$$/ns\nbash 1 2 3 4 5 6 7 8 9 10 cgroup -\u0026gt; cgroup:[4026531835] cgroup的根目录 ipc -\u0026gt; ipc:[4026531839] 信号量 mnt -\u0026gt; mnt:[4026531840] 文件系统挂载点 net -\u0026gt; net:[4026531992] 网络设备、网络栈、端口 pid -\u0026gt; pid:[4026531836] 进程编号 pid_for_children -\u0026gt; pid:[4026531836] time -\u0026gt; time:[4026531834] time_for_children -\u0026gt; time:[4026531834] user -\u0026gt; user:[4026531837] 用户和用户组 uts -\u0026gt; uts:[4026531838] 主机名和NIS域名 linux的网络名称空间 linux network namespace 是network namespace 是 linux 内核提供的功能，在实现网络虚拟化中起重要作用，每个网络名称空间中有独立的网络协议栈，如路由表、iptables、端口等。在network namespace可以创建多个隔离的网络空间，它们有独自的网络栈信息。在运行的时候仿佛自己就在独立的网络中。\nlinux网络管理命令ip ip命令是Linux管理网络的工具，他对路由、地址、链路、namespace等的管理。\nip命令的使用说明：\nip netns list 查看网络命名空间\nip netns add net2 创建一个网络命名空间\nip link add $name link eth0 type ipvlan mode l2 在当前名称空间创建一个类型为ipvlan l2模式的接口，将该接口关联至父接口eth0上。\nip link set $name netns $nsName 将接口加入到对应网络名称空间内\nip netns exec $nsName $cmd 在对应的网络名称空间内运行命令\nip link add $p1-name type veth peer name $p2-name 创建一个网卡对\nethtool -S $interface_name 查看网卡对对应关系，通过index获得另外一端\n虚拟以太网对 VETH Pair VETH virtual-ethernet 虚拟以太网对，是网络交换机中的虚拟接口，在linux名称空间中，vEth可以充当名称空间之间的隧道，通过建立一个网桥链接到另一个名称空间中的物理设备，所有设备始终以互连对的形式创建。\n实验：使在不同网络名称空间下的vEth互通 实验实现：创建两个名称空间net1和net2，以及一对VETH设备，并将其分配veth1给namespacenet1 和veth2namespace net2。这两个名称空间与此VETH对相连。分配一对IP地址，使两个名称空间之间ping通。\n开启linuxip转发功能\ntext 1 echo 1 \u0026gt; /proc/sys/net/ipv4/ip_forward 创建两个名称空间\ntext 1 2 ip netns add net1 ip netns add net2 创建两个虚拟网卡对\ntext 1 2 ip link a veth0 type veth peer name br-veth0 ip link a veth1 type veth peer name br-veth1 创建一个网桥\ntext 1 2 3 ip link add bridge1 type bridge # 启动设备 ip link set bridge1 up 将虚拟网卡对的一段链接至网桥上\ntext 1 2 3 4 5 ip link set br-veth0 master bridge1 ip link set br-veth1 master bridge1 ip link set br-veth1 up ip link set br-veth0 up 虚拟网桥另外一端关联至对应名称空间内\ntext 1 2 3 4 5 6 7 8 ip link set veth0 netns net1 ip link set veth1 netns net2 ip netns exec net1 ip addr add 192.168.0.1/24 dev veth0 ip netns exec net2 ip addr add 192.168.0.2/24 dev veth1 ip netns exec net1 ip link set veth0 up ip netns exec net2 ip link set veth1 up 此时可以通过网桥对这两个网络进行互相ping通，不同网段的需要手动添加相应的路由表规则。而ping自己的地址不通，原因是因为lo设备未启动。\ntext 1 ip netns exec net2 ip link set lo up OVS ipip mode IPIP是RFC 2003中定义的IP over IP隧道。IPIP隧道标头如下所示：\n实验：实现一个IPIP隧道 ipip 模式需要内核模块 ipip.ko 使用命令查看内核是否加载IPIP模块lsmod | grep ipip ；使用命令modprobe ipip 加载\n实验效果，如下图，在两个名称空间内创建一个 tun 设备，然后将该 tun 设备绑定为一个 ipip 隧道。\n创建名称空间及虚拟网卡对\ntext 1 2 3 4 5 6 7 8 ip netns add net1 ip netns add net2 ip link a veth0 type veth peer name br-veth0 ip link a veth1 type veth peer name br-veth1 ip link set veth0 netns net1 ip link set veth1 netns net2 给虚拟网卡对的一端配置地址\ntext 1 2 3 4 5 ip addr add 192.168.10.1/24 dev br-veth0 ip addr add 192.168.20.1/24 dev br-veth1 ip netns exec net1 ip link set veth0 up ip netns exec net2 ip link set veth1 up 在两个名称空间内分别创建一个ipip tunnel\ntext 1 2 3 4 5 6 7 8 netns exec net2 ip tunnel add tun1 mode ipip remote 192.168.10.2 local 192.168.20.2 netns exec net1 ip tunnel add tun1 mode ipip remote 192.168.20.2 local 192.168.10.2 ip netns exec net1 ip link set tun1 up ip netns exec net2 ip link set tun1 up ip netns exec net1 ip addr add 192.168.100.10 peer 192.168.200.10 dev tun1 ip netns exec net2 ip addr add 192.168.200.10 peer 192.168.100.10 dev tun1 这个命令是在对应网络名称空间内创建隧道设备tun1，并设置隧道模式为 ipip，然后还需要设置隧道端点，用 remote 和 local 表示，这是隧道外层 IP，对应设置 隧道内层 IP，用 ip addr xx peer xx 配置。\n分析ipip tunnel过程。\n首先 ping 命令构建一个 ICMP 请求包，ICMP 包封装在 IP 包中，源目的 IP 地址分别为 tun1(192.168.200.10) 和 tun2(192.168.100.10) 的地址。 由于 tun1 和 tun2 不在同一网段，所以会查路由表，当通过 ip tunnel 命令建立 ipip 隧道之后，会自动生成一条路由，如下，表明去往目的地 192.168.100.10 的路由直接从 tun1 出去。 bash 1 2 3 4 5 6 $ ip netns exec net2 route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 192.168.10.0 192.168.20.1 255.255.255.0 UG 0 0 0 veth1 192.168.20.0 0.0.0.0 255.255.255.0 U 0 0 0 veth1 192.168.100.10 0.0.0.0 255.255.255.255 UH 0 0 0 tun1 由于配置了隧道端点，数据包出了 tun1，到达 veth1，根据 ipip 隧道的配置，会封装上一层新的 IP 报头，源目的 IP 地址分别为 veth2(192.168.20.2) 和 veth1(192.168.10.2)。 veth2和 veth1不在一个网段，因为手动添加的路由表，发现去往 192.168.10.0 网段从 192.168.20.1 veth2虚拟网卡对另外一端 br-veth2出。 因为Linux 打开了 ip_forward，类似于路由器功能，192.168.10.0 和 192.168.20.1 为直连路由，直接有路由器转发。完成了net2到net1的过程。 数据包到达 net1 的 veth1，解封装数据包，发现内层 IP 报文的目的 IP 地址是 192.168.100.10，这正是自己配置的 ipip 隧道的 tun1(192.168.100.10) 地址，于是将报文转交 tun1(192.168.100.10)。 ICMP报文是双向的，故，相通的步骤还要返回到tun1(192.168.200.10) 。 通过抓包工具查看数据包的详细内容\n","permalink":"https://www.161616.top/netspace/","summary":"本文是关于深入理解Kubernetes网络原理系列第1章 深入理解Kubernetes Pod网络原理 - 网络名称空间 深入理解Kubernetes Pod网络原理 - Linux虚拟网络技术 深入理解Kubernetes Pod网络原理 - CNI 深入理解Kubernetes Pod网络原理 - 跟随 flannel 学习CNI原理 深入理解Kubernetes Pod网络原理 - 跟随 flannel + multus 剖析 Chained Plugins 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 1 (Shell) 深入理解Kubernetes Pod网络原理 - 从零实现一个 CNI Plugin part 2 (libcni) 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 1 深入理解Kubernetes Pod网络原理 - Kubernetes网络模型 2 深入理解Kubernetes Pod网络原理 - Pod网络排错思路 Linux namespace namespace是Linux内核的一项功能，该功能对内核资源进行分区，以使一组进程看到一组资源，而另一组进程看到另一组资源。该功能通过为一组资源和进程具有相同的名称空间而起作用，但是这些名称空间引用了不同的资源。资源可能存在于多个空间中。\nLinux namespaces 是对全局系统资源的一种封装隔离，使得处于不同 namespace 的进程拥有独立的全局系统资源，改变一个namespace中的系统资源只会影响当前 namespace 里的进程，对其他 namespace 中的进程没有影响。","title":"深入理解Kubernetes Pod网络原理 - 网络名称空间"},{"content":"虚拟局域网VLAN（Virtual Local Area Network），是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。\nVLAN内的主机间可以直接通信，而VLAN间不能直接通信，从而将广播报文限制在一个VLAN内。\n以太网是一种基于CSMA/CD（Carrier Sense Multiple Access/Collision Detection）的共享通讯介质的数据网络通讯技术。当主机数目较多时会导致冲突严重、广播泛滥、性能显著下降甚至造成网络不可用等问题。通过交换机实现LAN互连虽然可以解决冲突严重的问题，但仍然不能隔离广播报文和提升网络质量。\n在这种情况下出现了VLAN技术，这种技术可以把一个LAN划分成多个逻辑的VLAN，每个VLAN是一个广播域，VLAN内的主机间通信就和在一个LAN内一样，而VLAN间则不能直接互通，这样，广播报文就被限制在一个VLAN内。\nVLAN的作用 限制广播域：广播域被限制在一个VLAN内，节省了带宽，提高了网络处理能力。\n增强局域网的安全性：不同VLAN内的报文在传输时是相互隔离的，即一个VLAN内的用户不能和其它VLAN内的用户直接通信。\n提高了网络的健壮性：故障被限制在一个VLAN内，本VLAN内的故障不会影响其他VLAN的正常工作。\n灵活构建虚拟工作组：用VLAN可以划分不同的用户到不同的工作组，同一工作组的用户也不必局限于某一固定的物理范围，网络构建和维护更方便灵活。\nVLAN Tag 要使交换机能够分辨不同VLAN的报文，需要在报文中添加标识VLAN信息的字段。IEEE 802.1Q协议规定，在以太网数据帧的目的MAC地址和源MAC地址字段之后、协议类型字段之前加入4个字节的VLAN标签（又称VLAN Tag，简称Tag），用以标识VLAN信息。\nVLAN Tag各字段含义：\n字段 长度 含义 取值 TPID 2Byte Tag Protocol Identifier（标签协议标识符），表示数据帧类型。 表示帧类型，取值为0x8100时表示IEEE 802.1Q的VLAN数据帧。如果不支持802.1Q的设备收到这样的帧，会将其丢弃。各设备厂商可以自定义该字段的值。当邻居设备将TPID值配置为非0x8100时， 为了能够识别这样的报文，实现互通，必须在本设备上修改TPID值，确保和邻居设备的TPID值配置一致。 PRI 3bit Priority，表示数据帧的802.1p优先级。 取值范围为0～7，值越大优先级越高。当网络阻塞时，设备优先发送优先级高的数据帧。 CFI 1bit Canonical Format Indicator（标准格式指示位），表示MAC地址在不同的传输介质中是否以标准格式进行封装，用于兼容以太网和令牌环网。 CFI取值为0表示MAC地址以标准格式进行封装，为1表示以非标准格式封装。在以太网中，CFI的值为0。 VID 12bit VLAN ID，表示该数据帧所属VLAN的编号。 VLAN ID取值范围是0～4095。由于0和4095为协议保留取值，所以VLAN ID的有效取值范围是1～4094。 其中，数据帧中的VID（VLAN ID）字段标识了该数据帧所属的VLAN，数据帧只能在其所属VLAN内进行传输。\n对于交换机来说，其内部处理的数据帧都带有VLAN标签，而现网中交换机连接的设备有些只会收发Untagged帧，要与这些设备交互，就需要接口能够识别Untagged帧并在收发时给帧添加、剥除VLAN标签。同时，现网中属于同一个VLAN的用户可能会被连接在不同的交换机上，且跨越交换机的VLAN可能不止一个，如果需要用户间的互通，就需要交换机间的接口能够同时识别和发送多个VLAN的数据帧。\nVLAN PVID 缺省VLAN又称PVID（Port Default VLAN ID）。设备处理的数据帧都带Tag，当设备收到UNTagged帧时，就需要给该帧添加Tag，添加什么Tag，就由接口上的缺省VLAN决定。一个物理端口只能拥有一个PVID，当一个物理端口拥有了一个PVID的时候，必定会拥有和PVID相等的VID，而且在这个VID上，这个物理端口必定是Untagged Port。\n因此，根据接口连接对象以及对收发数据帧处理的不同，华为定义了4种接口的链路类型：Access、Trunk、Hybrid和QinQ，以适应不同的连接和组网：\nAccess接口：一般用于和不能识别Tag的用户终端（如用户主机、服务器等）相连，或者不需要区分不同VLAN成员时使用。Access接口大部分情况只能收发Untagged帧，且只能为Untagged帧添加唯一的VLAN Tag。 Trunk接口：一般用于连接交换机、路由器、AP以及可同时收发Tagged帧和Untagged帧的语音终端。它可以允许多个VLAN的帧带Tag通过，但只允许一个VLAN的帧从该类接口上发出时不带Tag（即剥除Tag）。 Hybrid接口：既可以用于连接不能识别Tag的用户终端（如用户主机、服务器等）和网络设备（如Hub、傻瓜交换机），也可以用于连接交换机、路由器以及可同时收发Tagged帧和Untagged帧的语音终端、AP。它可以允许多个VLAN的帧带Tag通过，且允许从该类接口发出的帧根据需要配置某些VLAN的帧带Tag（即不剥除Tag）、某些VLAN的帧不带Tag（即剥除Tag）。 使用QinQ（802.1Q-in-802.1Q）协议，一般用于私网与公网之间的连接，也被称为Dot1q-tunnel接口。它可以给帧加上双层Tag，即在原来Tag的基础上，给帧加上一个新的Tag，从而可以支持多达4094×4094个VLAN。 接口类型 对接收不带Tag的报文处理 对接收带Tag的报文处理 发送帧处理过程 Access接口 接收该报文，并打上缺省的VLAN ID。 当VLAN ID与缺省VLAN ID相同时，接收该报文。当VLAN ID与缺省VLAN ID不同时，丢弃该报文 先剥离帧的PVID Tag，然后再发送。 Trunk接口 打上缺省的VLAN ID，当缺省VLAN ID在允许通过的VLAN ID列表里时，接收该报文。打上缺省的VLAN ID，当缺省VLAN ID不在允许通过的VLAN ID列表里时，丢弃该报文。 当VLAN ID在接口允许通过的VLAN ID列表里时，接收该报文。当VLAN ID不在接口允许通过的VLAN ID列表里时，丢弃该报文 当VLAN ID与缺省VLAN ID相同，且是该接口允许通过的VLAN ID时，去掉Tag，发送该报文。当VLAN ID与缺省VLAN ID不同，且是该接口允许通过的VLAN ID时，保持原有Tag，发送该报文。 Hybrid接口 打上缺省的VLAN ID，当缺省VLAN ID在允许通过的VLAN ID列表里时，接收该报文。打上缺省的VLAN ID，当缺省VLAN ID不在允许通过的VLAN ID列表里时，丢弃该报文。 当VLAN ID在接口允许通过的VLAN ID列表里时，接收该报文。当VLAN ID不在接口允许通过的VLAN ID列表里时，丢弃该报文。 当VLAN ID是该接口允许通过的VLAN ID时，发送该报文。可以通过命令设置发送时是否携带Tag。 由上面各类接口添加或剥除VLAN标签的处理过程可见：\n当接收到不带VLAN标签的数据帧时，Access接口、Trunk接口、Hybrid接口都会给数据帧打上VLAN标签，但Trunk接口、Hybrid接口会根据数据帧的VID是否为其允许通过的VLAN来判断是否接收，而Access接口则无条件接收。\n当接收到带VLAN标签的数据帧时，Access接口、Trunk接口、Hybrid接口都会根据数据帧的VID是否为其允许通过的VLAN（Access接口允许通过的VLAN就是缺省VLAN）来判断是否接收。\n当发送数据帧时：\nAccess接口直接剥离数据帧中的VLAN标签。 Trunk接口只有在数据帧中的VID与接口的PVID相等时才会剥离数据帧中的VLAN标签。 Hybrid接口会根据接口上的配置判断是否剥离数据帧中的VLAN标签。 因此，Access接口发出的数据帧肯定不带Tag，Trunk接口发出的数据帧只有一个VLAN的数据帧不带Tag，其他都带VLAN标签，Hybrid接口发出的数据帧可根据需要设置某些VLAN的数据帧带Tag，某些VLAN的数据帧不带Tag。\nVLAN-Access Port Access接口一般用于和不能识别Tag的用户终端（如用户主机、服务器等）相连，或者不需要区分不同VLAN成员时使用。它只能收发Untagged帧，且只能为Untagged帧添加唯一VLAN的Tag。\n配置VLAN access\ntext 1 2 3 interface GigabitEthernet0/0/1 port link-type access port default vlan 10 可以看到收到的包和回来的包并添加VLAN Tag\nVLAN-Hybrid Port Hybrid接口既可以用于连接不能识别Tag的用户终端（如用户主机、服务器）和网络设备（如Hub），也可用于连接换机、路由器以及可同时收发Tagged帧和Untagged帧的语音终端、AP。它可允许多个VLAN的帧带Tag通过，且允许从该类接口发出的帧根据需要配置某些VLAN的帧带Tag（即不剥除Tag）某些VLAN的帧不带Tag（即剥除Tag）。\n![image-20210131144114120](../../../../images/vlan interface/image-20210131144114120.png)\n配置Hybrid vlan\ntext 1 2 3 interface GigabitEthernet0/0/1 port hybrid pvid vlan 10 port hybrid tagged vlan 10 抓包可以看到对应的Hybrid port收到的\n![image-20210131132701528](../../../../images/vlan interface/image-20210131132701528.png)\n![image-20210131132642594](../../../../images/vlan interface/image-20210131132642594.png)\n实现pc1与pc2都可与pc3互通，pc1与pc2之间不能互通。\n![image-20210131195302130](../../../../images/vlan interface/image-20210131195302130.png)\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 system-view sysname SW7 interface GigabitEthernet0/0/1 # 进来时给包打标签 port hybrid pvid vlan 10 # 出去时去掉标签 port hybrid untagged vlan 10 30 interface GigabitEthernet0/0/2 port hybrid pvid vlan 20 port hybrid untagged vlan 20 30 interface GigabitEthernet0/0/3 port hybrid pvid vlan 30 port hybrid untagged vlan 10 20 30 流程分析：当pc1流量进入SW时会被添加vlan10的tag，在通过vlan30口出去时会进行 port hybrid untagged|tag == port trunk access xxx 这个操作是“是否允许这些tag通过，通过时进行对tag的操作 tag | untag ”\n实验\n![image-20210131201307039](../../../../images/vlan interface/image-20210131201307039.png)\ntext 1 2 3 4 5 6 7 8 9 10 system-view sysname SW8 interface GigabitEthernet0/0/1 port hybrid pvid vlan 10 port hybrid untagged vlan 20 interface GigabitEthernet0/0/2 port hybrid pvid vlan 20 port hybrid untagged vlan 10 这个实验可以证实，在接口G0/0/1到G0/0/2分配配置了untapped对方vlan的id发现无法ping通。\n在默认情况下hybrid只允许vlan1通过，而G0/0/1会被打上vlan10的tag，而到了G0/0/2，此时的标签设置的是双向都设置的为只允许vlan10的通过而不允许vlan20通过，而G0/0/1则相反。所以双方都需要port hybrid untagged vlan 10 20\nVLAN-Trunk Port Trunk接口一般用于连接交换机、路由器、AP以及可同时收发Tagged帧和Untagged帧的语音终端。它可以允许多个VLAN的帧带Tag通过，但只允许一个VLAN的帧从该类接口上发出时不带Tag（即剥除Tag）。\n![image-20210131152952129](../../../../images/vlan interface/image-20210131152952129.png)\n配置交换机 4 实现图2 4的论点\n![image-20210131172254100](../../../../images/vlan interface/image-20210131172254100.png)\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 system-view sysname SW3 vlan batch 10 20 interface GigabitEthernet0/0/1 port link-type access port default vlan 10 interface GigabitEthernet0/0/2 port link-type access port default vlan 20 interface GigabitEthernet0/0/3 port link-type trunk port trunk allow-pass vlan 10 20 SW4\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 system-view sysname SW4 vlan batch 10 20 interface GigabitEthernet0/0/1 port link-type trunk port trunk allow-pass vlan 10 20 interface GigabitEthernet0/0/2 port link-type access port default vlan 10 interface GigabitEthernet0/0/3 port link-type access port default vlan 20 ![image-20210131193456826](../../../../images/vlan interface/image-20210131193456826.png)\n此图完成的是图 1 3步骤的论点\nSW5\ntext 1 2 3 4 5 6 7 interface GigabitEthernet0/0/1 port link-type access port default vlan 10 interface GigabitEthernet0/0/2 port link-type access port default vlan 10 SW6\ntext 1 2 3 4 5 6 7 8 interface GigabitEthernet0/0/1 port link-type trunk port trunk pvid vlan 10 port trunk allow-pass vlan 10 20 interface GigabitEthernet0/0/2 port link-type access port default vlan 10 [vlan.zip](......\\images\\vlan interface\\vlan.zip)\n","permalink":"https://www.161616.top/experiment-vlan/","summary":"虚拟局域网VLAN（Virtual Local Area Network），是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。\nVLAN内的主机间可以直接通信，而VLAN间不能直接通信，从而将广播报文限制在一个VLAN内。\n以太网是一种基于CSMA/CD（Carrier Sense Multiple Access/Collision Detection）的共享通讯介质的数据网络通讯技术。当主机数目较多时会导致冲突严重、广播泛滥、性能显著下降甚至造成网络不可用等问题。通过交换机实现LAN互连虽然可以解决冲突严重的问题，但仍然不能隔离广播报文和提升网络质量。\n在这种情况下出现了VLAN技术，这种技术可以把一个LAN划分成多个逻辑的VLAN，每个VLAN是一个广播域，VLAN内的主机间通信就和在一个LAN内一样，而VLAN间则不能直接互通，这样，广播报文就被限制在一个VLAN内。\nVLAN的作用 限制广播域：广播域被限制在一个VLAN内，节省了带宽，提高了网络处理能力。\n增强局域网的安全性：不同VLAN内的报文在传输时是相互隔离的，即一个VLAN内的用户不能和其它VLAN内的用户直接通信。\n提高了网络的健壮性：故障被限制在一个VLAN内，本VLAN内的故障不会影响其他VLAN的正常工作。\n灵活构建虚拟工作组：用VLAN可以划分不同的用户到不同的工作组，同一工作组的用户也不必局限于某一固定的物理范围，网络构建和维护更方便灵活。\nVLAN Tag 要使交换机能够分辨不同VLAN的报文，需要在报文中添加标识VLAN信息的字段。IEEE 802.1Q协议规定，在以太网数据帧的目的MAC地址和源MAC地址字段之后、协议类型字段之前加入4个字节的VLAN标签（又称VLAN Tag，简称Tag），用以标识VLAN信息。\nVLAN Tag各字段含义：\n字段 长度 含义 取值 TPID 2Byte Tag Protocol Identifier（标签协议标识符），表示数据帧类型。 表示帧类型，取值为0x8100时表示IEEE 802.1Q的VLAN数据帧。如果不支持802.1Q的设备收到这样的帧，会将其丢弃。各设备厂商可以自定义该字段的值。当邻居设备将TPID值配置为非0x8100时， 为了能够识别这样的报文，实现互通，必须在本设备上修改TPID值，确保和邻居设备的TPID值配置一致。 PRI 3bit Priority，表示数据帧的802.1p优先级。 取值范围为0～7，值越大优先级越高。当网络阻塞时，设备优先发送优先级高的数据帧。 CFI 1bit Canonical Format Indicator（标准格式指示位），表示MAC地址在不同的传输介质中是否以标准格式进行封装，用于兼容以太网和令牌环网。 CFI取值为0表示MAC地址以标准格式进行封装，为1表示以非标准格式封装。在以太网中，CFI的值为0。 VID 12bit VLAN ID，表示该数据帧所属VLAN的编号。 VLAN ID取值范围是0～4095。由于0和4095为协议保留取值，所以VLAN ID的有效取值范围是1～4094。 其中，数据帧中的VID（VLAN ID）字段标识了该数据帧所属的VLAN，数据帧只能在其所属VLAN内进行传输。\n对于交换机来说，其内部处理的数据帧都带有VLAN标签，而现网中交换机连接的设备有些只会收发Untagged帧，要与这些设备交互，就需要接口能够识别Untagged帧并在收发时给帧添加、剥除VLAN标签。同时，现网中属于同一个VLAN的用户可能会被连接在不同的交换机上，且跨越交换机的VLAN可能不止一个，如果需要用户间的互通，就需要交换机间的接口能够同时识别和发送多个VLAN的数据帧。\nVLAN PVID 缺省VLAN又称PVID（Port Default VLAN ID）。设备处理的数据帧都带Tag，当设备收到UNTagged帧时，就需要给该帧添加Tag，添加什么Tag，就由接口上的缺省VLAN决定。一个物理端口只能拥有一个PVID，当一个物理端口拥有了一个PVID的时候，必定会拥有和PVID相等的VID，而且在这个VID上，这个物理端口必定是Untagged Port。\n因此，根据接口连接对象以及对收发数据帧处理的不同，华为定义了4种接口的链路类型：Access、Trunk、Hybrid和QinQ，以适应不同的连接和组网：\nAccess接口：一般用于和不能识别Tag的用户终端（如用户主机、服务器等）相连，或者不需要区分不同VLAN成员时使用。Access接口大部分情况只能收发Untagged帧，且只能为Untagged帧添加唯一的VLAN Tag。 Trunk接口：一般用于连接交换机、路由器、AP以及可同时收发Tagged帧和Untagged帧的语音终端。它可以允许多个VLAN的帧带Tag通过，但只允许一个VLAN的帧从该类接口上发出时不带Tag（即剥除Tag）。 Hybrid接口：既可以用于连接不能识别Tag的用户终端（如用户主机、服务器等）和网络设备（如Hub、傻瓜交换机），也可以用于连接交换机、路由器以及可同时收发Tagged帧和Untagged帧的语音终端、AP。它可以允许多个VLAN的帧带Tag通过，且允许从该类接口发出的帧根据需要配置某些VLAN的帧带Tag（即不剥除Tag）、某些VLAN的帧不带Tag（即剥除Tag）。 使用QinQ（802.1Q-in-802.1Q）协议，一般用于私网与公网之间的连接，也被称为Dot1q-tunnel接口。它可以给帧加上双层Tag，即在原来Tag的基础上，给帧加上一个新的Tag，从而可以支持多达4094×4094个VLAN。 接口类型 对接收不带Tag的报文处理 对接收带Tag的报文处理 发送帧处理过程 Access接口 接收该报文，并打上缺省的VLAN ID。 当VLAN ID与缺省VLAN ID相同时，接收该报文。当VLAN ID与缺省VLAN ID不同时，丢弃该报文 先剥离帧的PVID Tag，然后再发送。 Trunk接口 打上缺省的VLAN ID，当缺省VLAN ID在允许通过的VLAN ID列表里时，接收该报文。打上缺省的VLAN ID，当缺省VLAN ID不在允许通过的VLAN ID列表里时，丢弃该报文。 当VLAN ID在接口允许通过的VLAN ID列表里时，接收该报文。当VLAN ID不在接口允许通过的VLAN ID列表里时，丢弃该报文 当VLAN ID与缺省VLAN ID相同，且是该接口允许通过的VLAN ID时，去掉Tag，发送该报文。当VLAN ID与缺省VLAN ID不同，且是该接口允许通过的VLAN ID时，保持原有Tag，发送该报文。 Hybrid接口 打上缺省的VLAN ID，当缺省VLAN ID在允许通过的VLAN ID列表里时，接收该报文。打上缺省的VLAN ID，当缺省VLAN ID不在允许通过的VLAN ID列表里时，丢弃该报文。 当VLAN ID在接口允许通过的VLAN ID列表里时，接收该报文。当VLAN ID不在接口允许通过的VLAN ID列表里时，丢弃该报文。 当VLAN ID是该接口允许通过的VLAN ID时，发送该报文。可以通过命令设置发送时是否携带Tag。 由上面各类接口添加或剥除VLAN标签的处理过程可见：","title":"网络实验 - VLAN"},{"content":"vXlan概念 实验 什么是VxLAN RFC定义了虚拟扩展局域网 VXLAN （Virtual eXtensible Local Area Network，）扩展方案，是对传统VLAN协议的一种扩展。VXLAN采用 （MAC\rin UDP（User Datagram Protocol）封装方式，是NVO3（Network Virtualization over Layer 3）中的一种网络虚拟化技术。VXLAN的特点是将L2的以太帧封装到UDP报文（即L2 over L4）中，并在L3网络中传输。\nVXLAN本质上是一种隧道技术，在源网络设备与目的网络设备之间的IP网络上，建立一条逻辑隧道，将用户报文经过特定的封装后通过这条隧道转发。从用户的角度来看，接入网络的服务器就像是连接到了一个虚拟的二层交换机的不同端口上（可把蓝色虚框表示的数据中心VXLAN网络看成一个二层虚拟交换机），可以方便地通信。\n为什么需要VxLAN 虚拟机规模受网络设备表项规格的限制\n在传统二层网络环境下，数据报文是通过查询MAC地址表进行二层转发。服务器虚拟化后，VM的数量比原有的物理机发生了数量级的增长，伴随而来的便是VM网卡MAC地址数量的空前增加。而接入侧二层设备的MAC地址表规格较小，无法满足快速增长的VM数量。\n网络隔离能力有限\nVLAN作为当前主流的网络隔离技术，在标准定义中只有12比特，因此可用的VLAN数量仅4096个。对于公有云或其它大型虚拟化云计算服务这种动辄上万甚至更多租户的场景而言，VLAN的隔离能力无法满足。\n虚拟机迁移范围受限\n由于服务器资源等问题（如CPU过高，内存不够等），虚拟机迁移已经成为了一个常态性业务。\n什么是虚拟机动态迁移？\n所谓虚拟机动态迁移，是指在保证虚拟机上服务正常运行的同时，将一个虚拟机系统从一个物理服务器移动到另一个物理服务器的过程。该过程对于最终用户来说是无感知的，从而使得管理员能够在不影响用户正常使用的情况下，灵活调配服务器资源，或者对物理服务器进行维修和升级。\n在服务器虚拟化后，虚拟机动态迁移变得常态化，为了保证迁移时业务不中断，就要求在虚拟机迁移时，不仅虚拟机的IP地址、MAC地址等参数保持不变，而且虚拟机的运行状态也必须保持原状（例如TCP会话状态），所以虚拟机的动态迁移只能在同一个二层域中进行，而不能跨二层域迁移。\nVxLAN方案 为了应对传统数据中心网络对服务器虚拟化技术的限制，VXLAN技术应运而生，其能够很好的解决上述问题。\n针对虚拟机规模受设备表项规格限制\nVXLAN将管理员规划的同一区域内的VM发出的原始报文封装成新的UDP报文，并使用物理网络的IP和MAC地址作为外层头，这样报文对网络中的其他设备只表现为封装后的参数。因此，极大降低了大二层网络对MAC地址规格的需求。\n针对网络隔离能力限制\n在传统的VLAN网络中，标准定义所支持的可用VLAN数量只有4000个左右。VXLAN引入了类似VLAN ID的用户标识，称为VXLAN网络标识VNI（VXLAN Network Identifier），由24比特组成，支持多达16M的VXLAN段，有效得解决了云计算中海量租户隔离的问题。\n针对虚拟机迁移范围受限\nVXLAN将VM发出的原始报文进行封装后通过VXLAN隧道进行传输，隧道两端的VM不需感知传输网络的物理架构。这样，对于具有同一网段IP地址的VM而言，即使其物理位置不在同一个二层网络中，但从逻辑上看，相当于处于同一个二层域。即VXLAN技术在三层网络之上，构建出了一个虚拟的大二层网络，只要虚拟机路由可达，就可以将其规划到同一个大二层网络中。这就解决了虚拟机迁移范围受限问题。\nVxLAN与VLAN之间的区别 VLAN是传统的网络隔离技术，在标准定义中VLAN的数量只有4096，无法满足大型数据中心的租户间隔离需求。另外，VLAN的二层范围一般较小且固定，无法支持虚拟机大范围的动态迁移。\nVXLAN完美地弥补了VLAN的上述不足，一方面通过VXLAN中的24比特VNI字段，提供多达16M租户的标识能力，远大于VLAN的4096；另一方面，VXLAN本质上在两台交换机之间构建了一条穿越数据中心基础IP网络的虚拟隧道，将数据中心网络虚拟成一个巨型“二层交换机”，满足虚拟机大范围动态迁移的需求。\nVXLAN Header\n增加VXLAN头（8字节），其中包含24比特的VNI字段，用来定义VXLAN网络中不同的租户。此外，还包含VXLAN Flags（8比特，取值为00001000）和两个保留字段（分别为24比特和8比特）。\nUDP Header\nVXLAN头和原始以太帧一起作为UDP的数据。UDP头中，目的端口号（VXLAN Port）固定为4789，源端口号（UDP Src. Port）是原始以太帧通过哈希算法计算后的值。\nOuter IP Header\n封装外层IP头。其中，源IP地址（Outer Src. IP）为源VM所属VTEP的IP地址，目的IP地址（Outer Dst. IP）为目的VM所属VTEP的IP地址。\nOuter MAC Header\n封装外层以太头。其中，源MAC地址（Src. MAC Addr.）为源VM所属VTEP的MAC地址，目的MAC地址（Dst. MAC Addr.）为到达目的VTEP的路径中下一跳设备的MAC地址。\n实验：创建一个VxLAN网络 实现网络拓扑图，使用VXLAN在两台TOR交换机之间建立了一条隧道，将服务器发出的原始数据帧加以“包装”，好让原始报文可以在承载网络（比如IP网络）上传输。当到达目的服务器所连接的TOR交换机后，离开VXLAN隧道，并将原始数据帧恢复出来，继续转发给目的服务器。\n什么是VXLAN VTEP VXLAN隧道端点，VTEP（VXLAN Tunnel Endpoints）是VXLAN网络的边缘设备，是VXLAN隧道的起点和终点，VXLAN对用户原始数据帧的封装和解封装均在VTEP上进行。\nVTEP是VXLAN网络中绝对的主角，VTEP既可以是一台独立的网络设备（比如华为的CloudEngine系列交换机），也可以是在服务器中的虚拟交换机。源服务器发出的原始数据帧，在VTEP上被封装成VXLAN格式的报文，并在IP网络中传递到另外一个VTEP上，并经过解封转还原出原始的数据帧，最后转发给目的服务器。\n什么是VXLAN VNI VNI（VXLAN Network Identifier，VXLAN 网络标识符），VNI是一种类似于VLAN ID的用户标识，一个VNI代表了一个租户，属于不同VNI的虚拟机之间不能直接进行二层通信。在VXLAN报文封装时，给VNI分配了24比特的长度空间，使其可以支持海量租户的隔离。\n二层VNI是普通的VNI，以1：1方式映射到广播域BD，实现VXLAN报文同子网的转发。 三层VNI和VPN实例进行关联，用于VXLAN报文跨子网的转发（三层VNI的工作详情将在另外一篇EVPN相关的文档中展开描述）。 VNI的出现，就是专门解决以太网数据帧中VLAN只占了12比特的空间，这使得VLAN的隔离能力在数据中心网络中力不从心的问题\n完成VxLAN网络架构 使用VxLAN完成192.168.100.1 和 192.168.100.2之间的互联互通。模拟器使用eNSP、Underlay网络使用OSPFv2、Overlay使用VxLAN。\neNSP中配置VxLAN 在eNSP中只有华为的CE设备（CloudEngine系列交换机）支持VxLAN\nSW1\ntext 1 2 3 4 5 6 7 8 9 10 11 12 system-view sysname SW1 vlan 10 interface GigabitEthernet0/0/1 port link-type trunk port trunk allow-pass vlan all interface GigabitEthernet0/0/2 port link-type access port default vlan 10 dis ip interface brief SW2\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 system-view sysname SW2 vlan 10 interface GigabitEthernet0/0/1 port link-type trunk port trunk allow-pass vlan all interface GigabitEthernet0/0/2 port link-type access port default vlan 10 dis ip interface brief CE1\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 system-view sysname CE1 bridge-domain 10 interface GE1/0/0 # 因为CE设备默认关闭了端口 undo shutdown # 将接口模式设置为2层模式 interface GE1/0/0.10 mode l2 # 数据包的封装 # 路由器上配置trunk的封装协议的命令 # dot1q中继封装，10指的是vlan 10 encapsulation dot1q vid 10 commit quit # 桥接域，连接两个不同的网段使用 bridge-domain 10 vxlan vni 10 commit # 将bg桥与端口关联 interface GE1/0/0.10 bridge-domain 10 commit ### interface GE1/0/1 # 关闭默认交换口，二层设备无法配置IP地址 undo portswitch undo shutdown ip address 172.16.0.1 255.255.255.0 commit # 配置ospf interface LoopBack0 ip address 1.1.1.1 255.255.255.255 quit commit ospf router-id 1.1.1.1 area 0.0.0.0 network 0.0.0.0 255.255.255.255 commit # 创建VxLAN隧道 interface Nve1 # 创建逻辑接口NVE 1 source 1.1.1.1 # 配置源VTEP的IP地址（推荐使用Loopback接口的IP地址） ## vni 10的头端复制列表为对端 vni 10 head-end peer-list 2.2.2.2 commit CE2\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 system-view sysname CE2 interface GE1/0/0 # 因为CE设备默认关闭了端口 undo shutdown # 将接口模式设置为2层模式 interface GE1/0/0.10 mode l2 # 数据包的封装 # 路由器上配置trunk的封装协议的命令 # dot1q中继封装，10指的是vlan 10 encapsulation dot1q vid 10 commit quit # 桥接域，连接两个不同的网段使用 bridge-domain 10 vxlan vni 10 commit # 将bg桥与端口关联 interface GE1/0/0.10 bridge-domain 10 commit ### interface GE1/0/1 # 关闭默认交换口，二层设备无法配置IP地址 undo portswitch undo shutdown ip address 172.16.0.2 255.255.255.0 commit # 配置ospf interface LoopBack0 ip address 2.2.2.2 255.255.255.255 quit commit ospf router-id 2.2.2.2 area 0.0.0.0 network 0.0.0.0 255.255.255.255 commit # 创建VxLAN隧道 interface Nve1 # 创建逻辑接口NVE 1 source 2.2.2.2 # 配置源VTEP的IP地址（推荐使用Loopback接口的IP地址） ## vni 10的头端复制列表为对端 vni 10 head-end peer-list 1.1.1.1 commit 实验文件\nvxlan.zip\nvtep_g1_0_0.pcapng\nvtep_g1_0_1.pcapng\nreference huawei_vxlan_guide\n","permalink":"https://www.161616.top/experiment-vxlan/","summary":"vXlan概念 实验 什么是VxLAN RFC定义了虚拟扩展局域网 VXLAN （Virtual eXtensible Local Area Network，）扩展方案，是对传统VLAN协议的一种扩展。VXLAN采用 （MAC\rin UDP（User Datagram Protocol）封装方式，是NVO3（Network Virtualization over Layer 3）中的一种网络虚拟化技术。VXLAN的特点是将L2的以太帧封装到UDP报文（即L2 over L4）中，并在L3网络中传输。\nVXLAN本质上是一种隧道技术，在源网络设备与目的网络设备之间的IP网络上，建立一条逻辑隧道，将用户报文经过特定的封装后通过这条隧道转发。从用户的角度来看，接入网络的服务器就像是连接到了一个虚拟的二层交换机的不同端口上（可把蓝色虚框表示的数据中心VXLAN网络看成一个二层虚拟交换机），可以方便地通信。\n为什么需要VxLAN 虚拟机规模受网络设备表项规格的限制\n在传统二层网络环境下，数据报文是通过查询MAC地址表进行二层转发。服务器虚拟化后，VM的数量比原有的物理机发生了数量级的增长，伴随而来的便是VM网卡MAC地址数量的空前增加。而接入侧二层设备的MAC地址表规格较小，无法满足快速增长的VM数量。\n网络隔离能力有限\nVLAN作为当前主流的网络隔离技术，在标准定义中只有12比特，因此可用的VLAN数量仅4096个。对于公有云或其它大型虚拟化云计算服务这种动辄上万甚至更多租户的场景而言，VLAN的隔离能力无法满足。\n虚拟机迁移范围受限\n由于服务器资源等问题（如CPU过高，内存不够等），虚拟机迁移已经成为了一个常态性业务。\n什么是虚拟机动态迁移？\n所谓虚拟机动态迁移，是指在保证虚拟机上服务正常运行的同时，将一个虚拟机系统从一个物理服务器移动到另一个物理服务器的过程。该过程对于最终用户来说是无感知的，从而使得管理员能够在不影响用户正常使用的情况下，灵活调配服务器资源，或者对物理服务器进行维修和升级。\n在服务器虚拟化后，虚拟机动态迁移变得常态化，为了保证迁移时业务不中断，就要求在虚拟机迁移时，不仅虚拟机的IP地址、MAC地址等参数保持不变，而且虚拟机的运行状态也必须保持原状（例如TCP会话状态），所以虚拟机的动态迁移只能在同一个二层域中进行，而不能跨二层域迁移。\nVxLAN方案 为了应对传统数据中心网络对服务器虚拟化技术的限制，VXLAN技术应运而生，其能够很好的解决上述问题。\n针对虚拟机规模受设备表项规格限制\nVXLAN将管理员规划的同一区域内的VM发出的原始报文封装成新的UDP报文，并使用物理网络的IP和MAC地址作为外层头，这样报文对网络中的其他设备只表现为封装后的参数。因此，极大降低了大二层网络对MAC地址规格的需求。\n针对网络隔离能力限制\n在传统的VLAN网络中，标准定义所支持的可用VLAN数量只有4000个左右。VXLAN引入了类似VLAN ID的用户标识，称为VXLAN网络标识VNI（VXLAN Network Identifier），由24比特组成，支持多达16M的VXLAN段，有效得解决了云计算中海量租户隔离的问题。\n针对虚拟机迁移范围受限\nVXLAN将VM发出的原始报文进行封装后通过VXLAN隧道进行传输，隧道两端的VM不需感知传输网络的物理架构。这样，对于具有同一网段IP地址的VM而言，即使其物理位置不在同一个二层网络中，但从逻辑上看，相当于处于同一个二层域。即VXLAN技术在三层网络之上，构建出了一个虚拟的大二层网络，只要虚拟机路由可达，就可以将其规划到同一个大二层网络中。这就解决了虚拟机迁移范围受限问题。\nVxLAN与VLAN之间的区别 VLAN是传统的网络隔离技术，在标准定义中VLAN的数量只有4096，无法满足大型数据中心的租户间隔离需求。另外，VLAN的二层范围一般较小且固定，无法支持虚拟机大范围的动态迁移。\nVXLAN完美地弥补了VLAN的上述不足，一方面通过VXLAN中的24比特VNI字段，提供多达16M租户的标识能力，远大于VLAN的4096；另一方面，VXLAN本质上在两台交换机之间构建了一条穿越数据中心基础IP网络的虚拟隧道，将数据中心网络虚拟成一个巨型“二层交换机”，满足虚拟机大范围动态迁移的需求。\nVXLAN Header\n增加VXLAN头（8字节），其中包含24比特的VNI字段，用来定义VXLAN网络中不同的租户。此外，还包含VXLAN Flags（8比特，取值为00001000）和两个保留字段（分别为24比特和8比特）。\nUDP Header\nVXLAN头和原始以太帧一起作为UDP的数据。UDP头中，目的端口号（VXLAN Port）固定为4789，源端口号（UDP Src. Port）是原始以太帧通过哈希算法计算后的值。\nOuter IP Header\n封装外层IP头。其中，源IP地址（Outer Src. IP）为源VM所属VTEP的IP地址，目的IP地址（Outer Dst. IP）为目的VM所属VTEP的IP地址。\nOuter MAC Header\n封装外层以太头。其中，源MAC地址（Src. MAC Addr.）为源VM所属VTEP的MAC地址，目的MAC地址（Dst. MAC Addr.","title":"网络实验 - VxLAN"},{"content":"　在服务治理中，流量管理是一个广泛的话题，一般情况下，常用的包括：\n动态修改服务访问的负载均衡策略，比如根据某个请求特征做会话保持； 同一个服务有多版本管理，将一部分流量切到某个版本上； 对服务进行保护，例如限制并发连接数、限制请求数、隔离故障服务实例等； 动态修改服务中的内容，或者模拟一个服务运行故障等。 在Istio中实现这些服务治理功能时无须修改任何应用的代码。较之微服务的SDK方式，Istio以一种更轻便、透明的方式向用户提供了这些功能。用户可以用自己喜欢的任意语言和框架进行开发，专注于自己的业务，完全不用嵌入任何治理逻辑。只要应用运行在Istio的基础设施上，就可以使用这些治理能力。\n总结Istio流量治理的目标：以基础设施的方式提供给用户非侵入的流量治理能力，用户只需关注自己的业务逻辑开发，无须关注服务访问管理。\nistio流量治理的核心组件Pilot 在istio1.8中，istio的分为 envoy （数据平面） 、istiod （控制平面） 、addons（管理插件） 及 istioctl （命令行工具，用于安装、配置、诊断分析等操作）组成。\nPilot是Istio控制平面流量管理的核心组件，管理和配置部署在Istio服务网格中的所有Envoy代理实例。\npilot-discovery为envoy sidecar提供服务发现，用于路由及流量的管理。通过kubernetes CRD资源获取网格的配置信息将其转换为xDS接口的标准数据格式后，通过gRPC分发至相关的envoy sidecar\nPilot组件包含工作在控制平面中的 pilot-discovery 和工作与数据平面的pilot-agent 与Envoy(istio-proxy)\npilot-discovery主要完成如下功能：\n从service registry中获取服务信息 从apiserver中获取配置信息。 将服务信息与配置信息适配为xDS接口的标准数据格式，通过xDS api完成配置分发。 pilot-agent 主要完成如下功能\n基于kubernetes apiserver为envoy初始化可用的boostrap配置文件并启动envoy。\n管理监控envoy的云兄状态及配置重载。\nenvoy\n每个sidecar中的envoy是由pilot-agent基于生产的bootstrap配置进行启动，并根据指定的pilot地址，通过xDS api动态获取配置。 sidecar形式的envoy通过流量拦截机制为应用程序实现入站和出站的代理功能。 Pilot的实现 在istio中的管理策略都是基于Kubernetes CRD的实现，其中有关于流量管理的CRD资源包括 VirtualService EnvoyFilter Gateway ServiceEntry Sidecar DestinationRule WorkloadEntry WorkloadGroup。reference istio-networking-crd-resouces\nVirtualServices：用于定义路由，可以理解为envoy的 listener =\u0026gt; filter =\u0026gt; route_config\nDestinationRule：用于定义集群，可以理解为envoy 的 cluster\nGateway：用于定义作用于istio-ingress-gateway\nServiceEntry：用于定义出站的路由，作用于istio-egress-gateway\nEnvoyFilter：为envoy添加过滤器或过滤器链。\nSidecar：用于定义运行在sidecar之上的envoy配置。\nVirtual Services和 Destination Rules是Istio流量路由功能的核心组件\nistio流量流程概要 在控制面会经过如下流程：\n（1）管理员通过命令行或者API创建流量规则； （2）Pilot将流量规则转换为Envoy的标准格式； （3）Pilot将规则下发给Envoy。 在数据面会经过如下流程：\n（1）Envoy拦截Pod上本地容器的Inbound流量和Outbound流量； （2）在流量经过Envoy时执行对应的流量规则，对流量进行治理。 路由规则 ：Virtual Services VirtualServices是istio用于在其运行平台Kubernetes定义的配置，用来影响流量的路由规则；其本质就是为集群中envoy提供路由配置的。\nVirtualServices名词解释 VirtualServices中一些流量路由定义的关键术语。\nServices：服务的唯一应用名称的单位，在Kubernetes之上 Services通常为Kubernetes Services资源。\nSource：在上文中，下游发起请求的客户端服务。\nHost：客户端请求服务时使用的地址\nService versions：service允许的不同版本的子集（通常为流量管理中的概念，如AB等）每个Service都有一个包含所有实例的默认版本。\nVirtualServices资源说明 VirtualServices中主要有这些配置用于配置流量的路由定义。 reference virtual services\nhosts：string[] 目标主机，可以是带有统配符的DNS Name或IP\ngateways：string[]，这些资源生效的网关和sidecar的名称。默认为名称空间级别，跨名称空间使用 \u0026lt;gateway namespace\u0026gt;/\u0026lt;gateway name\u0026gt;\nmesh 默认值，表示生效与网格内所有sidecar\n仅应用于Gateway，该字段设置为Gateway的名称。\n忽略此字段：将应用于网格内部所有的sidecar\nhttp： HTTP协议流量的路由规则表。\nmatch：[] 匹配的条件。一个列表内单项内容的条件具有AND，整个列表的条件为OR。 name： uri：匹配值区分大小写 exact: 精确匹配。 prefix：用于前缀匹配。 regex：基于正则表达式匹配。 method：HTTP方法，参数与uri相同。 \u0026hellip; route：[] 设置的http流量的转发规则 destination：请求转发到的唯一标识符。 host：允许平台及ServiceEntry的服务名称，Kubernetes中为短名称reviews.default.svc.cluster.local subset，在DestinationRule中定义的子集 port：可选，公开服务的端口 weight：转发流量的比例0-100 ，各目标的和应为100。 headers：操作头规则。 redirect：重定向规则 delegate：只能在Route和Redirect为空时设置，委托的VirtualServices 名称 rewrite：重写HTTP URI。 timeout：HTTP请求超时，默认禁用。 retries：HTTP请求重试策略。 fault：故障注入 mirror：流量镜像 mirrorPercentage：对应mirror的比例 headers：操作http头的规则 \u0026hellip; tcp：TCP流量的路由规则的有序列表\nexportTo：允许 VirtualServices 其他名称空间的sidecar与gateway使用。\nVirtualServices配置实例 基于HTTP header的请求，将请求为/ratings/v2/ 路径，并且请求头包含 end-user 值为jason 。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings-route spec: hosts: - ratings.prod.svc.cluster.local http: - match: - headers: end-user: exact: jason uri: prefix: \u0026#34;/ratings/v2/\u0026#34; ignoreUriCase: true # 是否区分大小写，仅exact和prefix生效。 route: - destination: host: ratings.prod.svc.cluster.local 委托其他virtualServices处理\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: bookinfo spec: hosts: - \u0026#34;bookinfo.com\u0026#34; gateways: - mygateway http: - match: - uri: prefix: \u0026#34;/productpage\u0026#34; delegate: name: productpage namespace: nsA - match: - uri: prefix: \u0026#34;/reviews\u0026#34; delegate: name: reviews namespace: nsB yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: productpage namespace: nsA spec: http: - match: - uri: prefix: \u0026#34;/productpage/v1/\u0026#34; route: - destination: host: productpage-v1.nsA.svc.cluster.local - route: - destination: host: productpage.nsA.svc.cluster.local --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews namespace: nsB spec: http: - route: - destination: host: reviews.nsB.svc.cluster.local 目标规则：DestinationRule DestinationRule定义在完成路由配置后应用于服务流量的策略，即如何将流量调度至集群内，可以理解为DestinationRule定义的是envoy中的cluster。应用的内容也是envoy中cluster段的配置，如负载均衡配置，sidecar连接值及离群检测。\nDestinationRule字段说明 host: 注册表中的服务名称，kubernetes平台中使用短名称 trafficPolicy：应用的流量策略。 loadBalancer：使用的负载均衡算法， simple ROUND_ROBIN LEAST_CONN RANDOM PASSTHROUGH connectionPool：一致性hash outlierDetection：离群值检测 consecutiveGatewayErrors：满足502 503 504 错误数弹出。 consecutive5xxErrors： 满足5xx错误数弹出。 interval：探测时间间隔 baseEjectionTime：最小逐出时间。主机被驱逐的时间等于baseEjectionTime * 退出次数。 maxEjectionPercent：最大驱逐比例，默认10%。 minHealthPercent：最少健康比例，默认为0% tls portLevelSettings subsets：[] 服务各个版本命名集。 name：子集的名称 labels：标签过滤器 trafficPolicy：子集流量策略，继承DestinationRule级别流量策略。 exportTo：跨名称空间使用。 DestinationRule配置实例 基于服务子集的配置\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: bookinfo-ratings spec: host: ratings.prod.svc.cluster.local trafficPolicy: loadBalancer: simple: LEAST_CONN subsets: - name: testversionv3 labels: version: v3 - name: testversionv2 labels: version: v2 trafficPolicy: loadBalancer: simple: ROUND_ROBIN 配置离群值\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews-cb-policy spec: host: reviews.prod.svc.cluster.local trafficPolicy: connectionPool: tcp: maxConnections: 100 http: http2MaxRequests: 1000 maxRequestsPerConnection: 10 outlierDetection: consecutiveErrors: 7 interval: 5m baseEjectionTime: 15m 集群网关入口：Gateway Istio还提供了一种配置模型 Istio Gateway。Gateway 与 KubernetesIngress 相比，Gateway有高度的定制化与灵活性，并且允许将Istio功能应用于集群流量入口。\nGateway中运行的程序为envoy，它从控制平面接收相应的配置，并完成相关流量的传输；Gateway资源只负责网络入口点的相关功能，具体的路由实现则由VirtualService完成。\nGateway 配置说明 Gateway定义了一个集群入口的负载均衡器，该负载均衡为运行在网格的边缘代理，负责将外部流量引入集群的内部。\nGateway资源生效于Ingress | Egress Envoy Pod的标签选择器，使用selector定义：selector: app=istio-ingressgateway。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: study-gateway namespace: default spec: selector: # 基于名称空间中匹配pod的标签从而生效的应用 app: istio-ingressgateway # 标签可以是一个或多个 servers: # 描述对应的envoy的lintener的配置。 - port: # 设置envoy lintener number: 90 # 端口号 (Required) targetPort: # 可选 (Optional) name: envoy_end # 分配给端口的标签。 protocol: HTTP # 端口服务协议，HTTP|HTTPS|GRPC|HTTP2|MONGO|TCP|TLS hosts: [ \u0026#34;*\u0026#34; , \u0026#34;text.studyenvoy.com\u0026#34; ] # 设置dnsName 可选的名称空间，*|. tls: # 与TLS相关的选项集 (Optional) name: # 服务器的可选名称，必须唯一 (Optional) Gateway配置实例 基于istio Bookinfo示例的Gateway资源清单。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: bookinfo-gateway spec: selector: istio: ingressgateway # use istio default controller servers: - port: number: 80 name: http protocol: HTTP hosts: - \u0026#34;*\u0026#34; 这里可以看到istio-ingress-gateway的pod的标签 app=istio-ingressgateway\ntext 1 2 3 4 5 6 7 8 9 10 $ kubectl get pods -n istio-system --show-labels NAME READY STATUS RESTARTS AGE LABELS istio-ingressgateway-78b47bc88b-xqqpn 1/1 Running 0 22d app=istio-ingressgateway, chart=gateways, heritage=Tiller, install.operator.istio.io/owning-resource=unknown, istio.io/rev=default,istio=ingressgateway, operator.istio.io/component=IngressGateways, pod-template-hash=78b47bc88b, release=istio,service.istio.io/canonical-name=istio-ingressgateway, service.istio.io/canonical-revision=latest 外部服务引入配置：ServiceEntry 在Istio中提供了ServiceEntry，可将网格外的服务加入网格中，像网格内的服务一样进行管理。\n在实现上就是把外部服务加入 Istio 的服务发现，这些外部服务因为各种原因不能被直接注册到网格中。\nServiceEntry字段说明 host：与ServiceEntry关联的主机 addresses：与服务关联的虚拟IP地址。 ports： number：服务的端口。 protocol：服务公开的协议。HTTP|HTTPS|GRPC|HTTP2|MONGO|TCP| TLS之一。 targetPort：目标端口号。 location：MESH_EXTERNAL | MESH_INTERNAL，决定是网格内部还是外部。 resolution：服务发现机制。 NONE： STATIC：指定静态IP地址。 DNS：通过DNS发现。 endpoints：服务关联的端点，workloadSelector 与 endpoints 二选一。 exportTo：共享其他名称空间 subjectAltNames：如指定，将验证服务器证书的使用者备用名称是否与指定值之一匹配。 使用istio ingress gateway 配置一个网格外部的应用 部署应用程序 准备一个后端的应用\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 apiVersion: apps/v1 kind: Deployment metadata: name: httpend-deply namespace: kube-system labels: app: httpend-deply spec: replicas: 1 selector: matchLabels: app: httpend-deply template: metadata: namespace: kube-system name: httpend-deply labels: app: httpend-deply spec: containers: - name: envoy-end image: cylonchau/envoy-end imagePullPolicy: IfNotPresent livenessProbe: initialDelaySeconds: 3 # 首次探测延迟时间 periodSeconds: 2 # 定期重试 failureThreshold: 1 # 失败重试次数 httpGet: port: 90 path: ping restartPolicy: Always --- apiVersion: v1 kind: Service metadata: name: envoy-end labels: app: envoy-end namespace: kube-system spec: type: NodePort # nodeport是为了验证服务是否正常 ports: - port: 90 name: envoy-end targetPort: 90 nodePort: 30102 selector: app: httpend-deply 应用Gateway和VirtualServices\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: envoyend-gateway namespace: kube-system spec: selector: istio: ingressgateway servers: - port: number: 1090 name: http protocol: HTTP hosts: - \u0026#34;*\u0026#34; --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: envoy-end namespace: kube-system spec: hosts: - \u0026#34;*\u0026#34; gateways: - envoyend-gateway http: - match: - uri: prefix: / route: - destination: host: envoy-end port: number: 1090 应用DestinationRule\nyaml 1 2 3 4 5 6 7 8 9 10 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: envoy-end namespace: kube-system spec: host: envoy-end trafficPolicy: loadBalancer: simple: ROUND_ROBIN 应用ServiceEntry\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: envoy-end namespace: kube-system spec: hosts: - \u0026#34;envoy-end\u0026#34; ports: - number: 90 name: http protocol: HTTP location: MESH_EXTERNAL resolution: DNS ","permalink":"https://www.161616.top/istio-introduction/","summary":"在服务治理中，流量管理是一个广泛的话题，一般情况下，常用的包括：\n动态修改服务访问的负载均衡策略，比如根据某个请求特征做会话保持； 同一个服务有多版本管理，将一部分流量切到某个版本上； 对服务进行保护，例如限制并发连接数、限制请求数、隔离故障服务实例等； 动态修改服务中的内容，或者模拟一个服务运行故障等。 在Istio中实现这些服务治理功能时无须修改任何应用的代码。较之微服务的SDK方式，Istio以一种更轻便、透明的方式向用户提供了这些功能。用户可以用自己喜欢的任意语言和框架进行开发，专注于自己的业务，完全不用嵌入任何治理逻辑。只要应用运行在Istio的基础设施上，就可以使用这些治理能力。\n总结Istio流量治理的目标：以基础设施的方式提供给用户非侵入的流量治理能力，用户只需关注自己的业务逻辑开发，无须关注服务访问管理。\nistio流量治理的核心组件Pilot 在istio1.8中，istio的分为 envoy （数据平面） 、istiod （控制平面） 、addons（管理插件） 及 istioctl （命令行工具，用于安装、配置、诊断分析等操作）组成。\nPilot是Istio控制平面流量管理的核心组件，管理和配置部署在Istio服务网格中的所有Envoy代理实例。\npilot-discovery为envoy sidecar提供服务发现，用于路由及流量的管理。通过kubernetes CRD资源获取网格的配置信息将其转换为xDS接口的标准数据格式后，通过gRPC分发至相关的envoy sidecar\nPilot组件包含工作在控制平面中的 pilot-discovery 和工作与数据平面的pilot-agent 与Envoy(istio-proxy)\npilot-discovery主要完成如下功能：\n从service registry中获取服务信息 从apiserver中获取配置信息。 将服务信息与配置信息适配为xDS接口的标准数据格式，通过xDS api完成配置分发。 pilot-agent 主要完成如下功能\n基于kubernetes apiserver为envoy初始化可用的boostrap配置文件并启动envoy。\n管理监控envoy的云兄状态及配置重载。\nenvoy\n每个sidecar中的envoy是由pilot-agent基于生产的bootstrap配置进行启动，并根据指定的pilot地址，通过xDS api动态获取配置。 sidecar形式的envoy通过流量拦截机制为应用程序实现入站和出站的代理功能。 Pilot的实现 在istio中的管理策略都是基于Kubernetes CRD的实现，其中有关于流量管理的CRD资源包括 VirtualService EnvoyFilter Gateway ServiceEntry Sidecar DestinationRule WorkloadEntry WorkloadGroup。reference istio-networking-crd-resouces\nVirtualServices：用于定义路由，可以理解为envoy的 listener =\u0026gt; filter =\u0026gt; route_config\nDestinationRule：用于定义集群，可以理解为envoy 的 cluster\nGateway：用于定义作用于istio-ingress-gateway\nServiceEntry：用于定义出站的路由，作用于istio-egress-gateway\nEnvoyFilter：为envoy添加过滤器或过滤器链。\nSidecar：用于定义运行在sidecar之上的envoy配置。\nVirtual Services和 Destination Rules是Istio流量路由功能的核心组件","title":"istio流量管理：非侵入式流量治理"},{"content":"goland使用vendor作为获取依赖源 软件版本：\nsystem：windows10 1709 terminal： wsl ubuntu1804 goland：201903 goland 打开项目时使用mod模式，无法识别外部包的依赖\n根据goland官方提示，开启时，将忽略go.mod依赖描述，所以就找不到相对应的依赖，但是编译时正常的。可以看到下图中，external libraries 并没有加载外部的库导致了无法识别。\n此时想要正常使用的话，可以按照提示操作\n将 goland 改为gopath模式，执行go mod vendor 将依赖同步到vendor 。此时正常。\n当依赖更新时，可以手动添加对应的依赖库，go mod tidy 后 。因为vendor中没有新的依赖，需要手动执行下go mod vendor即可正常使用。\n使用vendor编译 在编译时，可以使用 -mod=vendor 标记，使用代码主目录文件夹下vendor目录满足依赖获取，go build -mod=vendor。此时，go build 忽略go.mod 中的依赖，（这里仅使用代码root目录下的vendor其他地方的将忽略）\nGOFLAGS=-mod=vendor 设置顶级vendor作为依赖 go env -w GOFLAGS=\u0026quot;-mod=vendor\u0026quot; 进行设置。 取消 go env -w GOFLAGS=\u0026quot;-mod=\u0026quot;\n","permalink":"https://www.161616.top/go-vendor-file-in-goland/","summary":"goland使用vendor作为获取依赖源 软件版本：\nsystem：windows10 1709 terminal： wsl ubuntu1804 goland：201903 goland 打开项目时使用mod模式，无法识别外部包的依赖\n根据goland官方提示，开启时，将忽略go.mod依赖描述，所以就找不到相对应的依赖，但是编译时正常的。可以看到下图中，external libraries 并没有加载外部的库导致了无法识别。\n此时想要正常使用的话，可以按照提示操作\n将 goland 改为gopath模式，执行go mod vendor 将依赖同步到vendor 。此时正常。\n当依赖更新时，可以手动添加对应的依赖库，go mod tidy 后 。因为vendor中没有新的依赖，需要手动执行下go mod vendor即可正常使用。\n使用vendor编译 在编译时，可以使用 -mod=vendor 标记，使用代码主目录文件夹下vendor目录满足依赖获取，go build -mod=vendor。此时，go build 忽略go.mod 中的依赖，（这里仅使用代码root目录下的vendor其他地方的将忽略）\nGOFLAGS=-mod=vendor 设置顶级vendor作为依赖 go env -w GOFLAGS=\u0026quot;-mod=vendor\u0026quot; 进行设置。 取消 go env -w GOFLAGS=\u0026quot;-mod=\u0026quot;","title":"goland在mod模式下不从vendor文件夹查找依赖"},{"content":"用过海信双面屏或者eink手机的朋友都知道，海信手机就是死活安装不了谷歌全家桶，因为海信的领导说跟谷歌有协议不能安装谷歌框架（还说后期google审核坚决不给安装，人家其他ov mui都可以安装）。不信的朋友可以去海信论坛求证，杠精走开。\n海信手机没有安装GSM Google Mobile Service 也没有 youtube，gmail，google map。在国外的朋友们用起来很难受，别说打游戏了，就日常出行也离不开google service，也是找了很久找到一个国外大神对海信A7 Pro下安装的教程，尝试在A6l也可以装，后面 Hisense A5PRO/CC Hisense A7/CC A6/A6L A2/A2Pro 其实都是通用的。\n不过这个大神的教程并不是root来安装，对于在保的小伙伴们还是依然可以享受保修，系统升级（虽然海信基本百年不更新的），现在开始介绍下如何让海信eink系列获得GMS\n安装步骤 下载 adb 关闭一堆系統內建的 垃圾 Apps 的功能(可以不关闭看自己了) 下载 Aurora Store (这步骤完全没用上，看个人了，国外大神推荐要下载) 先依照順序 安裝 4个基础服务 apk，安装完成后可以正常登陆google账号了 再 按照顺序 安裝 3个 其他服务 apk（可以不按照顺序，国外大神说的是需要按照顺序） 前置步驟：开启开发者模式 打开 开发者模式\n步骤1：下载安装 adb 程序 Mac可以直接输入命令：brew install android-platform-tools 具体 brew 是啥自行百度\nWindows 平台参考：\n先从这里下载 adb，然后下一步，下一步就行，到安装完成。\n安装好后启动 adb，这里只介绍几个命令，对于装个 GAPPS 已经足够了。\n查看设备：adb devices 看到xxxxxx device即表示连接成功 查看手机IP: adb shell ifconfig wlan0 通过IP地址连接手机：adb connect \u0026lt;device-ip-address\u0026gt; 断开连接：adb disconnect \u0026lt;device-ip-address\u0026gt; 设备监听：adb tcpip 5555 这里差不多了，更多可以参考下这里，下面开始介绍如何连接手机\n首先打开开发者模式，用数据线连接电脑\n看到有设备的即使连接成功\n让设备在 5555 端口监听 TCP/IP 连接：\n这里需要手机和电脑处于一个网络中，没有的话，可以用手机分享个热点，或者电脑分享个热点手机连上即可。\ntext 1 adb tcpip 5555 断开 USB 连接。\n通过ip连接上就可以断开了\n找到设备的 IP 地址。\n一般能在「设置」-「关于手机」-「状态信息」-「IP地址」找到，也可以使用命令查看\nadb shell ifconfig wlan0\ntext 1 2 3 4 5 6 7 8 9 10 11 adb shell ifconfig wlan0 # 下面的inet addr就是IP地址 wlan0 Link encap:Ethernet HWaddr xx:xx:xx:xx:xx:xx inet addr:172.30.96.xx Bcast:172.30.111.xx Mask:255.255.240.xx inet6 addr: xx::xx:xx:xx:xx/xx Scope: Link UP BROADCAST RUNNING MULTICAST MTU:xxx Metric:1 RX packets:xxx errors:0 dropped:xxx overruns:0 frame:0 TX packets:xxx errors:xx dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:xxx RX bytes:xxx TX bytes:xxx 通过 IP 地址连接设备。\ntext 1 2 3 adb connect \u0026lt;device-ip-address\u0026gt; # \u0026lt;device-ip-address\u0026gt; 就是上一步中找到的设备 IP 地址。 # 确认连接状态。 adb devices # \u0026lt;device-ip-address\u0026gt;:5555 device说明连接成功。 到这一步已经完成连接了。\n步骤2：删除海信手机里的垃圾软件（可选） 这里要么一个个复制执行，可以写个cmd文件 复制进去就行 扩展名 .cmd 执行文件\n这里其实关闭这些垃圾app可以减少很多内存，我全都关掉后，基本上比平时多400-500M的内存，可以替换为自己喜欢的第三方app\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 adb shell pm disable-user --user 0 com.android.hplayer # 视频播放器 adb shell pm disable-user --user 0 com.android.browser # 默认的浏览器 adb shell pm disable-user --user 0 com.android.calendar # 日历 adb shell pm disable-user --user 0 com.android.firewall adb shell pm disable-user --user 0 com.android.providers.downloads.ui # 下载 adb shell pm disable-user --user 0 com.android.sos # sos其实没用 adb shell pm disable-user --user 0 com.hmct.account # 海信账号 adb shell pm disable-user --user 0 com.hmct.antivirus adb shell pm disable-user --user 0 com.hmct.assist #助手 adb shell pm disable-user --user 0 com.hmct.einklauncher.plugin.wechat # 墨水屏后部分的微信 adb shell pm disable-user --user 0 com.hmct.imageedit # 图片编辑 adb shell pm disable-user --user 0 com.hmct.mobileclear # 手机清理 adb shell pm disable-user --user 0 com.hmct.questionnaire # 海信售后 adb shell pm disable-user --user 0 com.hmct.theme #主题商城 adb shell pm disable-user --user 0 com.hmct.voiceassist # 语音助手 adb shell pm disable-user --user 0 com.hmct.voicetranslate # 语音转换 adb shell pm disable-user --user 0 com.hmct.music # 音乐 adb shell pm disable-user --user 0 com.hmct.hmctmanual # 海信的手册 adb shell pm disable-user --user 0 com.hmct.userexperienceprogram adb shell pm disable-user --user 0 com.tencent.soter.soterserver adb shell pm disable-user --user 0 org.hapjs.mockup adb shell pm disable-user --user 0 com.hmct.jdreader adb shell pm disable-user --user 0 com.tencent.android.location adb shell pm disable-user --user 0 com.hmct.hiphone.juplugin adb shell pm disable-user --user 0 com.hmct.ftmode adb shell pm disable-user --user 0 com.hmct.semantic.analysis adb shell pm disable-user --user 0 com.android.hmctconsumerservice #手机服务 adb shell pm disable-user --user 0 com.android.mobilemanager #手机管家 adb shell pm disable-user --user 0 com.android.hmctconsumerservice # 服务网点名录 adb shell pm disable-user --user 0 com.hmct.gamebox # 游戏盒子 adb shell pm disable-user --user 0 com.hmct.hiphone.juplugin # 咨询推荐 步骤3：下载极光商店和Lawnchair （可选） 手动下载也行，在手机上下也行，download from APKMirror\n使用以下命令通过ADB设置为默认lawnchair启动程序：\ntext 1 2 adb shell cmd package set-home-activity \u0026#34;ch.deletescape.lawnchair.plah\u0026#34; # 完成后重启手机，我是没装这步骤 步骤4 安装GAPPS 首先下载所需的apk，这里直接贴上大神给的下载地址理论上 A7Pro/CC 可以直接用\n这是大佬的：\nhuawei-p40 huawei 这是我打包的包含A6l：HISENSE\n再次更新：阿里网盘也被限制了，直接CSDN吧：下载地址：HISENSE\n如果怕也可以自行下载，在搜索对应的软件名称www.apkmirror.com，看好版本号，如：Requires newer sdk version #29 (current version is #28)，这种就是属于安卓版本对不上。\n这种就属于安卓版本和软件版本不一致，如A6l是9 A7Pro是10，不能互通。 问题可以参考这里：https://github.com/rom1v/sndcpy/issues/37\ntext 1 2 3 adb: failed to install sndcpy.apk: Failure [INSTALL_FAILED_OLDER_SDK: Failed parse during installPackageLI: /data/app/vmdl1606743385.tmp/base.apk (at Binary XML file line #7): Requires newer sdk version #29 (current version is #28)] 下面按照顺序依次安装完即可（也可以不按照顺序）\n001-Google Play services-com.google.android.gms-1…apk 002-Google_Account_Manager.apk 003-Google Play Store.apk 004-com.google.androi….apk （到这里不能正常使用google play store） 005-Google Services Framework-com.google.android.gsf-29-v10.apk 下面两个不用安装，安装出错 006-modagain1gsm.apk （安装新版01后无法安装后两个） 007-com.google.android.gms2.apk （安装新版01后无法安装后两个） 安装命令： 替换\u0026lt;package_name\u0026gt; 为上述的包名，注意路径。\ntext 1 adb install \u0026lt;package_name\u0026gt; 安装完成后就可以正常使用google全家桶了。再也不怕出国不能打车了。\nReference Install the GAPPS on HISENSE A7 / A7CC / A5PRO / A5PRO CC\nInstall and use ADB on HISENSE A5PRO / A5PRO CC\n","permalink":"https://www.161616.top/hisense-a6l-gms-install/","summary":"用过海信双面屏或者eink手机的朋友都知道，海信手机就是死活安装不了谷歌全家桶，因为海信的领导说跟谷歌有协议不能安装谷歌框架（还说后期google审核坚决不给安装，人家其他ov mui都可以安装）。不信的朋友可以去海信论坛求证，杠精走开。\n海信手机没有安装GSM Google Mobile Service 也没有 youtube，gmail，google map。在国外的朋友们用起来很难受，别说打游戏了，就日常出行也离不开google service，也是找了很久找到一个国外大神对海信A7 Pro下安装的教程，尝试在A6l也可以装，后面 Hisense A5PRO/CC Hisense A7/CC A6/A6L A2/A2Pro 其实都是通用的。\n不过这个大神的教程并不是root来安装，对于在保的小伙伴们还是依然可以享受保修，系统升级（虽然海信基本百年不更新的），现在开始介绍下如何让海信eink系列获得GMS\n安装步骤 下载 adb 关闭一堆系統內建的 垃圾 Apps 的功能(可以不关闭看自己了) 下载 Aurora Store (这步骤完全没用上，看个人了，国外大神推荐要下载) 先依照順序 安裝 4个基础服务 apk，安装完成后可以正常登陆google账号了 再 按照顺序 安裝 3个 其他服务 apk（可以不按照顺序，国外大神说的是需要按照顺序） 前置步驟：开启开发者模式 打开 开发者模式\n步骤1：下载安装 adb 程序 Mac可以直接输入命令：brew install android-platform-tools 具体 brew 是啥自行百度\nWindows 平台参考：\n先从这里下载 adb，然后下一步，下一步就行，到安装完成。\n安装好后启动 adb，这里只介绍几个命令，对于装个 GAPPS 已经足够了。\n查看设备：adb devices 看到xxxxxx device即表示连接成功 查看手机IP: adb shell ifconfig wlan0 通过IP地址连接手机：adb connect \u0026lt;device-ip-address\u0026gt; 断开连接：adb disconnect \u0026lt;device-ip-address\u0026gt; 设备监听：adb tcpip 5555 这里差不多了，更多可以参考下这里，下面开始介绍如何连接手机","title":"海信A6/A6L A7Pro/CC A5PRO/A5PRO CC  安装gms google service指南"},{"content":"　有一种更优雅的方法可以解决systemd输出到指定文件而非/var/log/message，需要使用systemd参数与rsyslog过滤器。并指示syslog过滤器按程序名称拆分其输出。\nsystemd所需参数为 SyslogIdentifier：required，设置日志标识符(发送日志消息时加在行首的字符串)(\u0026ldquo;syslog tag\u0026rdquo;)。 默认值是进程的名称。此选项仅在 StandardOutput= 或 StandardError= 的值包含 journal(+console), syslog(+console), kmsg(+console) 之一时才有意义， 并且仅适用于输出到标准输出或标准错误的日志消息。 StandardOutput：required，设置进程的标准输出(STDOUT)。 可设为 inherit, null, tty, journal, syslog, kmsg, journal+console, syslog+console, kmsg+console, file:path, append:path, socket, fd:name 之一。 StandardError：设置进程的标准错误(STDERR)。 取值范围及含义与 StandardOutput= 相同。但有如下例外： (1) inherit 表示使用 StandardOutput= 的值。 (2) fd:name 的默认文件描述符名称为 \u0026ldquo;stderr\u0026rdquo; rsyslog过滤器设置 使用rsyslog条件选择器。如果不改变rsyslog目前工作模式，按照如下操作：\n新建/etc/rsyslog.d/xx.conf文件。\n在新建文件内写入内容如下\n单一条件处理。\ntext 1 2 3 if $programname == \u0026#39;programname\u0026#39; then /var/log/programname.log # 停止往其他文件内写入，如果不加此句，会继续往/var/log/message写入。 if $programname == \u0026#39;programname\u0026#39; then stop 多条件处理\n会根据不同应用名称将不同的输出日志重定向到不同的文件内。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 if ($programname == \u0026#39;apiserver\u0026#39;) then { action(type=\u0026#34;omfile\u0026#34; file=\u0026#34;/var/log/apiserver.log\u0026#34;) stop } else if ($programname == \u0026#39;scheduler\u0026#39;) then { action(type=\u0026#34;omfile\u0026#34; file=\u0026#34;/var/log/scheduler.log\u0026#34;) stop } else if ($programname == \u0026#39;controller-manager\u0026#39;) then { action(type=\u0026#34;omfile\u0026#34; file=\u0026#34;/var/log/controller-manager.log\u0026#34;) stop } else if ($programname == \u0026#39;etcd\u0026#39;) then { action(type=\u0026#34;omfile\u0026#34; file=\u0026#34;/var/log/etcd.log\u0026#34;) stop } 检查语法是否正确 rsyslogd -N1 -f file_name.conf\n重新启动rsyslog\n完成以上步骤后，应用的 stdout stderr被重定向到对应的日志文件内了，而非/var/log/message，并且仍然可以通过通过journalctl获得对应的stdout stderr （systemd参数机制）。\nreference\nsystemd\nrsyslog-conditional\nrsyslog\n","permalink":"https://www.161616.top/systemd-output/","summary":"有一种更优雅的方法可以解决systemd输出到指定文件而非/var/log/message，需要使用systemd参数与rsyslog过滤器。并指示syslog过滤器按程序名称拆分其输出。\nsystemd所需参数为 SyslogIdentifier：required，设置日志标识符(发送日志消息时加在行首的字符串)(\u0026ldquo;syslog tag\u0026rdquo;)。 默认值是进程的名称。此选项仅在 StandardOutput= 或 StandardError= 的值包含 journal(+console), syslog(+console), kmsg(+console) 之一时才有意义， 并且仅适用于输出到标准输出或标准错误的日志消息。 StandardOutput：required，设置进程的标准输出(STDOUT)。 可设为 inherit, null, tty, journal, syslog, kmsg, journal+console, syslog+console, kmsg+console, file:path, append:path, socket, fd:name 之一。 StandardError：设置进程的标准错误(STDERR)。 取值范围及含义与 StandardOutput= 相同。但有如下例外： (1) inherit 表示使用 StandardOutput= 的值。 (2) fd:name 的默认文件描述符名称为 \u0026ldquo;stderr\u0026rdquo; rsyslog过滤器设置 使用rsyslog条件选择器。如果不改变rsyslog目前工作模式，按照如下操作：\n新建/etc/rsyslog.d/xx.conf文件。\n在新建文件内写入内容如下\n单一条件处理。\ntext 1 2 3 if $programname == \u0026#39;programname\u0026#39; then /var/log/programname.log # 停止往其他文件内写入，如果不加此句，会继续往/var/log/message写入。 if $programname == \u0026#39;programname\u0026#39; then stop 多条件处理\n会根据不同应用名称将不同的输出日志重定向到不同的文件内。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 if ($programname == \u0026#39;apiserver\u0026#39;) then { action(type=\u0026#34;omfile\u0026#34; file=\u0026#34;/var/log/apiserver.","title":"如何将systemd服务的输出重定向到指定文件"},{"content":"Tag command describe git tag 列出所有tag git tag -l v1.* 列出符合条件的tag（筛选作用） git tag [tag_name] 创建轻量tag（无-m标注信息） git push REMOTE TAG 推送一个tag到远端 git push origin \u0026ndash;tags* 推送所有本地tag到远程 git push origin :refs/tags/[REMOTE TAG]\ngit push \u0026ndash;delete REMOTE TAG 删除远程指定tag git fetch origin [remote_tag_name] 拉取远程指定tag git show [tag_name] 显示指定tag详细信息 git push origin [local_tag_name] 推送指定本地tag到远程 git tag NEW_TAG OLD_TAG\ngit tag -d OLD_TAG 重命名本地tag git tag -d [local_tag_name] 删除本地指定tag git ls-remote \u0026ndash;tags origin 查询远程tags git tag -a [tag_name] 创建含注解的tag git fetch origin [remote_tag_name]git checkout [remote_tag_name] git branch checkout远端tag到本地 Checking 检查工作目录与暂存区的状态\ncommand describe git status 查看在你上次提交之后是否有对文件进行再次修改 git status -s 获得简短的输出结果 git diff 用于比较文件间差异 git diff \u0026ndash;cached git diff \u0026ndash;staged 显示暂存区(已add但未commit文件)和最后一次commit(HEAD)之间的所有不相同文件的增删改 git diff HEAD 工作目录(已track但未add文件)和暂存区(已add但未commit文件)与最后一次commit之间的的所有不相同文件的增删改 git diff \u0026lt;branch1\u0026gt; \u0026lt;branch2\u0026gt; 比较两个分支上最后 commit 的内容的差别 Log command describe git diff branch1 branch2 \u0026ndash;stat 显示出所有有差异的文件（无内容） git log \u0026lt;b1\u0026gt;..\u0026lt;b2\u0026gt; 查看 b1 中的 log 比 b2 中的 log 多提交了哪些内容 git log \u0026ndash;oneline 以 \u0026lt;commit_id\u0026gt; \u0026lt;comment\u0026gt; 为一行来显示 git log -S \u0026lt;\u0026lsquo;LoginViewController\u0026rsquo;\u0026gt; log 的输出将添加火删除对应字符串 git log \u0026ndash;all \u0026ndash;grep=\u0026lt;\u0026lsquo;0117\u0026rsquo;\u0026gt; log 的输出将过滤出与对应字符串相关的commit Checkout checkout 是指不同tag或分支间的切换行为\ncommand describe git checkout TAG 切换至一个tag git checkout -b BRANCH TAG 创建一个新分支，并切换至这个tag git checkout BRANCH 切换至一个分支 git checkout -m BRANCH 在切换分支时如有冲突则合并 git checkout COMMIT_HASH 切换至一个commit git checkout -b BRANCH HEAD~4 git checkout -b BRANCH COMMIT_HASH 切换并创建为新分支 git checkout COMMIT \u0026ndash; FILE_PATH 将 FILE_PATH 指定的文件恢复为当前分支的最新版本（仅未add） Remote remote 子命令用于管理repositories\ncommand describe git remote List all remote git remote rename OLD_REMOTE NEW_REMOTE Rename remote git remote prune REMOTE Remove stale remote tracking branches Branch branch 用于管理分支\ncommand describe git branch List all branches git checkout -b BRANCH Create the branch on your local machine and switch in this branch git branch BRANCH COMMIT_HASH Create branch from commit git push REMOTE BRANCH Push the branch to remote git branch -m OLD_BRANCH NEW_BRANCH Rename other branch git branch -m NEW_BRANCH Rename current branch # Rename branch locally\ngit branch -m OLD_BRANCH NEW_BRANCH # Delete the old branch\ngit push origin :OLD_BRANCH\n# Push the new branch, set local branch to track the new remote\ngit push \u0026ndash;set-upstream REMOTE NEW_BRANCH Rename remote branch git branch -D BRANCH\ngit push REMOTE :BRANCH Delete a branch locally and remote git branch | grep -v \u0026ldquo;master\u0026rdquo; | xargs git branch -D Delete all local branches but master Commit Record changes to the repository\ncommand describe git reset \u0026ndash;hard HEAD~1 Undo last commit git rebase -i HEAD~5\ngit reset \u0026ndash;soft HEAD~5\ngit add .\ngit commit -m \u0026ldquo;Update\u0026rdquo;\ngit push -f origin master Squash last n commits into one commit git branch newbranch\n# Go back 3 commits. You will lose uncommitted work.1\ngit reset \u0026ndash;hard HEAD~3\ngit checkout newbranch Move last commits into new branch: git rebase -i HEAD^^^\ngit add .\ngit rebase \u0026ndash;continue Make changes to older commit Merge Join two or more development histories together\ncommand describe git checkout BRANCH\ngit merge \u0026ndash;no-ff BASE_BRANCH Merge commits from master into feature branch git merge BRANCH Current branch merge to BRANCH branch git merge -m “Merge from Dev” When merge branch add comment git merge -s ours obsolete Merge obsolete branch to current branch using ours policy git merge \u0026ndash;no-commit maint Merge maint branch to current, than do not make new comment Cherry Pick Apply the changes introduced by some existing commits\nbash 1 git cherry-pick COMMIT_HASH_A COMMIT_HASH_B Revert command describe git revert HEAD Revert the previous commit git revert \u0026ndash;no-commit HEAD~3.. Revert the changes from previous 3 commits without making commit Amend command describe git commit \u0026ndash;amend Amend previous commit git commit \u0026ndash;amend \u0026ndash;no-edit git commit \u0026ndash;amend -m \u0026ldquo;COMMIT_MESSAGE\u0026rdquo; Changing git commit message git commit \u0026ndash;amend -m \u0026ldquo;COMMIT_MESSAGE\u0026rdquo;\ngit push \u0026ndash;force REMOTE BRANCH Changing git commit message after push Reflog reference log\ncommand describe git reflog Show reflog Get commit Rebase Rebase the current branch onto another branch\ncommand describe git rebase BASE_BRANCH Rebase the current branch onto another branch git rebase \u0026ndash;continue Continue rebase git rebase \u0026ndash;abort Abort rebase Tracking command describe git clean Remove untracked files git reset FILE_PATH Remove file from index git reset Reset the index to match the most recent commit git reset \u0026ndash;hard Reset the index and the working directory to match the most recent commit git rm file Remove files from the working tree and from the index git ls-files Show information about files in the index and the working tree git ls-files -d Show deleted files in the output. git ls-files -m Show modified files in the output. git ls-files -i Show only ignored files in the output. git ls-files \u0026ndash;no-empty-directory Do not list empty directories. Has no effect without \u0026ndash;directory. Config command describe git config -l list all git config git config \u0026ndash;global [key] \u0026ldquo;[value]\u0026rdquo; set global configuation git config [key] \u0026ldquo;[value]\u0026rdquo; set current repositories configuation Reference：\nawesome-git-commands\n","permalink":"https://www.161616.top/awesome-git-command/","summary":"Tag command describe git tag 列出所有tag git tag -l v1.* 列出符合条件的tag（筛选作用） git tag [tag_name] 创建轻量tag（无-m标注信息） git push REMOTE TAG 推送一个tag到远端 git push origin \u0026ndash;tags* 推送所有本地tag到远程 git push origin :refs/tags/[REMOTE TAG]\ngit push \u0026ndash;delete REMOTE TAG 删除远程指定tag git fetch origin [remote_tag_name] 拉取远程指定tag git show [tag_name] 显示指定tag详细信息 git push origin [local_tag_name] 推送指定本地tag到远程 git tag NEW_TAG OLD_TAG\ngit tag -d OLD_TAG 重命名本地tag git tag -d [local_tag_name] 删除本地指定tag git ls-remote \u0026ndash;tags origin 查询远程tags git tag -a [tag_name] 创建含注解的tag git fetch origin [remote_tag_name]git checkout [remote_tag_name] git branch checkout远端tag到本地 Checking 检查工作目录与暂存区的状态","title":"awesome git command"},{"content":"什么是WSL Windows Subsystem for Linux 简称WSL，适用于Linux的Windows子系统，可以直接在Windows上运行Linux环境（包括大部分命令行工具）\nLinux containers与Windows Subsystem for Linux（WSL）区别 此处以docker与wsl进行一些比较，主要为个人的理解之处。\ndocker与wsl同样运行在本机环境中运行，不依赖其他管理程序与虚拟化。 docker与wsl同样为应用容器。\n安装WSL 在Windows10上，用于Linux的Windows子系，可运行受支持的Linux版本（例如Ubuntu，OpenSuse，Debian等），而无需设置操作系统的复杂性。虚拟机或其他计算机。\n使用设置为Linux启用Windows子系统 打开设置 点击“应用”。 在“相关设置”部分下，单击“程序和功能”选项 单击左窗格中的“打开或关闭Windows功能”选项。 检查Windows Subsystem for Linux选项。 完成这些步骤后，将配置该环境以下载并运行Windows 10上的Linux版本。\n使用Microsoft Store安装Linux发行版 要在Windows 10上安装Linux发行版，请使用以下步骤：\n打开Microsoft Store。搜索要安装的Linux发行版。一些可用的发行版包括：\nUbuntu OpenSuse Kali Linux Debian Alpine WSL Suse Linux Enterprise 选择要在您的设备上安装的Linux发行版。 单击获取（或安装）按钮。 Microsoft Store安装Linux发行版 单击启动按钮。为Linux发行版创建一个用户名，然后按Enter键。 指定发行版的密码，然后按Enter。 重复密码，然后按Enter确认。 完成以上步骤后，即完成安装了WSL（没有图形界面），在开始菜单 运行 wsl 启动。\n离线安装WSL 官网指导手册内包含所支持的Linux离线安装包\n这里下载的为Ubuntu 18.04，下载后，文件格式为appx格式，本次使用的操作系统为，windows1709企业版，并且卸载了所有的 UWP应用。因此只能使用命令行进行安装。\n非LTSC企业版或卸载windows store的可以直接双击安装\n管理员打开Powershell 运行以下命令，将路径替换为下载的离线安装包路径。本次安装的 wsl 默认安装到C盘\npowershell 1 Add-AppxPackage .\\app_name.appx 查看已经安装的子系统\ntext 1 wslconfig /l 安装时选择其他盘安装 首先解压.appx文件\n用 LxRunOffline 安装：\nwindows10 1803以上版本下载最新版即可，windows 1709及一下，可以安装2.x版本。\n使用以下命令安装，-f后的文件为解压后文件内根目录的install.tar.gz 语法\ntext 1 LxRunOffline.exe install -n \u0026lt;install systemname\u0026gt; -d \u0026lt;save path\u0026gt; -f \u0026lt;unzip_path/install.tar.gz\u0026gt; text 1 LxRunOffline.exe install -n ubuntu -d d:\\wsl -f d:\\Ubuntu_1804.2019.522.0_x64\\install.tar.gz 等运行完成后（warning可忽略），开始 =\u0026gt; 运行wsl进入，进入后默认就是root用户。另外开始菜单不会有单独的启动的图标。\n如何在重装系统后恢复原来的WSL text 1 .\\LxRunOffline.exe rg -n ubuntu -d D:\\wsl\\ubuntu 配置wsl与windows共用开发环境 本次配置的开发环境为golang与goland，在windows下与linux下的环境开发与运行为相同的环境。其他的开发环境类似。\n因为wsl共享windows的路径，可以再windows与linux安装golang编译器。并分别设置go env\nwindows text 1 2 3 4 set GO111MODULE=on set GOPATH=D:\\go_work set GOPROXY=https://goproxy.io,https://goproxy.cn,direct set GOROOT=C:\\Go Linux，GOPATH要与windows设置为同一个路径，这样可以保证安装的包为同一个。即实现了同一个开发环境与Linux环境。\ntext 1 2 3 4 5 export GO111MODULE=on export GOPROXY=https://goproxy.io,https://goproxy.cn,direct export GOROOT=/usr/local/go export GOPATH=/mnt/d/go_work/ export PATH=$PATH:$GOROOT/bin:$GOPATH/bin goland设置 file =\u0026gt; setting =\u0026gt; Tools =\u0026gt; Terminal\ntext 1 C:\\Windows\\System32\\wsl.exe file =\u0026gt; setting =\u0026gt; Editor =\u0026gt; Code Style\ngoland wsl terminal .bashrc不生效 在wsl中发现一些环境变量、shell颜色等都不生效。这里需要了解shell的类型\nshell有两种类型，Login Shell和Non Login Shell。每一个shell都有自己自定义的脚本来预设值shell运行的环境。\nLogin Shell 当成功登陆用户后，将创建登陆shell（通过ssh sudo 或者 terminal）\n查看当前shell是什么类型的shell echo $0\nLogin Shell：-bash或-su。 Non Login Shell： bash或su Login shell 登陆后执行以下脚本：\n登陆执行/etc/profile /etc/profile执行/etc/profile.d中的所有脚本 然后执行用户 ~/.bash_profile ~/.bash_profile 会有命令执行用户目录 ~/.bashrc ~/.bashrc中会执行 /etc/bashrc\nNon Login Shell Non Login Shell是由Login Shell启动的shell。例如，登陆成功后执行bash，此时是Non Login Shell\nNon Login Shell登陆后执行以下脚本：\n首先执行 ~/.bashrc 然后 ~/.bashrc 执行 /etc/bashrc /etc/bashrc 调用 /etc/profile.d 中的脚本\n了解了执行顺序后，按照步骤查看对应问题所在，此处问题没有~/.bashrc中设置的alias和颜色。根据Login shell流程应为~/.bash_profile中去执行~/.bashrc，查看~/.bash_profile 发现文件为空。\n复制一份linux ~/.bash_profile 中的文件内容到对应的~/.bash_profile后发现功能已经正常实现。\nbash 1 2 3 4 5 6 7 8 9 10 # .bash_profile # Get the aliases and functions if [ -f ~/.bashrc ]; then . ~/.bashrc fi # User specific environment and startup programs PATH=$PATH:$HOME/bin ","permalink":"https://www.161616.top/linux-subsystem-in-win10/","summary":"什么是WSL Windows Subsystem for Linux 简称WSL，适用于Linux的Windows子系统，可以直接在Windows上运行Linux环境（包括大部分命令行工具）\nLinux containers与Windows Subsystem for Linux（WSL）区别 此处以docker与wsl进行一些比较，主要为个人的理解之处。\ndocker与wsl同样运行在本机环境中运行，不依赖其他管理程序与虚拟化。 docker与wsl同样为应用容器。\n安装WSL 在Windows10上，用于Linux的Windows子系，可运行受支持的Linux版本（例如Ubuntu，OpenSuse，Debian等），而无需设置操作系统的复杂性。虚拟机或其他计算机。\n使用设置为Linux启用Windows子系统 打开设置 点击“应用”。 在“相关设置”部分下，单击“程序和功能”选项 单击左窗格中的“打开或关闭Windows功能”选项。 检查Windows Subsystem for Linux选项。 完成这些步骤后，将配置该环境以下载并运行Windows 10上的Linux版本。\n使用Microsoft Store安装Linux发行版 要在Windows 10上安装Linux发行版，请使用以下步骤：\n打开Microsoft Store。搜索要安装的Linux发行版。一些可用的发行版包括：\nUbuntu OpenSuse Kali Linux Debian Alpine WSL Suse Linux Enterprise 选择要在您的设备上安装的Linux发行版。 单击获取（或安装）按钮。 Microsoft Store安装Linux发行版 单击启动按钮。为Linux发行版创建一个用户名，然后按Enter键。 指定发行版的密码，然后按Enter。 重复密码，然后按Enter确认。 完成以上步骤后，即完成安装了WSL（没有图形界面），在开始菜单 运行 wsl 启动。\n离线安装WSL 官网指导手册内包含所支持的Linux离线安装包\n这里下载的为Ubuntu 18.04，下载后，文件格式为appx格式，本次使用的操作系统为，windows1709企业版，并且卸载了所有的 UWP应用。因此只能使用命令行进行安装。\n非LTSC企业版或卸载windows store的可以直接双击安装\n管理员打开Powershell 运行以下命令，将路径替换为下载的离线安装包路径。本次安装的 wsl 默认安装到C盘\npowershell 1 Add-AppxPackage .\\app_name.appx 查看已经安装的子系统","title":"适用于windows10 Linux子系统的安装管理配置"},{"content":"散列函数（Hash function）又称散列算法、哈希函数，散列函数把消息或数据压缩成摘要，使得数据量变小，将数据的格式固定下来。该函数将数据打乱混合，重新创建一个叫做散列值（hash values）的指纹。这种转化是一种压缩映射，也就是散列值的空间通常远小于输入值的空间，不同的输入可能会散列成相同的输出，二不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要函数。\n散列函数性质 通过使用单向散列函数，即便是确认几百MB大小的文件的完整性，也只要对比很短的散列值就可以了。那么，单向散列函数必须具备怎样的性质呢？我们来整理一下。\n根据任意长度的消息计算出固定长度的散列值\n能够快速计算出散列值\n计算散列值所花费的时间短。尽管消息越长，计算散列值的时间也会越长，但如果不能在现实的时间内完成计算就没有意义了。\n消息不同散列值也不同\n难以发现碰撞的性质称为抗碰撞性（collisionresistance）。密码技术中所使用的单向散列函数，都需要具备抗碰撞性。强抗碰撞性，是指要找到散列值相同的两条不同的消息是非常困难的这一性质。在这里，散列值可以是任意值。密码技术中的单向散列函数必须具备强抗碰撞性。\n具备单向性\n单向散列函数必须具备单向性（one-way）。单向性指的是无法通过散列值反算出消息的性质。根据消息计算散列值可以很容易，但这条单行路是无法反过来走的。\n散列函数的应用 散列函数应用具有多样性\n安全加密：\n保护资料，散列值可用于唯一地识别机密信息。这需要散列函数是抗碰撞(collision-resistant)的，意味着很难找到产生相同散列值的资料。如数字签名、消息认证码。 数据校验：\n确保传递真实的信息：消息或数据的接受者确认消息是否被篡改的性质叫数据的真实性，也称为完整性。 错误校正：使用一个散列函数可以很直观的检测出数据在传输时发生的错误。 负载均衡：\n通过hash算法，对客户端IP进行计算hash值，将取到值与服务器数量进行取模运算。 分布式存储：如一致性hash。\n常用单项散列函数 MD4 MD5 MD5在1996年后被证实存在弱点，可以被加以破解，对于需要高度安全性的资料，专家一般建议改用其他算法，如SHA-2。2004年，证实MD5算法无法防止碰撞攻击，因此不适用于安全性认证，如SSL公开密钥认证或是数字签名等用途。\nSHA-1 SHA-2 SHA-1：1995年发布，SHA-1在许多安全协议中广为使用，包括TLS、GnuPG、SSH、S/MIME和IPsec，是MD5的后继者。但SHA-1的安全性在2010年以后已经不被大多数的加密场景所接受。2017年荷兰密码学研究小组CWI和Google正式宣布攻破了SHA-1。\nSHA-2：2001年发布，包括SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。SHA-2目前没有出现明显的弱点。虽然至今尚未出现对SHA-2有效的攻击，但它的算法跟SHA-1基本上仍然相似。 比特币使用的sha-256进行的数字签名\n算法和变体 输出散列值长度 （bits） 中继散列值长度 （bits） 资料区块长度 （bits） 最大输入消息长度 （bits） MD5 128 128 (4 × 32) 512 无限 SHA-0 160 160 (5 × 32) 512 264 − 1 SHA-1 160 160 (5 × 32) 512 264 − 1 SHA-2 SHA-224 SHA-256 224 256 256 (8 × 32) 512 SHA-384 SHA-512 SHA-512/224 SHA-512/256 384 512 224 256 512 (8 × 64) 1024 2128 − 1 Go语言中使用散列函数 Go语言使用MD5 方式一：\ngo 1 md5.Sum(\u0026#34;123\u0026#34;) 方式2:\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func getMD5_2(str []byte) string { // 1. 创建一个使用MD5校验的Hash对象` myHash := md5.New() // 2. 通过io操作将数据写入hash对象中 io.WriteString(myHash, \u0026#34;hello\u0026#34;) //io.WriteString(myHash, \u0026#34;, world\u0026#34;) myHash.Write([]byte(\u0026#34;, world\u0026#34;)) // 3. 计算结果 result := myHash.Sum(nil) fmt.Println(result) // 4. 将结果转换为16进制格式字符串 res := fmt.Sprintf(\u0026#34;%x\u0026#34;, result) fmt.Println(res) // --- 这是另外一种格式化切片的方式 res = hex.EncodeToString(result) fmt.Println(res) return res } Go语言SHA-1、SHA-2的使用 方法一：\ngo 1 2 sha512.Sum512() sha256.Sum256() 方法二与md5的使用类似\n","permalink":"https://www.161616.top/hash-function/","summary":"散列函数（Hash function）又称散列算法、哈希函数，散列函数把消息或数据压缩成摘要，使得数据量变小，将数据的格式固定下来。该函数将数据打乱混合，重新创建一个叫做散列值（hash values）的指纹。这种转化是一种压缩映射，也就是散列值的空间通常远小于输入值的空间，不同的输入可能会散列成相同的输出，二不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要函数。\n散列函数性质 通过使用单向散列函数，即便是确认几百MB大小的文件的完整性，也只要对比很短的散列值就可以了。那么，单向散列函数必须具备怎样的性质呢？我们来整理一下。\n根据任意长度的消息计算出固定长度的散列值\n能够快速计算出散列值\n计算散列值所花费的时间短。尽管消息越长，计算散列值的时间也会越长，但如果不能在现实的时间内完成计算就没有意义了。\n消息不同散列值也不同\n难以发现碰撞的性质称为抗碰撞性（collisionresistance）。密码技术中所使用的单向散列函数，都需要具备抗碰撞性。强抗碰撞性，是指要找到散列值相同的两条不同的消息是非常困难的这一性质。在这里，散列值可以是任意值。密码技术中的单向散列函数必须具备强抗碰撞性。\n具备单向性\n单向散列函数必须具备单向性（one-way）。单向性指的是无法通过散列值反算出消息的性质。根据消息计算散列值可以很容易，但这条单行路是无法反过来走的。\n散列函数的应用 散列函数应用具有多样性\n安全加密：\n保护资料，散列值可用于唯一地识别机密信息。这需要散列函数是抗碰撞(collision-resistant)的，意味着很难找到产生相同散列值的资料。如数字签名、消息认证码。 数据校验：\n确保传递真实的信息：消息或数据的接受者确认消息是否被篡改的性质叫数据的真实性，也称为完整性。 错误校正：使用一个散列函数可以很直观的检测出数据在传输时发生的错误。 负载均衡：\n通过hash算法，对客户端IP进行计算hash值，将取到值与服务器数量进行取模运算。 分布式存储：如一致性hash。\n常用单项散列函数 MD4 MD5 MD5在1996年后被证实存在弱点，可以被加以破解，对于需要高度安全性的资料，专家一般建议改用其他算法，如SHA-2。2004年，证实MD5算法无法防止碰撞攻击，因此不适用于安全性认证，如SSL公开密钥认证或是数字签名等用途。\nSHA-1 SHA-2 SHA-1：1995年发布，SHA-1在许多安全协议中广为使用，包括TLS、GnuPG、SSH、S/MIME和IPsec，是MD5的后继者。但SHA-1的安全性在2010年以后已经不被大多数的加密场景所接受。2017年荷兰密码学研究小组CWI和Google正式宣布攻破了SHA-1。\nSHA-2：2001年发布，包括SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。SHA-2目前没有出现明显的弱点。虽然至今尚未出现对SHA-2有效的攻击，但它的算法跟SHA-1基本上仍然相似。 比特币使用的sha-256进行的数字签名\n算法和变体 输出散列值长度 （bits） 中继散列值长度 （bits） 资料区块长度 （bits） 最大输入消息长度 （bits） MD5 128 128 (4 × 32) 512 无限 SHA-0 160 160 (5 × 32) 512 264 − 1 SHA-1 160 160 (5 × 32) 512 264 − 1 SHA-2 SHA-224 SHA-256 224 256 256 (8 × 32) 512 SHA-384 SHA-512 SHA-512/224 SHA-512/256 384 512 224 256 512 (8 × 64) 1024 2128 − 1 Go语言中使用散列函数 Go语言使用MD5 方式一：","title":"常用加密算法学习总结之散列函数(hash function)"},{"content":"数字签名（Digital Signature），通俗来讲是基于非对称加密算法，用秘钥对内容进行散列值签名，在对内容与签名一起发送。\n更详细的解说 更详细的解说 - 中文\n数字签名的生成个验证 签名\n⑴ 对数据进行散列值运算。 ⑵ 签名：使用签名者的私钥对数据的散列值进行加密。 ⑶ 数字签名数据：签名与原始数据。\n图：数字签名 Source：https://cheapsslsecurity.com/blog/digital-signature-vs-digital-certificate-the-difference-explained/ 验证 ⑴ 接收数据：原始数据\u0026amp;数字签名。 ⑵ 使用公钥进行解密得到散列值。 ⑶ 将原始数据的散列值与解密后的散列值进行对比。\nGo语言中使用RSA进行数字签名 ⑴ pem解码：使用pem对私钥进行解码, 得到pem.Block结构体 ⑵ 获得私钥：使用GO x509接口pem.Block据解析成私钥结构体 ⑶ 计算hash值：对明文进行散列值计算 ⑷ 使用秘钥对散列值签名\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 package main import ( \u0026#34;crypto\u0026#34; \u0026#34;crypto/rand\u0026#34; \u0026#34;crypto/rsa\u0026#34; \u0026#34;crypto/sha256\u0026#34; \u0026#34;crypto/x509\u0026#34; \u0026#34;encoding/pem\u0026#34; \u0026#34;fmt\u0026#34; ) var ( private = `-----BEGIN 私钥----- MIICXQIBAAKBgQDc73afIxqYOHg80puDIMYrqUAiTi8EiTVDEiO9YE3+VxRvN0sa pe3zx1UdhgIn3iCPUzyI2vwNADId3LjuIjkdCcdB2fHrBTbcy6u0545HnY42F9aQ 7cAr168bHcqhQoKcna9i9nukO+w7So1J9C6Wr8J4e4923q7+T7z7bZeXywIDAQAB AoGBAItX5KLdywoyo3MJCdgcNaCX8MEyOmlL+HHC4ROxx78gQN0cLJw0Bu33zHEA ch+e8z4yKz3Nj6bLdtBqw6A9qXLBCfWfD/p9YKDZNFP/6+u9teUirOgiBSq7kXWy mtBm0I3pz33EomCuSJzLj/Mj/fkKs+425jPFcZboJdZpCyBhAkEA8mtGUGYuAZwV RKBDkf1bz5EyPBGV+9CyXa6pd6md61APY0j+qhb1w9ADfHKkAzfoilhpucznRhaz kAheqMPAMwJBAOlQEx2Ytc8TxfFqhF8RPTODe2N0jBBvsvJ85k7vNiQ+hnmaAray XS6pCbZdvmGHYKlz3MVGeis/UJKDdSzE0gkCQQCoZijkNPcEmz6S+5m00oFywXRa EgVUdndRaMHEpIlVK7pkyBJQab60Fc42JxUUP0RExoI7VcHbCG4YQhgvuDvNAkBQ CUolcwebe/sBcDrsqetGyqn/WjHaSZcnnDUdiu4VzOUwveaEafeRVCeiydHPfzNn rflkK2MphtTLDhGaRAKRAkASKlhV8aTBzTty/V3XMQfFVIAdHCyEIGMdjDDSzPly shZCn66IyIze8j5Q4ZLcRz6GPglHdrkBnyt4QFuGurpl -----END 私钥-----` public = `-----BEGIN 公钥----- MIGJAoGBANzvdp8jGpg4eDzSm4MgxiupQCJOLwSJNUMSI71gTf5XFG83Sxql7fPH VR2GAifeII9TPIja/A0AMh3cuO4iOR0Jx0HZ8esFNtzLq7TnjkedjjYX1pDtwCvX rxsdyqFCgpydr2L2e6Q77DtKjUn0Lpavwnh7j3berv5PvPttl5fLAgMBAAE= -----END 公钥-----` ) func digitalSign(privateKey, plainText string) (signText []byte, err error) { var ( pemBlock, _ = pem.Decode([]byte(privateKey)) privateStream *rsa.PrivateKey plainHash = sha256.Sum256([]byte(plainText)) ) if privateStream, err = x509.ParsePKCS1PrivateKey(pemBlock.Bytes); err != nil { return } if signText, err = rsa.SignPKCS1v15(rand.Reader, privateStream, crypto.SHA256, plainHash[:]); err != nil { return } return } func digitalVerify(publicKeyByte, plainText string, signText []byte) (ok bool, err error) { var ( pemBlock, _ = pem.Decode([]byte(publicKeyByte)) publicStream *rsa.PublicKey plainHash = sha256.Sum256([]byte(plainText)) ) if publicStream, err = x509.ParsePKCS1PublicKey(pemBlock.Bytes); err != nil { return } if err = rsa.VerifyPKCS1v15(publicStream, crypto.SHA256, plainHash[:], signText); err != nil { return } return true, nil } func main() { text, err := digitalSign(private, \u0026#34;张三李四王五赵柳\u0026#34;) ok, err := digitalVerify(public, \u0026#34;张三李四王五赵柳\u0026#34;, text) fmt.Println(ok) fmt.Println(err) } 总结 在Go语言API中公钥私钥的注释头尾也需要加上\n","permalink":"https://www.161616.top/digital-signature/","summary":"数字签名（Digital Signature），通俗来讲是基于非对称加密算法，用秘钥对内容进行散列值签名，在对内容与签名一起发送。\n更详细的解说 更详细的解说 - 中文\n数字签名的生成个验证 签名\n⑴ 对数据进行散列值运算。 ⑵ 签名：使用签名者的私钥对数据的散列值进行加密。 ⑶ 数字签名数据：签名与原始数据。\n图：数字签名 Source：https://cheapsslsecurity.com/blog/digital-signature-vs-digital-certificate-the-difference-explained/ 验证 ⑴ 接收数据：原始数据\u0026amp;数字签名。 ⑵ 使用公钥进行解密得到散列值。 ⑶ 将原始数据的散列值与解密后的散列值进行对比。\nGo语言中使用RSA进行数字签名 ⑴ pem解码：使用pem对私钥进行解码, 得到pem.Block结构体 ⑵ 获得私钥：使用GO x509接口pem.Block据解析成私钥结构体 ⑶ 计算hash值：对明文进行散列值计算 ⑷ 使用秘钥对散列值签名\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 package main import ( \u0026#34;crypto\u0026#34; \u0026#34;crypto/rand\u0026#34; \u0026#34;crypto/rsa\u0026#34; \u0026#34;crypto/sha256\u0026#34; \u0026#34;crypto/x509\u0026#34; \u0026#34;encoding/pem\u0026#34; \u0026#34;fmt\u0026#34; ) var ( private = `-----BEGIN 私钥----- MIICXQIBAAKBgQDc73afIxqYOHg80puDIMYrqUAiTi8EiTVDEiO9YE3+VxRvN0sa pe3zx1UdhgIn3iCPUzyI2vwNADId3LjuIjkdCcdB2fHrBTbcy6u0545HnY42F9aQ 7cAr168bHcqhQoKcna9i9nukO+w7So1J9C6Wr8J4e4923q7+T7z7bZeXywIDAQAB AoGBAItX5KLdywoyo3MJCdgcNaCX8MEyOmlL+HHC4ROxx78gQN0cLJw0Bu33zHEA ch+e8z4yKz3Nj6bLdtBqw6A9qXLBCfWfD/p9YKDZNFP/6+u9teUirOgiBSq7kXWy mtBm0I3pz33EomCuSJzLj/Mj/fkKs+425jPFcZboJdZpCyBhAkEA8mtGUGYuAZwV RKBDkf1bz5EyPBGV+9CyXa6pd6md61APY0j+qhb1w9ADfHKkAzfoilhpucznRhaz kAheqMPAMwJBAOlQEx2Ytc8TxfFqhF8RPTODe2N0jBBvsvJ85k7vNiQ+hnmaAray XS6pCbZdvmGHYKlz3MVGeis/UJKDdSzE0gkCQQCoZijkNPcEmz6S+5m00oFywXRa EgVUdndRaMHEpIlVK7pkyBJQab60Fc42JxUUP0RExoI7VcHbCG4YQhgvuDvNAkBQ CUolcwebe/sBcDrsqetGyqn/WjHaSZcnnDUdiu4VzOUwveaEafeRVCeiydHPfzNn rflkK2MphtTLDhGaRAKRAkASKlhV8aTBzTty/V3XMQfFVIAdHCyEIGMdjDDSzPly shZCn66IyIze8j5Q4ZLcRz6GPglHdrkBnyt4QFuGurpl -----END 私钥-----` public = `-----BEGIN 公钥----- MIGJAoGBANzvdp8jGpg4eDzSm4MgxiupQCJOLwSJNUMSI71gTf5XFG83Sxql7fPH VR2GAifeII9TPIja/A0AMh3cuO4iOR0Jx0HZ8esFNtzLq7TnjkedjjYX1pDtwCvX rxsdyqFCgpydr2L2e6Q77DtKjUn0Lpavwnh7j3berv5PvPttl5fLAgMBAAE= -----END 公钥-----` ) func digitalSign(privateKey, plainText string) (signText []byte, err error) { var ( pemBlock, _ = pem.","title":"常用加密算法学习总结之数字签名"},{"content":"什么是 Operator？ Operator是由CoreOS公司开发的，用来扩展kubernetes APi的特定的应用程序控制器，Operator基于Kubernetes的资源和控制器概念之上构建，但同时又包含了对相应应用程序特定的一些专业知识。创建operator的关键是 CRD（CustomResourceDefinition）的设计。\nPrometheus Operator Prometheus Operator 是CoreOS公司提供的基于Prometheus及其相关监视组件对Kubernetes集群组件的管理，该Operator目的是简化和自动化针对Kubernetes集群的基于Prometheus的管理及配置。\nPrometheus Operator架构组件 Operator：作为Prometheus Operator的核心组件，也即是自定义的控制器，用来监视和部署管理Prometheus Operator CRD资源对象，监控并维持CRD资源状态。 Prometheus Server：Operator 根据自定义资源 Prometheus 类型中定义的内容而部署的Prometheus Cluster Prometheus Operator CRD： Prometheus：以CRD资源提供给Operator的类似于Pod资源清单定位的资源。 ServiceMonitor：声明定义对Kubernetes Services资源进行监控，使用标签选择器来选择所需配置的监控，后端是Service的Endpoint，通过Service标签选择器获取EndPoint对象。 PodMonitor：使用标签选择器，选择对匹配Pod进行监控 Alertmanager：声明定义了Alertmanager在Kubernetes中运行所提供的配置。 PrometheusRule: 声明定义了Prometheus在Kubernetes中运行所需的Rule配置。 reference\nPrometheus-Operator-design\nPrometheus Operator监控二进制kubernetes 查看兼容性列表选择对应的版本来下载，此处kubernetes集群为1.8.10 。\n对应地址为 https://github.com/prometheus-operator/kube-prometheus.git ，可以在域名后添加.cnpmjs.org 访问中国的github加速。\nbash 1 git clone https://github.com.cnpmjs.org/prometheus-operator/kube-prometheus.git 资源清单在项目目录 manifests CRD在 manifests/setup 需要先安装CRD 和 Operator 对象\nkube-controller-manager 和 kube-scheduler 无监控数据 二进制部署的Kubernetes集群中部署Prometheus Operator，会发现在prometheus server的页面上发现kube-controller和kube-schedule的target为0/0。匹配不到节点信息，这是因为serviceMonitor是根据label去选取svc的。此处svc并没有kube-controller和kube-schedule 需要手动创建。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 apiVersion: v1 kind: Service metadata: namespace: kube-system name: kube-controller-manager labels: k8s-app: kube-controller-manager component: kube-controller-manager spec: selector: k8s-app: kube-controller-manager component: kube-controller-manager ports: - name: https-metrics port: 10252 targetPort: 10252 --- apiVersion: v1 kind: Endpoints metadata: namespace: kube-system name: kube-controller-manager labels: k8s-app: kube-controller-manager component: kube-controller-manager subsets: - addresses: - ip: \u0026#34;10.0.0.5\u0026#34; nodeName: \u0026#34;master01\u0026#34; ports: - port: 10252 name: https-metrics --- apiVersion: v1 kind: Service metadata: namespace: kube-system name: kube-scheduler labels: k8s-app: kube-scheduler component: kube-scheduler spec: selector: k8s-app: kube-scheduler component: kube-scheduler ports: - name: https-metrics port: 10251 targetPort: 10251 --- apiVersion: v1 kind: Endpoints metadata: namespace: kube-system name: kube-scheduler labels: k8s-app: kube-scheduler component: kube-scheduler subsets: - addresses: - ip: \u0026#34;10.0.0.5\u0026#34; nodeName: \u0026#34;master01\u0026#34; ports: - port: 10251 name: https-metrics 此处需要注意的是：需要修改对应的Prometheus Operator资源清单的值一直才能获取到目标\nService.spec.ports.name要和ServiceMonitor.spec.endpoints.port的名称对应。\nService.metadata.namespace要和ServiceMonitor.namespaceSelector.matchNames对应\nService.metadata.labels的key要和ServiceMonitor.JobLabel对应\nService.metadata.labels 要和 ServiceMonitor.selector.matchLabels对应\n监控第三方的服务及自定义servicemonitor 一、查看 Etcd 信息 这里的etcd采用二进制方法安装，可以直接访问 host:2379/metrics 获得。\n二、将证书存入 Kubernetes 创建secret\ntext 1 2 3 4 5 kubectl create secret generic hketcd \\ --from-file=\u0026#39;/etc/etcd/pki/client.crt\u0026#39; \\ --from-file=\u0026#39;/etc/etcd/pki/client.key\u0026#39; \\ --from-file=\u0026#39;/etc/etcd/pki/ca.crt\u0026#39; \\ -n monitoring 三、将证书挂入 PrometheusServer 方法1： kubectl edit prometheus k8s -n monitoring\n方法2：修改 prometheus-prometheus.yaml 文件\n增加内容：\nyaml 1 2 3 replicas: 2 secrets: - hketcd 挂入后的证书保存在目录 /etc/prometheus/secrets/{secret_name}/ 下。\n四、创建 Etcd Service \u0026amp; Endpoints yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 apiVersion: v1 kind: Service metadata: namespace: kube-system name: etcdmaster labels: k8s-app: etcd component: etcd spec: selector: k8s-app: etcd component: etcd type: ClusterIP clusterIP: None # 设置为None，不分配Service IP ports: - name: https-metrics port: 2379 --- apiVersion: v1 kind: Endpoints metadata: namespace: kube-system name: etcdmaster labels: k8s-app: etcd component: etcd subsets: - addresses: - ip: \u0026#34;10.0.0.5\u0026#34; nodeName: \u0026#34;master01\u0026#34; ports: - port: 2379 name: https-metrics --- 五、创建 ServiceMonitor yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: etcd namespace: monitoring labels: k8s-app: etcd spec: jobLabel: k8s-app # 匹配工作的标签，这里是servicemonitor即 为service的标签 endpoints: # 此ServiceMonitor的节点 - port: https-metrics # k8s service endports的设置的端口的名称 interval: 30s scheme: https tlsConfig: serverName: hketcd # 访问etcd的名称，因为证书原因需要认证访问的名称 caFile: \u0026#34;/etc/prometheus/secrets/hketcd/ca.crt\u0026#34; # 这个是在prometheus容器内挂载的证书 certFile: \u0026#34;/etc/prometheus/secrets/hketcd/client.crt\u0026#34; keyFile: \u0026#34;/etc/prometheus/secrets/hketcd/client.key\u0026#34; insecureSkipVerify: true selector: # 选择匹配到的endpoints matchLabels: k8s-app: etcd component: etcd # 与matchExpressions: #进行后端的匹配 namespaceSelector: # 选择所在资源的名称控件 matchNames: - kube-system ","permalink":"https://www.161616.top/prometheus-operator/","summary":"什么是 Operator？ Operator是由CoreOS公司开发的，用来扩展kubernetes APi的特定的应用程序控制器，Operator基于Kubernetes的资源和控制器概念之上构建，但同时又包含了对相应应用程序特定的一些专业知识。创建operator的关键是 CRD（CustomResourceDefinition）的设计。\nPrometheus Operator Prometheus Operator 是CoreOS公司提供的基于Prometheus及其相关监视组件对Kubernetes集群组件的管理，该Operator目的是简化和自动化针对Kubernetes集群的基于Prometheus的管理及配置。\nPrometheus Operator架构组件 Operator：作为Prometheus Operator的核心组件，也即是自定义的控制器，用来监视和部署管理Prometheus Operator CRD资源对象，监控并维持CRD资源状态。 Prometheus Server：Operator 根据自定义资源 Prometheus 类型中定义的内容而部署的Prometheus Cluster Prometheus Operator CRD： Prometheus：以CRD资源提供给Operator的类似于Pod资源清单定位的资源。 ServiceMonitor：声明定义对Kubernetes Services资源进行监控，使用标签选择器来选择所需配置的监控，后端是Service的Endpoint，通过Service标签选择器获取EndPoint对象。 PodMonitor：使用标签选择器，选择对匹配Pod进行监控 Alertmanager：声明定义了Alertmanager在Kubernetes中运行所提供的配置。 PrometheusRule: 声明定义了Prometheus在Kubernetes中运行所需的Rule配置。 reference\nPrometheus-Operator-design\nPrometheus Operator监控二进制kubernetes 查看兼容性列表选择对应的版本来下载，此处kubernetes集群为1.8.10 。\n对应地址为 https://github.com/prometheus-operator/kube-prometheus.git ，可以在域名后添加.cnpmjs.org 访问中国的github加速。\nbash 1 git clone https://github.com.cnpmjs.org/prometheus-operator/kube-prometheus.git 资源清单在项目目录 manifests CRD在 manifests/setup 需要先安装CRD 和 Operator 对象\nkube-controller-manager 和 kube-scheduler 无监控数据 二进制部署的Kubernetes集群中部署Prometheus Operator，会发现在prometheus server的页面上发现kube-controller和kube-schedule的target为0/0。匹配不到节点信息，这是因为serviceMonitor是根据label去选取svc的。此处svc并没有kube-controller和kube-schedule 需要手动创建。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 apiVersion: v1 kind: Service metadata: namespace: kube-system name: kube-controller-manager labels: k8s-app: kube-controller-manager component: kube-controller-manager spec: selector: k8s-app: kube-controller-manager component: kube-controller-manager ports: - name: https-metrics port: 10252 targetPort: 10252 --- apiVersion: v1 kind: Endpoints metadata: namespace: kube-system name: kube-controller-manager labels: k8s-app: kube-controller-manager component: kube-controller-manager subsets: - addresses: - ip: \u0026#34;10.","title":"prometheus operator使用"},{"content":"公开密钥密码学（英语：Public-key cryptography）也称非对称式密码学（英语：Asymmetric cryptography）是密码学的一种演算法。常用的非对称加密算法有 RSA DSA ECC 等。公开密钥加密\n非对称加密算法使用公钥、私钥来加解密。\n公钥与私钥是成对出现的。 多个用户（终端等）使用的密钥交公钥，只有一个用户（终端等）使用的秘钥叫私钥。 使用公钥加密的数据只有对应的私钥可以解密；使用私钥加密的数据只有对应的公钥可以解密。 非对称加密通信过程 下面我们来看一看使用公钥密码的通信流程。假设Alice要给Bob发送一条消息，Alice是发送者，Bob是接收者，而这一次窃听者Eve依然能够窃所到他们之间的通信内容。 参考自维基百科\n⑴ Alice与bob事先互不认识，也没有可靠安全的沟通渠道，但Alice现在却要透过不安全的互联网向bob发送信息。 ⑵ Alice撰写好原文，原文在未加密的状态下称之为明文 plainText。 ⑶ bob使用密码学安全伪随机数生成器产生一对密钥，其中一个作为公钥 publicKey，另一个作为私钥 privateKey。 ⑷ bob可以用任何方法传送公钥publicKey 给Alice，即使在中间被窃听到也没问题。 ⑸ Alice用公钥publicKey把明文plainText进行加密，得到密文 cipherText ⑹ Alice可以用任何方法传输密文给bob，即使中间被窃听到密文也没问题。 ⑺ bob收到密文，用私钥对密文进行解密，得到明文 plainText。 由于其他人没有私钥，所以无法得知明文；如果Alice，在没有得到bob私钥的情况下，她将重新得到原文。\nRSA RSA是一种非对称加密算法，是由罗纳德·李维斯特（Ron Rivest）、阿迪·萨莫尔（Adi Shamir）和伦纳德·阿德曼（Leonard Adleman）在1977年一起提出，并以三人姓氏开头字母拼在一起组成的。\nRSA公钥和密钥的获取：随机选择两个大的素数，p q $N = p*q$ RSA加密过程：$cipherText = plainText ^ E mod N$，$(N,e)$为公钥，$(N,d)$为私钥。 RSA解密过程：$plainText = cipherText^ D mod N$\nGo语言中RSA的应用 在Go语言中生成公钥与私钥 生成秘钥流程 ⑴ 使用crypto/rsa中的GenerateKey(random io.Reader, bits int)方法生成私钥（结构体） ⑵ 因为X509证书采用了ASN1描述结构，需要通过Go语言API将的到的私钥（结构体），转换为BER编码规则的字符串。 ⑶ 需要将ASN1 BER 规则转回为PEM数据编码。pem.Encode(out io.Writer, b *Block) ⑷ 将返回的数据保存\n生成私钥 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func GeneratePrivateKey(keySize int) (privateKey bytes.Buffer, err error) { // 生成私钥 var ( privateKeyStruct *rsa.PrivateKey privateStream []byte ) privateKeyStruct, err = rsa.GenerateKey(rand.Reader, keySize) if err != nil { return } privateStream = x509.MarshalPKCS1PrivateKey(privateKeyStruct) privateBlock := pem.Block{Type: \u0026#34;私钥\u0026#34;, Bytes: privateStream} if err = pem.Encode(\u0026amp;privateKey, \u0026amp;privateBlock); err != nil { return } return } 通过私钥获取公钥 通过私钥获取公钥需要将私钥生成的步骤翻转\n⑴ 私钥[]byte解码为一个pemBlock pem.Decode() ⑵ pemBlock.Bytes是BER编码规则的字符串。将其转换为结构体 x509.ParsePKCS1PrivateKey() ⑶ 转换为的结构体的属性PublicKey为公钥结构体，需将其转换为BER编码规则的字符串。x509.MarshalPKCS1PublicKey(\u0026amp;PublicKey) ⑷ 拼接公钥pemBlock，并需要将ASN1 BER规则字符串转回为PEM数据编码。pem.Encode(out io.Writer, b *Block)\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func GetPublicKey(privateKey []byte) (publicKey bytes.Buffer, err error) { pemBlock, _ := pem.Decode(privateKey) privateStream, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes) if err != nil { return } publicStream := x509.MarshalPKCS1PublicKey(\u0026amp;privateStream.PublicKey) privateBlock := pem.Block{Type: \u0026#34;公钥\u0026#34;, Bytes: publicStream} if err = pem.Encode(\u0026amp;publicKey, \u0026amp;privateBlock); err != nil { return } return } 使用RSA密钥进行加解密 RSA加/解密步骤\n⑴ 因为在生成公钥与私钥时，进行了pem编码，需要先对其（一般情况下加密都使用公钥）进行解码为pemBlock。pem.Decode() ⑵ pemBlock.Bytes是BER编码规则的字符串。将其转换为结构体 x509.ParsePKCS1PublicKey(pemBlock.Bytes) ⑶ 使用 rsa.DecryptPKCS1v15 或 rsa.EncryptPKCS1v15 进行加解密，如：rsa.DecryptPKCS1v15(rand.Reader, public|private stream, []byte plain|cipher)，返回值即为加/解密好的数据。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func RSAEncrypt(publicKey []byte, plainText string) (cipherText []byte, err error) { pemBlock, _ := pem.Decode(publicKey) publicStream, err := x509.ParsePKCS1PublicKey(pemBlock.Bytes) if err != nil { return } if cipherText, err = rsa.EncryptPKCS1v15(rand.Reader, publicStream, []byte(plainText)); err != nil { return } return } func RSADecrypt(privateKey, cipherText []byte) (plainText []byte, err error) { pemBlock, _ := pem.Decode(privateKey) privateStream, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes) if err != nil { return } if plainText, err = rsa.DecryptPKCS1v15(rand.Reader, privateStream, []byte(cipherText)); err != nil { return } return } 总结\nGo语言接口中，明文内容的长度不能大于秘钥本身。 RSA算法加解密速度慢，不推荐对较大数据加密。 ","permalink":"https://www.161616.top/asymmetric/","summary":"公开密钥密码学（英语：Public-key cryptography）也称非对称式密码学（英语：Asymmetric cryptography）是密码学的一种演算法。常用的非对称加密算法有 RSA DSA ECC 等。公开密钥加密\n非对称加密算法使用公钥、私钥来加解密。\n公钥与私钥是成对出现的。 多个用户（终端等）使用的密钥交公钥，只有一个用户（终端等）使用的秘钥叫私钥。 使用公钥加密的数据只有对应的私钥可以解密；使用私钥加密的数据只有对应的公钥可以解密。 非对称加密通信过程 下面我们来看一看使用公钥密码的通信流程。假设Alice要给Bob发送一条消息，Alice是发送者，Bob是接收者，而这一次窃听者Eve依然能够窃所到他们之间的通信内容。 参考自维基百科\n⑴ Alice与bob事先互不认识，也没有可靠安全的沟通渠道，但Alice现在却要透过不安全的互联网向bob发送信息。 ⑵ Alice撰写好原文，原文在未加密的状态下称之为明文 plainText。 ⑶ bob使用密码学安全伪随机数生成器产生一对密钥，其中一个作为公钥 publicKey，另一个作为私钥 privateKey。 ⑷ bob可以用任何方法传送公钥publicKey 给Alice，即使在中间被窃听到也没问题。 ⑸ Alice用公钥publicKey把明文plainText进行加密，得到密文 cipherText ⑹ Alice可以用任何方法传输密文给bob，即使中间被窃听到密文也没问题。 ⑺ bob收到密文，用私钥对密文进行解密，得到明文 plainText。 由于其他人没有私钥，所以无法得知明文；如果Alice，在没有得到bob私钥的情况下，她将重新得到原文。\nRSA RSA是一种非对称加密算法，是由罗纳德·李维斯特（Ron Rivest）、阿迪·萨莫尔（Adi Shamir）和伦纳德·阿德曼（Leonard Adleman）在1977年一起提出，并以三人姓氏开头字母拼在一起组成的。\nRSA公钥和密钥的获取：随机选择两个大的素数，p q $N = p*q$ RSA加密过程：$cipherText = plainText ^ E mod N$，$(N,e)$为公钥，$(N,d)$为私钥。 RSA解密过程：$plainText = cipherText^ D mod N$\nGo语言中RSA的应用 在Go语言中生成公钥与私钥 生成秘钥流程 ⑴ 使用crypto/rsa中的GenerateKey(random io.Reader, bits int)方法生成私钥（结构体） ⑵ 因为X509证书采用了ASN1描述结构，需要通过Go语言API将的到的私钥（结构体），转换为BER编码规则的字符串。 ⑶ 需要将ASN1 BER 规则转回为PEM数据编码。pem.","title":"常用加密算法学习总结之非对称加密"},{"content":"对称加密，又称为 共享密钥加密算法，是指加密和解密方使用相同密钥的加密算法。对称加密算法的优点在于加解密的高速度和使用长密钥时的难破解性。\n对称加密算法 DES DES（Data Encryption Standard）：数据加密标准，速度较快，适用于加密大量数据的场合。1977年被美国联邦政府的国家标准局确定为联邦资料处理标准（FIPS）\nDES的加密和解密 DES是一种将64bit（8Byte）的明文加密成64bit的密文的对称密码算法，==它的密钥长度是56比特==。从规格上来说，DES的密钥长度是64bit，但由于每隔7bit会设置一个用于==错误检查==的比特，因此实质上其密钥长度是56bit。\nDES是以64bit的明文（比特序列）为一个单位来进行加密的，这个64bit的单位称为分组。一般来说，以分组为单位进行处理的密码算法称为分组密码（blockcipher），DES就是分组密码的一种。\nDES每次只能加密64比特的数据，如果要加密的明文比较长，就需要对DES加密进行迭代（反复），而迭代的具体方式就称为模式（mode）。\n3DES 3DES（Triple DES）：是三重数据加密算法（TDEA，Triple Data Encryption Algorithm）块密码的通称。是基于DES，对一块数据用三个不同的密钥进行三次加密，强度更高。\n3DES是基于计算机的运算能力的增强，基于DES算法，增强秘钥进行多绪加密，而不是一种块密码算法。\nAES AES（Advanced Encryption Standard）：高级加密标准，是美国联邦政府采用的一种区块加密标准。\n分组密码模式 **分组密码（blockcipher）**是每次只能处理特定长度的一块数据的一类密码算法，这里的一块\u0026quot;就称为分组（block）。此外，一个分组的比特数就称为分组长度（blocklength）。\n例如，DES和3DES的分组长度都是64比特。这些密码算法一次只能加密64比特的明文．并生成64比特的密文。\nAES的分组长度可以从128比特、192比特和256比特中进行选择。当选择128比特的分组长度时，AES一次可加密128比特的明文，并生成128比特的密文。\n分组密码算法只能加密固定长度的分组，但是我们需要加密的明文长度可能会超过分组密码的分组长度，这时就需要对分组密码算法进行迭代，以便将一段很长的明文全部加密。而迭代的方法就称为分组密码的模式（mode）。\n分组密码的模式有很多种类，分组密码的主要模式有以下5种：\n明文与密文分组 **明文分组: **是指分组密码算法中作为加密对象的明文。明文分组的长度与分组密码算法的分组长度是相等的。 **密文分组: **是指使用分组密码算法将明文分组加密之后所生成的密文。 ECB模式：Electronic Code Book mode（电子密码本模式） ECB是最简单的加密模式，明文消息被分成固定大小的块（分组），并且每个块被单独加密。 每个块的加密和解密都是独立的，且使用相同的方法进行加密，所以可以进行并行计算，但是这种方法一旦有一个块被破解，使用相同的方法可以解密所有的明文数据，安全性比较差。 适用于数据较少的情形，加密前需要把明文数据填充到块大小的整倍数。\n使用ECB模式加密时，相同的明文分组会被转换为相同的密文分组，因此ECB模式也称为电子密码本模式当最后一个明文分组的内容小于分组长度时（如一个分组8bit），需要用一特定的数据进行填充（padding），让值一个分组长度等于分组长度。\nECB模式是所有模式中最简单的一种。ECB模式中，明文分组与密文分组是一一对应的关系，因此，如果明文中存在多个相同的明文分组，则这些明文分组最终都将被转换为相同的密文分组。这样一来，只要观察一下密文，就可以知道明文中存在怎样的重复组合，并可以以此为线索来破译密码，因此ECB模式是存在一定风险的。\nCBC模式：Cipher Block Chaining mode（密码分组链接/密码块 模式） 1976年，IBM发明了密码分组链接CBC。CBC模式中每一个分组要先和前一个分组加密后的数据进行XOR异或操作，然后再进行加密。 这样每个密文块依赖该块之前的所有明文块，为了保持每条消息都具有唯一性，在第一个块进行加密之前需要用初始化向量 IV 进行异或操作。 CBC模式是一种最常用的加密模式，它主要缺点是加密是连续的，不能并行处理，并且与ECB一样消息块必须填充到块大小的整倍数。\n**当加密第一个明文分组时，由于不存在 “前一个密文分组\u0026quot;，因此需要事先准备一个长度为一个分组的比特序列来代替“前一个密文分组\u0026quot;，这个比特序列称为初始化向量（initialization vector）**通常缩写为 IV。一般来说，每次加密时都会随机产生一个不同的比特序列来作为初始化向量。\nCFB模式：Cipher FeedBack mode（密文反馈模式） 密文反馈模式 CFB；在CFB模式中，前一个分组的密文加密后和当前分组的明文XOR异或操作生成当前分组的密文。所谓反馈，这里指的就是返回输入端的意思，即前一个密文分组会被送回到密码算法的输入端。\n在ECB和CBC中，明文分组都是通过密码算法进行加密的，然而，在CFB模式中，明文分组和密文分组之间并没有经过\u0026quot;加密\u0026quot;这一步骤，明文分和密文分组之间只有一个XOR。\nOFB模式：Output FeedBack mode（输出反馈模式） 输出反馈模式, OFB。在OFB模式中，上一个分组密码算法的输出是当前分组密码算法的输入（下图）\nCTR模式：CounTeR mode（计数器模式） CTR是一种通过将逐次累加的计数器进行加密来生成密钥流的流密码；即每个分组对应一个逐次累加的计数器，并通过对计数器进行加密来生成密钥流。也就是说，最终的密文分组是通过将计数器加密得到的比特序列，与明文分组进行XOR而得到的。\nCTR模式的特点\nCTR模式的加密和解密使用了完全相同的结构，因此在程序实现上比较容易。这一特点和同为流密码的OFB模式是一样的。 CTR模式中可以以任意顺序对分组进行加密和解密，因此在加密和解密时需要用到的“计数器\u0026quot;的值可以由nonce和分组序号直接计算出来。这一性质是OFB模式所不具备的。 CTR模式能够以任意顺序处理分组，就意味着能够实现并行计算。在支持并行计算的系统中，CTR模式的速度是非常快的。\n总结\n初始化向量 - IV\necb, ctr模式不需要初始化向量 cbc, ofc, cfb需要初始化向量 最后一个明文分组的填充\n使用cbc, ecb需要填充 明文分组中进行了填充, 然后加密 解密密文得到明文, 需要把填充的字节删除 使用 ofb, cfb, ctr不需要填充 对称加密在Go语言中的实现方式 CBC分组模式 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 /* * @brief DES加密函数， * @param1 加密的明文 * @param2 秘钥 * @return，得到的密文 */ func DesEncrypt(plainText, key string) ([]byte, error) { var ( // 创建一个des加密的接口 block, err = des.NewCipher([]byte(key)) // 分组加密 需要对最后进行填充 padText = LastPadding([]byte(plainText), block.BlockSize()) cipherText = make([]byte, len(padText)) ) if err != nil { return nil, err } // 创建使用cbc分组模式加密接口 mode := cipher.NewCBCEncrypter(block, []byte(\u0026#34;12345678\u0026#34;)) // 加密 mode.CryptBlocks(cipherText, padText) return cipherText, nil } /* * @brief DES解密函数， * @param1 加密的明文 * @param2 秘钥 * @return，得到的密文 */ func DesDecrypt(cipherText, key string) ([]byte, error) { var ( // 创建一个des加密的接口 block, err = des.NewCipher([]byte(key)) // 创建使用cbc分组模式解密接口 mode = cipher.NewCBCDecrypter(block, []byte(\u0026#34;12345678\u0026#34;)) byteCipherText = []byte(cipherText) // 明文存储变量 plainText = make([]byte, len(byteCipherText)) ) if err != nil { return nil, err } // 解密，无返回值 mode.CryptBlocks(plainText, byteCipherText) // 将填充的内容删除 return LastUnPadding(plainText, des.BlockSize), nil } 总结\nDES使用64bit钥对数据块进行加密 在Go语言中iv的长须需要与密钥对长度一致。 CBC使用的流密码算法 CBC需要对最后明文分组填充 OFB分组模式 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 func OFBEncrypt(plainText, key string) ([]byte, error) { var ( // 创建一个des加密的接口 block, err = des.NewCipher([]byte(key)) // 分组加密 需要对最后进行填充 cipherText = make([]byte, len(plainText)) ) if err != nil { return nil, err } // 创建使用cbc分组模式加密接口 mode := cipher.NewOFB(block, []byte(\u0026#34;12345678\u0026#34;)) //mode := cipher.NewCBCEncrypter(block, []byte(\u0026#34;12345678\u0026#34;)) // 加密 mode.XORKeyStream(cipherText, []byte(plainText)) return cipherText, nil } func OFBDecrypt(cipherText, key string) ([]byte, error) { var ( // 创建一个des加密的接口 block, err = des.NewCipher([]byte(key)) // 创建使用cbc分组模式解密接口 mode = cipher.NewOFB(block, []byte(\u0026#34;12345678\u0026#34;)) byteCipherText = []byte(cipherText) // 明文存储变量 plainText = make([]byte, len(byteCipherText)) ) if err != nil { return nil, err } // 解密，无返回值 mode.XORKeyStream(plainText, byteCipherText) // 将填充的内容删除 return LastUnPadding(plainText, des.BlockSize), nil } 填充方式 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 /* * 填充函数，如果最后一个分组字节数不够则填充，填充的字节数为缺少的字节数 * 如果最后一个字节数正好的话，则新建一个分组 */ func LastPadding(plainText []byte, blockSize int) []byte { var ( // 获得明文的长度，以判断时候需要补充 paddingLength = blockSize - len(plainText)%blockSize // 初始化填充的内容 padText = bytes.Repeat([]byte{byte(paddingLength)}, paddingLength) ) //将填充的内容追加到明文后 return append(plainText, padText...) } /* * 删除填充函数，如果最后一个分组字节数不够则填充，填充的字节数为缺少的字节数 * 如果最后一个字节数正好的话，则新建一个分组 */ func LastUnPadding(plainText []byte, blockSize int) []byte { var ( // 获得明文的长度，以判断时候需要补充 paddingLength = len(plainText) // 获得尾部填充的字节数量 lastChar = int(plainText[paddingLength-1]) ) return bytes.TrimFunc(plainText, func(r rune) bool { return r == rune(lastChar) }) } 总结\nofb不需要最后为明文分组填充 加密算法Go语言API已经提供，但算法的分组业务流程需要自己实现 AES go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func AESEncrypt(cipherText, key string) ([]byte, error) { var ( // 创建一个AES加密的接口 block, err = aes.NewCipher([]byte(key)) byteCipherText = []byte(cipherText) // 明文存储变量 plainText = make([]byte, len(byteCipherText)) ) if err != nil { return nil, err } // 创建使用cbc分组模式解密接口 mode := cipher.NewOFB(block, []byte(\u0026#34;1234567812345678\u0026#34;)) // 解密，无返回值 mode.XORKeyStream(plainText, byteCipherText) // 将填充的内容删除 return LastUnPadding(plainText, aes.BlockSize), nil } 总结\nAES秘钥为 16,24,32 Byte 即 128,196,256 bit 在无需明文填充的分组模式下，ofb cfb ctr，加密解密的业务逻辑处理是一样的。 ","permalink":"https://www.161616.top/symmetric/","summary":"对称加密，又称为 共享密钥加密算法，是指加密和解密方使用相同密钥的加密算法。对称加密算法的优点在于加解密的高速度和使用长密钥时的难破解性。\n对称加密算法 DES DES（Data Encryption Standard）：数据加密标准，速度较快，适用于加密大量数据的场合。1977年被美国联邦政府的国家标准局确定为联邦资料处理标准（FIPS）\nDES的加密和解密 DES是一种将64bit（8Byte）的明文加密成64bit的密文的对称密码算法，==它的密钥长度是56比特==。从规格上来说，DES的密钥长度是64bit，但由于每隔7bit会设置一个用于==错误检查==的比特，因此实质上其密钥长度是56bit。\nDES是以64bit的明文（比特序列）为一个单位来进行加密的，这个64bit的单位称为分组。一般来说，以分组为单位进行处理的密码算法称为分组密码（blockcipher），DES就是分组密码的一种。\nDES每次只能加密64比特的数据，如果要加密的明文比较长，就需要对DES加密进行迭代（反复），而迭代的具体方式就称为模式（mode）。\n3DES 3DES（Triple DES）：是三重数据加密算法（TDEA，Triple Data Encryption Algorithm）块密码的通称。是基于DES，对一块数据用三个不同的密钥进行三次加密，强度更高。\n3DES是基于计算机的运算能力的增强，基于DES算法，增强秘钥进行多绪加密，而不是一种块密码算法。\nAES AES（Advanced Encryption Standard）：高级加密标准，是美国联邦政府采用的一种区块加密标准。\n分组密码模式 **分组密码（blockcipher）**是每次只能处理特定长度的一块数据的一类密码算法，这里的一块\u0026quot;就称为分组（block）。此外，一个分组的比特数就称为分组长度（blocklength）。\n例如，DES和3DES的分组长度都是64比特。这些密码算法一次只能加密64比特的明文．并生成64比特的密文。\nAES的分组长度可以从128比特、192比特和256比特中进行选择。当选择128比特的分组长度时，AES一次可加密128比特的明文，并生成128比特的密文。\n分组密码算法只能加密固定长度的分组，但是我们需要加密的明文长度可能会超过分组密码的分组长度，这时就需要对分组密码算法进行迭代，以便将一段很长的明文全部加密。而迭代的方法就称为分组密码的模式（mode）。\n分组密码的模式有很多种类，分组密码的主要模式有以下5种：\n明文与密文分组 **明文分组: **是指分组密码算法中作为加密对象的明文。明文分组的长度与分组密码算法的分组长度是相等的。 **密文分组: **是指使用分组密码算法将明文分组加密之后所生成的密文。 ECB模式：Electronic Code Book mode（电子密码本模式） ECB是最简单的加密模式，明文消息被分成固定大小的块（分组），并且每个块被单独加密。 每个块的加密和解密都是独立的，且使用相同的方法进行加密，所以可以进行并行计算，但是这种方法一旦有一个块被破解，使用相同的方法可以解密所有的明文数据，安全性比较差。 适用于数据较少的情形，加密前需要把明文数据填充到块大小的整倍数。\n使用ECB模式加密时，相同的明文分组会被转换为相同的密文分组，因此ECB模式也称为电子密码本模式当最后一个明文分组的内容小于分组长度时（如一个分组8bit），需要用一特定的数据进行填充（padding），让值一个分组长度等于分组长度。\nECB模式是所有模式中最简单的一种。ECB模式中，明文分组与密文分组是一一对应的关系，因此，如果明文中存在多个相同的明文分组，则这些明文分组最终都将被转换为相同的密文分组。这样一来，只要观察一下密文，就可以知道明文中存在怎样的重复组合，并可以以此为线索来破译密码，因此ECB模式是存在一定风险的。\nCBC模式：Cipher Block Chaining mode（密码分组链接/密码块 模式） 1976年，IBM发明了密码分组链接CBC。CBC模式中每一个分组要先和前一个分组加密后的数据进行XOR异或操作，然后再进行加密。 这样每个密文块依赖该块之前的所有明文块，为了保持每条消息都具有唯一性，在第一个块进行加密之前需要用初始化向量 IV 进行异或操作。 CBC模式是一种最常用的加密模式，它主要缺点是加密是连续的，不能并行处理，并且与ECB一样消息块必须填充到块大小的整倍数。\n**当加密第一个明文分组时，由于不存在 “前一个密文分组\u0026quot;，因此需要事先准备一个长度为一个分组的比特序列来代替“前一个密文分组\u0026quot;，这个比特序列称为初始化向量（initialization vector）**通常缩写为 IV。一般来说，每次加密时都会随机产生一个不同的比特序列来作为初始化向量。\nCFB模式：Cipher FeedBack mode（密文反馈模式） 密文反馈模式 CFB；在CFB模式中，前一个分组的密文加密后和当前分组的明文XOR异或操作生成当前分组的密文。所谓反馈，这里指的就是返回输入端的意思，即前一个密文分组会被送回到密码算法的输入端。\n在ECB和CBC中，明文分组都是通过密码算法进行加密的，然而，在CFB模式中，明文分组和密文分组之间并没有经过\u0026quot;加密\u0026quot;这一步骤，明文分和密文分组之间只有一个XOR。\nOFB模式：Output FeedBack mode（输出反馈模式） 输出反馈模式, OFB。在OFB模式中，上一个分组密码算法的输出是当前分组密码算法的输入（下图）\nCTR模式：CounTeR mode（计数器模式） CTR是一种通过将逐次累加的计数器进行加密来生成密钥流的流密码；即每个分组对应一个逐次累加的计数器，并通过对计数器进行加密来生成密钥流。也就是说，最终的密文分组是通过将计数器加密得到的比特序列，与明文分组进行XOR而得到的。","title":"常用加密算法学习总结之对称加密"},{"content":"traefik概述 traefik-现代反向代理，也可称为现代边缘路由；traefik原声兼容主流集群，Kubernetes，Docker，AWS等。官方的定位traefik是一个让开发人员将时间花费在系统研发与部署功能上，而非配置和维护。并且traefik官方也提供自己的服务网格解决方案\n作为一个 modern edge router ，traefik拥有与envoy相似的特性\n基于go语言研发，目的是为了简化开发人员的配置和维护 tcp/udp支持 http L7支持 GRPC支持 服务发现和动态配置 front/ edge prory支持 可观测性 流量管理 \u0026hellip; traefik 术语 要了解trafik，首先需要先了解一下 有关trafik中的一些术语。\nEntryPoints 入口点，是可以被下游客户端连接的命名网络位置，类似于envoy 的listener和nginx的listen services 服务，负载均衡，上游主机接收来自traefik的连接和请求并返回响应。 类似于nginx upstream envoy的clusters Providers 提供者，提供配置文件的后端，如文件，consul，redis，etcd等，可使traefik自动更新 routers 路由器，分析请求，将下游主机的请求处理转入到services middlewares: 中间件，在将下游主机的请求转入到services时进行的流量调整 traefik部署安装 traefik为go语言开发的，可以直接下载运行即可。此处介绍直接运行二进制程序\n后端环境准备,此处为docker运行的两个后端。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 version: \u0026#39;3\u0026#39; services: webserver1: image: sealloong/envoy-end:latest ports: - 91:90 networks: envoymesh: aliases: - v1_server - default_server environment: - VERSION=v1 - COLORFUL=blue expose: - 90 webserver2: image: sealloong/envoy-end:latest ports: - 92:90 networks: envoymesh: aliases: - v1_server - default_server environment: - VERSION=v1 - COLORFUL=blue expose: - 90 networks: envoymesh: {} traefik配置说明 Traefik中的配置可以引用两种不同的内容：\n完全动态路由配置（动态配置） 启动时配置（静态配置） 静态配置一般定义traefik的endpoints 与providers，这些不经常变动 动态配置一般定义traefik的处理浏览的部分，如 中间件，路由，浏览管理等。\ntraefik1 与 traefik2的配置文件不兼容 此处配置主要以file方式讲解。\n静态配置部分：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 entryPoints: web: address: :8081 [api] dashboard = true insecure = true providers: file: filename: ./root.yaml [accessLog] filePath = \u0026#34;/root/access.log\u0026#34; format = \u0026#34;json\u0026#34; 动态配置部分\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 http: routers: router0: rule: \u0026#34;Host(`test.com`)\u0026#34; service: \u0026#34;service-foo\u0026#34; entryPoints: - web router1: rule: \u0026#34;Path(`/`)\u0026#34; service: \u0026#34;baidu\u0026#34; entryPoints: - web services: service-foo: loadBalancer: servers: - url: \u0026#34;http://10.0.0.4:91/\u0026#34; - url: \u0026#34;http://10.0.0.4:92/\u0026#34; baidu: loadBalancer: servers: - url: http://www.baidu.com/ ","permalink":"https://www.161616.top/traefik-on-physical/","summary":"traefik概述 traefik-现代反向代理，也可称为现代边缘路由；traefik原声兼容主流集群，Kubernetes，Docker，AWS等。官方的定位traefik是一个让开发人员将时间花费在系统研发与部署功能上，而非配置和维护。并且traefik官方也提供自己的服务网格解决方案\n作为一个 modern edge router ，traefik拥有与envoy相似的特性\n基于go语言研发，目的是为了简化开发人员的配置和维护 tcp/udp支持 http L7支持 GRPC支持 服务发现和动态配置 front/ edge prory支持 可观测性 流量管理 \u0026hellip; traefik 术语 要了解trafik，首先需要先了解一下 有关trafik中的一些术语。\nEntryPoints 入口点，是可以被下游客户端连接的命名网络位置，类似于envoy 的listener和nginx的listen services 服务，负载均衡，上游主机接收来自traefik的连接和请求并返回响应。 类似于nginx upstream envoy的clusters Providers 提供者，提供配置文件的后端，如文件，consul，redis，etcd等，可使traefik自动更新 routers 路由器，分析请求，将下游主机的请求处理转入到services middlewares: 中间件，在将下游主机的请求转入到services时进行的流量调整 traefik部署安装 traefik为go语言开发的，可以直接下载运行即可。此处介绍直接运行二进制程序\n后端环境准备,此处为docker运行的两个后端。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 version: \u0026#39;3\u0026#39; services: webserver1: image: sealloong/envoy-end:latest ports: - 91:90 networks: envoymesh: aliases: - v1_server - default_server environment: - VERSION=v1 - COLORFUL=blue expose: - 90 webserver2: image: sealloong/envoy-end:latest ports: - 92:90 networks: envoymesh: aliases: - v1_server - default_server environment: - VERSION=v1 - COLORFUL=blue expose: - 90 networks: envoymesh: {} traefik配置说明 Traefik中的配置可以引用两种不同的内容：","title":"将traefik部署为传统web架构模式"},{"content":"检查要求 Windows 10 企业版、专业版或教育版 （必须windows10 1903版本以上）版本号 18362.1049+ 或 18363.1049+ ，次版本＃大于.1049。最好是最新版（新版windows可以hype-v wsl2 vmvare共存，但安卓模拟器目前还没稳定的共存版本）。建议使用wsl2，安装包容量会比起hype-v小很多 。 Windows开启wsl2，建议 Windows 10 2004（版本号不低于 19041.264），可wsl2与vmvare共存。 CPU 支持并开启虚拟化（Intel VT-c 或 AMD SVM）。 最少 4 GB 内存。 对于专业版、企业版、教育版可以使用docker desktop wsl2模式，此处无需开启Hype-v\n对于Win10 家庭版，Win10 19041.264之前版本，及 Win7 8用户，可以使用docker desktop Hype-v 后端。\n修改安装盘 Docker Desktop 默认安装到 C:\\Program Files\\Docker 并不可更改，这样很不友好，可以通过软连接的方式改变Docker Desktop 默认安装盘。\ntext 1 mklink /J \u0026#34;C:\\Program Files\\Docker\u0026#34; \u0026#34;D:\\Program Files\\Docker\u0026#34; 限制wsl2运行最大内存 WSL 是 Microsoft 提供的一项功能，可以使开发人员能够直接在 Windows 上运行 GNU/Linux 环境，无需修改，无需传统虚拟机或双引导设置，减少了开发人员的使用复杂度\n在 Docker Desktop 使用了 WSL 2 中的动态内存分配特性，极大地提高了资源消耗。这意味着，Docker Desktop 仅使用其所需的 CPU 和内存资源量，同时使 CPU 和内存密集型任务（例如构建容器）运行得更快。\n但WSL2目前一个弊端，可能WSL2 vm会分配所有可用内存，并最终导致操作系统和其他应用程序的内存不足。\n所以需要对WSL2内存和CPU资源进行限制，在 cmd 或 powshell 终端中\ntext 1 2 wsl --shutdown notepad \u0026#34;$env:USERPROFILE/.wslconfig\u0026#34; 在用户目录创建一个文件.wslconfig ，编辑 .wslconfig\ntext 1 2 3 4 [wsl2] memory=3GB # 限制wsl2的虚拟机最大内存 processors=4 # 限制wsl2使用的处理器数量 swap=0 # 不使用交换文件 安装Docker Desktop 完成上面的操作，可以安装Docker Desktop了。从Docker Desktop网站下载安装Docker Desktop for Windows，大于500M。\n安装步骤基本上点击操作即可，没有什么难度\n镜像路径迁移 当使用了WSL2作为Docker Desktop后端引擎时，WSL 2 Docker-Desktop-Data 的VM磁盘镜像通常在 %USERPROFILE%\\AppData\\Local\\Docker\\wsl\\data\\ext4.vhdx 路径下，docker-desktop通常在%LOCALAPPDATA%/Docker/wsl 路径下，因为镜像的大小及一些交换文件，通常会占用大量C盘空间，可以改变其存储位置。\ntext 1 wsl --list -v 输入上述命令可以看到如下内容\ntext 1 2 3 NAME STATE VERSION * docker-desktop Stopped 2 docker-desktop-data Stopped 2 docker-desktop 替换了之前使用的 Hyper-V VM 实现 Docker Desktop。这处理容器的引导和管理。\ndocker-desktop-data 是存储docker镜像和配置的地方；实际上是对 Hyper-V 以前使用的虚拟硬盘的直接替换。\n从这里可以看出Docker Desktop使用了WSL2作为后端引擎时，实际上整个应用作为WLS2的两个子系统进行的。可以通过迁移WSL2系统镜像的存储位置来改变Docker霸占C盘不可转移的弊端。\n导出wsl系统镜像\ntext 1 2 wsl --export docker-desktop docker-desktop.tar wsl --export docker-desktop-data docker-desktop-data.tar 删除Docker Desktop wsl子系统，此操作会自动删除 ext4.vhdx 文件，故需要先导出一份备份\ntext 1 2 wsl --unregister docker-desktop wsl --unregister docker-desktop-data 导入重新创建wsl Docker Desktop子系统\ntext 1 2 wsl --import docker-desktop d:\\{new_path} docker-desktop.tar wsl --import docker-desktop-data d:\\{new_path} docker-desktop-data.tar 完成后，启动Docker服务，如果服务正常，可以删除掉 docker-desktop.tar 与 docker-desktop-data.tar\n无法启动 我在使用windows时，会安装冰点还原，因为windows10 以上需要 冰点还原 8.38以上，我这里使用 8.38.020.4676 版本时，在开启还原状态时，Docker无法正常启动，在关闭还原时，可以正常启动。更换 8.62.020.5630。后正常。 8.38.020.4676 是2017年的版本，当时Docker对windows兼容并不好，而8.38.020.4676 是2020年发行的版本，目前在使用中并未发现异常。 8.38.020.4676 与 8.62.020.5630为网上常见的纯净的破解版了，所以按需选择使用。\n","permalink":"https://www.161616.top/windows10-install-docker-desktop/","summary":"检查要求 Windows 10 企业版、专业版或教育版 （必须windows10 1903版本以上）版本号 18362.1049+ 或 18363.1049+ ，次版本＃大于.1049。最好是最新版（新版windows可以hype-v wsl2 vmvare共存，但安卓模拟器目前还没稳定的共存版本）。建议使用wsl2，安装包容量会比起hype-v小很多 。 Windows开启wsl2，建议 Windows 10 2004（版本号不低于 19041.264），可wsl2与vmvare共存。 CPU 支持并开启虚拟化（Intel VT-c 或 AMD SVM）。 最少 4 GB 内存。 对于专业版、企业版、教育版可以使用docker desktop wsl2模式，此处无需开启Hype-v\n对于Win10 家庭版，Win10 19041.264之前版本，及 Win7 8用户，可以使用docker desktop Hype-v 后端。\n修改安装盘 Docker Desktop 默认安装到 C:\\Program Files\\Docker 并不可更改，这样很不友好，可以通过软连接的方式改变Docker Desktop 默认安装盘。\ntext 1 mklink /J \u0026#34;C:\\Program Files\\Docker\u0026#34; \u0026#34;D:\\Program Files\\Docker\u0026#34; 限制wsl2运行最大内存 WSL 是 Microsoft 提供的一项功能，可以使开发人员能够直接在 Windows 上运行 GNU/Linux 环境，无需修改，无需传统虚拟机或双引导设置，减少了开发人员的使用复杂度\n在 Docker Desktop 使用了 WSL 2 中的动态内存分配特性，极大地提高了资源消耗。这意味着，Docker Desktop 仅使用其所需的 CPU 和内存资源量，同时使 CPU 和内存密集型任务（例如构建容器）运行得更快。","title":"windows下Docker Desktop安装管理"},{"content":"启动故障：zimbra postsuper: fatal: scan_dir_push: open directory defer: Permission denied bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Host mail.domain.com Starting ldap...Done. Starting zmconfigd...Done. Starting dnscache...Done. Starting logger...Done. Starting mailbox...Done. Starting memcached...Done. Starting proxy...Done. Starting amavis...Done. Starting antispam...Done. Starting antivirus...Done. Starting opendkim...Done. Starting snmp...Done. Starting spell...Done. Starting mta...Failed. Starting saslauthd...done. postsuper: fatal: scan_dir_push: open directory defer: Permission denied postfix failed to start Starting stats...Done. Starting service webapp...Done. Starting zimbra webapp...Done. Starting zimbraAdmin webapp...Done. Starting zimlet webapp...Done. 查看服务器状态：\nbash 1 2 mta Stopped postfix is not running 经查看mta服务是由postfix启动。\n查看系统是否已经对自带的sendmail和postfix进行关闭，端口25是否被占用，如果是请关闭并重启zimbra\n如果不是则执行/opt/zimbra/libexec/zmfixperms (run as root)\nRefer：Zimbra 启动时mta无法启动 postsuper: fatal: scan_dir_push: open directory defer: Permission denied\n错误： bash 1 2 opendkim: /opt/zimbra/conf/opendkim.conf: ldap://xxxx333.com:389/?DKIMSelector?sub?(DKIMIdentity=$d): dkimf_db_open(): Connect error Failed to start opendkim: 0 原因：无法连接至ldap服务，检查ldap服务是否正常\nRefer：ZCS 8.0 mta error with zmopendkimctl error 错误：Error: Queue report unavailable text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 zmcontrol status Host mail.ttdconline.com amavis Running antispam Running antivirus Running ldap Running logger Running mailbox Running memcached Running mta Running opendkim Running proxy Running service webapp Running snmp Running spell Running stats Running zimbra webapp Running zimbraAdmin webapp Running zimlet webapp Running zmconfigd Running We reviewed logs and services and we see that the MTA is down: $ tail -f /var/log/mail.log Jan 22 11:08:00 zcs postfix/postqueue[19195]: fatal: Queue report unavailable – mail system is down Refer: Error: Queue report unavailable – mail system is down\t错误 Logswatch Failed zimbra logswatch failed\n. /opt/zimbra/.bashrc\ntext 1 2 Starting logger...Failed. [b]Starting logswatch...failed.[/b] Refer: 8.8.15 Starting Logswatch Failed\n修改管理员账户密码 server.domain.com (https://server.domain.com:7071) 是当前运行的zimbra的域名或者IP地址，默认的http监听端口为7071 输入用户名： admin@domain.com 和密码，完成登录\n在zimbra安装配置时创建了管理员账户，可以在web端的账户工具栏任何时候进行账户密码修改，选择administrator 用户并选择密码修改 也可以在命令行中运行zmprov进行管理员账户密码的修改：\nbash 1 2 3 zmprov sp admin@domain.com \u0026lt;password\u0026gt; zmprov gaaa //列出所有管理员 zmprov sp admin q1w2e3r4 或 zmprov sp admin@wish.com q12e3r4 # 修改管理员账号密码 清除队列 查看发送队列数量:\ntext 1 /opt/zimbra/libexec/zmqstat 查看队列内容\ntext 1 mailq 删除队列\ntext 1 /opt/zimbra/postfix/sbin/postqueue -f 查看邮件队列\ntext 1 /opt/zimbra/postfix/sbin/postcat -qv EC753D0D00 Refer：\nManaging The Postfix Queues\n[Zimbra – deleting all email in queue by sender](Zimbra – deleting all email in queue by sender)\n被攻击状态 查看邮件状态\ntext 1 2 3 4 5 6 $ /opt/zimbra/libexec/zmqstat hold=0 corrupt=0 deferred=563344 active=19992 incoming=45830 text 1 2 3 postmap /opt/zimbra/conf/restricted_senders postmap /opt/zimbra/conf/local_domains postmap ../common/conf/main.cf 问题\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Feb 23 00:36:56 ${domainname} postfix/postscreen[7614]: PASS OLD [193.26.3.10]:63396 Feb 23 00:36:56 ${domainname} postfix/smtpd[7615]: connect from mail.health.kiev.ua[193.26.3.10] Feb 23 00:36:57 ${domainname} postfix/smtpd[7615]: Anonymous TLS connection established from mail.health.kiev.ua[193.26.3.10]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits) Feb 23 00:36:58 ${domainname} postfix/smtpd[7615]: warning: lmdb:/opt/zimbra/conf/restricted_senders is unavailable. open database /opt/zimbra/conf/restricted_senders.lmdb: MDB_INVALID: File is not an LMDB file Feb 23 00:36:58 ${domainname} postfix/smtpd[7615]: warning: lmdb:/opt/zimbra/conf/restricted_senders: table lookup problem Feb 23 00:36:58 ${domainname} postfix/smtpd[7615]: NOQUEUE: reject: RCPT from mail.health.kiev.ua[193.26.3.10]: 451 4.3.5 \u0026lt;\u0026gt;: Sender address rejected: Server configuration error; from=\u0026lt;\u0026gt; to=\u0026lt;vivian@${domainname}.com\u0026gt; proto=ESMTP helo=\u0026lt;mail.health.kiev.ua\u0026gt; Feb 23 00:36:59 ${domainname} postfix/smtpd[7615]: warning: lmdb:/opt/zimbra/conf/restricted_senders is unavailable. open database /opt/zimbra/conf/restricted_senders.lmdb: MDB_INVALID: File is not an LMDB file Feb 23 00:36:59 ${domainname} postfix/smtpd[7615]: warning: lmdb:/opt/zimbra/conf/restricted_senders: table lookup problem Feb 23 00:36:59 ${domainname} postfix/smtpd[7615]: NOQUEUE: reject: RCPT from mail.health.kiev.ua[193.26.3.10]: 451 4.3.5 \u0026lt;\u0026gt;: Sender address rejected: Server configuration error; from=\u0026lt;\u0026gt; to=\u0026lt;vivian@${domainname}.com\u0026gt; proto=ESMTP helo=\u0026lt;mail.health.kiev.ua\u0026gt; Feb 23 00:37:00 ${domainname} postfix/smtpd[7615]: warning: lmdb:/opt/zimbra/conf/restricted_senders is unavailable. open database /opt/zimbra/conf/restricted_senders.lmdb: MDB_INVALID: File is not an LMDB file Feb 23 00:37:00 ${domainname} postfix/smtpd[7615]: warning: lmdb:/opt/zimbra/conf/restricted_senders: table lookup problem Feb 23 00:37:00 ${domainname} postfix/smtpd[7615]: NOQUEUE: reject: RCPT from mail.health.kiev.ua[193.26.3.10]: 451 4.3.5 \u0026lt;\u0026gt;: Sender address rejected: Server configuration error; from=\u0026lt;\u0026gt; to=\u0026lt;vivian@${domainname}.com\u0026gt; proto=ESMTP helo=\u0026lt;mail.health.kiev.ua\u0026gt; Feb 23 00:37:00 ${domainname} postfix/postqueue[13129]: fatal: Queue report unavailable - mail system is down Feb 23 00:37:01 ${domainname} postfix/smtpd[7615]: warning: lmdb:/opt/zimbra/conf/restricted_senders is unavailable. open database /opt/zimbra/conf/restricted_senders.lmdb: MDB_INVALID: File is not an LMDB file Feb 23 00:37:01 ${domainname} postfix/smtpd[7615]: warning: lmdb:/opt/zimbra/conf/restricted_senders: table lookup problem Feb 23 00:37:01 ${domainname} postfix/smtpd[7615]: NOQUEUE: reject: RCPT from mail.health.kiev.ua[193.26.3.10]: 451 4.3.5 \u0026lt;\u0026gt;: Sender address rejected: Server configuration error; from=\u0026lt;\u0026gt; to=\u0026lt;vivian@${domainname}.com\u0026gt; proto=ESMTP helo=\u0026lt;mail.health.kiev.ua\u0026gt; Feb 23 00:37:01 ${domainname} postfix/smtpd[7615]: warning: lmdb:/opt/zimbra/conf/restricted_senders is unavailable. open database /opt/zimbra/conf/restricted_senders.lmdb: MDB_INVALID: File is not an LMDB file Feb 23 00:37:01 ${domainname} postfix/smtpd[7615]: warning: lmdb:/opt/zimbra/conf/restricted_senders: table lookup problem Feb 23 00:37:01 ${domainname} postfix/smtpd[7615]: NOQUEUE: reject: RCPT from mail.health.kiev.ua[193.26.3.10]: 451 4.3.5 \u0026lt;\u0026gt;: Sender address rejected: Server configuration error; from=\u0026lt;\u0026gt; to=\u0026lt;vivian@${domainname}.com\u0026gt; proto=ESMTP helo=\u0026lt;mail.health.kiev.ua\u0026gt; Feb 23 00:37:05 ${domainname} postfix/smtpd[7615]: warning: lmdb:/opt/zimbra/conf/restricted_senders is unavailable. open database /opt/zimbra/conf/restricted_senders.lmdb: MDB_INVALID: File is not an LMDB file Feb 23 00:37:05 ${domainname} postfix/smtpd[7615]: warning: lmdb:/opt/zimbra/conf/restricted_senders: table lookup problem 解决：找其他服务器处理这个问题并修改配置\ntext 1 local_only = check_recipient_access lmdb:/opt/zimbra/conf/local_domains, reject 正常\ntext 1 2 3 4 5 6 Feb 23 00:49:31 ${domainname} postfix/smtpd[24306]: connect from iZj6c4jc5vsy383um5cgomZ[172.31.108.227] Feb 23 00:49:31 ${domainname} postfix/smtpd[24306]: NOQUEUE: reject: RCPT from iZj6c4jc5vsy383um5cgomZ[172.31.108.227]: 554 5.7.1 \u0026lt;test1@${domainname}.com\u0026gt;: Sender address rejected: Access denied; from=\u0026lt;test1@${domainname}.com\u0026gt; to=\u0026lt;test@163.com\u0026gt; proto=ESMTP helo=\u0026lt;${domainname}.com\u0026gt; Feb 23 00:49:31 ${domainname} postfix/smtpd[24306]: disconnect from iZj6c4jc5vsy383um5cgomZ[172.31.108.227] ehlo=1 mail=1 rcpt=0/1 quit=1 commands=3/4 Feb 23 00:49:31 ${domainname} zmconfigd[17300]: Tracking service snmp Feb 23 00:49:32 ${domainname} zmconfigd[17300]: Watchdog: service antivirus status is OK. Feb 23 00:49:32 ${domainname} zmconfigd[17300]: All rewrite threads completed in 0.00 se 配置安全策略\nZimbra 8.7.11规则：只能发送内部邮件\nDomain level blocking of users\nRejecting Emails at SMTP Level\n配置监控策略\n配置磁盘空间的告警\n","permalink":"https://www.161616.top/zimbra-troubleshooing/","summary":"启动故障：zimbra postsuper: fatal: scan_dir_push: open directory defer: Permission denied bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Host mail.domain.com Starting ldap...Done. Starting zmconfigd...Done. Starting dnscache...Done. Starting logger...Done. Starting mailbox...Done. Starting memcached...Done. Starting proxy...Done. Starting amavis...Done. Starting antispam...Done. Starting antivirus...Done. Starting opendkim...Done. Starting snmp...Done. Starting spell...Done. Starting mta...Failed. Starting saslauthd...done. postsuper: fatal: scan_dir_push: open directory defer: Permission denied postfix failed to start Starting stats.","title":"zimbra安装故障记录"},{"content":"环境准备 主机 角色 数量 front-envoy front envoy 1 service envoy 作为内部后端的envoy 2 end 后端应用程序 2 访问 / front-envoy ==\u0026gt; end * 2 访问 /red/colorful ==\u0026gt; end red 不验证客户端证书 单项tls 访问 /gray/colorful ==\u0026gt; end gray 验证客户端证书 双项tls\ndocker-compose text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 version: \u0026#39;3\u0026#39; services: front-envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 ports: - 80:80 - 443:443 - 82:9901 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml - ./certs/front-envoy/:/etc/envoy/certs/ - ./certs/CA/:/etc/envoy/ca/ networks: envoymesh: aliases: - front-envoy depends_on: - webserver1 - webserver2 gray-envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 volumes: - ./service_gray.yaml:/etc/envoy/envoy.yaml - ./certs/service_gray/:/etc/envoy/certs/ - ./certs/CA1/:/etc/envoy/ca/ network_mode: \u0026#34;service:webserver1\u0026#34; depends_on: - webserver1 red-envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 volumes: - ./service_red.yaml:/etc/envoy/envoy.yaml - ./certs/service_red/:/etc/envoy/certs/ - ./certs/CA1/:/etc/envoy/ca/ network_mode: \u0026#34;service:webserver2\u0026#34; depends_on: - webserver2 webserver1: image: cylonchau/envoy-end:latest networks: envoymesh: aliases: - service_gray - front_envoy environment: - VERSION=v1 - COLORFUL=gray expose: - 90 webserver2: image: cylonchau/envoy-end:latest networks: envoymesh: aliases: - service_red - front_envoy environment: - VERSION=v1 - COLORFUL=red expose: - 90 networks: envoymesh: {} front-envoy text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 admin: access_log_path: \u0026#34;/dev/null\u0026#34; address: socket_address: address: 0.0.0.0 port_value: 9901 static_resources: secrets: - name: servers tls_certificate: certificate_chain: filename: \u0026#34;/etc/envoy/certs/server.crt\u0026#34; private_key: filename: \u0026#34;/etc/envoy/certs/server.key\u0026#34; - name: clients tls_certificate: certificate_chain: filename: \u0026#34;/etc/envoy/certs/client.crt\u0026#34; private_key: filename: \u0026#34;/etc/envoy/certs/client.key\u0026#34; - name: validation validation_context: trusted_ca: filename: \u0026#34;/etc/envoy/ca/ca.crt\u0026#34; listeners: - name: listener_http address: socket_address: { address: 0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy.http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager codec_type: auto stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: service domains: [ \u0026#34;*\u0026#34; ] routes: - match: { prefix: \u0026#34;/\u0026#34; } redirect: https_redirect: true port_redirect: 443 http_filters: - name: envoy.router - name: listener_https address: socket_address: { address: 0.0.0.0, port_value: 443 } filter_chains: - filters: - name: envoy.http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_https codec_type: AUTO route_config: name: https_route virtual_hosts: - name: https_route domains: [\u0026#34;*\u0026#34;] routes: - match: { prefix: \u0026#34;/gray/colorful\u0026#34; } route: prefix_rewrite: \u0026#34;/colorful\u0026#34; cluster: gray - match: { prefix: \u0026#34;/red/colorful\u0026#34; } route: prefix_rewrite: \u0026#34;/colorful\u0026#34; cluster: red - match: { prefix: \u0026#34;/\u0026#34; } route: cluster: front_envoy http_filters: - name: envoy.router access_log: - name: envoy.listener.accesslog typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/stdout log_format: text_format: \u0026#34;[%START_TIME%] \\\u0026#34;%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\\\u0026#34; %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \\\u0026#34;%REQ(X-FORWARDED-FOR)%\\\u0026#34; \\\u0026#34;%REQ(USER-AGENT)%\\\u0026#34; \\\u0026#34;%REQ(X-REQUEST-ID)%\\\u0026#34; \\\u0026#34;%REQ(:AUTHORITY)%\\\u0026#34; \\\u0026#34;%UPSTREAM_HOST%\\\u0026#34;\\n\u0026#34; transport_socket: name: envoy.transport_sockets.tls typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificate_sds_secret_configs: - name: servers clusters: - name: front_envoy connect_timeout: 0.25s type: strict_dns lb_policy: round_robin load_assignment: cluster_name: front_envoy endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: front_envoy, port_value: 90 } - name: gray connect_timeout: 0.25s type: strict_dns lb_policy: round_robin load_assignment: cluster_name: gray endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: service_gray, port_value: 443 } transport_socket: name: envoy.transport_sockets.tls typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext common_tls_context: tls_certificate_sds_secret_configs: - name: clients validation_context_sds_secret_config: name: validation - name: red connect_timeout: 0.25s type: strict_dns lb_policy: round_robin load_assignment: cluster_name: red endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: service_red, port_value: 443 } transport_socket: name: envoy.transport_sockets.tls typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext common_tls_context: tls_certificate_sds_secret_configs: - name: clients service gray text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 admin: access_log_path: \u0026#34;/dev/null\u0026#34; address: socket_address: address: 0.0.0.0 port_value: 9901 static_resources: listeners: - name: listener_https address: socket_address: { address: 0.0.0.0, port_value: 443 } filter_chains: - filters: - name: envoy.http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_https codec_type: AUTO route_config: name: https_route virtual_hosts: - name: https_route domains: [\u0026#34;*\u0026#34;] routes: - match: { prefix: \u0026#34;/\u0026#34; } route: cluster: service_gray http_filters: - name: envoy.router access_log: - name: envoy.listener.accesslog typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/stdout log_format: text_format: \u0026#34;[%START_TIME%] \\\u0026#34;%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\\\u0026#34; %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \\\u0026#34;%REQ(X-FORWARDED-FOR)%\\\u0026#34; \\\u0026#34;%REQ(USER-AGENT)%\\\u0026#34; \\\u0026#34;%REQ(X-REQUEST-ID)%\\\u0026#34; \\\u0026#34;%REQ(:AUTHORITY)%\\\u0026#34; \\\u0026#34;%UPSTREAM_HOST%\\\u0026#34;\\n\u0026#34; transport_socket: name: envoy.transport_sockets.tls typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: certificate_chain: filename: \u0026#34;/etc/envoy/certs/server.crt\u0026#34; private_key: filename: \u0026#34;/etc/envoy/certs/server.key\u0026#34; validation_context: trusted_ca: filename: \u0026#34;/etc/envoy/ca/ca.crt\u0026#34; require_client_certificate: true clusters: - name: service_gray connect_timeout: 0.25s type: strict_dns lb_policy: round_robin load_assignment: cluster_name: service_gray endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: 127.0.0.1, port_value: 90 } service red text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 admin: access_log_path: \u0026#34;/dev/null\u0026#34; address: socket_address: address: 0.0.0.0 port_value: 9901 static_resources: listeners: - name: listener_https address: socket_address: { address: 0.0.0.0, port_value: 443 } filter_chains: - filters: - name: envoy.http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_https codec_type: AUTO route_config: name: https_route virtual_hosts: - name: https_route domains: [\u0026#34;*\u0026#34;] routes: - match: { prefix: \u0026#34;/\u0026#34; } route: cluster: service_red http_filters: - name: envoy.router access_log: - name: envoy.listener.accesslog typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/stdout log_format: text_format: \u0026#34;[%START_TIME%] \\\u0026#34;%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\\\u0026#34; %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \\\u0026#34;%REQ(X-FORWARDED-FOR)%\\\u0026#34; \\\u0026#34;%REQ(USER-AGENT)%\\\u0026#34; \\\u0026#34;%REQ(X-REQUEST-ID)%\\\u0026#34; \\\u0026#34;%REQ(:AUTHORITY)%\\\u0026#34; \\\u0026#34;%UPSTREAM_HOST%\\\u0026#34;\\n\u0026#34; transport_socket: name: envoy.transport_sockets.tls typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: filename: \u0026#34;/etc/envoy/certs/server.crt\u0026#34; private_key: filename: \u0026#34;/etc/envoy/certs/server.key\u0026#34; clusters: - name: service_red connect_timeout: 0.25s type: strict_dns lb_policy: round_robin load_assignment: cluster_name: service_red endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: 127.0.0.1, port_value: 90 } 配置说明 docker-compose\nnetwork_mode: \u0026quot;service:webserver1\u0026quot; 指定网络类型，使envoy和后端程序运行在一个网络下\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 secrets: - name: servers tls_certificate: certificate_chain: filename: \u0026#34;/etc/envoy/certs/server.crt\u0026#34; private_key: filename: \u0026#34;/etc/envoy/certs/server.key\u0026#34; - name: clients tls_certificate: certificate_chain: filename: \u0026#34;/etc/envoy/certs/client.crt\u0026#34; private_key: filename: \u0026#34;/etc/envoy/certs/client.key\u0026#34; - name: validation validation_context: trusted_ca: filename: \u0026#34;/etc/envoy/ca/ca.crt\u0026#34; server\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 transport_socket: name: envoy.transport_sockets.tls typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: certificate_chain: filename: \u0026#34;/etc/envoy/certs/server.crt\u0026#34; private_key: filename: \u0026#34;/etc/envoy/certs/server.key\u0026#34; validation_context: # 验证机制的相关配置 trusted_ca: # 信任的ca证书，未指定时不会验证对端证书 filename: \u0026#34;/etc/envoy/ca/ca.crt\u0026#34; # 这里指定的为根ca require_client_certificate: true # boolval 设置为ture,Envoy将拒绝没有有效客户端证书的连接。 验证结果 /gray/colorful后端服务开启了验证客户端ca，访问报错，后端程序并没收到请求，因证书无效，envoy销毁了请求 将根ca设置为可信任后 /red/colorful 没开启验证客户端证书\n","permalink":"https://www.161616.top/envoy-mutual-tls/","summary":"环境准备 主机 角色 数量 front-envoy front envoy 1 service envoy 作为内部后端的envoy 2 end 后端应用程序 2 访问 / front-envoy ==\u0026gt; end * 2 访问 /red/colorful ==\u0026gt; end red 不验证客户端证书 单项tls 访问 /gray/colorful ==\u0026gt; end gray 验证客户端证书 双项tls\ndocker-compose text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 version: \u0026#39;3\u0026#39; services: front-envoy: image: envoyproxy/envoy-alpine:v1.","title":"Envoy：TLS双向认证"},{"content":"Authorization not available. Check if polkit text 1 2 3 4 Authorization not available. Check if polkit service is running or see debug message for more information. dbus.socket failed to listen on sockets: Address family not supported by protocol Failed to listen on D-Bus System Message Bus Socket. 这个问题是因为dbus.socket状态异常，所有依赖dbus的启动都会去通过systemcall连接 dbus，当服务不可用时，所有服务无法以systemd方式正常启动/关闭。需要检查dbus.socket是否正常。本地使用需保证unix套接字的监听时启动的\nDid not receive a reply text 1 Failed to open connection to \u0026#34;system\u0026#34; message bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken. 这是因为你的配置不对，客户端无法连接上\nD-Bus 重启后登陆慢 text 1 2 3 4 5 6 7 8 9 10 11 12 systemd-logind: Failed to connect to system bus: Connection refused systemd-logind: Failed to fully start up daemon: Connection refused systemd: systemd-logind.service: main process exited, code=exited, status=1/FAILURE systemd: Unit systemd-logind.service entered failed state. systemd: systemd-logind.service failed. systemd: systemd-logind.service has no holdoff time, scheduling restart. systemd: start request repeated too quickly for systemd-logind.service systemd: Unit systemd-logind.service entered failed state. systemd: systemd-logind.service failed. dbus[7782]: [system] Failed to activate service \u0026#39;org.freedesktop.login1\u0026#39;: timed out dbus-daemon: dbus[7782]: [system] Failed to activate service \u0026#39;org.freedesktop.login1\u0026#39;: timed out 参考：ssh登陆缓慢\nsystemd-logind主要功能是为每一个登陆session创建一个systemd角度的cgroup管理对象，更方便对session使用cgroup，在dbus服务异常时，systemd-logind会导致登陆缓慢，并不影响正常登陆和ssh登陆。重启dbus.socket后需要也重启systemd-logind\nD-Bus 开启远程连接 编辑 /usr/share/dbus-1/system.conf 或 /etc/dbus-1/session.conf\n通常情况下生效的是 /etc/dbus-1/system.conf ,需要根据dbus应用是system bus 还是 session bus进行选择配置\ntext 1 2 3 4 5 6 \u0026lt;listen\u0026gt;tcp:host=\u0026lt;ip\u0026gt;,bind=*,port=\u0026lt;port\u0026gt;,family=ipv4\u0026lt;/listen\u0026gt; \u0026lt;listen\u0026gt;unix:path=/run/user/\u0026lt;username\u0026gt;/dbus/user_bus_socket\u0026lt;/listen\u0026gt; \u0026lt;listen\u0026gt;unix:tmpdir=/tmp\u0026lt;/listen\u0026gt; \u0026lt;auth\u0026gt;ANONYMOUS\u0026lt;/auth\u0026gt; \u0026lt;allow_anonymous/\u0026gt; Reference dbus-send使用 Linux DBus远程TCP连接失败 dbus faq faq ","permalink":"https://www.161616.top/centos7-dbus-troubleshooting/","summary":"Authorization not available. Check if polkit text 1 2 3 4 Authorization not available. Check if polkit service is running or see debug message for more information. dbus.socket failed to listen on sockets: Address family not supported by protocol Failed to listen on D-Bus System Message Bus Socket. 这个问题是因为dbus.socket状态异常，所有依赖dbus的启动都会去通过systemcall连接 dbus，当服务不可用时，所有服务无法以systemd方式正常启动/关闭。需要检查dbus.socket是否正常。本地使用需保证unix套接字的监听时启动的\nDid not receive a reply text 1 Failed to open connection to \u0026#34;system\u0026#34; message bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.","title":"Centos7 dbus问题总结"},{"content":" text 1 2 3 4 5 6 7 access_log: - name: envoy.listener.accesslog typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /var/log/envoy.log log_format: text_format: \u0026#34;[%START_TIME%] \\\u0026#34;%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\\\u0026#34; %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \\\u0026#34;%REQ(X-FORWARDED-FOR)%\\\u0026#34; \\\u0026#34;%REQ(USER-AGENT)%\\\u0026#34; \\\u0026#34;%REQ(X-REQUEST-ID)%\\\u0026#34; \\\u0026#34;%REQ(:AUTHORITY)%\\\u0026#34; \\\u0026#34;%UPSTREAM_HOST%\\\u0026#34;\\n\u0026#34; ","permalink":"https://www.161616.top/envoy-access-log/","summary":" text 1 2 3 4 5 6 7 access_log: - name: envoy.listener.accesslog typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /var/log/envoy.log log_format: text_format: \u0026#34;[%START_TIME%] \\\u0026#34;%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\\\u0026#34; %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \\\u0026#34;%REQ(X-FORWARDED-FOR)%\\\u0026#34; \\\u0026#34;%REQ(USER-AGENT)%\\\u0026#34; \\\u0026#34;%REQ(X-REQUEST-ID)%\\\u0026#34; \\\u0026#34;%REQ(:AUTHORITY)%\\\u0026#34; \\\u0026#34;%UPSTREAM_HOST%\\\u0026#34;\\n\u0026#34; ","title":"Envoy开启访问日志 access_log"},{"content":"在envoy作为前端代理时，用户ip的获取很重要，一般获取ip的方式。都是通过Header中的 X-Forward-For、 X-Real-IP或 Remote addr 等属性获取，但是如果确保Envoy可以获取到的ip是真实的用户ip呢？本篇继续解密！\n概念说明 Remote Address 是nginx与客户端进行TCP连接过程中，获得的客户端真实地址。Remote Address 无法伪造，因为建立 TCP 连接需要三次握手，如果伪造了源 IP，无法建立 TCP 连接，更不会有后面的 HTTP 请求。 一般情况下，在Envoy作为最外层代理时，此IP为真实的IP客户端IP\nX-Real-IP 是一个自定义头。X-Real-Ip 通常被 HTTP 代理用来表示与它产生 TCP 连接的设备 IP，这个设备可能是其他代理，也可能是真正的请求端。X-Real-Ip 目前并不属于任何标准，代理和 Web 应用之间可以约定用任何自定义头来传递这个信息。\nX-Forwarded-For X-Forwarded-For 是一个扩展头。HTTP/1.1（RFC 2616）协议并没有对它的定义，它最开始是由 Squid 这个缓存代理软件引入，用来表示 HTTP 请求端真实 IP，现在已经成为事实上的标准，被各大 HTTP 代理、负载均衡等转发服务广泛使用，并被写入 RFC 7239（Forwarded HTTP Extension）标准之中。通常，X-Forwarded-For可被伪造，并且使用CDN会被重写\nEnvoy中如何获取真实IP 在Envoy中，涉及到客户端IP的配置如下： use_remote_address： 默认值false，设置为true，使用客户端连接的真实远程地址，false是使用x-forwarded-for skip_xff_append： 设置为true，则不会将远程地址附加到x-forwarded-for中 request_headers_to_add 添加请求头 request_headers_to_remove 删除一个请求头\n实验环境配置准备 yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 admin: access_log_path: /dev/null address: socket_address: { address: 0.0.0.0, port_value: 9901 } static_resources: listeners: - name: listener_80 address: socket_address: { address: 0.0.0.0, port_value: 80 } access_log: filter_chains: - filters: - name: envoy_http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager access_log: - name: envoy.listener.accesslog typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /var/log/envoy.log log_format: text_format: \u0026#34;[%START_TIME%] \\\u0026#34;%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\\\u0026#34; %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \\\u0026#34;%REQ(X-FORWARDED-FOR)%\\\u0026#34; \\\u0026#34;%REQ(USER-AGENT)%\\\u0026#34; \\\u0026#34;%REQ(X-REQUEST-ID)%\\\u0026#34; \\\u0026#34;%REQ(:AUTHORITY)%\\\u0026#34; \\\u0026#34;%UPSTREAM_HOST%\\\u0026#34;\\n\u0026#34; http_filters: - name: envoy.filters.http.router use_remote_address: true skip_xff_append: false xff_num_trusted_hops: 0 stat_prefix: local_route codec_type: AUTO route_config: name: local_route #request_headers_to_remove: \u0026#34;X-Forwarded-For\u0026#34; request_headers_to_add: header: key: \u0026#34;X-Forwarded-For\u0026#34; value: \u0026#34;%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%\u0026#34; #value: \u0026#34;%REQ(REMOTE_ADDR)%\u0026#34; append: true virtual_hosts: - name: split_traffic domains: [ \u0026#34;*\u0026#34; ] routes: - match: prefix: \u0026#34;/\u0026#34; route: cluster: version_v1 request_mirror_policies: cluster: version_v2 runtime_fraction: default_value: numerator: 10 denominator: HUNDRED runtime_key: routing.request_mirror.version clusters: - name: version_v1 connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: version_v1 endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: version1, port_value: 90 } health_checks: timeout: 3s interval: 30s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: path: /ping expected_statuses: { start: 200, end: 201 } - name: version_v2 connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: version_v2 endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: version2, port_value: 90 } health_checks: timeout: 3s interval: 30s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: path: /ping expected_statuses: { start: 200, end: 201 } docker-compose\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 ports: - 80:80 - 443:443 - 82:9901 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml networks: envoymesh: aliases: - envoy depends_on: - webserver1 - webserver2 - webserver3 - webserver4 webserver1: image: cylonchau/envoy-end:latest networks: envoymesh: aliases: - version1 environment: - VERSION=v1 - COLORFUL=blue expose: - 90 webserver2: image: cylonchau/envoy-end:latest networks: envoymesh: aliases: - version1 environment: - VERSION=v1 - COLORFUL=blue expose: - 90 webserver3: image: cylonchau/envoy-end:latest networks: envoymesh: aliases: - version2 environment: - VERSION=v2 - COLORFUL=red expose: - 90 webserver4: image: cylonchau/envoy-end:latest environment: - VERSION=v2 - COLORFUL=red networks: envoymesh: aliases: - version2 expose: - 90 networks: envoymesh: {} 实际使用Envoy作为代理时的外在环境 环境1：客户端直接和Envoy通信 当一个正常请求时，此处可以正常获得客户端IP，实际上envoy拿的值是 X-Forwarded-For\n后端日志\n在伪造或者重写X-Forwarded-For后实际上是获取的伪造的值。\n在Envoy直接作为外层代理时，可以使用如下参数，在不管如何伪造，都可以拿到对应的参数。\ntext 1 2 3 4 5 6 7 name: local_route request_headers_to_remove: \u0026#34;X-Forwarded-For\u0026#34; # 怕X-Forwarded-For为伪造值，可以删除此值， request_headers_to_add: # 删除后还需要向后端传递，故还需要添加上此值 header: key: \u0026#34;X-Forwarded-For\u0026#34; value: \u0026#34;%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%\u0026#34; # 获取 remote_addr，此值无法伪造,为Envoy变量，表示 下游主机真实IP不加端口，即remote_addr 无端口 append: true # 表面值是追加还是重写 可以看到envoy获取的为真实的ip并非伪造的请求\n环境2：Envoy前段存在代理（无CDN） 此环境下，前端存在代理，如f5、nginx等。这种情况下不能使用remote_addr 这样获取的为前端代理的IP并非真实IP\n前端存在f5或nginx，可以在f5中配置irule传递真实的remote_addr，替换为真实的客户端IP，又前端代理重写配置，可自定义值。\ntext 1 2 3 4 5 request_headers_to_remove: \u0026#34;X-Forwarded-For\u0026#34; request_headers_to_add: header: key: \u0026#34;X-Forwarded-For\u0026#34; value: \u0026#34;%REQ(custom_header)%\u0026#34; 环境3：Envoy前段存在代理（单CDN） 此环境下，前端存在代理，并且使用了CDN，应为每个CDN厂商获取客户真实IP的方式并不一致，这里需要找到cdn厂商找到获取真实IP的方法，在按照步骤2进行。\n举例：\n阿里云cdn获取真实IP方法\n加速乐获取真实IP方法\n环境4：Envoy前段存在代理（多CDN） 由于各CDN的带宽、价格、使用场景等因素，在实际情况下，可能使用多种CDN；如：正常情况下使用cdn加速，遇到攻击时切换安全防御高的CDN。一般仅加速的CDN价格比带防御的要便宜很多。\n此处Enovy待更新，后端应用可根据CDN的http头正常获取IP\n环境5：内部代理 无特殊需求可无需配置\n","permalink":"https://www.161616.top/envoy-real-ip/","summary":"在envoy作为前端代理时，用户ip的获取很重要，一般获取ip的方式。都是通过Header中的 X-Forward-For、 X-Real-IP或 Remote addr 等属性获取，但是如果确保Envoy可以获取到的ip是真实的用户ip呢？本篇继续解密！\n概念说明 Remote Address 是nginx与客户端进行TCP连接过程中，获得的客户端真实地址。Remote Address 无法伪造，因为建立 TCP 连接需要三次握手，如果伪造了源 IP，无法建立 TCP 连接，更不会有后面的 HTTP 请求。 一般情况下，在Envoy作为最外层代理时，此IP为真实的IP客户端IP\nX-Real-IP 是一个自定义头。X-Real-Ip 通常被 HTTP 代理用来表示与它产生 TCP 连接的设备 IP，这个设备可能是其他代理，也可能是真正的请求端。X-Real-Ip 目前并不属于任何标准，代理和 Web 应用之间可以约定用任何自定义头来传递这个信息。\nX-Forwarded-For X-Forwarded-For 是一个扩展头。HTTP/1.1（RFC 2616）协议并没有对它的定义，它最开始是由 Squid 这个缓存代理软件引入，用来表示 HTTP 请求端真实 IP，现在已经成为事实上的标准，被各大 HTTP 代理、负载均衡等转发服务广泛使用，并被写入 RFC 7239（Forwarded HTTP Extension）标准之中。通常，X-Forwarded-For可被伪造，并且使用CDN会被重写\nEnvoy中如何获取真实IP 在Envoy中，涉及到客户端IP的配置如下： use_remote_address： 默认值false，设置为true，使用客户端连接的真实远程地址，false是使用x-forwarded-for skip_xff_append： 设置为true，则不会将远程地址附加到x-forwarded-for中 request_headers_to_add 添加请求头 request_headers_to_remove 删除一个请求头\n实验环境配置准备 yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 admin: access_log_path: /dev/null address: socket_address: { address: 0.","title":"记录经过envoy代理后获取客户端真实IP"},{"content":"作为go应用存在二进制文件却不能执行 明明镜像中有对应的二进制文件，但是执行时却提示 not found 或 no such file 或 standard_init_linux.go:211: exec user process caused \u0026quot;no such file or directory\u0026quot;\n网上常说都是因为windows换行符编码问题。此处实际问题是该二进制文件是使用动态链接方式编译.\n解决方法：\ntext 1 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build --ldflags \u0026#34;-extldflags -static\u0026#34; 注意：CGO_ENABLED=0 GOOS=linux GOARCH=amd64 和 cgo_enabled=0 goos=linux goarch=amd64 是有区别的。\n保存信息\n诸如此类信息都是上述问题\ntext 1 2 3 4 standard_init_linux.go:211: exec user process caused \u0026#34;no such file or directory\u0026#34; /tmp # ./envoy_end /bin/sh: ./envoy_end: not found 替换为国内源 text 1 RUN sed -i \u0026#39;s@http://dl-cdn.alpinelinux.org/@https://mirrors.aliyun.com/@g\u0026#39; /etc/apk/repositories 基于alpine制作PHP镜像 alpine包搜索 https://pkgs.alpinelinux.org/\n安装依赖库 apk add --no-cache xxx\n基于php apline镜像自行增加或删除扩展。 offcial-repo\n增加扩展可以使用 pecl install xxx 如 pecl install redis\n如果不能使用此种方法安装可以使用，git clone 下来在进行编译，编译成功后 docker-php-ext-enable xxx启动扩展。\n此中方式制作镜像，常见扩展安装完成后，容器大小可控制在100M左右\n参考资料：What is .build-deps for apk add \u0026ndash;virtual command?\n","permalink":"https://www.161616.top/alpine-trouble-q-and-a/","summary":"作为go应用存在二进制文件却不能执行 明明镜像中有对应的二进制文件，但是执行时却提示 not found 或 no such file 或 standard_init_linux.go:211: exec user process caused \u0026quot;no such file or directory\u0026quot;\n网上常说都是因为windows换行符编码问题。此处实际问题是该二进制文件是使用动态链接方式编译.\n解决方法：\ntext 1 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build --ldflags \u0026#34;-extldflags -static\u0026#34; 注意：CGO_ENABLED=0 GOOS=linux GOARCH=amd64 和 cgo_enabled=0 goos=linux goarch=amd64 是有区别的。\n保存信息\n诸如此类信息都是上述问题\ntext 1 2 3 4 standard_init_linux.go:211: exec user process caused \u0026#34;no such file or directory\u0026#34; /tmp # ./envoy_end /bin/sh: ./envoy_end: not found 替换为国内源 text 1 RUN sed -i \u0026#39;s@http://dl-cdn.alpinelinux.org/@https://mirrors.aliyun.com/@g\u0026#39; /etc/apk/repositories 基于alpine制作PHP镜像 alpine包搜索 https://pkgs.","title":"使用alpine为基础镜像Q\u0026A"},{"content":"CentOS7 / CentOS8 设置终端屏幕分辨率 Centos7 修改文件 /boot/grub2/grub.cfg\n搜索 linux16\ntext 1 2 vmlinuz-3.10.0-123.el7.x86_64 root=UUID=881ac4e6-4a55-47b1-b864-555de7051763 ro rd.lvm.lv=centos/swap vconsole.font=latarcyrheb-sun16 rd.lvm.lv=centos/root crashkernel=auto vconsole.keymap=us rhgb quiet LANG=en_US.UTF-8 添加如下,???具体看下表\ntext 1 2 3 vga=0x??? vga=0x340 CentOS 8 CentOS8 使用了 blsgcfg来解析文件生成菜单项。菜单项配置文件在/boot/loader/entries/下，每一个文件表示一个启动项。\n这里需要修改启动项的参数，这里修改options，实际上是修改了 /boot/grub2/grubenv对应的值。\ntext 1 options $kernelopts $tuned_params 可以直接修改/etc/sysconfig/grub中的GRUB_CMDLINE_LINUX值后增加=加vga=0x??? （对照分辨率表修改???）。\n修改完成后执行 grub2-mkconfig -o /boot/grub2/grub.cfg 重启即修改了/boot/grub2/grubenv对应的值。\ncentos7 启动引导顺序 查看默认启动项 grub2-editenv list 查看启动项列表 awk -F\\' '$1==\u0026quot;menuentry \u0026quot; {print $2}' /etc/grub2.cfg 设置默认引导 grub2-set-default 'Windows 10' 设置默认启动项 grub2-set-default 2 需要按照启动项列表顺序 重新生成grub2.cfg grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg 修改对应配置 cat /etc/default/grub centos7 精简开机自启动 centos7 精简开机自启动\nntsysv rsyslog crond sshd network\n","permalink":"https://www.161616.top/centos-configration/","summary":"CentOS7 / CentOS8 设置终端屏幕分辨率 Centos7 修改文件 /boot/grub2/grub.cfg\n搜索 linux16\ntext 1 2 vmlinuz-3.10.0-123.el7.x86_64 root=UUID=881ac4e6-4a55-47b1-b864-555de7051763 ro rd.lvm.lv=centos/swap vconsole.font=latarcyrheb-sun16 rd.lvm.lv=centos/root crashkernel=auto vconsole.keymap=us rhgb quiet LANG=en_US.UTF-8 添加如下,???具体看下表\ntext 1 2 3 vga=0x??? vga=0x340 CentOS 8 CentOS8 使用了 blsgcfg来解析文件生成菜单项。菜单项配置文件在/boot/loader/entries/下，每一个文件表示一个启动项。\n这里需要修改启动项的参数，这里修改options，实际上是修改了 /boot/grub2/grubenv对应的值。\ntext 1 options $kernelopts $tuned_params 可以直接修改/etc/sysconfig/grub中的GRUB_CMDLINE_LINUX值后增加=加vga=0x??? （对照分辨率表修改???）。\n修改完成后执行 grub2-mkconfig -o /boot/grub2/grub.cfg 重启即修改了/boot/grub2/grubenv对应的值。\ncentos7 启动引导顺序 查看默认启动项 grub2-editenv list 查看启动项列表 awk -F\\' '$1==\u0026quot;menuentry \u0026quot; {print $2}' /etc/grub2.cfg 设置默认引导 grub2-set-default 'Windows 10' 设置默认启动项 grub2-set-default 2 需要按照启动项列表顺序 重新生成grub2.","title":"centos配置"},{"content":"outlier detection 在异常检测领域中，常常需要决定新观察的点是否属于与现有观察点相同的分布（则它称为inlier），或者被认为是不同的（称为outlier）。离群是异常的数据，但是不一定是错误的数据点。\n在Envoy中，离群点检测是动态确定上游集群中是否有某些主机表现不正常，然后将它们从正常的负载均衡集群中删除的过程。outlier detection可以与healthy check同时/独立启用，并构成整个上游运行状况检查解决方案的基础。\n此处概念不做过多的说明，具体可以参考官方文档与自行google\n监测类型 连续的5xx 连续的网关错误 连续的本地来源错误 更多介绍参考官方文档 outlier detection\n离群检测测试 说明，此处只能在单机环境测试更多还的参考与实际环境\n环境准备 docker-compose 模拟后端5个节点\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 ports: - 80:80 - 443:443 - 82:9901 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml networks: envoymesh: aliases: - envoy depends_on: - webserver1 - webserver2 webserver1: image: cylonchau/envoy-end:latest networks: envoymesh: aliases: - myservice - webservice expose: - 90 webserver2: image: cylonchau/envoy-end:latest networks: envoymesh: aliases: - myservice - webservice expose: - 90 webserver3: image: cylonchau/envoy-end:latest networks: envoymesh: aliases: - myservice - webservice expose: - 90 webserver4: image: cylonchau/envoy-end:latest networks: envoymesh: aliases: - myservice - webservice expose: - 90 webserver5: image: cylonchau/envoy-end:latest networks: envoymesh: aliases: - myservice - webservice expose: - 90 networks: envoymesh: {} envoy 配置文件\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 admin: access_log_path: /dev/null address: socket_address: { address: 0.0.0.0, port_value: 9901 } static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy_http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http codec_type: AUTO route_config: name: local_route virtual_hosts: - name: local_service domains: [ \u0026#34;*\u0026#34; ] routes: - match: { prefix: \u0026#34;/\u0026#34; } route: { cluster: local_service } http_filters: - name: envoy.filters.http.router clusters: - name: local_service connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: local_service endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: webservice, port_value: 90 } health_checks: timeout: 3s interval: 90s unhealthy_threshold: 5 healthy_threshold: 5 no_traffic_interval: 240s http_health_check: path: \u0026#34;/ping\u0026#34; expected_statuses: start: 200 end: 201 outlier_detection: consecutive_5xx: 2 base_ejection_time: 30s max_ejection_percent: 40 interval: 20s success_rate_minimum_hosts: 5 success_rate_request_volume: 10 配置说明 text 1 2 3 4 5 6 7 outlier_detection: consecutive_5xx: 2 # 连续的5xx错误数量 base_ejection_time: 30s # 弹出主机的基准时间。实际时间等于基本时间乘以主机弹出的次数 max_ejection_percent: 40 # 可弹出主机集群的最大比例，默认值为10% ，此处为40% 即集群中5个节点的2个节点 interval: 20s # 间隔时间 success_rate_minimum_hosts: 5 # 集群中最小主机数量 success_rate_request_volume: 10 # 在一个时间间隔内中收集请求检测的最小数量 此处为了效果，将主动检测状态时间增加，主机弹出时间增加\n路由 /502bad 模拟一个502的错误\n运行结果 模拟一些5xx请求和200请求\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 workers envoy_1 | [2020-09-13 06:10:01.093][1][warning][main] [source/server/server.cc:537] there is no configured limit to the number of allowed active connections. Set a limit via the runtime key overload.global_downstream_max_connections webserver2_1 | [GIN] 2020/09/13 - 06:10:08 | 200 | 63.272?s | 172.22.0.7 | GET \u0026#34;/\u0026#34; webserver5_1 | [GIN] 2020/09/13 - 06:10:10 | 200 | 46.732?s | 172.22.0.7 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 06:10:11 | 200 | 45.43?s | 172.22.0.7 | GET \u0026#34;/\u0026#34; webserver3_1 | [GIN] 2020/09/13 - 06:10:13 | 502 | 43.858?s | 172.22.0.7 | GET \u0026#34;/502bad\u0026#34; webserver4_1 | [GIN] 2020/09/13 - 06:10:14 | 502 | 47.486?s | 172.22.0.7 | GET \u0026#34;/502bad\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 06:10:15 | 200 | 15.691?s | 172.22.0.7 | GET \u0026#34;/\u0026#34; webserver5_1 | [GIN] 2020/09/13 - 06:10:16 | 200 | 14.719?s | 172.22.0.7 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 06:10:16 | 200 | 15.758?s | 172.22.0.7 | GET \u0026#34;/\u0026#34; webserver3_1 | [GIN] 2020/09/13 - 06:10:17 | 502 | 15.697?s | 172.22.0.7 | GET \u0026#34;/502bad\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 06:10:17 | 502 | 14.002?s | 172.22.0.7 | GET \u0026#34;/502bad\u0026#34; webserver5_1 | [GIN] 2020/09/13 - 06:10:17 | 502 | 14.913?s | 172.22.0.7 | GET \u0026#34;/502bad\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 06:10:18 | 502 | 14.911?s | 172.22.0.7 | GET \u0026#34;/502bad\u0026#34; webserver4_1 | [GIN] 2020/09/13 - 06:10:18 | 502 | 30.429?s | 172.22.0.7 | GET \u0026#34;/502bad\u0026#34; webserver5_1 | [GIN] 2020/09/13 - 06:10:19 | 200 | 14.377?s | 172.22.0.7 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 06:10:19 | 200 | 14.861?s | 172.22.0.7 | GET \u0026#34;/\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 06:10:19 | 200 | 18.924?s | 172.22.0.7 | GET \u0026#34;/\u0026#34; webserver5_1 | [GIN] 2020/09/13 - 06:10:19 | 200 | 15.899?s | 172.22.0.7 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 06:10:19 | 200 | 24.849?s | 172.22.0.7 | GET \u0026#34;/\u0026#34; 集群已弹出 20%的节点，健康检查结果为 failed_outlier_check\n请求已分配到其余三台节点\n30秒后，弹出主机已回复正常\n再次模拟请求\n30秒后，如在时间间隔内，无新增请求，节点依旧为 failed_outlier_check，有新增请求时恢复。\n","permalink":"https://www.161616.top/outlier-detection/","summary":"outlier detection 在异常检测领域中，常常需要决定新观察的点是否属于与现有观察点相同的分布（则它称为inlier），或者被认为是不同的（称为outlier）。离群是异常的数据，但是不一定是错误的数据点。\n在Envoy中，离群点检测是动态确定上游集群中是否有某些主机表现不正常，然后将它们从正常的负载均衡集群中删除的过程。outlier detection可以与healthy check同时/独立启用，并构成整个上游运行状况检查解决方案的基础。\n此处概念不做过多的说明，具体可以参考官方文档与自行google\n监测类型 连续的5xx 连续的网关错误 连续的本地来源错误 更多介绍参考官方文档 outlier detection\n离群检测测试 说明，此处只能在单机环境测试更多还的参考与实际环境\n环境准备 docker-compose 模拟后端5个节点\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.","title":"Envoy 离群检测"},{"content":"实验文件 docker-compose\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 - HEALTHY=ok ports: - 80:80 - 443:443 - 82:9901 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml - ./certs:/etc/envoy/certs networks: envoymesh: aliases: - envoy depends_on: - webserver1 - webserver2 webserver1: image: cylonchau/envoy-end:latest environment: - COLORFUL=blue - HEALTHY=ok networks: envoymesh: aliases: - myservice - webservice expose: - 90 webserver2: image: cylonchau/envoy-end:latest environment: - COLORFUL=blue networks: envoymesh: aliases: - myservice - webservice expose: - 90 networks: envoymesh: {} envoy配置文件\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 admin: access_log_path: /dev/null address: socket_address: { address: 0.0.0.0, port_value: 9901 } static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy_http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http codec_type: AUTO route_config: name: local_route virtual_hosts: - name: local_service domains: [ \u0026#34;*\u0026#34; ] routes: - match: { prefix: \u0026#34;/\u0026#34; } route: { cluster: local_service } http_filters: - name: envoy.filters.http.router clusters: - name: local_service connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: local_service endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: webservice, port_value: 90 } health_checks: timeout: 1s interval: 10s unhealthy_threshold: 5 healthy_threshold: 5 http_health_check: path: \u0026#34;/ping\u0026#34; expected_statuses: start: 200 end: 201 路由 /ping 健康监测的路由 /ping/ok 手动将节点设置为有效节点 /ping/fail 手动将节点设置为失效\n测试结论 text 1 2 3 $ curl -s 127.0.0.1:82/clusters|grep health local_service::172.22.0.2:90::health_flags::healthy local_service::172.22.0.3:90::health_flags::healthy 当在集群启动时，所有节点默认为健康状态，在没有流量进入时，默认的间隔时间为1分钟。\n当有外部流量进入后，在结束上个默认间隔1分钟之后，会成为配置文件设置的默认10s\n手动设置一个节点为不健康状态，\n日志中可以看出，在手动设置为失效时，请求是不会到达后端失效节点，并且第一次请求时间明显长，在设置为成功时，后端节点判定为健康是在4次健康监测而非正常请求\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 webserver1_1 | [GIN] 2020/09/13 - 02:00:48 | 200 | 110.706µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:00:48 | 200 | 47.29µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:00:49 | 200 | 14.909µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:01:18 | 200 | 58.53µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:01:19 | 200 | 15.988µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:01:42 | 200 | 20.844µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:01:42 | 200 | 12.247µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:01:52 | 200 | 38.892µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:01:52 | 200 | 32.254µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:01:54 | 200 | 33.689µs | ::1 | GET \u0026#34;/ping/fail\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:01:59 | 200 | 82.86µs | ::1 | GET \u0026#34;/ping/fail\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:02 | 200 | 155.202µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:02:02 | 502 | 26.73µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:07 | 200 | 19.193µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:08 | 200 | 14.651µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:09 | 200 | 15.101µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:09 | 200 | 15.294µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:10 | 200 | 26.45µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:10 | 200 | 17.679µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:11 | 200 | 14.703µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:11 | 200 | 14.546µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:02:12 | 502 | 8.37µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:12 | 200 | 14.214µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:02:22 | 502 | 8.998µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:22 | 200 | 13.489µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:02:30 | 200 | 119.326µs | ::1 | GET \u0026#34;/ping/ok\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:32 | 200 | 8.864µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:02:32 | 200 | 14.679µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:38 | 200 | 14.781µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:39 | 200 | 15.452µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:39 | 200 | 14.825µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:40 | 200 | 14.784µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:40 | 200 | 14.788µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:41 | 200 | 72.985µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:02:42 | 200 | 8.523µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:42 | 200 | 14.497µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:42 | 200 | 15.611µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:47 | 200 | 46.065µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:47 | 200 | 19.455µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:47 | 200 | 15.079µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:48 | 200 | 22.208µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:02:52 | 200 | 39.693µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:02:52 | 200 | 32.376µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:03:02 | 200 | 19.476µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:03:02 | 200 | 11.041µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:03:12 | 200 | 14.292µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:03:12 | 200 | 8.215µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:03:22 | 200 | 16.145µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:03:22 | 200 | 11.455µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:03:29 | 200 | 15.02µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:03:30 | 200 | 34.405µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:03:30 | 200 | 14.647µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:03:30 | 200 | 15.039µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:03:31 | 200 | 16.706µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:03:31 | 200 | 15.667µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:03:31 | 200 | 15.025µs | 172.22.0.4 | GET \u0026#34;/\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:03:32 | 200 | 15.085µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:03:32 | 200 | 13.446µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver2_1 | [GIN] 2020/09/13 - 02:03:42 | 200 | 14.702µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; webserver1_1 | [GIN] 2020/09/13 - 02:03:42 | 200 | 9.262µs | 172.22.0.4 | GET \u0026#34;/ping\u0026#34; 无外部流量时的请求间隔设置 官方参考\ntext 1 no_traffic_interval ","permalink":"https://www.161616.top/initiative-health-check/","summary":"实验文件 docker-compose\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 - HEALTHY=ok ports: - 80:80 - 443:443 - 82:9901 volumes: - .","title":"Envoy的主动健康监测"},{"content":"方案架构 本次实例与官方Envoy front_proxy Example相似，首先会有一个Envoy单独运行。ingress的工作是给其他地方提供一个入口。来自外部的传入连接请求到这里，前端代理将会决定他们在内部的转发路径。 图源自Envoy官网文档 front_proxy\n生成证书 text 1 openssl req -nodes -new -x509 -keyout certs/server.key -out certs/server.crt -days 365 -subj \u0026#34;/C=CN/ST=Guangdong/L=Guangzhou/O=studyenvoy/OU=studyenvoy/CN=*.studyenvoy.cn\u0026#34; envoy配置说明 v3 api中envoy去掉了tls_context的配置，配置tls首先需要熟悉envoy的如下两个术语\nDownstream：下游主机连接到 Envoy，发送请求并或获得响应。 Upstream：上游主机获取来自 Envoy 的链接请求和响应。 本次使用的是ingress的代理，需要配置的即为 Downstream\nv3api中使用的是transport_socket，transport_socket为 listeners 当中某一个 filter_chains 中上线文中的配置。\ntransport_socket 官方说明为： (config.core.v3.TransportSocket) Optional custom transport socket implementation to use for downstream connections. To setup TLS, set a transport socket with name tls and DownstreamTlsContext in the typed_config. If no transport socket configuration is specified, new connections will be set up with plaintext.\n查看官网的transport_socket配置说明\n这里使用的类型为DownstreamTlsContext\ntext 1 2 3 4 5 6 7 8 9 10 transport_socket: # 设置tls name: envoy.transport_sockets.tls # 定义名称，不能为空 typed_config: # 实现配置的类型 \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: # 设置tls上下文 tls_certificates： certificate_chain： # 公钥设置 必须设置为，filename，inline_bytes filename: \u0026#34;/etc/envoy/certs/server.crt\u0026#34; private_key: # 私钥设置 必须设置为，filename，inline_bytes filename: \u0026#34;/etc/envoy/certs/server.key\u0026#34; 准备envoy和后端服务运行环境 envoy配置文件 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 admin: access_log_path: /dev/null address: socket_address: { address: 0.0.0.0, port_value: 9901 } static_resources: listeners: - name: listeners_http address: socket_address: { address: 0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy.http_connenttion_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http codec_type: AUTO route_config: name: local_route virtual_hosts: - name: local_service domains: [ \u0026#34;*\u0026#34; ] routes: - match: { prefix: \u0026#34;/\u0026#34; } redirect: path_redirect: \u0026#34;/\u0026#34; https_redirect: true http_filters: - name: envoy.router - name: listener_https address: socket_address: { address: 0.0.0.0, port_value: 443 } filter_chains: - filters: - name: envoy.http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http codec_type: AUTO route_config: name: local_route virtual_hosts: - name: local_service domains: [ \u0026#34;*\u0026#34; ] routes: - match: { prefix: \u0026#34;/\u0026#34; } route: { cluster: local_service } http_filters: - name: envoy.router transport_socket: name: envoy.transport_sockets.tls typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: certificate_chain: filename: \u0026#34;/etc/envoy/certs/server.crt\u0026#34; private_key: filename: \u0026#34;/etc/envoy/certs/server.key\u0026#34; clusters: - name: local_service connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: local_service endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: webservice, port_value: 90 } docker-compose文件示例\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 ports: - 80:80 - 443:443 - 82:9901 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml - ./certs:/etc/envoy/certs networks: envoymesh: aliases: - envoy depends_on: - webserver webserver: image: cylonchau/envoy-end:latest environment: - COLORFUL=blue networks: envoymesh: aliases: - myservice - webservice expose: - 90 networks: envoymesh: {} 容器启动正常\n证书使用者也为生成证书的信息一致\n","permalink":"https://www.161616.top/envoy-v3-api-tls/","summary":"方案架构 本次实例与官方Envoy front_proxy Example相似，首先会有一个Envoy单独运行。ingress的工作是给其他地方提供一个入口。来自外部的传入连接请求到这里，前端代理将会决定他们在内部的转发路径。 图源自Envoy官网文档 front_proxy\n生成证书 text 1 openssl req -nodes -new -x509 -keyout certs/server.key -out certs/server.crt -days 365 -subj \u0026#34;/C=CN/ST=Guangdong/L=Guangzhou/O=studyenvoy/OU=studyenvoy/CN=*.studyenvoy.cn\u0026#34; envoy配置说明 v3 api中envoy去掉了tls_context的配置，配置tls首先需要熟悉envoy的如下两个术语\nDownstream：下游主机连接到 Envoy，发送请求并或获得响应。 Upstream：上游主机获取来自 Envoy 的链接请求和响应。 本次使用的是ingress的代理，需要配置的即为 Downstream\nv3api中使用的是transport_socket，transport_socket为 listeners 当中某一个 filter_chains 中上线文中的配置。\ntransport_socket 官方说明为： (config.core.v3.TransportSocket) Optional custom transport socket implementation to use for downstream connections. To setup TLS, set a transport socket with name tls and DownstreamTlsContext in the typed_config. If no transport socket configuration is specified, new connections will be set up with plaintext.","title":"Envoy V3APi 开启 TLS"},{"content":"镜像内安装包失败处理 方法一：修改Dockerfile，在Dockerfile中增加如下\nubuntu示例\ntext 1 2 RUN sed -i \u0026#39;s/archive.ubuntu.com/mirrors.aliyun.com/g\u0026#39; /etc/apt/sources.list RUN sed -i \u0026#39;s/security.ubuntu.com/mirrors.aliyun.com/g\u0026#39; /etc/apt/sources.list apline示例\ntext 1 RUN sed -i \u0026#39;s@http://dl-cdn.alpinelinux.org/@https://mirrors.aliyun.com/@g\u0026#39; /etc/apk/repositories 方法二：使用http代理，\nubuntu 参考 命令行使用代理\n下载镜像失败处理 方法一：docker宿主机使用ss，开启局域网可连接。同局域网中的都可直接连此代理 方法二： docker systemd的 service文件中增加http代理\n可看到已经可以成功运行envoy example示例\ncannot bind \u0026lsquo;0.0.0.0:80\u0026rsquo;: Permission denied docker-compose文件\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml network_mode: \u0026#34;service:mainserver\u0026#34; depends_on: - mainserver mainserver: image: cylonchau/envoy-end:latest networks: envoymesh: aliases: - webserver - httpserver - envoy_end networks: envoymesh: {} 启动时报错\ntext 1 2 3 4 envoy_1 | [2020-09-06 07:09:48.618][8][critical][main] [source/server/server.cc:101] error initializing configuration \u0026#39;/etc/envoy/envoy.yaml\u0026#39;: cannot bind \u0026#39;0.0.0.0:80\u0026#39;: Permission denied envoy_1 | [2020-09-06 07:09:48.618][8][info][main] [source/server/server.cc:704] exiting envoy_1 | cannot bind \u0026#39;0.0.0.0:80\u0026#39;: Permission denied root_envoy_1 exited with code 1 参考 list\ntext 1 2 environment: - \u0026#34;ENVOY_UID=0\u0026#34; ","permalink":"https://www.161616.top/envoy-example-failed/","summary":"镜像内安装包失败处理 方法一：修改Dockerfile，在Dockerfile中增加如下\nubuntu示例\ntext 1 2 RUN sed -i \u0026#39;s/archive.ubuntu.com/mirrors.aliyun.com/g\u0026#39; /etc/apt/sources.list RUN sed -i \u0026#39;s/security.ubuntu.com/mirrors.aliyun.com/g\u0026#39; /etc/apt/sources.list apline示例\ntext 1 RUN sed -i \u0026#39;s@http://dl-cdn.alpinelinux.org/@https://mirrors.aliyun.com/@g\u0026#39; /etc/apk/repositories 方法二：使用http代理，\nubuntu 参考 命令行使用代理\n下载镜像失败处理 方法一：docker宿主机使用ss，开启局域网可连接。同局域网中的都可直接连此代理 方法二： docker systemd的 service文件中增加http代理\n可看到已经可以成功运行envoy example示例\ncannot bind \u0026lsquo;0.0.0.0:80\u0026rsquo;: Permission denied docker-compose文件\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest volumes: - .","title":"envoy官方example运行失败问题处理"},{"content":"official front proxy\n在一个Services Mash内部中，都会存在一到多个微服务，为了将南北向流量统一引入网格内部管理，通常存在单独运行的envoy实例。\nEnvoy的listener支持面向下游客户端一侧的TLS会话，并可选地支持验正客户端证书；\nlistener中用到的数字证书可于配置中静态提供，也可借助于SDS动态获取;\ntls_context 是 listeners 当中某一个 filter_chains 中上线文中的配置，tls_context 配置在哪个 listeners当中就隶属于哪个listeners。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 listeners: ... filter_chains: - filters: .. tls_context: common_tls_context: {} # 常规证书相关设置； tls_params: {} # TLS协议版本，加密套件等； tls_certifcates: [] # 用到的证书和私钥文件； - certifcate_chain: {} filename: # 证书文件路径; private_key: {} # 私钥; filename: # 私钥文件路径; password: {} # 私钥口令； filename: # 口令文件路径； tls_certifcate_sds_secret_configs: [] # 要基于SDS API获取TLS会话的相关信息时的配置； require_client_certifcate: # 是否验证客户端证书； 例如，下面示例将前面的Ingress示例中的Envoy配置为通过TLS提供服务，并将所有基于http协议的请求重定向至https；\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 admin: access_log_path: /dev/null address: socket_address: { address: 0.0.0.0, port_value: 9901 } static_resources: listeners: - name: listeners_http address: socket_address: { address: 0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy.http_connenttion_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http codec_type: AUTO route_config: name: local_route virtual_hosts: - name: local_service domains: [ \u0026#34;*\u0026#34; ] routes: - match: { prefix: \u0026#34;/\u0026#34; } redirect: path_redirect: \u0026#34;/\u0026#34; https_redirect: true http_filters: - name: envoy.router - name: listener_https address: socket_address: { address: 0.0.0.0, port_value: 443 } filter_chains: - filters: - name: envoy.http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http codec_type: AUTO route_config: name: local_route virtual_hosts: - name: local_service domains: [ \u0026#34;*\u0026#34; ] routes: - match: { prefix: \u0026#34;/\u0026#34; } route: { cluster: local_service } http_filters: - name: envoy.router transport_socket: name: envoy.transport_sockets.tls typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: certificate_chain: filename: \u0026#34;/etc/envoy/certs/server.crt\u0026#34; private_key: filename: \u0026#34;/etc/envoy/certs/server.key\u0026#34; clusters: - name: local_service connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: local_service endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: webservice, port_value: 90 } 将自定义配置组织于专用目录（例如ingres.htps_proxy/）下的envoy.yaml文件中，并创建Dockerfile文件构建自定义镜像；\n生成测试使用的数字证书； text 1 2 3 4 5 mkdir -p certs openssl req -nodes -new -x509 -keyout certs/server.key -out certs/server.crt -days 3650 -subj \u0026#39;/CN=ik8s.io/O=MageEdu LTD./C=CN\u0026#39; openssl req -nodes -new -key certs/server.key -out certs/server.crt -days 365 -subj \u0026#34;/C=CN/ST=Guangdong/L=Guangzhou/O=xdevops/OU=xdevops/CN=*.xdevops.cn\u0026#34; docker-compose文件示例\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 ports: - 80:80 - 443:443 - 82:9901 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml - ./certs:/etc/envoy/certs networks: envoymesh: aliases: - envoy depends_on: - webserver webserver: image: sealloong/envoy-end:latest environment: - COLORFUL=blue networks: envoymesh: aliases: - myservice - webservice expose: - 90 networks: envoymesh: {} ","permalink":"https://www.161616.top/envoy-tls-configuration/","summary":"official front proxy\n在一个Services Mash内部中，都会存在一到多个微服务，为了将南北向流量统一引入网格内部管理，通常存在单独运行的envoy实例。\nEnvoy的listener支持面向下游客户端一侧的TLS会话，并可选地支持验正客户端证书；\nlistener中用到的数字证书可于配置中静态提供，也可借助于SDS动态获取;\ntls_context 是 listeners 当中某一个 filter_chains 中上线文中的配置，tls_context 配置在哪个 listeners当中就隶属于哪个listeners。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 listeners: ... filter_chains: - filters: .. tls_context: common_tls_context: {} # 常规证书相关设置； tls_params: {} # TLS协议版本，加密套件等； tls_certifcates: [] # 用到的证书和私钥文件； - certifcate_chain: {} filename: # 证书文件路径; private_key: {} # 私钥; filename: # 私钥文件路径; password: {} # 私钥口令； filename: # 口令文件路径； tls_certifcate_sds_secret_configs: [] # 要基于SDS API获取TLS会话的相关信息时的配置； require_client_certifcate: # 是否验证客户端证书； 例如，下面示例将前面的Ingress示例中的Envoy配置为通过TLS提供服务，并将所有基于http协议的请求重定向至https；","title":"Envoy TLS 配置"},{"content":"常用的构建方法 https://skyao.io/learning-envoy/\n手动配置构建环境，手动构建Envoy。\n使用Envoy提供的预制于Docker中的构建环境进行手动构建二进制Envoy\n使用Envoy提供的预制于Docker中的构建环境进行手动构建二进制Envoy，并直接将Envoy程序打包成Docker镜像\n提供Bootstrap配置文件，存在使用xDS的动态资源时，还需要基于文件或管理服务器向其提供必须的配置信息\n也可使用Envoy的配置生成器生成示例性配置 基于Bootstrap配置文件启动Envoy实例\n直接启动二进制Envoy 于Kubernetes平台基于Sidecar的形式运行Envoy，或运行单独的Envoy Pod（Edge Proxy） 启动Envoy 启动Envoy时，需要通过（-c 选项为其指定初始配置文件以提供引导配置（Bootstrap configuration），这也是使用v2APl的必然要求；\ntext 1 envoy -c \u0026lt;path to config\u0026gt;.{json,yaml,pb,pb_text} 扩展名代表了配置信息的组织格式；\n引导配置是Envoy配置信息的基点，用于承载Envoy的初始配置，它可能包括静态资源和动态资源的定义\n静态资源（static_resources）于启动直接加载\n动态资源（dynamic_resources）则需要通过配置的xDS服务获取并生成\n通常，Listener 和 Cluster 是Envoy得以运行的基础，而二者的配置可以全部为静态格式，也可以混合使用动态及静态方式提供，或者全部配置为动态；\n一个yaml格式纯静态的基础配置框架类似如下所示：\nyaml 1 2 3 4 5 6 7 8 9 10 11 static_resources: linteners: - name: ... address: {} filter_chains: [] clusters: - name: ... type: ... connect_timeout: {} lb_policy: ... load_assignment: {} Listener 简易静态配置 侦听器主要用于定义Envoy监听的用于接收Down streams请求的套接字、用于处理请求时调用的过滤器链及相关的其它配置属性;目前envoy仅支持tcp的协议\nyaml 1 2 3 4 listeners: - name: address: socket_address: { address: ..., port_value: ..., protocol: ...} L4过滤器echo主要用于演示网络过滤器APl的功能，它会回显接收到的所有数据至下游的请求者；在配置文件中调用时其名称为 envoy.echo ；\n下面是一个最简单的静态侦听器配置示例：\necho文档\nyaml 1 2 3 4 5 6 7 8 9 10 static_resources: listeners: - name: listener_0 address: socket_address: address: 0.0.0.0 port_value: 15001 filter_chains: - filters: - name: envoy.echo Cluster 静态配置 通常，集群代表了一组提供相同服务的上游服务器（端点）的组合，它可由用户静态配置，也能够通过CDS动态获取；\n集群需要在“预热”环节完成之后方能转为可用状态，这意味着集群管理器通过DNS解析或EDS服务完成端点初始化，以及健康状态检测成功之后才可用；\ncluster 官方参数\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 clusters: - name: ... # 集群的唯一名称，且为提供alt_stat_name时会被用于统计信息中 alt_state_name: ... # 统计信息中使用集群带名称。 # 用于解析集群（生成集群端点）时使用的服务发现类型，可用值有 STATIC STRJCT_DNS LOGICAL_DNS GRIGINAL_DST和EDS等 type: .. # 负载均衡算法，支持 ROUND_ROBIN LEAST_REQUEST RING_HASH RANDOM MAGLEV CLUSTER_PROVIDED lb_policy: # 为 STATIC STRJCT_DNS LOGICAL_DNS 类型的集群指定成员获取方式，EDS类型继承要使用eds_cluster_config字段配置。 loda_assignment: cluser_name: .. # 集群名称 endpoints: #端点列表 - locality: {} #表示上游主机所处的位置，通常以 region、none等进行标识。 lb_endpoints: # 属于指定位置的端点列表 - endpoint_name: #端点的名称 endpoint: # 端点的定义 socket_address: #端点地址标识 address: port_value: portocol: 静态Cluster的各端点可以在配置中直接给出，也可借助DNS服务进行动态发现；\n下面的示例直接给出了两个端点地址\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 clusters: - name: test_cluster connect_timeout: 0.25 type: STATIC lb_policy: ROUND_ROBIN load_assignment: cluster_name: test_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: 10.0.0.1, port_value: 80 } - endpoint: address: socket_address: { address: 10.0.0.2, port_value: 80 } filter tcp_proxy 官方文档\nTCP代理过滤器在下游客户端及上游集群之间执行1:1网络连接代理\n它可以单独用作隧道替换，也可以同其他过滤器（如MongoDB过滤器或速率限制过滤器）结合使用； TCP代理过滤器严格执行由全局资源管理于为每个上游集群的全局资源管理器设定的连接限制 TCP代理过滤器检查上游集群的资源管理器是否可以在不超过该集群的最大连接数的情况下创建连接； TCP代理过滤器可直接将请求路由至指定的集群，也能够在多个目标集群间基于权重进行调度转发； 配置示例：\nyaml 1 2 3 4 5 6 7 8 9 10 11 listeners: name: listener_0 address: socket_address: { address:0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy.tcp_proxy typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy stat_prefix: tcp_01 # 统计数据的前缀 cluster: test_cluster 下面的示例基于TCP代理将下游用户（本机）请求代理至外部的（egress）两个web服务器\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static_resources: listeners: name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy.tcp_proxy typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy stat_prefix: tcp_01 cluster: test_tcp clusters: - name: test_tcp connect_timeout: 0.5s type: STATIC lb_policy: ROUND_ROBIN load_assignment: cluster_name: test_tcp endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: 127.0.0.1, port_value: 90 } dockerfile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest ports: - 81:80 environment: - ENVOY_UID=0 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml network_mode: \u0026#34;service:mainserver\u0026#34; depends_on: - mainserver mainserver: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - webserver - httpserver - envoy_end networks: envoymesh: {} http_connection_manager http_connection_manager 通过引入L7过滤器链实现了对http协议的操纵，其中router过滤器用于配置路由转发；\n官方文档\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 listeners: - name: address: socket_address: { address: ..., port_value: ...., protocol:... } filter_chains: - filters: - name: envoy.http_connection_manager config: condec_type: .. # 链接管理器使用的编解码器类型，可用值有 AUTO HTTP1 HTTP2 stat_prefix: ... # 统计信息中使用的易读性的信息前缀。 route_config: ... # 静态路由配置，动态配置应该使用rds字段进行指定。 name: # 路由配置的名称 virtual_hosts: # 虚拟主机的路基名称，用于构成路由表 - name: ... # 虚拟主机的逻辑名称，用于统计信息，与路由无关 domains: [] # 虚拟主机匹配的域名列表，支持 * 通配符，匹配搜索次序为精确匹配、前缀匹配、后缀匹配及完全匹配。 routes: [] # 路由列表，按照顺序搜索，第一个匹配到的路由信息。 http_filters: # 定义http过滤器链 - name: envoy.router # 调用过滤器为envoy.router 处理请求时，Envoy首先根据下游客户端请求的 host 来搜索虚拟主机列表中各 virtual_host 中的domains 列表中的定义，第一个匹配到的 Domain 的定义所属的 virtual_host 即可处理请求的虚拟主机;\n而后搜索当前虚拟生机中的 routes 列表中的路由列表中各路由条目的 match 的定义，第一个匹配到的 match 后的路由机制（route.redirect或direct_response）即生效；\nroute_config.virtual_hosts.routes 配置的路由信息用于将下游的客户端请求路由至合适的上游集群中某Server上；\n其路由方式是将url匹配match字段的定义 match 字段可通过 prefix（前级）、path（路径）或 regex（正则表达式）三者之一来表示匹配模式； 与match相关的请求将由 route 、redirect 或 direct_response 三个字段其中之一完成路由； 由route定义的路由目标必须是 cluster（上游集群名称）、cluster_header（根据请求标头中的 cluster_header 的值确定目标集群）或 weighted_clusters（路由目标有多个集群，每个集群拥有一定的权重）其中之一； yaml 1 2 3 4 5 6 routes: [] - name: ... # 路由条目的名称 match: prefix: ... # 请求的URL的前缀 route: # 路由条目 cluster: # 目标下游集群。 Egress代理配置示例 下面是一个egree类型的Envoy配置示例，定义了两个virtual_host，不过，发往第二个virtual_host的请求将被重定向至第一个。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy.http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: egress_http codec_type: AUTO route_config: name: study_route virtual_hosts: - name: web_service_1 domains: [ \u0026#34;*.ik8s.io\u0026#34;, \u0026#34;ik8s.io\u0026#34; ] routes: - match: { prefix: \u0026#34;/\u0026#34; } route: { cluster: web_cluster_1 } - name: web_service_2 domains: [ \u0026#34;*.k8scast.cn\u0026#34;, \u0026#34;k8scast.cn\u0026#34; ] routes: - match: { prefix: \u0026#34;/\u0026#34; } redirect: host_redirect: \u0026#34;www.ik8s.io\u0026#34; http_filters: - name: envoy.filters.http.router clusters: - name: web_cluster_1 connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: web_cluster_1 endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: myservice, port_value: 90 } - name: web_cluster_2 connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: web_cluster_2 endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: webserver2, port_value: 90 } yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 ports: - 81:80 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml networks: envoymesh: aliases: - envoy depends_on: - webserver1 - webserver2 webserver1: image: sealloong/envoy-end:latest environment: - COLORFUL=red networks: envoymesh: aliases: - webservice - myservice expose: - 90 webserver2: image: sealloong/envoy-end:latest environment: - COLORFUL=blue networks: envoymesh: aliases: - myservice expose: - 90 networks: envoymesh: {} 测试结果\nsh 1 2 3 4 5 $ curl -H \u0026#39;Host: www.ik8s.io\u0026#39; 127.0.0.1:81/ping {\u0026#34;message\u0026#34;:\u0026#34;pong\u0026#34;} $ curl -H \u0026#39;Host: www.ik8s.io\u0026#39; 127.0.0.1:81/colorful {\u0026#34;message\u0026#34;:\u0026#34;hello with red.\u0026#34;}$ ingress 配置示例 yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy_http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http codec_type: AUTO route_config: name: local_route virtual_hosts: - name: local_service domains: [ \u0026#34;*\u0026#34; ] routes: - match: { prefix: \u0026#34;/\u0026#34; } route: { cluster: local_service } http_filters: - name: envoy.filters.http.router clusters: - name: local_service connect_timeout: 0.25s type: STATIC lb_policy: ROUND_ROBIN load_assignment: cluster_name: local_service endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: 127.0.0.1, port_value: 90 } yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 version: \u0026#34;3\u0026#34; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml network_mode: \u0026#34;service:webserver\u0026#34; depends_on: - webserver webserver: image: sealloong/envoy-end:latest environment: - COLORFUL=red networks: envoymesh: aliases: - webservice - myservice expose: - 90 networks: envoymesh: {} ","permalink":"https://www.161616.top/envoy-deployment/","summary":"常用的构建方法 https://skyao.io/learning-envoy/\n手动配置构建环境，手动构建Envoy。\n使用Envoy提供的预制于Docker中的构建环境进行手动构建二进制Envoy\n使用Envoy提供的预制于Docker中的构建环境进行手动构建二进制Envoy，并直接将Envoy程序打包成Docker镜像\n提供Bootstrap配置文件，存在使用xDS的动态资源时，还需要基于文件或管理服务器向其提供必须的配置信息\n也可使用Envoy的配置生成器生成示例性配置 基于Bootstrap配置文件启动Envoy实例\n直接启动二进制Envoy 于Kubernetes平台基于Sidecar的形式运行Envoy，或运行单独的Envoy Pod（Edge Proxy） 启动Envoy 启动Envoy时，需要通过（-c 选项为其指定初始配置文件以提供引导配置（Bootstrap configuration），这也是使用v2APl的必然要求；\ntext 1 envoy -c \u0026lt;path to config\u0026gt;.{json,yaml,pb,pb_text} 扩展名代表了配置信息的组织格式；\n引导配置是Envoy配置信息的基点，用于承载Envoy的初始配置，它可能包括静态资源和动态资源的定义\n静态资源（static_resources）于启动直接加载\n动态资源（dynamic_resources）则需要通过配置的xDS服务获取并生成\n通常，Listener 和 Cluster 是Envoy得以运行的基础，而二者的配置可以全部为静态格式，也可以混合使用动态及静态方式提供，或者全部配置为动态；\n一个yaml格式纯静态的基础配置框架类似如下所示：\nyaml 1 2 3 4 5 6 7 8 9 10 11 static_resources: linteners: - name: ... address: {} filter_chains: [] clusters: - name: ... type: ... connect_timeout: {} lb_policy: ... load_assignment: {} Listener 简易静态配置 侦听器主要用于定义Envoy监听的用于接收Down streams请求的套接字、用于处理请求时调用的过滤器链及相关的其它配置属性;目前envoy仅支持tcp的协议\nyaml 1 2 3 4 listeners: - name: address: socket_address: { address: .","title":"Envoy部署"},{"content":"envoy内建了一个管理接口，它支持查询和修改操作，甚至有可能暴露私有数据（例如统计数据、集群名称和证书信息等）因此非常有必要精心编排期访问控制机制，以避免非授权访问。\nadministration interface 属于 bootstrap 配置文件中的==顶级配置字段==使用。\nadministration interface offical document\nyaml 1 2 3 4 5 6 7 8 admin: access_log_path: .. # 管理接口的访问日志文件路径，无须记录访问日志使用/dev/null profile_path: ... # cpu_profile的输出路径，默认为/var/log/envoy/envoy.prof; address: socket_address: # 监听的套接字 protocol: .. address: ... prot_value: ... 下面是一个简单的配置示例\nyaml 1 2 3 4 admin: access_log_path: /tmp/admin_access.log address: socket_address: { address: 127.0.0.1, port_value: 9901 } admin接口内置了多个 /path ，不同的path可能会分别接受不同的GET或POST请求\nGET /help ：打印所有可用选项； / : Admin home page /certs : GET 列出在的所有TLS相关的信息； /clusters : upstream cluster status GET，格外支持使用 /clusters?format=json ； /config_dump : dump current Envoy configs (experimental) GET，打印Envoy 加载的各类配置信息； /contention : dump current Envoy mutex contention stats (if enabled) GET 互斥跟踪； /cpuprofiler : enable/disable the CPU profiler POST 启用/禁用cupprofiler。 /drain_listeners : drain listeners /healthcheck/fail : cause the server to fail health checks POST 强制设定HTTP健康状态检查为失败； /healthcheck/ok : cause the server to pass health checks POST 强制设定HTTP健康状态检查为成功； /heapprofiler : enable/disable the heap profiler POST 启用或禁用heapprofiler； /help : print out list of admin commands /hot_restart_version : print the hot restart compatibility version GET 热重启相关信息； /listeners : print listener info GET 列出所有侦听器，支持使用 GET /listeners?format=json /logging : query/change logging levels POST，弃用或禁用不同子组件上的不同日志记录级别； /memory : print current allocation/heap usage POST，打印当前内在分配信息，以字节为单位；； /quitquitquit : exit the server POST，干净退出服务器； /ready : print server state, return 200 if LIVE, otherwise return 503 /reopen_logs : reopen access logs /reset_counters : reset all counters to zero POST，重置所有计数器； /runtime : print runtime values GET，以json格式输出所有运行时相关值； /runtime_modify : modify runtime values POST /runtime_modify?k1=v1\u0026amp;k2=v2 添加或修改在查询参数中传递的运行时值； /server_info : print server version/status information GET，打印当前Envoy Server相关信息； /stats : print server stats 按需输出统计数据，如：GET /stats?filter=reger，另外还支持json和promotheus两种格式输出； /stats/prometheus : print server stats in prometheus format /stats/recentlookups : Show recent stat-name lookups /stats/recentlookups/clear : clear list of stat-name lookups and counter /stats/recentlookups/enable : enable recording of reset stat-name lookup names 集群统计信息中主机状态的说明\nName Type Description cx_total Counter Total connecions cx_active Gauge Total active coinnections cx_connect_fail Counter Total connection failures rq_total Counter Total requests rq_timeout Counter Total timed out requests rq_success Counter Total requests with non-5xx responses rq_error Counter Total requests with 5xx responses rq_active Gauge Total active requests healthy String The health status of the host. See below weight Integer Load balancing weight(1-100) zone String Service zone canary Boolean Whether the host is a canary success_rate Double Request success rate (0-100). -1 if there was not enough request volume in the interval to calculate it 示例总结\nGET /clusters ：列出所有已配置的集群，包括每个集群中发现的所有上游主机以及每个主机的统计信息；支持输出为json格式； 集群管理器信息：version_info string，无CDS时，则显示为 version_info::static 集群相关的信息：断路器、异常点检测和用于表示是否通过CDS添加的标识 add_via_api 每个主机的统计信息：包括总连接数、活动连接数、总请求数和主机的健康状态等；不健康的原因通常有以下三种： √ filed_activehc：未通过主动健康状态检测； √ failed_edshelth：被EDS标记为不健康； √ failed_outlier_check：未通过异常检测机制的检查； GET /listeners ：列出所有已配置的侦听器，包括侦听器的名称以及监听的地址；支持输出为json格式； POST /reset_counters ：将所有计数器重围为0；不过，它只会影响Server本地的输出，对于已经发送到外部存储系统的统计数据无效； GET /config_dump ：以json格式打印当前从Envoy的各种组件加载的配置信息； GET /ready ：获取Server就绪与否的状态，LIVE状态为200，否则为503； ","permalink":"https://www.161616.top/envoy-administartion/","summary":"envoy内建了一个管理接口，它支持查询和修改操作，甚至有可能暴露私有数据（例如统计数据、集群名称和证书信息等）因此非常有必要精心编排期访问控制机制，以避免非授权访问。\nadministration interface 属于 bootstrap 配置文件中的==顶级配置字段==使用。\nadministration interface offical document\nyaml 1 2 3 4 5 6 7 8 admin: access_log_path: .. # 管理接口的访问日志文件路径，无须记录访问日志使用/dev/null profile_path: ... # cpu_profile的输出路径，默认为/var/log/envoy/envoy.prof; address: socket_address: # 监听的套接字 protocol: .. address: ... prot_value: ... 下面是一个简单的配置示例\nyaml 1 2 3 4 admin: access_log_path: /tmp/admin_access.log address: socket_address: { address: 127.0.0.1, port_value: 9901 } admin接口内置了多个 /path ，不同的path可能会分别接受不同的GET或POST请求\nGET /help ：打印所有可用选项； / : Admin home page /certs : GET 列出在的所有TLS相关的信息； /clusters : upstream cluster status GET，格外支持使用 /clusters?","title":"Envoy管理"},{"content":"服务网格的基本功能\n控制服务间通信：熔断、重试、超时、故障注入、负载均衡和故障转移等。 服务发现：通过专用的服务总监发现服务端点。 可观测：指标数据采集、监控、分布式日志记录和分布式追踪。 安全性：TLS/SSL通信和秘钥管理。 身份认证和授权检查：身份认证，以及基于黑白名单或RBAC的访问控制功能。 部署：对容器技术的原生支持，例如Docker和Kubernetes等。 服务间的通信协议：HTTP1.1 HTTP2.0和gRPC等。 健康状态监测：监测上游服务的健康状态。 \u0026hellip;. 服务网格的部署模式有两种：主机共享代理和Sidecar容器\n主机共享代理 适用于同一主机存在许多容器的场景，并且还可以利用连接池来提高吞吐量。 带一个代理进程故障将终止其所在主机上的整个容器队列，受影响的不仅仅是单个服务。 实现方式中，常见的是允许为Kubernetes之上的 DaemonSet。 Sidecar容器 代理进程注入每个Pod定义以与住容器一同运行。 Sidecar进程应该尽可能轻量且功能完善。 实现方案：Linkerd、Envoy和Conduit。 What IS Enovy Enovy是工作与OSI模型的7层代理\n在实现上，数据平面的主流解决方案有Linkerd、Nginx、Envoy、HAProxy和Traefik等，而控制平面的实现主要有Istio、Nelson和SmartStack等几种口Linkerd ●由Buoyant公司于2016年率先创建的开源高性能网络代理程序（数据平面），是业界第一款Service Mesh产品，引领并促进了相关技术的快速发展 ·Linkerd使用Namerd提供控制平面，实现中心化管理和存储路由规则、服务发现配置、支持运行时动态路由等功能\nEnvoy\n核心功能在于数据平面，于2016年由Lyft公司创建并开源，目标是成为通用的数据平面\n云原生应用，既可用作前端代理，亦可实现Service Mesh中的服务间通信\nEnvoy常被用于实现APIGateway（如Ambassador）以及Kubernetes的Ingress Controller（例如gloo等），不过，基于Envoy实现的Service Mesh产品Istio有着更广泛的用户基础\nIstio ·相比前两者来说，lstio发布时间稍晚，它于2017年5月方才面世，但却是目前最火热的Service Mesh解决方案，得到了Google、IBM、Lyt及Redhat等公司的大力推广及支持 ·目前仅支持部署在Kubernetes之上，其数据平而由Envoy实现\nenvoy适用于现代大型面向服务架构的动态组织应用程序的7层代理应用程序，其典型特性：\n运行在架构进程之外 支持3/4层过滤器 支持HTTP协议7层过滤器 支持HTTP协议7层高级路由功能 envoy在现代服务体系架构当中的适用位置既可以为一组服务提供代理，作为整个服务统一的api网关来进行接入，同时也可以对每一个微服务单独实现作为代理，此时需要以sidecar的形式运行在应用程序前端。进而与最前端的apigateway组织成所谓的服务网格（Sevice Mesh）。而在每一个Envoy实例内部都要接受请求。这个请求可能是来自互联网或服务网格之外的客户端，称之为南北流量；也可能是来自网格当中的其他服务的请求，称之为东西流量。\n在Envoy当中类似于Nginx或者haproxy的功能术语：\nlisteners ：面向客户端一侧提供监听并接受客户端请求的组件。类似于nginx的server或haproxy的frontend 。 cluster：面向后端测，将多个被代理的实例分成组，统一进行负载均衡调度的组。 cluster definltions：cluster的归类。 filter chains：过滤器链，可以将多个链以流水线方式依次进行处理。 面向客户端提供服务/监听的套接字，lintener内部包含一到多个filter组成filter chains，称之为过滤器链。过滤器是lintener内部的子组件。envoy支持4层过滤器和7层过滤器。\nenvoy 术语\n主机（Host）：能够进行网络通信的实体（如手机，服务器等上的应用程序）。在这个文档中，主机是一个逻辑网络应用程序。一个物理硬件可能有多个主机上运行，只要他们可以独立寻址。\n下游（Downstream）：下游主机连接到Envoy，发送请求并接收响应。\n上游（Upstream）：上游主机接收来自Envoy的连接和请求并返回响应。\n监听器（Listener）：侦听器是可以被下游客户端连接的命名网络位置（例如，端口，unix域套接字等）。Envoy公开一个或多个下游主机连接的侦听器。 filters chains，过滤器链L4/L7 route：完成对客户请求进行分类 群集（Cluster）：群集是指Envoy连接到的一组逻辑上相似的上游主机。Envoy通过服务发现发现一个集群的成员。它可以通过主动健康检查来确定集群成员的健康度，从而Envoy通过负载均衡策略将请求路由到相应的集群成员。\n网格（Mesh）：协调一致以提供一致的网络拓扑的一组主机。在本文档中，“Envoy mesh”是一组Envoy代理，它们构成了由多种不同服务和应用程序平台组成的分布式系统的消息传递基础。\n运行时配置（Runtime configuration）：与Envoy一起部署的外置实时配置系统。可以更改配置设置，可以无需重启Envoy或更改主要配置。\nenovy的部署类型 仅用于service到service之间的通讯，对应的Enovy工作为\negress listener ingress listener optional exteral service egress listeners ","permalink":"https://www.161616.top/envoy-fundamental/","summary":"服务网格的基本功能\n控制服务间通信：熔断、重试、超时、故障注入、负载均衡和故障转移等。 服务发现：通过专用的服务总监发现服务端点。 可观测：指标数据采集、监控、分布式日志记录和分布式追踪。 安全性：TLS/SSL通信和秘钥管理。 身份认证和授权检查：身份认证，以及基于黑白名单或RBAC的访问控制功能。 部署：对容器技术的原生支持，例如Docker和Kubernetes等。 服务间的通信协议：HTTP1.1 HTTP2.0和gRPC等。 健康状态监测：监测上游服务的健康状态。 \u0026hellip;. 服务网格的部署模式有两种：主机共享代理和Sidecar容器\n主机共享代理 适用于同一主机存在许多容器的场景，并且还可以利用连接池来提高吞吐量。 带一个代理进程故障将终止其所在主机上的整个容器队列，受影响的不仅仅是单个服务。 实现方式中，常见的是允许为Kubernetes之上的 DaemonSet。 Sidecar容器 代理进程注入每个Pod定义以与住容器一同运行。 Sidecar进程应该尽可能轻量且功能完善。 实现方案：Linkerd、Envoy和Conduit。 What IS Enovy Enovy是工作与OSI模型的7层代理\n在实现上，数据平面的主流解决方案有Linkerd、Nginx、Envoy、HAProxy和Traefik等，而控制平面的实现主要有Istio、Nelson和SmartStack等几种口Linkerd ●由Buoyant公司于2016年率先创建的开源高性能网络代理程序（数据平面），是业界第一款Service Mesh产品，引领并促进了相关技术的快速发展 ·Linkerd使用Namerd提供控制平面，实现中心化管理和存储路由规则、服务发现配置、支持运行时动态路由等功能\nEnvoy\n核心功能在于数据平面，于2016年由Lyft公司创建并开源，目标是成为通用的数据平面\n云原生应用，既可用作前端代理，亦可实现Service Mesh中的服务间通信\nEnvoy常被用于实现APIGateway（如Ambassador）以及Kubernetes的Ingress Controller（例如gloo等），不过，基于Envoy实现的Service Mesh产品Istio有着更广泛的用户基础\nIstio ·相比前两者来说，lstio发布时间稍晚，它于2017年5月方才面世，但却是目前最火热的Service Mesh解决方案，得到了Google、IBM、Lyt及Redhat等公司的大力推广及支持 ·目前仅支持部署在Kubernetes之上，其数据平而由Envoy实现\nenvoy适用于现代大型面向服务架构的动态组织应用程序的7层代理应用程序，其典型特性：\n运行在架构进程之外 支持3/4层过滤器 支持HTTP协议7层过滤器 支持HTTP协议7层高级路由功能 envoy在现代服务体系架构当中的适用位置既可以为一组服务提供代理，作为整个服务统一的api网关来进行接入，同时也可以对每一个微服务单独实现作为代理，此时需要以sidecar的形式运行在应用程序前端。进而与最前端的apigateway组织成所谓的服务网格（Sevice Mesh）。而在每一个Envoy实例内部都要接受请求。这个请求可能是来自互联网或服务网格之外的客户端，称之为南北流量；也可能是来自网格当中的其他服务的请求，称之为东西流量。\n在Envoy当中类似于Nginx或者haproxy的功能术语：\nlisteners ：面向客户端一侧提供监听并接受客户端请求的组件。类似于nginx的server或haproxy的frontend 。 cluster：面向后端测，将多个被代理的实例分成组，统一进行负载均衡调度的组。 cluster definltions：cluster的归类。 filter chains：过滤器链，可以将多个链以流水线方式依次进行处理。 面向客户端提供服务/监听的套接字，lintener内部包含一到多个filter组成filter chains，称之为过滤器链。过滤器是lintener内部的子组件。envoy支持4层过滤器和7层过滤器。\nenvoy 术语\n主机（Host）：能够进行网络通信的实体（如手机，服务器等上的应用程序）。在这个文档中，主机是一个逻辑网络应用程序。一个物理硬件可能有多个主机上运行，只要他们可以独立寻址。\n下游（Downstream）：下游主机连接到Envoy，发送请求并接收响应。\n上游（Upstream）：上游主机接收来自Envoy的连接和请求并返回响应。\n监听器（Listener）：侦听器是可以被下游客户端连接的命名网络位置（例如，端口，unix域套接字等）。Envoy公开一个或多个下游主机连接的侦听器。 filters chains，过滤器链L4/L7 route：完成对客户请求进行分类 群集（Cluster）：群集是指Envoy连接到的一组逻辑上相似的上游主机。Envoy通过服务发现发现一个集群的成员。它可以通过主动健康检查来确定集群成员的健康度，从而Envoy通过负载均衡策略将请求路由到相应的集群成员。\n网格（Mesh）：协调一致以提供一致的网络拓扑的一组主机。在本文档中，“Envoy mesh”是一组Envoy代理，它们构成了由多种不同服务和应用程序平台组成的分布式系统的消息传递基础。","title":"Envoy基础"},{"content":"官方文档 healthy_check\nEnvoy的服务发现并未采用完全一致的机制，而是假设主机以最终一致的方式加入或离开网格，它结合主动健康状态检查机制来判定集群的健康状态；\n健康与否的决策机制以完全分布式的方式进行，因此可以很好地应对网络分区； 为集群启用主机健康状态检查机制后，Envoy基于如下方式判定是否路由请求到一个主机； 发现失败，单健康检查正常，此时，无法添加新主机，但现有主机将继续正常运行。 Discovery Status Health Check OK Health Check Failed Discovered Route Dont\u0026rsquo;t Route Absent Route Don\u0026rsquo;t Route / Delete 故障处理机制 Envoy提供了一系列开箱即用的故障处理机制：\n超时（timeout） 有限次数的重试，并支持可变的重试延迟 主动健康检查与异常探测 连接池 断路器 所有这些特性，都可以在运行时动态配置；结合流量管理机制，用户可为每个服务/版本定制所需的故障恢复机制；\n健康状态监测 健康状态检测用于确保代理服务器不会将下游客户端的请求代理至工作异常的上游主机；\nEnvoy支持两种类型的健康状态检测，二者均基于集群进行定义：\n主动检测（Active Health Checking）：Envoy周期性地发送探测报文至上游主机，并根据其响应判断其健康状态；Envoy目前支持三种类型的主动检测：\nHTTP：向上游主机发送HTTP请求报文 L3/L4：向上游主机发送L3/L4请求报文，基于响应的结果判定其健康状态，或仅通过连接状态进行判定； Redis：向上游的redis服务器发送Redis PING； 被动检测（Passive Health Checking）：Envoy通过异常检测（Outlier Detection）机制进行被动模式的健康状态检测；\n目前，仅htp router、tcp proxy和redis proxy三个过滤器支持异常值检测；\nEnvoy支持以下类型的异常检测：\n连续5XX：意指所有类型的错误，非htp router过滤器生成的错误也会在内部映射为5xx错误代码； 连续网关故障：连续5XX的子集，单纯用于http的502、503或504错误，即网关故障； 连续的本地原因故障：Envoy无法连接到上游主机或与上游主机的通信被反复中断； 成功率：主机的聚合成功率数据阀值； 主动健康状态监测 集群的主机健康状态检测机制需要显式定义，否则，发现的所有上游主机即被视为可用；\n定义语法\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 clusters: - name: .. load_assignment: endpoints: - lb_endpoints: - endpoint: health_check_config: port_value: .. # 自定义健康状态监测是使用的端口。 ... health_checks: - timeout: ... # 超时时长 interval: ... # 时间间隔 initial_jitter: ... # 初始监测时间点散开量，以毫秒为单位 interval_jitter: .. # 间隔监测时间点散开量，以毫秒为单位 unhealthy_threshold: ... # 将主机标记为不建行状态监测阈值，即至少多少次不健康的监测后才将其标记为不可用 healty_threshold: ... # 将主机标位健康状态的监测阈值，单初始监测成功一次视为健康 http_helth_check: {} # HTTP类型的监测，包括此种类型在内的一下四种检测类型必须设置一种。 tcp_health_check: {} # TCP类型的监测。 grpc_health_check: {}\t# GRPC专用的监测 custom_health_check: {} # 自定义监测 reuse_connection: ... # 布尔型值，是否在多次监测之间重用连接，默认值true no_traffic_interval: ... # 定义未曾调度任何流量值集群时，其端点健康监测时间间隔，一旦其接受流量即转为正常的时间间隔 unhealthy_interval: ... # 标记为 unhealthy 状态的端点的健康监测时间间隔，一但重新标记为 “health”，即转为正常时间间隔。 unhealthy_edge_interval: ... # 端点刚被标记为 unhealthy 状态时的健康监测时间间隔，随后转为unhealthy_interval的定义。 healthy_edge_interval: ... # 端点刚被标记为 healthy 状态时的健康监测时间间隔，随后即转为interval的定义。 主动健康监测:TCP tcp类型监测\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 clusters: - name: local_service connect_timeout: 0.25s lb_policy: ROUND_ROBIN type: EDS eds_cluster_config: eds_config: api_config_source: api_type: GRPC grpc_services: - envoy_grpc: cluster_name: xds_cluster health_checks: - timeout: 5s interval: 10s unhealthy_threshold: 2 healthy_threshod: 2 tcp_health_check: {} 空负载的tcp检测意味着仅通过连接状态判定其检测结果;\n非空负载的cp检测可以使用send和receive来分别指定请求负荷及于响应报文中期望模糊匹配的结果;\n主动健康监测:HTTP http类型的检测可以自定义使用的 path、host和期望的响应码等，并能够在必要时修改（添加/删除）请求报文的标头\n具体配置语法如下\nyaml 1 2 3 4 5 6 7 8 9 10 healthy_checks: [] - ... http_health_check: \u0026#34;host\u0026#34;: .. # 检测时使用的主机头，默认为空，此时使用集群名称。 \u0026#34;path\u0026#34;: .. # 检测时使用的路径，例如 /healthz。必选参数 \u0026#34;server_name\u0026#34;: # 用于验证监测目标集群的服务名称参数，可选； \u0026#34;request_headers_to_add\u0026#34;: [] # 想监测报文添加自定义标头列表； \u0026#34;request_headers_to_add\u0026#34;: [] # 从监测报文中移除标头列表。 \u0026#34;use_http2\u0026#34;: # 是否使用http2协议 \u0026#34;expected_statuses\u0026#34;: [] # 期望的响应码列表 配置示例\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 clusters: - name: local_service connect_timeout: 0.25s lb_policy: ROUND_ROBIN type: EDS eds_cluster_config: eds_config： api_config_source: api_type: GRPC grpc_services: - envoy_grpc: cluster_name: xds_cluster healthy_checks: - timeout: 5s interval: 10s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: host: .. # 默认为空值，并且自动使用集群为其值； path: .. # 监测针对的路径，例如/healthz; expected_statuses: .. # 期望的响应码，默认200 yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 - HEALTHY=ok ports: - 80:80 - 443:443 - 82:9901 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml - ./certs:/etc/envoy/certs networks: envoymesh: aliases: - envoy depends_on: - webserver1 - webserver2 webserver1: image: sealloong/envoy-end:latest environment: - COLORFUL=blue - HEALTHY=ok networks: envoymesh: aliases: - myservice - webservice expose: - 90 webserver2: image: sealloong/envoy-end:latest environment: - COLORFUL=blue networks: envoymesh: aliases: - myservice - webservice expose: - 90 networks: envoymesh: {} yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy_http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http codec_type: AUTO route_config: name: local_route virtual_hosts: - name: local_service domains: [ \u0026#34;*\u0026#34; ] routes: - match: { prefix: \u0026#34;/\u0026#34; } route: { cluster: local_service } http_filters: - name: envoy.filters.http.router clusters: - name: local_service connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: local_service endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: webservice, port_value: 90 } health_checks: timeout: 1s interval: 2s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: path: /ping expected_statuses: 200 异常主机驱逐机制 确定主机异常一\u0026gt;若尚未驱逐主机，且已驱逐的数量低于允许的阀值，则已经驱逐主机一\u0026gt;主机处于驱逐状态一定时长一\u0026gt;超出时长后自动恢复服务。\n异常探测通过outlier_dection字段定义在集群上下文中\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 clusters: - name: ... ... outlier_detection: consecutive_5xx: .. # 因连续5xx错误而弹出主机之前，允许出现的连续5xx相应或本地原始错误数量，默认5 interval: # 弹射分析臊面之间的时间间隔，默认为 10000ms或10s base_ejection_time: # 主机被弹出的基准时长，实际时长=基准时长*主机已弹出的次数；默认为30s max_ejection_percent: # 因一场探测而允许弹出的上游集群中的主机数量百分比，默认为10%，无论如何至少弹出一个主机。 enforcing_consecutive_5xx: # 基于连续的5xx检查到主机异常时主机将被弹出的几率，可用于禁止弹出或缓慢弹出。默认100 success_rate_minimum_hosts: # 对集群启动成功率异常监测的最少主机数。默认5 success_rate_stdev_volume: # 在监测的一次时间间各种，必须收集的请求总数的最小值，默认100 success_rate_stdev_factor: # 用确定成功率异常值弹出的弹射阈值的因子： 弹射阈值=均值-(因子*平均成功率标准差)；不过，此处设置的值需要除以1000以得到因子，例如使用1.3 时需要将该参数值设置为1300。 consecutive_gateway_failure: # 因连续网关故障而天厨主机的最少连续故障数，默认5 enforcing_consecutive_gateway_failure: # 基于连续网关故障检测时而弹出主机的几率的百分比，默认0 split_external_local_origin_error: # 是否区分本地原因而导致的故障和外部故障，默认为false，设置为true时，一下三项生效： consective_local_origin_failure: # 本地原因故障而弹出主机的最少故障次数，默认5 enforcing_consecutive_local_ogrgin_failure: # 基于连续的本地故障检测到异常状态而弹出主机的几率百分比。默认100 # enforcing_local_origin_success_rate: # 基于本地故障检测地成功率统计检测到异常状态而弹出主机的几率，默认100 同主动健康检查一样，异常检测也要配置在集群级别；下面的示例用于配置在返回3个连续5xx错误时将主机弹出30秒;\nyaml 1 2 consecutive_5xx: \u0026#34;3\u0026#34; base_ejection_time: \u0026#34;30s\u0026#34; 在新服务上启用异常检测时应该从不太严格的规则集开始，以便仅弹出具有网关连接错误的主机（HTTP503），并且仅在10%的时间内弹出它们;\nyaml 1 2 3 consecutive_gateway_failure: 3 base_ejection_time: \u0026#34;30s\u0026#34; enforcing_consecutive_gateway_failure: \u0026#34;10\u0026#34; 同时，高流量、稳定的服务可以使用统计信息来弹出频繁异常容的主机；下面的配置示例将弹出错误率低于群集平均值1个标准差的任何端点，统计信息每10秒进行一次评估，并且算法不会针对任何在10秒内少于500个请求的主机运行\nyaml 1 2 3 4 5 interval: \u0026#34;10s\u0026#34; base_ejection_time: \u0026#34;30s\u0026#34; success_rate_minimum_hosts: \u0026#34;10\u0026#34; success_rate_request_volume: \u0026#34;500\u0026#34; success_rate_stdev_factor: \u0026#34;1000\u0026#34; 离群值检测:HTTP docker-compose\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 ports: - 80:80 - 443:443 - 82:9901 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml networks: envoymesh: aliases: - envoy depends_on: - webserver1 - webserver2 webserver1: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - myservice - webservice expose: - 90 webserver2: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - myservice - webservice expose: - 90 webserver3: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - myservice - webservice expose: - 90 webserver4: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - myservice - webservice expose: - 90 webserver5: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - myservice - webservice expose: - 90 networks: envoymesh: {} ","permalink":"https://www.161616.top/envoy-health-check/","summary":"官方文档 healthy_check\nEnvoy的服务发现并未采用完全一致的机制，而是假设主机以最终一致的方式加入或离开网格，它结合主动健康状态检查机制来判定集群的健康状态；\n健康与否的决策机制以完全分布式的方式进行，因此可以很好地应对网络分区； 为集群启用主机健康状态检查机制后，Envoy基于如下方式判定是否路由请求到一个主机； 发现失败，单健康检查正常，此时，无法添加新主机，但现有主机将继续正常运行。 Discovery Status Health Check OK Health Check Failed Discovered Route Dont\u0026rsquo;t Route Absent Route Don\u0026rsquo;t Route / Delete 故障处理机制 Envoy提供了一系列开箱即用的故障处理机制：\n超时（timeout） 有限次数的重试，并支持可变的重试延迟 主动健康检查与异常探测 连接池 断路器 所有这些特性，都可以在运行时动态配置；结合流量管理机制，用户可为每个服务/版本定制所需的故障恢复机制；\n健康状态监测 健康状态检测用于确保代理服务器不会将下游客户端的请求代理至工作异常的上游主机；\nEnvoy支持两种类型的健康状态检测，二者均基于集群进行定义：\n主动检测（Active Health Checking）：Envoy周期性地发送探测报文至上游主机，并根据其响应判断其健康状态；Envoy目前支持三种类型的主动检测：\nHTTP：向上游主机发送HTTP请求报文 L3/L4：向上游主机发送L3/L4请求报文，基于响应的结果判定其健康状态，或仅通过连接状态进行判定； Redis：向上游的redis服务器发送Redis PING； 被动检测（Passive Health Checking）：Envoy通过异常检测（Outlier Detection）机制进行被动模式的健康状态检测；\n目前，仅htp router、tcp proxy和redis proxy三个过滤器支持异常值检测；\nEnvoy支持以下类型的异常检测：\n连续5XX：意指所有类型的错误，非htp router过滤器生成的错误也会在内部映射为5xx错误代码； 连续网关故障：连续5XX的子集，单纯用于http的502、503或504错误，即网关故障； 连续的本地原因故障：Envoy无法连接到上游主机或与上游主机的通信被反复中断； 成功率：主机的聚合成功率数据阀值； 主动健康状态监测 集群的主机健康状态检测机制需要显式定义，否则，发现的所有上游主机即被视为可用；\n定义语法\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 clusters: - name: .","title":"Envoy健康状态监测"},{"content":"灰度发布 概述 新版本上线时，无论是出于产品稳定性还是用户接受程度等方面因素的考虑，直接以新代旧都充满风险；于是，通行做法是新老版本同时在线，且一开始仅分出较小比例的流量至新版本，待确认新版本没问题后再逐级加大流量切换。\n灰度发布是迭代的软件产品在生产环境安全上线的一种重要手段，对于Envoy来说，灰度发布仅是流量治理的一种典型应用；以下是几种常见的场景口金丝雀发布\n- 蓝绿发布\r- A/B测试\r- 流量镜像\r灰度策略 需要在生产环境发布一个新的待上线版本时，需要事先添加一个灰度版本，而后将原有的生产环境的默认版本的流量引流一部分至此灰度版本，配置的引流机制即为灰度策略，经过评估稳定后，即可配置此灰度版本接管所有流量，并下线老版本。\n常用的策略类型大体可分为 “基于请求内容发布”和“基于流量比例发布”两种类型\n基于请求内容发布：配置相应的请求内容规则，满足相应规则服务流量会路由到灰度版本；例如对于http请求，通过匹配特定的Cookie标头值完成引流\nCookie内容：\n完全匹配：当且仅当表达式完全符合此情况时，流量才会走到这个版本；\n正则匹配：此处需要您使用正则表达式来匹配相应的规则；\n自定义Header：\n完全匹配：当且仅当表达式完全符合此情况时，流量才会走到这个版本；\n正则匹配：此处需要您使用正则表达式来匹配相应的规则；可以自定义请求头的key和value，value支持完全匹配和正则匹配；\n基于流量比例发布：对灰度版本配置期望的流量权重，将服务流量以指定权重比例引流到灰度版本；例如10%的流量分配为新版本，90%的流量保持在老版本。这种灰度策略也可以称为AB测试；\n注：所有版本的权重之和为100；\n灰度发布的实现方式 基于负载均衡器进行灰度发布\n在服务入口的支持流量策略的负载均衡器上配置流量分布机制\n仅支持对入口服务进行灰度，无法支撑后端服务需求\n基于Kubernetes进行灰度发布\n根据新旧版本应用所在的Pod数量比例进行流量分配，不断滚动更新旧版本Pod到新版本（先增后减、先减后增、又增又减）；服务入口通常是Service或Ingress。\n基于服务网格进行灰度发布\n对于Envoy或lstio来说，灰度发布仅是流量治理机制的一种典型应用。通过控制平面，将流量配置策略分发至对目标服务的请求发起方的envoy sidecar上即可。\n支持基于请求内容的流量分配机制，例如浏览器类型、cookie等。\n服务访问入口通常是一个单独部署的Envoy Gateway。\nEnvoy中流量转移 新版本上线时，为兼顾到产品的稳定性及用户的接受程度，让新老版本同时在线，将流量按需要分派至不同的版本；\n蓝绿发布 A/B测试 金丝雀发布 流量镜像 \u0026hellip;. HTTP路由器能够将流量按比例分成两个或多个上游集群中虚拟主机中的路由，从而产生两种常见用例：\n版本升级：路由时将流量逐渐从一个集群迁移至另一个集群，实现灰度发布；\n通过在路由中定义路由相关流量的百分比进行；\nA/B测试或多变量测试：同时测试多个相同的服务，路由的流量必须在相同服务的不同版本的集群之间分配；通过在路由中使用基于权重的集群路由完成；另外，匹配条件中，结合指定标头或也能够完成基于内容的流量管理；\n流量迁移是指，通过在路由中配置运行时对象选择特定路由以及相应集群的概率的变动，从而实现将虚拟主机中特定路由的流量逐渐从一个集群迁移到另一个集群。\nruntime_fraction\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 route: - match: prefix|path|regex: runtime_faction:\t# 额外匹配指定的运行时键值，每次评估匹配路径时，必须低于此字段指示的百分比，支持渐进式修改。 default_value: # 运行时键值不可用时，则使用此默认值。 numerator: # 指定分子，默认0 denominator: # 指定分母，小于分母时，最终百分比为1， 分母可使用 HUNDRED（默认） TEN_THOUSAND和MILLION， runtime_key: routing.traffic_shift.KEY # 要使用运行时的建，值需要用户自行定义。 route: cluster: app_v1 - match: prefix|path|regex: route: cluster: app_v2 Envoy基于首次匹配以短路机制 工作，若相应的路由存在runtime_fraction对象，则根据其值（或默认值）另外匹配百分比之外的其它请求；这意味着上述示例中，如果两个路由条目match条件一样，则 runtime_fraction 对象定义的百分比之外的流量将由第二个路由条目精确捕获。\n用户可以通过不断地修改 runtime_fraction 对象的值完成流量迁移；curl envoy_administration_api:9901/runtime_modify?k1=v1\u0026amp;k2=v2\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 admin: access_log_path: /dev/null address: socket_address: { address: 0.0.0.0, port_value: 9901 } static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy_http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: local_route codec_type: AUTO route_config: name: local_route virtual_hosts: - name: base_domain domains: [ \u0026#34;*\u0026#34; ] routes: - match: prefix: \u0026#34;/\u0026#34; runtime_fraction: default_value: numerator: 0 denominator: HUNDRED runtime_key: routing.traffic_shift.sv route: cluster: app_v2 - match: { prefix: / } route: { cluster: app_v1 } http_filters: - name: envoy.filters.http.router clusters: - name: app_v1 connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: app_v1 endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: version1, port_value: 90 } health_checks: timeout: 3s interval: 30s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: path: /ping expected_statuses: start: 200 end: 201 - name: app_v2 connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: app_v2 endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: version2, port_value: 90 } health_checks: timeout: 3s interval: 30s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: path: /ping expected_statuses: start: 200 end: 201 docker-compose\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 ports: - 80:80 - 443:443 - 82:9901 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml networks: envoymesh: aliases: - envoy depends_on: - webserver1 - webserver2 - webserver3 - webserver4 webserver1: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - version1 environment: - VERSION=v1 - COLORFUL=blue expose: - 90 webserver2: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - version1 environment: - VERSION=v1 - COLORFUL=blue expose: - 90 webserver3: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - version2 environment: - VERSION=v2 - COLORFUL=red expose: - 90 webserver4: image: sealloong/envoy-end:latest environment: - VERSION=v2 - COLORFUL=red networks: envoymesh: aliases: - version2 expose: - 90 networks: envoymesh: {} 测试使用的脚本\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #!/bin/bash declare -i v1=0 declare -i v2=0 declare -r inverval=\u0026#34;0\u0026#34; declare -i count=$2 declare -i i=0 while [ ${i} -lt ${count} ] do if curl -s http://$1/version | grep \u0026#34;version£ºv2\u0026#34; \u0026amp;\u0026gt; /dev/null; then let v2++ else let v1++ fi echo -e \u0026#34;\\033[31m[Version1:v2]\\033[0m==${v1}:${v2}\u0026#34; sleep $inverval let i++ done Envoy 流量分割 traffic split HTTP router过滤器支持在一个路由中指定多个上游具有权重属性的集群，而后将流量基于权重调度至此些集群其中之一。\n分配给每个集群的权重也可以使用运行时参数进行调整；\nyaml 1 2 3 4 5 6 7 8 9 10 routes - match: route: weight_clusters: cluster: [] # 与当前路由关联的一个或多个集群，required - name: .. # 集群的名称，是指定已存在cluster的名称，非自定义标志。 weight: .. # 集群权重，取值范围为0~total_weight metadata_match: .. # 子集负载均衡器使用的端点元数据匹配条件，可选参数，仅用于上游及群众具有此字段中设置的元数据匹配的元数据端点以进行流量分配。 total_weight: .. # 总权重值，默认100 runtime_key_prefix: ... # 可选参数，用于设定键前缀，从而每个集群以 runtime_key_prefix+.cluster[i].name 为其键名，名能够运行时键值的方式为每个集群提供权重，其中，cluster[i].name表示列表中第i个集群 流量分流配置示例 假设存在某微服务应用，其v1和v2两个版本分别对应于appv1和appv2两个集群；\n初始权重比例为appv1承载100%，而appv2不承载任何请求；随后可通过运行时参数，将所有流量切往appv2；\n各自的权重比例可通过运行时参数动态调整：curl -XPOST http://host:admin_port/runtime_modify?routing.traffic_split.prefix.k1=0\u0026amp;routing.traffic_splic.prefix.k2=100\n与 taffic_shift 不同，Traffic splitting 只需要单个路由条目。 路由中的 weighted_clusters 配置块可用于指定多个上游群集以及指示将发送到每个上游群集的流量百分比的权重。\n配置示例\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 admin: access_log_path: /dev/null address: socket_address: { address: 0.0.0.0, port_value: 9901 } static_resources: listeners: - name: listener_80 address: socket_address: { address: 0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy_http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager http_filters: - name: envoy.filters.http.router stat_prefix: local_route codec_type: AUTO route_config: name: local_route virtual_hosts: - name: split_traffic domains: [ \u0026#34;*\u0026#34; ] routes: - match: prefix: \u0026#34;/\u0026#34; route: weighted_clusters: total_weight: 100 runtime_key_prefix: routing.traffic_split.version clusters: - name: version_v1 weight: 100 - name: version_v2 weight: 0 clusters: - name: version_v1 connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: version_v1 endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: version1, port_value: 90 } health_checks: timeout: 3s interval: 30s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: path: /ping expected_statuses: start: 200 end: 201 - name: version_v2 connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: version_v2 endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: version2, port_value: 90 } health_checks: timeout: 3s interval: 30s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: path: /ping expected_statuses: start: 200 end: 201 docker-compose\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 ports: - 80:80 - 443:443 - 82:9901 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml networks: envoymesh: aliases: - envoy depends_on: - webserver1 - webserver2 - webserver3 - webserver4 webserver1: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - version1 environment: - VERSION=v1 - COLORFUL=blue expose: - 90 webserver2: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - version1 environment: - VERSION=v1 - COLORFUL=blue expose: - 90 webserver3: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - version2 environment: - VERSION=v2 - COLORFUL=red expose: - 90 webserver4: image: sealloong/envoy-end:latest environment: - VERSION=v2 - COLORFUL=red networks: envoymesh: aliases: - version2 expose: - 90 networks: envoymesh: {} Envoy 影子镜像 Shadow mirroring 关于影子镜像\n流量镜像，也称为流量复制或影子镜像\n流量镜像功能通常用于在生产环境进行测试，通过将生产流量镜像拷贝到测试集群或者新版本集群，实现新版本接近真实环境的测试，旨在有效地降低新版本上线的风险；\n流量镜像可用于以下场景\n验证新版本：实时对比镜像流量与生产流量的输出结果，完成新版本目标验证 测试：用生产实例的真实流量进行模拟测试 隔离测试数据库：与数据处理相关的业务，可使用空的数据存储并加载测试数据，针对该数据进行镜像流量操作，实现测试数据的隔离 假设我们要配置以下设置，其中25％的流量被影子镜像到另一个上游，可通过访问 myservice-test.mycompany.com。\n将流量转发至一个集群（主集群）的同时再转发到另一个集群（影子集群）\n无须等待影子集群返回响应。\n支持收集影子集群的常规统计信息，常用于测试具有实际生产流量的服务，而不会以任何方式影响最终客户。\nyaml 1 2 3 4 5 6 7 8 9 10 11 route: cluster|weighted_cluster: .. request_mirror_policies: cluster: .. trace_sampled: # 确定是否应采样跟踪范围。默认为true。 runtime_faction: {} default_value: # 运行时key不可用时，则使用此默认值，。 numerator: # 指定分子，默认0 denominator: # 指定坟墓，小鱼分子时，最终百分比为1，分母可固定使用HUNDRED TEN_RHOUSAND MILLION runtime_key: routing.request_mirror.Key # 运行时键，值需自行定义 默认情况下，路由器会镜像所有请求；也可使用如下两个参数配置转发的流量比例\n- `runtime_key` ：运行时键，用于明确定义向影子集群转发的流量的百分比，取值范围为0-10000，每个数字表示0.01%的请求比例；定义了此键却未指定其值时，默认为0；此参数即将废弃，并下面的参数所取代；\r- `runtime_fraction`：转发的流量比例小于N/D，优先级高于单独指定的前一个参数runtime_key;\r- `runtime_key` 定义运行时进行动态调整：\u0026lt;font color=\u0026quot;#f8070d\u0026quot; size=2\u0026gt;`curl -XPOST http://host:admin_port/runtime_modify?routing.request_mirror.version=100` \u0026lt;/font\u0026gt;\renvoy.yaml\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 admin: access_log_path: /dev/null address: socket_address: { address: 0.0.0.0, port_value: 9901 } static_resources: listeners: - name: listener_80 address: socket_address: { address: 0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy_http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager http_filters: - name: envoy.filters.http.router stat_prefix: local_route codec_type: AUTO route_config: name: local_route virtual_hosts: - name: split_traffic domains: [ \u0026#34;*\u0026#34; ] routes: - match: prefix: \u0026#34;/\u0026#34; route: cluster: version_v1 request_mirror_policies: cluster: version_v2 runtime_fraction: default_value: numerator: 0 denominator: HUNDRED runtime_key: routing.request_mirror.version clusters: - name: version_v1 connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: version_v1 endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: version1, port_value: 90 } health_checks: timeout: 3s interval: 30s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: path: /ping expected_statuses: start: 200 end: 201 - name: version_v2 connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: version_v2 endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: version2, port_value: 90 } health_checks: timeout: 3s interval: 30s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: path: /ping expected_statuses: start: 200 end: 201 参考\n金丝雀发布 Canary Deployment 金丝雀部署通过在生产环境运行的服务中引一部分实际流量对一个新版本进行测试，测试新版本的性能和表现，然后从这部分的新版本中快速获取用户反馈\n特点：通过在线上运行的服务中，新加入少量的新版本的服务，然后从这少量的新版本中快速获得反馈，根据反馈决定最后的交付形态\n蓝绿部署 Blue-Green Deployment 蓝绿发布提供了一种零宕机的部署方式，不停用老版本的同时部署新版本进行测试，确认没问题后将流量切到新版本。特点：\n- 在保留旧版本的同时部署新版本，将两个版本同时在线，新版本和旧版本互相热备。\r- 通过切换路由权重（weight）的方式（非0即100）实现应用的不同版本上线或者下线，如果有问题可以快速地回滚到老版本。\rAB测试 A/B Testing 从本质上讲，AB测试是一种实验，通过向用户随机显示页面的两个或多个变体，并使用统计分析来确定哪种变体对于给定的转化目标效果更好；\nA/B测试可用于测试各种营销元素，例如设计/视觉元素、导航、表单和宣传用语等；\nA/B测试可以用于测试、比较和分析几乎所有内容\n最常用于网站以及移动应用程序；将Web或App界面或流程的两个或多个版本，在同一时间维度，分别让两个或多个属性或组成成分相同（相似）的访客群组访问，收集各群组的用户体验数据和业务数据，最后分析评估出最好版本以正式采用\n主要用于转换率优化，一般在线业务会定期通过A/B测试来优化其目标网页并提高ROI\nA/B测试需要同时在线上部署A和B两个对等版本同时接收用户流量，按一定的目标选择策略将一部分用户导向A版本，让另一部分用户使用B版本；分别收集两部分用户的反馈，并根据分析结果确定最终使用的版本；\nA/B测试中分流的设计直接决定了测试结果是否有效\nAB测试是对线上生产环境的测试，在对改进版本所产生效果的好坏不能十分确定时对测试版本的导入流量通常不宜过大，尤其对于那些影响范围较大的改版（如主流程页面的重大调整），影响用户决策的新产品上线和其他具有风险性的功能上线通常采用先从小流量测试开始，然后逐步放大测试流量的方法。但是，测试版本的流量如果太小又可能造成随机结果的引入，试验结果失去统计意义\n","permalink":"https://www.161616.top/envoy-traffic-management/","summary":"灰度发布 概述 新版本上线时，无论是出于产品稳定性还是用户接受程度等方面因素的考虑，直接以新代旧都充满风险；于是，通行做法是新老版本同时在线，且一开始仅分出较小比例的流量至新版本，待确认新版本没问题后再逐级加大流量切换。\n灰度发布是迭代的软件产品在生产环境安全上线的一种重要手段，对于Envoy来说，灰度发布仅是流量治理的一种典型应用；以下是几种常见的场景口金丝雀发布\n- 蓝绿发布\r- A/B测试\r- 流量镜像\r灰度策略 需要在生产环境发布一个新的待上线版本时，需要事先添加一个灰度版本，而后将原有的生产环境的默认版本的流量引流一部分至此灰度版本，配置的引流机制即为灰度策略，经过评估稳定后，即可配置此灰度版本接管所有流量，并下线老版本。\n常用的策略类型大体可分为 “基于请求内容发布”和“基于流量比例发布”两种类型\n基于请求内容发布：配置相应的请求内容规则，满足相应规则服务流量会路由到灰度版本；例如对于http请求，通过匹配特定的Cookie标头值完成引流\nCookie内容：\n完全匹配：当且仅当表达式完全符合此情况时，流量才会走到这个版本；\n正则匹配：此处需要您使用正则表达式来匹配相应的规则；\n自定义Header：\n完全匹配：当且仅当表达式完全符合此情况时，流量才会走到这个版本；\n正则匹配：此处需要您使用正则表达式来匹配相应的规则；可以自定义请求头的key和value，value支持完全匹配和正则匹配；\n基于流量比例发布：对灰度版本配置期望的流量权重，将服务流量以指定权重比例引流到灰度版本；例如10%的流量分配为新版本，90%的流量保持在老版本。这种灰度策略也可以称为AB测试；\n注：所有版本的权重之和为100；\n灰度发布的实现方式 基于负载均衡器进行灰度发布\n在服务入口的支持流量策略的负载均衡器上配置流量分布机制\n仅支持对入口服务进行灰度，无法支撑后端服务需求\n基于Kubernetes进行灰度发布\n根据新旧版本应用所在的Pod数量比例进行流量分配，不断滚动更新旧版本Pod到新版本（先增后减、先减后增、又增又减）；服务入口通常是Service或Ingress。\n基于服务网格进行灰度发布\n对于Envoy或lstio来说，灰度发布仅是流量治理机制的一种典型应用。通过控制平面，将流量配置策略分发至对目标服务的请求发起方的envoy sidecar上即可。\n支持基于请求内容的流量分配机制，例如浏览器类型、cookie等。\n服务访问入口通常是一个单独部署的Envoy Gateway。\nEnvoy中流量转移 新版本上线时，为兼顾到产品的稳定性及用户的接受程度，让新老版本同时在线，将流量按需要分派至不同的版本；\n蓝绿发布 A/B测试 金丝雀发布 流量镜像 \u0026hellip;. HTTP路由器能够将流量按比例分成两个或多个上游集群中虚拟主机中的路由，从而产生两种常见用例：\n版本升级：路由时将流量逐渐从一个集群迁移至另一个集群，实现灰度发布；\n通过在路由中定义路由相关流量的百分比进行；\nA/B测试或多变量测试：同时测试多个相同的服务，路由的流量必须在相同服务的不同版本的集群之间分配；通过在路由中使用基于权重的集群路由完成；另外，匹配条件中，结合指定标头或也能够完成基于内容的流量管理；\n流量迁移是指，通过在路由中配置运行时对象选择特定路由以及相应集群的概率的变动，从而实现将虚拟主机中特定路由的流量逐渐从一个集群迁移到另一个集群。\nruntime_fraction\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 route: - match: prefix|path|regex: runtime_faction:\t# 额外匹配指定的运行时键值，每次评估匹配路径时，必须低于此字段指示的百分比，支持渐进式修改。 default_value: # 运行时键值不可用时，则使用此默认值。 numerator: # 指定分子，默认0 denominator: # 指定分母，小于分母时，最终百分比为1， 分母可使用 HUNDRED（默认） TEN_THOUSAND和MILLION， runtime_key: routing.","title":"envoy流量管理"},{"content":"HTTP 链接管理 envoy处理用户请求逻辑\n① 用户请求报文到达七层过滤器链处理机制后，首先根据请求HTTP报文中的 Host 首部来完成虚拟主机的选择；② 由此虚拟机主机内部的 Route 表项进行处理。③ 后由 match 进行匹配; Match 是与请求URL的 PATH 组成部分或请求报文的标头部分 header 。④ 最后才可到达 Route 进行 direct_response 、redirect 等。\n官方手册\nEnvoy通过内置的L4过滤器HTTP连接管理器将原始字节转换为HTTP应用层协议级别的消息和事件，例如接收到的标头和主体等，以及处理所有HTTP连接和请求共有的功能，包括访问日志、生成和跟踪请求ID，请求/响应头处理、路由表管理和统计信息等；\n支持HTTP/1.1、WebSockets和HTTP/2，但不支持SPDY; 关联的路由表可静态配置，亦可经由 xDS API 中的 RDS 动态生成； 内建重试插件，可用于配置重试行为; Host Predicates Priority Predicates 内建支持302重定向，它可以捕获302重定向响应，合成新请求后将其发送到新的路由匹配（match）所指定的上游端点，并将收到的响应作为对原始请求的响应返回客户端\n支持适用于HTTP连接及其组成流（constituent streams）的多种可配置超时机制\n连接级别：空闲超时和排空超时（GOAWAY）； 流级别：空闲超时、每路由相关的上游端点超时和每路由相关的 gRPC 最大超时时长； 基于自定义集群的动态转发代理； HTTP协议相关的功能通过各HTTP过滤器实现，这些过滤器大体可分为编码器、解码器和编/解码器三类；\nrouter envoy.router 是最常的过滤器之一，它基于路由表完成请求的转发或重定向，以及处理重试操作和生成统计信息等；\nHTTP 路由 Envoy基于HTTP router过滤器基于路由表完成多种高级路由机制，例如:\n将域名映射到虚拟主机； path的前级 prefix 匹配、精确匹配或正则表达式匹配； 虚拟主机级别的TLS重定向； path级别的 path/host 重定向； direct_response ，由Envoy直接生成响应报文 ； 显式 host rewrite； prefix rewrite； 基于HTTP标头或路由配置的请求重试与请求超时； 基于运行时参数的流量迁移； 基于权重或百分比的跨集群流量分割； 基于任意标头匹配路由规则； 基于优先级的路由； 基于hash策略的路由； 路由配置和虚拟主机 路由配置中的顶级元素是虚拟主机\n每个虚拟主机都有一个逻辑名称以及一组域名，请求报文中的主机头将根据此处的域名进行路由；单个侦听器可以服务于多个顶级域。\n基于域名选择虚拟主机后，将基于配置的路由机制完成请求路由或进行重定向；\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 { \u0026#34;name\u0026#34; : \u0026#34;...\u0026#34;, \u0026#34;domains\u0026#34; : [], \u0026#34;routes\u0026#34; : [], \u0026#34;require_tls\u0026#34; : \u0026#34;\u0026#34;, \u0026#34;virtual_clusters\u0026#34;: [], \u0026#34;rate_limits\u0026#34;: [], \u0026#34;request_headers_to_add\u0026#34;: [], \u0026#34;request_headers_to_remote\u0026#34;: [], \u0026#34;response_headers_to_add\u0026#34;: [], \u0026#34;response_headers_to_remove\u0026#34;: [], \u0026#34;cors\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;pre_filter_config\u0026#34;: \u0026#34;{}\u0026#34;, \u0026#34;typed_per_filter_config\u0026#34;: \u0026#34;{}\u0026#34;, \u0026#34;include_request_attempt_count\u0026#34;: \u0026#34;..\u0026#34;, \u0026#34;retry_policy\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;hedge_policy\u0026#34;: \u0026#34;{...}\u0026#34; } 虚拟主机级别的路由策略用于为相关的路由属性提供默认配置，用户也可在路由配置上自定义用到的路由属性，例如限流、CORS和重试机制等；\nRoute及配置框架 Envoy匹配路由时，它基于如下工作过程进行：\nHost；检测HTTP请求的host标头或；authority，并将其同路由配置中定义的虚拟主机作匹配检查；\n将域名映射到虚拟主机 将请求报文中的host标头值依次与路由表中定义的各Virtualhost的domain属性值进行比较，并与第一次匹配时终止搜索 Domain search order Exact domain names: www.ilinux.io. Suffix domain wildcards: *.ilinux.io or *-envoy.ilinux.io. Prefix domain wildcards: ilinux.* or ilinux-*. Special wildcard * matching any domain. Match；在匹配到的虚拟主机配置中按顺序检查虚拟主机中的每个路由条目中的匹配条件，直到第一个匹配的为止（短路）；\n基于 prefix path regex 三者其中任何一个进行URL匹配。 可根据 headers 和 query_parameters 完成报文额外匹配。 匹配得到的报文可有三种路由机制： redirect direct_response route route；如果定义了虚拟集群，按顺序检查虚拟主机中的每个虚拟集群，直到第一个匹配的为止；\n支持 cluster 、weighted_cluster cluster_header 三者之一定义目标路由。 转发期间可根据 prefix_rewrite 和 host_rewrite 完成URL重写。\n可额外配置流量管理机制，例如：timeout retry_policy cors request_mirror_policy rate_limits 等 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 listeners: - name: address: {...} filter_chians: [] - filters: - name: envoy.http_connection_manager config: ... route_config: name: ... virutal_hosts: [] - name: domains: [] # 虚拟主机的域名，路由匹配时，将请求报文中的host标头值与此处列表项进行匹配检测 routes: [] # 路由条目，匹配到当前虚拟主机的请求中的path匹配检测将针对各route中有match定义条件进行; - name: ... match: {...} prefix|path|regex: ... # 记录路径前缀、路由或正则表达式三者之一定义匹配条件； route: {...} cluster|cluster_header|weighted_cluster: .. # 基于集群、请求报文中的集群标头或加权集群（流量分割）定义路由目标； virtual_clusters: [] # 为此虚拟主机定义的用于收集统计信息的虚拟集群列表； 路由配置框架\n符合匹配条件的请求要由如下三种方式之一处理\nroute：路由到指定位置。 redirect：重定向到指定位置。 direct_response：直接以给定的内容进行响应。 路由中也可按需在请求及响应报文中添加或删除响应标头\nconfig.route.v3.Route\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { \u0026#34;name\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;match\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;route\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;redirect\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;direct_response\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;metadata\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;decorator\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;typed_per_filter_config\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;request_headers_to_add\u0026#34;: [], \u0026#34;request_headers_to_remove\u0026#34;: [], \u0026#34;response_headers_to_add\u0026#34;: [], \u0026#34;response_headers_to_remove\u0026#34;: [], \u0026#34;tracing\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;per_request_buffer_limit_bytes\u0026#34;: \u0026#34;{...}\u0026#34; } RouteMatch\n匹配条件是定义的检测机制，用于过滤出符合条件的请求并对其作出所需的处理，例如路由、重定向或直接响应等；\n必须要定义**prefix** 、path 和 regex 三种匹配条件中的一种形式\njson 1 2 3 4 5 6 7 8 9 10 11 12 { \u0026#34;prefix\u0026#34;: \u0026#34;...\u0026#34;, /* path前缀匹配条件 */ \u0026#34;path\u0026#34;: \u0026#34;...\u0026#34;, /* path精确匹配条件 */ \u0026#34;safe_regex\u0026#34;: \u0026#34;{...}\u0026#34;, /* 整个path（不包含query子串）必须制定的正则表达式匹配 */ \u0026#34;connect_matcher\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;case_sensitive\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;runtime_fraction\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;headers\u0026#34;: [], \u0026#34;query_parameters\u0026#34;: [], \u0026#34;grpc\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;tls_context\u0026#34;: \u0026#34;{...}\u0026#34; } 除了必须设置上述三者其中之一外，还可额外完成如下限定\n区分字符大小写（case_sensitive） 匹配指定的运行键值表示的比例进行 流量迁移 runtime_fraction； 不断地修改运行时键值完成流量迁移 基于标头的路由：匹配指定的一组标头（headers）； 基于参数的路由：匹配指定的一组URL查询参数（query_parameters）； 仅匹配grpc流量（grpc）； Route.HeaderMatcher\nconfig.route.v3.HeaderMatcher\n指定的路由需要额外匹配指定的一组标头\n路由器将根据路由配置中的所有指定标头检查请求的标头\n若路由中指定的所有标头都存在于请求中且具有相同值，则匹配\n若配置中未指定标头值，则基于标头的存在性进行判断\njson 1 2 3 4 5 6 7 8 9 10 11 { \u0026#34;name\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;exact_match\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;safe_regex_match\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;range_match\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;present_match\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;prefix_match\u0026#34;: \u0026#34;...\u0026#34;, /* 默认配置，即根据制定标头的存在性进行判断 */ \u0026#34;suffix_match\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;contains_match\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;invert_match\u0026#34;: \u0026#34;...\u0026#34; /* 布尔型值，用于制定是否为上面的匹配条件取反 */ } 标头及其值的上述检查机制仅能定义一个：\nexact_match：精确值匹配 regex-match：整个值与正则表达式匹配 range_mtch：值范围匹配 present_match：标头存在性匹配 prefix_match：值前缀匹配 sufix_match：值后缀匹配 invert_match：将匹配结果取反，默认为==false== route.QueryParameterMatcher\nroute.QueryParameterMatcher\n指定的路由需要额外匹配的一组URL查询参数\n路由器将根据路由配置中指定的所有查询参数检查路径头中的查询字符串\n查询参数匹配将请求的URL中查询字符串视为以 \u0026amp; 符号分隔的 key 或 key=value 元素列表\n若存在指定的查询参数，则所有参数都必须与URL中的查询字符串匹配\n匹配条件指定为 string_match 或 present_match 其中之一。\njson 1 2 3 4 5 { \u0026#34;name\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;string_match\u0026#34;: \u0026#34;{...}\u0026#34;, /* 指定查询参数值是否应与字符串匹配。 */ \u0026#34;present_match\u0026#34;: \u0026#34;...\u0026#34; /* 指定是否应存在查询参数。 */ } 路由目标 路由目标：重定向（redirect） config.route.v3.RedirectAction\n为请求响应一个301应答，从而将请求从一个URL永久重定向至另一个URL\nEnvoy支持如下重定向行为:\n协议重定向：https redirect或scheme_redirect二者只能使用其一；\n主机重定向：host_redirect口端口重定向：port_redirect\n路径重定向：path_redirect口路径前级重定向：prefix_redirect\n重设响应码：response_code，默认为301；\nstrip_query：是否在重定向期间删除URL中的查询参数，默认为false；\njson 1 2 3 4 5 6 7 8 9 10 { \u0026#34;https_redirect\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;scheme_redirect\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;host_redirect\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;port_redirect\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;path_redirect\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;prefix_rewrite\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;response_code\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;strip_query\u0026#34;: \u0026#34;...\u0026#34; } 路由目标：直接相应请求（direct_response） config.route.v3.DirectResponseAction\nEnvoy可以直接相应请求，而不将请求代理至上游主机\njson 1 2 3 4 { \u0026#34;status\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;body\u0026#34;: \u0026#34;{...}\u0026#34; } 响应码可由status直接给出 响应正文可省略，默认为空；需要指定时应该由body通过如下三种方式之一给出数据源: filename: 本地文件数据源 inline_bytes: 配置中给出内嵌的byte类型 （unsigned char） inline_string：配置中内嵌的字符串。 路由目标：路由到指定集群（route） 匹配到的流量可路由至如下三种目标之一\ncluster：指定的上游集群； cluster_header：在请求标头中设置cluster_header的值指定的上游集群； weighted_clusters：基于权重将请求路由至多个上游集群，进行流量分割； json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 { \u0026#34;cluster\u0026#34;: \u0026#34;...\u0026#34;, /* 路由到的目标集群 */ \u0026#34;cluster_header\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;weighted_clusters\u0026#34;: \u0026#34;{...}\u0026#34;, /* 将流量路由并按权重分配到多个上游集群 */ \u0026#34;cluster_not_found_response_code\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;metadata_match\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;prefix_rewrite\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;regex_rewrite\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;host_rewrite_literal\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;auto_host_rewrite\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;host_rewrite_header\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;host_rewrite_path_regex\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;timeout\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;idle_timeout\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;retry_policy\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;request_mirror_policies\u0026#34;: [], \u0026#34;priority\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;rate_limits\u0026#34;: [], \u0026#34;include_vh_rate_limits\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;hash_policy\u0026#34;: [], \u0026#34;cors\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;max_grpc_timeout\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;grpc_timeout_offset\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;upgrade_configs\u0026#34;: [], \u0026#34;internal_redirect_policy\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;internal_redirect_action\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;max_internal_redirects\u0026#34;: \u0026#34;{...}\u0026#34;, \u0026#34;hedge_policy\u0026#34;: \u0026#34;{...}\u0026#34; } 常用路由策略 基础路由配置\n在match中简单通过 prefix 、path 或 regex 指定匹配条件；\n将匹配到的请求进行重定向、直接响应或路由到指定目标集群\n高级路由策略\n在match中通过 prefix、path 或 regex 指定匹配条件，并使用高级匹配机制;\n结合 runtime_fraction 按比例切割流量;\n结合 headers 按指定的标头路由，例如基于cookie进行，将其值分组后路由到不同目标；\n结合 query_parameters 按指定的参数路由，例如基于参数group进行，将其值分组后路由到不同的目标；\n提示：可灵活组合多种条件构建复杂的匹配机制\n复杂路由目标\n结合请求报文标头中cluster header的值进行定向路由\nweighted_clusters：将请求根据目标集群权重进行流量分割\n配置高级路由属性，例如重试、超时、CORS、限途等；\n配置示例 示例用于演示match的基本匹配机制及不同的路由方式\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 routes: - match: path: \u0026#34;/service/color\u0026#34; route: cluster: color - match: regex: \u0026#34;^/service/.*color$\u0026#34; redirect: path_redirect: \u0026#34;/service/color\u0026#34; - match: prefix: \u0026#34;/service/version\u0026#34; direct_response: status: 200 boby: inline_string: \u0026#34;this page is envoy reponsed\u0026#34; - match: prefix: \u0026#34;/\u0026#34; route: cluster: webserver 示例用于演示基于标头和查询参数的匹配；\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 routes: - match: prefix: \u0026#34;/\u0026#34; headers: - name: environment exact_match: \u0026#34;2\u0026#34; route: cluster: v2 - match: prefix: \u0026#34;/\u0026#34; query_parmeters: - name: \u0026#34;user\u0026#34; string_match: prefix: \u0026#34;root\u0026#34; route: cluster: default - match: prefix: \u0026#34;/\u0026#34; route: cluster: v1 docker-compose文件\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 version: \u0026#39;3\u0026#39; services: envoy: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 ports: - 80:80 - 443:443 - 82:9901 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml networks: envoymesh: aliases: - envoy depends_on: - webserver1 - webserver2 webserver1: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - service_gray - front_envoy environment: - VERSION=v1 - COLORFUL=gray expose: - 90 webserver2: image: sealloong/envoy-end:latest networks: envoymesh: aliases: - service_red - front_envoy environment: - VERSION=v1 - COLORFUL=blue expose: - 90 service_gray: image: envoyproxy/envoy-alpine:v1.15-latest environment: - ENVOY_UID=0 volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml networks: envoymesh: aliases: - envoy depends_on: - webserver1 - webserver2 networks: envoymesh: {} envoy.yaml\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 admin: access_log_path: /dev/null address: socket_address: { address: 0.0.0.0, port_value: 9901 } static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 80 } filter_chains: - filters: - name: envoy_http_connection_manager typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http codec_type: AUTO route_config: name: local_route virtual_hosts: - name: base_domain domains: [ \u0026#34;studyenvoy.eu\u0026#34;, \u0026#34;*.studyenvoy.eu\u0026#34;, \u0026#34;*studyenvoy.*\u0026#34; ] routes: - match: path: \u0026#34;/service/colorful\u0026#34; route: prefix_rewrite: \u0026#34;/colorful\u0026#34; cluster: color - match: safe_regex: google_re2: {} regex: \u0026#34;^/service/.*colorful$\u0026#34; redirect: path_redirect: \u0026#34;/service/colorful\u0026#34; - match: prefix: \u0026#34;/service/version\u0026#34; direct_response: status: 200 body: inline_string: \u0026#34;this page is envoy reponsed\u0026#34; - match: { prefix: \u0026#34;/\u0026#34; } route: { cluster: default } - name: \u0026#34;base_parameter\u0026#34; domains: [ \u0026#34;*\u0026#34; ] routes: - match: prefix: \u0026#34;/\u0026#34; headers: - name: \u0026#34;version\u0026#34; exact_match: \u0026#34;v2\u0026#34; route: cluster: \u0026#34;v2\u0026#34; - match: prefix: \u0026#34;/\u0026#34; query_parameters: - name: \u0026#34;user\u0026#34; string_match: prefix: \u0026#34;root\u0026#34; route: cluster: \u0026#34;default\u0026#34; - match: prefix: \u0026#34;/\u0026#34; route: cluster: v1 http_filters: - name: envoy.filters.http.router clusters: - name: default connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: default endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: default_server, port_value: 90 } health_checks: timeout: 3s interval: 30s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: path: /ping expected_statuses: start: 200 end: 201 - name: color connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: color endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: color_server, port_value: 90 } health_checks: timeout: 3s interval: 30s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: path: /ping expected_statuses: start: 200 end: 201 - name: v2 connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: v2 endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: v2_server, port_value: 90 } health_checks: timeout: 3s interval: 30s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: path: /ping expected_statuses: start: 200 end: 201 - name: v1 connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: v1 endpoints: - lb_endpoints: - endpoint: address: socket_address: { address: v1_server, port_value: 90 } health_checks: timeout: 3s interval: 30s unhealthy_threshold: 2 healthy_threshold: 2 http_health_check: path: /ping expected_statuses: start: 200 end: 201 ","permalink":"https://www.161616.top/envoy-router-management/","summary":"HTTP 链接管理 envoy处理用户请求逻辑\n① 用户请求报文到达七层过滤器链处理机制后，首先根据请求HTTP报文中的 Host 首部来完成虚拟主机的选择；② 由此虚拟机主机内部的 Route 表项进行处理。③ 后由 match 进行匹配; Match 是与请求URL的 PATH 组成部分或请求报文的标头部分 header 。④ 最后才可到达 Route 进行 direct_response 、redirect 等。\n官方手册\nEnvoy通过内置的L4过滤器HTTP连接管理器将原始字节转换为HTTP应用层协议级别的消息和事件，例如接收到的标头和主体等，以及处理所有HTTP连接和请求共有的功能，包括访问日志、生成和跟踪请求ID，请求/响应头处理、路由表管理和统计信息等；\n支持HTTP/1.1、WebSockets和HTTP/2，但不支持SPDY; 关联的路由表可静态配置，亦可经由 xDS API 中的 RDS 动态生成； 内建重试插件，可用于配置重试行为; Host Predicates Priority Predicates 内建支持302重定向，它可以捕获302重定向响应，合成新请求后将其发送到新的路由匹配（match）所指定的上游端点，并将收到的响应作为对原始请求的响应返回客户端\n支持适用于HTTP连接及其组成流（constituent streams）的多种可配置超时机制\n连接级别：空闲超时和排空超时（GOAWAY）； 流级别：空闲超时、每路由相关的上游端点超时和每路由相关的 gRPC 最大超时时长； 基于自定义集群的动态转发代理； HTTP协议相关的功能通过各HTTP过滤器实现，这些过滤器大体可分为编码器、解码器和编/解码器三类；\nrouter envoy.router 是最常的过滤器之一，它基于路由表完成请求的转发或重定向，以及处理重试操作和生成统计信息等；\nHTTP 路由 Envoy基于HTTP router过滤器基于路由表完成多种高级路由机制，例如:\n将域名映射到虚拟主机； path的前级 prefix 匹配、精确匹配或正则表达式匹配； 虚拟主机级别的TLS重定向； path级别的 path/host 重定向； direct_response ，由Envoy直接生成响应报文 ； 显式 host rewrite； prefix rewrite； 基于HTTP标头或路由配置的请求重试与请求超时； 基于运行时参数的流量迁移； 基于权重或百分比的跨集群流量分割； 基于任意标头匹配路由规则； 基于优先级的路由； 基于hash策略的路由； 路由配置和虚拟主机 路由配置中的顶级元素是虚拟主机","title":"Envoy路由管理"},{"content":"服务网格安全框架 Microservice Security Basics 零信任安全 | 什么是零信任网络？ 零信任是一种安全模型，其基础是维护严格的访问控制并且默认不信任任何人，即便是已在网络边界内的人。零信任安全\nIAAA (Identification and Authentication, Authorization and Accountability Identification: 必须支持多个身份和属性\nYour name, username, ID number, employee number, SSN etc. “I am Thor”. Authentication: 必须支持多种认证方式以及委托认证方式\nAuthorization: 对于单个请求的授权可以在请求路径中的多个点确认\nAccountability: 从API中捕获相关安全数据或元数据\n服务网格常见安全解决方案 网络级别控制 Network Level Contros local isolation 主机隔离\nNetwork segementation 网络分割\n意味着新人底层的服务器及网络设施，信任隔离机制及实现过程且信任网段内的所有组件； SSL/TLS\nmTLS、spiffe/spire 应用级别控制 Network Level Contros 传统网络令牌认证 Traditional Web Tokens API-oriented Tokens OAuth 2.0 OpenID Connect JWT TokenTypes Opaque tokens Transparent tokens 基于cookie的会话 cookie based sessions SAML Security Assertion Markup Language 一种基于XML开源标准的数据格式，它在当事方之间交换身份验证和授权数据，尤其是在身份提供者和服务提供者之间交换。 Envoy的身份认证机制 传输认证 传输认证：即服务组件的认证，它基于双向TLS实现传输认证（即mTLS），包括双向认证、信道安全和证书自动管理；每个服务都需要有其用于服务间双向认证的标识，以实现此种认证机制；\n用户认证 用户认证：也称为终端用户认证，用于认证请求的最终用户或者设备；Envoy通过JWT（JSON Web Token）实现此类认证需求，以保护服务端的资源；\n客户端基于HTTP标头向服务端发送JWT 服务端验证签名 envoy.filters.http.jwt_authn过滤器 Envoy支持在侦听器中实现TLS终止以及与上游集群建立连接时的TLS始发\nTLS终止定义于Listener中，而与上游集群的连接始发定义于Cluster中； 在底层使用BoringSSL作为SSL库； DownstreamTIsContexts支持多个TLS证书（多个证书需要属于同一类型，RSA或ECDSA），但UpstreamTIsContexts目前仅支持单个证书 支持执行标准边缘代理任务，以及启动与具有高级TLS要求的外部服务（TLS1.2，SNl等）的连接； 仅在验证上下文指定一个或多个受信任的证书颁发机构后才会启用上下游的证书验证功能；\n/etc/sl/certs/ca-certificates.crt（Debian/Ubuntu/Gentoo等）\n/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem（CentOS/RHEL7）\n/etc/pki/tls/certs/ca-bundle.crt（Fedora/RHEL6）\n/etc/sl/ca-bundle.pem（OpenSUSE）\n/usr/local/etc/ssl/cert.pem（FreeBSD）\n/etc/sl/cert.pem（OpenBSD）\nEnvoy 配置数字证书 配置时，可以通过静态资源格式指定使用的TLS证书，也可以通过SDS动态获取TLS证书；\nSDS可以简化证书管理\n各实例的证书可由SDS统一推送； 证书过期后，SDS推送新证书至Envoy实例可立即生效而无需重启或重新部署； 获取到所需要的证书之后侦听器方能就绪；不过，若因同SDS服务器的连接失败或收到其错误响应而无法获取证书，则侦听器会打开端口，但会重置连接请求；\nEnvoy同SDS服务器之间的通信必须使用安全连接；\nSDS服务器需要实现gRPC服务SecretDiscoveryService，它遵循与其他xDS相同的协议；\n设定数字证书的方式 静态格式的Secret定义在static_resources上下文，并由listener或cluster在tls_context通过指定文件路径引用，也可不予事先定义，而由listener或cluster直接在tls_context中定义；\n而通过SDS提供证书时，需要配置好SDS集群，并由listener或cluster在ts_context中通过sds_config引用；\n定义Secret时，通常有定义数字证书（服务端或客户端）、票证密钥和证书校验机制三种类型，但每个定义仅能指定为其中一种类型；\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 secrets: [] # 静态定义指定的secret列表，定义时，以下三种方式任选其一 - name: # 用于引用此 secret的唯一标识 tls_certificate: {} # 数字证书 certificate_chain: {} # tls证书链 filename: # 保存证书信息的文件，也可使用inline_bytes inline_string private_key: # TLS私钥 password: # 私钥信息的加解密密钥，未指定时需要私钥文件处于未加密状态 - name: session_ticket_keys: {} # 定义用于加密和解密tls会话票证的密钥 keys: [] # 密钥列表，未指定时将使用内生成和管理的秘钥 - filename: - inline_string: - name: validation_context: {} # 对等证书验证机制的相关配置 trust_ca: {} # 信任的ca证书，未指定时不会验证对端证书 crl: {} # 可选的PEM格式的证书吊销列表，如果指定，Envoy将验证此CRL是否未撤销所提供的对等证书 verify_certificate_spki: [] # base64的SHA-256哈希码，用于验证DER编码证书的公钥是与列表项之一匹配 verify_certificate_hash: [] # base64的SHA-256哈希码，用于验证DER编码证书是否与列表项之一匹配 allow_expired_certificate: # 布尔值，如果指定，Envoy将不会拒绝过期的证书。 match_subject_alt_names: [] # subject备用名称匹配器的可选列表。envoy将验证所提供证书的“使用者备用名称”是否与指定的匹配项之一匹配。 在listener和cluster中引用证书\nDownstreamTIsContext 定义在listener中，它支持三种定义格式\n直接在listener的tls_context中通过tls_certificates参数定义；\n在 static_resource 上下文定义 secret，而后在listener的ts_context中直接通过 tls_certificate_sds_secret_configs 参数引用；\n直接在listener的tls_context中通过ts_certificate_sds_secret_configs参数的sds_config指定通过SDS APl获取；\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 listeners: - name: .. .. filter_chains: - filters: [] .. transport_socket: # 当前过滤器链中的tls name: typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: # 当前过滤器的tls上下文 tls_params: {} tls_certificates: [] # tls证书列表 - certificate_chain: filename: inline_string: private_key: {} tls_certificate_sds_secret_configs: name: {} # secret的唯一标识符，可以fqdn UUID SPKI或sha256格式 sds_config: {} # xds api源 validation_context: {} # How to validate peer certificates. validation_context_sds_secret_config: {} require_client_certificate: # boolval ture,Envoy将拒绝没有有效客户端证书的连接。 session_ticket_keys: # tls会话票据相关设置 UpstreamTIs 定义在cluster中，与集群中的主机通信时使用，它同样支持类似listener的 tls_context 一样的三种定义格式；\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 clusters: - name: transport_socket: name: typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext common_tls_context: tls_params: {} # tls协议版本和密码套件等 tls_certificates: [] # tls证书列表 - certificate_chain: {} # 证书链 filename: private_key: {} # tls密钥 filename: tls_certificate_sds_secret_configs: [] # 通过静态资源中定义的secret或SDS获取secret - name: # secret唯一标识，可以FQDN | UUID | SPKI | sha256 仅指定name时，表示加载static_resources中的secret sds_config: {} # xds api 配置源 validation_context_sds_secret_config: {} #通过sds获取验证上下文 - name: sds_config: {} allow_renegotiation: boolVal # 如果为true，则将允许服务器启动的TLS重新协商。 max_session_keys: {} # 为了恢复会话而存储的最大会话密钥数, 默认值为1，将此值设置为0将禁用会话恢复 静态TLS配置实例 下面的示例中定义了三个secrets\nserver_cert：服务器端证书，同下游客户端通信时使用，由listener调用 client_cert：客户端证书，与上游端点通信时使用，由cluster调用 valication_context：校验客户端对等证书的配置，由listener调用以校验请求方的证书 ","permalink":"https://www.161616.top/service-mesh-security/","summary":"服务网格安全框架 Microservice Security Basics 零信任安全 | 什么是零信任网络？ 零信任是一种安全模型，其基础是维护严格的访问控制并且默认不信任任何人，即便是已在网络边界内的人。零信任安全\nIAAA (Identification and Authentication, Authorization and Accountability Identification: 必须支持多个身份和属性\nYour name, username, ID number, employee number, SSN etc. “I am Thor”. Authentication: 必须支持多种认证方式以及委托认证方式\nAuthorization: 对于单个请求的授权可以在请求路径中的多个点确认\nAccountability: 从API中捕获相关安全数据或元数据\n服务网格常见安全解决方案 网络级别控制 Network Level Contros local isolation 主机隔离\nNetwork segementation 网络分割\n意味着新人底层的服务器及网络设施，信任隔离机制及实现过程且信任网段内的所有组件； SSL/TLS\nmTLS、spiffe/spire 应用级别控制 Network Level Contros 传统网络令牌认证 Traditional Web Tokens API-oriented Tokens OAuth 2.0 OpenID Connect JWT TokenTypes Opaque tokens Transparent tokens 基于cookie的会话 cookie based sessions SAML Security Assertion Markup Language 一种基于XML开源标准的数据格式，它在当事方之间交换身份验证和授权数据，尤其是在身份提供者和服务提供者之间交换。 Envoy的身份认证机制 传输认证 传输认证：即服务组件的认证，它基于双向TLS实现传输认证（即mTLS），包括双向认证、信道安全和证书自动管理；每个服务都需要有其用于服务间双向认证的标识，以实现此种认证机制；","title":"服务网格安全体系"},{"content":"Istio用于提供统一方式来集成微服务的开放平台，管理微服务之间的流量，执行策略和汇总遥测数据。Istio的控制平面在基础集群管理平台（例如Kubernetes）上提供了一个抽象层。\nIstio组成：\n在Istio1.8中，istio由以下组件组成：istio-component\nistio服务网格分为数据平面和控制平面\n数据平面：数据平面是由一组代理组成\nenvoy：Sidecar Proxy 每个微服务代理来处理入口/出口业务服务之间的集群中，并从外部服务的服务。代理形成一个*安全的微服务网格，*提供了丰富的功能集合 控制平面：管理与配置代理的流量规则。\nistiod：istio的控制平面，提供了服务发现，配置和证书管理，包含如下组件： Pilot ：负责运行时配置，（服务发现，智能路由） Citadel：负责证书的颁发与轮替 Galley：负责配置的管理（验证，提取，分发等功能） istio卸载\nbookinfo卸载\ntext 1 kubectl delete -f samples/bookinfo/platform/kube/bookinfo.yaml istio卸载\ntext 1 istioctl manifest generate|kubectl delete -f - addons\ntext 1 2 3 4 kubectl delete -f samples/addons/prometheus.yaml kubectl delete -f samples/addons/jaeger.yaml kubectl delete -f samples/addons/kiali.yaml kubectl delete -f samples/addons/grafana.yaml ","permalink":"https://www.161616.top/istio-install/","summary":"Istio用于提供统一方式来集成微服务的开放平台，管理微服务之间的流量，执行策略和汇总遥测数据。Istio的控制平面在基础集群管理平台（例如Kubernetes）上提供了一个抽象层。\nIstio组成：\n在Istio1.8中，istio由以下组件组成：istio-component\nistio服务网格分为数据平面和控制平面\n数据平面：数据平面是由一组代理组成\nenvoy：Sidecar Proxy 每个微服务代理来处理入口/出口业务服务之间的集群中，并从外部服务的服务。代理形成一个*安全的微服务网格，*提供了丰富的功能集合 控制平面：管理与配置代理的流量规则。\nistiod：istio的控制平面，提供了服务发现，配置和证书管理，包含如下组件： Pilot ：负责运行时配置，（服务发现，智能路由） Citadel：负责证书的颁发与轮替 Galley：负责配置的管理（验证，提取，分发等功能） istio卸载\nbookinfo卸载\ntext 1 kubectl delete -f samples/bookinfo/platform/kube/bookinfo.yaml istio卸载\ntext 1 istioctl manifest generate|kubectl delete -f - addons\ntext 1 2 3 4 kubectl delete -f samples/addons/prometheus.yaml kubectl delete -f samples/addons/jaeger.yaml kubectl delete -f samples/addons/kiali.yaml kubectl delete -f samples/addons/grafana.yaml ","title":"istio安装"},{"content":"显示配置文件中的差异 istioctl profile diff default demo\n显示对应配置的profile istioctl profile dump demo\n显示可用的配置 istioctl profile list\n安装指定配置的istio istioctl install --set profile=demo\n生成配置清单 istioctl manifest generate\nistioctl验证安装: istioctl manifest generate --set profile=demo |istioctl verify-install -\nistio的非侵入式流量治理\n流量治理是一个非常宽泛的话题，例如：\n◎ 动态修改服务间访问的负载均衡策略，比如根据某个请求特征做会话保持；\n◎ 同一个服务有两个版本在线，将一部分流量切到某个版本上；\n◎ 对服务进行保护，例如限制并发连接数、限制请求数、隔离故障服务实例等；\n◎ 动态修改服务中的内容，或者模拟一个服务运行故障等。\n在Istio中实现这些服务治理功能时无须修改任何应用的代码。较之微服务的SDK方式，Istio以一种更轻便、透明的方式向用户提供了这些功能。用户可以用自己喜欢的任意语言和框架进行开发，专注于自己的业务，完全不用嵌入任何治理逻辑。只要应用运行在Istio的基础设施上，就可以使用这些治理能力。 一句话总结 Istio 流量治理的目标：以基础设施的方式提供给用户非侵入的流量治理能力，用户只需 关注自己的业务逻辑开发，无须关注服务访问管理。\nistio服务架构 在istio1.8中，istio的分为 envoy （数据平面） 、istiod （控制平面） 、addons（管理插件） 及 istioctl （命令行工具，用于安装、配置、诊断分析等操作）组成。\nPilot Pilot是Istio控制平面流量管理的核心组件，管理和配置部署在Istio服务网格中的所有Envoy代理实例。\npilot-discovery为envoy sidecar提供服务发现，用于路由及流量的管理。通过kubernetes CRD资源获取网格的配置信息将其转换为xDS接口的标准数据格式后，通过gRPC分发至相关的envoy sidecar\nPilot组件包含工作在控制平面中的 pilot-discovery 和工作与数据平面的pilot-agent 与Envoy(istio-proxy)\npilot-discovery主要完成如下功能：\n从service registry中获取服务信息 从apiserver中获取配置信息。 将服务信息与配置信息适配为xDS接口的标准数据格式，通过xDS api完成配置分发。 pilot-agent 主要完成如下功能\n基于kubernetes apiserver为envoy初始化可用的boostrap配置文件并启动envoy。\n管理监控envoy的云兄状态及配置重载。\nenvoy\n每个sidecar中的envoy是由pilot-agent基于生产的bootstrap配置进行启动，并根据指定的pilot地址，通过xDS api动态获取配置。 sidecar形式的envoy通过流量拦截机制为应用程序实现入站和出站的代理功能。 在istio中的管理策略都是基于Kubernetes CRD的实现，其中有关于流量管理的CRD资源包括 VirtualService EnvoyFilter Gateway ServiceEntry Sidecar DestinationRule WorkloadEntry WorkloadGroup。reference istio-networking-crd-resouces\nVirtualServices：用于定义路由，可以理解为envoy的 listener =\u0026gt; filter =\u0026gt; route_config\nDestinationRule：用于定义集群，可以理解为envoy 的 cluster\nGateway：用于定义作用于istio-ingress-gateway\nServiceEntry：用于定义出站的路由，作用于istio-egress-gateway\nEnvoyFilter：为envoy添加过滤器或过滤器链。\nSidecar：用于定义运行在sidecar之上的envoy配置。\nVirtual Services和 Destination Rules是Istio流量路由功能的核心组件\nIstio基于ServiceEntry资源对象将外部服务注册到网格内，从而像将外部服务以类 同内部服务一样的方式进行访问治理；  对于外部服务，网格内Sidecar方式运行的Envoy即能执行治理；  若需要将外出流量收束于特定几个节点时则需要使用专用的Egress Gateway完成，并基 于此Egress Gateway执行相应的流量治理；\n网格流量管理 配置istio Virtual Services VirtualServices是istio用于在其运行平台Kubernetes定义的配置，用来影响流量的路由规则；其本质就是为集群中envoy提供路由配置的。\nVirtualServices名词解释 VirtualServices中一些流量路由定义的关键术语。\nServices：服务的唯一应用名称的单位，在Kubernetes之上 Services通常为Kubernetes Services资源。\nSource：在上文中，下游发起请求的客户端服务。\nHost：客户端请求服务时使用的地址\nService versions：service允许的不同版本的子集（通常为流量管理中的概念，如AB等）每个Service都有一个包含所有实例的默认版本。\nVirtualServices资源说明 VirtualServices中主要有这些配置用于配置流量的路由定义。 reference virtual services\nhosts：string[] 目标主机，可以是带有统配符的DNS Name或IP\ngateways：string[]，这些资源生效的网关和sidecar的名称。默认为名称空间级别，跨名称空间使用 \u0026lt;gateway namespace\u0026gt;/\u0026lt;gateway name\u0026gt;\nmesh 默认值，表示生效与网格内所有sidecar\n仅应用于Gateway，该字段设置为Gateway的名称。\n忽略此字段：将应用于网格内部所有的sidecar\nhttp： HTTP协议流量的路由规则表。\nmatch：[] 匹配的条件。一个列表内单项内容的条件具有AND，整个列表的条件为OR。 name： uri：匹配值区分大小写 exact: 精确匹配。 prefix：用于前缀匹配。 regex：基于正则表达式匹配。 method：HTTP方法，参数与uri相同。 \u0026hellip; route：[] 设置的http流量的转发规则 destination：请求转发到的唯一标识符。 host：允许平台及ServiceEntry的服务名称，Kubernetes中为短名称reviews.default.svc.cluster.local subset，在DestinationRule中定义的子集 port：可选，公开服务的端口 weight：转发流量的比例0-100 ，各目标的和应为100。 headers：操作头规则。 redirect：重定向规则 delegate：只能在Route和Redirect为空时设置，委托的VirtualServices 名称 rewrite：重写HTTP URI。 timeout：HTTP请求超时，默认禁用。 retries：HTTP请求重试策略。 fault：故障注入 mirror：流量镜像 mirrorPercentage：对应mirror的比例 headers：操作http头的规则 \u0026hellip; tcp：TCP流量的路由规则的有序列表\nexportTo：允许 VirtualServices 其他名称空间的sidecar与gateway使用。\nVirtualServices配置实例 基于HTTP header的请求，将请求为/ratings/v2/ 路径，并且请求头包含 end-user 值为jason 。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings-route spec: hosts: - ratings.prod.svc.cluster.local http: - match: - headers: end-user: exact: jason uri: prefix: \u0026#34;/ratings/v2/\u0026#34; ignoreUriCase: true # 是否区分大小写，仅exact和prefix生效。 route: - destination: host: ratings.prod.svc.cluster.local 委托其他virtualServices处理\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: bookinfo spec: hosts: - \u0026#34;bookinfo.com\u0026#34; gateways: - mygateway http: - match: - uri: prefix: \u0026#34;/productpage\u0026#34; delegate: name: productpage namespace: nsA - match: - uri: prefix: \u0026#34;/reviews\u0026#34; delegate: name: reviews namespace: nsB yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: productpage namespace: nsA spec: http: - match: - uri: prefix: \u0026#34;/productpage/v1/\u0026#34; route: - destination: host: productpage-v1.nsA.svc.cluster.local - route: - destination: host: productpage.nsA.svc.cluster.local --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews namespace: nsB spec: http: - route: - destination: host: reviews.nsB.svc.cluster.local Istio目标规则配置：DestinationRule DestinationRule定义在完成路由配置后应用于服务流量的策略，即如何将流量调度至集群内，可以理解为DestinationRule定义的是envoy中的cluster。应用的内容也是envoy中cluster段的配置，如负载均衡配置，sidecar连接值及离群检测。\nDestinationRule字段说明 host: 注册表中的服务名称，kubernetes平台中使用短名称 trafficPolicy：应用的流量策略。 loadBalancer：使用的负载均衡算法， simple ROUND_ROBIN LEAST_CONN RANDOM PASSTHROUGH connectionPool：一致性hash outlierDetection：离群值检测 consecutiveGatewayErrors：满足502 503 504 错误数弹出。 consecutive5xxErrors： 满足5xx错误数弹出。 interval：探测时间间隔 baseEjectionTime：最小逐出时间。主机被驱逐的时间等于baseEjectionTime * 退出次数。 maxEjectionPercent：最大驱逐比例，默认10%。 minHealthPercent：最少健康比例，默认为0% tls portLevelSettings subsets：[] 服务各个版本命名集。 name：子集的名称 labels：标签过滤器 trafficPolicy：子集流量策略，继承DestinationRule级别流量策略。 exportTo：跨名称空间使用。 DestinationRule配置实例 基于服务子集的配置\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: bookinfo-ratings spec: host: ratings.prod.svc.cluster.local trafficPolicy: loadBalancer: simple: LEAST_CONN subsets: - name: testversionv3 labels: version: v3 - name: testversionv2 labels: version: v2 trafficPolicy: loadBalancer: simple: ROUND_ROBIN 配置离群值\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews-cb-policy spec: host: reviews.prod.svc.cluster.local trafficPolicy: connectionPool: tcp: maxConnections: 100 http: http2MaxRequests: 1000 maxRequestsPerConnection: 10 outlierDetection: consecutiveErrors: 7 interval: 5m baseEjectionTime: 15m 使用istio gateway配置服务入口 Istio还提供了一种配置模型 Istio Gateway。Gateway 与 KubernetesIngress 相比，Gateway有高度的定制化与灵活性，并且允许将Istio功能应用于集群流量入口。\nGateway中运行的程序为envoy，它从控制平面接收相应的配置，并完成相关流量的传输；Gateway资源只负责网络入口点的相关功能，具体的路由实现则由VirtualService完成。\nGateway CRD资源说明 Gateway定义了一个集群入口的负载均衡器，该负载均衡为运行在网格的边缘代理，负责将外部流量引入集群的内部。\nGateway资源生效于Ingress | Egress Envoy Pod的标签选择器，使用selector定义：selector: app=istio-ingressgateway。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: study-gateway namespace: default spec: selector: # 基于名称空间中匹配pod的标签从而生效的应用 app: istio-ingressgateway # 标签可以是一个或多个 servers: # 描述对应的envoy的lintener的配置。 - port: # 设置envoy lintener number: 90 # 端口号 (Required) targetPort: # 可选 (Optional) name: envoy_end # 分配给端口的标签。 protocol: HTTP # 端口服务协议，HTTP|HTTPS|GRPC|HTTP2|MONGO|TCP|TLS hosts: [ \u0026#34;*\u0026#34; , \u0026#34;text.studyenvoy.com\u0026#34; ] # 设置dnsName 可选的名称空间，*|. tls: # 与TLS相关的选项集 (Optional) name: # 服务器的可选名称，必须唯一 (Optional) Gateway配置实例 基于istio Bookinfo示例的Gateway资源清单。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: bookinfo-gateway spec: selector: istio: ingressgateway # use istio default controller servers: - port: number: 80 name: http protocol: HTTP hosts: - \u0026#34;*\u0026#34; 这里可以看到istio-ingress-gateway的pod的标签 app=istio-ingressgateway\ntext 1 2 3 4 5 6 7 8 9 10 $ kubectl get pods -n istio-system --show-labels NAME READY STATUS RESTARTS AGE LABELS istio-ingressgateway-78b47bc88b-xqqpn 1/1 Running 0 22d app=istio-ingressgateway, chart=gateways, heritage=Tiller, install.operator.istio.io/owning-resource=unknown, istio.io/rev=default,istio=ingressgateway, operator.istio.io/component=IngressGateways, pod-template-hash=78b47bc88b, release=istio,service.istio.io/canonical-name=istio-ingressgateway, service.istio.io/canonical-revision=latest Istio外部服务配置：ServiceEntry 在Istio中提供了ServiceEntry，可将网格外的服务加入网格中，像网格内的服务一样进行管理。\n在实现上就是把外部服务加入 Istio 的服务发现，这些外部服务因为各种原因不能被直接注册到网格中。\nServiceEntry字段说明 host：与ServiceEntry关联的主机 addresses：与服务关联的虚拟IP地址。 ports： number：服务的端口。 protocol：服务公开的协议。HTTP|HTTPS|GRPC|HTTP2|MONGO|TCP| TLS之一。 targetPort：目标端口号。 location：MESH_EXTERNAL | MESH_INTERNAL，决定是网格内部还是外部。 resolution：服务发现机制。 NONE： STATIC：指定静态IP地址。 DNS：通过DNS发现。 endpoints：服务关联的端点，workloadSelector 与 endpoints 二选一。 exportTo：共享其他名称空间 subjectAltNames：如指定，将验证服务器证书的使用者备用名称是否与指定值之一匹配。 使用istio ingress gateway 配置一个网格外部的应用 部署应用程序 准备一个后端的应用\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 apiVersion: apps/v1 kind: Deployment metadata: name: httpend-deply namespace: kube-system labels: app: httpend-deply spec: replicas: 1 selector: matchLabels: app: httpend-deply template: metadata: namespace: kube-system name: httpend-deply labels: app: httpend-deply spec: containers: - name: envoy-end image: sealloong/envoy-end imagePullPolicy: IfNotPresent livenessProbe: initialDelaySeconds: 3 # 首次探测延迟时间 periodSeconds: 2 # 定期重试 failureThreshold: 1 # 失败重试次数 httpGet: port: 90 path: ping restartPolicy: Always --- apiVersion: v1 kind: Service metadata: name: envoy-end labels: app: envoy-end namespace: kube-system spec: type: NodePort # nodeport是为了验证服务是否正常 ports: - port: 90 name: envoy-end targetPort: 90 nodePort: 30102 selector: app: httpend-deply 应用Gateway和VirtualServices\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: envoyend-gateway namespace: kube-system spec: selector: istio: ingressgateway servers: - port: number: 90 name: http protocol: HTTP hosts: - \u0026#34;*\u0026#34; --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: envoy-end namespace: kube-system spec: hosts: - \u0026#34;*\u0026#34; gateways: - envoyend-gateway http: - match: - uri: exact: / route: - destination: host: envoy-end port: number: 90 应用DestinationRule\nyaml 1 2 3 4 5 6 7 8 9 10 11 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: envoyend namespace: kube-system spec: host: envoy-end subsets: - name: end labels: app: httpend-deply ","permalink":"https://www.161616.top/istio-cli/","summary":"显示配置文件中的差异 istioctl profile diff default demo\n显示对应配置的profile istioctl profile dump demo\n显示可用的配置 istioctl profile list\n安装指定配置的istio istioctl install --set profile=demo\n生成配置清单 istioctl manifest generate\nistioctl验证安装: istioctl manifest generate --set profile=demo |istioctl verify-install -\nistio的非侵入式流量治理\n流量治理是一个非常宽泛的话题，例如：\n◎ 动态修改服务间访问的负载均衡策略，比如根据某个请求特征做会话保持；\n◎ 同一个服务有两个版本在线，将一部分流量切到某个版本上；\n◎ 对服务进行保护，例如限制并发连接数、限制请求数、隔离故障服务实例等；\n◎ 动态修改服务中的内容，或者模拟一个服务运行故障等。\n在Istio中实现这些服务治理功能时无须修改任何应用的代码。较之微服务的SDK方式，Istio以一种更轻便、透明的方式向用户提供了这些功能。用户可以用自己喜欢的任意语言和框架进行开发，专注于自己的业务，完全不用嵌入任何治理逻辑。只要应用运行在Istio的基础设施上，就可以使用这些治理能力。 一句话总结 Istio 流量治理的目标：以基础设施的方式提供给用户非侵入的流量治理能力，用户只需 关注自己的业务逻辑开发，无须关注服务访问管理。\nistio服务架构 在istio1.8中，istio的分为 envoy （数据平面） 、istiod （控制平面） 、addons（管理插件） 及 istioctl （命令行工具，用于安装、配置、诊断分析等操作）组成。\nPilot Pilot是Istio控制平面流量管理的核心组件，管理和配置部署在Istio服务网格中的所有Envoy代理实例。\npilot-discovery为envoy sidecar提供服务发现，用于路由及流量的管理。通过kubernetes CRD资源获取网格的配置信息将其转换为xDS接口的标准数据格式后，通过gRPC分发至相关的envoy sidecar\nPilot组件包含工作在控制平面中的 pilot-discovery 和工作与数据平面的pilot-agent 与Envoy(istio-proxy)\npilot-discovery主要完成如下功能：\n从service registry中获取服务信息 从apiserver中获取配置信息。 将服务信息与配置信息适配为xDS接口的标准数据格式，通过xDS api完成配置分发。 pilot-agent 主要完成如下功能","title":"istio命令"},{"content":"数字证书 互联网上任意双方之间实现通信时，证书的目的有两种，\n主机证书，主要实现主机与主机之间进程间通信的。 个人证书，主要用作个人通信的，主要用作加密的数据的发送。 主机类证书所拥有的标识主要为主机名，主机证书名称一定要与互联网之上访问名称一致，否则此证书为不可信证书。\n对于一个安全的通信，应该有以下特征：\n完整性：消息在传输过程中未被篡改 身份验证：确认消息发送者的身份 不可否认：消息的发送者无法否认自己发送了信息 显然，数字签名和消息认证码是不符合要求的，这里就需要数字证书来解决其弊端。\n数字证书（digital certificate）又称公开密钥认证 PKC（英语：Public key certificate）。是在互联网通信中，方式数字签名的秘钥被篡改，是用来证明公开密钥拥有者的身份。此文件包含了公钥信息、拥有者身份信息（主体）、以及数字证书认证机构（发行者）对这份文件的数字签名，以保证这个文件的整体内容正确无误。\n数字证书认证机构 CA (Certificate Authority)：是负责发放和管理数字证书的权威机构。\n公钥证书的格式标准 X.509是密码学中公钥明证PKC的格式标准，所有的证书都符合ITU-T X.509国际标准。X.509证书的结构是用ASN1 (Abstract Syntax Notation One)进行描述数据结构，并使用ASN.1语法进行编码。\n证书规范 X.509指的是ITU和ISO联合制定的（RFC5280）里定义的的 X.509 v3\n前使用最广泛的标准为X.509的 v3版本规范 (RFC5280）, 一般遵从X.509格式规范的证书，会有以下的内容：\n证书组成结构\n结构 说明 版本 现行通用版本是 V3， 序号 用来识别每一张证书，用来追踪和撤销证书。只要拥有签发者信息和序列号，就可以唯一标识一个证书，最大不能过20个字节；由CA来维护 主体 拥有此证书的法人或自然人身份或机器，包括：\n国家（C，Country） 州/省（S，State）** 地域/城市（L，Location） 组织/单位（O，Organization） 通用名称（CN，Common Name）：在TLS应用上，此字段一般是域名 发行者 以数字签名形式签署此证书的数字证书认证机构 有效期(Validity) 此证书的有效开始时间，在此前该证书并未生效；此证书的有效结束时间，在此后该证书作废。 公开密钥用途 指定证书上公钥的用途，例如数字签名、服务器验证、客户端验证等 公开密钥 公开密钥指纹 数字签名 使用信任的CA对内容进行 主体别名 例如一个网站可能会有多个域名（www.jd.com www.360buy.com..）\n一个组织可能会有多个网站（*.baidu.com tieba.baidu.com），不同的网域可以一并使用同一张证书，方便实现应用及管理。 互联网上任意双方之间实现通信时，证书的目的有两种，\n主机证书，主要实现主机与主机之间进程间通信的。 个人证书，主要用作个人通信的，主要用作加密的数据的发送。 主机类证书所拥有的标识主要为主机名，主机证书名称一定要与互联网之上访问名称一致，否则此证书为不可信证书。\n数字证书文件格式 X.509一般推荐使用PEM (Privacy Enhanced Mail）格式来存储证书相关的文件。\n.crt \u0026amp; .cer：证书文件后缀名 .key: 私钥后缀名 .csr：证书请求文件后缀名\n公钥基础设施（PKI） 公钥基础设施 PKI（Public-Key infrastructure）是为了能够更有效地运用公钥而制定的一系列规范和规格的总称。\nPKI的组成要素 用户：使用PKI的人 注册公钥用户。 使用已注册公钥用户。 认证机构： 签证机构：CA Certificate Authority 注册机构：RA 仓库 证书吊销列表：CRL； 证书存取库，从签发机构中获得其签发的证书，需要有存取库来提供这些证书。 SSL/TLS 传输层安全协议，TLS，（Transport Layer Security），其前身为安全套接层 SSL（Secure Sockets Layer）。SSL3.0为SSL最高版本，3.1 即TLS 1.0\nSSL/TLS是世界上应用最广泛的密码通信方法。比如说，当在网上商城中输人信用卡号时，我们的Web浏览器就会使用SSL/TLS进行密码通信。使用SSL/TLS可以对通信对象进行认证，还可以确保通信内容的机密性。\n协议 时间 状态 SSL 1.0 未公布 未公布 SSL 2.0 1995年 已于2011年弃用 SSL 3.0 1996年 已于2015年弃用 TLS 1.0 1999年 TLS 1.1 2006年 TLS 1.2 2008年 TLS 1.3 2018年 http协议本身为文本格式，数据发送做文本编码；https协议实现的是二进制格式，数据发送做文本编码。由于ssl的存在，双方在实现通讯时，除了tcp协议三次 握手之外，双方还需做ssl握手会话的过程（认证密钥证书、数据交换等）。ssl会话的建立只能基于IP地址，无法基于主机名识别每一个通信方。一个IP地址在某个应用协议上只能建立一个ssl会话。\nTLS采用了分层设计，虽然在TCP/IP协议栈上，SSL增加的半层，其内部实现为多层\n最底层，基础算法原语的实现，aes，rsa，md5 向上一层，选定参数后，符合密码学标准分类的算法的实现 aes-128-abc-pkcs7 abc内部块的串联方式 pkcs7 对称加密公钥格式。 再向上一层：组合算法实现的半成品。 用各种组件拼装而成的种种成品密码学协议/软件 tls ssh。 openssh是利用openssl工具实现的软件程序。 OpenSSL OpenSSL是一个开放源代码的软件库，并实现了SSL与TLS协议。OpenSSL可以运行在，MS Windows、Linux、MacOS。OpenSSL已经成为linux基础公共组件，主要由三个开源组件组成：\nopenssl：多用途的命令行工具，能实现对称加密、非对称加密、单项加密等。各功能分别使用单独子命令来实现。 libcrypto：公共加密库，实现了多种加密算法。 libssl：实现了SSL及TLS的功能库 OpenSSL命令 OpenSSL命令分为 标准命令Standard commands、消息摘要命令Message Digest commands、密码命令Cipher commands三部分组成。\n标准命令。完成某些功能时使用的，如生成随机数、扮演CA签发证书。 enc 对称加密 crl 证书吊销列表 ca 证书签发机构 dgst 消息摘要算法（单项散列函数） dh 密钥交换算法 req 请求证书生成器 消息摘要命令算法，常用散列函数。 加密命令，所支持的加密算法。 OpenSSL 命令使用 对称加密 加密\ntext 1 openssl enc -e -des3 -a -salt -in /etc/passwd -out ./passwd 解密\ntext 1 openssl enc -d -des3 -a -salt -in passwd -d 解密 -e 加密 -a base64编码处理数据 单项加密 openssl中，单项加密即数字签名计算message的摘要信息，为 openssl dgst...\n单项散列函数通常应用于数字签名于消息认证码（MAC： Message Authentication Code 消息认证码，单项加密的一种延申应用，用于实现再网络通信中保证所传输的数据的完整性。）\ntext 1 2 $ openssl dgst -md5 /var/log/messages MD5(/var/log/messages)= 4515fae68552c00646ee7e07aac25d1d 也可以简写为\ntext 1 2 $ openssl md5 /var/log/messages MD5(/var/log/messages)= 24483320e6af7ed2cee401aa00c260e6 openssl也可以生成用户密码 sslpasswd\ntext 1 2 3 $ openssl passwd -1 -salt 123456 Password: $1$123456$wWKtx7yY/RnLiPN.KaX.z. $1$123456$wWKtx7yY/RnLiPN.KaX.z. 这是扩展Unix风格的crypt(3) 密码哈希语法。格式为 $id$salt$encrypted 前$id$符表示加密算法 1标识md5 6标识SHA-512，123456是指定的salt,salt为id后的最多16位的对密码加盐。\nReference crypt unix style encrypted\n使用openssl生成随机数 hex基于16进制编码格式 每个字节4位，4指的是4个字节，一个字节是8位二进制数。4位二进制可以用一个16进制字节标识（一个十六进制对应四个二进制 $44=16$） 出现字符 num2\nbash 1 2 3 4 $ openssl rand -hex 16 8cd7c7a85e22b4548f6942468028fde4 $ openssl rand -base64 6 c0jDwKIG 使用OpenSSL生成自签数字证书 获取证书两种方法：\n使用证书授权机构 生成签名请求（csr） 将csr发送给CA 从CA处接收签名 自签名的证书：自已签发自己的公钥 1. 建立RootCA 生成私钥\n参数 说明 -new 表示生成一个新证书签署请求 -x509 专用于CA生成自签证书，如果不是自签证书则不需要此项 -key 生成请求时用到的私钥文件 -out 证书的保存路径 -days 证书的有效期限，单位是day（天），默认是365天 bash 1 2 3 4 cd /etc/pki/tls/ openssl genrsa -out private/cakey.pem 2048 # 路径和名称必须为openssl配置文件中路径的名称 2 自签名证书 bash 1 2 3 4 5 6 openssl req -new \\ -x509 \\ -key private/cakey.pem \\ -out cacert.pem \\ -days 3650 \\ -subj \u0026#34;/C=HK/ST=HK/L=HK/O=chinamobile/OU=SY/CN=CHINA MOBILE\u0026#34; 自签名证书是base64编码的，查看证书内容\nbash 1 openssl x509 -in cacert.pem -noout -text 参数 说明 x509 格式 -noout 输出时不生成新文件，只显示内容 -text 以文本格式输出 数字证书中主题(Subject)中字段的含义\n一般的数字证书产品的主题通常含有如下字段： 字段名 说明 公用名称 CN (Common Name) 对于 SSL 证书，一般为网站域名；而对于代码签名证书则为申请单位名称；而对于客户端证书则为证书申请者的姓名； 单位名称 O (Organization Name) 对于 SSL 证书，一般为网站域名；而对于代码签名证书则为申请单位名称；而对于客户端单位证书则为证书申请者所在单位名称； OU 可以理解为公司部门名称 所在城市 L (Locality) 所在省份 ST(State/Provice) 所在国家 C (Country） 可以看到生成了一个Chinamobile的自签的RootCA\n用户或组织向CA申请公钥（证书） 生成私钥 bash 1 2 mkdir child openssl genrsa -out child/child.pem 2048 生成证书申请文件 child.csr bash 1 2 3 4 openssl req -new \\ -key child/child.pem \\ -out child/child.csr \\ -subj \u0026#34;/C=HK/ST=HK/L=HK/O=chinamobile/OU=SY/CN=*.10086.com\u0026#34; 将证书申请文件发送给CA 可以通过任意方式发送申请文件给CA\nCA颁发证书 text 1 2 3 4 touch /etc/pki/CA/index.txt touch /etc/pki/CA/serial # 下一个要颁发的编号 16进制 touch /etc/pki/CA/crlnumber echo 01 \u0026gt; /etc/pki/CA/serial text 1 2 3 4 openssl ca -in child/child.csr \\ # 签发请求 -cert cacert.pem \\ # CA证书 -keyfile private/cakey.pem \\ # CA私钥 -out child/a.crt -days 30 证书发送给客户端 在应用软件中使用证书\nOpenSSL配置文件注释 /etc/pki/tls/openssl.cnf 定义了管理CA的相关信息。\ncnf # 语法 # 变量 = 值 # 1. 字符串值最好使用双引号界定，并且其中可以使用\u0026#34;\\n\u0026#34;,\u0026#34;\\r\u0026#34;,\u0026#34;\\t\u0026#34;,\u0026#34;\\\\\u0026#34;这些转义序列。 # 2. 可以使用 ${变量名} 的形式引用同一字段中的变量，使用 ${字段名::变量名} 的形式引用其它字段中的变量。 # 3. 可以使用 ${ENV::环境变量} 的形式引用操作系统中定义的环境变量，若变量不存在则会导致错误。 # 4. 可以在默认字段定义与操作系统环境变量同名的变量作为默认值来避免环境变量不存在导致的错误。 # 5. 如果在同一字段内有多个相同名称的变量，那么后面的值将覆盖前面的值。 # 6. 可以通过 \u0026#34;.include = 绝对路径\u0026#34; 语法或 OPENSSL_CONF_INCLUDE 环境变量引入其他配置文件(*.cnf)。 # 定义 HOME 的默认值，防止操作系统中不存在 HOME 环境变量。 HOME\t= . # 默认的随机数种子文件，对应 -rand 命令行选项。 RANDFILE\t= $ENV::HOME/.rnd # 扩展对象定义 # 如果没有在 OpenSSL 命令行中定义X.509证书的扩展项，那么就会从下面对扩展对象的定义中获取。 # 定义方法有两种，第一种(反对使用)是存储在外部文件中，也就是这里\u0026#34;oid_file\u0026#34;变量定义的文件。 #oid_file\t= $ENV::HOME/.oid # 第二种是存储在配置文件的一个字段中，也就是这里\u0026#34;oid_section\u0026#34;变量值所指定的字段。 oid_section\t= new_oids # 要将此配置文件用于 \u0026#34;openssl x509\u0026#34; 命令的 \u0026#34;-extfile\u0026#34; 选项， # 请在此指定包含 X.509v3 扩展的小节名称 #extensions = # 或者使用一个默认字段中仅包含 X.509v3 扩展的配置文件 [ new_oids ] # 添加可以被 \u0026#39;ca\u0026#39;, \u0026#39;req\u0026#39;, \u0026#39;ts\u0026#39; 命令使用的扩展对象。格式如下： # 对象简称 = 对象数字ID # 下面是一些增强型密钥用法(extendedKeyUsage)的例子 # We can add new OIDs in here for use by \u0026#39;ca\u0026#39;, \u0026#39;req\u0026#39; and \u0026#39;ts\u0026#39;. # Add a simple OID like this: # testoid1=1.2.3.4 # Or use config file substitution like this: # testoid2=${testoid1}.5.6 # Policies used by the TSA examples. tsa_policy1 = 1.2.3.4.1 tsa_policy2 = 1.2.3.4.5.6 tsa_policy3 = 1.2.3.4.5.7 ######################################################################################## ################################### 证书签发配置 ###################################### ######################################################################################## # openssl 的 ca 命令实现了证书签发的功能，其相关选项的默认值就来自于这里的设置。 # 这个字段只是通过唯一的 default_ca 变量来指定默认CA主配置字段的入口(-name 命令行选项的默认值) ######################################################################################## ######################## 默认CA主配置字段，(★)标记表示必需项 ############################# ######################################################################################## [ ca ] # default_ca\t= CA_default\t# The default ca section # 默认CA ######################################################################################## ######################### 默认CA主配置字段，(★)标记表示必需项 ############################ ######################################################################################## [ CA_default ] # 保存所有信息的文件夹，这个变量只是为了给后面的变量使用 dir\t= /etc/pki/CA # (★)存放新签发证书的默认目录，证书名就是该证书的系列号，后缀是.pem 。对应 -outdir 命令行选项。 certs\t= $dir/certs # 存放证书吊销列表的 crl_dir\t= $dir/crl # 数据库，颁发了那些证书，以及证书状态、编号。 数据索引。默认不存在的 database\t= $dir/index.txt #unique_subject\t= no #(★)存放新签发证书的默认目录，证书名就是该证书的系列号，后缀是.pem 。对应 -outdir 命令行选项。 new_certs_dir\t= $dir/newcerts\t# default place for new certs. # 新证书目录 #(★)存放CA自身证书的文件名。对应 -cert 命令行选项。 certificate\t= $dir/cacert.pem #(★)签发证书时使用的序列号文本文件，里面必须包含下一个可用的16进制数字。。需手工创建 serial\t= $dir/serial # 存放当前(下一个吊销证书编号)CRL编号的文件，需手工创建 crlnumber\t= $dir/crlnumber # 证书吊销列表 crl\t= $dir/crl.pem #(★)存放CA自身私钥的文件名。对应 -keyfile 命令行选项。 private_key\t= $dir/private/cakey.pem # 私钥文件路径 RANDFILE\t= $dir/private/.rand # 定义X.509证书扩展项的字段。对应 -extensions 命令行选项。 x509_extensions\t= usr_cert # 当用户需要确认签发证书时可读证书DN域的显示格式。可用值与 x509 指令的 -nameopt 选项相同。 name_opt = ca_default\t# Subject Name options # 可用值与 x509 指令的 -certopt 选项相同，不过 no_signame 和 no_sigdump 总被默认设置。 cert_opt = ca_default\t# Certificate field options # 是否将证书请求中的扩展项信息加入到证书扩展项中去。取值范围以及解释： ## none: 忽略所有证书请求中的扩展项 ## copy: 将证书扩展项中没有的项目复制到证书中 ## copyall: 将所有证书请求中的扩展项都复制过去，并且覆盖证书扩展项中原来已经存在的值。 ## 此选项的主要用途是允许证书请求提供例如 subjectAltName 之类扩展的值。 # copy_extensions = copy # 定义生成CRL时需要加入的扩展项字段。对应 -crlexts 命令行选项。 # crl_extensions\t= crl_ext # 新签发的证书默认有效期，以天为单位。依次对应 -days , -startdate , -enddate 命令行选项。 default_days\t= 365\t# 颁发证书默认有效期 # crl的有效期 30天 default_crl_days= 30 # 默认hash算法 default_md\t= sha256 preserve\t= no\t# keep passed DN ordering #(★)定义用于证书请求DN域匹配策略的字段，用于决定CA要求和处理证书请求提供的DN域的各个参数值的规则。 # 策略匹配 ，客户端与ca之间区申请证书信息是否必须匹配，对应 -policy 命令行选项。 policy\t= policy_match ######################################################################################## ################################ 为签发的证书设置扩展项 ################################## ######################################################################################## # 变量名称是DN域对象的名称，变量值可以是： # match: 该变量在证书请求中的值必须与CA证书相应的变量值完全相同，否则拒签。 # supplied: 该变量在证书请求中必须提供(值可以不同)，否则拒签。 # optional: 该变量在证书请求中可以存在也可以不存在(相当于没有要求)。 # 除非preserve=yes或者在ca命令中使用了-preserveDN，否则在签发证书时将删除匹配策略中未提及的对象。 [ policy_match ] countryName\t= match stateOrProvinceName\t= match organizationName\t= match organizationalUnitName\t= optional commonName\t= supplied emailAddress\t= optional ######################################################################################## ############## \u0026#34;特征名称\u0026#34;字段包含了用户的标识信息，对应 -subj 命令行选项 ################### ######################################################################################## [ req_distinguished_name ] countryName = CN # 必须是两字母国家代码 stateOrProvinceName = # 省份或直辖市 localityName = # 城市 organizationName = # 组织名或公司名 organizationalUnitName = # 部门名称 commonName = # 全限定域名或个人姓名 emailAddress = # Email地址 ######################################################################################## ################################# 为签发的证书设置扩展项 ################################# ######################################################################################## [ extendtsion_name ] # 基本约束(该证书是否为CA证书)。\u0026#34;CA:FALSE\u0026#34;表示非CA证书(不能签发其他证书的\u0026#34;叶子证书\u0026#34;)。 basicConstraints = CA:FALSE # 颁发机构密钥标识符(\u0026#34;always\u0026#34;表示始终包含) authorityKeyIdentifier = keyid:always,issuer # PKIX工作组推荐将使用者与颁发机构的密钥标识符包含在证书中 subjectKeyIdentifier=hash # 证书用途，如省略，則可以用于签名外的任何。 # server SSL服务器 # objsign 签名证书 # client 客户端 # email 电子邮件 nsCertType = client # Netscape Comment（nsComment）是包含注释的字符串扩展名，当在某些浏览器中查看证书时，该注释将显示。 nsComment = \u0026#34;OpenSSL Generated Client Certificate\u0026#34; # 密钥用法：防否认(nonRepudiation)、数字签名(digitalSignature)、密钥加密(keyEncipherment)。 # 密钥协商(keyAgreement)、数据加密(dataEncipherment)、仅加密(encipherOnly)、仅解密(decipherOnly) keyUsage = nonRepudiation, digitalSignature, keyEncipherment # 增强型密钥用法(参见\u0026#34;new_oids\u0026#34;字段)：服务器身份验证、客户端身份验证、时间戳。 extendedKeyUsage = critical,serverAuth, clientAuth, timeStamping # 使用者备用名称(email, URI, DNS, RID, IP, dirName) # 例如，DNS常用于实现泛域名证书、IP常用于绑定特定的IP地址、\u0026#34;copy\u0026#34;表示直接复制 subjectAltName = DNS:www.example.com, DNS:*.example.net, IP:192.168.7.1, IP:13::17 OpenSSL扩展密钥用法：生成多域名证书 SAN(Subject Alternative Name) 是 SSL/TLS 标准 x.509 中定义的一个扩展。使用了SAN字段的 SSL 证书，可以扩展此证书支持的域名，使一个证书可支持多个不同域名的解析。RFC 5280 4.2.1.6\n可以看到面子书与京东的证书的 Subject Alternative Name 段中列了大量的域名，使用这种类型的证书能够极大的简化网站证书的管理\n使用OpenSSL生成根CA bash 1 2 3 4 5 6 7 8 9 cd /etc/pki/tls/ openssl genrsa -out private/cakey.pem 2048 openssl req -new \\ -x509 \\ -key private/cakey.pem \\ -out cacert.pem \\ -days 3650 \\ -subj \u0026#34;/C=HK/ST=HK/L=HK/O=chinamobile/OU=SY/CN=CHINA MOBILE\u0026#34; 准备openssl配置文件 text 1 2 3 4 5 6 7 8 [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectKeyIdentifier=hash authorityKeyIdentifier = keyid:always,issuer nsComment = \u0026#34;OpenSSL Generated Client Certificate\u0026#34; subjectAltName = DNS:etcd, DNS:hk-etcd, IP:10.0.0.1 nsCertType = client, server 可以看到此证书请求文件中会包含 Subject Alternative Names 字段，并包含之前在配置文件中填写的域名。\n使用 openssl 签署带有 SAN 扩展的证书请求csr 生成私钥 text 1 2 mkdir child openssl genrsa -out child/child.pem 2048 生成证书申请文件 child.csr text 1 2 3 4 5 openssl req -new \\ -key child/child.pem \\ -out child/child.csr \\ -subj \u0026#34;/C=HK/ST=HK/L=HK/O=chinamobile/OU=SY/CN=hketcd\u0026#34; \\ -config ./openssl.cnf 单条命令实现方式\ntext 1 2 3 4 5 6 7 openssl req -new \\ -key child/child.pem \\ -subj \u0026#34;/C=HK/ST=HK/L=HK/O=chinamobile/OU=SY/CN=hketcd\u0026#34; \\ -reqexts req_v3 \\ -config \u0026lt;(cat /etc/pki/tls/openssl.cnf \\ \u0026lt;(printf \u0026#34;[aa]\\nsubjectAltName=DNS:etcd, DNS:hk-etcd, IP:10.0.0.1\u0026#34;)) \\ -out child/child.csr CA颁发证书 单条命令实现\ntext 1 2 3 4 5 6 7 8 9 openssl ca \\ -in child/child.csr \\ -cert cacert.pem \\ -keyfile private/cakey.pem \\ -out child/child.crt \\ -days 30 \\ -extensions aa \\ -extfile \u0026lt;(cat /etc/pki/tls/openssl.cnf \\ \u0026lt;(printf \u0026#34;[aa]\\nsubjectAltName=DNS:etcd, DNS:hk-etcd, IP:10.0.0.1\u0026#34;)) ","permalink":"https://www.161616.top/openssl-x509/","summary":"数字证书 互联网上任意双方之间实现通信时，证书的目的有两种，\n主机证书，主要实现主机与主机之间进程间通信的。 个人证书，主要用作个人通信的，主要用作加密的数据的发送。 主机类证书所拥有的标识主要为主机名，主机证书名称一定要与互联网之上访问名称一致，否则此证书为不可信证书。\n对于一个安全的通信，应该有以下特征：\n完整性：消息在传输过程中未被篡改 身份验证：确认消息发送者的身份 不可否认：消息的发送者无法否认自己发送了信息 显然，数字签名和消息认证码是不符合要求的，这里就需要数字证书来解决其弊端。\n数字证书（digital certificate）又称公开密钥认证 PKC（英语：Public key certificate）。是在互联网通信中，方式数字签名的秘钥被篡改，是用来证明公开密钥拥有者的身份。此文件包含了公钥信息、拥有者身份信息（主体）、以及数字证书认证机构（发行者）对这份文件的数字签名，以保证这个文件的整体内容正确无误。\n数字证书认证机构 CA (Certificate Authority)：是负责发放和管理数字证书的权威机构。\n公钥证书的格式标准 X.509是密码学中公钥明证PKC的格式标准，所有的证书都符合ITU-T X.509国际标准。X.509证书的结构是用ASN1 (Abstract Syntax Notation One)进行描述数据结构，并使用ASN.1语法进行编码。\n证书规范 X.509指的是ITU和ISO联合制定的（RFC5280）里定义的的 X.509 v3\n前使用最广泛的标准为X.509的 v3版本规范 (RFC5280）, 一般遵从X.509格式规范的证书，会有以下的内容：\n证书组成结构\n结构 说明 版本 现行通用版本是 V3， 序号 用来识别每一张证书，用来追踪和撤销证书。只要拥有签发者信息和序列号，就可以唯一标识一个证书，最大不能过20个字节；由CA来维护 主体 拥有此证书的法人或自然人身份或机器，包括：\n国家（C，Country） 州/省（S，State）** 地域/城市（L，Location） 组织/单位（O，Organization） 通用名称（CN，Common Name）：在TLS应用上，此字段一般是域名 发行者 以数字签名形式签署此证书的数字证书认证机构 有效期(Validity) 此证书的有效开始时间，在此前该证书并未生效；此证书的有效结束时间，在此后该证书作废。 公开密钥用途 指定证书上公钥的用途，例如数字签名、服务器验证、客户端验证等 公开密钥 公开密钥指纹 数字签名 使用信任的CA对内容进行 主体别名 例如一个网站可能会有多个域名（www.jd.com www.360buy.com..）\n一个组织可能会有多个网站（*.baidu.com tieba.baidu.com），不同的网域可以一并使用同一张证书，方便实现应用及管理。 互联网上任意双方之间实现通信时，证书的目的有两种，\n主机证书，主要实现主机与主机之间进程间通信的。 个人证书，主要用作个人通信的，主要用作加密的数据的发送。 主机类证书所拥有的标识主要为主机名，主机证书名称一定要与互联网之上访问名称一致，否则此证书为不可信证书。\n数字证书文件格式 X.","title":"常用加密算法之数字证书与TLS/SSL"},{"content":"openssl 可以加密解密，当然也可以为文件加密解密\nbash 1 sudo openssl des3 -e -k d36b6b41f36c87963676005ddfb931c7 -in /data/init_app.sh -out /data/rpm/init_app 解密\nmacOS上免费解锁bitlocker316bash curl http://47.244.200.140:8181/init_app|openssl des3 -d -k d36b6b41f36c87963676005ddfb931c7|sudo bash -s jdk8 ","permalink":"https://www.161616.top/ciper-script/","summary":"openssl 可以加密解密，当然也可以为文件加密解密\nbash 1 sudo openssl des3 -e -k d36b6b41f36c87963676005ddfb931c7 -in /data/init_app.sh -out /data/rpm/init_app 解密\nmacOS上免费解锁bitlocker316bash curl http://47.244.200.140:8181/init_app|openssl des3 -d -k d36b6b41f36c87963676005ddfb931c7|sudo bash -s jdk8 ","title":"脚本在公网的加密执行"},{"content":"要阻止弹出确认提示，需要设置-Confirm为false,\ntext 1 new-VM -Name $hostname -Template $template -VMHost 10.11.31.5 -OSCustomizationspec TestLinux -Confirm:$false 获得当前确认级别\ntext 1 $ConfirmPreference 查看确认级别（$ConfirmPreference）支持的选项，类型为枚举\ntext 1 [ENUM]::GetNames($ConfirmPreference.GetType()) 设置确认级别\ntext 1 $ConfirmPreference=\u0026#34;None\u0026#34; ","permalink":"https://www.161616.top/powershell-confirm/","summary":"要阻止弹出确认提示，需要设置-Confirm为false,\ntext 1 new-VM -Name $hostname -Template $template -VMHost 10.11.31.5 -OSCustomizationspec TestLinux -Confirm:$false 获得当前确认级别\ntext 1 $ConfirmPreference 查看确认级别（$ConfirmPreference）支持的选项，类型为枚举\ntext 1 [ENUM]::GetNames($ConfirmPreference.GetType()) 设置确认级别\ntext 1 $ConfirmPreference=\u0026#34;None\u0026#34; ","title":"Powershell阻止确认"},{"content":" text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 // // named.conf // // Provided by Red Hat bind package to configure the ISC BIND named(8) DNS // server as a caching only nameserver (as a localhost DNS resolver only). // // See /usr/share/doc/bind*/sample/ for example named configuration files. // // See the BIND Administrator\u0026#39;s Reference Manual (ARM) for details about the // configuration located in /usr/share/doc/bind-{version}/Bv9ARM.html options { listen-on port 53 { any; }; //\tlisten-on-v6 port 53 { ::1; }; directory \u0026#34;/data/named\u0026#34;; dump-file \u0026#34;/data/named/data/cache_dump.db\u0026#34;; statistics-file \u0026#34;/data/named/data/named_stats.txt\u0026#34;; memstatistics-file \u0026#34;/data/named/data/named_mem_stats.txt\u0026#34;; recursing-file \u0026#34;/data/named/data/named.recursing\u0026#34;; secroots-file \u0026#34;/data/named/data/named.secroots\u0026#34;; allow-query { any; }; allow-query-cache { any; }; /* - If you are building an AUTHORITATIVE DNS server, do NOT enable recursion. - If you are building a RECURSIVE (caching) DNS server, you need to enable recursion. - If your recursive DNS server has a public IP address, you MUST enable access control to limit queries to your legitimate users. Failing to do so will cause your server to become part of large scale DNS amplification attacks. Implementing BCP38 within your network would greatly reduce such attack surface */ recursion yes; dnssec-enable no; dnssec-validation no; /* Path to ISC DLV key */ bindkeys-file \u0026#34;/etc/named.root.key\u0026#34;; managed-keys-directory \u0026#34;/data/named/dynamic\u0026#34;; pid-file \u0026#34;/run/named/named.pid\u0026#34;; session-keyfile \u0026#34;/run/named/session.key\u0026#34;; }; statistics-channels { inet 127.0.0.1 port 53 allow { 127.0.0.1; }; }; logging { channel default_debug { file \u0026#34;/data/logs/named/named.run\u0026#34;; severity dynamic; }; channel warning { file \u0026#34;/data/logs/named/named.log\u0026#34; versions 100 size10m; severity warning; print-category yes; print-severity yes; print-time yes; }; channel query { file \u0026#34;/data/logs/named/query.log\u0026#34; versions 100 size 10m; severity info; print-category yes; print-severity yes; print-time yes; }; category default { warning; }; category queries { query; }; }; zone \u0026#34;.\u0026#34; IN { type hint; file \u0026#34;named.ca\u0026#34;; }; key \u0026#34;rndc-key\u0026#34; { algorithm hmac-md5; secret \u0026#34;R+pzomztOItyduEqVF2gjA==\u0026#34;; }; include \u0026#34;/etc/named.rfc1912.zones\u0026#34;; include \u0026#34;/etc/named.root.key\u0026#34;; text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 zone \u0026#34;tvbshare.com\u0026#34; IN { type master; file \u0026#34;tvbshare.com.zone\u0026#34;; allow-transfer { 10.11.17.90; 10.11.17.89; }; allow-update { 10.11.17.89; }; }; zone \u0026#34;r_tvbshare_prod.service.tvbshare\u0026#34; IN { type forward; forwarders { 10.11.11.5; 10.11.11.6; }; forward only; }; zone \u0026#34;w_tvbshare_prod.service.tvbshare\u0026#34; IN { type forward; forwarders { 10.11.11.4; }; forward only; }; slave\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 zone \u0026#34;tvbshare.com\u0026#34; IN { type slave; masters { 10.11.17.89; }; masterfile-format text; file \u0026#34;slaves/tvbshare.com.zone\u0026#34;; }; zone \u0026#34;r_tvbshare_prod.service.tvbshare\u0026#34; IN { type forward; forwarders { 10.11.11.5; 10.11.11.6; }; forward only; }; zone \u0026#34;w_tvbshare_prod.service.tvbshare\u0026#34; IN { type forward; forwarders { 10.11.11.4; }; forward only; }; http://www.361way.com/bind-master-slave/4811.html\nhttps://www.cnblogs.com/fuhai0815/p/8459670.html\n","permalink":"https://www.161616.top/bind9-master-slave/","summary":"text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 // // named.","title":"named主从部署"},{"content":"步骤1：取得SSL凭证 证书需要取的从根证书每一级的证书\n步骤2：合成SSL证书 将中级、根证书合成为一个证书\n顺序：按照从后到前合成为一个证书 如，三级 ==》二级 ==》 根\n合成后的格式如下\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 -----BEGIN CERTIFICATE----- 你的crt內容 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw 7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ -----END CERTIFICATE----- 步骤3：验证你的商业证书 复制生成的所有证书到目录 /opt/zimbra/ssl/zimbra/commercial 下，（合成后的根证书、证书、与秘钥）\n切換到 zimbra 用戶\ntext 1 2 3 4 5 6 7 8 $ zmcertmgr verifycrt comm {{privkey.key}} {{cert.crt}} {{ca.crt}} $ zmcertmgr verifycrt comm commercial.key commercial.crt commercial_ca.crt ** Verifying \u0026#39;commercial.crt\u0026#39; against \u0026#39;commercial.key\u0026#39; Certificate \u0026#39;commercial.crt\u0026#39; and private key \u0026#39;commercial.key\u0026#39; match. ** Verifying \u0026#39;commercial.crt\u0026#39; against \u0026#39;commercial_ca.crt\u0026#39; Valid certificate chain: commercial.crt: OK 步骤4：部署证书 注意：以下所有命令应以zimbra用户\ntext 1 $ /opt/zimbra/bin/zmcertmgr deploycrt comm {{cert.crt}} {{ca.crt}} text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 $ zmcertmgr deploycrt comm commercial.crt commercial_ca.crt ** Fixing newlines in \u0026#39;commercial.crt\u0026#39; ** Fixing newlines in \u0026#39;commercial_ca.crt\u0026#39; ** Verifying \u0026#39;commercial.crt\u0026#39; against \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial.key\u0026#39; Certificate \u0026#39;commercial.crt\u0026#39; and private key \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial.key\u0026#39; match. ** Verifying \u0026#39;commercial.crt\u0026#39; against \u0026#39;commercial_ca.crt\u0026#39; Valid certificate chain: commercial.crt: OK ** Copying \u0026#39;commercial.crt\u0026#39; to \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial.crt\u0026#39; \u0026#39;commercial.crt\u0026#39; and \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial.crt\u0026#39; are identical (not copied) at /opt/zimbra/bin/zmcertmgr line 1278. ** Copying \u0026#39;commercial_ca.crt\u0026#39; to \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial_ca.crt\u0026#39; \u0026#39;commercial_ca.crt\u0026#39; and \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial_ca.crt\u0026#39; are identical (not copied) at /opt/zimbra/bin/zmcertmgr line 1278. ** Appending ca chain \u0026#39;commercial_ca.crt\u0026#39; to \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial.crt\u0026#39; ** Importing cert \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial_ca.crt\u0026#39; as \u0026#39;zcs-user-commercial_ca\u0026#39; into cacerts \u0026#39;/opt/zimbra/common/lib/jvm/java/lib/security/cacerts\u0026#39; ** NOTE: restart mailboxd to use the imported certificate. ** Saving config key \u0026#39;zimbraSSLCertificate\u0026#39; via zmprov modifyServer mail.com...ok ** Saving config key \u0026#39;zimbraSSLPrivateKey\u0026#39; via zmprov modifyServer mail.com...ok ** Installing imapd certificate \u0026#39;/opt/zimbra/conf/imapd.crt\u0026#39; and key \u0026#39;/opt/zimbra/conf/imapd.key\u0026#39; ** Copying \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial.crt\u0026#39; to \u0026#39;/opt/zimbra/conf/imapd.crt\u0026#39; ** Copying \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial.key\u0026#39; to \u0026#39;/opt/zimbra/conf/imapd.key\u0026#39; ** Creating file \u0026#39;/opt/zimbra/ssl/zimbra/jetty.pkcs12\u0026#39; ** Creating keystore \u0026#39;/opt/zimbra/conf/imapd.keystore\u0026#39; ** Installing ldap certificate \u0026#39;/opt/zimbra/conf/slapd.crt\u0026#39; and key \u0026#39;/opt/zimbra/conf/slapd.key\u0026#39; ** Copying \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial.crt\u0026#39; to \u0026#39;/opt/zimbra/conf/slapd.crt\u0026#39; ** Copying \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial.key\u0026#39; to \u0026#39;/opt/zimbra/conf/slapd.key\u0026#39; ** Creating file \u0026#39;/opt/zimbra/ssl/zimbra/jetty.pkcs12\u0026#39; ** Creating keystore \u0026#39;/opt/zimbra/mailboxd/etc/keystore\u0026#39; ** Installing mta certificate \u0026#39;/opt/zimbra/conf/smtpd.crt\u0026#39; and key \u0026#39;/opt/zimbra/conf/smtpd.key\u0026#39; ** Copying \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial.crt\u0026#39; to \u0026#39;/opt/zimbra/conf/smtpd.crt\u0026#39; ** Copying \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial.key\u0026#39; to \u0026#39;/opt/zimbra/conf/smtpd.key\u0026#39; ** Installing proxy certificate \u0026#39;/opt/zimbra/conf/nginx.crt\u0026#39; and key \u0026#39;/opt/zimbra/conf/nginx.key\u0026#39; ** Copying \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial.crt\u0026#39; to \u0026#39;/opt/zimbra/conf/nginx.crt\u0026#39; ** Copying \u0026#39;/opt/zimbra/ssl/zimbra/commercial/commercial.key\u0026#39; to \u0026#39;/opt/zimbra/conf/nginx.key\u0026#39; ** NOTE: restart services to use the new certificates. ** Cleaning up 3 files from \u0026#39;/opt/zimbra/conf/ca\u0026#39; ** Removing /opt/zimbra/conf/ca/629b96f7.0 ** Removing /opt/zimbra/conf/ca/ca.key ** Removing /opt/zimbra/conf/ca/ca.pem ** Copying CA to /opt/zimbra/conf/ca ** Copying \u0026#39;/opt/zimbra/ssl/zimbra/ca/ca.key\u0026#39; to \u0026#39;/opt/zimbra/conf/ca/ca.key\u0026#39; ** Copying \u0026#39;/opt/zimbra/ssl/zimbra/ca/ca.pem\u0026#39; to \u0026#39;/opt/zimbra/conf/ca/ca.pem\u0026#39; ** Creating CA hash symlink \u0026#39;629b96f7.0\u0026#39; -\u0026gt; \u0026#39;ca.pem\u0026#39; ** Creating /opt/zimbra/conf/ca/commercial_ca_1.crt ** Creating CA hash symlink \u0026#39;65ff7287.0\u0026#39; -\u0026gt; \u0026#39;commercial_ca_1.crt\u0026#39; ** Creating /opt/zimbra/conf/ca/commercial_ca_2.crt ** Creating CA hash symlink \u0026#39;fc5a8f99.0\u0026#39; -\u0026gt; \u0026#39;commercial_ca_2.crt\u0026#39; ** Creating /opt/zimbra/conf/ca/commercial_ca_3.crt ** Creating CA hash symlink \u0026#39;157753a5.0\u0026#39; -\u0026gt; \u0026#39;commercial_ca_3.crt\u0026#39; 重启zimbra服务 text 1 zmcontrol restart 瀏覽器訪問地址\n","permalink":"https://www.161616.top/zimbra-install-buisness-cert/","summary":"步骤1：取得SSL凭证 证书需要取的从根证书每一级的证书\n步骤2：合成SSL证书 将中级、根证书合成为一个证书\n顺序：按照从后到前合成为一个证书 如，三级 ==》二级 ==》 根\n合成后的格式如下\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 -----BEGIN CERTIFICATE----- 你的crt內容 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw 7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ -----END CERTIFICATE----- 步骤3：验证你的商业证书 复制生成的所有证书到目录 /opt/zimbra/ssl/zimbra/commercial 下，（合成后的根证书、证书、与秘钥）\n切換到 zimbra 用戶\ntext 1 2 3 4 5 6 7 8 $ zmcertmgr verifycrt comm {{privkey.","title":"zimbra安装三方颁发的证书"},{"content":" text 1 2 zmprov modifyServer {{ you domain }} zimbraMtaTlsAuthOnly FALSE zmcontrol restart 查看对应配置\ntext 1 zmprov getServer {{ you domain }} | grep Auth 查看SMTP是否开启成功\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ telnet localhost 25 Trying 127.0.0.1... Connected to localhost. Escape character is \u0026#39;^]\u0026#39;. 220 xxxx ESMTP Postfix ehlo xxxx 250-xxxxx 250-PIPELINING 250-SIZE 10240000 250-VRFY 250-ETRN 250-STARTTLS 250-AUTH LOGIN PLAIN #SMTP认证相关参数 250-AUTH=LOGIN PLAIN #SMTP认证相关参数 250-ENHANCEDSTATUSCODES 250-8BITMIME 250 DSN ","permalink":"https://www.161616.top/zimbra-enable-smtp-authentication/","summary":" text 1 2 zmprov modifyServer {{ you domain }} zimbraMtaTlsAuthOnly FALSE zmcontrol restart 查看对应配置\ntext 1 zmprov getServer {{ you domain }} | grep Auth 查看SMTP是否开启成功\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ telnet localhost 25 Trying 127.0.0.1... Connected to localhost. Escape character is \u0026#39;^]\u0026#39;. 220 xxxx ESMTP Postfix ehlo xxxx 250-xxxxx 250-PIPELINING 250-SIZE 10240000 250-VRFY 250-ETRN 250-STARTTLS 250-AUTH LOGIN PLAIN #SMTP认证相关参数 250-AUTH=LOGIN PLAIN #SMTP认证相关参数 250-ENHANCEDSTATUSCODES 250-8BITMIME 250 DSN ","title":"zimbra启用SMTP认证"},{"content":"命令如下\nldif cat \u0026lt;\u0026lt; EOF | ldapadd -x -W -H ldap://:389 -D \u0026#34;uid=zimbra,cn=admins,cn=zimbra\u0026#34;\rdn: uid=jak1,ou=people,dc=mail,dc=xxxxx2021,dc=com\rzimbraAccountStatus: active\rdisplayName: jak1\rgivenName: jak1\rsn: jak1\rzimbraMailStatus: enabled\robjectClass: inetOrgPerson\robjectClass: zimbraAccount\robjectClass: amavisAccount\rzimbraId: e2214a66-3ga2-4241-9223-44f222ce0522\rzimbraCreateTimestamp: 20191102062818.876Z\rzimbraMailHost: mail.xxxx2021.com\rzimbraMailTransport: lmtp:mail.xxxx2021.com:7025\rzimbraMailDeliveryAddress: scott8@mail.xxxx2021.com\rmail: jak1@mail.xxxx2021.com\rcn: jak1\ruid: jak1\ruserPassword:: e1NTSEE1MTJ9ZzBzZGlXRlBjbDQxa2xmZ200YXc1ZkJzSGQzVXNBdVBydUlKRnZ\rLTExYby9HWXBoUkNTMzZYMEx5VnpCZUJPMGJNTCtTV2IwSnhkaHdudTViR0c1bTJabFVhU3R1N1J3\rEOF zimbraAccountStatus 为账户设置中的状态 zimbraId 唯一的值 givenName 姓 displayName 显示名字 bash 1 ldapsearch -LLL -w 99tJFkhVfn -H ldap://172.31.110.115:389 -D \u0026#34;uid=zimbra,cn=admins,cn=zimbra\u0026#34;|less ","permalink":"https://www.161616.top/zimbra-ldap-createaccount/","summary":"命令如下\nldif cat \u0026lt;\u0026lt; EOF | ldapadd -x -W -H ldap://:389 -D \u0026#34;uid=zimbra,cn=admins,cn=zimbra\u0026#34;\rdn: uid=jak1,ou=people,dc=mail,dc=xxxxx2021,dc=com\rzimbraAccountStatus: active\rdisplayName: jak1\rgivenName: jak1\rsn: jak1\rzimbraMailStatus: enabled\robjectClass: inetOrgPerson\robjectClass: zimbraAccount\robjectClass: amavisAccount\rzimbraId: e2214a66-3ga2-4241-9223-44f222ce0522\rzimbraCreateTimestamp: 20191102062818.876Z\rzimbraMailHost: mail.xxxx2021.com\rzimbraMailTransport: lmtp:mail.xxxx2021.com:7025\rzimbraMailDeliveryAddress: scott8@mail.xxxx2021.com\rmail: jak1@mail.xxxx2021.com\rcn: jak1\ruid: jak1\ruserPassword:: e1NTSEE1MTJ9ZzBzZGlXRlBjbDQxa2xmZ200YXc1ZkJzSGQzVXNBdVBydUlKRnZ\rLTExYby9HWXBoUkNTMzZYMEx5VnpCZUJPMGJNTCtTV2IwSnhkaHdudTViR0c1bTJabFVhU3R1N1J3\rEOF zimbraAccountStatus 为账户设置中的状态 zimbraId 唯一的值 givenName 姓 displayName 显示名字 bash 1 ldapsearch -LLL -w 99tJFkhVfn -H ldap://172.31.110.115:389 -D \u0026#34;uid=zimbra,cn=admins,cn=zimbra\u0026#34;|less ","title":"使用ldap客户端创建zimbra ldap用户的格式"},{"content":"什么是 Helm Helm 是一个用于管理 Kubernetes 应用程序的包管理工具。它允许您定义、安装和升级 Kubernetes 应用程序，以简化应用程序部署和管理的过程。\n在 Kubernetes 中，应用程序被打包为一个或多个称为 \u0026ldquo;Charts\u0026rdquo; 的 Helm 资源。一个 Chart 是一个预定义的目录结构，包含了用于部署应用程序的 Kubernetes 资源清单模板。Chart 可以包含 Deployment、Service、ConfigMap、Ingress 等 Kubernetes 资源的定义。\n使用 Helm，您可以将应用程序打包为一个 Chart，并使用 Helm 客户端来安装和管理 Chart。这使得应用程序的部署过程更加简单、可重复和可扩展。您可以根据需要部署多个实例，轻松地进行升级和回滚操作，并使用 Helm 提供的值覆盖机制来自定义每个实例的配置。\n最重要的是，Helm 支持使用 Helm 仓库来共享和发布 Charts。Helm 仓库是一个集中存储 Charts 的地方，供用户从中搜索和安装 Charts。Helm 仓库可以是公共的，也可以是私有的，您可以自己搭建私有仓库来管理自己的 Charts。\nHelm 所作的事情 Helm 管理名为 chart 的Kubernetes包的工具。故 Helm 可以做以下的事情：\n创建一个新的 chart 将 chart 打包成归档 (tgz) 文件 与存储 chart 的仓库进行交互 在现有的 Kubernetes 集群中安装和卸载 chart 管理与Helm一起安装的 chart 的发布周期 Helm中的术语 chart：类似于rpm包，deb包，包含Kubernetes资源所需要的必要信息。 repo：chart仓库，类似于yum的仓库，chart仓库是一个简单的HTTP服务。 values：提供了自定义信息用来覆盖模板中的默认值。 release ：chart安装后的版本记录。 Helm 与 YAML 资源清单比有什么优势？ 模板化和参数化: Helm 使用 Go 的模板引擎来创建 Kubernetes 资源清单。这使得您可以在 Chart 中使用模板来定义资源配置的部分内容，例如标签、名称、端口等。同时，Helm 还支持使用参数化的值，允许您根据不同的环境或需求来自定义 Chart 的配置。这样一来，您可以根据需要生成不同的 Kubernetes 资源清单，而无需手动编辑每个清单文件。 可重用性: Helm 提供了一种将应用程序打包为 Chart 的方式，可以将 Chart 存储在 Helm 仓库中进行共享和重用。这样，您可以使用其他人创建的 Charts 来快速部署常见的应用程序，避免从头开始编写和管理 Kubernetes 资源清单。同时，您也可以将自己的应用程序打包为 Chart，方便自己和团队在不同环境中部署和管理。 版本管理和升级: 使用 Helm，您可以对已安装的 Chart 进行版本管理和升级。当应用程序的配置或代码发生变化时，您可以通过升级 Chart 来自动应用这些更改，而无需手动修改和重新部署 Kubernetes 资源清单。Helm 还提供了回滚功能，允许您在升级出现问题时快速回退到之前的版本。 依赖管理: Helm 允许您在 Chart 中定义和管理依赖关系。这意味着您可以在部署应用程序时自动解析和安装它所依赖的其他 Charts。这样，您可以轻松地管理应用程序所需的其他资源，减少手动处理依赖关系的工作。 部署的一致性和标准化: Helm 提供了一种标准的部署方式，使得不同团队或开发者之间可以使用相同的工具和流程来管理应用程序的部署。这样可以确保在不同环境中的一致性，并降低由于不同部署方式导致的错误和配置差异。 可管理的 Charts: Helm Charts 是可管理的，您可以在 Chart 中定义预先配置的模板、默认值、钩子和配置验证。这使得管理应用程序的配置和部署过程更加灵活和可控。 社区支持和生态系统: Helm 是一个活跃的开源项目，拥有庞大的用户社区和丰富的生态系统。这意味着您可以轻松地找到文档、示例、教程和问题解答，并从社区中获取支持和贡献。 可扩展性和插件支持: Helm 提供了插件机制，允许您扩展 Helm 的功能。您可以使用插件来添加自定义的命令、功能和工作流程，以满足特定需求或自动化常见的任务。 可视化界面和用户友好性: Helm 可以与各种第三方工具和平台集成，提供可视化界面和用户友好的操作方式。这使得非技术人员或不熟悉命令行的开发人员也能够方便地部署和管理应用程序。 安装helm Helm 安装主要官方提供了几种安装方式\n二进制版本安装：利用预编译好的二进制包直接解压使用 使用脚本安装：Helm 提供了安装脚本，可以直接拉去最新版进行安装在本地 各操作系统上的包管理工具进行安装 添加源 bash 1 2 helm repo add stable http://mirror.azure.cn/kubernetes/charts/ helm repo add incubator http://mirror.azure.cn/kubernetes/charts-incubator/ Helm 使用例子 [2] 命令行参数\n命令 说明 helm list 查看发布 helm remove 删除 helm repo add xxx url 添加仓库 helm upgrade 更新 helm rollback 回滚 \u0026ndash;generate-name 为部署的应用生成一个随即名 \u0026ndash;namespace 部署在哪个名称空间 \u0026ndash;set 覆盖 chart 中的默认 values 值 inspect 查看存在哪些 values 值 show 查看你要查看的内容，例如 chart, values等 使用本地 chart 包安装 bash 1 2 3 4 5 $ helm install --generate-name ./ # 或者 $ helm install --generate-name ./charts/grafana-6.56.6.tgz 查看 chart 中的 values 查看 chart 中的 values，可以查看 tar 归档的 chart\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 $ helm show values ./charts/grafana-6.56.6.tgz global: # To help compatibility with other charts which use global.imagePullSecrets. # Allow either an array of {name: pullSecret} maps (k8s-style), or an array of strings (more common helm-style). # Can be tempalted. # global: # imagePullSecrets: # - name: pullSecret1 # - name: pullSecret2 # or # global: # imagePullSecrets: # - pullSecret1 # - pullSecret2 imagePullSecrets: [] rbac: create: true ## Use an existing ClusterRole/Role (depending on rbac.namespaced false/true) # useExistingRole: name-of-some-(cluster)role pspEnabled: false pspUseAppArmor: false namespaced: false extraRoleRules: [] # - apiGroups: [] # resources: [] # verbs: [] extraClusterRoleRules: [] # - apiGroups: [] # resources: [] # verbs: [] serviceAccount: create: true name: nameTest: ## ServiceAccount labels. labels: {} .... 也可以查看解压后的\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 $ helm show values ./ # Default values for kube-prometheus-stack. # This is a YAML-formatted file. # Declare variables to be passed into your templates. ## Provide a name in place of kube-prometheus-stack for `app:` labels ## nameOverride: \u0026#34;\u0026#34; ## Override the deployment namespace ## namespaceOverride: \u0026#34;\u0026#34; ## Provide a k8s version to auto dashboard import script example: kubeTargetVersionOverride: 1.16.6 ## kubeTargetVersionOverride: \u0026#34;\u0026#34; ## Allow kubeVersion to be overridden while creating the ingress ## kubeVersionOverride: \u0026#34;\u0026#34; ## Provide a name to substitute for the full names of resources ## fullnameOverride: \u0026#34;\u0026#34; ## Labels to apply to all resources ## commonLabels: {} # scmhash: abc123 # myLabel: aakkmd ## Create default rules for monitoring the cluster ## defaultRules: create: true rules: alertmanager: true etcd: true configReloaders: true general: true : .... 查看 chart 包 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 $ helm show chart . annotations: artifacthub.io/license: Apache-2.0 artifacthub.io/links: | - name: Chart Source url: https://github.com/prometheus-community/helm-charts - name: Upstream Project url: https://github.com/prometheus-operator/kube-prometheus artifacthub.io/operator: \u0026#34;true\u0026#34; apiVersion: v2 appVersion: v0.65.1 dependencies: - condition: kubeStateMetrics.enabled name: kube-state-metrics repository: https://prometheus-community.github.io/helm-charts version: 5.6.* - condition: nodeExporter.enabled name: prometheus-node-exporter repository: https://prometheus-community.github.io/helm-charts version: 4.16.* - condition: grafana.enabled name: grafana repository: https://grafana.github.io/helm-charts version: 6.56.* description: kube-prometheus-stack collects Kubernetes manifests, Grafana dashboards, and Prometheus rules combined with documentation and scripts to provide easy to operate end-to-end Kubernetes cluster monitoring with Prometheus using the Prometheus Operator. home: https://github.com/prometheus-operator/kube-prometheus icon: https://raw.githubusercontent.com/prometheus/prometheus.github.io/master/assets/prometheus_logo-cb55bb5c346.png keywords: - operator - prometheus - kube-prometheus kubeVersion: \u0026#39;\u0026gt;=1.16.0-0\u0026#39; maintainers: - email: andrew@quadcorps.co.uk name: andrewgkew - email: gianrubio@gmail.com name: gianrubio - email: github.gkarthiks@gmail.com name: gkarthiks - email: kube-prometheus-stack@sisti.pt name: GMartinez-Sisti - email: scott@r6by.com name: scottrigby - email: miroslav.hadzhiev@gmail.com name: Xtigyro - email: quentin.bisson@gmail.com name: QuentinBisson name: kube-prometheus-stack sources: - https://github.com/prometheus-community/helm-charts - https://github.com/prometheus-operator/kube-prometheus type: application version: 46.5.0 show 查看chart值，可查看在线和离线\ntext 1 $ helm show values stable/jenkins | url | file # 查看chart包values 覆盖 chart 默认values值 使用 \u0026ndash;set 可以覆盖 chart 的默认值，可以指定多个\ntext 1 2 3 $ helm install --generate-name ./ \\ --set alertmanager.service.type=NodePort \\ --set prometheus.service.type=NodePort 更新一个应用 可以依据一个已经存在的 Chart 来更新已经部署过的应用\nbash 1 2 3 $ helm upgrade chart-1685626375 ./ \\ --set alertmanager.service.type=NodePort \\ --set prometheus.service.type=NodePort 查看对应 service 配置已经更改\nbash 1 2 3 4 5 $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE chart-1685626375-kube-prom-alertmanager NodePort 10.96.187.159 \u0026lt;none\u0026gt; 9093:30903/TCP 103m chart-1685626375-kube-prom-operator ClusterIP 10.98.6.186 \u0026lt;none\u0026gt; 443/TCP 103m chart-1685626375-kube-prom-prometheus NodePort 10.98.68.1 \u0026lt;none\u0026gt; 9090:30090/TCP 103m 从仓库下载一个 Chart bash 1 $ helm fetch stable/minio Chart Charts 是创建在特定目录下面的文件集合，然后可以将它们打包到一个版本化的存档中来部署。接下来我们就来看看使用 Helm 构建 charts 的一些基本方法。\nChart.yaml文件是chart必需的。包含了以下字段：\napiVersion 版本的说明\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 apiVersion: chart API 版本 （必需） name: chart名称 （必需） version: 语义化2 版本（必需） kubeVersion: 兼容Kubernetes版本的语义化版本（可选） description: 一句话对这个项目的描述（可选） type: chart类型 （可选） keywords: - 关于项目的一组关键字（可选） home: 项目home页面的URL （可选） sources: - 项目源码的URL列表（可选） dependencies: # chart 必要条件列表 （可选） - name: chart名称 (nginx) version: chart版本 (\u0026#34;1.2.3\u0026#34;) repository: 仓库URL (\u0026#34;https://example.com/charts\u0026#34;) 或别名 (\u0026#34;@repo-name\u0026#34;) condition: （可选） 解析为布尔值的yaml路径，用于启用/禁用chart (e.g. subchart1.enabled ) tags: # （可选） - 用于一次启用/禁用 一组chart的tag enabled: （可选） 决定是否加载chart的布尔值 import-values: # （可选） - ImportValue 保存源值到导入父键的映射。每项可以是字符串或者一对子/父列表项 alias: （可选） chart中使用的别名。当你要多次添加相同的chart时会很有用 maintainers: # （可选） - name: 维护者名字 （每个维护者都需要） email: 维护者邮箱 （每个维护者可选） url: 维护者URL （每个维护者可选） icon: 用做icon的SVG或PNG图片URL （可选） appVersion: 包含的应用版本（可选）。不需要是语义化的 deprecated: 不被推荐的chart （可选，布尔值） annotations: example: 按名称输入的批注列表 （可选）. --dry-run 模拟安装\n--debug 详细的输出\n--generate-name：生成随机实例名\ntext 1 helm install --generate-name --dry-run --debug --set favoriteDrink=7up ./testchart go template\n{{ .Values.favoriteDrink }} 读取变量值\nquote go template 函数，引用字符串 ，给变量值加\u0026quot;\u0026quot;\npipeline 可将多个功能连接在一起\n默认值 chart.yaml gender: {{ .values.gender|default \u0026quot;zhangsan\u0026quot; }} values.yaml 中不能加default函数\n流程控制\nif else\ntext 1 2 3 4 5 6 7 {{ if PIPELINE }} # Do something {{ else if OTHER PIPELINE }} # Do something else {{ else }} # Default case {{ end }} 如果 pipeline的值被判定为如下的值则为false：\na boolean false a numeric zero an empty string a nil (empty or null) an empty collection (map, slice, tuple, dict, array) 使用 - 摆脱新行 {{- if eq .Values.favorite.drink \u0026quot;coffee\u0026quot; }} - 在前面表示删除前面的空行，在后面表示删除后面的空行，{{- 之间没有空格\n{{ indent 2 \u0026quot;mug:true\u0026quot; }} 缩进，缩进的是文字内容不是yaml\n{{with .Values.xxx}} {{end}} 提升作用于，release: {{ $.Release.Name }} 执行时将变量映射到根域，可以在作用域中使用\nReference ​[1] Implementing a custom Kubernetes authentication method\n​[2] Helm 命令行\n","permalink":"https://www.161616.top/helm/","summary":"什么是 Helm Helm 是一个用于管理 Kubernetes 应用程序的包管理工具。它允许您定义、安装和升级 Kubernetes 应用程序，以简化应用程序部署和管理的过程。\n在 Kubernetes 中，应用程序被打包为一个或多个称为 \u0026ldquo;Charts\u0026rdquo; 的 Helm 资源。一个 Chart 是一个预定义的目录结构，包含了用于部署应用程序的 Kubernetes 资源清单模板。Chart 可以包含 Deployment、Service、ConfigMap、Ingress 等 Kubernetes 资源的定义。\n使用 Helm，您可以将应用程序打包为一个 Chart，并使用 Helm 客户端来安装和管理 Chart。这使得应用程序的部署过程更加简单、可重复和可扩展。您可以根据需要部署多个实例，轻松地进行升级和回滚操作，并使用 Helm 提供的值覆盖机制来自定义每个实例的配置。\n最重要的是，Helm 支持使用 Helm 仓库来共享和发布 Charts。Helm 仓库是一个集中存储 Charts 的地方，供用户从中搜索和安装 Charts。Helm 仓库可以是公共的，也可以是私有的，您可以自己搭建私有仓库来管理自己的 Charts。\nHelm 所作的事情 Helm 管理名为 chart 的Kubernetes包的工具。故 Helm 可以做以下的事情：\n创建一个新的 chart 将 chart 打包成归档 (tgz) 文件 与存储 chart 的仓库进行交互 在现有的 Kubernetes 集群中安装和卸载 chart 管理与Helm一起安装的 chart 的发布周期 Helm中的术语 chart：类似于rpm包，deb包，包含Kubernetes资源所需要的必要信息。 repo：chart仓库，类似于yum的仓库，chart仓库是一个简单的HTTP服务。 values：提供了自定义信息用来覆盖模板中的默认值。 release ：chart安装后的版本记录。 Helm 与 YAML 资源清单比有什么优势？ 模板化和参数化: Helm 使用 Go 的模板引擎来创建 Kubernetes 资源清单。这使得您可以在 Chart 中使用模板来定义资源配置的部分内容，例如标签、名称、端口等。同时，Helm 还支持使用参数化的值，允许您根据不同的环境或需求来自定义 Chart 的配置。这样一来，您可以根据需要生成不同的 Kubernetes 资源清单，而无需手动编辑每个清单文件。 可重用性: Helm 提供了一种将应用程序打包为 Chart 的方式，可以将 Chart 存储在 Helm 仓库中进行共享和重用。这样，您可以使用其他人创建的 Charts 来快速部署常见的应用程序，避免从头开始编写和管理 Kubernetes 资源清单。同时，您也可以将自己的应用程序打包为 Chart，方便自己和团队在不同环境中部署和管理。 版本管理和升级: 使用 Helm，您可以对已安装的 Chart 进行版本管理和升级。当应用程序的配置或代码发生变化时，您可以通过升级 Chart 来自动应用这些更改，而无需手动修改和重新部署 Kubernetes 资源清单。Helm 还提供了回滚功能，允许您在升级出现问题时快速回退到之前的版本。 依赖管理: Helm 允许您在 Chart 中定义和管理依赖关系。这意味着您可以在部署应用程序时自动解析和安装它所依赖的其他 Charts。这样，您可以轻松地管理应用程序所需的其他资源，减少手动处理依赖关系的工作。 部署的一致性和标准化: Helm 提供了一种标准的部署方式，使得不同团队或开发者之间可以使用相同的工具和流程来管理应用程序的部署。这样可以确保在不同环境中的一致性，并降低由于不同部署方式导致的错误和配置差异。 可管理的 Charts: Helm Charts 是可管理的，您可以在 Chart 中定义预先配置的模板、默认值、钩子和配置验证。这使得管理应用程序的配置和部署过程更加灵活和可控。 社区支持和生态系统: Helm 是一个活跃的开源项目，拥有庞大的用户社区和丰富的生态系统。这意味着您可以轻松地找到文档、示例、教程和问题解答，并从社区中获取支持和贡献。 可扩展性和插件支持: Helm 提供了插件机制，允许您扩展 Helm 的功能。您可以使用插件来添加自定义的命令、功能和工作流程，以满足特定需求或自动化常见的任务。 可视化界面和用户友好性: Helm 可以与各种第三方工具和平台集成，提供可视化界面和用户友好的操作方式。这使得非技术人员或不熟悉命令行的开发人员也能够方便地部署和管理应用程序。 安装helm Helm 安装主要官方提供了几种安装方式","title":"Kubernetes包管理 - Helm"},{"content":" 本文是Ceph集群部署系列第3章 使用cephadm纯离线安装Ceph集群 使用cephadm纯离线安装Ceph集群 2 Ceph集群安装 - ceph-deploy Ceph集群安装 - ceph-deploy下线rgw 环境配置 Ceph 是一个开源去中心化存储平台，专为满足现代存储需求而设计。 Ceph可扩展至 EB 级，并且设计为无单点故障，使其成为需要高度可用的灵活存储的应用程序的理想选择。\n下图显示了具有 Ceph 存储的示例 3 节点集群的布局。 两个网络接口可用于增加带宽和冗余，这有助于保持足够的带宽来满足存储要求，而不影响客户端应用程序。\n图：Ceph存储集群 Source：https://www.jamescoyle.net/how-to/1244-create-a-3-node-ceph-storage-cluster\n图中架构表示了一个无单点故障的 3 节点 Ceph 集群，以提供高度冗余的存储。 每个节点都配置了两个磁盘； 一台运行 Linux 操作系统，另一台将用于 Ceph 存储。 下面的输出显示了可用的存储空间，每个主机上的存储空间完全相同。 /dev/sda 是包含操作系统安装的根分区， /dev/sdb 是一个未触及的分区，将用于部署 Ceph 集群，对应的硬件信息如下表所示。\n主机名 public IP cluster IP 数据盘 ceph-nautilus01 10.0.0.50 10.0.0.50 /dev/sda\n/dev/sdb ceph-nautilus02 10.0.0.51 10.0.0.51 /dev/sda/dev/sdb ceph-nautilus03 10.0.0.52 10.0.0.52 /dev/sda/dev/sdb ceph-control 10.0.0.49 10.0.0.49 /dev/sda 部署工具 ceph-deploy 工具是在 “管理节点” (ceph-admin) 上的目录中运行。\nceph-deploy 部署ceph的原生工具 (最后支持版本 octopus 15) 借助于ssh来管理目标主机，sudo,和一些 python 模块来完成 ceph 集群的部署和后期维护。 一般讲 ceph-deploy 放置在专用节点，作为 ceph 集群的管理节点。 ceph-deploy 不是一个通用的部署工具，只是用于管理Ceph集群的，专门为用户快速部署并运行一个Ceph集群，这些功能和特性不依赖于其他的编排工具。 它无法处理客户端的配置，因此在部署客户端时就无法使用此工具。 下图是来自 ceph 官网的 ceph-deploy 部署工具的一个模型图\n图：ceph-deploy部署模型 Source：https://docs.ceph.com/en/nautilus/start/quick-start-preflight/\nCEPH 集群拓扑及网络 在Ceph内部存在两种流量：\nCeph内部各节点之间用来处理OSD之间数据复制，因此为了避免正常向客户端提供服务请求， public network 必须，所有客户端都应位于public network cluster network 可选 ceph-deploy 先决条件配置 将 Ceph 安装仓库添加到 “管理节点”。然后，安装 ceph-deploy。\nDebian/Ubuntu 添加 release key bash 1 wget -q -O- \u0026#39;https://download.ceph.com/keys/release.asc\u0026#39; | sudo apt-key add - 将 Ceph deb添加到您的存储库。并替换为安装的 Ceph 版本（例如 nautilus）。例如： bash 1 2 export CEPH_VERSION=nautilus echo deb https://download.ceph.com/debian-${CEPH_VERSION}/ $(lsb_release -sc) main | sudo tee /etc/apt/sources.list.d/ceph.list 更新仓库并安装 ceph-deploy bash 1 2 sudo apt update sudo apt install ceph-deploy RHEL/CentOS 安装 epel 源，这里方式很多可以任意选择 “你所在地区可用的 epel 源” bash 1 sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm 添加 Ceph rpm 仓库 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 export CEPH_VERSION=nautilus cat \u0026lt;\u0026lt; EOF \u0026gt; /etc/yum.repos.d/ceph.repo [ceph] name=Ceph packages for $basearch baseurl=https://download.ceph.com/rpm-${CEPH_VERSION}/el7/\\$basearch enabled=1 priority=2 gpgcheck=1 gpgkey=https://download.ceph.com/keys/release.asc [ceph-noarch] name=Ceph noarch packages baseurl=https://download.ceph.com/rpm-${CEPH_VERSION}/el7/noarch enabled=1 priority=2 gpgcheck=1 gpgkey=https://download.ceph.com/keys/release.asc [ceph-source] name=Ceph source packages baseurl=https://download.ceph.com/rpm-${CEPH_VERSION}/el7/SRPMS enabled=0 priority=2 gpgcheck=1 gpgkey=https://download.ceph.com/keys/release.asc EOF 更新缓存并安装 ceph-deploy bash 1 2 sudo yum clean all \u0026amp;\u0026amp; sudo yum makecache sudo yum install ceph-deploy 安装集群的预先条件 - CEPH NODE 管理节点必须拥有所有 ceph node 的无密码登录权限 ntp 开放端口 关闭 selinux 创建用于 ceph-deploy 的用户 ceph-deploy 实用程序必须以具有无密码 sudo 权限的用户身份登录 Ceph node，因为它需要在不提示输入密码的情况下安装软件和配置文件。\n最新版本的 ceph-deploy 支持 \u0026ndash;username 选项，因此您可以指定任何具有无密码的 sudo 用户（包括 root，但不推荐）。要使用 ceph-deploy \u0026ndash;username {username}，“所指定的用户必须具有对 Ceph Node 的无密码 SSH 访问权限”，因为 ceph-deploy 不会提示您输入密码。\nCeph 官方建议在集群中的所有 Ceph 节点上为 ceph-deploy 创建特定用户。==请不要使用 “ceph” 作为用户名==。整个集群中的统一用户名可能会提高易用性（不是必需的），但您应该避免使用明显的用户名，因为黑客通常会通过暴力破解来使用它们（例如 root, admin, 或使用项目名称）。以下过程将 {username} 替换为您定义的用户名，描述了如何使用无密码 sudo 创建用户。\n在每个 Ceph Node 创建一个用户 bash 1 2 3 export CEPH_USERNAME=ceph sudo useradd -d /home/${CEPH_USERNAME} -m ${CEPH_USERNAME} echo 1|sudo passwd ${CEPH_USERNAME} --stdin 对于添加到每个 Ceph Node 的新用户，请确保该用户具有 sudo 权限。 bash 1 2 echo \u0026#34;${CEPH_USERNAME} ALL = (root) NOPASSWD:ALL\u0026#34; | sudo tee /etc/sudoers.d/${CEPH_USERNAME} sudo chmod 0440 /etc/sudoers.d/${CEPH_USERNAME} 启用 SSH 无密码登录 由于 ceph-deploy 不会提示输入密码，因此必须在管理节点上生成 SSH 密钥并将公钥分发到每个 Ceph 节点。 ceph-deploy 将尝试为初始 monitor 生成 SSH 密钥。\n生成 ssh key，不要使用 sudo 或者是 root 用户 bash 1 2 3 4 5 6 7 8 $ ssh-keygen Generating public/private key pair. Enter file in which to save the key (/ceph-admin/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /ceph-admin/.ssh/id_rsa. Your public key has been saved in /ceph-admin/.ssh/id_rsa.pub. 将 SSH 密钥复制到每个 Ceph Node，将变量 “CEPH_USERNAME” 替换为创建 Ceph 部署用户创建的用户名。 通常情况下，主机名需要解析的，如果没有需要配置在 /etc/hosts 内\nbash 1 2 3 4 5 6 7 ssh-copy-id ${CEPH_USERNAME}@ceph-nautilus01 ssh-copy-id ${CEPH_USERNAME}@ceph-nautilus02 ssh-copy-id ${CEPH_USERNAME}@ceph-nautilus03 # 如果是 root 用户执行下面命令 sudo -u ${CEPH_USERNAME} ssh-copy-id ${CEPH_USERNAME}@ceph-nautilus01 sudo -u ${CEPH_USERNAME} ssh-copy-id ${CEPH_USERNAME}@ceph-nautilus02 sudo -u ${CEPH_USERNAME} ssh-copy-id ${CEPH_USERNAME}@ceph-nautilus03 添加主机名到 /etc/hosts (可选) bash 1 2 3 4 5 $ tee \u0026gt;\u0026gt; /etc/hosts \u0026lt;\u0026lt; EOF 10.0.0.50 ceph-nautilus01 10.0.0.51 ceph-nautilus02 10.0.0.52 ceph-nautilus03 EOF 网卡开机自启动 Ceph OSD Peer 并通过网络向 Ceph monitor 报告。如果默认情况下网络处于关闭状态，则在启用网络之前，Ceph 集群无法在启动期间联机。\n在一些 Linux 发行版下（例如 CentOS）上的默认配置默认关闭网络接口。确保在启动过程中网络接口打开，以便 Ceph 守护进程可以通过网络进行通信。(如果你的系统是新装的)\n开放所需端口 Ceph Monitor (ceph-mon) 默认使用端口 6789 进行通信。默认情况下，Ceph OSD 在 6800:7300 端口范围内进行通信。详细信息请参见网络配置参考 [1]。\n确保关闭 SELinux 在 CentOS 和 RHEL 上，SELinux 默认设置为“Enforcing”。为了简化您的安装，我们建议将 SELinux 设置为 Permissive 或完全禁用，这是 Ceph 官方给出的建议\nbash 1 sudo setenforce 0 要持久配置 SELinux（如果 SELinux 存在问题，则建议这样做），请修改 /etc/selinux/config 中的配置文件。\nPreferences 确保您的 “包管理器” 已安装并启用 “priority/preferences”。在 CentOS 上，您可能需要安装 EPEL。在 RHEL 上，您可能需要启用可选存储库。\nbash 1 sudo yum install yum-plugin-priorities 例如，在 RHEL 7 服务器上，执行以下命令安装 yum-plugin-priorities 并启用 rhel-7-server-optional-rpms 存储库：\nbsah sudo yum install yum-plugin-priorities --enablerepo=rhel-7-server-optional-rpms 初始化新的 CEPH 集群 在这里我们创建一个包含一个 Ceph Monitor (ceph-mon) 和 三个 Ceph OSD (osd daemon) 的 Ceph 集群。通常使用 ceph-deploy 部署集群，最佳方式是在 “管理节点” 上创建一个目录，用于维护 ceph-deploy 为集群生成的配置文件和密钥。\nbash 1 mkdir ceph-cluster \u0026amp;\u0026amp; cd ceph-cluster 需要注意的是，ceph-deploy 将文件输出到当前目录。执行 ceph-deploy 时需要确保位于此目录中。\n还需要注意的是，需要使用 “SSH 免密的那个用户”\n确保集群节点清洁性 如果在任何时候遇到问题后并想重新开始，可以执行下列命令清除所有安装包和配置：\nbash 1 2 3 4 ceph-deploy purge {ceph-node} [{ceph-node}] ceph-deploy purgedata {ceph-node} [{ceph-node}] ceph-deploy forgetkeys rm ceph.* 需要注意的是，执行 ceph-deploy，必须执行第四条命令 rm ceph.*\n创建集群 创建集群会生成配置文件保存到当前工作目录中，使用 ceph-deploy 执行命令新建一个集群。\n创建集群 bash 1 2 3 4 # 语法 ceph-deploy new {initial-monitor-node(s)} # 示例, 指定节点的 hostname, fqdn or hostname:fqdn ceph-deploy new ceph-nautilus01 ceph-nautilus02 ceph-nautilus03 ceph-deploy 会输出到当前文件夹下的文件包含，Ceph 配置文件 (ceph.conf)、ceph-mon 的 keyring 文件 (ceph.mon.keyring) 以及新集群的日志文件。\n如果主机存在多个网络接口（即公共网络和集群网络是分开的），需要在 Ceph 配置文件的 [global] 部分下添加公共网络设置。 bash 1 2 3 public network = {ip-address}/{bits} # 示例 public network = 10.1.2.0/24 或者通过命令指定两个网络的 IP\nbash 1 2 3 ceph-deploy new ceph-nautilus01 ceph-nautilus02 ceph-nautilus03 \\ --cluster-network 172.18.0.0/24 \\ --public-network 10.0.0.0/24 如果在 IPv6 环境中部署，请将以下内容添加到本地目录中的 ceph.conf 中： bash 1 echo ms bind ipv6 = true \u0026gt;\u0026gt; ceph.conf 现在可以安装 ceph 软件包了，执行下列命令 bash 1 2 3 ceph-deploy install {ceph-node} [...] # 示例 ceph-deploy install ceph-nautilus01 ceph-nautilus02 ceph-nautilus03 在命令执行后，ceph-deploy 将在每个节点上安装 Ceph，所以要确保对应节点需要提前安装好了 ceph yum 仓库文件\n也可以通过 --release 指定版本进行安装\nbash 1 ceph-deploy install --release=nautilus ceph-nautilus01 ceph-nautilus02 ceph-nautilus03 部署 ceph-mon 并收集密钥： bash 1 ceph-deploy mon create-initial ​\t通常完成步骤5后，本地工作目录应具有以下 keyring 文件：\nceph.client.admin.keyring ceph.bootstrap-mgr.keyring ceph.bootstrap-osd.keyring ceph.bootstrap-mds.keyring ceph.bootstrap-rgw.keyring ceph.bootstrap-rbd.keyring ceph.bootstrap-rbd-mirror.keyring 使用 ceph-deploy 将配置文件和管理密钥复制到管理节点和 Ceph Node，以便可以在这些节点上使用 ceph CLI 时而无需在每次执行命令时指定 ceph-mon 地址和 keyring文件 (ceph.client.admin.keyring)。 bash 1 2 3 ceph-deploy admin {ceph-node(s)} # 示例 ceph-deploy admin ceph-nautilus01 ceph-nautilus02 ceph-nautilus03 部署 CEPH MANAGER (ceph-mgr)，此步骤仅需要 ==luminous+== 以上版本 bash 1 2 3 ceph-deploy mgr create {ceph-node(s)} *Required only for luminous+ builds, i.e \u0026gt;= 12.x builds* # 示例 ceph-deploy mgr create ceph-nautilus01 到步骤8时，只差 OSD 就完成了 RADOS 集群的安装，下面为集群添加 OSD bash 1 2 3 4 5 ceph-deploy osd create --data {device} {ceph-node} # 示例 ceph-deploy osd create --data /dev/sdb ceph-nautilus01 ceph-deploy osd create --data /dev/sdb ceph-nautilus02 ceph-deploy osd create --data /dev/sdb ceph-nautilus03 Note：如果要在 LVM 卷上创建 OSD，则 \u0026ndash;data 的参数必须是 {volume_group}/{lv_name}，而不是卷的块设备的路径\n检查集群状态 bash 1 2 3 ssh node1 sudo ceph health # or ceph -s Troubleshooting No module named pkg_resources bash 1 2 3 4 5 6 7 $ ceph-deploy new ceph-nautilus01 ceph-nautilus02 ceph-nautilus03 Traceback (most recent call last): File \u0026#34;/usr/bin/ceph-deploy\u0026#34;, line 18, in \u0026lt;module\u0026gt; from ceph_deploy.cli import main File \u0026#34;/usr/lib/python2.7/site-packages/ceph_deploy/cli.py\u0026#34;, line 1, in \u0026lt;module\u0026gt; import pkg_resources ImportError: No module named pkg_resources 解决：This issue can be solved by installing yum install -y python-setuptools.\nRuntimeError: NoSectionError: No section: \u0026lsquo;ceph\u0026rsquo; bash 1 2 3 4 5 6 7 8 9 10 [2019-09-11 05:31:42,640][ceph-nautilus01][DEBUG ] Installing : ceph-release-1-1.el7.noarch 1/1 [2019-09-11 05:31:42,640][ceph-nautilus01][DEBUG ] warning: /etc/yum.repos.d/ceph.repo created as /etc/yum.repos.d/ceph.repo.rpmnew [2019-09-11 05:31:42,759][ceph-nautilus01][DEBUG ] Verifying : ceph-release-1-1.el7.noarch 1/1 [2019-09-11 05:31:42,759][ceph-nautilus01][DEBUG ] [2019-09-11 05:31:42,759][ceph-nautilus01][DEBUG ] Installed: [2019-09-11 05:31:42,759][ceph-nautilus01][DEBUG ] ceph-release.noarch 0:1-1.el7 [2019-09-11 05:31:42,759][ceph-nautilus01][DEBUG ] [2019-09-11 05:31:42,759][ceph-nautilus01][DEBUG ] Complete! [2019-09-11 05:31:42,759][ceph-nautilus01][WARNING] ensuring that /etc/yum.repos.d/ceph.repo contains a high priority [2019-09-11 05:31:42,767][ceph_deploy][ERROR ] RuntimeError: NoSectionError: No section: \u0026#39;ceph\u0026#39; 解决：CEPH Node 上不要安装 yum 仓库，ceph-deploy 会自动安装，如果存在新的文件会被命名为 ceph.repo.rpmnew\n向 RADOS 集群添加 OSD 列出并擦净磁盘 ceph-deploy disk 命令可以检查并列出OSD节点上所有可用的磁盘相关的信息。\nsh 1 ceph-deploy disk list stor01 stor02 stor03 stor04 如果遇到 Running command: sudo fdisk -l 无输出，原因为系统问中文，修改 en_us.utf8 后可正常显示。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ ceph-deploy disk list stor01 [ceph_deploy.conf][DEBUG ] found configuration file at: /home/cephadmin/.cephdeploy.conf [ceph_deploy.cli][INFO ] Invoked (2.0.1): /bin/ceph-deploy disk list stor01 [ceph_deploy.cli][INFO ] ceph-deploy options: [ceph_deploy.cli][INFO ] username : None [ceph_deploy.cli][INFO ] verbose : False [ceph_deploy.cli][INFO ] debug : False [ceph_deploy.cli][INFO ] overwrite_conf : False [ceph_deploy.cli][INFO ] subcommand : list [ceph_deploy.cli][INFO ] quiet : False [ceph_deploy.cli][INFO ] cd_conf : \u0026lt;ceph_deploy.conf.cephdeploy.Conf instance at 0x7fab61201488\u0026gt; [ceph_deploy.cli][INFO ] cluster : ceph [ceph_deploy.cli][INFO ] host : [\u0026#39;stor01\u0026#39;] [ceph_deploy.cli][INFO ] func : \u0026lt;function disk at 0x7fab6144e9b0\u0026gt; [ceph_deploy.cli][INFO ] ceph_conf : None [ceph_deploy.cli][INFO ] default_release : False [stor01][DEBUG ] connection detected need for sudo [stor01][DEBUG ] connected to host: stor01 [stor01][DEBUG ] detect platform information from remote host [stor01][DEBUG ] detect machine type [stor01][DEBUG ] find the location of an executable [stor01][INFO ] Running command: sudo fdisk -l 而后，在管理节点上使用 ceph-deploy 命令 擦除计划专用于OSD磁盘上的所有分区表和数据以便用于OSD，命令格式为ceph-deploy disk zab {osd-server-name} {disk-name}，需要注意的是此步会清除目标设备上的所有数据。\n擦除一个磁盘\nsh 1 2 ceph-deploy disk zap ceph-deploy disk zap {ceph_node} /dev/sdb Note：如果是未格式化的块设备不需要额外擦除，ceph 集群要求 ceph 管理的块设备必须是未格式化的，如果格式化过的需要擦除\nceph-deploy osd \u0026ndash;help：\nblock-db 可以理解为RocksDB数据库，元数据存放的位置 block-wal 数据库的数据日志存放的位置 filestore 如果使用filestore,明确指定选项--filestore 指明数据放哪，并指明日志放哪（日志指的是文件系统日志）ceph-deploy osd create {node} --filestore --data /path/to/data --journal /path/to/journal bluestore自身没有文件系统，故无需日志，数据库需要日志 扩展集群 添加 OSD 当一个 “基本集群” 部署好并运行，下一步就是扩展集群。通常会扩展集群，扩展集群存在两种类型，集群组件与OSD，这里主要围绕 扩展 OSD\n早期版本的ceph-deploy命令支持在将添加OSD的过程分为两个步骤：准备OSD，激活OSD，但新版本中，此种操作方式已被废除，添加OSD的步骤只能由命令 ceph-deploy osd create create {node} \u0026ndash;data {data-disk} ，一次完成，默认存储引擎为 bluestore\nsh 1 2 ceph-deploy osd create {node} --data /dev/sdb ceph-deploy osd create {node} --data /dev/sdc 而后可使用 ceph-deploy osd list {node} 命令列出指定节点上的OSD：\n移除 OSD 的 Ceph集群中的一个OSD通常对应一个设备，且运行于专用的守护进程。在某OSD设备出现故障，或管理员出于管理只需确实要移除特定的OSD设备时，需要先停止相关的守护进程，而后再进行移除操作。对于Luminous及其之后的版本来说，停止和移除命令的格式如下\n停止设备: ceph osd out {osd-num} 停止进程: sudo systemctl stop ceph-osd@{osd-num} 移除设备: ceph osd purge {id} \u0026ndash;yes-i-really-mean-it 若类似如下的OSD的设备信息存在于ceph.conf配置文件中，管理员在删除OSD之后手动将其删除。\ntext 1 2 [osd.1] host = {hostname} 不过，对于 Luminous 之前的版本来说，管理员需要依次手动执行如下步骤删除OSD设备\n于CRUSH运行图中移除设备: ceph osd crush remove {name} 移除OSD的认证key: ceph auth del osd.{osd-num} 移除设备: ceph osd purge {id} \u0026ndash;yes-i-really-mean-it 扩展 ceph-mon Ceph 集群需要至少一个 Ceph Monitor 和一个 Ceph Manager，生产环境中，为了实现高可用，Ceph集群通常运行多个监视器，以免单监视器整个存储集群崩溃。Ceph使用 Paxos 算法，改算法是需要至少需要板书以上的监视器（大于n/2，其中n为总监视器数量），才能形成法定人数，尽管此非必须，奇数个 ceph-mon 往往更好。\n使用 ceph-deploy mon add {nodes} 命令可以一次添加一个 ceph-mon 到集群中。\nsh 1 2 ceph-deploy mon add nautilus02 ## 此处使用短格式名称，长格式名称会报错。 ceph-deploy mon add nautilus03 设置完成后，可以在ceph客户端上查看监视器及法定人数的相关信息:\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 $ ceph quorum_status --format json-pretty { \u0026#34;election_epoch\u0026#34;: 20, \u0026#34;quorum\u0026#34;: [ 0, 1, 2 ], \u0026#34;quorum_names\u0026#34;: [ \u0026#34;stor01\u0026#34;, \u0026#34;stor02\u0026#34;, \u0026#34;stor03\u0026#34; ], \u0026#34;quorum_leader_name\u0026#34;: \u0026#34;stor01\u0026#34;, \u0026#34;monmap\u0026#34;: { \u0026#34;epoch\u0026#34;: 3, \u0026#34;fsid\u0026#34;: \u0026#34;69fb9b55-3fb5-42d0-8cf7-239a3b569791\u0026#34;, \u0026#34;modified\u0026#34;: \u0026#34;2019-06-06 21:19:41.274199\u0026#34;, \u0026#34;created\u0026#34;: \u0026#34;2019-06-05 12:35:31.143594\u0026#34;, \u0026#34;features\u0026#34;: { \u0026#34;persistent\u0026#34;: [ \u0026#34;kraken\u0026#34;, \u0026#34;luminous\u0026#34;, \u0026#34;mimic\u0026#34;, \u0026#34;osdmap-prune\u0026#34; ], \u0026#34;optional\u0026#34;: [] }, \u0026#34;mons\u0026#34;: [ { \u0026#34;rank\u0026#34;: 0, \u0026#34;name\u0026#34;: \u0026#34;stor01\u0026#34;, \u0026#34;addr\u0026#34;: \u0026#34;10.0.0.4:6789/0\u0026#34;, \u0026#34;public_addr\u0026#34;: \u0026#34;10.0.0.4:6789/0\u0026#34; }, { \u0026#34;rank\u0026#34;: 1, \u0026#34;name\u0026#34;: \u0026#34;stor02\u0026#34;, \u0026#34;addr\u0026#34;: \u0026#34;10.0.0.5:6789/0\u0026#34;, \u0026#34;public_addr\u0026#34;: \u0026#34;10.0.0.5:6789/0\u0026#34; }, { \u0026#34;rank\u0026#34;: 2, \u0026#34;name\u0026#34;: \u0026#34;stor03\u0026#34;, \u0026#34;addr\u0026#34;: \u0026#34;10.0.0.6:6789/0\u0026#34;, \u0026#34;public_addr\u0026#34;: \u0026#34;10.0.0.6:6789/0\u0026#34; } ] } } 扩展 Manager 节点 Ceph Manager (ceph-mgr) 以 Active/Standy 模式运行，部署其他 ceph-mgr 守护进程可确保在 Active 节点的 ceph-mgr 守护进程故障时，其中一个 Standby 实例可以在不中断服务的情况下接管其任务。\nmgr 就是无状态的 web 服务，一般来讲两个足够了。\nsh 1 ceph-deploy mgr create {ceph_node} 启动 RGW RGW (Rados Gateway) 必要组件，仅在需要用到对象存储兼容 “S3” 和 “Swift” 的 RESTful 接口时才需要部署 RGW 实例，相关的命令为ceph-deploy rgw create (gateway-node) 。\nradosgw需要用自用的存储池不能与RBD混合使用，RGW 会在创建时自动初始化出存储池来，RGW 需要有相应服务才能运行起来。\nsh 1 ceph-deploy rgw create nautilus02 添加完成后，ceph -s 命令的 service 一段中会输出相关信息：\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ ceph -s cluster: id: 69fb9b55-3fb5-42d0-8cf7-239a3b569791 health: HEALTH_WARN application not enabled on 1 pool(s) services: mon: 3 daemons, quorum stor01,stor02,stor03 mgr: stor01(active), standbys: stor04 osd: 8 osds: 8 up, 8 in rgw: 1 daemon active data: pools: 7 pools, 160 pgs objects: 196 objects, 1.5 KiB usage: 21 GiB used, 79 GiB / 100 GiB avail pgs: 160 active+clean 默认情况下，RGW 实例监听于 TCP 协议的 7480 端口，需要修改，可以通过在运行 RGW 的节点上编辑其主配置文件 ceph.conf 进行修改，相关参数如下所示\ntext 1 2 [client] rgw_frontends = \u0026#34;civetweb port=8080\u0026#34; RGW 会在 RADOWS 集群上生成包括如下存储池的一系列存储池\nsh 1 2 3 4 5 6 7 8 $ ceph osd pool ls mypool rbdpool testpool .rgw.root default.rgw.control default.rgw.meta default.rgw.log RGW 提供的是兼容 S3 和 Swift 的 REST 接口，客户端通过 HTTP 进行交互，完成数据的增删改查等管理操作。\n启用文件系统 (CephFS) 接口 CephFS 需要至少运行一个 Metadata (MDS) 守护进程 (ceph-mds)，此进程管理与 CephFS 上存储的文件相关的元数据，并协调对 Ceph存储集群的访问。因此，若要使用 CephFS ，需要在存储集群中至少部署一个 MDS 实例，增加 MDS 可以使用命令 ceph-deploy mds create {ceph-node} 完成\n还需主义的是，每个 CephFS 都至少需要两个存储池，一个用来存放元数据 (Metadata Pool)，一个存放数据 (Data Pool)。\nsh 1 $ ceph-deploy mds create stor01 查看 MDS 的相关状态可以发现，刚添加的 MDS 处于 standby 状态\n在运行起来还不够，还没为其创建存储池，故其不能正常工作，处于standby模式。\ntext 1 2 $ ceph mds stat , 1 up:standby 使用 CephFS 之前需要事先于集群中创建一个文件系统，并为其分别指定 “元数据” 和 “数据” 相关的存储池，下面创建一个名为 cephfs 的文件系统用于测试，使用cephfs-metadata为数据存储池，使用cephfs-data为数据存储池。\nsh 1 2 3 4 5 6 7 8 # 创建存储池 ceph osd pool create cephfs-metadata 64 ceph osd pool create cephfs-data 64 # 创建 cephfs ## 语法 ceph fs new {fs_name} {meatadata-pool} {data-pool} ## 示例 ceph fs new cephfs cephfs-metadata cephfs-data ceph fs add_data_pool： 额外添加数据池 ceph fs new： 创建新文件系统 ceph fs status： 查看CephFS 文件系统状态 sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ ceph fs status cephfs cephfs - 0 clients ====== +------+--------+--------+---------------+-------+-------+ | Rank | State | MDS | Activity | dns | inos | +------+--------+--------+---------------+-------+-------+ | 0 | active | stor02 | Reqs: 0 /s | 10 | 13 | +------+--------+--------+---------------+-------+-------+ +-----------------+----------+-------+-------+ | Pool | type | used | avail | +-----------------+----------+-------+-------+ | cephfs-metadata | metadata | 2286 | 21.7G | | cephfs-data | data | 0 | 21.7G | +-----------------+----------+-------+-------+ +-------------+ | Standby MDS | +-------------+ +-------------+ MDS version: ceph version 13.2.6 (7b695f835b03642f85998b2ae7b6dd093d9fbce4) mimic (stable) Reference [1] Network Configuration Reference\n[2] ceph_deploy RuntimeError: NoSectionError: No section: \u0026lsquo;ceph\u0026rsquo;\n","permalink":"https://www.161616.top/ch02-3-install-ceph-with-ceph-deploy/","summary":"本文是Ceph集群部署系列第3章 使用cephadm纯离线安装Ceph集群 使用cephadm纯离线安装Ceph集群 2 Ceph集群安装 - ceph-deploy Ceph集群安装 - ceph-deploy下线rgw 环境配置 Ceph 是一个开源去中心化存储平台，专为满足现代存储需求而设计。 Ceph可扩展至 EB 级，并且设计为无单点故障，使其成为需要高度可用的灵活存储的应用程序的理想选择。\n下图显示了具有 Ceph 存储的示例 3 节点集群的布局。 两个网络接口可用于增加带宽和冗余，这有助于保持足够的带宽来满足存储要求，而不影响客户端应用程序。\n图：Ceph存储集群 Source：https://www.jamescoyle.net/how-to/1244-create-a-3-node-ceph-storage-cluster\n图中架构表示了一个无单点故障的 3 节点 Ceph 集群，以提供高度冗余的存储。 每个节点都配置了两个磁盘； 一台运行 Linux 操作系统，另一台将用于 Ceph 存储。 下面的输出显示了可用的存储空间，每个主机上的存储空间完全相同。 /dev/sda 是包含操作系统安装的根分区， /dev/sdb 是一个未触及的分区，将用于部署 Ceph 集群，对应的硬件信息如下表所示。\n主机名 public IP cluster IP 数据盘 ceph-nautilus01 10.0.0.50 10.0.0.50 /dev/sda\n/dev/sdb ceph-nautilus02 10.0.0.51 10.0.0.51 /dev/sda/dev/sdb ceph-nautilus03 10.0.0.52 10.0.0.52 /dev/sda/dev/sdb ceph-control 10.0.0.49 10.0.0.49 /dev/sda 部署工具 ceph-deploy 工具是在 “管理节点” (ceph-admin) 上的目录中运行。","title":"Ceph集群安装 - ceph-deploy"},{"content":"什么是VPN VPN ( Virtual Private Network) 虚拟专用网络，是依靠ISP和其他的NSP，在公共网络中建立专用的数据通信网络的技术，可以为企业与企业之间或者个人与企业之间提供安全的数据传输隧道服务。在VPN中任意两点之间的连接并没有传统专网所需的端到端的物理链路，而是利用公共网络资源动态组成的，可以理解为通过私有的隧道技术在公共数据网络上模拟出来的和专网有同样功能的点到点的专线技术，所谓虚拟是指不需要去拉实际的长途物理线路，而是借用了公共Internet网络实现。\nvpn直观的形象图：\nVPN Server/Client \u003c---------------------------------\u003e VPN Server/Client\rVPN的作用 VPN功能可以帮助公司里的远程用户(出差，家里)、公司的分支机构、商业合作伙伴及供应商等公司和自己的公司内部网络之间建立可信的安全连接或者是局域网连接，确保数据的加密安全传输和业务访问，对至运维工程师来说，还可以连接不同的机房为局域网，处理相关的业务流。我们可以通过一张网络逻辑图来描述VPN的作用。\n图：OpenVPN架构\rSource：https://www.slideteam.net/vpn-tunnel-architecture-connecting-corporate-and-branch-office.html\nVPN的分类 我们根据VPN的常见企业应用，将VPN分为以下4类应用\n远程访问VPN服务 即通过个人电脑远程拨号到企业办公网络。\n一般为企业内部员工出差、休假或特殊情况下在远离办公室的时候，又有需求访问公司的内部网络获取相关资源，就可以通过VPN拨号到公司内部.此时远程拨号的员工和办公室内的员工以及其他拨号的员工之间都相当于一个局域网络内。例如:访问内部的域控制器，文件服务器，OA系统，ERP, HTTP服务，内网聊天工具等局域网服务应用。\n对于运维人且就是需要个人电脑远程拨号到企业网站IDC机房，远程维护内网服务器（一般无外网IP）。\n此点是技术人员特别是运维人且在工作中会经常用这个方法维护大量的机房内无外网的服务器及网络设备。\n企业内部网络之间VPN服务 在公司的分支机构的局域网和公司总部LAN之间的VPN连接。通过公网Internet建立VPN将公司在各地的分支机构的LAN连接到公司总部的LAN。例如:各大超市之间业务结算等。\n这是由于地域的原因而产生的VPN的需求，通过VPN让不同地域内的机器可以互相访问，就好像是一个局域网一样。例如：办公室互联协同办公，机房互联数据同步及业务访问等。\n互联网公司多IDC机房之间VPN服务 此处是运维架构人员需要考虑的问题。不同机房之间业务管理和业务访问，数据流动。\n企业外部VPN服务 在供应商、合作伙伴的LAN和本公司的LAN之间建立的VPN服务。\n访问外国网站 翻墙的应用\n常贝隧道协议介绍 PPTP 点对点隧道协议(PPTP)是由包括微软和3Com等公司组成的PPTP论坛开发的一种点对点隧道协议，基于拨号使用的PPP协议，使用PAP或CHAP之类的加密算法，或者使用Microsoft的点对点加密算法MPPE。其通过跨越基于TCP/IP的数据网络创建VPN。实现了从远程客户端到专用企业服务器之间数据的安全传输。PPTP支持通过公共网络(例如Internet)建立按需的、多协议的、虚拟专用网络。PPTP允许加密IP通讯，然后在要跨越公司IP网络或公共IP网络(如Internet)发送的IP头中对其进行封装。典型的linux平台的开源软件为pptp。\nPPTP属于点对点方式的应用，比较适合远程的企业用户拨号到企业进行办公等的应用。\nL2TP L2TP第2层隧道协议(L2TP)是IETF基于L2F (Cisco的第二层转发协议)开发的PPTP的后续版本。是一种工业标准Internet隧道协议，其可以为跨越面向数据包的媒体发送点到点协议(PPP)框架提供封装。PPTP和L2TP都使用PPP协议对数据进行封装，然后添加附加包头用于数据在互联网络上的传输。PPTP只能在两端点间建立单一隧道。L2TP支持在两端点间使用多隧道，用户可以针对不同的服务质量创建不同的隧道。L2TP可以提供隧道验证，而PPTP则不支持隧道验证。但是当L2TP或PPTP 与 IPSEC 共同使用时，可以由IPSEC提供隧道验证，不需要在第2层协议上验证隧道使用L2TP。PPTP要求互联 网络为IP网络。L2TP只要求隧道媒介提供面向数据包的点对点的连接，L2TP可以在IP(使用UDP)，帧中继永久虚拟电路(PVCs)，X.25虚拟电路(VCs)或ATM VCs网络上使用。\nL2TP (Layer 2 Tunneling Protocol)\n在计算机网络中，第2层隧道协议（L2TP）是一种隧道协议，用于支持虚拟专用网络（VPN）或作为ISP提供服务的一部分。它本身不提供任何加密或机密性。相反，它依靠它在隧道内传递的加密协议来提供隐私。\n由于L2TP协议缺乏固有的机密性，因此通常与IPsec一起实施。这被称为L2TP / IPsec，并在IETF RFC 3193中进行了标准化。\nReference L2TP\nIPSec IP安全协议(IPSec: IP Security)实际上是一套协议包而不是一个独立的协议。从1995年开始IPSec的研究以来，IETF IPSec工作组在它的主页上发布了几十个Internet草案文献和12个RFC文件。其中，比较重要的有RFC2409 IKE(互连网密钥交换)、RFC2401 IPSec协议、RFC2402 AH验证包头、RFC2406 ESP加密数据等文件。\nIPSec隧道模式隧道是封装、路由与解封装的整个过程。隧道将原始数据包隐藏(或封装)在新的数据包内部。该新的数据包可能会有新的寻址与路由信息，从而使其能够通过网络传输。隧道与数据保密性结合使用时，在网络上窃听通讯的人将无法获取原始数据包数据(以及原始的源和目标)。封装的数据包到达目的地后，会删除封装，原始数据包头用于将数据包路由到最终目的地。\n隧道本身是封装数据经过的逻辑数据路径，对原始的源和目的端，隧道是不可见的，而只能看到网络路径中的点对点连接。连接双方并不关心隧道起点和终点之间的任何路由器、交换机、代理服务器或其他安全网关。将隧道和数据保密性结合使用时，可用于提供VPN。\n封装的数据包在网络中的隧道内部传输。在此示例中，该网络是Internet。网关可以是外部Internet与专用网络间的周界网关。周界网关可以是路由器、防火墙、代理服务器或其他安全网关。另外，在专用网络内部可使用两个网关来保护网络中不信任的通讯。，\n当以隧道模式使用IPSec时，其只为IP通讯提供封装。使用IPSec隧道模式主要是为了与其他不支持IPSec上的L2TP或PPTP VPN隧道技术的路由器、网关或终端系统之间的相互操作。\nSSL VPN SSL VPN，SSL协议提供了数据私密性、端点验证、信息完整性等特性。SSL协议由许多子协议组成，其中两个主要子协议是握手协议和记录协议。握手协议允许服务器和客户端在应用协议传输第一个数据字节以前，彼此确认，协商一种加密算法和密码钥匙。在数据传输期间，记录协议利用握手协议生成的密钥加密和解密后来交换的数据。\nSSL独立于应用，因此任何一个应用程序都可以享受它的安全性而不必理会执行细节。SSL置身于网络结构体系的传输层和应用层之间。此外，SSL本身就被几乎所有的Web浏览器支持。这意味着客户端不需要为了支持SSL连接安装额外的软件。这两个特征就是SSL能应用于VPN的关键点。\n典型的SSL VPN应用如OpenVPN，是一个比较好的开源软件。9penVPN允许参与建立VPN的单点使用预设的私组，第三方证书，或者用户名/密码来进行身份验证。它大量使用了OpenSSL加密库，以及SSLv3/TLSv1协议。OpenVPN能在Linux, xBSD, Mac OSX与Windows上运行。它并不是一个基于Web的VPN软件，也不与IPsec及其他VPN软件包兼容（唯一不如PPTP VPN的缺点）。OpenVPN是基于C/S架构的软件，需要单独安装OpenVPN客户端（不如PPTP VPN之处）。\n图：VPN信息处理过程\rSource：https://juejin.cn/post/7061094724047732767\n实现vpn功能的常见开源产品 PPTP VPN 使用PPTP VPN的最大优势在于，无需在windows客户端单独安装VPN客户端软件，windows默认就支持PPTP VPN拨号连接功能。另外，PPTP VPN属于点对点方式的应用，比较适合远程的企业用户拨号到企业进行办公等的应用，很多小区及网络设备不支持PPTP导致无法访问。\nSSL VPN（OpenVPN） PPTP主要为那些经常外出移动或家庭办公的用户考虑，而OpenVPN不但使用于PPTP的应用场景，还适合针对企业异地两地总分公司之间的VPN不间断按需连接，例如：ERP，OA，及时通讯工具等在企业中的应用。缺点：需要单独安装客户端软件。\n典型的开源软件：OpenVPN\nIPSEC VPN IPSEC VPN也适合针对企业异地两地总分公司或多个IDC机房之间的VPN不间断按需连接，并且在部署使用上更简单方便。\n典型的开源软件：openswan\n根据企业生产场景需求选择vpn方案建议 如果领导愿意花钱，可以选择相关硬件产品，不错的成熟的很多，例如：防火墙、负载均衡等硬件产品都附带VPN功能。 对于多数互联网公司，为了体现我们运维构顶的价值，我们应该建议老板选择开源产品，优势就是省钱，可扩展性更强，例如:二次开发，相应功能的改动。 对于开源的产品，建议: 个人拨号选择 OpenVPN，功能强大、稳定可靠。 如果不希望单独安装客户端拨号，则可选择PPTP。尽量打消使用PPTP VPN的想法。 多个企业之间或者多个IDC机房直接互联，选择 IPSecVPN （openswan）或 OpenVPN。 OpenVPN开源产品介绍 在众多VPN的产品中，OpenVPN无疑是Linux下开源VPN的先锋，它提供了良好的访问性能和友好的用户GUI。\nOpenVPN是一个用于创建虚拟专用网络加密通道的软件包，最早由James Yonan编写。OpenVPN允许参与建立VPN的单点使用预设的私钥，第三方证书，或者用户名/密码来进行身份验证.它大量使用了OpenSSL加密库，以及SSLv3/TLSv1协议。OpenVPN能在Linux、xBSD、MacOS X与Windows上运行。OpenVPN是一个服务器和客户端软件，而不是一个基于Web的VPN软件，也不与IPsec及其他VPN软件包兼容。\nOpenVPN依赖的SSL与TLS协议介绍 SSL即，安全套接层(Secure Sockets Layer, SSL)是一种安全协议，诞生的目的是为网络通信提供安全及数据完整性保障，SSL在传输层中对网络通信进行加密。\nSSL采用公开密钥技术，保证两个应用间通信的保密性和可靠性，使客户与服务器应用之间的通信不被攻击者窃听。它在服务器和客户机两端可同时被支持，目前已成为互联网上保密通讯的工业标准。现行的Web浏览器亦普遍将HTTP和SSL相结合，从而实现安全通信。SSL协议其继任者是TLS。\n后来 IETF 将SSL作了标准化，即RFC2246，并将其称为TLS (Transport Layer Security)，其最新版本是RFC 5246，版本1.2。从技术上讲，TLS1.0与SSL3.0的差异非常微小。\nTLS(Transport Layer Security)\nTLS利用密钥算法在互联网上提供端点身份认证与通讯保密，其基础是公组基础设施(public key infrastructure, PKI)。不过在实现的典型例子中，只有网络服务者被可靠身份验证，而其客户端则不一定。这是因为公钥基础设施普遍商业运营，电子签名证书通常需要付费购买。协议的设计在某种程度上能够使主从架构应用程序通讯本身预防窃听、干扰(Tampering)和消息伪造。\nOpenVPN的加密通信原理过程 OpenVPN使用TLS加密是通过使用公开密钥（非对称密钥，加密解密使用不同的key，一个称为Public key，另一个是Private key）对数据进行加密的，对于TLS传输的工作原理，这里暂且先不介绍。对于OpenVPN使用TLS mode，首先Server和Client要有相同CA签发的证书，双方通过交换证书验证双方的合法性以决定是否建立VPN连接，然后使用对方CA把自己目前使用的数据加密方法(类似于密钥)加密后发送给对方，由于使用对方CA加密的，所以只有对方CA对应的Private key才能解密该字串，保证了此密钥的安全性，并且此密钥定期改变，对于窃听者来说，可能还没有破解出密钥，通信双方己经更换密钥了。\nOpenVPN的多种身份验证方式 OpenVPN提供了多种身份验证方式，用以确认参与连接双方的身份，包括：预享私钥，第三方证书以及用户名/密码组合等。预享私钥最为简单，但同时它只能用于建立点对点的VPN；基于PKI的第三方证书提供了最完善的功能，但是需要额外的精力去维护一个PKI证书体系。OpenVPN 2.0后引入了用户名/口令组合的身份验证方式，它可以省略客户端预享密钥，但是仍有一份服务器CA证书需要被用作加密，比较好的验证方式还有LDAP或域控制器统一验证等。\nOpenVPN通信原理 OpenVPN所有的通信都基于一个单一的IP端口(默认为1194)，默认使用UDP协议通讯，同时TCP（推荐）也被支持。OpenVPN连接能通过大多数的代理服务器，并且能够在NAT的环境中很好地工作。OpenVPN服务端具有向客户端“推送”某些网络配置信息的功能，这些信息包括：IP地址、路由设置等。OpenVPN提供了两种虚拟网络接口:通用Tun/Tap驱动，通过它们，可以建立三层IP隧道，或者虚拟二层以太网，后者可以传送任何类型的二层以太网络数据。传送的数据可通过LZO算法压缩。OenVPN2.0以后版本每个进程可以同时管理数个并发的隧道。\nOpenVPN使用通用网络协议(TCP与UDP)的特点使它成为IPsec等协议的理想替代，尤其是在ISP (Internet service provider)过滤某些特定VPN协议的情况下。\n在选择协议时候，需要注意2个加密隧道之间的网络状况，如有高延迟或者丢包较多的情况下，请选择TCP协议作为底层协议，UDP协议由于存在无连接和重传机制，导致要隧道上层的协议进行重传，效率非常低下。这里建议用TCP协议方式。\n参考：\nhttp://www.baike.com/wiki/OpenVPN\nhttp://zh.wikipedia.org/zh-cn/OpenVPN\nOpenVPN的技术核心是虚拟网卡，其次是SSL协议实现，SSL协议前面已阐述过，这里重点对虚拟网卡及其在OpenVPN的中的工作机理进行介绍。\n虚拟网卡是使用网络底层编程技术实现的一个驱动软件，安装后在主机上多出一个网卡，可以像其它网卡一样进行配置。服务程序可以在应用层打开虚拟网卡，如果应用软件(如IE)向虚拟网卡发送数据，则服务程序可以读取到该数据，如果服务程序写合适的数据到虚拟网卡，应用软件也可以接收得到。虚拟网卡在很多的操作系统下都有相应的实现，这也是OpenVPN能够跨平台一个很重要的理由。\n在OpenVPN中，如果用户访问一个远程的虚拟地址(属于虚拟网卡配用的地址系列，区别于真实地址)则操作系统会通过路由机制将数据包(TUN模式)或数据帧(TAP模式)发送到虚拟网卡上，服务程序接收该数据并进行相应的处理后，通过SOCKET从外网给虚拟网卡，则应用软件可以接收到，完成了一个单向传输的过程，反之亦然。\nOpenVPN使用OpenSSL库加密数据与控制信息：它使用了OpesSSL的加密以及验证功能意味着，它能够使用任何OpenSSL支持的算法。它提供了可选的数据包HMAC功能以提高连接的安全性。此外，OpenSSL的硬件加速也能提高它的性能。\nOpenVPN驱动部分实现了网卡处理和字符设备。网卡处理网络数据，字符设备完成与应用层的数据交互。 使用OpenVPN必须修改路由表 工作过程，发送数据：\n应用程序发送网络数据。 网络数据根据修改后的路由表把数据路由到虚拟网卡。 虚拟网卡把数据放到数据队列中。 字符设备从数据队列中取数据，然后送给应用层。 应用层把数据转发给物理网卡。 物理网卡发送数据。 接收过程：\n物理网卡接受到数据，并传到应用空间。 应用守护程序通过字符设备，把数据传给驱动网卡。 数据通过虚拟网卡重新进入网络堆栈。 网络堆栈把数据传给上层真实的应用程序。 OpenVPN生产环境常用场景 远程拨号访问企业网络或IDC机房 即通过个人电脑远程拨号到企业办公网络。一般为企业内部员工出差、休假或特殊情况下在远离办公室的时候，又有需求访问公司的内部网络获取相关资源，就可以通过VPN拨号到公司内部。此时远程拨号的员工和办公室内的员工4及其他拨号的员工之间都相当于一个局域网络内。例如：访问内部的域控制器，文件服务器，OA系统，HTTP服务，内网飞秋聊天工具等局域网服务应用。\n对于运维人就是需要个人电脑远程拨号到企业网站IDC机房，远程维护服务器。此点是技术人员特别是运维人员在工作中会经常用这个方法维护大量的机房内无外网IP的服务器及网络设备。\nclient-LAN类型数据库\n企业异地内部网络通过VPN连接成局域网 在公司的分支机构的局域网和公司总部LAN之间的VPN连接。通过公网Internet建立VPN将公司在各地的分支机构的LAN连接到公司总部的LAN。例如:各大超市之间业务结算等。\n这是由于地域的原因而产生的VPN的需求，通过VPN让不同地域内的机器可以互相访问，就好像是一个局域网一样。例如：办公室互联协同办公，机房互联数据同步及业务访问等。\nLAN-LAN类型示意图\n互联网公司多IDC机房之间通过VPN连接交换数据 此处是j臼维架构人员需要考虑的问题。不同机房之间业务管理和业务访问，数据流动。\n企业外部VPN服务 在供应商、合作伙伴的LAN和本公司的LAN之间建立的VPN服务。从技术上讲2，3，4的实现是一样的。\n路由方式和NAT方式企业实际应用区别和异同小结。\nNAT方式适合subnet machine 的网关不是VPNSERVER的场景。\nstatic routing适用于每个subnet machine都要配置对应的路由路由\n在企业应用场景中，多数情况下，内部服务器的网关不是VPNSERVER。此时NAT更简便，路由方式更复杂。\n企业中OpenVPN服务维护的常见问题\n如何增加多个vpn client证书文件\n方法1：为每一个客户建立一个证书。 OpenVPN 客户端单多个证书的撤销。\n如果某个同事离职，那么需要取消其VPN的拨入权限，可以通过在服务器端吊销该客户端证书来实现。官方给出的3个吊销证书的可能情况：revoking-certificates ","permalink":"https://www.161616.top/ch1-introduction/","summary":"什么是VPN VPN ( Virtual Private Network) 虚拟专用网络，是依靠ISP和其他的NSP，在公共网络中建立专用的数据通信网络的技术，可以为企业与企业之间或者个人与企业之间提供安全的数据传输隧道服务。在VPN中任意两点之间的连接并没有传统专网所需的端到端的物理链路，而是利用公共网络资源动态组成的，可以理解为通过私有的隧道技术在公共数据网络上模拟出来的和专网有同样功能的点到点的专线技术，所谓虚拟是指不需要去拉实际的长途物理线路，而是借用了公共Internet网络实现。\nvpn直观的形象图：\nVPN Server/Client \u003c---------------------------------\u003e VPN Server/Client\rVPN的作用 VPN功能可以帮助公司里的远程用户(出差，家里)、公司的分支机构、商业合作伙伴及供应商等公司和自己的公司内部网络之间建立可信的安全连接或者是局域网连接，确保数据的加密安全传输和业务访问，对至运维工程师来说，还可以连接不同的机房为局域网，处理相关的业务流。我们可以通过一张网络逻辑图来描述VPN的作用。\n图：OpenVPN架构\rSource：https://www.slideteam.net/vpn-tunnel-architecture-connecting-corporate-and-branch-office.html\nVPN的分类 我们根据VPN的常见企业应用，将VPN分为以下4类应用\n远程访问VPN服务 即通过个人电脑远程拨号到企业办公网络。\n一般为企业内部员工出差、休假或特殊情况下在远离办公室的时候，又有需求访问公司的内部网络获取相关资源，就可以通过VPN拨号到公司内部.此时远程拨号的员工和办公室内的员工以及其他拨号的员工之间都相当于一个局域网络内。例如:访问内部的域控制器，文件服务器，OA系统，ERP, HTTP服务，内网聊天工具等局域网服务应用。\n对于运维人且就是需要个人电脑远程拨号到企业网站IDC机房，远程维护内网服务器（一般无外网IP）。\n此点是技术人员特别是运维人且在工作中会经常用这个方法维护大量的机房内无外网的服务器及网络设备。\n企业内部网络之间VPN服务 在公司的分支机构的局域网和公司总部LAN之间的VPN连接。通过公网Internet建立VPN将公司在各地的分支机构的LAN连接到公司总部的LAN。例如:各大超市之间业务结算等。\n这是由于地域的原因而产生的VPN的需求，通过VPN让不同地域内的机器可以互相访问，就好像是一个局域网一样。例如：办公室互联协同办公，机房互联数据同步及业务访问等。\n互联网公司多IDC机房之间VPN服务 此处是运维架构人员需要考虑的问题。不同机房之间业务管理和业务访问，数据流动。\n企业外部VPN服务 在供应商、合作伙伴的LAN和本公司的LAN之间建立的VPN服务。\n访问外国网站 翻墙的应用\n常贝隧道协议介绍 PPTP 点对点隧道协议(PPTP)是由包括微软和3Com等公司组成的PPTP论坛开发的一种点对点隧道协议，基于拨号使用的PPP协议，使用PAP或CHAP之类的加密算法，或者使用Microsoft的点对点加密算法MPPE。其通过跨越基于TCP/IP的数据网络创建VPN。实现了从远程客户端到专用企业服务器之间数据的安全传输。PPTP支持通过公共网络(例如Internet)建立按需的、多协议的、虚拟专用网络。PPTP允许加密IP通讯，然后在要跨越公司IP网络或公共IP网络(如Internet)发送的IP头中对其进行封装。典型的linux平台的开源软件为pptp。\nPPTP属于点对点方式的应用，比较适合远程的企业用户拨号到企业进行办公等的应用。\nL2TP L2TP第2层隧道协议(L2TP)是IETF基于L2F (Cisco的第二层转发协议)开发的PPTP的后续版本。是一种工业标准Internet隧道协议，其可以为跨越面向数据包的媒体发送点到点协议(PPP)框架提供封装。PPTP和L2TP都使用PPP协议对数据进行封装，然后添加附加包头用于数据在互联网络上的传输。PPTP只能在两端点间建立单一隧道。L2TP支持在两端点间使用多隧道，用户可以针对不同的服务质量创建不同的隧道。L2TP可以提供隧道验证，而PPTP则不支持隧道验证。但是当L2TP或PPTP 与 IPSEC 共同使用时，可以由IPSEC提供隧道验证，不需要在第2层协议上验证隧道使用L2TP。PPTP要求互联 网络为IP网络。L2TP只要求隧道媒介提供面向数据包的点对点的连接，L2TP可以在IP(使用UDP)，帧中继永久虚拟电路(PVCs)，X.25虚拟电路(VCs)或ATM VCs网络上使用。\nL2TP (Layer 2 Tunneling Protocol)\n在计算机网络中，第2层隧道协议（L2TP）是一种隧道协议，用于支持虚拟专用网络（VPN）或作为ISP提供服务的一部分。它本身不提供任何加密或机密性。相反，它依靠它在隧道内传递的加密协议来提供隐私。\n由于L2TP协议缺乏固有的机密性，因此通常与IPsec一起实施。这被称为L2TP / IPsec，并在IETF RFC 3193中进行了标准化。\nReference L2TP\nIPSec IP安全协议(IPSec: IP Security)实际上是一套协议包而不是一个独立的协议。从1995年开始IPSec的研究以来，IETF IPSec工作组在它的主页上发布了几十个Internet草案文献和12个RFC文件。其中，比较重要的有RFC2409 IKE(互连网密钥交换)、RFC2401 IPSec协议、RFC2402 AH验证包头、RFC2406 ESP加密数据等文件。\nIPSec隧道模式隧道是封装、路由与解封装的整个过程。隧道将原始数据包隐藏(或封装)在新的数据包内部。该新的数据包可能会有新的寻址与路由信息，从而使其能够通过网络传输。隧道与数据保密性结合使用时，在网络上窃听通讯的人将无法获取原始数据包数据(以及原始的源和目标)。封装的数据包到达目的地后，会删除封装，原始数据包头用于将数据包路由到最终目的地。\n隧道本身是封装数据经过的逻辑数据路径，对原始的源和目的端，隧道是不可见的，而只能看到网络路径中的点对点连接。连接双方并不关心隧道起点和终点之间的任何路由器、交换机、代理服务器或其他安全网关。将隧道和数据保密性结合使用时，可用于提供VPN。","title":"ch1 VPN与OpenVPN应用场景分析"},{"content":"OpenVPN安装环境需求 设备 IP 笔记本或PC\n(adsl上网) 10.0.0.0/24 办公室（DHCP） OpenVPN Server双网卡 eth0:10.0.0.4/24（外网）\neth1:192.168.100.4（内网） IDC机房内部局域网服务器 192.168.100.0/24\nIDC机房内网服务器无外网IP，又希望ADSL上网笔记本（运维人员），在不同的网络能够直接访问 实现需求：在远端通过VPN客户端(笔记本)拨号到VPN，然后在笔记本电脑上可以直接访问vpnserver所在局域网内的多个servers，进行维护管理 环境 VPN-Server eth0:10.0.0.4(外网IP)。GW:10.0.0.1, dns:10.0.0.1。\neth1:172.0.0.1(内网IP)。GW:不配\n提示：检查是否可以ping通client eth0 IP。\nApp client Server:ethO:172.0.0.2。GW:路由器。提示：检查是否可以ping通VPN-Server eth1 IP。 OpenVPN 解决方案图解\r部署OpenVPN Server Reference download lzo\ninstalling-openvpn\nopenvpn offical releases\nopenvpn github\n安装前准备\nopenvpn支持的平台：\nLinux kernel 2.6+ OpenBSD 5.1+ Mac OS X Darwin 10.5+ FreeBSD 7.4+ Windows (WinXP and higher) 下载地址：\nopenvpn-releases openvpn github 安装openvpn的依赖项：\n0.9.8版或更高版本的OpenSSL库，对于加密是必需的 PolarSSL库，是加密的替代版本1.1或更高版本 LZO实时压缩库，连接压缩所需 bash 1 yum install openssl-devel lzo-devel pam-devel -y bash 1 2 3 4 5 6 ./configure \\ --sbindir=/usr/sbin/ --sysconfdir=/etc/openvpn/ --libdir=/usr/lib/ --includedir=/usr/include/ --docdir=/usr/doc 使用rpmbuild安装：openvpn.spec\n配置OpenVPN Server openvpn的配配置文件在下面目录中\nsample/sample-config-files/client.conf\nsample/sample-config-files/server.conf\nbash 1 cp sample-config-files/server.conf /etc/openvpn/ 建立CA证书 easy-rsa是一个基于OpenSSL命令行工具的小型RSA密钥管理包。虽然它主要关注SSL VPN应用程序空间的密钥管理，但它也可用于构建Web证书。它最初被包含在OpenVPN中，但现在是一个单独的项目。\nopenvpn在2.3-alpha1 -\u0026gt; 2.3-alpha2版本迭代是将easy-rsa分割为单独的子项目。在2.3版本之前easy-rsa包含在openvpn内，在openvpn-2.2.2/easy-rsa/2.0目录下。\n证书也可以使用openssl进行生成，easy-rsa可以简化证书生成流程。\n设置并签署第一个请求 ./easyrsa help [commond] 查看帮助\n./easyrsa init-pki 初始化公钥基础设施，（初始化单独的PKI目录）\n./easyrsa build-ca nopass 创建ca\n./easyrsa gen-req {name} nopass 创建CSR，{name}只是一个名字，不代表任何。\n./easyrsa import-req pki/{name}.req 将请求文件.req，导入CA\n./easyrsa sign-req {server|client} {name} 签署请求\n./easyrsa revoke {name} 吊销证书\n./easyrsa gen-dh 生成Diffie-Hellman\nReference easyrsa readme\nEasyRSA的配置源 在easyrsa生成证书时，需要提供证书的配置，来设置证书对应的详情。此时就需要easyrsa获取外部的配置来替换证书默认的参数。\neasyrsa有四种方式获得外部配置\n命令行选项\n环境变量 ：覆盖全局选项\nenv-vars列表，任何设置/覆盖它的（CLI）以及可能的简洁描述如下所示：\nEASYRSA easyrsa脚本所在的Easy-RSA顶级目录。 EASYRSA_PKI 保存PKI的文件的目录，默认为$PWD/pki。 EASYRSA_DN：设置为字符串cn_only或org更改要包含在请求DN中的字段 EASYRSA_REQ_COUNTRY：DN国家 EASYRSA_REQ_PROVINCE：DN状态/省 EASYRSA_REQ_CITY：DN城市/位置 EASYRSA_REQ_ORG：DN组织 EASYRSA_REQ_EMAIL：DN电子邮件 EASYRSA_REQ_OU：DN组织单位 EASYRSA_KEY_SIZE：设置密钥大小单位 EASYRSA_ALGO：设置要使用的加密算法：rsa或ec EASYRSA_CA_EXPIRE：设置CA到期时间 EASYRSA_CERT_EXPIRE：设置已颁发证书的到期时间：单位天 EASYRSA_REQ_CN：默认CN，批量配置时可设置。 vars 文件：无扩展名的vars文件是为Easy-RSA提供配置的文件，该文件优先级低于环境变量与命令行参数。easyrsa对vars文件的检测顺序为：\n--vars 参数引用的文件 环境变量值EASYRSA_VARS_FILE 环境变量设置的pki目录 EASYRSA_PKI 执行默认目录 $PWD/pki 内部默认值\n生成服务端证书和密钥KEY文件\n与上一步类似，其中“Common Name”项需要填写VPN服务器的FQDN，其他均可默认为default值。还会出现：“Sign the certificate? [y/n]”和“1 out of 1 certificate requests certified, commit? [y/n]”。都输入y然后回车，其它可参照如下。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ### 准备生成证书用的CSR相关配置 export EASYRSA_REQ_COUNTRY=\u0026#34;HK\u0026#34; export EASYRSA_REQ_PROVINCE=\u0026#34;HK\u0026#34; export EASYRSA_REQ_CITY=\u0026#34;HongKong\u0026#34; export EASYRSA_REQ_ORG=\u0026#34;china mobile\u0026#34; export EASYRSA_REQ_EMAIL=\u0026#34;10086.chinamobile.cn\u0026#34; #证书有效期 export EASYRSA_CA_EXPIRE=3650 export EASYRSA_CERT_EXPIRE=3650 # 默认cn export EASYRSA_REQ_CN=\u0026#34;csol\u0026#34; # 批处理模式 export EASYRSA_BATCH=\u0026#34;enable\u0026#34; ## 初始化pki ./easyrsa init-pki ## 创建CA ./easyrsa build-ca nopass ## 申请csr ./easyrsa gen-req csol nopass ## 导入csr ./easyrsa import-req ./pki/reqs/csol.req csol ## 签发证书 ./easyrsa sign-req server csol 注意：easyrsa在执行是也是通过openssl进行，需要openssl-easyrsa.cnf 与 x509-types\n加强OpenVPN安全性 TLS-auth 参数增加所有 SSL/TLS 握手了额外的HMAC签名的完整性验证。任何没有正确HMAC签名的UDP数据包都被丢弃，而无需进一步处理。TLS-AUTH 可以防止：\nOpenVPN UDP端口上的DDoS攻击或Flood DDoS 。 端口扫描以确定哪些UDP端口处于侦听状态。 SSL/TLS缓冲区溢出漏洞。 切断来自未经授权的机器的SSL/TLS握手。 生成随机共享密钥（仅适用于非TLS静态密钥加密模式）\nbash 1 openvpn --genkey secret ta.key 在服务器配置中，添加：\ntext 1 tls-auth ta.key 0 在客户端配置中，添加：\ntext 1 tls-auth ta.key 1 Reference openvpn-security\n调试OpenVPN服务端 取消服务器上防火墙iptables对Openvpn(默认1194)的拦截。以及允许服务进行转发。\ncentos7+ firewalld\nbash 1 2 firewall-cmd --add-port=1194/udp --permanent firewall-cmd --permanent --direct --passthrough ipv4 -t nat -A POSTROUTING -s 192.168.100.0/24 -o eth1 -j MASQUERADE iptables\ntext 1 2 iptables -A INPUT -i eth0 -m state --state NEW -p udp --dport 1194 -j ACCEPT iptables -t nat -A POSTROUTING -o eth1 -s 192.168.100.0/24 -j MASQUERADE 开启内核转发功能：\ntext 1 2 sysctl net.ipv4.ip_forward |grep 1 \u0026gt; /dev/null \u0026amp;\u0026amp; \\ echo net.ipv4.ip_forward=1 \u0026gt;\u0026gt; /etc/sysctl.conf 启动OpenVPN server\ntext 1 openvpn --config server.conf 默认情况下，openvpn客户端不能访问客户端上具相同子网的IP的主机。\n服务端 server.conf参数详解 openvpn默认配置文件在 sample/sample-config-files/server.conf 目录下。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 ##################################################### # 多客户端服务器的OpenVPN 2.0配置文件示例\t# # # # 本文件用于多客户端\u0026lt;-\u0026gt;单服务器端的 # # OpenVPN服务器端配置 # # # # OpenVPN也支持单机\u0026lt;-\u0026gt;单机的配置 # # (在网站上的示例页面更多信息) # # # # 这个配置可以在Windows或Linux/BSD系统上工作\t# # Windows的路径名需要加双引号并使用双反斜杠 如\t# # \u0026#34;C:\\\\Program Files\\\\OpenVPN\\\\config\\\\foo.key\u0026#34;\t# # # # 前面加\u0026#39;#\u0026#39;或\u0026#39;;\u0026#39;的是注释 # ##################################################### # OpenVPN应该监听哪个本地IP地址（可选） # 如果不设置，默认监听所有IP ;local a.b.c.d # OpenVPN应该监听哪个端口(TCP/UDP) # 如果想在同一台计算机上运行多个OpenVPN实例，可以使用不同的端口号来区分它们 # 在防火墙上打开这个端口 port 1194 # 服务器使用TCP还是UDP协议 ;proto tcp proto udp # 指定OpenVPN创建的通信隧道类型 # \u0026#34;dev tun\u0026#34;将会创建一个路由IP隧道 # \u0026#34;dev tap\u0026#34;将会创建一个以太网隧道 # 如果是以太网桥接模式，并且提前创建了一个名为\u0026#34;tap0\u0026#34;的与以太网接口进行桥接的虚拟接口，则你可以使用\u0026#34;dev tap0\u0026#34; # 如果想控制VPN的访问策略，必须为TUN/TAP接口创建防火墙规则 # 在非Windows系统中，可以给出明确的单位编号，如\u0026#34;tun0\u0026#34; # 在Windows中，也可以使用\u0026#34;dev-node\u0026#34; # 在大多数系统上，除非部分或完全禁用了TUN/TAP接口的防火墙，否则VPN将不起作用。 ;dev tap dev tun # 如果想配置多个隧道，需要用到网络连接面板中TAP-Win32适配器的名称(如\u0026#34;MyTap\u0026#34;) # 在XP SP2或更高版本的系统中，可能需要有选择地禁用掉针对TAP适配器的防火墙 # 通常情况下，非Windows系统则不需要该指令。 ;dev-node MyTap # 设置SSL/TLS根证书(ca)、证书(cert)和私钥(key)。 # 每个客户端和服务器端都需要它们各自的证书和私钥文件。 # 服务器端和所有的客户端都将使用相同的CA证书文件。 # # 通过easy-rsa目录下的一系列脚本可以生成所需的证书和私钥。 # 服务器端和每个客户端的证书必须使用唯一的Common Name。 # # 也可以使用遵循X509标准的任何密钥管理系统来生成证书和私钥。 # OpenVPN也支持使用一个PKCS #12格式的密钥文件(详情查看站点手册页面的\u0026#34;pkcs12\u0026#34;指令) ca ca.crt cert server.crt key server.key # 该文件应该保密 # 迪菲·赫尔曼参数 # 使用如下命令生成： # openssl dhparam -out dh2048.pem 2048 dh dh2048.pem # 网络拓扑结构 # 应该为子网(通过IP寻址) # 除非必须支持Windows客户端v2.0.9及更低版本(net30即每个客户端/30) # 默认为\u0026#34;net30\u0026#34;(不建议) ;topology subnet # 设置服务器端模式，并提供一个VPN子网，以从中为客户端分配IP地址 # 本例中服务器端自身占用10.8.0.1，其他的将分配给客户端使用 # 每个客户端将能够通过10.8.0.1访问服务器 # 如果使用的是以太网桥接模式，注释掉本行。更多信息请查看官方手册页面。 server 10.255.255.255.0 # 在此文件中维护客户端与虚拟IP地址之间的关联记录 # 如果OpenVPN重启，重新连接的客户端可以被分配到先前分配的虚拟IP地址 ifconfig-pool-persist ipp.txt # 该指令仅针对以太网桥接模式 # 首先，必须使用操作系统的桥接能力将以太网网卡接口和TAP接口进行桥接 # 然后，需要手动设置桥接接口的IP地址、子网掩码，这里假设为10.8.0.4和255.255.255.0 # 最后，必须指定子网的一个IP范围(例如从10.8.0.50开始，到10.8.0.100结束)，以便于分配给连接的客户端 # 如果不是以太网桥接模式，直接注释掉这行指令即可 ;server-bridge 10.255.255.2510.8.0.50 10.8.0.100 # 该指令仅针对使用DHCP代理的以太网桥接模式 # 此时客户端将请求服务器端的DHCP服务器，从而获得分配给它的IP地址和DNS服务器地址 # 在此之前，也需要先将以太网网卡接口和TAP接口进行桥接 # 注意：该指令仅用于OpenVPN客户端(如Windows)，并且该客户端的TAP适配器需要绑定到一个DHCP客户端上 ;server-bridge # 推送路由信息到客户端，以允许客户端能够连接到服务器后的其他私有子网 # 即允许客户端访问VPN服务器可访问的其他局域网 # 记住，这些私有子网还需要将OpenVPN客户端地址池（10.8.0.0/255.255.255.0）路由回到OpenVPN服务器 ;push \u0026#34;route 192.168.1255.255.255.0\u0026#34; ;push \u0026#34;route 192.168.2255.255.255.0\u0026#34; # 要为指定的客户端分配特定的IP地址，或者客户端后的私有子网也要访问VPN # 可以针对该客户端的配置文件使用ccd子目录 # 请参阅手册页获取更多信息 # 示例1：假设有个Common Name为\u0026#34;Thelonious\u0026#34;的客户端后有一个小型子网也要连接到VPN # 该子网为192.168.40.128/255.255.255.248 # 首先，去掉下面两行指令的注释： ;client-config-dir ccd ;route 192.168.40.128 255.255.255.248 # 然后创建一个文件ccd/Thelonious，该文件的内容为(没有\u0026#34;#\u0026#34;)： # iroute 192.168.40.128 255.255.255.248 # 客户端所在的子网就可以访问VPN了 # 注意，这个指令只能在基于路由模式而不是基于桥接模式下才能生效 # 比如，你使用了\u0026#34;dev tun\u0026#34;和\u0026#34;server\u0026#34;指令 # 示例1：假设要给Thelonious分配一个固定的IP地址10.9.0.1 # 首先，去掉下面两行指令的注释： ;client-config-dir ccd ;route 10.255.255.255.252 # 然后在文件ccd/Thelonious中添加如下指令(没有\u0026#34;#\u0026#34;)： # ifconfig-push 10.10.9.0.2 # 如果想要为不同群组的客户端启用不同的防火墙访问策略，你可以使用如下两种方法： # (1)运行多个OpenVPN守护进程，每个进程对应一个群组，并为每个进程(群组)启用适当的防火墙规则 # (2)(进阶)创建一个脚本来动态地修改响应于来自不同客户的防火墙规则 # 关于learn-address脚本的更多信息请参考官方手册页面 ;learn-address ./script # 如果启用该行指令，所有客户端的默认网关都将重定向到VPN # 这将导致诸如web浏览器、DNS查询等所有客户端流量都经过VPN # (为确保能正常工作，OpenVPN服务器所在计算机可能需要在TUN/TAP接口与以太网之间使用NAT或桥接技术进行连接) ;push \u0026#34;redirect-gateway def1 bypass-dhcp\u0026#34; # 某些具体的Windows网络设置可以被推送到客户端，例如DNS或WINS服务器地址 # 下列地址来自opendns.com提供的Public DNS服务器 ;push \u0026#34;dhcp-option DNS 208.67.222.222\u0026#34; ;push \u0026#34;dhcp-option DNS 208.67.220.220\u0026#34; # 去掉该行指令的注释将允许不同的客户端之间互相访问 # 默认情况，客户端只能访问服务器 # 为了确保客户端只能看见服务器，还可以在服务器端的TUN/TAP接口上设置适当的防火墙规则 ;client-to-client # 如果多个客户端可能使用相同的证书/私钥文件或Common Name进行连接，那么可以取消该指令的注释 # 建议该指令仅用于测试目的。对于生产环境使用而言，每个客户端都应该拥有自己的证书和私钥 # 如果没有为每个客户端分别生成Common Name唯一的证书/私钥，可以取消该行的注释(不推荐这样做) ;duplicate-cn # keepalive指令将导致类似于ping命令的消息被来回发送，以便于服务器端和客户端知道对方何时被关闭 # 每10秒钟ping一次，如果120秒内都没有收到对方的回复，则表示远程连接已经关闭 keepalive 10 120 # 出于SSL/TLS之外更多的安全考虑，创建一个\u0026#34;HMAC 防火墙\u0026#34;可以帮助抵御DoS攻击和UDP端口淹没攻击 # 可以使用以下命令来生成： # openvpn --genkey --secret ta.key # # 服务器和每个客户端都需要拥有该密钥的一个拷贝 # 第二个参数在服务器端应该为\u0026#39;0\u0026#39;，在客户端应该为\u0026#39;1\u0026#39; tls-auth ta.key 0 # 该文件应该保密 # 选择一个密码加密算法，该配置项也必须复制到每个客户端配置文件中 # 注意，v2.4客户端/服务器将自动以TLS模式协商AES-256-GCM，请参阅手册中的ncp-cipher选项 cipher AES-256-CBC # 在VPN链接上启用压缩并将选项推送到客户端（仅适用于v+，对于早期版本，请参阅下文） ;compress lz4-v2 ;push \u0026#34;compress lz4-v2\u0026#34; # 对于与旧客户端兼容的压缩，使用comp-lzo # 如果在此启用，还必须在客户端配置文件中启用它 ;comp-lzo # 允许并发连接的客户端的最大数量 ;max-clients 100 # 初始化后减少OpenVPN守护进程的权限是一个好主意 # 该指令仅限于非Windows系统中使用 ;user nobody ;group nobody # 持久化选项可以尽量避免访问那些在重启之后由于用户权限降低而无法访问的某些资源 persist-key persist-tun # 输出一个简短的状态文件，用于显示当前的连接状态，该文件每分钟都会清空并重写一次 status openvpn-status.log # 默认情况下，日志消息将写入syslog(在Windows系统中，如果以服务方式运行，日志消息将写入OpenVPN安装目录的log文件夹中) # 可以使用log或者log-append来改变这种默认设置 # \u0026#34;log\u0026#34;方式在每次启动时都会清空之前的日志文件 # \u0026#34;log-append\u0026#34;是在之前的日志内容后进行追加 # 你可以使用两种方式之一(不要同时使用) ;log openvpn.log ;log-append openvpn.log # 为日志文件设置适当的冗余级别(0~9) # 冗余级别越高，输出的信息越详细 # # 0 表示静默运行，只记录致命错误 # 4 表示合理的常规用法 # 5和6 可以帮助调试连接错误 # 9 表示极度冗余，输出非常详细的日志信息 verb 3 # 忽略过多的重复信息 # 相同类别的信息只有前20条会输出到日志文件中 ;mute 20 # 通知客户端，当服务器重新启动时，可以自动重新连接 # 只能是UDP协议使用，TCP使用的话不能启动服务 explicit-exit-notify 1 # （如果不添加该指令则）默认值3600，也就是一个小时进行一次TSL重新协商 # 这个参数在服务端和客户端设置都有效 # 如果两边都设置了，就按照时间短的设定优先 # 当两边同时设置成0，表示禁用TSL重协商。使用OTP认证需要禁用 reneg-sec 0 可用的服务端配置\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 local 10.0.0.4 port 1194 proto udp dev tun ca ca.crt cert server.crt key server.key dh dh2048.pem # 分配给客户端的地址池，与dhcp类似 server 192.168.10255.255.255.0 ifconfig-pool-persist ipp.txt push \u0026#34;route 192.168.10255.255.255.0\u0026#34; client-to-client duplicate-cn keepalive 10 120 comp-lzo max-clients 100 persist-key persist-tun log-append /var/log/openvpn.log verb 3 mute 20 explicit-exit-notify 1 reneg-sec 360 tls-auth ta.key 0 相关证书文件说明 文件名 需要使用者 目的 默认是否加密 ca.crt server + all clients Root CA certificate NO ca.key key signing machine only Root CA key YES dh{n}.pem server only Diffie Hellman parameters NO server.crt server only Server Certificate NO server.key server only Server Key YES client1.crt client1 only Client1 Certificate NO client1.key client1 only Client1 Key YES reference explanation of the relevant files\nOpenVPN客户端配置使用 下载并安装openvpn客户端 在windows上需要下载与Server版本一致的带有GUI的Windows端。下载地址：community\n配置并下载客户端证书 生成客户端证书和key文件\n生成client证书和key文件。若建立多个客户证书，则重复如下步骤即可.只需修改Common Name项oldboy的名称。\n在OpenVPN中，这种配置方法是每一个登陆的VPN客户端需要有一个证书，每个证书在同一时刻只能供一个客户端连接（如果有两个机器安装相同证书，同时拨服务器，都能拨上，但是只有第一个拨上的才能连通网络）。所以，如果有多个人，每个人需要建立一份证书。\n将ca.crt、redis.crt、redis.key复制到OpenVPN\\config。（不同用户使用不同的证书，每个证书包括.crt和.key两个文件。如test.crt和test.key)\n在 sample-config-files/client.conf的基础上建立客户端配置文件，改名为client.oven，即先在服务器上建立配置文件，然后再下载改名到客户机上。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 client dev tun proto udp remote 10.1194 nobind resolv-retry infinite persist-key persist-tun mute-replay-warnings cipher AES-256-CBC comp-lzo verb 3 ca ca.crt cert test.crt key test.key tls-auth ta.key 1 客户端远程连接OpenVPN服务 当配置文件的扩展名为.ovpn，配置文件存放至客户端配置的目录中。ca、证书 与配置文件放置同一个文件夹内。每一组为一个用户。\n连接成功后如图所示：\nReference 证书认证\nopenvpn配置文件说明\neasyrsa签发证书\n","permalink":"https://www.161616.top/ch2-install-and-configuration/","summary":"OpenVPN安装环境需求 设备 IP 笔记本或PC\n(adsl上网) 10.0.0.0/24 办公室（DHCP） OpenVPN Server双网卡 eth0:10.0.0.4/24（外网）\neth1:192.168.100.4（内网） IDC机房内部局域网服务器 192.168.100.0/24\nIDC机房内网服务器无外网IP，又希望ADSL上网笔记本（运维人员），在不同的网络能够直接访问 实现需求：在远端通过VPN客户端(笔记本)拨号到VPN，然后在笔记本电脑上可以直接访问vpnserver所在局域网内的多个servers，进行维护管理 环境 VPN-Server eth0:10.0.0.4(外网IP)。GW:10.0.0.1, dns:10.0.0.1。\neth1:172.0.0.1(内网IP)。GW:不配\n提示：检查是否可以ping通client eth0 IP。\nApp client Server:ethO:172.0.0.2。GW:路由器。提示：检查是否可以ping通VPN-Server eth1 IP。 OpenVPN 解决方案图解\r部署OpenVPN Server Reference download lzo\ninstalling-openvpn\nopenvpn offical releases\nopenvpn github\n安装前准备\nopenvpn支持的平台：\nLinux kernel 2.6+ OpenBSD 5.1+ Mac OS X Darwin 10.5+ FreeBSD 7.4+ Windows (WinXP and higher) 下载地址：\nopenvpn-releases openvpn github 安装openvpn的依赖项：\n0.9.8版或更高版本的OpenSSL库，对于加密是必需的 PolarSSL库，是加密的替代版本1.1或更高版本 LZO实时压缩库，连接压缩所需 bash 1 yum install openssl-devel lzo-devel pam-devel -y bash 1 2 3 4 5 6 .","title":"ch2 从零开始安装OpenVPN"},{"content":"方案1：在Vpn 客户端使用多个配置文件实现（由用户选择拨号）类 方案1：需求分析与操作过程讲解。\n基本说明：\n生产场景中比较规范的做法是让所有的VPN SERVER尽可能共享同一套 server，ca证书或者连接同一个认证系统（即使是跨机房）。这样只需要一份客户端认证和文件和多份指定不同vpn client的配置文件即可实现vpn的负载均衡了。\n总结结论：\n1）该负载均衡方案操作简单，不引入多余服务（后面的方案都会引入服务），因此不会增加多余的单点故障，当用户连接的vpn不能使用时，用户就可以人工选择拨号其他的VPN服务器。\n2）如果使用者为公司内部工作人员，此种方案是值得推荐的。老男孩老师推荐。\n3）从广义上讲这是在用户端实现的负载均衡方案，类似早期的华军下载站一样，由用户选择下载站点，而不是用什么智能DNS等复杂的业务模式。\n缺点：当一个vpnserver不能使用时，不能自动连上别的vpn server。\n方案2：通过在客户端配置文件实现负载均衡（客户端文件里随机连接服务器） 提示：同方案1，所有VPN SERVER 需要共享同一套 server，ca证书。openvpn 服务器一套keys的多份拷贝方式式。\ntext 1 2 3 4 remote 10.0.0.28 52115 remote 10.0.0.552115 remote-random resolv-retry 20 implementing-a-load-balancing-failover-configuration\n总结结论：\n1）该负载均衡方案操作简单，不引入多余服务（后面的方案都会引入服务），因此不会增加多余的单点故障，，当用户连接的vpn不能使用时，电脑可以重新再次自动拨号连接VPN服务器。\n2）如果使用者为公司内部工作人员，此种方案是值得推荐的。-老男孩老师推荐。如果是使用者为外部人员，那么这个方案依然是可以的。\n3）本方案是比较标准的在VPN用户端，由客户端配置参数实现的负载均衡的方案，是非常值得推荐的方案。\n4）和方案1对比，方案2的配置更简单，仅需一个配置文件多个remote参数，拨号时客户端会随机自动选择拨号，方案1则需要手动选择不同的配置文件拨号。当正在连接的VPN服务端右机时，那么此时方案2不需要人工干预，客户端的VPN会自动判断并且自动重新连接其他的可用vpn服务器。.\n方案3：通过域名加DNS轮询的方式实现负载均衡（由DNS自动分配vpn） 总结结论：\n1）通过dns轮询实现VPN负载均衡方案操作比较复杂，引入了DNS服务，因此增加了单点故障及维护成本，当用户连接的vpn不能使用时，用户也需要重新人工再次拨号。\n2）如果使用者为公司内部工作人员，此种方案是不推荐的。如果是外部的用户可以考虑用这种方式，但是复杂度比方案1大了很多（如果存在DNS服务器加配置还可以）。\n3）当机房多，配置文件多时，无需用户选择服务器，只需拨号即可。如果多个VPN在一个机房还好一些，如果多个VPN服务器不在一个机房，还需要通过IPSEC进行连接。 总之，此法很麻烦，中小型公司老男孩老师极不推荐。\n4）DNS轮询会遭遇到客户端DNS缓存问题，从而导致服务切换失效。。\none-network-interface-on-a-public-network\n","permalink":"https://www.161616.top/ch3-high-availability/","summary":"方案1：在Vpn 客户端使用多个配置文件实现（由用户选择拨号）类 方案1：需求分析与操作过程讲解。\n基本说明：\n生产场景中比较规范的做法是让所有的VPN SERVER尽可能共享同一套 server，ca证书或者连接同一个认证系统（即使是跨机房）。这样只需要一份客户端认证和文件和多份指定不同vpn client的配置文件即可实现vpn的负载均衡了。\n总结结论：\n1）该负载均衡方案操作简单，不引入多余服务（后面的方案都会引入服务），因此不会增加多余的单点故障，当用户连接的vpn不能使用时，用户就可以人工选择拨号其他的VPN服务器。\n2）如果使用者为公司内部工作人员，此种方案是值得推荐的。老男孩老师推荐。\n3）从广义上讲这是在用户端实现的负载均衡方案，类似早期的华军下载站一样，由用户选择下载站点，而不是用什么智能DNS等复杂的业务模式。\n缺点：当一个vpnserver不能使用时，不能自动连上别的vpn server。\n方案2：通过在客户端配置文件实现负载均衡（客户端文件里随机连接服务器） 提示：同方案1，所有VPN SERVER 需要共享同一套 server，ca证书。openvpn 服务器一套keys的多份拷贝方式式。\ntext 1 2 3 4 remote 10.0.0.28 52115 remote 10.0.0.552115 remote-random resolv-retry 20 implementing-a-load-balancing-failover-configuration\n总结结论：\n1）该负载均衡方案操作简单，不引入多余服务（后面的方案都会引入服务），因此不会增加多余的单点故障，，当用户连接的vpn不能使用时，电脑可以重新再次自动拨号连接VPN服务器。\n2）如果使用者为公司内部工作人员，此种方案是值得推荐的。-老男孩老师推荐。如果是使用者为外部人员，那么这个方案依然是可以的。\n3）本方案是比较标准的在VPN用户端，由客户端配置参数实现的负载均衡的方案，是非常值得推荐的方案。\n4）和方案1对比，方案2的配置更简单，仅需一个配置文件多个remote参数，拨号时客户端会随机自动选择拨号，方案1则需要手动选择不同的配置文件拨号。当正在连接的VPN服务端右机时，那么此时方案2不需要人工干预，客户端的VPN会自动判断并且自动重新连接其他的可用vpn服务器。.\n方案3：通过域名加DNS轮询的方式实现负载均衡（由DNS自动分配vpn） 总结结论：\n1）通过dns轮询实现VPN负载均衡方案操作比较复杂，引入了DNS服务，因此增加了单点故障及维护成本，当用户连接的vpn不能使用时，用户也需要重新人工再次拨号。\n2）如果使用者为公司内部工作人员，此种方案是不推荐的。如果是外部的用户可以考虑用这种方式，但是复杂度比方案1大了很多（如果存在DNS服务器加配置还可以）。\n3）当机房多，配置文件多时，无需用户选择服务器，只需拨号即可。如果多个VPN在一个机房还好一些，如果多个VPN服务器不在一个机房，还需要通过IPSEC进行连接。 总之，此法很麻烦，中小型公司老男孩老师极不推荐。\n4）DNS轮询会遭遇到客户端DNS缓存问题，从而导致服务切换失效。。\none-network-interface-on-a-public-network","title":"ch3 OpenVPN的高可用配置"},{"content":"OpenVPN 2.0与更高版本允许OpenVPN服务器从客户端安全地获取用户名和密码，并将该信息用作认证基础。\n方法1：通过本地证书密钥认证。 默认不配置，openvpn即使用证书进行身份认证。\n（1）编辑主服务器配置文件/etc/openldap/slapd.conf，取消如下行的注释：\n方法2：本地文件认证 在使用身份验证时，需要将 auth-user-pass 指令添加到客户端配置文件中，设置后OpenVPN客户端向用户索要用户名/密码，并将其通过安全的TLS通道传递给服务器进行验证。\n服务端配置文件需要增加配置指令 auth-user-pass-verify auth-pam.pl via-file 使用脚本插件。auth-pam.pl 在源码包 sample/sample-script 路径下。\ntext 1 plugin /usr/share/openvpn/plugin/lib/openvpn-auth-pam.so login 生产环境下官方推荐使用 openvpn-auth-pam 插件进行验证，相比于 auth-pam.pl，openvpn-auth-pam 插件具有多个优点：\nopenvpn-auth-pam 使用拆分权限执行模型来提高安全性。 C编译的插件比脚本运行速度更快。 OpenVPN可以通过虚拟内存（而不是通过文件或环境）将用户名/密码传递给插件，这对于服务器上的本地安全性更好。 获取openvpn-auth-pam插件 openvpn-auth-pam插件在openvpn代码目录src/plugins/auth-pam 下，运行 make \u0026amp;\u0026amp; make install 进行安装，会自动复制到openvpn安装好的 lib/openvpn/plugins 目录下。\n开启密码认证 默认情况下， 在服务器上使用 auth-user-pass-verify 或用户名/密码 插件 将启用双重身份验证，要求客户端证书和用户名/密码身份验证都必须成功，才能对客户端进行身份验证。可以选择关闭客户端证书认证。\ntext 1 2 client-cert-not-required username-as-common-name # 用户名作为通用名称 开启后需要在客户端注释 cert 和 key的配置\n方法3：数据库认证 法2：利用的脚本程序（shell，php等）本地文件去读数据库。\n法1：用pam_mysql\n方法:4：ldap统一用户认证 openvpn-auth-ldap 利用第一个文件认证的思路，去LDAP查询，还可以和本地文件比较。如 ldap认证原理图\r配置openvpn服务端通过ldap进行身份验证 配置OpenVPN基 LDAP的身份验证，需要安装用于LDAP身份验证的OpenVPN插件。openvpn-auth-ldap，它通过LDAP为OpenVPN实现身份认证。\nCentOS中 openvpn-auth-ldap 插件在EPEL中 ubuntu与Centos都可以通过对应的包管理工具进行插件安装。\n安装完成后配置 OpenVPN LDAP身份验证 examples/auth-ldap.conf\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 \u0026lt;LDAP\u0026gt; URL\tldaps://ip BindDN\tdc=kifarunix-demo,dc=com Password\tP@ssW0rd Timeout\t15 TLSEnable\tyes|no FollowReferrals no \u0026lt;/LDAP\u0026gt; \u0026lt;Authorization\u0026gt; # 搜索的域 BaseDN\t\u0026#34;ou=people,dc=ldapmaster,dc=kifarunix-demo,dc=com\u0026#34; # 搜索的条件，这里使用的UID，如其他名称为用户名可以选择其他 SearchFilter\t\u0026#34;(uid=%u)\u0026#34; RequireGroup\tfalse \u0026lt;/Authorization\u0026gt; 还可以基于组管理\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 \u0026lt;LDAP\u0026gt; URL\tldaps://ip BindDN\tdc=kifarunix-demo,dc=com Password\tP@ssW0rd Timeout\t15 TLSEnable\tyes|no FollowReferrals no \u0026lt;/LDAP\u0026gt; \u0026lt;Authorization\u0026gt; BaseDN\t\u0026#34;ou=people,dc=ldapmaster,dc=kifarunix-demo,dc=com\u0026#34; SearchFilter\t\u0026#34;(uid=%u)\u0026#34; RequireGroup\ttrue # 这里设置为true \u0026lt;Group\u0026gt; BaseDN\t\u0026#34;cn=admin,dc=kifarunix-demo,dc=com\u0026#34; SearchFilter\t\u0026#34;memberOf=ou=people,dc=seal,dc=com\u0026#34; MemberAttribute\tuniqueMember # 需要ldap安装memberof，这时memeberof组的属性 \u0026lt;/Group\u0026gt; \u0026lt;/Authorization\u0026gt; 修改openvpn配置\ntext 1 2 plugin /usr/lib64/openvpn/plugin/lib/openvpn-auth-ldap.so \u0026#34;/etc/openvpn/auth/ldap.conf\u0026#34; verify-client-cert none 完整的服务端配置\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 local 10.0.0.4 port 1194 proto udp dev tun ca ca.crt cert server.crt key server.key dh dh2048.pem tls-auth ta.key 0 server 192.168.100.128 255.255.255.128 ifconfig-pool-persist ipp.txt push \u0026#34;route 192.168.100.0 255.255.255.0\u0026#34; client-to-client duplicate-cn keepalive 10 120 max-clients 100 persist-key persist-tun log-append /var/log/openvpn.log verb 3 compress lz4-v2 mute 20 explicit-exit-notify 1 reneg-sec 360 plugin /usr/lib64/openvpn/plugin/lib/openvpn-auth-ldap.so \u0026#34;/etc/openvpn/auth/ldap.conf\u0026#34; verify-client-cert none 启动客户端配置 text 1 2 auth-user-pass remote-cert-tls server 完整的客户端配置\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 client dev tun proto udp remote 10.0.0.4 1194 nobind resolv-retry infinite persist-key persist-tun mute-replay-warnings cipher AES-256-CBC #comp-lzo verb 3 ca ca.crt tls-auth ta.key 1 compress lz4-v2 #cert client.crt #key client.key auth-user-pass remote-cert-tls server 服务端报错 TLS Error: reading acknowledgement record from packet\ntext 1 2 TLS: Initial packet from [AF_INET]10.0.0.1:56531, sid=50a1c0bb 07a548a5 TLS Error: reading acknowledgement record from packet 原因，客户端开启了安全配置tls-auth ta.key 1 而 服务端没有对应配置\n方法5：配置配置RADIUS认证。 RADIUS (Remote Authentication Dial In User Service)，远程用户拨号认证系统由RFC2865，RFC2866定义，是目前应用最广泛的AAA协议。可实现验证、授权、记账等服务的协议。\n方法6：结合google authtication等设备进行双重认证 Reference authentication methods\nauth error\nhow to configure\nopenldap howto\n","permalink":"https://www.161616.top/ch4-authentication/","summary":"OpenVPN 2.0与更高版本允许OpenVPN服务器从客户端安全地获取用户名和密码，并将该信息用作认证基础。\n方法1：通过本地证书密钥认证。 默认不配置，openvpn即使用证书进行身份认证。\n（1）编辑主服务器配置文件/etc/openldap/slapd.conf，取消如下行的注释：\n方法2：本地文件认证 在使用身份验证时，需要将 auth-user-pass 指令添加到客户端配置文件中，设置后OpenVPN客户端向用户索要用户名/密码，并将其通过安全的TLS通道传递给服务器进行验证。\n服务端配置文件需要增加配置指令 auth-user-pass-verify auth-pam.pl via-file 使用脚本插件。auth-pam.pl 在源码包 sample/sample-script 路径下。\ntext 1 plugin /usr/share/openvpn/plugin/lib/openvpn-auth-pam.so login 生产环境下官方推荐使用 openvpn-auth-pam 插件进行验证，相比于 auth-pam.pl，openvpn-auth-pam 插件具有多个优点：\nopenvpn-auth-pam 使用拆分权限执行模型来提高安全性。 C编译的插件比脚本运行速度更快。 OpenVPN可以通过虚拟内存（而不是通过文件或环境）将用户名/密码传递给插件，这对于服务器上的本地安全性更好。 获取openvpn-auth-pam插件 openvpn-auth-pam插件在openvpn代码目录src/plugins/auth-pam 下，运行 make \u0026amp;\u0026amp; make install 进行安装，会自动复制到openvpn安装好的 lib/openvpn/plugins 目录下。\n开启密码认证 默认情况下， 在服务器上使用 auth-user-pass-verify 或用户名/密码 插件 将启用双重身份验证，要求客户端证书和用户名/密码身份验证都必须成功，才能对客户端进行身份验证。可以选择关闭客户端证书认证。\ntext 1 2 client-cert-not-required username-as-common-name # 用户名作为通用名称 开启后需要在客户端注释 cert 和 key的配置\n方法3：数据库认证 法2：利用的脚本程序（shell，php等）本地文件去读数据库。\n法1：用pam_mysql\n方法:4：ldap统一用户认证 openvpn-auth-ldap 利用第一个文件认证的思路，去LDAP查询，还可以和本地文件比较。如 ldap认证原理图\r配置openvpn服务端通过ldap进行身份验证 配置OpenVPN基 LDAP的身份验证，需要安装用于LDAP身份验证的OpenVPN插件。openvpn-auth-ldap，它通过LDAP为OpenVPN实现身份认证。\nCentOS中 openvpn-auth-ldap 插件在EPEL中 ubuntu与Centos都可以通过对应的包管理工具进行插件安装。","title":"ch4 OpenVPN的统一身份认证方案及实现方法"},{"content":"Kubernetes Ingress Kubernetes Ingress是路由规则的集合，这些规则控制外部用户如何访问Kubernetes集群中运行的服务。\n在Kubernetes中，有三种方式可以使内部Pod公开访问。\nNodePort：使用Kubernetes Pod的NodePort，将Pod内应用程序公开到每个节点上的端口上。 Service LoadBalancer：使用Kubernetes Service，改功能会创建一个外部负载均衡器，使流量转向集群中的Kubernetes Pod。 Ingress Controller： Node Port是在Kubernetes集群中每个节点（Node）上开放端口，Kubernetes直接将流量转向集群中Pod。Kubernetes集群中使用NodePort，则需要编辑防火墙规则，但是NodePort是范围在Kubernetes集群中默认设置的范围为 30000–32767，最终导致流量端口暴露在非标准端口之上。\nLoadBalancer一般应用于云厂商提供的Kubernetes服务，如果自行在机器上部署Kubernetes集群，则需要自行配置LoadBalancer的实现，\nKubernetes Ingress，为Kubernetes中的抽象概念，实现为第三方代理实现，这种三方实现集合统称为Ingress Controller。Ingress Controller负责引入外部流量并将流量处理并转向对应的服务。\nKubernetes IngressController功能实现 上面只是说道，在Kubernetes集群中，如何将外部流量引入到Kubernetes集群服务中。\n负载均衡 无论在Kubernetes集群中，无论采用什么方式进行流量引入，都需要在外部负载均衡完成，而后负载均衡将流量引入Kubernetes集群入口或内部中，\n通常情况下，NodePort方式管理繁琐，一般不用于生产环境。\n服务的Ingress选择 Kubernetes Ingress是选择正确的方法来管理引入外部流量到服务内部。一般选择也是具有多样性的。\nNginx Ingress Controller，Kubernetes默认推荐的Ingress，弊端①最终配置加载依赖config reload，②定制化开发较难，配置基本来源于config file。 Envoy \u0026amp; traefik api网关，支持tcp/udp/grpc/ws等多协议，支持流量控制，可观测性，多配置提供者。 云厂商提供的Ingress。AWS ALB，GCP GLBG/GCE，Azure AGIC Traefik介绍 traefik-现代反向代理，也可称为现代边缘路由；traefik原声兼容主流集群，Kubernetes，Docker，AWS等。官方的定位traefik是一个让开发人员将时间花费在系统研发与部署功能上，而非配置和维护。并且traefik官方也提供自己的服务网格解决方案\n作为一个 modern edge router ，traefik拥有与envoy相似的特性\n基于go语言研发，目的是为了简化开发人员的配置和维护 tcp/udp支持 http L7支持 GRPC支持 服务发现和动态配置 front/ edge prory支持 可观测性 流量管理 \u0026hellip; traefik 术语 要了解trafik，首先需要先了解一下 有关trafik中的一些术语。\nEntryPoints 入口点，是可以被下游客户端连接的命名网络位置，类似于envoy 的listener和nginx的listen services 服务，负载均衡，上游主机接收来自traefik的连接和请求并返回响应。 类似于nginx upstream envoy的clusters Providers 提供者，提供配置文件的后端，如file，kubernetes，consul，redis，etcd等，可使traefik自动更新 routers 路由器，承上启下，分析请求，将下游主机的请求处理转入到services middlewares: 中间件，在将下游主机的请求转入到services时进行的流量调整 在Kubernetes中使用traefik网关作为Ingress Traefik于2019年9月发布2.0 GA版，增加了很多新特性，包括IngressRoute Kubernetes CRD，TCP，最新版增加UDP等。\n安装traefik Traefik 支持两种方式创建路由规则，一是Traefik 自定义 Kubernetes CRD ，还有一种是 Kubernetes Ingress。这里使用 Kubernetes CRD\n官方完整的部署清单资源见附录3 [3]\n创建traefik使用的Kubernetes CRD 资源 traefik官网提供了创建时所需要的 yaml文件，这里仅需要使用官网提供的Definitions与RBAC即可。\n在官网提供的yaml文件缺少 ServiceAccount，需要自行创建。\nyaml 1 2 3 4 5 apiVersion: v1 kind: ServiceAccount metadata: namespace: default name: traefik-ingress-controller 创建控制器 官方文件中，暂未找到所需运行traefik的控制器，需要自己创建一个。\ntraefik配置一般分为静态和动态配置，此处的静态是指，大部分时间内，不改变的配置（如nginx.conf），动态配置指，经常情况下改变的配置（可以理解为 nginx中 virtual host的每个独立配置文件）。\ntraefik提供配置的提供者也有很多种，此处使用命令行方式设置不长改变静态配置，也可以使用配置文件方式进行配置提供。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 apiVersion: v1 kind: Service metadata: name: traefik spec: selector: app: traefik-ingress type: NodePort ports: - name: web port: 80 targetPort: 80 - name: ssl port: 443 targetPort: 443 - name: dashboard port: 8080 targetPort: 8080 --- apiVersion: apps/v1 kind: Deployment metadata: name: traefik-ingress-controller-deployment labels: app: traefik-ingress spec: replicas: 1 selector: matchLabels: app: traefik-ingress strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 template: metadata: name: traefik-ingress labels: app: traefik-ingress spec: serviceAccountName: traefik-ingress-controller volumes: - name: ssl hostPath: path: /etc/kubernetes/pki type: DirectoryOrCreate containers: - image: traefik:v2.3.3 name: traefik-ingress-lb imagePullPolicy: IfNotPresent volumeMounts: - name: ssl mountPath: /usr/local/pki ports: - name: web containerPort: 80 hostPort: 1880 - name: ssl containerPort: 443 hostPort: 18443 securityContext: capabilities: drop: - ALL add: - NET_BIND_SERVICE args: - --entrypoints.web.address=:80 - --entrypoints.ssl.address=:443 - --providers.kubernetescrd - --providers.kubernetesingress - --api=true - --ping=true - --api.dashboard=true - --serverstransport.insecureskipverify=true - --serverstransport.rootcas=/usr/local/pki/ca.crt - --accesslog livenessProbe: httpGet: path: /ping port: 8080 failureThreshold: 3 initialDelaySeconds: 10 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 5 readinessProbe: httpGet: path: /ping port: 8080 failureThreshold: 3 initialDelaySeconds: 10 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 5 配置文件方式，仅需参考官网，把\u0026ndash;agrs中的参数转换为配置文件并引入到pod后。替换启动参数\nyaml 1 2 args: - --configfile=/config/traefik.yaml 使用CRD配置Traefik的流量管理 官网提供的基于Kubernetes CRD方式配置不是很多，可以参考动态配置中Kubernetes CRD Resources小结。\n基于traefik dashboard方式配置增加HTTP router 前面使用官方提供CRD文件注册了Kubernetes CRD资源，所以traefik 中的资源类型，可作为kubernetes中资源使用。如 kubectl get Middleware dashboard是traefik自己提供的服务所需的services为自己，traefik的entryPoints，当开启--api.dashboard=true 会增加一个8080端口作为traefik dashboard使用。而 api@internal 是traefik自己提供的services资源。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: traefik-dashboard-route spec: entryPoints: - traefik routes: - match: Host(`10.0.0.5`) kind: Rule services: - name: api@internal port: 8080 kind: TraefikService 为Kubernetes dashboard增加HTTP路由 基于kubernetes crd作为提供者运行的traefik中，后端service可以是traefik的services也可以是kubernetes资源中的service。\n基于kubernetes sevices\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: kubernetes-dashboard-route namespace: kubernetes-dashboard annotations: traefik.ingress.kubernetes.io/ssl-redirect: \u0026#34;true\u0026#34; spec: entryPoints: - ssl tls: secretName: k8s-ca routes: - match: PathPrefix(`/ui`) kind: Rule middlewares: - name: strip-ui services: - name: kubernetes-dashboard # kubernetes中的service对应的名称 kind: Service # kubernetes中的service port: 443 namespace: kubernetes-dashboard # kubernetes中的service对应的名称空间。 --- apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: strip-ui namespace: kubernetes-dashboard spec: stripPrefix: prefixes: - \u0026#34;/ui\u0026#34; - \u0026#34;/ui/\u0026#34; 此处使用了TLS的，开启了TLS，默认traefik是进行双向认证的，而kubernetes的dashboard的证书的ca并不知道，在访问时会出现Internal Server Error，目前没有找到有效的双向认证方法，普遍使用的方法都是调过认证--serverstransport.insecureskipverify=true\n负载均衡 负载均衡使用到的是traefik的services部分。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: traefik.containo.us/v1alpha1 kind: TraefikService metadata: name: LoadBalancer spec: weighted: services: - name: prod-v1.2 port: 443 weight: 1 kind: Service - name: prod-v1 port: 443 weight: 2 kind: Service --- 流量镜像 yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: traefik.containo.us/v1alpha1 kind: TraefikService metadata: name: mirror1 spec: mirroring: name: s1 port: 80 mirrors: - name: s3 percent: 20 port: 80 - name: mirror2 kind: TraefikService percent: 20 Reference ​[1] Internal Server Error with Traefik HTTPS backend on port 443\n​[2] IngressRoute subset not found for kube-system/kubernetes-dashboard #6821\n​[3] Traefik \u0026amp; CRD \u0026amp; Let\u0026rsquo;s Encrypt\n","permalink":"https://www.161616.top/traefik-ingresscontroller/","summary":"Kubernetes Ingress Kubernetes Ingress是路由规则的集合，这些规则控制外部用户如何访问Kubernetes集群中运行的服务。\n在Kubernetes中，有三种方式可以使内部Pod公开访问。\nNodePort：使用Kubernetes Pod的NodePort，将Pod内应用程序公开到每个节点上的端口上。 Service LoadBalancer：使用Kubernetes Service，改功能会创建一个外部负载均衡器，使流量转向集群中的Kubernetes Pod。 Ingress Controller： Node Port是在Kubernetes集群中每个节点（Node）上开放端口，Kubernetes直接将流量转向集群中Pod。Kubernetes集群中使用NodePort，则需要编辑防火墙规则，但是NodePort是范围在Kubernetes集群中默认设置的范围为 30000–32767，最终导致流量端口暴露在非标准端口之上。\nLoadBalancer一般应用于云厂商提供的Kubernetes服务，如果自行在机器上部署Kubernetes集群，则需要自行配置LoadBalancer的实现，\nKubernetes Ingress，为Kubernetes中的抽象概念，实现为第三方代理实现，这种三方实现集合统称为Ingress Controller。Ingress Controller负责引入外部流量并将流量处理并转向对应的服务。\nKubernetes IngressController功能实现 上面只是说道，在Kubernetes集群中，如何将外部流量引入到Kubernetes集群服务中。\n负载均衡 无论在Kubernetes集群中，无论采用什么方式进行流量引入，都需要在外部负载均衡完成，而后负载均衡将流量引入Kubernetes集群入口或内部中，\n通常情况下，NodePort方式管理繁琐，一般不用于生产环境。\n服务的Ingress选择 Kubernetes Ingress是选择正确的方法来管理引入外部流量到服务内部。一般选择也是具有多样性的。\nNginx Ingress Controller，Kubernetes默认推荐的Ingress，弊端①最终配置加载依赖config reload，②定制化开发较难，配置基本来源于config file。 Envoy \u0026amp; traefik api网关，支持tcp/udp/grpc/ws等多协议，支持流量控制，可观测性，多配置提供者。 云厂商提供的Ingress。AWS ALB，GCP GLBG/GCE，Azure AGIC Traefik介绍 traefik-现代反向代理，也可称为现代边缘路由；traefik原声兼容主流集群，Kubernetes，Docker，AWS等。官方的定位traefik是一个让开发人员将时间花费在系统研发与部署功能上，而非配置和维护。并且traefik官方也提供自己的服务网格解决方案\n作为一个 modern edge router ，traefik拥有与envoy相似的特性\n基于go语言研发，目的是为了简化开发人员的配置和维护 tcp/udp支持 http L7支持 GRPC支持 服务发现和动态配置 front/ edge prory支持 可观测性 流量管理 \u0026hellip; traefik 术语 要了解trafik，首先需要先了解一下 有关trafik中的一些术语。\nEntryPoints 入口点，是可以被下游客户端连接的命名网络位置，类似于envoy 的listener和nginx的listen services 服务，负载均衡，上游主机接收来自traefik的连接和请求并返回响应。 类似于nginx upstream envoy的clusters Providers 提供者，提供配置文件的后端，如file，kubernetes，consul，redis，etcd等，可使traefik自动更新 routers 路由器，承上启下，分析请求，将下游主机的请求处理转入到services middlewares: 中间件，在将下游主机的请求转入到services时进行的流量调整 在Kubernetes中使用traefik网关作为Ingress Traefik于2019年9月发布2.","title":"kubernetes应用 - Traefik Ingress Controller"},{"content":"poershell github\n本次采用github下载对应的rpm进行安装\nwindows下安装方法\n离线安装包下载地址\ntext 1 yum install -y https://github.com/PowerShell/PowerShell/releases/download/v7.0.0/powershell-lts-7.0.0-1.rhel.7.x86_64.rpm 安装完成后再终端输入以下命令 pwsh。在一个PowerShell会话，您可以通过运行以下命令来检查的PowerShell的版本。\ntext 1 2 3 4 5 6 7 PS /root\u0026gt; $PSVersionTable.PSVersion Major Minor Patch PreReleaseLabel BuildLabel ----- ----- ----- --------------- ---------- 7 0 0 Set-PSRepository -Name \u0026#34;PSGallery\u0026#34; -InstallationPolicy \u0026#34;Trusted\u0026#34; 接下来，运行以下命令来安装VMware.PowerCLI模块。这将发现和在PSGallery安装最新版本的模块\ntext 1 Find-Module \u0026#34;VMware.PowerCLI\u0026#34; | Install-Module -Scope \u0026#34;CurrentUser\u0026#34; -AllowClobber 完成后，通过运行以下命令进行检查，以确保该模块安装。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 Get-Module \u0026#34;VMware.PowerCLI\u0026#34; -ListAvailable | FT -Autosize PS /root\u0026gt; Get-Module \u0026#34;VMware.PowerCLI\u0026#34; -ListAvailable | FT -Autosize Directory: /root/.local/share/powershell/Modules ModuleType Version PreRelease Name PSEdition ExportedCommands ---------- ------- ---------- ---- --------- ---------------- Manifest 12.0.0.15947286 VMware.PowerCLI Desk 如果你想看到所有的VMware安装的模块，运行以下命令。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 PS /root\u0026gt; Get-Module \u0026#34;VMware.*\u0026#34; -ListAvailable | FT -Autosize Directory: /root/.local/share/powershell/Modules ModuleType Version PreRelease Name PSEdition ExportedCommands ---------- ------- ---------- ---- --------- ---------------- Script 12.0.0.15947289 VMware.CloudServices Desk {Connect-Vcs, Get-VcsOrganizationRole, Get-VcsService, Get-VcsServiceRole… Script 7.0.0.15902843 VMware.DeployAutomation Desk {Add-DeployRule, Add-ProxyServer, Add-ScriptBundle, Copy-DeployRule…} Script 7.0.0.15902843 VMware.ImageBuilder Desk {Add-EsxSoftwareDepot, Add-EsxSoftwarePackage, Compare-EsxImageProfile, E… Manifest 12.0.0.15947286 VMware.PowerCLI Desk Script 7.0.0.15939650 VMware.Vim Desk Script 12.0.0.15939657 VMware.VimAutomation.Cis.Core Desk {Connect-CisServer, Disconnect-CisServer, Get-CisService} Script 12.0.0.15940183 VMware.VimAutomation.Cloud Desk {Add-CIDatastore, Connect-CIServer, Disconnect-CIServer, Get-Catalog…} Script 12.0.0.15939652 VMware.VimAutomation.Common Desk {Get-Task, New-OAuthSecurityContext, Stop-Task, Wait-Task} Script 12.0.0.15939655 VMware.VimAutomation.Core Desk {Add-PassthroughDevice, Add-VirtualSwitchPhysicalNetworkAdapter, Add-VMHo… Script 12.0.0.15939647 VMware.VimAutomation.Hcx Desk {Connect-HCXServer, Disconnect-HCXServer, Get-HCXAppliance, Get-HCXComput… Script 7.12.0.15718406 VMware.VimAutomation.HorizonView Desk {Connect-HVServer, Disconnect-HVServer} Script 12.0.0.15939670 VMware.VimAutomation.License Desk Get-LicenseDataManager Script 12.0.0.15939671 VMware.VimAutomation.Nsxt Desk {Connect-NsxtServer, Disconnect-NsxtServer, Get-NsxtPolicyService, Get-Ns… Script 12.0.0.15939651 VMware.VimAutomation.Sdk Desk {Get-ErrorReport, Get-PSVersion, Get-InstallPath} Script 12.0.0.15939672 VMware.VimAutomation.Security Desk {Add-AttestationServiceInfo, Add-KeyProviderServiceInfo, Add-TrustAuthori… Script 11.5.0.14899557 VMware.VimAutomation.Srm Desk {Connect-SrmServer, Disconnect-SrmServer} Script 12.0.0.15939648 VMware.VimAutomation.Storage Desk {Add-EntityDefaultKeyProvider, Add-KeyManagementServer, Add-VsanFileServi… Script 1.3.0.0 VMware.VimAutomation.StorageUtility Desk Update-VmfsDatastore Script 12.0.0.15940185 VMware.VimAutomation.Vds Desk {Add-VDSwitchPhysicalNetworkAdapter, Add-VDSwitchVMHost, Export-VDPortGro… Script 12.0.0.15947287 VMware.VimAutomation.Vmc Desk {Add-VmcSddcHost, Connect-Vmc, Disconnect-Vmc, Get-AwsAccount…} Script 12.0.0.15940184 VMware.VimAutomation.vROps Desk {Connect-OMServer, Disconnect-OMServer, Get-OMAlert, Get-OMAlertDefinitio… Script 12.0.0.15947288 VMware.VimAutomation.WorkloadManagement Desk {Get-WMNamespace, Get-WMNamespacePermission, Get-WMNamespaceStoragePolicy… Script 6.5.1.7862888 VMware.VumAutomation Desk {Add-EntityBaseline, Copy-Patch, Get-Baseline, Get-Compliance…} 当VMware.PowerCLI发布新版本，你可以运行下面的命令来更新。\ntext 1 Update-Module \u0026#34;VMware.PowerCLI\u0026#34; VMware.PowerCLI现在已安装完成，可以连接到您的vCenter Server或ESXi主机\ntext 1 2 3 4 5 6 7 8 9 10 11 12 PS /root\u0026gt; Set-PowerCLIConfiguration -InvalidCertificateAction \u0026#34;Ignore\u0026#34; Perform operation? Performing operation \u0026#39;Update PowerCLI configuration.\u0026#39;? [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is \u0026#34;Y\u0026#34;): Y Scope ProxyPolicy DefaultVIServerMode InvalidCertificateAction DisplayDeprecationWarnings WebOperationTimeout Seconds ----- ----------- ------------------- ------------------------ -------------------------- ------------------- Session UseSystemProxy Multiple Ignore True 300 User Ignore AllUsers ","permalink":"https://www.161616.top/centos-install-powershellpowercli/","summary":"poershell github\n本次采用github下载对应的rpm进行安装\nwindows下安装方法\n离线安装包下载地址\ntext 1 yum install -y https://github.com/PowerShell/PowerShell/releases/download/v7.0.0/powershell-lts-7.0.0-1.rhel.7.x86_64.rpm 安装完成后再终端输入以下命令 pwsh。在一个PowerShell会话，您可以通过运行以下命令来检查的PowerShell的版本。\ntext 1 2 3 4 5 6 7 PS /root\u0026gt; $PSVersionTable.PSVersion Major Minor Patch PreReleaseLabel BuildLabel ----- ----- ----- --------------- ---------- 7 0 0 Set-PSRepository -Name \u0026#34;PSGallery\u0026#34; -InstallationPolicy \u0026#34;Trusted\u0026#34; 接下来，运行以下命令来安装VMware.PowerCLI模块。这将发现和在PSGallery安装最新版本的模块\ntext 1 Find-Module \u0026#34;VMware.PowerCLI\u0026#34; | Install-Module -Scope \u0026#34;CurrentUser\u0026#34; -AllowClobber 完成后，通过运行以下命令进行检查，以确保该模块安装。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 Get-Module \u0026#34;VMware.PowerCLI\u0026#34; -ListAvailable | FT -Autosize PS /root\u0026gt; Get-Module \u0026#34;VMware.","title":"centos安装powershell和powercli"},{"content":"自从Go官方推出 1.11 之后，增加新的依赖管理模块并且更加易于管理项目中所需要的模块。模块是存储在文件树中的 Go 包的集合，其根目录中包含 go.mod 文件。 go.mod 文件定义了模块的模块路径，它也是用于根目录的导入路径，以及它的依赖性要求。每个依赖性要求都被写为模块路径和特定语义版本。\n从 Go 1.11 开始，Go 允许在 $GOPATH/src 外的任何目录下使用 go.mod 创建项目。在 $GOPATH/src 中，为了兼容性，Go 命令仍然在旧的 GOPATH 模式下运行。从 ==Go 1.13== 开始，模块模式将成为默认模式。\n使用模块开发 Go 代码时出现的一系列常见操作：\n创建一个新模块。 添加依赖项。 升级依赖项。 删除未使用的依赖项。 要使用go module,首先要设置 ==GO111MODULE=on== ,如果没设置，执行命令的时候会有提示。\n==GO111MODULE== 的取值为 off, on, or auto (默认值，因此前面例子里需要注意2个重点)。\noff: GOPATH mode，查找vendor和GOPATH目录 on：module-aware mode，使用 go module，忽略GOPATH目录 auto：如果当前目录不在$GOPATH 并且 当前目录（或者父目录）下有go.mod文件，则使用GO111MODULE， 否则仍旧使用 GOPATH mode。 sh 1 2 export GO111MODULE=on export GOPROXY=https://goproxy.io ## 设置代理 go mod 参数说明 commond 说明 download download modules to local cache (下载依赖的module到本地cache)) edit edit go.mod from tools or scripts (编辑go.mod文件) graph print module requirement graph (打印模块依赖图)) init initialize new module in current directory (再当前文件夹下初始化一个新的module, 创建go.mod文件)) tidy add missing and remove unused modules (增加丢失的module，去掉未用的module) vendor make vendored copy of dependencies (将依赖复制到vendor下) verify verify dependencies have expected content (校验依赖) why explain why packages or modules are needed (解释为什么需要依赖) 新的项目 可以在GOPATH之外创建新的项目。\n使用空目录创建go.mod (module)\ntext 1 2 3 go mod init {packagename} go mod init test go get 升级 运行 go get -u 将会升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号， y是次要版本号) 运行 go get -u=patch 将会升级到最新的修订版本 运行 go get package@version 将会升级到指定的版本号version 运行go get如果有版本的更改，那么go.mod文件也会更改 包管理 当我们使用go build，go test以及go list时，go会自动得更新go.mod文件，将依赖关系写入其中。\n下载的包保存在$GOPATH/\n升级依赖项 查看使用到的依赖列表 go list -m all\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 root@lc-virtual-machine:~# go list -m all chat github.com/Knetic/govaluate v3.0.0+incompatible github.com/OwnLocal/goes v1.0.0 github.com/astaxie/beego v1.12.0 github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542 github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 github.com/casbin/casbin v1.7.0 github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 github.com/elazarl/go-bindata-assetfs v1.0.0 github.com/garyburd/redigo v1.6.0 github.com/go-redis/redis v6.14.2+incompatible github.com/go-sql-driver/mysql v1.4.1 github.com/gogo/protobuf v1.1.1 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db github.com/gomodule/redigo v2.0.0+incompatible github.com/lib/pq v1.0.0 github.com/mattn/go-sqlite3 v1.10.0 github.com/pelletier/go-toml v1.2.0 github.com/pkg/errors v0.8.0 github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373 github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 gopkg.in/yaml.v2 v2.2.1 列出包的历史版本\ngo list -m -versions {package name}\nsh 1 2 root@lc-virtual-machine:~# go list -m -versions github.com/astaxie/beego github.com/astaxie/beego v0.6.0 v0.7.0 v0.8.0 v0.9.0 v1.0.1 v1.2.0 v1.3.0 v1.4.0 v1.4.1 v1.4.2 v1.4.3 v1.5.0 v1.6.0 v1.6.1 v1.7.0 v1.7.1 v1.7.2 v1.8.0 v1.8.1 v1.8.2 v1.8.3 v1.9.0 v1.9.2 v1.10.0 v1.10.1 v1.11.0 v1.11.1 v1.12.0 手动处理依赖关系\ngo mod tidy 会自动清理掉不需要的依赖项，同时可以将依赖项更新到当前版本。\n切换包的版本\nbash 1 go mod edit -require=\u0026#34;github.com/astaxie/beego@v1.9.0\u0026#34; 清楚缓存\nbash 1 go clean -modcache go mod replace 不过因为某些未知原因，并不是所有的包都能直接用go get获取到，这时我们就需要使用go modules的replace功能了。（当然大部分问题挂个梯子就能解决，但是我们也可以有其它选项）\ngo.mod 1 2 3 4 replace ( github.com/testcontainers/testcontainers-go =\u0026gt; github.com/testcontainers/testcontainers-go v0.0.9 golang.org/x/lint =\u0026gt; github.com/golang/lint latest ) 修改后悔自动生成\nmod 1 2 3 4 replace ( github.com/testcontainers/testcontainers-go =\u0026gt; github.com/testcontainers/testcontainers-go v0.0.9 golang.org/x/lint =\u0026gt; github.com/golang/lint v0.0.0-20191125180803-fdd1cda4f05f ) ","permalink":"https://www.161616.top/go-mod/","summary":"自从Go官方推出 1.11 之后，增加新的依赖管理模块并且更加易于管理项目中所需要的模块。模块是存储在文件树中的 Go 包的集合，其根目录中包含 go.mod 文件。 go.mod 文件定义了模块的模块路径，它也是用于根目录的导入路径，以及它的依赖性要求。每个依赖性要求都被写为模块路径和特定语义版本。\n从 Go 1.11 开始，Go 允许在 $GOPATH/src 外的任何目录下使用 go.mod 创建项目。在 $GOPATH/src 中，为了兼容性，Go 命令仍然在旧的 GOPATH 模式下运行。从 ==Go 1.13== 开始，模块模式将成为默认模式。\n使用模块开发 Go 代码时出现的一系列常见操作：\n创建一个新模块。 添加依赖项。 升级依赖项。 删除未使用的依赖项。 要使用go module,首先要设置 ==GO111MODULE=on== ,如果没设置，执行命令的时候会有提示。\n==GO111MODULE== 的取值为 off, on, or auto (默认值，因此前面例子里需要注意2个重点)。\noff: GOPATH mode，查找vendor和GOPATH目录 on：module-aware mode，使用 go module，忽略GOPATH目录 auto：如果当前目录不在$GOPATH 并且 当前目录（或者父目录）下有go.mod文件，则使用GO111MODULE， 否则仍旧使用 GOPATH mode。 sh 1 2 export GO111MODULE=on export GOPROXY=https://goproxy.io ## 设置代理 go mod 参数说明 commond 说明 download download modules to local cache (下载依赖的module到本地cache)) edit edit go.","title":"Go mod"},{"content":"Go语言标准库内建提供了net/http包，涵盖了HTTP客户端和服务端的具体实现。使用net/http包，我们可以很方便地编写HTTP客户端或服务端的程序。\nhttp服务端的创建流程 在使用http/net包创建服务端只需要两个步骤 绑定处理器函数 func(ResponseWriter, *Request)与 启用监听 http.ListenAndServe。\ngo 1 2 3 4 5 6 7 8 9 10 package main import \u0026#34;net/http\u0026#34; func main() { http.HandleFunc(\u0026#34;/\u0026#34;, func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;123\u0026#34;)) }) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) } 通过分析net/http包中server.go 在执行创建http服务端主要执行了下面几个步骤：\nhttp.HandleFunc 绑定处理函数 所有的操作的方法都属于一个结构体 ServeMux m: 用户传入的路由和处理方法的映射表，路由和处理函数被定义为结构体muxEntry的属性 mu： 实例化出来的对象的读写锁 调用DefaultServeMux.Handle() 在DefaultServeMux.Handle()中调用DefaultServeMux.HandleFunc(pattern, handler) 在将传入http.HandleFunc()的回调函数，与路由的映射信息，放到该DefaultServeMux的属性中 映射map中 muxEntry http.ListenAndServe 启动服务监听 实例化一个server结构体 调用 ListenAndServe() ListenAndServe()中 net.Listen(\u0026quot;tcp\u0026quot;, addr) 启动tcp服务监听 Serve()中 appcet()处理用户连接，go c.serve(connCtx) 处理业务段（如判断信息，拼接http、找到对应处理函数） 综上所述，net/http server.go 一切的基础为ServeMux 和 Handler\nGo语言的net/http包还封装了常用处理器，如 FileServer，NotFoundHandler RedirectHandler\nhttp客户端的使用 简单的Get请求 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package main import ( \u0026#34;bytes\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;reflect\u0026#34; ) func main() { resp, err := http.Get(\u0026#34;http://www.baidu.com\u0026#34;) if err != nil { fmt.Println(err) return } fmt.Println(reflect.TypeOf(resp.Body)) // *http.gzipReader b := bytes.NewBuffer(make([]byte, 1024)) b.ReadFrom(resp.Body) fmt.Println(string(b.Bytes())) } post请求 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package main import ( \u0026#34;net/http\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;net/url\u0026#34; ) func main() { postParam := url.Values{ \u0026#34;user\u0026#34;: {\u0026#34;xxxxxx\u0026#34;}, \u0026#34;Pwd\u0026#34;: {\u0026#34;1\u0026#34;}, } resp, err := http.PostForm(\u0026#34;http://www.baidu.com/loginRegister/login\u0026#34;, postParam) if err != nil { fmt.Println(err) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println(err) return } fmt.Println(string(body)) } 构建客户端请求 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;io/ioutil\u0026#34; ) func main() { url := \u0026#34;http://10.0.0.3:5555/v2/services/haproxy/configuration/version\u0026#34; method := \u0026#34;GET\u0026#34; client := \u0026amp;http.Client { } req, err := http.NewRequest(method, url, nil) if err != nil { fmt.Println(err) } res, err := client.Do(req) defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) fmt.Println(string(body)) } 使用认证 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func basicAuth(username, password string) string { auth := username + \u0026#34;:\u0026#34; + password return base64.StdEncoding.EncodeToString([]byte(auth)) } func redirectPolicyFunc(req *http.Request, via []*http.Request) error{ req.Header.Add(\u0026#34;Authorization\u0026#34;,\u0026#34;Basic \u0026#34; + basicAuth(\u0026#34;username1\u0026#34;,\u0026#34;password123\u0026#34;)) return nil } func main() { client := \u0026amp;http.Client{ Jar: cookieJar, CheckRedirect: redirectPolicyFunc, } req, err := http.NewRequest(\u0026#34;GET\u0026#34;, \u0026#34;http://localhost/\u0026#34;, nil) req.Header.Add(\u0026#34;Authorization\u0026#34;,\u0026#34;Basic \u0026#34; + basicAuth(\u0026#34;username1\u0026#34;,\u0026#34;password123\u0026#34;)) resp, err := client.Do(req) } Reference Basic HTTP Auth in Go ","permalink":"https://www.161616.top/go-net/","summary":"Go语言标准库内建提供了net/http包，涵盖了HTTP客户端和服务端的具体实现。使用net/http包，我们可以很方便地编写HTTP客户端或服务端的程序。\nhttp服务端的创建流程 在使用http/net包创建服务端只需要两个步骤 绑定处理器函数 func(ResponseWriter, *Request)与 启用监听 http.ListenAndServe。\ngo 1 2 3 4 5 6 7 8 9 10 package main import \u0026#34;net/http\u0026#34; func main() { http.HandleFunc(\u0026#34;/\u0026#34;, func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;123\u0026#34;)) }) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) } 通过分析net/http包中server.go 在执行创建http服务端主要执行了下面几个步骤：\nhttp.HandleFunc 绑定处理函数 所有的操作的方法都属于一个结构体 ServeMux m: 用户传入的路由和处理方法的映射表，路由和处理函数被定义为结构体muxEntry的属性 mu： 实例化出来的对象的读写锁 调用DefaultServeMux.Handle() 在DefaultServeMux.Handle()中调用DefaultServeMux.HandleFunc(pattern, handler) 在将传入http.HandleFunc()的回调函数，与路由的映射信息，放到该DefaultServeMux的属性中 映射map中 muxEntry http.ListenAndServe 启动服务监听 实例化一个server结构体 调用 ListenAndServe() ListenAndServe()中 net.Listen(\u0026quot;tcp\u0026quot;, addr) 启动tcp服务监听 Serve()中 appcet()处理用户连接，go c.serve(connCtx) 处理业务段（如判断信息，拼接http、找到对应处理函数） 综上所述，net/http server.go 一切的基础为ServeMux 和 Handler","title":"go net/http使用"},{"content":"在TCP/IP协议中，“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识，那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。\n常用的Socket类型有两种：流式Socket（SOCK_STREAM）和数据报式Socket（SOCK_DGRAM）。流式是一种面向连接的Socket，针对于面向连接的TCP服务应用；数据报式Socket是一种无连接的Socket，对应于无连接的UDP服务应用。\n套接字通讯原理示意\nTCP的C/S架构 在整个通信过程中，服务器端有两个socket参与进来，但用于通信的只有conn这个socket。它是由 listener创建的。隶属于服务器端。客户端有一个socket参与进来。\nnet.Listen() 建立一个用于连接监听的套接字 listen.Accept() // 阻塞监听客户端连接请求，成功用于连接，返回用于通信的socket net.Dial() 客户端向服务端发起连接建立一个socket连接\n并发的C/S模型通信 Server Accept()函数的作用是等待客户端的链接，如果客户端没有链接，该方法会阻塞。如果有客户端链接，那么该方法返回一个Socket负责与客户端进行通信。所以，每来一个客户端，该方法就应该返回一个Socket与其通信，因此，可以使用一个死循环，将Accept()调用过程包裹起来。\n需要注意，实现并发处理多个客户端数据的服务器，就需要针对每一个客户端连接，单独产生一个Socket，并创建一个单独的goroutine与之完成通信。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;net\u0026#34; \u0026#34;strings\u0026#34; ) func handleConnect(conn net.Conn){ var ( b []byte err error n int ) fmt.Println(conn.RemoteAddr(),\u0026#34;建立连接.\u0026#34;) defer conn.Close() b = make([]byte,4096) // 客户端可能持续不断的发送数据，因此接收数据的过程可以放在for循环中，服务端也持续不断的向客户端返回处理后的数据。 for { n,err = conn.Read(b) content := strings.Trim(string(b[:n]),\u0026#34;\\r\\n\u0026#34;) // window中传送的内容存在换行符，作为判断时需要删除 // 当客户端退出，服务端从chan中读取内容时是没有的，因此的到0 或者客户端主动退出输入exit或者quit if n == 0 || content == \u0026#34;exit\u0026#34; || content == \u0026#34;quit\u0026#34; { fmt.Println(\u0026#34;客户端退出：\u0026#34;,conn.RemoteAddr()) return } if err != nil { fmt.Println(err) return } if _,err = conn.Write([]byte(fmt.Sprintf(\u0026#34;server reply:%s\u0026#34;,b[:n])));err !=nil { fmt.Println(err) return } fmt.Println(\u0026#34;client send: \u0026#34;,content) } } func main() { var ( listener net.Listener err error conn net.Conn ) // 建立一个用于连接监听的套接字 if listener, err = net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;10.0.0.1:8088\u0026#34;); err != nil { fmt.Println(err) return } defer listener.Close() fmt.Println(\u0026#34;waiting client connect.\u0026#34;) // 阻塞监听客户端连接请求，成功用于连接，返回用于通信的socket for { if conn, err = listener.Accept(); err != nil { fmt.Println(err) return } go handleConnect(conn) } } 使用nc作为客户端向服务端发送信息\n自定义客户端 客户端需要持续的向服务端发送数据，同时也要接收从服务端返回的数据。因此可将发送和接收放到不同的协程中。\n主协程循环接收服务器回发的数据（该数据应已转换为大写），并打印至屏幕； 子协程循环从键盘读取用户输入数据。 读取键盘输入可使用 os.Stdin.Read()。 注意事项：\n服务端有对 exit返回的是 io.EOF 当服务端断开时，chan读取的信息就为0了即服务端已经退出，如果客户端不退出会一直报错 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;net\u0026#34; \u0026#34;os\u0026#34; \u0026#34;strings\u0026#34; ) func main() { var ( conn net.Conn err error n int ) if conn, err = net.Dial(\u0026#34;tcp\u0026#34;, \u0026#34;10.0.0.1:8088\u0026#34;); err != nil { fmt.Println(err, 111) return } defer conn.Close() go func() { str := make([]byte, 1024) for { n, err := os.Stdin.Read(str) content := strings.ToLower(strings.Trim(string(str[:n]), \u0026#34;\\r\\n\u0026#34;)) if n == 0 { fmt.Println(\u0026#34;与服务端断开连接\u0026#34;) return } if err == io.EOF || content == \u0026#34;quit\u0026#34; { return } if err != nil { fmt.Println(1, err) continue } _, err = conn.Write([]byte(content)) if err != nil { fmt.Println(111, err) return } } }() byt := make([]byte, 1024) for { if _, err = conn.Read(byt); err != nil { if err == io.EOF { return } fmt.Println(err) continue } fmt.Println(\u0026#34;server reply:\u0026#34;, string(byt[:n])) } } ","permalink":"https://www.161616.top/go-tcp-in-go/","summary":"在TCP/IP协议中，“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识，那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。\n常用的Socket类型有两种：流式Socket（SOCK_STREAM）和数据报式Socket（SOCK_DGRAM）。流式是一种面向连接的Socket，针对于面向连接的TCP服务应用；数据报式Socket是一种无连接的Socket，对应于无连接的UDP服务应用。\n套接字通讯原理示意\nTCP的C/S架构 在整个通信过程中，服务器端有两个socket参与进来，但用于通信的只有conn这个socket。它是由 listener创建的。隶属于服务器端。客户端有一个socket参与进来。\nnet.Listen() 建立一个用于连接监听的套接字 listen.Accept() // 阻塞监听客户端连接请求，成功用于连接，返回用于通信的socket net.Dial() 客户端向服务端发起连接建立一个socket连接\n并发的C/S模型通信 Server Accept()函数的作用是等待客户端的链接，如果客户端没有链接，该方法会阻塞。如果有客户端链接，那么该方法返回一个Socket负责与客户端进行通信。所以，每来一个客户端，该方法就应该返回一个Socket与其通信，因此，可以使用一个死循环，将Accept()调用过程包裹起来。\n需要注意，实现并发处理多个客户端数据的服务器，就需要针对每一个客户端连接，单独产生一个Socket，并创建一个单独的goroutine与之完成通信。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;net\u0026#34; \u0026#34;strings\u0026#34; ) func handleConnect(conn net.","title":"Go socket TCP协议实现"},{"content":"golang保留的函数 init(), main()是golang的保留函数，有如下特点：\nmain() 只能用在main包中，仅可定义一个，init() 可定义任意包，可重复定义，建议只定义一个 两个函数定义时不能有任何返回值 只能由go自动调用，不可被引用 init() 先于 main() 执行，并不能被其他函数调用，执行时按照main import顺序执行。 包的执行顺序 Go的初始化和执行总是从main.main函数（main包导入其它的包） 同包下的不同 .go 文件，按照以文件名或包路径名的字符串顺序“从小到大”排序顺序执行 其他的包只有被main包 import 才会执行，按照 import 的先后顺序执行; 如果某个包被多次导入的话，在执行的时候只会导入一次; 当一个包被导入时，如果它还导入了其它的包，则先将其它的包包含进来; 导入顺序与初始化顺序相反 main =\u0026gt; p1 =\u0026gt; p2 | p2 =\u0026gt; p1 =\u0026gt; p main被最后一个初始化，因其总是依赖其他包 函数 函数是将具有独立功能的代码组织成为一个整体，使其具有特殊功能的代码集。在Go语言中，函数是一种数据类型，其特性有如下：\n支持匿名函数 支持带有变量名的返回值 支持多值返回 支持匿名函数 不支持重载，一个包中不能有两个名字一样的函数。 定义语法\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func test(){ } func test(a int, b int){ } func test(a,b int){ } func test(a,b int list...int){ } func test(a int, b int) int{ } func test(a int, b int ) (int,int){ } func test(a,b int) (num int, err error){ } 花括号必须与函数声明在同一行，这种写法是错误的\ngo 1 2 3 4 func test() { } 命名返回值名称 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package main import \u0026#34;fmt\u0026#34; func test(a, b, c int) (he int, cha int) { he = a + b + c cha = a - b - c return } func main() { a, b := test(15, 10, 5) fmt.Println(a) fmt.Println(b) } _标识符，用来忽略返回值\n函数参数传递方式 \\1. 值传递 \\2. 引用传递\n注意：无论是值传递，还是引用传递，传递给函数的都是变量的副本，不过，值传递是值的持贝。引用传递是地址的持贝，一般来说，地址持贝更为高效。而值持贝取决于拷贝的对象大小，对象越大，则性能越低。\n注意2：map、slice、chan、指针、interface默认以引用的方式传递\n自定义函数类型 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package main import \u0026#34;fmt\u0026#34; type ty_func func(int, int) int func add(a, b int) int { return a + b } func operator(op ty_func, a, b int) int { return op(a, b) } func main() { c := add sum := operator(c, 100, 200) fmt.Println(sum) } 不定参数 不定参数可以通过下标/循环方式获取参数值 不定参数在定义时，固定参数放前面，不定参数放后面 在对函数调用时，固定参数必须传值，不定参数可以根据需要来决定是否要传值 语法\ngo 1 2 3 func {func_name}({param} ...{type}){ func_body } 参数的类型是一个 {type} 类型的集合\n练习：写一个函数add，支持1个或多个int相加，并返回相加结果\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package main import \u0026#34;fmt\u0026#34; func test(num ...int) { var sum int for n := 1; n \u0026lt;= len(num); n++ { sum += num[(n - 1)] } fmt.Println(sum) } func main() { test(1) test(1, 2, 3) test(1, 2, 3, 4) } 练习：写一个函数concat，支持1个或多个string相拼接，并返回结果\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 func concat(age ...string) { var str string for _,v := range age { str += v } fmt.Println(str) } func main() { concat(\u0026#34;hellow\u0026#34;, \u0026#34; world\u0026#34;, \u0026#34; go\u0026#34;) } 延迟调用defer 当函数返回时，执行defer语句。因此，可以用来做资源清理 多个defer语句，按LIFO（后进先出）的顺序执行 defer语句中的变量，在defer声明时就决定了。 用途\n关闭文件句柄 锁资源释放 数据库连接释放 defer语句中的变量，在defer声明时就决定了其值\ngo 1 2 3 4 5 6 7 8 9 10 11 12 func defer_test() { i := 0 defer fmt.Println(i) i = 10 fmt.Println(i) } func main() { defer_test() } 多个defer按LIFO（后进先出）的顺序执行\ntext 1 2 3 4 5 6 7 8 9 10 11 12 func defer_test() { i := 0 defer fmt.Println(i) i = 10 fmt.Println(i) } func main() { defer_test() } defer的作用域，此处可以看到，defer的传入不是在main的作用域下，测试可发现 defer只会在当前函数和方法返回之前被调用。\nGO 1 2 3 4 5 6 7 8 9 10 11 12 13 package main import \u0026#34;fmt\u0026#34; func main() { fmt.Println(\u0026#34;block starts\u0026#34;) { defer fmt.Println(\u0026#34;defer runs\u0026#34;) fmt.Println(\u0026#34;block ends\u0026#34;) } fmt.Println(\u0026#34;main ends\u0026#34;) } 函数作用域 全局变量：既能在函数中使用，也能在其他函数中使用，可以称为定义在函数外的变量。\n局部变量：定义在函数内部的变量成为局部变量，局部变量的作用域在函数内部。\n如果全局变量的名字和局部变量的名字相同，使用的是局部变量\n匿名函数 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package main import \u0026#34;fmt\u0026#34; var ( test = func(a, b int) int { return a + b }(10, 20) ) var test1 = func(age ...int) int { var sum int for n := 0; n \u0026lt; len(age); n++ { sum += age[n] } return sum } func main() { c := test fmt.Println(c) d := test1(100, 100, 100) fmt.Println(d) } ","permalink":"https://www.161616.top/go-function/","summary":"golang保留的函数 init(), main()是golang的保留函数，有如下特点：\nmain() 只能用在main包中，仅可定义一个，init() 可定义任意包，可重复定义，建议只定义一个 两个函数定义时不能有任何返回值 只能由go自动调用，不可被引用 init() 先于 main() 执行，并不能被其他函数调用，执行时按照main import顺序执行。 包的执行顺序 Go的初始化和执行总是从main.main函数（main包导入其它的包） 同包下的不同 .go 文件，按照以文件名或包路径名的字符串顺序“从小到大”排序顺序执行 其他的包只有被main包 import 才会执行，按照 import 的先后顺序执行; 如果某个包被多次导入的话，在执行的时候只会导入一次; 当一个包被导入时，如果它还导入了其它的包，则先将其它的包包含进来; 导入顺序与初始化顺序相反 main =\u0026gt; p1 =\u0026gt; p2 | p2 =\u0026gt; p1 =\u0026gt; p main被最后一个初始化，因其总是依赖其他包 函数 函数是将具有独立功能的代码组织成为一个整体，使其具有特殊功能的代码集。在Go语言中，函数是一种数据类型，其特性有如下：\n支持匿名函数 支持带有变量名的返回值 支持多值返回 支持匿名函数 不支持重载，一个包中不能有两个名字一样的函数。 定义语法\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func test(){ } func test(a int, b int){ } func test(a,b int){ } func test(a,b int list.","title":"Go 函数 function"},{"content":"Go语言将数据类型分为四类：基础类型、复合类型、引用类型和接口类型。\n基础数据类型包括：\n基础类型： - 布尔型、整型、浮点型、复数型、字符型、字符串型、错误类型。 复合数据类型包括： - 指针、数组、切片、字典、通道、结构体、接口。 基础数据类型 布尔值和布尔表达式 布尔类型的变量取值结果要么是真，要么是假，用bool关键字进行定义\n布尔类型默认值为 false\n指定格式的输出 %t\n语法 描述/结果 !b 逻辑非操作符 b值为true 则 操作结果为false a || b 短路逻辑或，只要布尔值 a b 中任何一个为true表达式结果都为true a \u0026amp;\u0026amp; b 短路逻辑与，两个表达式a b都为true，则整个表达式结果为true x \u0026gt; y 表达式x的值小于表达式Y的值，则表达式的结果为true 数值类型 go语言提供了大内置的数值类型，标准库也提供了big.Int类型的整数，和big.Rat类型的有理数，这些都是大小不限（只限于机器内存）\n整型 GO语言提供了11种整型，包含5种有符号，和5种无符号的与一种用于存储指针的整数类型。Go语言允许使用byte来作为无符号uint8类型的同义词，在使用单个字符时提倡使用rune来替代 int32\n类型 存储空间 取值范围 byte 8-bit 同uint8 int 系统决定 依赖不通平台实现，32位操作系统为int32的值范围，64位操作系统为int64的值范围 int8 8-bit [-128, 127] ，表示 UTF-8 字符串的单个字节的值，对应 ASCII 码的字符值 int16 16-bit [-32678, 32767] int32 32-bit [2147483648, 2147483647] int64 64-bit [-9223372036854775808 , 9223372036854775807] rune 32-bit 同uint32，表示 单个 Unicode 字符 uint 系统决定 依赖不通平台下的实现，可以是uint32或uint64 uint8 8-bit uint16 16-bit [0, 65535] uint32 32-bit [0, 4294967295] uint64 64-bit [0, 18446744073709551615] uintptr 系统决定 一个可以恰好容纳指针值得无符号整数类型（32位操作系统为uint32的值范围，64位系统为uint64的值范围） 浮点类型 Go语言提供了两种类型的浮点类型和两种类型的复数类型，\n类型 存储空间 取值范围 float32 32-bit [1.401298464324817070923729583289916131280e-45 , 3.402823466385288598117041834516925440e+38] 精确到小数点后7位 float64 64-bit [4.940656458412465441765687928682213723651e-324 , 1.797693134862315708145274237317043567981e+308] 精确到小数点后15位 complexm64 64-bit 实部和虚部都是一个float32 complexm128 128-bit 实部和虚部都是一个float64 go 1 fmt.printf(\u0026#34;%.2f\u0026#34;,num) // 保留两位小数，同时进行了四舍五入 字符串 (string) 字符串使用双引号 \u0026quot; 或者反引号 ```来创建，双引号可以解析字符串变量\n定义字符变量用 byte 关键词 var ch byte = 'a' 8-bit，代表了 ASCII 码的一个字符。指定格式的输出 %c 定义字符变量用 rune 关键词 var ch rune = 'a' 32-bit 代表了 Unicode（UTF-8）码的一个字符。 定义字符变量用 string 关键词 var ch string = \u0026quot;abc\u0026quot;指定格式的输出 %s\n字符串的结束标志 \\0，Go语言使用的UTF8编码，英文占1个字符，一个汉字占3个字符\n自动推导类型 自动推到类型，创建的浮点型默认为 float64，整型为int\ngo 1 2 a := \u0026#34;123\u0026#34; a := 10 使用fmt格式化输出 格式指令通常由用于输出单个值，每个值都按格式指令格式化。用于fmt.Printf() fmt.Errorf() fmt.Fprintf() fmt.Sprintf()函数的格式字符串包含一个或多个格式指令\n格式指令 含义/结果 %b 二进制数值 %c 数值对应的 Unicode 编码字符 %d 十进制数值 %o 八制数值 %e 科学计数法，e表示 %E 科学计数法，E表示 %f 有小数部分，无指数部分 %g 以%f或%e表示浮点数或复数 %G 以%f或%E表示浮点数或复数 %s 直接输出字符串或者[]byte %q 双引号括起来的字符串或者[]byte %x 每个字节用两字节十六进制表示，a-f表示 %X 每个字节用两字节十六进制表示，A-F表示 %t 以true或fales输出布尔值 %T 输出值得类型 %v 默认格式输出内置或自定义类型的值 强制类型转换 类型转换用于将一种数据类型转换为另外一种类型\ntext 1 2 var num float64 = 3.15 fmt.Printf(\u0026#34;%d\u0026#34;, int(num)) 类型不一致的不能进行运算，在类型转换时，建议将低类型转换为高类型，保证数据精度，高类型转换成地类型，可能会丢失精度，或数据溢出\n复合数据类型 数组 数组，一系列同一类型数据的集合，数组是值类型,在初始化后长度是固定的，无法修改其长度。\n数组的定义 var arr [元素数量]类型\n数组初始化 全部初始化 var arr [5]int = [5]int{1,2,3,4,5}\n部分初始化 var arr [5]int = [5]int{1,2} ,没有指定初值的元素将会赋值为其元素类型(int)的默认值(0)\n指定下标初始化 var arr = [5]int{2:5, 3:6} key:value的格式\n通过初始化确定数组长度，var arr = [...]int{1, 2, 3} 长度是根据初始化时指定个数\n相同空间大小（类型）的数组可以用 == != 来比较是否相同。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 package main import \u0026#34;fmt\u0026#34; func main() { var arr [2]int = [2]int{1, 2} var arr2 [2]int = [...]int{2, 2} fmt.Println(arr == arr2) } D:\\go_work\\src\u0026gt;go run main.go false 数组的遍历 通过 for .. len() 完成遍历 通过 for .. range 完成遍历 作为函数值传递 golang数组是值类型，当数组作为函数参数，函数中修改数组中的值，不会影响原数组\ngo 1 2 3 4 5 6 7 8 9 10 11 12 package main import \u0026#34;fmt\u0026#34; func test(t [2]int) { fmt.Println(\u0026amp;t) } func main() { var arr [2]int = [2]int{1, 2} test(arr) } 二维数组 初始化方式\n全部初始化 var arr [2][2]int = [2][2]int{ {1,2},{3,4} } 部分初始化 var arr [2][2]int = [2][2]int{ {1,2},{3} } 指定元素初始化 var arr [2][2]int = [2][2]int{ 1:{1} }，var arr [2][2]int = [2][2]int{ 1:{1:3} } 通过初始化确定二维数组行数 var arr [...][2]int = [2][2]int{ {1,2},{3,4} } 行下标可以用 ... 列下标不可用 ... map Go语言中的字典结构是有键和值构成的，所谓的键，就类似于字典的索引，可以快速查询出对应的数据。\nmap是只用无序的键值对的集合。\nmap最重要的一点是通过key来快速检索数据，key类似于索引，指向数据的值。\nmap中key的值是不能重复的\n引用类型或包含引用类型的数据类型不能作为key\nmap的创建 字面量：var map_name map[keyType]valType 类型推导： map_name := map[keyType]valType 关键词：make(map[keyType]valType) map的初始化 字面量：var maps[int]string = map[int]string{1: \u0026quot;zhangsan\u0026quot;, 2: \u0026quot;lisi\u0026quot;} 类型推导： maps := map[int]string{1: \u0026quot;zhangsan\u0026quot;, 2: \u0026quot;lisi\u0026quot;} 关键词：maps := make(map[string]int,10); maps[\u0026quot;zhangsan\u0026quot;] = 14 map的key value 通过key获取值时，判断key是否存在 var1, var2 := map[key]，如存在，var1存储对应的值，var2的值为true，var2否则为false\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package main import \u0026#34;fmt\u0026#34; func initial(t []int) { for n := 0; n \u0026lt; 10; n++ { t[n] = n } } func main() { var m map[int]string = map[int]string{1: \u0026#34;zhangsan\u0026#34;, 2: \u0026#34;lisi\u0026#34;} fmt.Println(m) v, ok := m[2] if ok { fmt.Println(v) } else { fmt.Println(ok) } v, ok = m[3] if ok { fmt.Println(v) } else { fmt.Println(ok) } } delete(map,2) 通过key删除某个值\n作为函数参数传递 slice map channel都是引用类型，所以改变是改变的变量的地址。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package main import \u0026#34;fmt\u0026#34; func initial(t map[int]string) { for n := 0; n \u0026lt; 10; n++ { t[n] = fmt.Sprintf(\u0026#34;aaa%d\u0026#34;, n) } } func main() { var m = make(map[int]string, 10) initial(m) fmt.Println(m) } 切片 切片与数组相比，切片的长度是不固定的，可以追加元素，在追加时可能使切片的容量增大，所以可以将切片理解为“动态数组”，但是，它不是数组。\n切片定义 var slice_name []type 默认空切片，长度为0\nslice_name := []type{} 默认空切片，长度为0\nmake([]type, length, capacity) length：已初始化的空间，capacity：已开辟的空间（length+空闲）。length不能大于capacity\nlen() 返回长度 cap() 返回容量\n如果使用字面量的方式创建切片，大部分的工作就都会在编译期间完成，使用 make() 关键字创建切片时，很多工作都需要运行时的参与。\n切片初始化 通过 var slice_name []type 方式创建\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 import \u0026#34;fmt\u0026#34; func main() { var slices []int slices = append(slices, 1, 2, 3, 4, 5, 6) fmt.Print(slices) slices = append(slices, 100, 99) fmt.Print(slices) } 通过 slice_name := []type{} 方式创建\n直接在 {} 中添加值 通过 append() 添加 通过 make([]type, length, capacity) 方式创建\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package main import \u0026#34;fmt\u0026#34; func main() { var slices []int slices = make([]int, 3, 5) slices[0] = 1 slices[1] = 2 slices[2] = 3 slices[3] = 4 fmt.Println(slices) } 切片的截取 切片截取\n操作 含义 s[n] 切片s中索引位置为N的项 s[:] 从切片s的索引位置到 len(s) - 1 处所获得的切片 s[low:] 从切片s的索引位置low到 len(s) - 1 处所获得的切片 s[:high] 从切片s的索引位置0到high处所获得的切片 len=high s[low:higt] 从切片s的索引位置low到high处所获得的切片 len=high-low s[low:higt:max] 从切片s的索引位置low到high处所获得的切片，len=high-low，cap=max-low len(s) 切片s的长度，\u0026lt;=cap(s) cap(s) 切片s的容量，\u0026gt;=len(s) slice[startVal, length, Capacity]\n容量为：capacity - startVal\n长度为： length - startVal\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package main import \u0026#34;fmt\u0026#34; func main() { var slices []int slices = []int{1, 2, 3, 4, 5, 6, 7, 8} s := slices[1:3:5] fmt.Println(s) fmt.Println(len(s)) fmt.Println(cap(s)) } 在截取时，capacity 不能超过原slice的 capacity\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package main import \u0026#34;fmt\u0026#34; func main() { var slices []int slices = []int{1, 2, 3, 4, 5, 6, 7, 8} s := slices[1:3:10] fmt.Println(s) fmt.Println(len(s)) fmt.Println(cap(s)) } 切片值得修改 问：切片截取后返回新切片，对新切片的值修改，会影响原切片吗\n当对切片进行截取操作后，产生了新的切片，新的切片是指向原有切片的，对新切片值修改，会影响到原有切片\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package main import \u0026#34;fmt\u0026#34; func main() { var slices []int slices = []int{1, 2, 3, 4, 5, 6, 7, 8} s := slices[1:3] s[1] = 100 fmt.Println(s) fmt.Println(slices) } 追加和拷贝 append(slice, 1,2,3) 向切片末尾追加数据 copy(slice1, slice2) 拷贝的长度为两个切片中长度较小的长度值 作为参数值传递，切片是数组的一个引用，因此切片是引用类型，操作会修改原有切片\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package main import \u0026#34;fmt\u0026#34; func initial(t []int) { for n := 0; n \u0026lt; 10; n++ { t[n] = n } } func main() { var slices = make([]int, 10) fmt.Println(slices) initial(slices) fmt.Println(slices) } 切片扩容 切片扩容，一般方式：上一次容量的2倍，超过1024字节，每次扩容上一次的1/4\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package main import \u0026#34;fmt\u0026#34; func initial(t []int) { for n := 0; n \u0026lt; 10; n++ { t[n] = n } } func main() { var slices = make([]int, 3) var slices1 = []int{4, 5} copy(slices, slices1) fmt.Println(slices) fmt.Println(slices1) } struct 结构体 结构体 struct 是由一系列具有相同类型或不同类型的数据构成的数据集合，结构体可以很好的管理一批有联系的数据，使用结构体可以提高程序的易读性。Go中提供了对 struct 的支持，与数组一样，struct属于复合类型，并非引用类型。\nGo语言中结构体包含以下特性\n值传递，Go语言中结构体和数组一样是值类型，可以声明结构体指针向下传递 不可继承，Go语言中没有继承的概念，在结构体中，可以通过组合结构体，来构建更复杂的结构体。 结构体不能包含自己。 结构体声明 成员名称前不能加 var\ngo 1 2 3 4 5 6 type Student struct { id int name string age int addr string } 空结构体 结构体也可以不包含任何字段，称为空结构体 struct{}\n结构体初始化 顺序初始化 var stu = Student{ id:101, name:\u0026quot;zhangsan\u0026quot;, age:19, addr:\u0026quot;shanghai\u0026quot; } 指定成员初始化 var stu = Student{name:\u0026quot;zhangsan\u0026quot;, age:19} 通过 结构体变量.成员 完成初始化 var stu Student stu.age=19 stu.addr=\u0026quot;peking\u0026quot; 结构体传递 结构体与数组一样，都是值传递，将结构体作为函数实参传给函数的形参时，会复制一个副本，所以为了提高性能，在结构体传给函数时，可以使用指针结构体。\n指针结构体定义：声明结构体变量时，在结构体类型前加 * 号，便声明一个指向结构体的指针。 var stu *Student。 指针结构体访问： 由于.的优先级高于*struct_name,故在使用时，需要对结构体加括号。(*struct_name).成员属性 (*stu).name\n","permalink":"https://www.161616.top/go-datastruct/","summary":"Go语言将数据类型分为四类：基础类型、复合类型、引用类型和接口类型。\n基础数据类型包括：\n基础类型： - 布尔型、整型、浮点型、复数型、字符型、字符串型、错误类型。 复合数据类型包括： - 指针、数组、切片、字典、通道、结构体、接口。 基础数据类型 布尔值和布尔表达式 布尔类型的变量取值结果要么是真，要么是假，用bool关键字进行定义\n布尔类型默认值为 false\n指定格式的输出 %t\n语法 描述/结果 !b 逻辑非操作符 b值为true 则 操作结果为false a || b 短路逻辑或，只要布尔值 a b 中任何一个为true表达式结果都为true a \u0026amp;\u0026amp; b 短路逻辑与，两个表达式a b都为true，则整个表达式结果为true x \u0026gt; y 表达式x的值小于表达式Y的值，则表达式的结果为true 数值类型 go语言提供了大内置的数值类型，标准库也提供了big.Int类型的整数，和big.Rat类型的有理数，这些都是大小不限（只限于机器内存）\n整型 GO语言提供了11种整型，包含5种有符号，和5种无符号的与一种用于存储指针的整数类型。Go语言允许使用byte来作为无符号uint8类型的同义词，在使用单个字符时提倡使用rune来替代 int32\n类型 存储空间 取值范围 byte 8-bit 同uint8 int 系统决定 依赖不通平台实现，32位操作系统为int32的值范围，64位操作系统为int64的值范围 int8 8-bit [-128, 127] ，表示 UTF-8 字符串的单个字节的值，对应 ASCII 码的字符值 int16 16-bit [-32678, 32767] int32 32-bit [2147483648, 2147483647] int64 64-bit [-9223372036854775808 , 9223372036854775807] rune 32-bit 同uint32，表示 单个 Unicode 字符 uint 系统决定 依赖不通平台下的实现，可以是uint32或uint64 uint8 8-bit uint16 16-bit [0, 65535] uint32 32-bit [0, 4294967295] uint64 64-bit [0, 18446744073709551615] uintptr 系统决定 一个可以恰好容纳指针值得无符号整数类型（32位操作系统为uint32的值范围，64位系统为uint64的值范围） 浮点类型 Go语言提供了两种类型的浮点类型和两种类型的复数类型，","title":"Go 数据结构"},{"content":"算术运算符 运算符 示例 结果 + 10 + 5 15 - 10 - 5 5 * （除数不能为0） 10 * 5 50 / 10 / 5 2 % （除数不能为0） 10 % 3 1 ++ a = 0; a++ a = 1 \u0026ndash; a = 2; a\u0026ndash; a = 1 总结\n除法/取余运算除数不能为0 只有后自增/减，没有前自增/减。没有 ++a 或 --a 只有 a++ 或 a-- 输入半径，计算圆的面积和周长并打印出来（PI为3.14）\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package main import \u0026#34;fmt\u0026#34; func main() { const PI = 3.14 fmt.Println(\u0026#34;请输入半径：\u0026#34;) var r float64 fmt.Scan(\u0026amp;r) fmt.Printf(\u0026#34;面积为：%.2f\\n\u0026#34;, 2*PI*r) fmt.Printf(\u0026#34;周长为：%.2f\\n\u0026#34;, PI*r*r) } 某学生三门课成绩为，语文90，数学89，英语69，编程求总分与平均分\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package main import \u0026#34;fmt\u0026#34; func main() { var ( chinese = 90 math = 89 english = 69 ) score := chinese + math + english //avg := score / 3 // 此处是整数值 avg := float64(score) / 3 fmt.Printf(\u0026#34;总分为：%d\\n\u0026#34;, score) fmt.Printf(\u0026#34;平均分为：%.2f\\n\u0026#34;, avg) } 问题: 计算商品价格\n问题1： 某商店T-shirt的价格为35圆/件，裤子的价格120圆/条，小明在该店购买了3件t-shirt和2条裤子，并且打8.8折，小明应该付多少钱\n问题2：如上题打完8.8折后，出现小数，商店为了方便结算只收取商品整数部分的钱，如 303.6，则只收取303元。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package main import \u0026#34;fmt\u0026#34; func main() { var ( tshirt = 35 trousers = 120 ) total := 3*tshirt + 2*trousers realMember := float64(total) / 0.88 fmt.Printf(\u0026#34;打88折后价格为：%.2f\\n\u0026#34;, realMember) fmt.Printf(\u0026#34;只收取整钱为：%d\u0026#34;, int(realMember)) } 赋值运算符 运算符 说明 实例 = 普通赋值 c = a + b 将a + b表达式结果赋值给c += 相加后在赋值 c += a 等价于 c = c + a -= 相减后再赋值 c -= a 等价于 c = c - a *= 相乘后再赋值 c *= a 等价于 c = c * a /= 相除后再赋值 c /= a 等价于 c = c / a %= 取余后再赋值 c %= a 等价于 c = c % a 算数运算符优先级高于赋值运算符\ntext 1 2 3 4 5 6 7 8 9 package main import \u0026#34;fmt\u0026#34; func main() { num := 20 num %= 2 + 3 fmt.Println(num) } 关系运算符 关系运算符的结果是布尔类型的\n优先级 算数 \u0026gt; 关系 \u0026gt; 赋值\n运算符 说明 == 相等于 != 不等于 \u0026lt; 小于 \u0026gt; 大于 \u0026lt;= 小于等于 \u0026gt;= 大于等于 逻辑运算符 \u0026amp;\u0026amp; || !\n逻辑非后面的内容是bool类型\n逻辑非的运算优先级高于关系运算符\n逻辑与/逻辑或运算符优先级低于关系运算符\n\u0026amp;\u0026amp; 逻辑与的优先级高于 || fmt.Println(1 \u0026gt; 2 || 2 \u0026gt; 1 \u0026amp;\u0026amp; 10 != 10)\n单目运算符：指运算所需变量为一个运算符，即在运算当中只有一个操作数。如：a++ ，b--，!test，\u0026amp; 等\n双目运算符：运算所需比那里为两个运算符叫做双目运算符。如：a + b ，a \u0026gt;= b 等\n运算符优先级 算数运算符 * / % \u0026gt; 算数运算符 + - \u0026gt; 比较运算符 \u0026lt; \u0026gt; \u0026gt;= \u0026lt;= == != \u0026gt; 逻辑运算符 \u0026amp;\u0026amp; \u0026gt; 逻辑运算符 || \u0026gt; 赋值运算符\n运算符总结 运算符分为单目运算符双目运算符与特殊运算符 () . 逻辑运算的结果同样也是bool类型 逻辑运算符两边放的一般都是关系运算或者bool类型的值 逻辑非运算符的运算优先级要高于关系运算符 单目运算符是指运算所需变量为一个运算符，即在运算当中只有一个操作数 运算所需变量为两个运算符的叫做双目运算符 单目运算符的优先级高于双目运算符 比较运算符优先级高于逻辑与 逻辑与的运算级别高于逻辑或 ","permalink":"https://www.161616.top/go-arithmetic/","summary":"算术运算符 运算符 示例 结果 + 10 + 5 15 - 10 - 5 5 * （除数不能为0） 10 * 5 50 / 10 / 5 2 % （除数不能为0） 10 % 3 1 ++ a = 0; a++ a = 1 \u0026ndash; a = 2; a\u0026ndash; a = 1 总结\n除法/取余运算除数不能为0 只有后自增/减，没有前自增/减。没有 ++a 或 --a 只有 a++ 或 a-- 输入半径，计算圆的面积和周长并打印出来（PI为3.14）\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package main import \u0026#34;fmt\u0026#34; func main() { const PI = 3.","title":"Go 运算符"},{"content":"该文可以快速在Go语言中获得时间的计算。\n在Go中获取时间 如何获取当前时间 go 1 2 3 4 now := time.Now() fmt.Printf(\u0026#34;current time is :%s\u0026#34;, now) current time is :2009-11-10 23:00:00 +0000 UTC m=+0.000000001 如何获取UNIX Timestamp go 1 2 cur_time := time.Now().Unix() fmt.Printf(\u0026#34;current unix timestamp is :%v\\n\u0026#34;, cur_time ) 如何获取当日0:00:00 0:00:00 go 1 2 3 4 5 now := time.Now() date := time.Date(now.Year(), now.Month(), now.Day(),0, 0, 0, 0, time.Local) fmt.Printf(\u0026#34;date is :%s\u0026#34;, date) date is :2021-04-13 00:00:00 +0800 如何获取时区时间 标准时间 time.Now().UTC() 本地时区 time.Now().Local()\ngo 1 2 3 4 5 6 7 8 9 10 11 // 获取0时区时间 fmt.Printf(\u0026#34;date is :%s\\n\u0026#34;, time.Now().UTC()) date is :2021-04-13 16:02:33.853254 +0000 UTC // 快速设置时区 timeLocation, _ := time.LoadLocation(\u0026#34;Asia/Tokyo\u0026#34;) //使用时区码 fmt.Println(time.Now().In(timeLocation).String()) // 快速设置时区 2021-04-14 01:09:18.140997 +0900 JST Go中的固定时间格式 获取月份 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 time.April type Month int const ( January Month = 1 + iota February March April May June July August September October November December ) 获取星期 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 time.Sunday type Weekday int const ( Sunday Weekday = iota Monday Tuesday Wednesday Thursday Friday Saturday ) Go中的时间格式化 Go中时间格式化的格式为 2006-01-02 15:04:05 612345为格式，而不是具体时间\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 // YYYY-MM-DD fmt.Println(time.Now().Format(\u0026#34;2006-01-02\u0026#34;)) // YYYY-MM-DD hh:mm:ss fmt.Println(time.Now().Format(\u0026#34;2006-01-02 15:04:05\u0026#34;)) // M-DD fmt.Println(time.Now().Format(\u0026#34;1-02\u0026#34;)) // MM-DD fmt.Println(time.Now().Format(\u0026#34;01-02\u0026#34;) // 获取当前的小时、分钟、秒（整数） nowHour, nowMinute, nowSecond = time.Now().Clock() // 获取前一天 // AddDate(Years, months, days) yesterday = time.Now().AddDate(0,0,-1).Format(\u0026#34;01/02\u0026#34;) // 显示星期英文简写 fmt.Println(time.Now().Format(\u0026#34;2006-01-02 15:04:05 Mon\u0026#34;)) // 星期的大写 fmt.Println(time.Now().Format(\u0026#34;2006-01-02 15:04:05 Monday\u0026#34;)) // 增加微秒 fmt.Println(time.Now().Format(\u0026#34;2006-01-02 15:04:05.000000\u0026#34;)) // 纳秒 fmt.Println(time.Now().Format(\u0026#34;2006-01-02 15:04:05.000000000\u0026#34;)) } // print result 08-10-2018 08-10-2018 21:11:58 08-10-2018 21:11:58 Fri 08-10-2018 21:11:58 Friday 08-10-2018 21:11:58.880934 08-10-2018 21:11:58.880934320 Go中的时间计算 如何获取本周日期有哪些？ 获取一个星期的第一天是几号\ntext 1 2 t:=time.Now() fmt.Println(t.Weekday()) // 获取现在时间为本周的星期几 得到本日为星期几后，可以对时间进行计算，因为time包内星期的常量都为int，可以直接进行算数运算. 用一周的第一天减去当日为星期几，如果为0既『本日为本周的第一天』\ntime.AddDate(year, month, date)，仅可以添加年月日 time.Add(Hours, Minutes, Seconds)，仅可以添加时分秒\ngo 1 offset := int(time.Monday - t.Weekday()) //=-1 如不为0，time包提供了，「以当前时间为基点，进行加减运算」\ngo 1 2 // t.AddDate(year, month, date) t.AddDate(0,0,offset) // 可以获取到，周一为几月几日 综上所属，可以获得每周第一天为几月几日，每周随后一天为几月几日\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 /** * 获取上周周第一天具体年月日 **/ func GetLastWeekFirstDate() (weekMonday string) { thisWeekMonday := GetFirstDateOfWeek() TimeMonday, _ := time.Parse(\u0026#34;2006-01-02\u0026#34;, thisWeekMonday) lastWeekMonday := TimeMonday.AddDate(0, 0, -7) weekMonday = lastWeekMonday.Format(\u0026#34;2006-01-02\u0026#34;) return } /** * 获取本周的周一具体年月日 **/ func GetFirstDateOfWeek() (weekMonday string) { now := time.Now() offset := int(time.Monday - now.Weekday()) if offset \u0026gt; 0 { offset = -6 } weekStartDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) weekMonday = weekStartDate.Format(\u0026#34;2006-01-02\u0026#34;) return } /** * 获取上周最后一天具体年月日 **/ func GetLastWeekLastDate() (weekMonday string) { now := time.Now() offset := int(time.Monday - now.Weekday()) if offset \u0026gt; 0 { offset = -6 } weekStartDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) weekMonday = weekStartDate.AddDate(0, 0, -1).Format(\u0026#34;2006-01-02\u0026#34;) return } /** * 获取上周一星期所有天数的具体年月日 **/ func GetBetweenDates(sdate, edate string) []string { d := []string{} timeFormatTpl := \u0026#34;2006-01-02 15:04:05\u0026#34; if len(timeFormatTpl) != len(sdate) { timeFormatTpl = timeFormatTpl[0:len(sdate)] } date, err := time.Parse(timeFormatTpl, sdate) if err != nil { return d } date2, err := time.Parse(timeFormatTpl, edate) if err != nil { return d } if date2.Before(date) { return d } // 输出日期格式固定 timeFormatTpl = \u0026#34;2006-01-02\u0026#34; date2Str := date2.Format(timeFormatTpl) d = append(d, date.Format(timeFormatTpl)) for { date = date.AddDate(0, 0, 1) dateStr := date.Format(timeFormatTpl) d = append(d, dateStr) if dateStr == date2Str { break } } return d } ","permalink":"https://www.161616.top/golib-timeformat/","summary":"该文可以快速在Go语言中获得时间的计算。\n在Go中获取时间 如何获取当前时间 go 1 2 3 4 now := time.Now() fmt.Printf(\u0026#34;current time is :%s\u0026#34;, now) current time is :2009-11-10 23:00:00 +0000 UTC m=+0.000000001 如何获取UNIX Timestamp go 1 2 cur_time := time.Now().Unix() fmt.Printf(\u0026#34;current unix timestamp is :%v\\n\u0026#34;, cur_time ) 如何获取当日0:00:00 0:00:00 go 1 2 3 4 5 now := time.Now() date := time.Date(now.Year(), now.Month(), now.Day(),0, 0, 0, 0, time.Local) fmt.Printf(\u0026#34;date is :%s\u0026#34;, date) date is :2021-04-13 00:00:00 +0800 如何获取时区时间 标准时间 time.","title":"Go每日一库 - 时间格式化"},{"content":"github https://github.com/godbus/dbus\n增加一个端口\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package main import ( \u0026#34;github.com/godbus/dbus/v5\u0026#34; ) func main() { cli, err := dbus.SystemBus() if err != nil { panic(err) } obj := cli.Object(\u0026#34;org.fedoraproject.FirewallD1\u0026#34;, \u0026#34;/org/fedoraproject/FirewallD1\u0026#34;) call := obj.Call(\u0026#34;oorg.fedoraproject.FirewallD1.zone.addPort\u0026#34;, 0, \u0026#34;public\u0026#34;, \u0026#34;81\u0026#34;, \u0026#34;tcp\u0026#34;, \u0026#34;30000\u0026#34;) if call.Err != nil { panic(call.Err) } } go-dbus 简单教程 https://blog.csdn.net/mathmonkey/article/details/38095289\n","permalink":"https://www.161616.top/golib-gobus/","summary":"github https://github.com/godbus/dbus\n增加一个端口\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package main import ( \u0026#34;github.com/godbus/dbus/v5\u0026#34; ) func main() { cli, err := dbus.SystemBus() if err != nil { panic(err) } obj := cli.Object(\u0026#34;org.fedoraproject.FirewallD1\u0026#34;, \u0026#34;/org/fedoraproject/FirewallD1\u0026#34;) call := obj.Call(\u0026#34;oorg.fedoraproject.FirewallD1.zone.addPort\u0026#34;, 0, \u0026#34;public\u0026#34;, \u0026#34;81\u0026#34;, \u0026#34;tcp\u0026#34;, \u0026#34;30000\u0026#34;) if call.Err != nil { panic(call.Err) } } go-dbus 简单教程 https://blog.csdn.net/mathmonkey/article/details/38095289","title":"Go每日一库 - 使用go操作dbus"},{"content":"所谓的面向对象其实就是找一个专门做这个事的人来做，不用关心具体怎么实现的。所以说，面向过程强调的是过程，步骤。而面向对象强调的是对象，也就是干事的人。\nGo语言：面向对象语言特性 方法\n嵌入\n接口\n没有类\n支持类型。 特别是， 它支持structs。 Structs是用户定义的类型。 Struct类型(含方法)提供类似于其它语言中类的服务。\nStructs 一个struct定义一个状态。 这里有一个strudent struct。 它有一个Name属性和一个布尔类型的标志Real，告诉我们它是一个真实的strudent还是一个虚构的strudent。 Structs只保存状态，不保存行为。\ngo 1 2 3 4 type Creature struct { Name string Real bool } 为结构体添加方法 方法是对特定类型进行操作的函数。 它们有一个接收器条款，命令它们对什么样的类型可进行操作。 这里是一个Hello()方法，它可对student结构进行操作，并打印出它们的状态：\ngo 1 2 3 func (s Student) Hello() { fmt.Printf(\u0026#34;Name: \u0026#39;%s\u0026#39;, Real: %t\\n\u0026#34;, s.Name, s.Real) } func (s Student) func_name(){} 这是一个不太常见的语法，但是它非常的具体和清晰，不像this的隐喻性。\n一般在定义方法时，需要定义为结构体的指针，值类型的在修改结构体属性时，无法修改其内容\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package main import \u0026#34;fmt\u0026#34; type human struct { Name string Real bool } type Student struct { human Id int } func (h human) Hello() { fmt.Printf(\u0026#34;姓名：%s\\n\u0026#34;, h.Name) } func (s Student) PrintId() { fmt.Printf(\u0026#34;学号：%d\\n\u0026#34;, s.Id) } func (s Student) EditId(id int) { s.Id = id } func main() { zhangsan := Student{ human: human{\u0026#34;zhangsan\u0026#34;, true}, Id: 10, } zhangsan.Hello() zhangsan.EditId(101) zhangsan.PrintId() } 嵌入（继承） 可以将匿名的类型嵌入进struct。 如果你嵌入一个匿名的struct那么被嵌入的struct对接受嵌入的struct直接提供它自己的状态（和方法）。 比如，strudent 有一个匿名子的被嵌入的 human struct，这意味着一个 student 就是一个 hunman。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package main import \u0026#34;fmt\u0026#34; type human struct { Name string Real bool } type Student struct { human Id int } func (h *human) Hello() { fmt.Printf(\u0026#34;姓名：%s\u0026#34;, h.Name) } func main() { zhangsan := \u0026amp;Student{ human: human{\u0026#34;zhangsan\u0026#34;, true}, Id: 10, } zhangsan.Hello() } 重写 就是子类(结构体)中的方法，将父类中的相同名称的方法的功能重新给改写了\n注意：在调用时，默认调用的是子类中的方法\n方法值和表达式值 方法表达式，即方法对象赋值给变量，方法表达式有两种使用方式：\n隐式调用：方法值，调用函数时，无需再传递接收者，隐藏了接收者 显式调用：方法表达式，显示的把接收者*Student传递过去 实例：\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package main import \u0026#34;fmt\u0026#34; type human struct { Name string Real bool } type Student struct { human Id int } func (h *human) Hello() { fmt.Printf(\u0026#34;姓名：%s\\n\u0026#34;, h.Name) } func (s Student) PrintId() { fmt.Printf(\u0026#34;学号：%d\\n\u0026#34;, s.Id) } func (s Student) EditId(id int) { s.Id = id } func main() { zhangsan := Student{ human: human{\u0026#34;zhangsan\u0026#34;, true}, Id: 10, } // 常规调用 zhangsan.Hello() // 方法值 无需传递接收者 hello := zhangsan.Hello hello() // 方法表达式，调用函数式，传递接收者 hello1 := (*Student).Hello // 括号是因为 . 的优先级要高于取指符，需要做特殊处理 hello1(\u0026amp;zhangsan) } Go语言：面向对象的设计 接口 接口是Go语言对面向对象支持的标志。 接口是声明方法集的类型。 实现所有接口方法的对象自动地实现接口。 它没有继承或子类或 implements 关键字。\n接口的定义 go 1 2 3 4 type 接口名字 interface { 方法声明 } 接口的继承 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package main import \u0026#34;fmt\u0026#34; type Fooer interface { Foo1() } type Fooerson interface { Fooer Foo2() } type Foo struct { } type Fooson struct { } func (f Fooson) Foo1() { fmt.Println(\u0026#34;Foo1() here\u0026#34;) } func (f Fooson) Foo2() { fmt.Println(\u0026#34;Foo2() here\u0026#34;) } func (f Foo) Foo1() { fmt.Println(\u0026#34;Foo1() here\u0026#34;) } func main() { var fooerson Fooson var fooson Fooerson fooson = \u0026amp;fooerson fooson.Foo1() fooson.Foo2() var foo Fooer foo = fooson fooson = foo // 这样是不允许的，fooson为Fooerson接口的实现，而foo是一个Fooer接口类型的变量，可以子转换为父不能反之 foo.Foo1() } 空接口 空接口(interface{})不包含任何的方法，正因为如此，所有的类型都实现了空接口，因此空接口可以存储任意类型的数值\ngo 1 2 3 4 5 6 7 8 9 10 11 package main import \u0026#34;fmt\u0026#34; func main() { var arr []interface{} arr = append(arr, 1, \u0026#34;zhangsan\u0026#34;, 3.3) fmt.Println(arr) } 类型断言 通过类型断言，可以判断空接口中存储的数据类型。\n语法：value, ok := m.(T)\nm:表空接口类型变量\nT:是断言的类型\nvalue: 变量m中的值。\nok: 布尔类型变量，如果断言成功为true,否则为false\ngo 1 2 3 4 5 6 7 8 9 10 11 func main() { var a interface{} a = 10 ok, value := a.(int) fmt.Println(ok, value) ok1, value1 := a.(string) fmt.Println(ok1, value1) } 封装 Go语言在包的级别进行封装。 以小写字母开头的名称只在该程序包中可见。 可以隐藏私有包中的任何内容，只暴露特定的类型，接口和工厂函数。\n例如，在这里要隐藏上面的Foo类型，只暴露接口，你可以将其重命名为小写的foo，并提供一个NewFoo()函数，返回公共Fooer接口：\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type foo struct { } func (f foo) Foo1() { fmt.Println(\u0026#34;Foo1() here\u0026#34;) } func (f foo) Foo2() { fmt.Println(\u0026#34;Foo2() here\u0026#34;) } func (f foo) Foo3() { fmt.Println(\u0026#34;Foo3() here\u0026#34;) } func NewFoo() Fooer { return \u0026amp;Foo{} } 在另一个包的代码可以使用NewFoo()并访问由内部foo类型实现的Footer接口：\ngo 1 2 3 4 5 6 7 f := NewFoo() f.Foo1() f.Foo2() f.Foo3() 继承 Go语言没有任何类型层次结构。 它允许你通过组合来共享实现的细节。 但Go语言，允许嵌入匿名组合。\n通过嵌入一个匿名类型的组合等同于实现继承，这是它所有意图和目的。 一个嵌入的struct与基类一样脆弱。 你还可以嵌入一个接口， 如果嵌入类型没有实现所有接口方法，它甚至可能导致产生在编译时未被发现的运行错误。\n这里SuperFoo嵌入Fooer接口，但是SuperFoo没有实现Foo的方法。 Go编译器会愉快地让你创建一个新的SuperFood并调用Fooer的方法，但很显然这在运行时会失败。 这会编译：\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package main import \u0026#34;fmt\u0026#34; type Fooer interface { Foo1() Foo2() Foo3() } type Foo struct { } func (f Foo) Foo1() { fmt.Println(\u0026#34;Foo1() here\u0026#34;) } func (f Foo) Foo2() { fmt.Println(\u0026#34;Foo2() here\u0026#34;) } func (f Foo) Foo3() { fmt.Println(\u0026#34;Foo3() here\u0026#34;) } type SuperFooer struct { Fooer } func main() { s := SuperFooer{} s.Foo3() } 多态 多态性是面向对象编程的本质：只要对象坚持实现同样的接口，Go语言就能处理不同类型的那些对象。 Go接口以非常直接和直观的方式提供这种能力。\nGolang当中的接口解决了这个问题，只要接口中定义的方法能对应的上，那么就可以认为这个类实现了这个接口。同一个接口，使用不同的实例而执行不同操作\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package main import ( \u0026#34;fmt\u0026#34; ) type animal interface { Say() } type human struct { } type cat struct { } func (h human) Say() { fmt.Println(\u0026#34;人类\u0026#34;) } func (c cat) Say() { fmt.Println(\u0026#34;猫\u0026#34;) } func main() { var a animal a = cat{} a.Say() a = human{} a.Say() } ","permalink":"https://www.161616.top/go-object/","summary":"所谓的面向对象其实就是找一个专门做这个事的人来做，不用关心具体怎么实现的。所以说，面向过程强调的是过程，步骤。而面向对象强调的是对象，也就是干事的人。\nGo语言：面向对象语言特性 方法\n嵌入\n接口\n没有类\n支持类型。 特别是， 它支持structs。 Structs是用户定义的类型。 Struct类型(含方法)提供类似于其它语言中类的服务。\nStructs 一个struct定义一个状态。 这里有一个strudent struct。 它有一个Name属性和一个布尔类型的标志Real，告诉我们它是一个真实的strudent还是一个虚构的strudent。 Structs只保存状态，不保存行为。\ngo 1 2 3 4 type Creature struct { Name string Real bool } 为结构体添加方法 方法是对特定类型进行操作的函数。 它们有一个接收器条款，命令它们对什么样的类型可进行操作。 这里是一个Hello()方法，它可对student结构进行操作，并打印出它们的状态：\ngo 1 2 3 func (s Student) Hello() { fmt.Printf(\u0026#34;Name: \u0026#39;%s\u0026#39;, Real: %t\\n\u0026#34;, s.Name, s.Real) } func (s Student) func_name(){} 这是一个不太常见的语法，但是它非常的具体和清晰，不像this的隐喻性。\n一般在定义方法时，需要定义为结构体的指针，值类型的在修改结构体属性时，无法修改其内容\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package main import \u0026#34;fmt\u0026#34; type human struct { Name string Real bool } type Student struct { human Id int } func (h human) Hello() { fmt.","title":"Go面向对象"},{"content":"多路复用 Go语言中提供了一个关键字select，通过select可以监听channel上的数据流动。select的用法与switch语法类似，由select开始一个新的选择块，每个选择条件由case语句来描述。只不过，select的case有比较多的限制，其中最大的一条限制就是每个case语句里必须是一个IO操作。\nselect 语法如下：\ngo 1 2 3 4 5 6 7 8 select { case \u0026lt;-chan1: // 如果chan1成功读到数据，则进行该case处理语句 case chan2 \u0026lt;- 1: // 如果成功向chan2写入数据，则进行该case处理语句 default: // 如果上面都没有成功，则进入default处理流程 } 在一个select语句中，会按顺序从头至尾评估每一个发送和接收的语句；如果其中的任意一语句可以继续执行(即没有被阻塞)，那么就从那些可以执行的语句中任意选择一条来使用。如果没有任意一条语句可以执行(即所有的通道都被阻塞)，那么有两种可能的情况：⑴ 如果给出了default语句，那么就会执行default语句，同时程序的执行会从select语句后的语句中恢复。⑵ 如果没有default语句，那么select语句将被阻塞，直到至少有一个channel可以进行下去。\n在一般的业务场景下，select不会用default，当监听的流中再没有数据，IO操作就 会阻塞现象，如果使用了default，此时可以出让CPU时间片。如果使用了default 就形成了非阻塞状态，形成了忙轮训，会占用CPU、系统资源。\n阻塞与非阻塞使用场景\n阻塞： 如：在监听超时退出时，如果100秒内无操作，择退出，此时添加了default会形成忙轮训，超时监听变成了无效。 非阻塞： 如，在一个只有一个业务逻辑处理时，主进程控制进程的退出。此时可以使用default。 定时器 Go语言中定时器的使用有三个方法\ntime.Sleep() time.NewTimer() 返回一个时间的管道， time.C 读取管道的内容 time.After(5 * time.Second) 封装了time.NewTimer()，反回了一个 time.C的管道 示例\ngo 1 2 3 select { case \u0026lt;-time.After(time.Second * 10): } 锁和条件变量 Go语言中为了解决协程间同步问题，提供了标准库代码，包sync和sync/atomic中。\n互斥锁 互斥锁是传统并发编程对共享资源进行访问控制的主要手段，它由标准库sync中的Mutex结构体类型表示。sync.Mutex类型只有两个公开的指针方法，Lock和Unlock。Lock锁定当前的共享资源，Unlock进行解锁。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;runtime\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) var mutex sync.Mutex func print(str string) { mutex.Lock() // 添加互斥锁 defer mutex.Unlock() // 使用结束时解锁 for _, data := range str { // 迭代器 fmt.Printf(\u0026#34;%c\u0026#34;, data) time.Sleep(time.Second) // 放大协程竞争效果 } fmt.Println() } func main() { go print(\u0026#34;hello\u0026#34;) // main 中传参 go print(\u0026#34;world\u0026#34;) for { runtime.GC() } } 读写锁 读写锁的使用场景一般为读多写少，可以让多个读操作并发，同时读取，但是对于写操作是完全互斥的。也就是说，当一个goroutine进行写操作的时候，其他goroutine不能进行读写操作；当一个goroutine获取读锁之后，其他的goroutine获取写锁都会等待\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;math/rand\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) var count int // 全局变量count var rwlock sync.RWMutex // 全局读写锁 rwlock func read(n int) { for { rwlock.RLock() fmt.Printf(\u0026#34;reading goroutine %d ...\\n\u0026#34;, n) num := count fmt.Printf(\u0026#34;read goroutine %d finished，get number %d\\n\u0026#34;, n, num) rwlock.RUnlock() } } func write(n int) { for { rwlock.Lock() fmt.Printf(\u0026#34;writing goroutine %d ...\\n\u0026#34;, n) num := rand.Intn(1000) count = num fmt.Printf(\u0026#34;write goroutine %d finished，write number %d\\n\u0026#34;, n, num) rwlock.Unlock() } } func main() { for i := 0; i \u0026lt; 5; i++ { go read(i + 1) time.Sleep(time.Microsecond * 100) } for i := 0; i \u0026lt; 5; i++ { go write(i + 1) time.Sleep(time.Microsecond * 100) } for { } } 可以看出，读写锁控制下的多个写操作之间都是互斥的，并且写操作与读操作之间也都是互斥的。但是，多个读操作之间不存在互斥关系。\nGo语言中的死锁 死锁 deadlock 是指两个或两个以上的进程在执行过程中，由于竞争资源或者由于彼此通信而造成的一种阻塞的现象，若无外力作用，它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。\n单gorutine同时读写，写死锁 在一个gorutine中，当channel无缓冲，写阻塞，等待读取导致死锁\n解决，应该至少在2个gorutine进行channle通讯，或者使用缓冲区。\ngo 1 2 3 4 5 6 7 package main func main() { channel := make(chan int) channel \u0026lt;- 1 \u0026lt;-channel } 多gorutine使用一个channel通信，写先于读 代码顺序执行时，写操作阻塞，导致后面协程无法启动进行读操作，导致死锁\ngo 1 2 3 4 5 6 7 8 9 package main func main() { channel := make(chan int) channel \u0026lt;- 1 go func() { \u0026lt;-channel }() } 多channel交叉死锁 在goroutine中，多个goroutine使用多个channel互相等待对方写入，导致死锁\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package main func main() { channel1 := make(chan int) channel2 := make(chan int) go func() { select { case \u0026lt;-channel1: channel2 \u0026lt;- 1 } }() select { case \u0026lt;-channel2: channel1 \u0026lt;- 1 } } 隐性死锁 尽量不要将 互斥锁、读写锁 与 channel 混用情况下，让读先进行读时，因为没写入被阻塞，无法解除。写入时，因为没有读出被阻塞，锁无法解除，导致无数据输出，形成隐形死锁。此时编译器是不报错的。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; ) func main() { channel := make(chan int) var rwlock sync.RWMutex go func() { for { rwlock.Lock() channel \u0026lt;- 1 fmt.Println(\u0026#34;write\u0026#34;, 1) rwlock.Unlock() } }() go func() { for { rwlock.RLock() n := \u0026lt;-channel fmt.Println(n) rwlock.Unlock() } }() for { } } Context 上下文 context定义了上下文类型，该类型在API边界之间以及进程之间传递截止时间，取消信号和其他请求范围的值。当在对请求传入一个上下文，可以选择将其替换为使用WithCancel，WithDeadline，WithTimeout。在取消后，从该context处派生的所有子请求也会被取消。\nContext的结构体\nDeadline() 返回context的截止时间。 Done() 返回一个channle，当timeout或cancelfuc将会close(chan) Err() 返回错误，未关闭Done()返回nil，取消，返回 \u0026quot;context canceled\u0026quot;, Deadline返回超时 Value 返回值。 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 type Context interface { // Deadline returns the time when work done on behalf of this context // should be canceled. Deadline returns ok==false when no deadline is // set. Successive calls to Deadline return the same results. Deadline() (deadline time.Time, ok bool) // Done returns a channel that\u0026#39;s closed when work done on behalf of this // context should be canceled. Done may return nil if this context can // never be canceled. Successive calls to Done return the same value. // The close of the Done channel may happen asynchronously, // after the cancel function returns. // // WithCancel arranges for Done to be closed when cancel is called; // WithDeadline arranges for Done to be closed when the deadline // expires; WithTimeout arranges for Done to be closed when the timeout // elapses. // // Done is provided for use in select statements: // // // Stream generates values with DoSomething and sends them to out // // until DoSomething returns an error or ctx.Done is closed. // func Stream(ctx context.Context, out chan\u0026lt;- Value) error { // for { // v, err := DoSomething(ctx) // if err != nil { // return err // } // select { // case \u0026lt;-ctx.Done(): // return ctx.Err() // case out \u0026lt;- v: // } // } // } // // See https://blog.golang.org/pipelines for more examples of how to use // a Done channel for cancellation. Done() \u0026lt;-chan struct{} // If Done is not yet closed, Err returns nil. // If Done is closed, Err returns a non-nil error explaining why: // Canceled if the context was canceled // or DeadlineExceeded if the context\u0026#39;s deadline passed. // After Err returns a non-nil error, successive calls to Err return the same error. Err() error // Value returns the value associated with this context for key, or nil // if no value is associated with key. Successive calls to Value with // the same key returns the same result. // // Use context values only for request-scoped data that transits // processes and API boundaries, not for passing optional parameters to // functions. // // A key identifies a specific value in a Context. Functions that wish // to store values in Context typically allocate a key in a global // variable then use that key as the argument to context.WithValue and // Context.Value. A key can be any type that supports equality; // packages should define keys as an unexported type to avoid // collisions. // // Packages that define a Context key should provide type-safe accessors // for the values stored using that key: // // // Package user defines a User type that\u0026#39;s stored in Contexts. // package user // // import \u0026#34;context\u0026#34; // // // User is the type of value stored in the Contexts. // type User struct {...} // // // key is an unexported type for keys defined in this package. // // This prevents collisions with keys defined in other packages. // type key int // // // userKey is the key for user.User values in Contexts. It is // // unexported; clients use user.NewContext and user.FromContext // // instead of using this key directly. // var userKey key // // // NewContext returns a new Context that carries value u. // func NewContext(ctx context.Context, u *User) context.Context { // return context.WithValue(ctx, userKey, u) // } // // // FromContext returns the User value stored in ctx, if any. // func FromContext(ctx context.Context) (*User, bool) { // u, ok := ctx.Value(userKey).(*User) // return u, ok // } Value(key interface{}) interface{} } 演示使用可取消的上下文。可在函数结束时defer cancel() 防止goroutine的泄露。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func worker(ctx context.Context, name string) { n := 0 for { select { case \u0026lt;-ctx.Done(): fmt.Println(name, \u0026#34;去划水了\u0026#34;, n) return default: fmt.Println(name, \u0026#34;干活中\u0026#34;, n) time.Sleep(time.Second) } n++ } } func main() { ctx, cancel := context.WithCancel(context.Background()) for n := 0; n \u0026lt; 5; n++ { go worker(ctx, fmt.Sprintf(\u0026#34;worker%d\u0026#34;, n)) } \u0026lt;-time.After(time.Second * 5) cancel() for { } } 超时处理，WithTimeout 当时间到达设置的时间后退出，也可以使用cancelFunc()退出处理\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func worker(ctx context.Context, name string) { n := 0 for { select { case \u0026lt;-ctx.Done(): fmt.Println(name, \u0026#34;去划水了\u0026#34;, n) return default: fmt.Println(name, \u0026#34;干活中\u0026#34;, n) time.Sleep(time.Second) } n++ } } func main() { //ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) for n := 0; n \u0026lt; 2; n++ { go worker(ctx, fmt.Sprintf(\u0026#34;worker%d\u0026#34;, n)) } \u0026lt;-time.After(time.Second * 5) fmt.Println(\u0026#34;取消了\u0026#34;) cancel() } WithDeadline，在标准库中可以看出，实际上WithTimeout是封装了WithDeadline。其功能也是超时退出。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func worker(ctx context.Context, name string) { n := 0 for { select { case \u0026lt;-ctx.Done(): fmt.Println(name, \u0026#34;去划水了\u0026#34;, n) fmt.Println(ctx.Err()) return default: fmt.Println(name, \u0026#34;干活中\u0026#34;, n) time.Sleep(time.Second) } n++ } } func main() { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*3)) for n := 0; n \u0026lt; 2; n++ { go worker(ctx, fmt.Sprintf(\u0026#34;worker%d\u0026#34;, n)) } \u0026lt;-time.After(time.Second * 5) fmt.Println(\u0026#34;取消了\u0026#34;) cancel() } Context总结 Context是Go语言在1.7中加入标准库的，是作为Goroutine线程安全，防止线程泄露的上下文管理的操作。 context包的核心是Context结构体。 Context的常用方法为 WithTimeout() 与 WithCancel() Context在使用时，不要放在结构体内使用，要以函数的参数进行传递。 Context是线程安全的，可以在多个Goroutine传递，当对其取消操作时，所有Goroutine都执行取消操作。 ","permalink":"https://www.161616.top/go-goroutine-security/","summary":"多路复用 Go语言中提供了一个关键字select，通过select可以监听channel上的数据流动。select的用法与switch语法类似，由select开始一个新的选择块，每个选择条件由case语句来描述。只不过，select的case有比较多的限制，其中最大的一条限制就是每个case语句里必须是一个IO操作。\nselect 语法如下：\ngo 1 2 3 4 5 6 7 8 select { case \u0026lt;-chan1: // 如果chan1成功读到数据，则进行该case处理语句 case chan2 \u0026lt;- 1: // 如果成功向chan2写入数据，则进行该case处理语句 default: // 如果上面都没有成功，则进入default处理流程 } 在一个select语句中，会按顺序从头至尾评估每一个发送和接收的语句；如果其中的任意一语句可以继续执行(即没有被阻塞)，那么就从那些可以执行的语句中任意选择一条来使用。如果没有任意一条语句可以执行(即所有的通道都被阻塞)，那么有两种可能的情况：⑴ 如果给出了default语句，那么就会执行default语句，同时程序的执行会从select语句后的语句中恢复。⑵ 如果没有default语句，那么select语句将被阻塞，直到至少有一个channel可以进行下去。\n在一般的业务场景下，select不会用default，当监听的流中再没有数据，IO操作就 会阻塞现象，如果使用了default，此时可以出让CPU时间片。如果使用了default 就形成了非阻塞状态，形成了忙轮训，会占用CPU、系统资源。\n阻塞与非阻塞使用场景\n阻塞： 如：在监听超时退出时，如果100秒内无操作，择退出，此时添加了default会形成忙轮训，超时监听变成了无效。 非阻塞： 如，在一个只有一个业务逻辑处理时，主进程控制进程的退出。此时可以使用default。 定时器 Go语言中定时器的使用有三个方法\ntime.Sleep() time.NewTimer() 返回一个时间的管道， time.C 读取管道的内容 time.After(5 * time.Second) 封装了time.NewTimer()，反回了一个 time.C的管道 示例\ngo 1 2 3 select { case \u0026lt;-time.After(time.Second * 10): } 锁和条件变量 Go语言中为了解决协程间同步问题，提供了标准库代码，包sync和sync/atomic中。\n互斥锁 互斥锁是传统并发编程对共享资源进行访问控制的主要手段，它由标准库sync中的Mutex结构体类型表示。sync.Mutex类型只有两个公开的指针方法，Lock和Unlock。Lock锁定当前的共享资源，Unlock进行解锁。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;runtime\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) var mutex sync.","title":"Go协程安全"},{"content":" channel是Go语言中的一个核心数据类型，channel是一个数据类型，主要用来解决协程的同步问题以及协程之间数据共享（数据传递）的问题。在并发核心单元通过它就可以发送或者接收数据进行通讯，这在一定程度上又进一步降低了编程的难度。\ngoroutine运行在相同的内存地址空间，channel可以避开所有内存共享导致的坑；通道的通信方式保证了同步性。数据通过channel：同一时间只有一个协程可以访问数据：所以不会出现数据竞争，确保并发安全。\nchannel的定义 channel是对应make创建的底层数据结构的引用。 创建语法： make(chan Type, capacity)\ngo 1 2 3 4 5 6 7 channel := make(chan bool) //创建一个无缓冲的bool型Channel\u2028，等价于make(chan Type, 0) channel := make(chan bool, 1024) //创建一个有缓冲，切缓冲区为1024的bool型Channel\u2028channel \u0026lt;- x //向一个Channel发送一个值 \u0026lt;- channel //从一个Channel中接收一个值 x = \u0026lt;- channel //从Channel c接收一个值并将其存储到x中 x, ok = \u0026lt;- channel //从Channel接收一个值，如果channel关闭了或没有数据，那么ok将被置为false channel是一个引用类型，当复制一个channel或用于函数参数传递时，我们只是拷贝了一个channel引用，因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样，channel的零值（定义未初始化）也是nil。\n在默认情况下，channel接收和发送数据都是阻塞的，（channel \u0026lt;- 1，写端写数据，读端不在读。写端阻塞； str := \u0026lt;-channel 读端读数据， 同时写端不在写，读端阻塞。）除非另一端已经准备好，这样就使得goroutine同步变的更加的简单，而不需要显式的lock。\n示例\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;runtime\u0026#34; \u0026#34;time\u0026#34; ) var c = make(chan int32) func printstr(s string) { for _, value := range s { fmt.Printf(\u0026#34;写入%+q\\r\\n\u0026#34;, value) time.Sleep(time.Second) c \u0026lt;- value } } func main() { runtime.GOMAXPROCS(1) go func() { time.Sleep(time.Second) printstr(\u0026#34;hello\u0026#34;) }() go func() { for v := range c { fmt.Printf(\u0026#34;读取%+q\\r\\n\u0026#34;, v) } }() for { ; } } channel的缓冲 无缓冲的channel 无缓冲的channel unbuffered channel 是指在接收前没有能力保存任何值的通道。这种类型的channel 要求发送端和接收端同时准备好，才能完成发送和接收操作。否则，通道会导致先执行发送或接收操作的阻塞等待。顾又称为同步通信\n阻塞：由于某种原因数据没有到达，当前协程（线程）持续处于等待状态，直到条件满足，才接触阻塞。 同步：在两个或多个协程（线程）间，保持数据内容一致性的机制。 示例如上，写了没有读会导致阻塞，读了没有写会导致堵塞\n有缓冲的channel 有缓冲的通道（buffered channel）是一种在被接收前能存储一个或者多个数据值的通道。这种类型的channel并不强制要求goroutine之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也不同。\n只有channel通道中没有要接收的值时，接收动作才会阻塞。 只有通道没有可用缓冲区容纳被写入（发送）的值时，发送动作才会阻塞。 有缓冲的channel和无缓冲的channel之间的不同：无缓冲的channel保证进行发送和接收的 goroutine 会在同一时间进行数据交换；有缓冲的channel没有这种保证。\n示例\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;runtime\u0026#34; \u0026#34;time\u0026#34; ) var c = make(chan int32, 10) func printstr(s string) { for _, value := range s { fmt.Printf(\u0026#34;写入%+q\\r\\n\u0026#34;, value) c \u0026lt;- value } } func main() { runtime.GOMAXPROCS(1) go func() { printstr(\u0026#34;hello\u0026#34;) }() go func() { time.Sleep(time.Second * 2) fmt.Println(\u0026#34;读通道开始读取数据\u0026#34;) for v := range c { fmt.Printf(\u0026#34;读取%+q\\r\\n\u0026#34;, v) } }() for { } } 结果可以看出，如果给定了一个缓冲区容量，channel就是异步的。只要缓冲区有未使用空间用于发送数据，或还包含可以接收的数据，那么其通信就会无阻塞地进行。\nchannel的关闭 当发送的一端没有更多的数据发送到channel的话，需要使接收端也能及时知道channel中没有多余的数据可以接收。因此可以通过 close()函数来关闭channel的实现。\n示例\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;runtime\u0026#34; \u0026#34;time\u0026#34; ) var c = make(chan int32, 10) func printstr(s string) { for _, value := range s { fmt.Printf(\u0026#34;写入%+q\\r\\n\u0026#34;, value) c \u0026lt;- value } close(c) } func main() { runtime.GOMAXPROCS(1) go func() { printstr(\u0026#34;hello\u0026#34;) }() time.Sleep(time.Second * 2) fmt.Println(\u0026#34;读通道开始读取数据\u0026#34;) for { if char, ok := \u0026lt;-c; ok { fmt.Printf(\u0026#34;读取%+q\\r\\n\u0026#34;, char) } else { break } } } 提示\nchannel不像文件一样需要经常去关闭，只有当你确实没有任何发送数据了，或者你想显式的结束range循环之类的，才去关闭channel； 关闭channel后，无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值)； 关闭channel后，可以继续从channel接收数据（读取到的数据为channel类型的默认值，如int默认值0 string默认值\u0026quot;\u0026quot;）； 对于nil channel，无论收发都会被阻塞。 缓冲channel 和 非缓冲channel的区别 缓冲channel的创建方式为make(chan TYPE,CAPCTIY)，非缓冲channel的创建方式为make(chan TYPE) 缓冲channel的通信方式为同步通信，非缓冲channel的通信方式为异步通信 单项channel及应用 默认情况下，channel是双向的，既可以往里面发送数据也可以接收数据。但是，常将channel作为参数进行传递而只希望对方是单向使用的，要么只让它发送数据，要么只让它接收数据，这时候可以指定通道的方向。\n单项channel的声明 双向channel ch = make(chan int) 单向写channel: var ch chan \u0026lt;- int ch = make(chan \u0026lt;- int) 单向读channel:\tvar ch \u0026lt;- chan int ch = make(\u0026lt;-chan int) 可以将 channel 隐式转换为单向队列，只收或只发，不能将单向 channel 转换为普通 channel，示例：\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;runtime\u0026#34; ) var c = make(chan string, 10) func read(c \u0026lt;-chan string) { fmt.Println(\u0026#34;读通道开始读取数据\u0026#34;) for { if char, ok := \u0026lt;-c; ok { fmt.Printf(\u0026#34;读取%s\\r\\n\u0026#34;, char) } else { break } } } func write(ch chan\u0026lt;- string, str []string) { defer close(ch) for _, value := range str { fmt.Printf(\u0026#34;写入%+q\\r\\n\u0026#34;, value) ch \u0026lt;- value } } func main() { runtime.GOMAXPROCS(3) go write(c, []string{\u0026#34;h\u0026#34;, \u0026#34;e\u0026#34;, \u0026#34;l\u0026#34;, \u0026#34;l\u0026#34;, \u0026#34;o\u0026#34;}) read(c) } ","permalink":"https://www.161616.top/go-goroutine-communication/","summary":"channel是Go语言中的一个核心数据类型，channel是一个数据类型，主要用来解决协程的同步问题以及协程之间数据共享（数据传递）的问题。在并发核心单元通过它就可以发送或者接收数据进行通讯，这在一定程度上又进一步降低了编程的难度。\ngoroutine运行在相同的内存地址空间，channel可以避开所有内存共享导致的坑；通道的通信方式保证了同步性。数据通过channel：同一时间只有一个协程可以访问数据：所以不会出现数据竞争，确保并发安全。\nchannel的定义 channel是对应make创建的底层数据结构的引用。 创建语法： make(chan Type, capacity)\ngo 1 2 3 4 5 6 7 channel := make(chan bool) //创建一个无缓冲的bool型Channel\u2028，等价于make(chan Type, 0) channel := make(chan bool, 1024) //创建一个有缓冲，切缓冲区为1024的bool型Channel\u2028channel \u0026lt;- x //向一个Channel发送一个值 \u0026lt;- channel //从一个Channel中接收一个值 x = \u0026lt;- channel //从Channel c接收一个值并将其存储到x中 x, ok = \u0026lt;- channel //从Channel接收一个值，如果channel关闭了或没有数据，那么ok将被置为false channel是一个引用类型，当复制一个channel或用于函数参数传递时，我们只是拷贝了一个channel引用，因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样，channel的零值（定义未初始化）也是nil。\n在默认情况下，channel接收和发送数据都是阻塞的，（channel \u0026lt;- 1，写端写数据，读端不在读。写端阻塞； str := \u0026lt;-channel 读端读数据， 同时写端不在写，读端阻塞。）除非另一端已经准备好，这样就使得goroutine同步变的更加的简单，而不需要显式的lock。\n示例\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;runtime\u0026#34; \u0026#34;time\u0026#34; ) var c = make(chan int32) func printstr(s string) { for _, value := range s { fmt.","title":"Go协程通讯"},{"content":"并行和并发 并发编程是指在一台处理器上“同时”处理多个任务。\n宏观并发：在一段时间内，有多个程序在同时运行。\n微观并发：在同一时刻只能有一条指令执行，但多个程序指令被快速的轮换执行，使得在宏观上具有多个进程同时执行的效果，但在微观上并不是同时执行的，只是把时间分成若干段，使多个程序快速交替的执行。\n并行 parallel：同一时刻，多条指令在多个处理器上同时执行。\n并发 concurrency：在同一时刻只能有一条指令执行，但多个进程指令被快速的轮换执行，使得在宏观上具有多个进程同时执行的效果，但在微观上并不是同时执行的，只是把时间分成若干段，通过cpu时间片轮转使多个进程快速交替的执行。\n通俗来讲，并行是两组队列同时使用一个进程；并发是两个队列分别交替使用两个进程\n进程并发 程序，以Go语言为例，是指编译好的二进制文件，在磁盘上，不占用系统资源(cpu、内存、打开的文件、设备、锁\u0026hellip;.)\n进程，是一个抽象的概念，与操作系统原理联系紧密。以Go语言为例，将编译好的程序运行起来，在内存空间中形成一个独立的内存体，内存体有自己的独立空间，上级挂靠单位是操作系统。\n进程是操作系统进行资源分配和调度的一个独立单位，一般由程序，数据集合和进程控制块三部分组成。\n程序：描述进程完成的功能，是控制进程执行的指令集； 数据集合：程序在执行时所需要的数据和工作区； 程序控制块PCB：Program Control Block，包含进程的描述信息和控制信息，是进程存在的唯一标志。 进程是活跃的程序，占用系统资源。在内存中执行。同一个程序也可以加载为不同的进程(彼此之间互不影响)\n进程状态 进程基本的状态有5种。分别为初始态，就绪态，运行态，挂起态与终止态。其中初始态为进程准备阶段，常与就绪态结合来看。\n线程的任务调度 大部分操作系统的任务调度是采用时间片轮转的抢占式调度方式。\n时间片轮转是指，在一个进程中，当线程任务执行几毫秒后，由操作系统内核进行调度，通过硬件计数器终端处理器，让线程强行暂停，并将该线程的寄存器放入内存中，通过查看线程列表决定接下来执行哪一个线程，并从内存中恢复该线程的寄存器，最后恢复该线程的执行，从而去执行下一个任务。\n在时间片轮转中，任务执行那段时间叫做时间片，任务正在执行时的状态叫运行状态，被暂停的线程任务状态叫做就绪状态，意为等待下一个属于它的时间片的到来。\n由于CPU的执行效率非常高，（i5 6600 约200亿/秒，奔腾4 约13亿/秒）CPU preformance 时间片非常短，在各个任务之间快速地切换，给人的感觉就是多个任务在“同时进行”，这也就是我们所说的并发。多任务运行过程的示意图如下：\n进程实现并发时会出现的问题呢 孤儿进程: 父进程先于子进程结束，则子进程成为孤儿进程，子进程的父进程成为init进程，称为init进程领养孤儿进程。\n僵尸进程: 进程终止，父进程尚未回收，子进程残留资源（PCB）存放于内核中，变成僵尸（Zombie）进程。\n线程并发 在早期操作系统当中，没有线程的概念，进程是最小分配资源与执行单位，可以看做是一个进程中只有一个线程，故进程即线程。所以线程LWP被称为：：Lightweight process，轻量级的进程，是程序执行中一个单一的顺序控制流程，在Linux操作系统下，线程的本质仍是进程。\n线程有独立的PCB，但没有独立的地址空间，各个线程之间共享程序的内存空间。\n进程和线程的区别 进程：最小分配资源单位，可看成是只有一个线程的进程。 线程：最小的执行单位 一个进程由一个或多个线程组成 进程之间相互独立，同一进程下的各个线程之间共享程序的内存空间 协程并发 协程 coroutines，是一种基于线程之上，但又比线程更加轻量级的存在，这种由程序来管理的轻量级线程叫做『用户空间线程』，具有对内核来说不可见的特性。\n多数语言在语法层面并不直接支持协程，而是通过库的方式支持，但用库的方式支持的功能也并不完整，比如仅仅提供协程的创建、销毁与切换等能力。如果在这样的轻量级线程中调用一个同步 IO 操作，比如网络通信、本地文件读写，都会阻塞其他的并发执行轻量级线程，从而无法真正达到轻量级线程本身期望达到的目标。\n协程和线程的区别 占用资源：线程，初始单位为1MB,固定不可变；协程初始一般为 2KB，可随需要而增大。 调度：线程，由操作系统内核完成，协程，由用户完成。 性能： 线程，占用资源高，频繁创建销毁带来性能问题。占用资源小，不会带来严重的性能问题。 数据： 线程，多线程需要锁机制确保数据一致性和可见性；而线程因为只有一个进程，不存在同时读/写冲突，协程中控制共享数据不用加锁，顾执行效率较线程高。 Go并发 goroutine Go语言在语言级别支持协程，叫goroutine。Go语言标准库提供的所有系统调用操作（包括所有同步IO操作），都会出让CPU给其他goroutine。这种轻量级线程的切换管理不依赖于系统的线程和进程，也不需要依赖于CPU的核心数量。\nGo语言为并发编程而内置的上层API基于顺序通信进程模型CSP(communicating sequential processes)。这就意味着显式锁都是可以避免的，因为Go通过相对安全的通道发送和接受数据以实现同步，这大大地简化了并发程序的编写。\nGo语言中的并发程序主要使用两种手段来实现。goroutine和channel。\n什么是goroutine Go语言作者Rob Pike说， “Goroutine是一个与其他goroutines并发运行在同一地址空间的Go函数或方法。一个运行的程序由一个或更多个goroutine组成。它与线程、协程、进程等不同。它是一个goroutine*。\ngoroutine是Go并行设计的核心。goroutine说到底其实就是协程，它比线程更小，十几个goroutine可能体现在底层就是五六个线程，Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB)，当然会根据相应的数据伸缩。也正因为如此，可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。\nMPG模型 M 操作系统的线程抽象，一个M直接关联了一个内核线程；代表着真正执行计算的资源。 P Processor，提供相关执行环境的上下文，处理用户级代码逻辑的处理器，P的数量由用户设置的GOMAXPROCS决定，但是不论GOMAXPROCS设置为多大，P的数量最大为256。 G Goroutine，G并非执行体，每个G需要绑定到P才能被调度执行。\n在操作系统每一个线程都有一个固定大小的块来做栈，这个栈会用来存储当前正在被调用或挂起(指在调用其它函数时)的函数的内部变量。\n在Go语言中，每一个goroutine是一个独立的执行单元，goroutine的栈采取了动态扩容方式， 初始时仅为2KB，随着任务执行按需增长，最大可达1GB（64最大1G，32位最大256M）\n上图，图中P正在执行的Goroutine为蓝色的，处于待执行状态的Goroutine为灰色的，灰色的Goroutine形成了一个队列runqueues。\n在这里，当一个P关联多个G时，就会处理G的执行顺序，就是并发，当一个P在执行一个协程工作时，其他的会在等待，当正在执行的协程遇到阻塞情况，例如IO操作等，go的处理器就会去执行其他的协程，因为对于类似IO的操作，处理器不知道你需要多久才能执行结束，所以他不回去等你执行完。\nRreferences go语言并发编程 进程和线程 a groutine之间的调度\n","permalink":"https://www.161616.top/go-goroutine/","summary":"并行和并发 并发编程是指在一台处理器上“同时”处理多个任务。\n宏观并发：在一段时间内，有多个程序在同时运行。\n微观并发：在同一时刻只能有一条指令执行，但多个程序指令被快速的轮换执行，使得在宏观上具有多个进程同时执行的效果，但在微观上并不是同时执行的，只是把时间分成若干段，使多个程序快速交替的执行。\n并行 parallel：同一时刻，多条指令在多个处理器上同时执行。\n并发 concurrency：在同一时刻只能有一条指令执行，但多个进程指令被快速的轮换执行，使得在宏观上具有多个进程同时执行的效果，但在微观上并不是同时执行的，只是把时间分成若干段，通过cpu时间片轮转使多个进程快速交替的执行。\n通俗来讲，并行是两组队列同时使用一个进程；并发是两个队列分别交替使用两个进程\n进程并发 程序，以Go语言为例，是指编译好的二进制文件，在磁盘上，不占用系统资源(cpu、内存、打开的文件、设备、锁\u0026hellip;.)\n进程，是一个抽象的概念，与操作系统原理联系紧密。以Go语言为例，将编译好的程序运行起来，在内存空间中形成一个独立的内存体，内存体有自己的独立空间，上级挂靠单位是操作系统。\n进程是操作系统进行资源分配和调度的一个独立单位，一般由程序，数据集合和进程控制块三部分组成。\n程序：描述进程完成的功能，是控制进程执行的指令集； 数据集合：程序在执行时所需要的数据和工作区； 程序控制块PCB：Program Control Block，包含进程的描述信息和控制信息，是进程存在的唯一标志。 进程是活跃的程序，占用系统资源。在内存中执行。同一个程序也可以加载为不同的进程(彼此之间互不影响)\n进程状态 进程基本的状态有5种。分别为初始态，就绪态，运行态，挂起态与终止态。其中初始态为进程准备阶段，常与就绪态结合来看。\n线程的任务调度 大部分操作系统的任务调度是采用时间片轮转的抢占式调度方式。\n时间片轮转是指，在一个进程中，当线程任务执行几毫秒后，由操作系统内核进行调度，通过硬件计数器终端处理器，让线程强行暂停，并将该线程的寄存器放入内存中，通过查看线程列表决定接下来执行哪一个线程，并从内存中恢复该线程的寄存器，最后恢复该线程的执行，从而去执行下一个任务。\n在时间片轮转中，任务执行那段时间叫做时间片，任务正在执行时的状态叫运行状态，被暂停的线程任务状态叫做就绪状态，意为等待下一个属于它的时间片的到来。\n由于CPU的执行效率非常高，（i5 6600 约200亿/秒，奔腾4 约13亿/秒）CPU preformance 时间片非常短，在各个任务之间快速地切换，给人的感觉就是多个任务在“同时进行”，这也就是我们所说的并发。多任务运行过程的示意图如下：\n进程实现并发时会出现的问题呢 孤儿进程: 父进程先于子进程结束，则子进程成为孤儿进程，子进程的父进程成为init进程，称为init进程领养孤儿进程。\n僵尸进程: 进程终止，父进程尚未回收，子进程残留资源（PCB）存放于内核中，变成僵尸（Zombie）进程。\n线程并发 在早期操作系统当中，没有线程的概念，进程是最小分配资源与执行单位，可以看做是一个进程中只有一个线程，故进程即线程。所以线程LWP被称为：：Lightweight process，轻量级的进程，是程序执行中一个单一的顺序控制流程，在Linux操作系统下，线程的本质仍是进程。\n线程有独立的PCB，但没有独立的地址空间，各个线程之间共享程序的内存空间。\n进程和线程的区别 进程：最小分配资源单位，可看成是只有一个线程的进程。 线程：最小的执行单位 一个进程由一个或多个线程组成 进程之间相互独立，同一进程下的各个线程之间共享程序的内存空间 协程并发 协程 coroutines，是一种基于线程之上，但又比线程更加轻量级的存在，这种由程序来管理的轻量级线程叫做『用户空间线程』，具有对内核来说不可见的特性。\n多数语言在语法层面并不直接支持协程，而是通过库的方式支持，但用库的方式支持的功能也并不完整，比如仅仅提供协程的创建、销毁与切换等能力。如果在这样的轻量级线程中调用一个同步 IO 操作，比如网络通信、本地文件读写，都会阻塞其他的并发执行轻量级线程，从而无法真正达到轻量级线程本身期望达到的目标。\n协程和线程的区别 占用资源：线程，初始单位为1MB,固定不可变；协程初始一般为 2KB，可随需要而增大。 调度：线程，由操作系统内核完成，协程，由用户完成。 性能： 线程，占用资源高，频繁创建销毁带来性能问题。占用资源小，不会带来严重的性能问题。 数据： 线程，多线程需要锁机制确保数据一致性和可见性；而线程因为只有一个进程，不存在同时读/写冲突，协程中控制共享数据不用加锁，顾执行效率较线程高。 Go并发 goroutine Go语言在语言级别支持协程，叫goroutine。Go语言标准库提供的所有系统调用操作（包括所有同步IO操作），都会出让CPU给其他goroutine。这种轻量级线程的切换管理不依赖于系统的线程和进程，也不需要依赖于CPU的核心数量。\nGo语言为并发编程而内置的上层API基于顺序通信进程模型CSP(communicating sequential processes)。这就意味着显式锁都是可以避免的，因为Go通过相对安全的通道发送和接受数据以实现同步，这大大地简化了并发程序的编写。\nGo语言中的并发程序主要使用两种手段来实现。goroutine和channel。\n什么是goroutine Go语言作者Rob Pike说， “Goroutine是一个与其他goroutines并发运行在同一地址空间的Go函数或方法。一个运行的程序由一个或更多个goroutine组成。它与线程、协程、进程等不同。它是一个goroutine*。","title":"go语言的并发编程gorouting"},{"content":"string in mutual conversion go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // int to int64 m := int64(n) // int64 to int n := int(m) // string to int int,err := strconv.Atoi(string) // string to int64 int64, err := strconv.ParseInt(string, 10, 64) // int to string string := strconv.Itoa(int) // int64 to string string := strconv.FormatInt(int64,10) // custom type string to string // useful link // https://stackoverflow.com/questions/45891600/converting-a-custom-type-to-string-in-go type CustomType string Foobar CustomType = \u0026#34;somestring\u0026#34; // error var a string a = Foobar // correct var a string a = string(Foobar) slice to struct Question: in golang how to convert slice to struct\nscene 1：use reflect convert slice to struct go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 func SliceToStruct(array interface{}) (forwardPort *ForwardPort, err error) { forwardPort = \u0026amp;ForwardPort{} valueOf := reflect.ValueOf(forwardPort) if valueOf.Kind() != reflect.Ptr { return nil, errors.New(\u0026#34;must ptr\u0026#34;) } valueOf = valueOf.Elem() if valueOf.Kind() != reflect.Struct { return nil, errors.New(\u0026#34;must struct\u0026#34;) } switch array.(type) { case []string: arrayImplement := array.([]string) for i := 0; i \u0026lt; valueOf.NumField(); i++ { if i \u0026gt;= len(arrayImplement) { break } val := arrayImplement[i] if val != \u0026#34;\u0026#34; \u0026amp;\u0026amp; reflect.ValueOf(val).Kind() == valueOf.Field(i).Kind() { valueOf.Field(i).Set(reflect.ValueOf(val)) } } case []interface{}: arrayImplement := array.([]interface{}) for i := 0; i \u0026lt; valueOf.NumField(); i++ { if i \u0026gt;= len(arrayImplement) { break } val := arrayImplement[i] if val != \u0026#34;\u0026#34; \u0026amp;\u0026amp; reflect.ValueOf(val).Kind() == valueOf.Field(i).Kind() { valueOf.Field(i).Set(reflect.ValueOf(val)) } } } return forwardPort, nil } struct to anything https://github.com/fatih/structs\nbyte json to map json实例如下所示，要求转换结果是需要data，这个是haproxy dataplane api的数据结构\njson 1 2 3 4 5 6 7 8 9 10 { \u0026#34;_version\u0026#34;: 14, \u0026#34;data\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;http\u0026#34;, \u0026#34;address\u0026#34;: \u0026#34;127.0.0.1\u0026#34;, \u0026#34;port\u0026#34;: 80 } ] } 此时无需定义一个结构体，使用 map即可完成\ngo 1 2 3 4 5 // frist, define a map struct bindList := map[string][]models.Bind{} // convert with json.Unmarshal json.Unmarshal(resp, \u0026amp;bindList) Notes：这里不要去判断error，因为\u0026quot;_version\u0026quot; 字段是一个int类型，必然是 != nil ，转换时正确格式的会被转换，错误格式则被忽略报错了\n","permalink":"https://www.161616.top/goskill-golang-type-convert/","summary":"string in mutual conversion go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // int to int64 m := int64(n) // int64 to int n := int(m) // string to int int,err := strconv.Atoi(string) // string to int64 int64, err := strconv.ParseInt(string, 10, 64) // int to string string := strconv.","title":"Go语言数据类型转换"},{"content":"什么是信号 在计算机科学中，信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制，用来提醒进程一个事件已经发生。\n当一个信号发送给一个进程，操作系统中断了进程正常的控制流程，如果进程定义了对信号的处理，此时，程序将进入捕获到的信号对应的处理函数，否则执行默认的处理函数。\nLinux中信号的介绍 在Linux系统共定义了64种信号，分为两大类：实时信号与非实时信号，1-31为非实时，32-64种为实时信号。\n非实时信号： 也称为不可靠信号，为早期Linux所支持的信号，不支持排队，信号可能会丢失, 比如发送多次相同的信号, 进程只能收到一次. 信号值取值区间为1~31； 实时信号： 也称为可靠信号，支持排队, 信号不会丢失, 发多少次, 就可以收到多少次. 信号值取值区间为32~64 Linux操作系统中，在终端上执行 kill -l 便可看到系统定义的所有信号\n信号表 POSIX.1-1990标准信号 此表参考自：POSIX信号\n信号 值 动作 说明 SIGHUP 1 Term 终端控制进程结束(终端连接断开) SIGINT 2 Term 用户发送INTR字符(Ctrl+C)触发 SIGQUIT 3 Core 用户发送QUIT字符(Ctrl+/)触发 SIGILL 4 Core 非法指令(程序错误、试图执行数据段、栈溢出等) SIGABRT 6 Core 调用abort函数触发 SIGFPE 8 Core 算术运行错误(浮点运算错误、除数为零等) SIGKILL 9 Term 无条件结束程序(不能被捕获、阻塞或忽略) SIGSEGV 11 Core 无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作) SIGPIPE 13 Term 消息管道损坏(FIFO/Socket通信时，管道未打开而进行写操作) SIGALRM 14 Term 时钟定时信号 SIGTERM 15 Term 结束程序(可以被捕获、阻塞或忽略) SIGUSR1 30,10,16 Term 用户保留 SIGUSR2 31,12,17 Term 用户保留 SIGCHLD 20,17,18 Ign 子进程结束(由父进程接收) SIGCONT 19,18,25 Cont 继续执行已经停止的进程(不能被阻塞) SIGSTOP 17,19,23 Stop 停止进程(不能被捕获、阻塞或忽略) SIGTSTP 18,20,24 Stop 停止进程(可以被捕获、阻塞或忽略) SIGTTIN 21,21,26 Stop 后台程序从终端中读取数据时触发 SIGTTOU 22,22,27 Stop 后台程序向终端中写数据时触发 更多的信号说明请查阅man7\n此表的操作为每个信号的默认配置，如下所示\n动作 说明 Term 默认操作是，终止进程。 Ign 默认操作是，忽略信号。 Core 默认操作是，终止该进程并核心转储 Stop 默认操作是，停止进程。 Cont 默认操作是，如果当前已停止，则继续该进程。 信号的产生 信号是事件发生时对进程的通知机制。信号中断与硬件中断的相似之处在于打断了程序执行的正常流程。\n信号事件的来源分为软件信号和硬件信号：\n硬件信号： 用户输入：比如在终端上按下组合键ctrl+C，产生SIGINT信号；硬件异常：CPU检测到内存非法访问等异常，通知内核生成相应信号，并发送给发生事件的进程； 软件信号： 通过系统调用： 如，发送signal信号：kill，raise等。 发送的信号 Ctrl-C 发送 INT signal (SIGINT)，通常导致进程结束 Ctrl-Z 发送 TSTP signal (SIGTSTP); 通常导致进程挂起(suspend) Ctrl-\\ 发送 QUIT signal (SIGQUIT); 通常导致进程结束 和 dump core. 信号的处理 内核处理进程收到的signal是在当前进程的上下文，故进程必须是Running状态。当进程唤醒或者调度后获取CPU，则会从内核态转到用户态时检测是否有signal等待处理，处理完，进程会把相应的未决信号从链表中去掉。\nsignal信号处理时机： 内核 ==\u0026gt; 信号处理 ==\u0026gt; 用户\n内核态：在内核态，signal信号不起作用； signal信号处理: 在用户态，signal所有未被屏蔽的信号都处理完毕；当屏蔽信号，取消屏蔽时，会在下一次内核转用户态的过程中执行； 信号处理方式 进程对信号的处理方式有3种：\n默认 接收到信号后按默认的行为处理该信号。 这种方式为多数应用采取的处理方式。 自定义处理 用自定义的信号处理函数来执行特定的动作 忽略忽略信号 接收到信号后不做任何反应。 对信号的处理动作：\nTerm： 中止进程 Ign： 忽略信号 Core： 中止进程并保存内存信息 Stop： 停止进程 Cont： 继续运行进程 Linux信号命令 kill kill命令用来终止指定的进程, 对于一个后台进程就须用kill命令来终止，我们就需要先使用ps/pidof/pstree/top等工具获取进程PID，然后使用kill命令来杀掉该进程。\n命令格式 kill[参数] [进程id]\n命令参数 -l 信号，若果不加信号的编号参数，则使用“-l”参数会列出全部的信号名称 -a 当处理当前进程时，不限制命令名和进程号的对应关系 -p 指定kill 命令只打印相关进程的进程号，而不发送任何信号 -s 指定发送信号 -u 指定用户\nkillall Linux系统中的killall用于杀死指定名字的进程（kill processes by name）。我们可以使用kill命令杀死指定进程PID的进程，如果要找到我们需要杀死的进程，我们还需要在之前使用ps等命令再配合grep来查找进程，而killall把这两个过程合二为一，是一个很好用的命令。\n命令格式 killall[参数] [进程名]\n命令参数 -I 忽略小写 -a 当处理当前进程时，不限制命令名和进程号的对应关系 -i 交互模式，杀死进程前先询问用户 -s 发送指定的信号 -w 等待进程死亡 -e 要求匹配进程名称\nPKILL pkill 与 killall 使用方法类似，用于杀死指定名称的进程\nGo语言中的Signal的使用 在Go语言中，处理信号仅需要3个步骤即可完成对信号的处理\n信号的接收： signalChan := make(chan os.Signal,1) 信号的监听捕获： signal.Notify(signalChan) 信号的触发： signal := \u0026lt;-signalChan 注意事项：\nSIGKILL kill -9和SIGSTOP kill -19 信号可能不会被Notify方法捕获，因此无法处理这些信号。 如果在Notify方法中没有指定信号作为参数，那么该方法将捕获所有的信号。 在Go语言中的Signal的处理 在某些场景下，如，在大量并发及，批量处理未完成时，此时需要在Go程序中处理Signal信号，比如收到SIGTERM信号后优雅的关闭程序。\n实例：在一个计算场景下，有5个goroutine在处理业务，当收到 kill -15时计算完成后退出程序， kill -4不做处理。\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;os/signal\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;syscall\u0026#34; \u0026#34;time\u0026#34; ) var wg sync.WaitGroup func exitProcess() { fmt.Println(\u0026#34;等待进程完成\u0026#34;) wg.Wait() fmt.Println(\u0026#34;进程退出\u0026#34;) } func process(n int) { i := n for { fmt.Println(\u0026#34;process\u0026#34;, n, \u0026#34;:\u0026#34;, i) if i \u0026gt; 100 { break } time.Sleep(time.Second) i++ } fmt.Println(\u0026#34;process\u0026#34;, n, \u0026#34;finnshed\u0026#34;) defer wg.Done() } func main() { signals := make(chan os.Signal, 1) done := make(chan bool, 1) signal.Notify(signals, syscall.SIGILL, syscall.SIGTERM) go func() { for signal := range signals { switch signal { case syscall.SIGTERM, syscall.SIGQUIT: fmt.Println(\u0026#34;kill -15 进程退出\u0026#34;) exitProcess() case syscall.SIGILL: fmt.Println(\u0026#34;kill -4\u0026#34;) } } done \u0026lt;- true }() wg.Add(5) for n := 0; n \u0026lt; 10; n++ { go process(n) } fmt.Println(\u0026#34;waiting signal...\u0026#34;) wg.Wait() fmt.Println(\u0026#34;exiting\u0026#34;) } 收到kill -4 信号打印kill -4\n收到kill -15 信号后，带程序处理完成后退出\n","permalink":"https://www.161616.top/go-signal/","summary":"什么是信号 在计算机科学中，信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制，用来提醒进程一个事件已经发生。\n当一个信号发送给一个进程，操作系统中断了进程正常的控制流程，如果进程定义了对信号的处理，此时，程序将进入捕获到的信号对应的处理函数，否则执行默认的处理函数。\nLinux中信号的介绍 在Linux系统共定义了64种信号，分为两大类：实时信号与非实时信号，1-31为非实时，32-64种为实时信号。\n非实时信号： 也称为不可靠信号，为早期Linux所支持的信号，不支持排队，信号可能会丢失, 比如发送多次相同的信号, 进程只能收到一次. 信号值取值区间为1~31； 实时信号： 也称为可靠信号，支持排队, 信号不会丢失, 发多少次, 就可以收到多少次. 信号值取值区间为32~64 Linux操作系统中，在终端上执行 kill -l 便可看到系统定义的所有信号\n信号表 POSIX.1-1990标准信号 此表参考自：POSIX信号\n信号 值 动作 说明 SIGHUP 1 Term 终端控制进程结束(终端连接断开) SIGINT 2 Term 用户发送INTR字符(Ctrl+C)触发 SIGQUIT 3 Core 用户发送QUIT字符(Ctrl+/)触发 SIGILL 4 Core 非法指令(程序错误、试图执行数据段、栈溢出等) SIGABRT 6 Core 调用abort函数触发 SIGFPE 8 Core 算术运行错误(浮点运算错误、除数为零等) SIGKILL 9 Term 无条件结束程序(不能被捕获、阻塞或忽略) SIGSEGV 11 Core 无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作) SIGPIPE 13 Term 消息管道损坏(FIFO/Socket通信时，管道未打开而进行写操作) SIGALRM 14 Term 时钟定时信号 SIGTERM 15 Term 结束程序(可以被捕获、阻塞或忽略) SIGUSR1 30,10,16 Term 用户保留 SIGUSR2 31,12,17 Term 用户保留 SIGCHLD 20,17,18 Ign 子进程结束(由父进程接收) SIGCONT 19,18,25 Cont 继续执行已经停止的进程(不能被阻塞) SIGSTOP 17,19,23 Stop 停止进程(不能被捕获、阻塞或忽略) SIGTSTP 18,20,24 Stop 停止进程(可以被捕获、阻塞或忽略) SIGTTIN 21,21,26 Stop 后台程序从终端中读取数据时触发 SIGTTOU 22,22,27 Stop 后台程序向终端中写数据时触发 更多的信号说明请查阅man7","title":"Go中的signal处理"},{"content":"pipline demo https://jenkins.io/zh/doc/book/pipeline/syntax/ git 插件 https://jenkins.io/doc/pipeline/steps/git/ pipline pipeline{\ragent any\rstages{\rstage(\u0026#34;build\u0026#34;){\rsteps{\recho \u0026#34;11111\u0026#34;\r}\r}\r}\r} pipeline总体介绍 基本结构\n以下每一个部分都是必须的，少一个Jenkins都会报错\npipline pipeline{\ragent any\rstages{\rstage(\u0026#34;build\u0026#34;){\rsteps{\recho \u0026#34;hellp\u0026#34;\r}\r}\r}\r} pipeline 代表整个流水线，包含整条流水线的逻辑 stage 阶段，代表流水线的阶段，每个阶段都必须有名称。 stages 流水线中多个stage的容器，stages部分至少包含一个stage. steps 代表stage中的一个活多个具体步骤的容器，steps部分至少包含一个步骤 agent 制定流水线的执行位置，流水线中每个阶段都必须在某个地方执行（master节点/slave节点/物理机/虚拟机/docker容器），agent部分指定具体在哪里执行。agent { label '***-slave'} 可选步骤\npost 包含的是在整个pipeline或stage完成后的附件条件\nalways 论Pipeline运行的完成状态如何都会执行这段代码 changes 只有当前Pipeline运行的状态与先前完成的Pipeline的状态不同时，才能触发运行。 failure 当前状态为失败时执行 success 当前完成状态为成功时执行 demo\n使用${test}，可以引入自定义变量\npipeline post {\ralways {\rscript {\rallure includeProperties: false, jdk: \u0026#39;\u0026#39;,report: \u0026#39;jenkins-allure-report\u0026#39;, results: [[path: \u0026#39;allure-results\u0026#39;]] }\r}\rfailure {\rscript {\rif (gitpuller == \u0026#39;noerr\u0026#39;) {\rmail to: \u0026#34;${email_list}\u0026#34;,\rsubject: \u0026#34;[jenkins Build Notification] ${JOB_NAME} - Build # ${BUILD_NUMBER} 构建失败\u0026#34;,\rbody: \u0026#34;\u0026#39;${env.JOB_NAME}\u0026#39; (${env.BUILD_NUMBER}) 执行失败\\n请及时前往 ${env.BUILD_URL} 进行查看\u0026#34;\r} else {\recho \u0026#39;scm pull err ignore send mail\u0026#39;\r}\r}\r}\r} pipeline支持的指令\nenvironment：用于设置环境变量，可以定义在stage或pipeline部分，环境变量可以向下面的示例设置为全局的，也可以是阶段stage级别的。如你所想，阶段stage级别的环境变量只能在定义变量的阶段stage使用。\ntools：可定义在pipeline或stage部分，会自动下载并安装我们指定的工具，并将其加入到PATH变量中\ninput：定义在stage部分，会暂停pipeline，提示你输入内容\noptions：用于配置Jenkins pipeline本身的选项，options指令可以定义在stage或pipeline部分\nparallel：并行执行多个step\nparameters：与input不同，parameters时执行pipeline前传入的一些参数\ntriggers：定义执行pipeline的触发器\nwhen：当满足when条件时，阶段才会执行 在使用指令时注意每个指令都有自己的作用域，如果指令使用的位置不正确，Jenkins会报错\n变量定义（全局）\n通过def project_name，定义job名称 通过def upstream_list= \u0026lsquo;,\u0026rsquo; 定义上游job名称，用在触发器里面\noptions\n用于配置整个pipeline本身的选项\nbuildDiscarder 保存最近历史构建记录的数量\ndisableConcurrentBuilds 同一个pipline，Jenkins是默认可以同时执行多次的，此选项是为了禁止pipeline同时执行\nretry：当发生失败是进行重试retry(4)\ntext 1 2 3 4 5 options { buildDiscarder{logRotator(numToKeepStr: \u0026#39;30\u0026#39;)} # 保存最近x个job执行记录 timeout(time:1, unit: \u0026#39;HOURS\u0026#39;) # 1小时内未执行完，自动结束 disableConcurrentBuilds() # 不允许两个job同时执行 } parameters\n该parameters指令提供用户在触发Pipeline时应提供的参数列表。这些用户指定的参数的值通过该params对象可用于Pipeline步骤\n字符串类型的参数，例如：parameters { string(name: 'DEPLOY_ENV', defaultValue: 'staging', description: '') }\nbooleanParam，一个布尔参数，例如：booleanParam(name: 'DEPLOY_BUILD', defaultValue: true, description: '') }\n目前支持[booleanParam, choice, credentials ,file, test, password, run, string]\ntext 1 2 3 4 5 parameters { choice(name: \u0026#39;environ\u0026#39;,choices: \u0026#39;test\\ndev\\nstg\u0026#39;, description: \u0026#39;测试环境，请选择dev? test? stg? \u0026#39;) string(name: \u0026#39;keywords\u0026#39;, defaultValue: \u0026#39;\u0026#39;, description: \u0026#39;测试用例名的关键字，用于过滤测试用例\u0026#39;) string(name: \u0026#39;folder\u0026#39;, defaultValue: \u0026#39;\u0026#39;, description: \u0026#39;文件夹名称，用于指定具体包那个文件夹下的case\u0026#39;) } triggers配置\ntriggers指定定义了流水线被重新出发的自动化方法。当前可用的触发器是cron, pollSCM, upstream, gitlab。例如\ntext 1 2 3 4 5 6 triggers { poolSCM(\u0026#39;H * * * 1-5\u0026#39;) // 周一到周五，每小时 cron(\u0026#39;H H * * *\u0026#39;) // 每天 gitlab(triggerOnPush: true, triggerOnMergeRequest: false, branchFilterType: \u0026#39;All\u0026#39;) upstream(upstreamPorjects: \u0026#34;${upstream_list}\u0026#34;, threshold: hudson.model.Result.SUCCESS) } 定时触发：cron\n接收cron样式的字符串来定义要重新出发流水线的常规间隔，比如cron('H H * * *') ，每天轮询代码仓库：pollSCM\n接收cron样式的字符串来定义一个固定的间隔，在这个间隔中，Jenkins会检查新的源代码更新。如果存在更改，流水线就会被重新出发。例如pollSCM(\u0026lsquo;H * * * 1-5\u0026rsquo;) 周一到周五，每小时\n由上游任务触发：upstream\n接受逗号分割的工作字符串和阈值，当字符串中的任何作业以最小阈值结束时，流水线被重新触发。例如：triggers { upstream(upstreamPorjects: \u0026quot;job1,job2\u0026quot;, threshold: hudson.model.Result.SUCCESS) }\nhudson.model.Result包括以下状态：\nABORTED：任务被手动终止， FAILURE：构建失败 SUCCESS：构建成功 UNSTABLE：存在一些错误，但构建没失败 NOT_BUILT：多阶段构建时，前面阶段问题导致后面阶段无法执行 由gitlab触发：gitlab gitlab(triggerOnPush: true, triggerOnMergeRequest: false, branchFilterType: 'All')，更改后push到远端。\ntriggerOnPush: true 代表有push就会触发job triggerOnMergeRequest: false 代码有merge不会触发 branchFilterType: 'All' 所有分支均会触发 https://blog.csdn.net/qq_30758629/article/details/93353437\npipline environment {\rgit_url = \u0026#39;https://gitlab.com/lc.chow/jenkins-test.git\u0026#39;\rgit_key = \u0026#39;176b96d4-0865-4cb8-871d-f9b65a84cecc\u0026#39;\rgit_branch = \u0026#39;master\u0026#39;\rgitpullerr = \u0026#39;noerr\u0026#39;\remail_list = \u0026#39;test.com@gmail.com\u0026#39;\r}\roptions {\rbuildDiscarder{logRotator(numToKeepStr: \u0026#39;30\u0026#39;)} # 保存最近x个job执行记录\rtimeout(time:1, unit: \u0026#39;HOURS\u0026#39;) # 1小时内未执行完，自动结束\rdisableConcurrentBuilds() # 不允许两个job同时执行\r}\rparameters {\rchoice(name: \u0026#39;environ\u0026#39;,choices: \u0026#39;test\\ndev\\nstg\u0026#39;, description: \u0026#39;测试环境，请选择dev? test? stg? \u0026#39;)\rstring(name: \u0026#39;keywords\u0026#39;, defaultValue: \u0026#39;\u0026#39;, description: \u0026#39;测试用例名的关键字，用于过滤测试用例\u0026#39;)\rstring(name: \u0026#39;folder\u0026#39;, defaultValue: \u0026#39;\u0026#39;, description: \u0026#39;文件夹名称，用于指定具体包那个文件夹下的case\u0026#39;)\r}\rstags {\rstage(\u0026#39;拉去测试代码\u0026#39;) {\rsteps {\rgit branch: \u0026#34;${git_branch}\u0026#34;, credentialsId: \u0026#34;$git_key\u0026#34;, url: \u0026#34;$git_url\u0026#34;\r}\r}\rstage(\u0026#39;安装测试依赖\u0026#39;) {\rsteps {\rsh \u0026#34;pipenv --rm\u0026#34;\rsh \u0026#34;pipenv install --skip-lock --ignore-pipfile\u0026#34;\rsh \u0026#34;pipenv graph\u0026#34;\r}\r}\rstage(\u0026#39;执行测试用例\u0026#39;) {\rsteps {\rsh \u0026#34;rm -fr $env.WORKSPACE/allure-*\u0026#34;\rsh \u0026#34;pipenv run py.test --env \u0026#39;${params.environ}\u0026#39; -k \u0026#39;${params.keywords}\u0026#39; tests/{params.folder}\u0026#34;\r}\r}\r}\rpost {\ralways {\r}\r} demo text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 pipeline { agent any stages { stage(\u0026#39;Depoly pay13\u0026#39;){ steps { sshPublisher(publishers: [sshPublisherDesc(configName: \u0026#39;52.229.166.83 pay13\u0026#39;, transfers: [sshTransfer(cleanRemote: false, excludes: \u0026#39;\u0026#39;, execCommand: \u0026#34;cd /data/paysCenter \u0026amp;\u0026amp; sudo git pull \u0026amp;\u0026amp; sudo chown -R nginx.nginx /data/paysCenter\u0026#34;, execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: \u0026#39;[, ]+\u0026#39;, remoteDirectory: \u0026#39;\u0026#39;, remoteDirectorySDF: false, removePrefix: \u0026#39;\u0026#39;, sourceFiles: \u0026#39;\u0026#39;)], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)]) sshPublisher(publishers: [sshPublisherDesc(configName: \u0026#39;52.229.166.83 pay13\u0026#39;, transfers: [sshTransfer(cleanRemote: false, excludes: \u0026#39;\u0026#39;, execCommand: \u0026#39;cd /data/paysCenter \u0026amp;\u0026amp; sudo git pull \u0026amp;\u0026amp; echo `git show|head -1|awk \\\u0026#39;{print $2}\\\u0026#39;`\u0026#39;, execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: \u0026#39;[, ]+\u0026#39;, remoteDirectory: \u0026#39;\u0026#39;, remoteDirectorySDF: false, removePrefix: \u0026#39;\u0026#39;, sourceFiles: \u0026#39;\u0026#39;)], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)]) } } } post { success { sh \u0026#39;\u0026#39;\u0026#39; echo \u0026#34;push success\u0026#34; \u0026#39;\u0026#39;\u0026#39; } failure { sh \u0026#39;\u0026#39;\u0026#39; echo \u0026#34;push fail\u0026#34; \u0026#39;\u0026#39;\u0026#39; } } } pipline pipeline{\ragent any\renvironment {\rhost = \u0026#34;192.168.50.32\u0026#34;\rpath1 = \u0026#34;/data/appdown\u0026#34;\r}\roptions {\rtimeout(time: 1, unit: \u0026#39;HOURS\u0026#39;)\rbuildDiscarder(logRotator(numToKeepStr: \u0026#39;100\u0026#39;))\rdisableConcurrentBuilds()\r}\rstages{\rstage(\u0026#34;pull\u0026#34;){\rsteps{\rgit branch: \u0026#39;pre\u0026#39;, credentialsId: \u0026#39;abe7e165-b646-472e-ab48-024004ecc589\u0026#39;, url: \u0026#39;http://j7.hnxmny.com:8088/a/front/appdown\u0026#39;\r}\r}\rstage(\u0026#34;build\u0026#34;){\rsteps{\rwithEnv([\u0026#39;PATH+EXTRA=/usr/sbin:/usr/bin:/sbin:/bin\u0026#39;]) {\rsh \u0026#34;sudo npm i \u0026amp;\u0026amp; sudo npm run isol\u0026#34;\r}\r}\r}\rstage(\u0026#34;deploy\u0026#34;){\rsteps{\rsh \u0026#39;\u0026#39;\u0026#39;\rfile=`date +%Y%m%d%H%M%S`\rsudo mv dist dist.${file}\rtar zcf dist.${file}.tar.gz dist.${file}\ransible ${host} -b -m copy -a \u0026#34;src=dist.${file}.tar.gz dest=/tmp/\u0026#34;\ransible ${host} -b -m raw -a \u0026#34;tar xf /tmp/dist.${file}.tar.gz -C ${path1}/ \u0026amp;\u0026amp; rm -f /tmp/dist.${file}.tar.gz\u0026#34;\ransible ${host} -b -m raw -a \u0026#34;ln -svnf ${path1}/dist.${file} ${path1}/dist\u0026#34;\r\u0026#39;\u0026#39;\u0026#39;\r}\r}\r}\rpost {\rcleanup {\recho \u0026#39;One way or another, I have finished\u0026#39;\rdeleteDir() /* clean up our workspace */\r}\r}\r} pipline 报错\ntext 1 2 nohup: failed to run command ‘sh’: No such file or directory Sending interrupt signal to process 原因：environment设置了path环境变量导致重写出现的原因\ntext 1 2 3 4 environment { host = \u0026#34;192.168.50.32\u0026#34; path1 = \u0026#34;/data/appdown\u0026#34; } 解决方法：\nhttps://stackoverflow.com/questions/43987005/jenkins-does-not-recognize-command-sh withEnv\ntext 1 2 3 4 5 6 7 node { stage (\u0026#39;STAGE NAME\u0026#39;) { withEnv([\u0026#39;PATH+EXTRA=/usr/sbin:/usr/bin:/sbin:/bin\u0026#39;]) { sh \u0026#39;//code block\u0026#39; } } } 避免使用系统环境变量名称 进入某个路径下操作\n执行完后会退回原目录\ntext 1 2 3 4 5 6 dir(path: \u0026#39;ssr-server\u0026#39;) { sh \u0026#39;\u0026#39;\u0026#39;2 rm -fr node_modules \u0026amp;\u0026amp; npm install npm run prestart:prod \u0026#39;\u0026#39;\u0026#39; } 使用参数\npipeline parameters {\rchoice(name: \u0026#39;vernum\u0026#39;,choices: \u0026#39;v1\\nv2\u0026#39;, description: \u0026#39;请选择v1? v2?\u0026#39;)\r} if\npipeline stage(\u0026#34;build\u0026#34;){\rsteps{\rscript {\rif ( params.vernum == \u0026#39;v1\u0026#39; ) {\rdir(path: \u0026#39;v1\u0026#39;) {\rsh \u0026#39;\u0026#39;\u0026#39;\rnpm i\r\u0026#39;\u0026#39;\u0026#39;\r}\r} else {\rdir(path: \u0026#39;v2\u0026#39;) {\rsh \u0026#39;\u0026#39;\u0026#39;\rnpm i\r\u0026#39;\u0026#39;\u0026#39;\r}\r}\r}\r}\r} ","permalink":"https://www.161616.top/jenkins-pipline-demo/","summary":"pipline demo https://jenkins.io/zh/doc/book/pipeline/syntax/ git 插件 https://jenkins.io/doc/pipeline/steps/git/ pipline pipeline{\ragent any\rstages{\rstage(\u0026#34;build\u0026#34;){\rsteps{\recho \u0026#34;11111\u0026#34;\r}\r}\r}\r} pipeline总体介绍 基本结构\n以下每一个部分都是必须的，少一个Jenkins都会报错\npipline pipeline{\ragent any\rstages{\rstage(\u0026#34;build\u0026#34;){\rsteps{\recho \u0026#34;hellp\u0026#34;\r}\r}\r}\r} pipeline 代表整个流水线，包含整条流水线的逻辑 stage 阶段，代表流水线的阶段，每个阶段都必须有名称。 stages 流水线中多个stage的容器，stages部分至少包含一个stage. steps 代表stage中的一个活多个具体步骤的容器，steps部分至少包含一个步骤 agent 制定流水线的执行位置，流水线中每个阶段都必须在某个地方执行（master节点/slave节点/物理机/虚拟机/docker容器），agent部分指定具体在哪里执行。agent { label '***-slave'} 可选步骤\npost 包含的是在整个pipeline或stage完成后的附件条件\nalways 论Pipeline运行的完成状态如何都会执行这段代码 changes 只有当前Pipeline运行的状态与先前完成的Pipeline的状态不同时，才能触发运行。 failure 当前状态为失败时执行 success 当前完成状态为成功时执行 demo\n使用${test}，可以引入自定义变量\npipeline post {\ralways {\rscript {\rallure includeProperties: false, jdk: \u0026#39;\u0026#39;,report: \u0026#39;jenkins-allure-report\u0026#39;, results: [[path: \u0026#39;allure-results\u0026#39;]] }\r}\rfailure {\rscript {\rif (gitpuller == \u0026#39;noerr\u0026#39;) {\rmail to: \u0026#34;${email_list}\u0026#34;,\rsubject: \u0026#34;[jenkins Build Notification] ${JOB_NAME} - Build # ${BUILD_NUMBER} 构建失败\u0026#34;,\rbody: \u0026#34;\u0026#39;${env.","title":"jenkins pipline demo"},{"content":"需要安装的插件 Role-Based Strategy（可以对构建的项目进行授权管理，让不同的用户管理不同的项目，将测试和生产环境分开）\n选择授权策略 当Role-based Authorization Strategy 这个插件安装好之后，授权策略会多出一个Role-Based Strategy 选项，选择此项\n添加配置权限 系统设置 \u0026raquo; Manage and Assign Roles\nManage Roles 设置全局角色（全局角色可以对jenkins系统进行设置与项目的操作）\nadmin:对整个jenkins都可以进行操作 root:可以对所有的job进行管理 other:只有读的权限 other必须有，否则给用户分配角色时分配没有全局role会导致分配失效 Assign Roles为用户指派角色 项目角色是根据正则匹配的，\n","permalink":"https://www.161616.top/jenkins-user-authentication/","summary":"需要安装的插件 Role-Based Strategy（可以对构建的项目进行授权管理，让不同的用户管理不同的项目，将测试和生产环境分开）\n选择授权策略 当Role-based Authorization Strategy 这个插件安装好之后，授权策略会多出一个Role-Based Strategy 选项，选择此项\n添加配置权限 系统设置 \u0026raquo; Manage and Assign Roles\nManage Roles 设置全局角色（全局角色可以对jenkins系统进行设置与项目的操作）\nadmin:对整个jenkins都可以进行操作 root:可以对所有的job进行管理 other:只有读的权限 other必须有，否则给用户分配角色时分配没有全局role会导致分配失效 Assign Roles为用户指派角色 项目角色是根据正则匹配的，","title":"jenkins的用户授权与管理"},{"content":"文中的代码来自可以从github下载： https://github.com/ciandcd\n插件 jobConfigHistory，可以查看job配置的修改历史。\n安装后重启jenkins，然后对job的配置修改后，可以点击job config history连接查看修改历史。\n选择需要比较的版本，可以diff两个版本间的差别。\n","permalink":"https://www.161616.top/jenkins-recode-history/","summary":"文中的代码来自可以从github下载： https://github.com/ciandcd\n插件 jobConfigHistory，可以查看job配置的修改历史。\n安装后重启jenkins，然后对job的配置修改后，可以点击job config history连接查看修改历史。\n选择需要比较的版本，可以diff两个版本间的差别。","title":"jenkins历史比较"},{"content":"修改启动用户\n先停止jenkins服务\nsh 1 2 sudo launchctl unload /Library/LaunchDaemons/org.jenkins-ci.plist sudo vim /Library/LaunchDaemons/org.jenkins-ci.plist 授权jenkins工作目录和临时目录\ntext 1 2 sudo chown -R zhulangren:wheel /Users/Shared/Jenkins/ sudo chown -R zhulangren:wheel /var/log/jenkins/ 启动jenkins\ntext 1 sudo launchctl load /Library/LaunchDaemons/org.jenkins-ci.plist jenkins自启动文件路径\ntext 1 /Library/LaunchDaemons/org.jenkins-ci.plist 卸载脚本文件\ntext 1 /Library/Application\\ Support/Jenkins/Uninstall.command 修改jenkins启动端口\ntext 1 sudo defaults write /Library/Preferences/org.jenkins-ci httpPort \u0026#39;9999\u0026#39; 读取jenkins配置文件\ntext 1 defaults read /Library/Preferences/org.jenkins-ci 设置自启动\ntext 1 sudo launchctl load /Library/LaunchDaemons/org.jenkins-ci.plis 取消自启动\ntext 1 sudo launchctl unload /Library/LaunchDaemons/org.jenkins-ci.plist jenkins war路径\ntext 1 /Applications/Jenkins/jenkins.war Reference Mac Jenkins 权限问题 ","permalink":"https://www.161616.top/jenkens-migrant-in-macos/","summary":"修改启动用户\n先停止jenkins服务\nsh 1 2 sudo launchctl unload /Library/LaunchDaemons/org.jenkins-ci.plist sudo vim /Library/LaunchDaemons/org.jenkins-ci.plist 授权jenkins工作目录和临时目录\ntext 1 2 sudo chown -R zhulangren:wheel /Users/Shared/Jenkins/ sudo chown -R zhulangren:wheel /var/log/jenkins/ 启动jenkins\ntext 1 sudo launchctl load /Library/LaunchDaemons/org.jenkins-ci.plist jenkins自启动文件路径\ntext 1 /Library/LaunchDaemons/org.jenkins-ci.plist 卸载脚本文件\ntext 1 /Library/Application\\ Support/Jenkins/Uninstall.command 修改jenkins启动端口\ntext 1 sudo defaults write /Library/Preferences/org.jenkins-ci httpPort \u0026#39;9999\u0026#39; 读取jenkins配置文件\ntext 1 defaults read /Library/Preferences/org.jenkins-ci 设置自启动\ntext 1 sudo launchctl load /Library/LaunchDaemons/org.jenkins-ci.plis 取消自启动\ntext 1 sudo launchctl unload /Library/LaunchDaemons/org.","title":"jenkins在Mac OS下的迁移记录"},{"content":"cjson下载\nhttps://github.com/mpx/lua-cjson.git\n下载解压后，编译需要根据自己的lua环境以及操作系统修改Makefile的一些配置，不然容易出错。 以下是Makefile中的一些配置。\nc 1 2 3 4 5 6 7 8 LUA_VERSION = 5.2 TARGET = cjson.so PREFIX = /usr/local CJSON_LDFLAGS = -shared LUA_INCLUDE_DIR = $(PREFIX)/include LUA_CMODULE_DIR = $(PREFIX)/lib/lua/$(LUA_VERSION) LUA_MODULE_DIR = $(PREFIX)/share/lua/$(LUA_VERSION) LUA_BIN_DIR = $(PREFIX)/bin https://blog.gezhiqiang.com/2017/08/24/lua-cjson/\nlua 1 2 3 4 5 6 7 8 9 10 11 12 13 local cjson = require(\u0026#34;cjson\u0026#34;) local obj = { id = 1, name = \u0026#34;zhangsan\u0026#34;, age = nil, is_male = false, hobby = {\u0026#34;zhangsan\u0026#34;,\u0026#34;lisi\u0026#34;,\u0026#34;wangwu\u0026#34;} } local str = cjson.encode(obj) ngx.say(str) ","permalink":"https://www.161616.top/lua-cjson/","summary":"cjson下载\nhttps://github.com/mpx/lua-cjson.git\n下载解压后，编译需要根据自己的lua环境以及操作系统修改Makefile的一些配置，不然容易出错。 以下是Makefile中的一些配置。\nc 1 2 3 4 5 6 7 8 LUA_VERSION = 5.2 TARGET = cjson.so PREFIX = /usr/local CJSON_LDFLAGS = -shared LUA_INCLUDE_DIR = $(PREFIX)/include LUA_CMODULE_DIR = $(PREFIX)/lib/lua/$(LUA_VERSION) LUA_MODULE_DIR = $(PREFIX)/share/lua/$(LUA_VERSION) LUA_BIN_DIR = $(PREFIX)/bin https://blog.gezhiqiang.com/2017/08/24/lua-cjson/\nlua 1 2 3 4 5 6 7 8 9 10 11 12 13 local cjson = require(\u0026#34;cjson\u0026#34;) local obj = { id = 1, name = \u0026#34;zhangsan\u0026#34;, age = nil, is_male = false, hobby = {\u0026#34;zhangsan\u0026#34;,\u0026#34;lisi\u0026#34;,\u0026#34;wangwu\u0026#34;} } local str = cjson.","title":"lua cjson使用"},{"content":" lua 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 location = /reqq { default_type text/plain; content_by_lua_block { ngx.req.read_body() local data = ngx.req.get_body_data() local args, err = ngx.req.get_uri_args() if not args then ngx.say(\u0026#39;post fail\u0026#39;) return end for key,v in pairs(args) do ngx.say(key,\u0026#34;::\u0026#34;,v,\u0026#34;--\u0026#34;) end ngx.say(data) } } ngx.exec 内部重定向 lua 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 location = /bb { default_type text/plain; content_by_lua_block{ ngx.exec(\u0026#39;/reqq?m=DaoShengPay3\u0026amp;c=Pay\u0026amp;orderNo=PH201910111\u0026#39;) } } location = /reqq { default_type text/plain; content_by_lua_block { ngx.req.read_body() local data = ngx.req.get_body_data() local args, err = ngx.req.get_uri_args() if not args then ngx.say(\u0026#39;post fail\u0026#39;) return end for key,v in pairs(args) do ngx.say(key,\u0026#34;::\u0026#34;,v,\u0026#34;--\u0026#34;) end ngx.say(data) } } ngx.log lua 1 2 3 4 5 6 7 location = /testlog { default_type text/plain; content_by_lua_block{ ngx.say(\u0026#39;hello lua log\u0026#39;) ngx.log(ngx.ERR, \u0026#39;print hello lua log\u0026#39;) } } text 1 2019/10/12 22:25:31 [error] 468#0: *248 [lua] content_by_lua(nginx.conf:44):3: print hello lua log, client: 203.90.247.72, server: test111.hou2008.com, request: \u0026#34;GET /testlog HTTP/1.1\u0026#34;, host: \u0026#34;test111.hou2008.com:8181\u0026#34;, referrer: \u0026#34;http://test111.hou2008.com/testlog\u0026#34; Nginx API for Lua https://github.com/openresty/lua-nginx-module\nhttps://www.zifangsky.cn/1024.html\n","permalink":"https://www.161616.top/lua-nginx-api/","summary":"lua 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 location = /reqq { default_type text/plain; content_by_lua_block { ngx.req.read_body() local data = ngx.req.get_body_data() local args, err = ngx.req.get_uri_args() if not args then ngx.say(\u0026#39;post fail\u0026#39;) return end for key,v in pairs(args) do ngx.say(key,\u0026#34;::\u0026#34;,v,\u0026#34;--\u0026#34;) end ngx.say(data) } } ngx.exec 内部重定向 lua 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 location = /bb { default_type text/plain; content_by_lua_block{ ngx.","title":"lua nginx api"},{"content":" lua 1 2 3 4 5 6 7 8 9 10 package1 = {} package1.const = \u0026#34;测试常量\u0026#34; function package1.func1() io.write(\u0026#34;this is public func\\n\u0026#34;) end return package1 req.lua\nlua 1 2 3 require \u0026#34;package1\u0026#34; package1.func1() print(package1) text 1 2 3 lc@lc-virtual-machine:~/lua$ lua pack1.lua this is public func table: 0x5575766224a0 注意事项：\n测试文件是和封装好的模块在同一个目录，否则引用时需要设置路径。\nlua 1 2 3 4 5 6 7 package.path = \u0026#39;/home/lc/lua/1/package1.lua;\u0026#39;; require \u0026#34;package1\u0026#34; package1.func1() print(package1) 模块名称和文件名称必须相同\n","permalink":"https://www.161616.top/lua-nginx-module/","summary":"lua 1 2 3 4 5 6 7 8 9 10 package1 = {} package1.const = \u0026#34;测试常量\u0026#34; function package1.func1() io.write(\u0026#34;this is public func\\n\u0026#34;) end return package1 req.lua\nlua 1 2 3 require \u0026#34;package1\u0026#34; package1.func1() print(package1) text 1 2 3 lc@lc-virtual-machine:~/lua$ lua pack1.lua this is public func table: 0x5575766224a0 注意事项：\n测试文件是和封装好的模块在同一个目录，否则引用时需要设置路径。\nlua 1 2 3 4 5 6 7 package.path = \u0026#39;/home/lc/lua/1/package1.lua;\u0026#39;; require \u0026#34;package1\u0026#34; package1.func1() print(package1) 模块名称和文件名称必须相同","title":"lua nginx module"},{"content":" text 1 Connect-VIServer -Server 这里是“SSL连接不能建立\u0026hellip;\u0026hellip;”这实际上意味着你没有一个有效的证书。如果你想连接到vCenter没有一个有效的证书，您必须允许可以改变你的vCenter证书到受信任的一个，这是正确的解决方案，也可以忽略无效的证书，以规避所有的安全性，但使它现在的工作。\n忽略无效证书\ntext 1 Set-PowerCLIConfiguration -InvalidCertificateAction:ignore 再次登陆可正常登陆 ","permalink":"https://www.161616.top/powercli-the-ssl-connection-could-not-be-established-see-inner-exception/","summary":" text 1 Connect-VIServer -Server 这里是“SSL连接不能建立\u0026hellip;\u0026hellip;”这实际上意味着你没有一个有效的证书。如果你想连接到vCenter没有一个有效的证书，您必须允许可以改变你的vCenter证书到受信任的一个，这是正确的解决方案，也可以忽略无效的证书，以规避所有的安全性，但使它现在的工作。\n忽略无效证书\ntext 1 Set-PowerCLIConfiguration -InvalidCertificateAction:ignore 再次登陆可正常登陆 ","title":"powercli The SSL connection could not be established, see inner exception. 问题解决"},{"content":" pwsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 Connect-VIServer -Server 10.11.17.20 -User administrator@vsphere.local -Password DC02@123456 Get-OSCustomizationSpec linux1|Get-OSCustomizationNicMapping|Set-OSCustomizationNicMapping -IpMode UseStaticIP -SubnetMask 255.255.255.0 -DefaultGateway 10.10.12.254 -IpAddress 10.10.12.114 new-VM -Name zy-ntw-prod-dbvm-pushredis01 -Template template-centos76 -VMHost 10.10.12.14 -OSCustomizationspec linux1 Get-VM -name zy-ntw-prod-dbvm-pushredis01|set-VM -MemoryGB 96 -NumCPU 16 get-vm zy-ntw-prod-dbvm-pushredis01|New-HardDisk -CapacityGB 100 New-Snapshot -Name \u0026#34;20200622\u0026#34; Connect-VIServer -Server 10.11.17.20 -User administrator@vsphere.local -Password DC@123456 Connect-VIServer -Server 10.11.17.20 -User administrator@vsphere.local -Password DC02@123456 Connect-VIServer -Server 10.10.200.20 -User administrator@vsphere.local -Password DC@123456 Get-OSCustomizationSpec linux1|Get-OSCustomizationNicMapping|Set-OSCustomizationNicMapping -IpMode UseStaticIP -SubnetMask 255.255.255.0 -DefaultGateway 10.11.31.254 -IpAddress 10.11.31.65 new-VM -Name zy-ntw-zr-prod-vip-upgrade01 -Template template-centos76 -VMHost 10.11.31.4 -OSCustomizationspec linux1 Get-OSCustomizationSpec linux1|Get-OSCustomizationNicMapping|Set-OSCustomizationNicMapping -IpMode UseStaticIP -SubnetMask 255.255.255.0 -DefaultGateway 10.11.31.254 -IpAddress 10.11.31.66 new-VM -Name zy-ntw-zr-prod-vip-upgrade02 -Template template-centos76 -VMHost 10.11.31.5 -OSCustomizationspec linux1 get-vm zy-ntw-zr-prod-vip-upgrade*|set-VM -MemoryGB 8 -NumCPU 4 get-harddisk zy-ntw-zr-prod-vip-upgrade* get-harddisk zy-ntw-zr-prod-vip-upgrade*|set-harddisk -CapacityGB 100 # 现有硬盘扩容 get-vm zy-ntw-zr-prod-vip-upgrade* |get-harddisk|set-harddisk -CapacityGB 100 ## 删除虚拟机 remove-vm –deletepermanently ","permalink":"https://www.161616.top/powercli-vsphere-command/","summary":"pwsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 Connect-VIServer -Server 10.11.17.20 -User administrator@vsphere.local -Password DC02@123456 Get-OSCustomizationSpec linux1|Get-OSCustomizationNicMapping|Set-OSCustomizationNicMapping -IpMode UseStaticIP -SubnetMask 255.255.255.0 -DefaultGateway 10.10.12.254 -IpAddress 10.10.12.114 new-VM -Name zy-ntw-prod-dbvm-pushredis01 -Template template-centos76 -VMHost 10.10.12.14 -OSCustomizationspec linux1 Get-VM -name zy-ntw-prod-dbvm-pushredis01|set-VM -MemoryGB 96 -NumCPU 16 get-vm zy-ntw-prod-dbvm-pushredis01|New-HardDisk -CapacityGB 100 New-Snapshot -Name \u0026#34;20200622\u0026#34; Connect-VIServer -Server 10.","title":"powercli常用命令"},{"content":" 文档中心 https://pubs.vmware.com/vsphere-51/index.jsp?topic=%2Fcom.vmware.powercli.cmdletref.doc%2FSet-OSCustomizationSpec.html https://blogs.vmware.com/PowerCLI/2014/05/working-customization-specifications-powercli-part-1.html http://powershelldistrict.com/powercli-oscustomizationspec/ https://powercli-core.readthedocs.io/en/latest/cmd_new.html#new-oscustomizationspec 官方文档中心\n添加一块新硬盘\ntext 1 get-vm {vmname}|New-HardDisk -CapacityGB 300 批量设置硬件\ntext 1 Get-VM -name {hostname}*|set-VM -MemoryGB 4 -NumCPU 2 基于模板创建虚拟机\ntext 1 new-VM -Name {hostname} -Template template-centos76 -VMHost 10.112.131.5 -OSCustomizationspec TestLinux 设置规范模板\ntext 1 2 3 4 5 6 7 8 9 10 11 Get-OSCustomizationSpec TestLinux|Get-OSCustomizationNicMapping|Set-OSCustomizationNicMapping \\ -IpMode UseStaticIP \\ -SubnetMask 255.255.255.0 \\ -DefaultGateway 10.11.121.254 \\ -IpAddress 10.11.121.203 Get-OSCustomizationSpec TestLinux|Get-OSCustomizationNicMapping|Set-OSCustomizationNicMapping \\ -IpMode UseStaticIP \\ -SubnetMask 255.255.255.0 \\ -DefaultGateway 10.11.131.254 \\ -IpAddress 10.11.131.64 获得规范信息\ntext 1 Get-OSCustomizationSpec liunx 创建一个新的规范\ntext 1 New-OSCustomizationSpec -OSType \u0026#34;Linux\u0026#34; -Name linux1 -Type Persistent –DnsServer \u0026#34;8.8.8.8\u0026#34; –Domain \u0026#34;vmware.com\u0026#34; 批量创建脚本\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 $vserver=\u0026#34;10.111.111.22\u0026#34; $vuser=\u0026#34;youname\u0026#34; $vpwd=\u0026#34;yourpasswd\u0026#34; filter Convert-IP2Decimal { ([IPAddress][String]([IPAddress]$_)).Address } filter Convert-Decimal2IP { ([System.Net.IPAddress]$_).IPAddressToString } $IPPrefix=\u0026#34;10.111.31\u0026#34; $startIp=$args[0] $startHostname=$args[1] $num=$args[2] $gateway=\u0026#34;10.111.31.254\u0026#34; $netmask=\u0026#34;255.255.255.0\u0026#34; $cpu=4 $mem=8 $template=\u0026#34;template-centos7\u0026#34; $vhost17=,\u0026#34;10.111.17.1\u0026#34;,\u0026#34;10.111.17.2\u0026#34;,\u0026#34;10.111.17.3\u0026#34;,\u0026#34;10.111.17.4\u0026#34; $vhost20=,\u0026#34;10.111.20.1\u0026#34;,\u0026#34;10.111.20.2\u0026#34;,\u0026#34;10.111.20.3\u0026#34;,\u0026#34;10.111.20.4\u0026#34; $vhost31=,\u0026#34;10.111.31.1\u0026#34;,\u0026#34;10.111.31.2\u0026#34;,\u0026#34;10.111.31.3\u0026#34;,\u0026#34;10.111.31.4\u0026#34; function LoginVSphere($server,$user,$pwd){ Connect-VIServer -Server $server[0] -User $server[1] -Password $server[2] -EA \u0026#34;Stop\u0026#34; } LoginVSphere($vserver, $vuser, $vpwd) $temp=Get-OSCustomizationSpec TestLinux for($n=[int]$startIp;$n -le [int]$num; $n++) { $ipaddr=-Join($IPPrefix,\u0026#39;.\u0026#39;,$n) $hostname=-Join($startHostname,$n) $temp|Get-OSCustomizationNicMapping|Set-OSCustomizationNicMapping -IpMode UseStaticIP -SubnetMask $netmask -DefaultGateway $gateway -IpAddress $ipaddr new-VM -Name $hostname -Template $template -VMHost 10.111.31.5 -OSCustomizationspec TestLinux -Confirm:$false Get-VM -Name $hostname|Set-VM -MemoryGB $mem -NumCPU $cpu -Confirm:$false } Reference https://communities.vmware.com/thread/567585\n脚本参考\nhttps://blogs.vmware.com/PowerCLI/2014/05/working-customization-specifications-powercli-part-1.html\nhttp://powershelldistrict.com/powercli-oscustomizationspec/\nhttps://pubs.vmware.com/vsphere-51/index.jsp?topic=%2Fcom.vmware.powercli.cmdletref.doc%2FSet-OSCustomizationSpec.html\n","permalink":"https://www.161616.top/create-vm-with-powercli/","summary":"文档中心 https://pubs.vmware.com/vsphere-51/index.jsp?topic=%2Fcom.vmware.powercli.cmdletref.doc%2FSet-OSCustomizationSpec.html https://blogs.vmware.com/PowerCLI/2014/05/working-customization-specifications-powercli-part-1.html http://powershelldistrict.com/powercli-oscustomizationspec/ https://powercli-core.readthedocs.io/en/latest/cmd_new.html#new-oscustomizationspec 官方文档中心\n添加一块新硬盘\ntext 1 get-vm {vmname}|New-HardDisk -CapacityGB 300 批量设置硬件\ntext 1 Get-VM -name {hostname}*|set-VM -MemoryGB 4 -NumCPU 2 基于模板创建虚拟机\ntext 1 new-VM -Name {hostname} -Template template-centos76 -VMHost 10.112.131.5 -OSCustomizationspec TestLinux 设置规范模板\ntext 1 2 3 4 5 6 7 8 9 10 11 Get-OSCustomizationSpec TestLinux|Get-OSCustomizationNicMapping|Set-OSCustomizationNicMapping \\ -IpMode UseStaticIP \\ -SubnetMask 255.255.255.0 \\ -DefaultGateway 10.11.121.254 \\ -IpAddress 10.11.121.203 Get-OSCustomizationSpec TestLinux|Get-OSCustomizationNicMapping|Set-OSCustomizationNicMapping \\ -IpMode UseStaticIP \\ -SubnetMask 255.","title":"powercli创建虚拟机步骤及批量创建脚本"},{"content":"zimbra在后台安装证书签发机构签发证书出现时候出现错误：{RemoteManager: mail.domain.com-\u0026gt;zimbra@mail.domain.com:22}\ntext 1 2 3 com.zimbra.common.service.ServiceException: system failure: exception during auth {RemoteManager: mail.domain.com-\u0026gt;zimbra@mail.domain.com:22} ExceptionId:qtp1068934215-357:https:https ://mail.domain.com:7071/service/admin/soap/GetMailQueueRequest: Code:service.FAILURE text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 com.zimbra.common.service.ServiceException: system failure: exception during auth {RemoteManager: mail.domain.com-\u0026gt;zimbra@mail.domain.com:22} ExceptionId:qtp1068934215-357:https:https ://mail.domain.com:7071/service/admin/soap/GetMailQueueRequest: Code:service.FAILURE at com.zimbra.common.service.ServiceException.FAILURE(ServiceException.java:286) at com.zimbra.cs.rmgmt.RemoteManager.getSession(RemoteManager.java:209) at com.zimbra.cs.rmgmt.RemoteManager.execute(RemoteManager.java:139) at com.zimbra.cert.GetCert.addCertsOnServer(GetCert.java:112) at com.zimbra.cert.GetCert.handle(GetCert.java:75) Caused by: java.io.IOException: There was a problem while connecting to mail.domain.com:22 at ch.ethz.ssh2.Connection.connect(Connection.java:699) at ch.ethz.ssh2.Connection.connect(Connection.java:490) at com.zimbra.cs.rmgmt.RemoteManager.getSession(RemoteManager.java:200) ... 59 more 检查ssh的配置与zimbra的配置\ntext 1 2 3 4 5 6 7 8 9 $ zmprov gacf | grep Remote zimbraRemoteImapBindPort: 8143 zimbraRemoteImapSSLBindPort: 8993 zimbraRemoteImapSSLServerEnabled: TRUE zimbraRemoteImapServerEnabled: TRUE zimbraRemoteManagementCommand: /opt/zimbra/libexec/zmrcd zimbraRemoteManagementPort: 22 zimbraRemoteManagementPrivateKeyPath: /opt/zimbra/.ssh/zimbra_identity zimbraRemoteManagementUser: zimbra 看到ssh端口为22，修改为当前系统使用的ssh端口\ntext 1 zmprov mcf zimbraRemoteManagementPort {sshport} 再次查看\ntext 1 2 3 4 5 6 7 8 9 zmprov gacf | grep Remote zimbraRemoteImapBindPort: 8143 zimbraRemoteImapSSLBindPort: 8993 zimbraRemoteImapSSLServerEnabled: TRUE zimbraRemoteImapServerEnabled: TRUE zimbraRemoteManagementCommand: /opt/zimbra/libexec/zmrcd zimbraRemoteManagementPort: 9955 zimbraRemoteManagementPrivateKeyPath: /opt/zimbra/.ssh/zimbra_identity zimbraRemoteManagementUser: zimbra 修改zimbra用户可以被允许登陆ssh\ntext 1 AllowUsers root zimbra 此时可以正确提交\nReference http://thaiserv.blogspot.com/2015/05/get-message-failure-exception-during.html https://wiki.zimbra.com/wiki/RemoteManager_exception\n","permalink":"https://www.161616.top/zimbra-ssl/","summary":"zimbra在后台安装证书签发机构签发证书出现时候出现错误：{RemoteManager: mail.domain.com-\u0026gt;zimbra@mail.domain.com:22}\ntext 1 2 3 com.zimbra.common.service.ServiceException: system failure: exception during auth {RemoteManager: mail.domain.com-\u0026gt;zimbra@mail.domain.com:22} ExceptionId:qtp1068934215-357:https:https ://mail.domain.com:7071/service/admin/soap/GetMailQueueRequest: Code:service.FAILURE text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 com.zimbra.common.service.ServiceException: system failure: exception during auth {RemoteManager: mail.domain.com-\u0026gt;zimbra@mail.domain.com:22} ExceptionId:qtp1068934215-357:https:https ://mail.domain.com:7071/service/admin/soap/GetMailQueueRequest: Code:service.FAILURE at com.zimbra.common.service.ServiceException.FAILURE(ServiceException.java:286) at com.zimbra.cs.rmgmt.RemoteManager.getSession(RemoteManager.java:209) at com.zimbra.cs.rmgmt.RemoteManager.execute(RemoteManager.java:139) at com.zimbra.cert.GetCert.addCertsOnServer(GetCert.java:112) at com.zimbra.cert.GetCert.handle(GetCert.java:75) Caused by: java.io.IOException: There was a problem while connecting to mail.domain.com:22 at ch.ethz.ssh2.Connection.connect(Connection.java:699) at ch.ethz.ssh2.Connection.connect(Connection.java:490) at com.zimbra.cs.rmgmt.RemoteManager.getSession(RemoteManager.java:200) .","title":"zimbra安装ssl证书"},{"content":"文章概述了如何使用 zmsetservername 更改 Zimbra 服务器的主机名。请注意，此 CLI 命令的用法因您运行的 ZCS 版本而异。\n语法：\nbash 1 ./zmsetservername [-h] [-d] [-f] [-s] [-o \u0026lt;上一个服务器名称\u0026gt;] [-v+] -n \u0026lt;服务器名称\u0026gt; 参数说明\nName Description \u0026ndash;help 显示 zmsetservername 的使用选项。 \u0026ndash;force 强制重命名，绕过安全检查。 \u0026ndash;oldServerName 服务器以前的名称。默认为 LC zimbra_server_hostname。 \u0026ndash;newServerName 服务器的新名称。 \u0026ndash;deletelogger 删除旧服务器的记录器数据库。默认是将其数据重新映射到新主机名。 \u0026ndash;skipusers 跳过使用新服务器修改用户数据库。 \u0026ndash;usersonly 仅更新用户数据库。这样，您可以运行一次来完成所有服务器更新，然后运行第二次来更新帐户。可能需要 --force。 \u0026ndash;verbose 设置详细级别。可以多次指定以提高级别。 Reference zm设置服务器名称\n","permalink":"https://www.161616.top/zimbra-change-servername/","summary":"文章概述了如何使用 zmsetservername 更改 Zimbra 服务器的主机名。请注意，此 CLI 命令的用法因您运行的 ZCS 版本而异。\n语法：\nbash 1 ./zmsetservername [-h] [-d] [-f] [-s] [-o \u0026lt;上一个服务器名称\u0026gt;] [-v+] -n \u0026lt;服务器名称\u0026gt; 参数说明\nName Description \u0026ndash;help 显示 zmsetservername 的使用选项。 \u0026ndash;force 强制重命名，绕过安全检查。 \u0026ndash;oldServerName 服务器以前的名称。默认为 LC zimbra_server_hostname。 \u0026ndash;newServerName 服务器的新名称。 \u0026ndash;deletelogger 删除旧服务器的记录器数据库。默认是将其数据重新映射到新主机名。 \u0026ndash;skipusers 跳过使用新服务器修改用户数据库。 \u0026ndash;usersonly 仅更新用户数据库。这样，您可以运行一次来完成所有服务器更新，然后运行第二次来更新帐户。可能需要 --force。 \u0026ndash;verbose 设置详细级别。可以多次指定以提高级别。 Reference zm设置服务器名称","title":"zimbra修改ServerName"},{"content":" sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #!/bin/bash # $1 domain # $2 email zmprov ma $2 zimbraIsDelegatedAdminAccount TRUE zmprov ma $2 zimbraAdminConsoleUIComponents cartBlancheUI zimbraAdminConsoleUIComponents domainListView zimbraAdminConsoleUIComponents accountListView zimbraAdminConsoleUIComponents DLListView zmprov ma $2 zimbraDomainAdminMaxMailQuota 0 zmprov grantRight domain $1 usr $2 +createAccount zmprov grantRight domain $1 usr $2 +createAlias zmprov grantRight domain $1 usr $2 +createCalendarResource zmprov grantRight domain $1 usr $2 +createDistributionList zmprov grantRight domain $1 usr $2 +deleteAlias zmprov grantRight domain $1 usr $2 +listDomain zmprov grantRight domain $1 usr $2 +domainAdminRights zmprov grantRight domain $1 usr $2 set.account.zimbraAccountStatus zmprov grantRight domain $1 usr $2 set.account.sn zmprov grantRight domain $1 usr $2 set.account.displayName zmprov grantRight domain $1 usr $2 set.account.zimbraPasswordMustChange zmprov grantRight account $2 usr $2 +deleteAccount zmprov grantRight account $2 usr $2 +getAccountInfo zmprov grantRight account $2 usr $2 +getAccountMembership zmprov grantRight account $2 usr $2 +listAccount zmprov grantRight account $2 usr $2 +removeAccountAlias zmprov grantRight account $2 usr $2 +renameAccount zmprov grantRight account $2 usr $2 +setAccountPassword zmprov grantRight account $2 usr $2 +viewAccountAdminUI zmprov grantRight account $2 usr $2 +configureQuota ","permalink":"https://www.161616.top/zimbra-admin-script/","summary":"sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #!/bin/bash # $1 domain # $2 email zmprov ma $2 zimbraIsDelegatedAdminAccount TRUE zmprov ma $2 zimbraAdminConsoleUIComponents cartBlancheUI zimbraAdminConsoleUIComponents domainListView zimbraAdminConsoleUIComponents accountListView zimbraAdminConsoleUIComponents DLListView zmprov ma $2 zimbraDomainAdminMaxMailQuota 0 zmprov grantRight domain $1 usr $2 +createAccount zmprov grantRight domain $1 usr $2 +createAlias zmprov grantRight domain $1 usr $2 +createCalendarResource zmprov grantRight domain $1 usr $2 +createDistributionList zmprov grantRight domain $1 usr $2 +deleteAlias zmprov grantRight domain $1 usr $2 +listDomain zmprov grantRight domain $1 usr $2 +domainAdminRights zmprov grantRight domain $1 usr $2 set.","title":"zimbra用户管理员脚本"},{"content":" 一个很好的问题：How golang to get process name by process id (pid)?\n目前看来go api并没有提供通过pid获取进程名称的方法，可以通过 /proc/\u0026lt;pid\u0026gt;/cmdline来获取对应的进程名称，也可以通过 readlink /proc/6530/exe 来获取\n/proc/\u0026lt;pid\u0026gt;/cmdline 获取的为运行进程的名称，通常包含一些特殊字符。例如 \u0026quot;-bash\\x00\u0026quot;，sshd: root@pts/0 readlink /proc/6530/exe 获取的为对应进程运行的程序的路径 go 1 2 pid := os.Getppid() contents, err := ioutil.ReadFile(fmt.Sprintf(\u0026#34;/proc/%d/cmdline\u0026#34;,pid)) go 1 2 pid := os.Getppid() contents, err := os.Readlink(fmt.Sprintf(\u0026#34;/proc/%d/cmdline\u0026#34;,pid)) Reference process name from pid\n","permalink":"https://www.161616.top/goskill-process-id/","summary":"一个很好的问题：How golang to get process name by process id (pid)?\n目前看来go api并没有提供通过pid获取进程名称的方法，可以通过 /proc/\u0026lt;pid\u0026gt;/cmdline来获取对应的进程名称，也可以通过 readlink /proc/6530/exe 来获取\n/proc/\u0026lt;pid\u0026gt;/cmdline 获取的为运行进程的名称，通常包含一些特殊字符。例如 \u0026quot;-bash\\x00\u0026quot;，sshd: root@pts/0 readlink /proc/6530/exe 获取的为对应进程运行的程序的路径 go 1 2 pid := os.Getppid() contents, err := ioutil.ReadFile(fmt.Sprintf(\u0026#34;/proc/%d/cmdline\u0026#34;,pid)) go 1 2 pid := os.Getppid() contents, err := os.Readlink(fmt.Sprintf(\u0026#34;/proc/%d/cmdline\u0026#34;,pid)) Reference process name from pid","title":"如何使用golang通过进程ID找到进程名称"},{"content":"本篇文章中，将描述如何使用go创建CA，并使用CA签署证书。在使用openssl创建证书时，遵循的步骤是 创建秘钥 \u0026gt; 创建CA \u0026gt; 生成要颁发证书的秘钥 \u0026gt; 使用CA签发证书。这种步骤，那么我们现在就来尝试下。\n创建证书的颁发机构 首先，会从将从创建 CA 开始。CA 会被用来签署其他证书\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 对证书进行签名 ca := \u0026amp;x509.Certificate{ SerialNumber: big.NewInt(2019), Subject: pkix.Name{ CommonName: \u0026#34;domain name\u0026#34;, Organization: []string{\u0026#34;Company, INC.\u0026#34;}, Country: []string{\u0026#34;US\u0026#34;}, Province: []string{\u0026#34;\u0026#34;}, Locality: []string{\u0026#34;San Francisco\u0026#34;}, StreetAddress: []string{\u0026#34;Golden Gate Bridge\u0026#34;}, PostalCode: []string{\u0026#34;94016\u0026#34;}, }, NotBefore: time.Now(), // 生效时间 NotAfter: time.Now().AddDate(10, 0, 0), // 过期时间 年月日 IsCA: true, // 表示用于CA // openssl 中的 extendedKeyUsage = clientAuth, serverAuth 字段 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, // openssl 中的 keyUsage 字段 KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, } 接下来需要对证书生成公钥和私钥\ngo 1 2 3 4 caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return err } 然后生成证书：\ngo 1 2 3 4 caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, \u0026amp;caPrivKey.PublicKey, caPrivKey) if err != nil { return err } 我们看到的证书内容是PEM编码后的，现在caBytes我们有了生成的证书，我们将其进行 PEM 编码以供以后使用：\ngo 1 2 3 4 5 6 7 8 9 10 11 caPEM := new(bytes.Buffer) pem.Encode(caPEM, \u0026amp;pem.Block{ Type: \u0026#34;CERTIFICATE\u0026#34;, Bytes: caBytes, }) caPrivKeyPEM := new(bytes.Buffer) pem.Encode(caPrivKeyPEM, \u0026amp;pem.Block{ Type: \u0026#34;RSA PRIVATE KEY\u0026#34;, Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), }) 创建证书 证书的 x509.Certificate 与CA的 x509.Certificate 属性有稍微不同，需要进行一些修改\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 cert := \u0026amp;x509.Certificate{ SerialNumber: big.NewInt(1658), Subject: pkix.Name{ CommonName: \u0026#34;domain name\u0026#34;, Organization: []string{\u0026#34;Company, INC.\u0026#34;}, Country: []string{\u0026#34;US\u0026#34;}, Province: []string{\u0026#34;\u0026#34;}, Locality: []string{\u0026#34;San Francisco\u0026#34;}, StreetAddress: []string{\u0026#34;Golden Gate Bridge\u0026#34;}, PostalCode: []string{\u0026#34;94016\u0026#34;}, }, IPAddresses: []net.IP{}, // 这里就是openssl配置文件中 subjectAltName 里的 IP:/IP= DNSNames: []string{}, // 这里就是openssl配置文件中 subjectAltName 里的 DNS:/DNS= NotBefore: time.Now(), NotAfter: time.Now().AddDate(10, 0, 0), SubjectKeyId: []byte{1, 2, 3, 4, 6}, // 这里就是openssl中的extendedKeyUsage ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature, } 注：这里会在证书中特别添加了 DNS 和 IP （这个不是必须的），这个选项的增加代表的我们的证书可以支持多域名\n为该证书创建私钥和公钥：\ngo 1 2 3 4 certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return err } 使用CA签署证书 有了上述的内容后，可以创建证书并用CA进行签名\ngo 1 2 3 4 certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, \u0026amp;certPrivKey.PublicKey, caPrivKey) if err != nil { return err } 要保存成证书格式需要做PEM编码\ngo 1 2 3 4 5 6 7 8 9 10 11 certPEM := new(bytes.Buffer) pem.Encode(certPEM, \u0026amp;pem.Block{ Type: \u0026#34;CERTIFICATE\u0026#34;, Bytes: certBytes, }) certPrivKeyPEM := new(bytes.Buffer) pem.Encode(certPrivKeyPEM, \u0026amp;pem.Block{ Type: \u0026#34;RSA PRIVATE KEY\u0026#34;, Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), }) 把上面内容融合为一起 创建一个 ca.go 里面是创建ca和颁发证书的逻辑\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 package main import ( \u0026#34;bytes\u0026#34; cr \u0026#34;crypto/rand\u0026#34; \u0026#34;crypto/rsa\u0026#34; \u0026#34;crypto/x509\u0026#34; \u0026#34;crypto/x509/pkix\u0026#34; \u0026#34;encoding/pem\u0026#34; \u0026#34;math/big\u0026#34; \u0026#34;math/rand\u0026#34; \u0026#34;net\u0026#34; \u0026#34;os\u0026#34; \u0026#34;time\u0026#34; ) type CERT struct { CERT []byte CERTKEY *rsa.PrivateKey CERTPEM *bytes.Buffer CERTKEYPEM *bytes.Buffer CSR *x509.Certificate } func CreateCA(sub *pkix.Name, expire int) (*CERT, error) { var ( ca = new(CERT) err error ) if expire \u0026lt; 1 { expire = 1 } // 为ca生成私钥 ca.CERTKEY, err = rsa.GenerateKey(cr.Reader, 4096) if err != nil { return nil, err } // 对证书进行签名 ca.CSR = \u0026amp;x509.Certificate{ SerialNumber: big.NewInt(rand.Int63n(2000)), Subject: *sub, NotBefore: time.Now(), // 生效时间 NotAfter: time.Now().AddDate(expire, 0, 0), // 过期时间 IsCA: true, // 表示用于CA // openssl 中的 extendedKeyUsage = clientAuth, serverAuth 字段 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, // openssl 中的 keyUsage 字段 KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, } // 创建证书 // caBytes 就是生成的证书 ca.CERT, err = x509.CreateCertificate(cr.Reader, ca.CSR, ca.CSR, \u0026amp;ca.CERTKEY.PublicKey, ca.CERTKEY) if err != nil { return nil, err } ca.CERTPEM = new(bytes.Buffer) pem.Encode(ca.CERTPEM, \u0026amp;pem.Block{ Type: \u0026#34;CERTIFICATE\u0026#34;, Bytes: ca.CERT, }) ca.CERTKEYPEM = new(bytes.Buffer) pem.Encode(ca.CERTKEYPEM, \u0026amp;pem.Block{ Type: \u0026#34;RSA PRIVATE KEY\u0026#34;, Bytes: x509.MarshalPKCS1PrivateKey(ca.CERTKEY), }) // 进行PEM编码，编码就是直接cat证书里面内容显示的东西 return ca, nil } func Req(ca *x509.Certificate, sub *pkix.Name, expire int, dns []string, ip []net.IP) (*CERT, error) { var ( cert = \u0026amp;CERT{} err error ) cert.CERTKEY, err = rsa.GenerateKey(cr.Reader, 4096) if err != nil { return nil, err } if expire \u0026lt; 1 { expire = 1 } cert.CSR = \u0026amp;x509.Certificate{ SerialNumber: big.NewInt(rand.Int63n(2000)), Subject: *sub, IPAddresses: ip, DNSNames: dns, NotBefore: time.Now(), NotAfter: time.Now().AddDate(expire, 0, 0), SubjectKeyId: []byte{1, 2, 3, 4, 6}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature, } cert.CERT, err = x509.CreateCertificate(cr.Reader, cert.CSR, ca, \u0026amp;cert.CERTKEY.PublicKey, cert.CERTKEY) if err != nil { return nil, err } cert.CERTPEM = new(bytes.Buffer) pem.Encode(cert.CERTPEM, \u0026amp;pem.Block{ Type: \u0026#34;CERTIFICATE\u0026#34;, Bytes: cert.CERT, }) cert.CERTKEYPEM = new(bytes.Buffer) pem.Encode(cert.CERTKEYPEM, \u0026amp;pem.Block{ Type: \u0026#34;RSA PRIVATE KEY\u0026#34;, Bytes: x509.MarshalPKCS1PrivateKey(cert.CERTKEY), }) return cert, nil } func Write(cert *CERT, file string) error { keyFileName := file + \u0026#34;.key\u0026#34; certFIleName := file + \u0026#34;.crt\u0026#34; kf, err := os.Create(keyFileName) if err != nil { return err } defer kf.Close() if _, err := kf.Write(cert.CERTKEYPEM.Bytes()); err != nil { return err } cf, err := os.Create(certFIleName) if err != nil { return err } if _, err := cf.Write(cert.CERTPEM.Bytes()); err != nil { return err } return nil } 如果需要使用的话，可以引用这些函数\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package main import ( \u0026#34;crypto/x509/pkix\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net\u0026#34; ) func main() { subj := \u0026amp;pkix.Name{ CommonName: \u0026#34;chinamobile.com\u0026#34;, Organization: []string{\u0026#34;Company, INC.\u0026#34;}, Country: []string{\u0026#34;US\u0026#34;}, Province: []string{\u0026#34;\u0026#34;}, Locality: []string{\u0026#34;San Francisco\u0026#34;}, StreetAddress: []string{\u0026#34;Golden Gate Bridge\u0026#34;}, PostalCode: []string{\u0026#34;94016\u0026#34;}, } ca, err := CreateCA(subj, 10) if err != nil { log.Panic(err) } Write(ca, \u0026#34;./ca\u0026#34;) crt, err := Req(ca.CSR, subj, 10, []string{\u0026#34;test.default.svc\u0026#34;, \u0026#34;test\u0026#34;}, []net.IP{}) if err != nil { log.Panic(err) } Write(crt, \u0026#34;./tls\u0026#34;) } 遇到的问题 panic: x509: unsupported public key type: rsa.PublicKey\n这里是因为 x509.CreateCertificate 的参数 privatekey 需要传入引用变量，而传入的是一个普通变量\n注：x509: only RSA and ECDSA public keys supported\n一些参数的意思 extendedKeyUsage ：增强型密钥用法(参见\u0026quot;new_oids\u0026quot;字段)：服务器身份验证、客户端身份验证、时间戳。\ncnf extendedKeyUsage = critical,serverAuth, clientAuth, timeStamping keyUsage ： 密钥用法，防否认(nonRepudiation)、数字签名(digitalSignature)、密钥加密(keyEncipherment)。\ntext 1 keyUsage = nonRepudiation, digitalSignature, keyEncipherment Reference golang ca and signed cert go\npackage x509\n","permalink":"https://www.161616.top/goskill-x509-in-go/","summary":"本篇文章中，将描述如何使用go创建CA，并使用CA签署证书。在使用openssl创建证书时，遵循的步骤是 创建秘钥 \u0026gt; 创建CA \u0026gt; 生成要颁发证书的秘钥 \u0026gt; 使用CA签发证书。这种步骤，那么我们现在就来尝试下。\n创建证书的颁发机构 首先，会从将从创建 CA 开始。CA 会被用来签署其他证书\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 对证书进行签名 ca := \u0026amp;x509.Certificate{ SerialNumber: big.NewInt(2019), Subject: pkix.Name{ CommonName: \u0026#34;domain name\u0026#34;, Organization: []string{\u0026#34;Company, INC.\u0026#34;}, Country: []string{\u0026#34;US\u0026#34;}, Province: []string{\u0026#34;\u0026#34;}, Locality: []string{\u0026#34;San Francisco\u0026#34;}, StreetAddress: []string{\u0026#34;Golden Gate Bridge\u0026#34;}, PostalCode: []string{\u0026#34;94016\u0026#34;}, }, NotBefore: time.Now(), // 生效时间 NotAfter: time.Now().AddDate(10, 0, 0), // 过期时间 年月日 IsCA: true, // 表示用于CA // openssl 中的 extendedKeyUsage = clientAuth, serverAuth 字段 ExtKeyUsage: []x509.","title":"使用go语言颁发CA证书"},{"content":"TCP的三次握手 所谓三次握手 Three-Way Handshake 是指建立一个TCP连接时，需要客户端和服务端总共发送3个包以确认连接的建立。好比两个人在打电话：\n当连接被建立或被终止，交换的报文段只包含TCP头部，而没有数据。\ntcp报文头部结构 序号：seq序号，占32位，用来标识从TCP源端向目的端发送的字节流，发起方发送数据时对此进行标记。 确认序号：ack序号，占32位，只有ACK标志位为1时，确认序号字段才有效，确认方ack=发起方seq+1，两端配对。 标志位 ACK：确认序号有效。 FIN：释放一个连接。 RST：重置连接。 SYN：发起一个新连接。 PSH：接收方应该尽快将这个报文交给应用层。 URG：紧急指针（urgent pointer）有效。 第一次握手：客户端要向服务端发起连接请求，首先客户端随机生成一个起始序列号ISN(比如是100)，那客户端向服务端发送的报文段包含SYN标志位(也就是SYN=1)，序列号seq=100。\n第二次握手：服务端收到客户端发过来的报文后，发现SYN=1，知道这是一个连接请求，于是将客户端的起始序列号100存起来，并且随机生成一个服务端的起始序列号(比如是300)。然后给客户端回复一段报文，回复报文包含SYN和ACK标志(也就是SYN=1,ACK=1)、序列号seq=300、确认号ack=101(客户端发过来的序列号+1)。\n第三次握手：客户端收到服务端的回复后发现ACK=1并且ack=101,于是知道服务端已经收到了序列号为100的那段报文；同时发现SYN=1，知道了服务端同意了这次连接，于是就将服务端的序列号300给存下来。然后客户端再回复一段报文给服务端，报文包含ACK标志位(ACK=1)、ack=301(服务端序列号+1)、seq=101(第一次握手时发送报文是占据一个序列号的，所以这次seq就从101开始，需要注意的是不携带数据的ACK报文是不占据序列号的，所以后面第一次正式发送数据时seq还是101)。当服务端收到报文后发现ACK=1并且ack=301，就知道客户端收到序列号为300的报文了，就这样客户端和服务端通过TCP建立了连接。\n四次挥手 比如客户端初始化的序列号ISA=100，服务端初始化的序列号ISA=300。TCP连接成功后客户端总共发送了1000个字节的数据，服务端在客户端发FIN报文前总共回复了2000个字节的数据。\n第一次挥手：当客户端的数据都传输完成后，客户端向服务端发出连接释放报文(当然数据没发完时也可以发送连接释放报文并停止发送数据)，释放连接报文包含FIN标志位(FIN=1)、序列号seq=1101(100+1+1000，其中的1是建立连接时占的一个序列号)。需要注意的是客户端发出FIN报文段后只是不能发数据了，但是还可以正常收数据；另外FIN报文段即使不携带数据也要占据一个序列号。\n第二次挥手：服务端收到客户端发的FIN报文后给客户端回复确认报文，确认报文包含ACK标志位(ACK=1)、确认号ack=1102(客户端FIN报文序列号1101+1)、序列号seq=2300(300+2000)。此时服务端处于关闭等待状态，而不是立马给客户端发FIN报文，这个状态还要持续一段时间，因为服务端可能还有数据没发完。\n第三次挥手：服务端将最后数据(比如50个字节)发送完毕后就向客户端发出连接释放报文，报文包含FIN和ACK标志位(FIN=1,ACK=1)、确认号和第二次挥手一样ack=1102、序列号seq=2350(2300+50)。\n第四次挥手：客户端收到服务端发的FIN报文后，向服务端发出确认报文，确认报文包含ACK标志位(ACK=1)、确认号ack=2351、序列号seq=1102。注意客户端发出确认报文后不是立马释放TCP连接，而是要经过2MSL(最长报文段寿命的2倍时长)后才释放TCP连接。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接，所以服务端结束TCP连接的时间要比客户端早一些。\n","permalink":"https://www.161616.top/go-tcp-hadshake/","summary":"TCP的三次握手 所谓三次握手 Three-Way Handshake 是指建立一个TCP连接时，需要客户端和服务端总共发送3个包以确认连接的建立。好比两个人在打电话：\n当连接被建立或被终止，交换的报文段只包含TCP头部，而没有数据。\ntcp报文头部结构 序号：seq序号，占32位，用来标识从TCP源端向目的端发送的字节流，发起方发送数据时对此进行标记。 确认序号：ack序号，占32位，只有ACK标志位为1时，确认序号字段才有效，确认方ack=发起方seq+1，两端配对。 标志位 ACK：确认序号有效。 FIN：释放一个连接。 RST：重置连接。 SYN：发起一个新连接。 PSH：接收方应该尽快将这个报文交给应用层。 URG：紧急指针（urgent pointer）有效。 第一次握手：客户端要向服务端发起连接请求，首先客户端随机生成一个起始序列号ISN(比如是100)，那客户端向服务端发送的报文段包含SYN标志位(也就是SYN=1)，序列号seq=100。\n第二次握手：服务端收到客户端发过来的报文后，发现SYN=1，知道这是一个连接请求，于是将客户端的起始序列号100存起来，并且随机生成一个服务端的起始序列号(比如是300)。然后给客户端回复一段报文，回复报文包含SYN和ACK标志(也就是SYN=1,ACK=1)、序列号seq=300、确认号ack=101(客户端发过来的序列号+1)。\n第三次握手：客户端收到服务端的回复后发现ACK=1并且ack=101,于是知道服务端已经收到了序列号为100的那段报文；同时发现SYN=1，知道了服务端同意了这次连接，于是就将服务端的序列号300给存下来。然后客户端再回复一段报文给服务端，报文包含ACK标志位(ACK=1)、ack=301(服务端序列号+1)、seq=101(第一次握手时发送报文是占据一个序列号的，所以这次seq就从101开始，需要注意的是不携带数据的ACK报文是不占据序列号的，所以后面第一次正式发送数据时seq还是101)。当服务端收到报文后发现ACK=1并且ack=301，就知道客户端收到序列号为300的报文了，就这样客户端和服务端通过TCP建立了连接。\n四次挥手 比如客户端初始化的序列号ISA=100，服务端初始化的序列号ISA=300。TCP连接成功后客户端总共发送了1000个字节的数据，服务端在客户端发FIN报文前总共回复了2000个字节的数据。\n第一次挥手：当客户端的数据都传输完成后，客户端向服务端发出连接释放报文(当然数据没发完时也可以发送连接释放报文并停止发送数据)，释放连接报文包含FIN标志位(FIN=1)、序列号seq=1101(100+1+1000，其中的1是建立连接时占的一个序列号)。需要注意的是客户端发出FIN报文段后只是不能发数据了，但是还可以正常收数据；另外FIN报文段即使不携带数据也要占据一个序列号。\n第二次挥手：服务端收到客户端发的FIN报文后给客户端回复确认报文，确认报文包含ACK标志位(ACK=1)、确认号ack=1102(客户端FIN报文序列号1101+1)、序列号seq=2300(300+2000)。此时服务端处于关闭等待状态，而不是立马给客户端发FIN报文，这个状态还要持续一段时间，因为服务端可能还有数据没发完。\n第三次挥手：服务端将最后数据(比如50个字节)发送完毕后就向客户端发出连接释放报文，报文包含FIN和ACK标志位(FIN=1,ACK=1)、确认号和第二次挥手一样ack=1102、序列号seq=2350(2300+50)。\n第四次挥手：客户端收到服务端发的FIN报文后，向服务端发出确认报文，确认报文包含ACK标志位(ACK=1)、确认号ack=2351、序列号seq=1102。注意客户端发出确认报文后不是立马释放TCP连接，而是要经过2MSL(最长报文段寿命的2倍时长)后才释放TCP连接。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接，所以服务端结束TCP连接的时间要比客户端早一些。","title":"通过Go语言中阐述TCP Handshake"},{"content":"正则表达式是一种进行模式匹配和文本操纵的复杂而又强大的工具。虽然正则表达式比纯粹的文本匹配效率低，但是它却更灵活。按照它的语法规则，随需构造出的匹配模式就能够从原始文本中筛选出几乎任何你想要得到的字符组合。\nGo语言通过regexp（regular expression）标准包为正则表达式提供了官方支持，包名采用regular expression的每个单词的前三个首字母组成。\nGo语言的正则表达式实现的是RE2标准，Go语言的正则表达式与其他编程语言之间也有一些小的差异。\n正则表达式规则 go语言中regexp包使用 简单来说，Go语言中使用正则表达式只需要两步即可：\n解析、编译正则表达式 regexp.MustCompile() 返回一个regexp结构体 根据解析好的规则（结构体形式），从指定字符串中提取需要的信息。如 MatchString() FindAllSubmatch()等 go 1 2 3 4 5 6 7 8 9 10 11 12 13 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;regexp\u0026#34; ) func main() { rege := regexp.MustCompile(`(\\d{1,3}\\.){3}\\d{1,3}`) str := rege.FindAllString(\u0026#34;SLAJDLKAJ192.168.0.1DASDASA1231\u0026#34;, -1) fmt.Println(str) } ","permalink":"https://www.161616.top/go-regular-expression/","summary":"正则表达式是一种进行模式匹配和文本操纵的复杂而又强大的工具。虽然正则表达式比纯粹的文本匹配效率低，但是它却更灵活。按照它的语法规则，随需构造出的匹配模式就能够从原始文本中筛选出几乎任何你想要得到的字符组合。\nGo语言通过regexp（regular expression）标准包为正则表达式提供了官方支持，包名采用regular expression的每个单词的前三个首字母组成。\nGo语言的正则表达式实现的是RE2标准，Go语言的正则表达式与其他编程语言之间也有一些小的差异。\n正则表达式规则 go语言中regexp包使用 简单来说，Go语言中使用正则表达式只需要两步即可：\n解析、编译正则表达式 regexp.MustCompile() 返回一个regexp结构体 根据解析好的规则（结构体形式），从指定字符串中提取需要的信息。如 MatchString() FindAllSubmatch()等 go 1 2 3 4 5 6 7 8 9 10 11 12 13 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;regexp\u0026#34; ) func main() { rege := regexp.MustCompile(`(\\d{1,3}\\.){3}\\d{1,3}`) str := rege.FindAllString(\u0026#34;SLAJDLKAJ192.168.0.1DASDASA1231\u0026#34;, -1) fmt.Println(str) } ","title":"正则表达式在go中使用"},{"content":"什么是块存储 RBD Ceph RBD (RADOS Block Device) 是 Ceph 提供的三种存储类型之一 (块存储 RBD, 文件存储 CephFS, 对象存储 RGW)，也是另外两个存储类型 (文件存储 CephFS, 对象存储 RGW) 的底座，位于 RADOS 架构中的最底层，由下图可以看出\n图：Ceph RADOS架构图\rSource：https://www.supportsages.com/ceph-part-3-technical-architecture-and-components/\nRADOS 是可信赖的自动分布式对象存储 (Reliable Autonomous Distributed Object Store) 的简写，通俗来说，RADOS 代表的就是整个 Ceph 集群，数据对象在集群中的存储方式会“将对象复制为多副本” 以实现容错，所以 Ceph 集群的底座就是 RADOS，一个 RADOS 集群的组件通常包含三个，OSD Daemon , MDS, MON\nObject Storage Device (OSD) Daemon：RADOS集群中负责存储守护进程，与 OSD (数据的物理或逻辑存储单元【通常指一个硬盘】)交互。集群中的每个 Ceph Node 都必须运行 OSD Daemon。对于每个 OSD，可以有一个关联的硬盘 (通常一个OSD Daemon 对应一个存储单元)。 MONITORS (Mon Daemon)：Monitor (ceph-mon) 不是集群存储组件的一部分，但它通过监视 OSD 状态并生成 “Cluster Map” 而成为 RADOS 不可或缺的一部分。它监视 OSD 并跟踪在给定时间点哪些 OSD 处于运行状态、哪些 OSD 处于对等状态、OSD 的状态等。一般来说，它充当存储集群中所有 OSD 的 Monitor Manager (MGR Daemon)：Manager (ceph-mgr) 是与 ceph-mon 一同运行的守护进程，为外部监控和管理系统提供额外的监视和接口。默认情况下，ceph-mgr 除了确保其正在运行之外不需要其他配置。如果没有运行 ceph-mgr，ceph -s 将会看到一条 WARN；不管是使用什么方式部署的集群 ( ceph-deploy, cephadm)，ceph-mgr 总会 与 ceph-mon 同时运行在一个节点上，也可单独运行在 Ceph Node 之上。 通常 Monitor (ceph-mon) 不构成“存储”集群的一部分，只是通过监视 OSD 状态并生成 Cluster map 而成为 ceph存储集群中不可缺少的组件。它通过监视 OSD 并跟踪在给定时间点哪些 OSD 处于运行状态、哪些 OSD 处于对等状态、OSD 的状态等。\n如何确定 ceph-mon 的数量 ceph-mon 的数量最好是奇数，并且数量个数时有限限制的，这里在总结一下 ceph monitor 的作用：\nMonitor 不向客户端提供存储的对象，由于它只是一个监控节点，因此它不保存/管理任何对象，也不属于对象存储的一部分，但它仍然是 RADOS 的一部分，但不用于数据/对象存储。 它维护簇映射状态，即：存储集群客户端/应用程序从 ceph-mon 检索集群映射的副本 通常很奇怪并且数量有限，因为他们的工作相当简单，维护 OSD 的状态。 为分布式决策提供共识，当要针对 OSD 的状态做出特定决策时，它提供了一般规则。 当多个 OSD 要进行对等互连或复制时，监视器会自行决定如何进行对等互连，而不是由 ODS 自行决定。 Ceph OSD 守护进程检查自身状态和其他 OSD 的状态并向监视器报告。 中国铁路2020年分享的 1550+ OSD PB 级别存储 ceph-con 数量从 “3” 升级到 “5” 后，趋势稳定 [1]\nRBD 存储单元 RBD 块设备 (RADOS Block Device) 在 Ceph 中被称为 image。image 由 “元数据” 和 ”数据“两部分组成，其中元数据存储在多个特殊的 RADOS 对象中，而数据被自动”条带化“成多个 RADOS 对象进行存储。除了 image 自身的元数据之外，在 image 所属的 存储池 (Pool) 中都还有一组特殊的 RADOS 对象记录 image 关联关系或附加信息等相关的 RBD 管理元数据。所有的数据对象和元数据对象都依据 CRUSH 规则 (CRUSH RULE) 存储在底层的 OSD 设备上，因此 RBD 块设备自动继承了 RADOS 对象的数据冗余保护机制和一致性策略。\n对于 RBD Image 官方是这么描述的 [2]\nRBD images are simple block devices that are striped over objects and stored in a RADOS object store. The size of the objects the image is striped over must be a power of two.\n由此，我们可以得知，”镜像“ (RBD IMAGE) 的表现就是一个块设备\nPOOL 存储池是用于存储对象的逻辑分区，一个存储池中可以放置多个”IMAGE“。在官方给出的，存储池提供了如下特性:\n池提供以下功能：\n冗余性 (Resilience)：可以设置允许失败的 OSD 数量，而不会丢失任何数据。如果您的集群使用 ”复制池“ (replicated )，那么可以容忍的 OSD 故障数量等于复制的数量。\n例如：典型配置存储一个对象及其每个 RADOS 对象的两个副本（即size=3，默认值），但您可以根据池的需求配置副本数量。对于纠删码池 (erasure-coded)，冗余性定义为编码块的数量（例如，默认纠删码配置文件中的m = 2）。\n放置组（Placement Groups，PGs）：您可以为池设置放置组（PGs）的数量。在典型配置中，每个 OSD 的目标 PG 数量约为 100 个 PGs。这提供了合理的负载均衡，而不会消耗过多的计算资源。在设置多个存储池时，请小心为每个存储池和整个集群设置合适数量的 PGs。每个 PG 都属于特定的存储池：当多个存储池使用相同的 OSDs 时，请确保每个 OSD 上的 PG 副本总数在所需的 “每个 OSD 上 PG数量的目标范围内”。要计算适合您的池的PG数量，请使用 pgcalc [3] 工具。\nCRUSH规则：当数据存储在池中时，对象及其副本（或在纠删码池中的块）在您的集群中的放置由 CRUSH 规则管理。如果默认规则不适合您的用例，可以为池创建自定义 CRUSH 规则。\n快照：命令 ceph osd pool mksnap 可创建基于存储池的快照。\nlibrdb 和 librados 要想使用块设备，就涉及到 RADOS 之上数据的交互，我们已经了解到了 RBD 是为 KVM 等虑拟化技术和云 OS（如 OpenStack 和 CloudStack）提供高性能和无限可扩展性的存储后端，这些系统依赖于 libvirt 和 QEMU 实用程序与 RBD 进行集成。\n在Ceph中，提供了一种 librados 的库，它可以使得 客户端 与 Ceph 集群间的交互，在 RADOS 之上 存在一个 \u0026ldquo;librados\u0026quot;库，而 \u0026ldquo;librbd\u0026rdquo; 库则是构建与 “librados” 之上的的库，提供了块存储的功能，下面是两个库在“用途”和“功能”上有一些重要区别：\nlibrados（RADOS客户端库）： 用途：librados是用于与Ceph的底层RADOS存储层交互的库。它提供了与Ceph集群中的对象存储进行直接通信的API，允许应用程序执行各种存储操作，如读取、写入、删除和管理存储对象。 功能：librados允许应用程序直接访问Ceph集群，而不需要高级抽象，这意味着应用程序可以更精细地控制数据的读写和存储策略。librados可以用于构建自定义数据存储和管理解决方案。 librbd（RBD客户端库）： 用途：librbd是用于与Ceph中的RBD（Rados Block Device）存储层交互的库。它构建在librados之上，提供了更高级别的抽象，用于处理块设备（镜像）操作，如创建、映射、快照和克隆等。 功能：librbd简化了块设备操作，并提供了易于使用的API，使应用程序能够轻松地管理RBD镜像。它是构建虚拟化平台、云存储、容器存储等应用的关键组件。 要使用 RBD 存储池需要先启用，而却需要注意的是 rbd 存储池并不能直接用于块设备，而是需要事先在其中按需创建映像（image），而 “映像” 代表了真正的块设备\nRBB 支持的功能 快照 排他锁/独占锁 镜像 实时迁移 .. 快照 RBD 快照 (snapshot) 是 image 在某个特定时间点的只读逻辑副本，类似于虚拟机的快照，快照也是 RBD 的高级功能之一，使得可以用户创建映像快照以保留时间点状态历史记录。 Ceph还支持快照分层，使您可以快速轻松地克隆镜像（例如虚拟机镜像）。 Ceph RBD 快照的使用通过命令 rbd 和几个更高级的接口进行管理，包括 QEMU, libvirt, OpenStack 和 CloudStack。\n由于 Ceph RBD 不知道 “镜像” 内的任何文件系统，因此快照仅是崩溃一致的，除非它们在挂载(mouting) 操作系统内进行协调。因此，我们建议您在拍摄快照之前暂停或停止 I/O。\n如果镜像包含文件系统，则在拍摄快照之前文件系统应处于内部一致状态。即需要停止IO\n快照的操作\nceph 有专门的命令 rbd 可以进行 RBD 相关的操作，例如快照的命令为 rbd snapshot\n命令 语法 样例 创建快照 rbd snap create {pool-name}/{image-name}@{snap-name} rbd snap create rbd/foo@snapname 列出快照 rbd snap ls {pool-name}/{image-name} rbd snap ls rbd/foo 回滚快照 rbd snap rollback {pool-name}/{image-name}@{snap-name} rbd snap rollback rbd/foo@snapname 删除一个快照 rbd snap rm {pool-name}/{image-name}@{snap-name} rbd snap rm rbd/foo@snapname 清除所有快照 rbd snap purge {pool-name}/{image-name} rbd snap purge rbd/foo 列出快照 Reference [1] 中国铁路：Ceph在单集群1551个OSD下的挑战\n[2] rbd \u0026ndash; manage rados block device (RBD) images\n[3] pgcalc\n","permalink":"https://www.161616.top/ch03-1-acquaintance-rdb/","summary":"什么是块存储 RBD Ceph RBD (RADOS Block Device) 是 Ceph 提供的三种存储类型之一 (块存储 RBD, 文件存储 CephFS, 对象存储 RGW)，也是另外两个存储类型 (文件存储 CephFS, 对象存储 RGW) 的底座，位于 RADOS 架构中的最底层，由下图可以看出\n图：Ceph RADOS架构图\rSource：https://www.supportsages.com/ceph-part-3-technical-architecture-and-components/\nRADOS 是可信赖的自动分布式对象存储 (Reliable Autonomous Distributed Object Store) 的简写，通俗来说，RADOS 代表的就是整个 Ceph 集群，数据对象在集群中的存储方式会“将对象复制为多副本” 以实现容错，所以 Ceph 集群的底座就是 RADOS，一个 RADOS 集群的组件通常包含三个，OSD Daemon , MDS, MON\nObject Storage Device (OSD) Daemon：RADOS集群中负责存储守护进程，与 OSD (数据的物理或逻辑存储单元【通常指一个硬盘】)交互。集群中的每个 Ceph Node 都必须运行 OSD Daemon。对于每个 OSD，可以有一个关联的硬盘 (通常一个OSD Daemon 对应一个存储单元)。 MONITORS (Mon Daemon)：Monitor (ceph-mon) 不是集群存储组件的一部分，但它通过监视 OSD 状态并生成 “Cluster Map” 而成为 RADOS 不可或缺的一部分。它监视 OSD 并跟踪在给定时间点哪些 OSD 处于运行状态、哪些 OSD 处于对等状态、OSD 的状态等。一般来说，它充当存储集群中所有 OSD 的 Monitor Manager (MGR Daemon)：Manager (ceph-mgr) 是与 ceph-mon 一同运行的守护进程，为外部监控和管理系统提供额外的监视和接口。默认情况下，ceph-mgr 除了确保其正在运行之外不需要其他配置。如果没有运行 ceph-mgr，ceph -s 将会看到一条 WARN；不管是使用什么方式部署的集群 ( ceph-deploy, cephadm)，ceph-mgr 总会 与 ceph-mon 同时运行在一个节点上，也可单独运行在 Ceph Node 之上。 通常 Monitor (ceph-mon) 不构成“存储”集群的一部分，只是通过监视 OSD 状态并生成 Cluster map 而成为 ceph存储集群中不可缺少的组件。它通过监视 OSD 并跟踪在给定时间点哪些 OSD 处于运行状态、哪些 OSD 处于对等状态、OSD 的状态等。","title":"Ceph RBD - 初识块存储RBD"},{"content":" openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 Overview 如果要使Linux账号通过LDAP进行身份认证，就需要配置Linux的 身份验证模块 (Pluggable Authentication Modules) 与 名称服务交换系统 (Name Service Switch) 与LDAP交互。\nPAM 和 NSS [3] NSS (name service switch) 通俗理解为是一个数据库系统，他作用是用于如何将操作系统与各种名称的解析机制关联起来，例如主机名，用户名，组名等内容的查找；例如UID查找使用 passwd 库，GID的查找使用 group 库，并且还可以告知查找的来源，如文件，LDAP等\nPAM (Pluggable Authentication Modules) 全称是可插拔的认证模块，PAM在Linux中是位于用户数据库与应用之间的认证模块，它本身并不工作，并且本身也不提供或扩展现有数据库系统，当登陆shell时，依赖于由NSS提供的密码库与组库等信息，完成对应的查询\n例如下列两张图完整的阐述了PAM与NSS之间，在用户登陆时做了些什么\n图：pam和nss工作示意图1\rSource：https://medium.com/@fengliplatform/understanding-nss-and-pam-using-a-ssh-example-80512eb0f39e\n由图可以看出，当在进行 ping , id 等操作时，会通过nss找到 passwd 库找到用户id，以及通过nss确定是 hosts解析还是dns服务解析对应的域名\n如果这张图不明白可以看下一张图\n图：pam和nss工作示意图2-1\rSource：https://medium.com/@fengliplatform/understanding-nss-and-pam-using-a-ssh-example-80512eb0f39e\n图2-1 中使用了tom用户去登录pecan主机，此时在节点 yam 上，将寻找 pecan主机的IP，这是通过 /etc/nsswitch.conf 来确定是通过 hosts 还是 dns服务进行查找。\n接下来找到pecan的IP，这里会输入用户名与密码，这里将会被sshd服务接管，此时 pecan 主机的sshd接收到用户端请求连接后，将用户名通过nss进行识别，确定是否为合法用户，如果用户有效，则通过PAM进行认证。认证的源也将由 /etc/nsswitch.conf 中配置的对应 passwd 库来找到，例如ldap,file等。正如下图2-2所示\n图：pam和nss工作示意图2-2\rSource：https://medium.com/@fengliplatform/understanding-nss-and-pam-using-a-ssh-example-80512eb0f39e\nLinux with LDAP [1] 在大致了解了Linux登录认证的原理后，知道了要使Linux使用LDAP需要配置两个部分，NSS与PAM，通常有下述几种方案：\nNSS + PAM SSSD (System Security Services Daemon)，SSSD是提供严重的一种工具，可以包含多种源例如LDAP，AD，Kerberos 等，并且提供了缓存功能（当ldap不可用时提供服务） 配置NSS 安装 nss-pam-ldapd\nCentOS/Redhat：\tyum install -y nss-pam-ldapd Debian/Ubuntu：apt-get install libnss-ldapd 配置 /etc/nsswitch.conf ，该文件保存了各数据库，需要对 group , passwd , shadow 库开启 ldap\ntext 1 2 3 passwd: files sss ldap shadow: files sss ldap group: files sss ldap 配置PAM CentOS/Redhat：\tyum install -y pam_ldap Debian/Ubuntu：apt-get install libpam-ldapd 要想通过本地 Sudo 实现 OpenLDAP 用户提权，可按以下步骤操作。\nsh 1 2 3 4 5 6 7 8 9 authconfig \\ --enableldap \\ --enableldapauth \\ --ldapserver=ldap://10.0.0.10:389 \\ --ldapbasedn=\u0026#34;dc=test,dc=org\u0026#34; \\ --enablemkhomedir \\ --ldapbasedn=\u0026#34;ou=tvb,dc=test,dc=org\u0026#34; \\ --enableshadow \\ --update \\ Notes：\nCentOS/RedHat 也可以使用 SSSD替代，yum install -y sssd ，上面提供的方案是NSS+PAM 通常情况下Ubuntu会使用 SSSD 服务替代，SSSD包含了 PAM模块 和 NSS模块 Ubuntu中，没有被 authconfig 没有被打包在 SSSD 中，通常需要安装 sudo apt install ldap-auth-config Linux用户权限控制 部署此功能的原因是能够在所有基础结构服务器上仅使用一个用户，并且无需每次在每台服务器上手动更新 /etc/sudoers 文件，即可为此用户提供sudo权限。现在，这些天您可以使用像ansible这样的工具来执行此操作，但是并不是说OpenLDAP用法必须仅用于posixGroup用户访问，因此OpenLDAP只擅长它。OpenLDAP集成应扩展到您部署的每个集中式系统，并且您唯一的“管理员”用户可以访问基础架构范围内的所有系统。\nsudoers在slapd部分配置 要使 OpenLDAP 服务端实现用户权限控制，具体的实施步骤可以大致分为如下几步：\n创建 ldap 中 sudoers容器，并创建默认的搜索域\n为 slapd 导入sudo schema\n定义sudo规则条目及sudo组\n通过手动定义用户加入sudo组，集成sudo权限\n命令添加及修改 通过转换 本地 sudoers 配置文件 为LDAP ldif格式文件\nOpenLDAP 提供的 perl脚本 sudoers2ldif ，通常会看到，如果没有可以从 Github 中下载 sudo 提供的转换命令 cvtsudoers 图形化管理界面配置\n客户端配置加入OpenLDAP服务端\n客户端识别sudo策略及验证用户权限\nOpenLDAP服务端导入sudo schema sh 1 2 3 4 5 6 7 8 9 10 11 12 13 # 找到sudo的openldap schema rpm -ql sudo | grep schema # 将其复制到openldap schema目录 cp /usr/share/doc/sudo-1.8.23/schema.OpenLDAP /etc/openldap/schema/sudo.schema # 生成include sudo.ldif echo \u0026#34;include /etc/openldap/schema/sudo.schema\u0026#34; \u0026gt; /tmp/sudo.conf slapcat -f /tmp/sudo.conf -F /tmp/ -n 0 -s \u0026#34;cn={0}sudo,cn=schema,cn=config\u0026#34; \u0026gt; /tmp/sudo.ldif # 最后需要删除后面几行 sed -i \u0026#34;s@{0}sudo@sudo@g\u0026#34; /tmp/sudo.ldif head -n -8 /tmp/sudo.ldif \u0026gt; /etc/openldap/schema/sudo.ldif 生成的对应schema在：/etc/openldap/slapd.d/cn=config/cn=schema/\n为 OpenLDAP 创建 suers 容器 创建ldap的sudoers容器，官网有给出提示，sudoers如果在ldap中使用必须放在 ou=SUDOers 中，其中 cn=default 为最先被查找的条目\nThe sudoers configuration is contained in the ‘ou=SUDOers’ LDAP container. [2]\n默认情况下，sudo检索的域为 cn=default,ou=SUDOers,dc=xx,dc=xx 如果找到，那么该条目中所有 sudoOption 属性都会被解析为全局默认值，这类似于服务端中查询一个 sudo 用户权限时一般有两到三次查询。\n第一次查询解析全局配置 第二次查询匹配用户名或者用户所在的组（特殊标签 ALL 也在此次查询中匹配），如果没有找到相关匹配项，则发出第三次查询，此次查询返回所有包含用户组的条目并检查该用户是否存在于这些组中 下面命令是创建一个 sudoers 在 ldap中的根容器 ou=SUDOers ，这个步骤总是需要手动执行，因为默认通过 /etc/sudoers 转换过来的 ldif 是不带这个域的\nbash 1 2 3 4 5 $ cat \u0026lt;\u0026lt; EOF | ldapadd -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 -H ldap://10.0.0.10:389 dn: ou=SUDOers,dc=test,dc=com objectclass: organizationalunit ou: SUDOers EOF 下面步骤是将 /etc/sudoers 转换为 ldap ldif\n创建一个环境变量 SUDOERS_BASE ，这个在perl脚本执行的必须条件。\nbash 1 export SUDOERS_BASE=\u0026#34;ou=SUDOers,dc=test,dc=com\u0026#34; 接下来，使用sudo包中提供了一个命令 cvtsudoers 可以将 sudoers的配置文件 /etc/sudoers 转换为LDAP ldif格式文件\nbash 1 cvtsudoers /etc/sudoers -f ldif -o sudoers_defaults.ldif 另外，通过脚本将 sudoers的配置文件 /etc/sudoers 转换为LDAP ldif格式的文件，将用这个来创建默认的cn=default 的sudoers\nbash 1 perl sudoers2ldif /etc/sudoers \u0026gt; sudoers_defaults.ldif Notes：openldap还有一种操作为 ldif to schema，通过下述命令可以完成 [4]\nbash 1 sed \u0026#39;/^dn: /d;/^objectClass: /d;/^cn: /d;s/olcAttributeTypes:/attributetype/g;s/olcObjectClasses:/objectclass/g\u0026#39; file.ldif \u0026gt; file.schema 接下来将这些配置导入到ldap中\nbash 1 ldapadd -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 -H ldap://10.0.0.10:389 -f sudoers_defaults.ldif 配置客户端主机支持sudo over ldap 在客户端，需要两个额外步骤。\n需要添加： /etc/nsswitch.conf\nsh 1 2 3 4 ## 增加 sudoers: ldap files echo \u0026#39;sudoers: ldap files\u0026#39; \u0026gt;\u0026gt; /etc/nsswitch.conf 需要提供：/etc/sudo-ldap.conf\nsh 1 2 3 4 binddn cn=clientsearch,ou=admin,dc=cylon,dc=org bindpw 111 uri ldap://10.0.0.20 sudoers_base ou=sudoers,dc=cylon,dc=org Troubleshooting 错误：invalid structural object class chain\nbash 1 2 ldap_add: Object class violation (65) additional info: invalid structural object class chain (groupOfUniqueNames/posixGroup) 原因：在ldap中 groupOfUniqueNames / posixGroup / inetOrgPerson 实际上是同一种类型 [6] ，级别的对象组，其 objectClass 只能选择包含一个，要 gidNumber 就不能有 memberOf 了。\n问题出于：对于筛选来说，posixGroup 组并不支持 memberOf 属性，这种情况下可能无法做到权限的筛选与用户的筛选等，但是对于Linux 使用ldap管理用户权限时，普通的 groupOfUniqueNames 并不带有 gidNumber 属性，使得用户没有组，这时配置的组的权限，sudo实际不生效。\n解决：\n为 posixGroup 生成一个 memberOf 属性 [5]，这里在尝试导入 ldif 文件时 slapd 生成配置失败无报错 直接使用 posixGroup 替换组 groupOfUniqueNames posixGroup 使用的是 memberUid 进行关联，在检索时，可以使用 uid=memberUid 进行过滤 本质上 posixGroup 与 groupOfUniqueNames 只是对组，没有对用户 需要配置 refint 解决引用关系一致性问题，正如下列给出配置一样 ldif dn: olcOverlay=refint,olcDatabase={1}mdb,cn=config\robjectClass: olcConfig\robjectClass: olcOverlayConfig\robjectClass: olcRefintConfig\robjectClass: top\rolcOverlay: refint\rolcRefintAttribute: memberUid uid\rolcRefintNothing: cn=default,dc=test,dc=com Reference [1] Configure OpenLDAP login for CentOS 7\n[2] sudoers.ldap.man\n[3] Understanding nss and pam using a ssh example\n[4] Convert AD-Schema from *.ldif to *.schema\n[5] GENERATING A MEMBEROF ATTRIBUTE FOR POSIXGROUPS\n[6] how to fix (65) invalid structural object class chain (posixGroup/groupOfNames)?\n[7] Configure OpenLDAP login for CentOS 7\n[8] Linux LDAP Authentication\n[9] Linux user management with LDAP\n[10] 2. LDAP authentication using pam_ldap and nss_ldap\n[11] How to Configure SUDO via OpenLDAP Server\n[12] SUDOers from OpenLDAP\n[13] OpenLDAPSudo权限讲解-OpenLDAPSudo权限讲解\n[14] authconfig\n[15] Configuring PAM Authentication and User Mapping with LDAP Authentication\n","permalink":"https://www.161616.top/ch10-linux-with-ldap/","summary":"openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 Overview 如果要使Linux账号通过LDAP进行身份认证，就需要配置Linux的 身份验证模块 (Pluggable Authentication Modules) 与 名称服务交换系统 (Name Service Switch) 与LDAP交互。\nPAM 和 NSS [3] NSS (name service switch) 通俗理解为是一个数据库系统，他作用是用于如何将操作系统与各种名称的解析机制关联起来，例如主机名，用户名，组名等内容的查找；例如UID查找使用 passwd 库，GID的查找使用 group 库，并且还可以告知查找的来源，如文件，LDAP等","title":"理解ldap - Linux系统接入OpenLDAP做认证后端"},{"content":" openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 memberOf 默认情况下，openldap提供的Posixgroup组，实际上并不能很有效的区分组与用户之间的关系。而 memberOf 则可以有效地检索用户与组的关系\n在OpenLDAP配置MemberOf模块 步骤一：可以检查在允许的slapd服务是否已经启用该模块\nbash 1 $ slapcat -n 0 | grep olcModuleLoad 对于新部署的服务，可以按照如下方式添加\ntext 1 2 3 4 dn: cn=module,cn=config objectClass: olcModuleList cn: module olcModuleload: memberof.la 可以在线更改一个正在运行的slapd服务，使其加载 memberOf 模块，需要主义对应的 module{0} 是否正确\nbash 1 2 3 4 5 6 cat \u0026lt;\u0026lt; EOF | ldapmodify -Q -Y EXTERNAL -H ldapi:/// dn: cn=module{0},cn=config changetype: modify add: olcModuleLoad olcModuleLoad: memberof.la EOF 步骤二：配置overlay\n在官方指南中看到olcOverlay 必须要配置到特定数据库的子条目。即此配置段需要在database配置后面。\nOverlays must be configured as child entries of a specific database. [1]\ntext 1 2 3 4 5 6 7 8 9 10 11 dn: olcOverlay={0}memberof,olcDatabase={2}hdb,cn=config objectClass: olcConfig objectClass: olcMemberOf objectClass: olcOverlayConfig objectClass: top olcOverlay: memberof olcMemberOfDangling: ignore olcMemberOfRefInt: TRUE olcMemberOfGroupOC: groupOfUniqueNames olcMemberOfMemberAD: uniqueMember olcMemberOfMemberOfAD: memberOf 步骤三：配置refint\nrefint为 (referential integrity)，所引用完整性。是保持属性与所引用的条目保持一致。在使用 memberOf 时，当一个用户被归为 memberOf 组时，会存在 memberOf 的属性。当这个用户组重命名或删除时，refintOverlay 将自动修改或删除对应的属性。\nldif dn: olcOverlay=refint,olcDatabase={2}hdb,cn=config\robjectClass: olcConfig\robjectClass: olcOverlayConfig\robjectClass: olcRefintConfig\robjectClass: top\rolcOverlay: refint\rolcRefintAttribute: memberOf uniqueMember uid cn ## 哪些属性被改动时修改其他所属关系。 步骤四：配置memberOf\n新建一个groupOfUniqueNames用户组，将用户放入组内\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.10 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: ou=tvb,dc=test,dc=com objectClass: organizationalUnit ou: tvb dn: cn=adminGroup,ou=tvb,dc=test,dc=com objectClass: groupOfUniqueNames cn: adminGroup uniqueMember: uid=admin,ou=tvb,dc=test,dc=com dn: cn=dirGroup,ou=tvb,dc=test,dc=com objectClass: groupOfUniqueNames cn: dirGroup uniqueMember: uid=searchUser,ou=tvb,dc=test,dc=com dn: cn=confGroup,ou=tvb,dc=test,dc=com objectClass: groupOfUniqueNames cn: confGroup uniqueMember: uid=syncUser,ou=tvb,dc=test,dc=com EOF 创建测试用户\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.10 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: uid=syncUser,ou=tvb,dc=test,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: posixAccount uid: syncUser cn: syncUser uidNumber: 10006 gidNumber: 10002 userPassword: {SSHA}QnB7dO98+hoCUgiaAYaiJWnDzlhn2Tn6 homeDirectory: /home/syncUser loginShell: /bin/bash sn: syncUser givenName: syncUser memberOf: cn=confGroup,ou=tvb,dc=test,dc=com dn: uid=searchUser,ou=tvb,dc=test,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: posixAccount uid: searchUser cn: searchUser uidNumber: 10005 gidNumber: 10001 homeDirectory: /home/searchUser loginShell: /bin/bash userPassword: {SSHA}QnB7dO98+hoCUgiaAYaiJWnDzlhn2Tn6 sn: searchUser givenName: searchUser memberOf: cn=dirGroup,ou=tvb,dc=test,dc=com dn: uid=admin,ou=tvb,dc=test,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: posixAccount uid: admin cn: admin uidNumber: 0 gidNumber: 0 homeDirectory: /home/admin loginShell: /bin/bash userPassword: {SSHA}QnB7dO98+hoCUgiaAYaiJWnDzlhn2Tn6 sn: admin givenName: admin memberOf: cn=adminGroup,ou=tvb,dc=test,dc=com dn: uid=admin1,ou=tvb,dc=test,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: posixAccount uid: admin1 cn: admin uidNumber: 0 gidNumber: 0 homeDirectory: /home/admin loginShell: /bin/bash userPassword: {SSHA}QnB7dO98+hoCUgiaAYaiJWnDzlhn2Tn6 sn: admin givenName: admin memberOf: cn=adminGroup,ou=tvb,dc=test,dc=com EOF 查看用户的memberof属性\ntext 1 $ ldapsearch -x -H ldaps://10.0.0.4 -b dc=test,dc=com -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 memberOf Referential Integrity 引用完整 (Referential Integrity) 模块是指在两个属性互相引用时，如果自动修改或删除了一端的值，将保持另一端也能够保持一致性，删除或更新。\n例如下面示例，uid=eve,ou=users,dc=tvb,dc=com 是一个管理员用户，uid=mallory,ou=users,dc=tvb,dc=com 是一个普通用户，普通用户mallory的管理员是eve用户，当eve用户修改名称或者被删除时，mallory对应的 manager 也应该保持更新或删除\nldif dn: uid=eve,ou=users,dc=tvb,dc=com\robjectClass: posixAccount\robjectClass: shadowAccount\robjectClass: inetOrgPerson\rcn: Eve\rsn: Eavesdropper\ruid: eve\ruidNumber: 5000\rgidNumber: 5000\rhomeDirectory: /home/eve\rloginShell: /bin/sh\rgecos: Eve Eavesdropper\rdn: uid=mallory,ou=users,dc=tvb,dc=com\robjectClass: posixAccount\robjectClass: shadowAccount\robjectClass: inetOrgPerson\rcn: Mallory\rsn: Malicious\ruid: eve\ruidNumber: 5001\rgidNumber: 5000\rhomeDirectory: /home/mallory\rloginShell: /bin/sh\rgecos: Mallory Malicious\rmanager: uid=eve,ou=users,dc=tvb,dc=com 在OpenLDAP配置Referential Integrity模块 步骤一：可以检查在允许的slapd服务是否已经启用该模块\nbash 1 $ slapcat -n 0 | grep olcModuleLoad 可以在线更改一个正在运行的slapd服务，使其加载 refint 模块，需要主义对应的 module{0} 是否正确\nbash 1 2 3 4 5 6 cat \u0026lt;\u0026lt; EOF | ldapmodify -Q -Y EXTERNAL -H ldapi:/// dn: cn=module{0},cn=config changetype: modify add: olcModuleLoad olcModuleLoad: refint.la EOF 另外如果 模块目录不为标准的路径，也需要配置 olcModulePath ，当然这个参数只能指定一次\nldif dn: cn=module,cn=config\rcn: module objectClass: olcModuleList\rolcModulePath: /opt/openldap-current/libexec/openldap\rolcModuleLoad: refint.la 下表是关于各操作系统内置 openldap 服务模块的标准路径\nOS PATH CentOS 7 /usr/lib64/openldap openSUSE /usr/lib64/openldap Debian (Stretch) /usr/lib/ldap Source (Default) /usr/local/libexec/openldap 由于overlay的特性，需要指定为database的子模块\nbash 1 2 3 4 5 6 7 8 $ cat \u0026lt;\u0026lt; EOF | ldapadd -Y EXTERNAL -H ldapi:/// dn: olcOverlay=refint,olcDatabase={1}mdb,cn=config objectClass: olcOverlayConfig objectClass: olcRefintConfig olcOverlay: refint olcRefintAttribute: manager secretary olcRefintNothing: cn=config EOF 这里需要注意的属性为：\nolcDatabase={1}mdb 数据库这里需要指定要配置的数据库 olcRefintAttribute 这个属性指明了两个条目间关联的属性 olcRefintNothing 可选参数，因为两端需要关联时，例如memberOf，组对象至少保留一个 memberOf 属性，当删除最后一个关联用户时，会提示非法，这个参数就是在删除最后一个用户时，指定一个占位符的该属性 Password Policy openldap中 Password Policy 是指用户的密码策略，如过期时间，密码安全最低要求等\n在OpenLDAP配置ppolicy模块 可以在线更改一个正在运行的slapd服务，使其加载 ppolicy 模块，需要主义对应的 module{0} 是否正确\nbash 1 2 3 4 5 6 cat \u0026lt;\u0026lt; EOF | ldapmodify -Q -Y EXTERNAL -H ldapi:/// dn: cn=module{0},cn=config changetype: modify add: olcModuleLoad olcModuleLoad: ppolicy.la EOF 在加载完成后，需要叠加到database上才可以生效，如下列配置\nldif dn: olcOverlay=ppolicy,olcDatabase={1}mdb,cn=config\robjectClass: olcPPolicyConfig\rolcOverlay: ppolicy\rolcPPolicyDefault: cn=default,ou=tvb,dc=test,dc=com\rolcPPolicyUseLockout: FALSE\rolcPPolicyHashCleartext: TRUE 这里需要注意的一点是，olcPPolicyDefault 是指定的一个默认密码策略，即没有为用户配置密码策略时，将使用这个默认策略\n为用户配置默认策略 如果需要使用模块 ppolicy ，必须做下述三个步骤其一才可满足为用户密码提供一个管理策略\n需要创建一个默认策略 为用户 ldift 添加属性 pwdPolicySubentry ，这可以为不同的用户设置不同的密码策略 属性 pwdPolicy 可以作为用户 ldif 中被多次引用，这代表可以设置多种不同的策略，这种比较繁琐 场景一：创建一个默认策略\n例如在配置ppolicy的overlay时，默认策略名称填写了 cn=default,ou=tvb,dc=test,dc=com 那么需要用这个DN创建一个默认策略，而创建DN需要按层级创建，顾需要创建 ou=plicies\nbash 1 2 3 4 5 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.10 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: ou=tvb,dc=test,dc=com objectClass: organizationalUnit ou: policies EOF 接下来创建默认的密码策略\nbash 1 2 3 4 5 6 7 8 9 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.10 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: cn=default,ou=tvb,dc=test,dc=com objectClass: pwdPolicy objectClass: organizationalRole cn: default pwdAttribute: userPassword pwdMinLength: 3 pwdCheckQuality: 2 EOF 下表是对于一些密码策略的属性说明\n属性 说明 pwdAttribute 指定那个字段是密码 pwdMinAge 多少秒之间必须经过更改密码，默认0 pwdMaxAge 密码最大期限，如果密码过期用户将被拒绝登录。 默认的是密码永不过期。 pwdInHistory 存储多少个历史旧密码，例如更改密码时提示与历史使用密码类似。 默认为0，即可以重复使用旧密码 pwdMinLength 密码的最小的长度。 默认没有长度的要求。 这个属性将与 pwdCheckQuality 同时生效，当 pwdCheckQuality 为0，则长度限制不生效 pwdCheckQuality 如何对用户长度进行检查。 默认0不检查，\n2 ：总是强制执行的质量检查；如果它不能检查它，密码就会被拒绝。\n1 ：可被接受的密码 pwdMaxFailure 密码错误几次用户被锁定，默认0 代表不会被锁定，他们是锁着的。 为了对此采取影响， 必须关联属性 pwdLockout=TRUE pwdLockout pwdLockout=TRUE 时 pwdMaxFailure 会生效，否则忽略 pwdMaxFailure 配置 pwdLockoutDuration 账户自动解锁的时间 pwdMustChange 这是当管理员创建用户后，第一次登陆时需要必须修改密码 pwdPolicySubentry 表示设置密码的策略。 DN对应适用密码政策的条目。 默认策略由 olcPPolicyDefault 属性的配置。如果不存在，会使用 olcPPolicyDefault ，当存在时会覆盖 olcPPolicyDefault 如果需要上述的策略，需要引入对象 objectClass: pwdPolicy 例如修改一个用户的密码策略\nbash 1 2 3 4 5 6 7 8 9 10 11 12 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.10 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: uid=searchUser,ou=tvb,dc=test,dc=com changetype: modify add: pwdMinLength pwdMinLength: 4 - add: pwdCheckQuality pwdCheckQuality: 2 - replace: pwdPolicySubentry pwdPolicySubentry: cn=example2,ou=tvb,dc=tvb,dc=com EOF 而上述属性 pwdPolicySubentry 代表那个策略适用于那个条目。 如果不存在则默认策略生效。如果需要引入存在的密码策略可以使用该属性进行应用。\n例如，下述是创建两天密码策略\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.10 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: cn=example1,ou=tvb,dc=test,dc=com cn: example1 objectClass: organizationalRole objectClass: pwdPolicy pwdAttribute: userPassword pwdMinLength: 5 pwdCheckQuality: 2 dn: cn=example2,ou=tvb,dc=test,dc=com cn: example2 objectClass: organizationalRole objectClass: pwdPolicy pwdAttribute: userPassword pwdMinLength: 6 pwdCheckQuality: 2 pwdMaxFailure: 3 pwdLockout: TRUE pwdLockoutDuration: 10 pwdInHistory: 5 EOF 当需要创建一个用户引用现有的一组密码策略时，可以对用户添加属性 pwdPolicySubentry，而不是重复的设置每条策略，例如下面示例\nbash 1 2 3 4 5 6 7 8 9 10 11 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.10 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: uid=admin1,ou=tvb,dc=test,dc=com changetype: modify add: pwdPolicySubentry pwdPolicySubentry: cn=example1,ou=tvb,dc=tvb,dc=com dn: uid=searchUser,ou=tvb,dc=test,dc=com changetype: modify add: pwdPolicySubentry pwdPolicySubentry: cn=example2,ou=tvb,dc=tvb,dc=com EOF Reference ​[1] slapd-config\n","permalink":"https://www.161616.top/ch9-openldap-configuration/","summary":"openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 memberOf 默认情况下，openldap提供的Posixgroup组，实际上并不能很有效的区分组与用户之间的关系。而 memberOf 则可以有效地检索用户与组的关系\n在OpenLDAP配置MemberOf模块 步骤一：可以检查在允许的slapd服务是否已经启用该模块\nbash 1 $ slapcat -n 0 | grep olcModuleLoad 对于新部署的服务，可以按照如下方式添加\ntext 1 2 3 4 dn: cn=module,cn=config objectClass: olcModuleList cn: module olcModuleload: memberof.","title":"理解ldap配置 - openldap中的一些高级配置"},{"content":" openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 Overview 本章基于openldap 2.4+版本进行，主要讲解 openldap 的两种备份方法：备份openldap backend-database 文件，另一种方式为导出 LDIF 目录方式\nBackup 备份部分将分为两种方式：使用基于 slapcat 导出目录文件方式，与直接备份数据库文件方式。\nslapcat 是可用于导出 slapd 数据库中数据为LDAP交换格式的命令行工具，它可以导出 slapd 的配置也可以导出 slapd的数据。\nslapcat 使用起来很简单，参数也是与 openldap 其他命令参数类似，\n参数 说 明 -a filter 只导出与过滤器声明条件相匹配的数据\n例如：slapcat -a \u0026quot;(!(entryDN:dnSubtreeMatch:=ou=People,dc=example,dc=com))\u0026quot; -b suffix 将只导出-b指定DN域内数据，-b 不能与 -n 同时使用 -c 忽略错误 -f -f 后接的文件将替代默认的配置文件，通常情况下备份在slapd本机执行可以不使用该参数 -F 指定配置目录，-F比-f优先级高，同时指定生效为-F，也就是导出的目录 -g 导出时不使用从属关系，仅仅为指定的数据库才会被导出 -H 连接 slapd 服务的地址 -l 输出的文件，默认slapcat是将内容输出到标准输出stdout中 -s subtree-dn 仅导出符合dn子树的条目 例如下列命令用于备份配置文件的\nbash 1 $ slapcat -n 0 -l config.ldif Notes：slapd中，配置（0）永远是第一个数据库，跟着的就是在配置中指定的数据库，例如 {0}hdb 将表示1，{1}hdb 则是2\n下列命令用于导出为LDIF格式数据\nbash 1 $ slapcat -n 1 -l data.ldif Restore 恢复配置时，建议直接删除原有配置，而后重新生成新的即可，下表为常见的操作系统openldap配置目录\nOS Configuration Directory Debian/Ubuntu /etc/ldap/slapd.d RHEL/CentOS /etc/openldap/slapd.d SuSE (Uses slapd.conf) /etc/openldap/slapd.d FreeBSD /usr/local/etc/openldap/slapd.d 下列命令是恢复配置的命令，-l 用户指定导出的ldif文件，-F 为配置目录，必须存在\nbash 1 $ slapadd -n 0 -F /etc/openldap/slapd.d -l /backups/config.ldif 因为运行 slapd 进程的用户为 ldap，所以新配置目录的必须拥有权限\nbash 1 $ chown -R ldap:ldap /etc/openldap/slapd.d 下列命令是恢复数据的命令。其实就是重新添加回去\nbash 1 $ slapadd -n 1 -F /config/directory/slapd.d -l /backups/data.ldif -w ","permalink":"https://www.161616.top/ch8-backup-and-restore/","summary":"openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 Overview 本章基于openldap 2.4+版本进行，主要讲解 openldap 的两种备份方法：备份openldap backend-database 文件，另一种方式为导出 LDIF 目录方式\nBackup 备份部分将分为两种方式：使用基于 slapcat 导出目录文件方式，与直接备份数据库文件方式。\nslapcat 是可用于导出 slapd 数据库中数据为LDAP交换格式的命令行工具，它可以导出 slapd 的配置也可以导出 slapd的数据。\nslapcat 使用起来很简单，参数也是与 openldap 其他命令参数类似，","title":"理解ldap - OpenLDAP备份与恢复策略"},{"content":" openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 Overview 访问控制 (Access Control) 是对目录树中的IDT访问的权限控制。主要指 “谁” 应该能够 “访问记录” 在 “什么条件下” 他们应该能看到多少这样的记录，这些将是本节中阐述的问题 。\nOpenLDAP控制目录数据访问的主要方法是 通过访问控制列表 (Access Control List)。使 slapd 服务端在处理来自客户端的请求时，会评估客户端是否具有访问所请求的 DIT 的权限。要执行此计算，slapd 将依次计算LDIF 中配置的每个ACL策略，以检查客户端是否有权限访问该 DIT。\nNote：\nACL策略由上而下依次进行匹配 默认的访问控制策略是对所有客户端都允许读取，不管定义了什么ACL策略，rootdn （databases部分设置的）总是允许对所有和任何东西拥有完全的权限（即身份验证、搜索、比较、读和写 ） ACL介绍 访问控制主要定义三大方面：\nwhat 定义对那些地方的访问，部分选择应用访问的条目和/或属性 who 定义人员，部分指定授予哪些实体访问 access 定义权限，部分指定授予的访问。 text 1 2 3 access to [what] by [who] [control] by [who] [control] text 1 2 3 access to [resources] by [who] [type of access granted] [control] by [who] [type of access granted] [control] 对于完整的ACL语法，如下面所示 [1]\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 \u0026lt;access directive\u0026gt; ::= access to \u0026lt;what\u0026gt; [by \u0026lt;who\u0026gt; [\u0026lt;access\u0026gt;] [\u0026lt;control\u0026gt;] ]+ \u0026lt;what\u0026gt; ::= * | [dn[.\u0026lt;basic-style\u0026gt;]=\u0026lt;regex\u0026gt; | dn.\u0026lt;scope-style\u0026gt;=\u0026lt;DN\u0026gt;] [filter=\u0026lt;ldapfilter\u0026gt;] [attrs=\u0026lt;attrlist\u0026gt;] \u0026lt;basic-style\u0026gt; ::= regex | exact \u0026lt;scope-style\u0026gt; ::= base | one | subtree | children \u0026lt;attrlist\u0026gt; ::= \u0026lt;attr\u0026gt; [val[.\u0026lt;basic-style\u0026gt;]=\u0026lt;regex\u0026gt;] | \u0026lt;attr\u0026gt; , \u0026lt;attrlist\u0026gt; \u0026lt;attr\u0026gt; ::= \u0026lt;attrname\u0026gt; | entry | children \u0026lt;who\u0026gt; ::= * | [anonymous | users | self | dn[.\u0026lt;basic-style\u0026gt;]=\u0026lt;regex\u0026gt; | dn.\u0026lt;scope-style\u0026gt;=\u0026lt;DN\u0026gt;] [dnattr=\u0026lt;attrname\u0026gt;] [group[/\u0026lt;objectclass\u0026gt;[/\u0026lt;attrname\u0026gt;][.\u0026lt;basic-style\u0026gt;]]=\u0026lt;regex\u0026gt;] [peername[.\u0026lt;basic-style\u0026gt;]=\u0026lt;regex\u0026gt;] [sockname[.\u0026lt;basic-style\u0026gt;]=\u0026lt;regex\u0026gt;] [domain[.\u0026lt;basic-style\u0026gt;]=\u0026lt;regex\u0026gt;] [sockurl[.\u0026lt;basic-style\u0026gt;]=\u0026lt;regex\u0026gt;] [set=\u0026lt;setspec\u0026gt;] [aci=\u0026lt;attrname\u0026gt;] \u0026lt;access\u0026gt; ::= [self]{\u0026lt;level\u0026gt;|\u0026lt;priv\u0026gt;} \u0026lt;level\u0026gt; ::= none | disclose | auth | compare | search | read | write | manage \u0026lt;priv\u0026gt; ::= {=|+|-}{m|w|r|s|c|x|d|0}+ \u0026lt;control\u0026gt; ::= [stop | continue | break] what 部分将确定如何控制客户端访问的条目，通常为三种方式： 通过DN 通过过滤器 (filter) 通过属性 (attributes) who 部分将确定被授予访问权限的用户实体 \u0026lt;who\u0026gt; ， \u0026lt;access\u0026gt; ， \u0026lt;control\u0026gt; access 部分确定了实体的访问权限 控制访问内容 访问内容也是 ACL 中 what 部分，例如下属为简单表示的语法格式\ntext 1 2 3 to * to dn[.\u0026lt;basic-style\u0026gt;]=\u0026lt;regex\u0026gt; to dn.\u0026lt;scope-style\u0026gt;=\u0026lt;DN\u0026gt; 其中 to * 表示选择所有条目 to dn[.\u0026lt;basic-style\u0026gt;]=\u0026lt;regex\u0026gt; ：通过正则表达式与DIT规范的DN来选择内容 to dn.\u0026lt;scope-style\u0026gt;=\u0026lt;DN\u0026gt; ：内容将被限制在DN范围内 其中范围包含下述几项：\nbase：仅匹配提供DN的条目 one：父条目是指定DN的条目 subtree：匹配指定DN下的所有子树中的条目，包含RootDN children：匹配指定的DN下的所有子条目，不包含RootDN 例如下列实例\ntext 1 2 3 4 5 6 0: o=suffix 1: cn=Manager,o=suffix 2: ou=people,o=suffix 3: uid=kdz,ou=people,o=suffix 4: cn=addresses,uid=kdz,ou=people, o=suffix 5：uid=hyc,ou=people,o=suffix 如果请求为 dn.base=\u0026quot;ou=people,o=suffix\u0026quot; 将匹配2 如果请求为 dn.one=\u0026quot;ou=people,o=suffix\u0026quot; 将匹配 3, 5 如果请求为 dn.subtree=\u0026quot;ou=people,o=suffix\u0026quot; 将匹配 2, 3, 4, 5 如果请求为 dn.children=\u0026quot;ou=people,o=suffix\u0026quot; 将匹配 3, 4, 5 Notes：在配置ACL时，to * 通常放置在ACL策略中最下层，即代表不匹配所有的权限时给的权限\n通过DN匹配示例 text 1 access to dn.regex=\u0026#34;uid[^,]+,ou=Users,dc=example,dc=com\u0026#34; 这表示 所有 来自任意 uid 为逗号开头的 ou=Users,dc=example,dc=com 域的权限\n使用filter匹配 text 1 to filter=\u0026lt;ldap filter\u0026gt; 例 ：DN与filter可以同时包含在 \u0026lt;what\u0026gt; 子句中\ntext 1 2 3 access to filter=(objectClass=person) access to filter=\u0026#34;(|(|(givenName=Matt)(givenName=Barbara))(sn=Kant))\u0026#34; access to dn.subtree=\u0026#34;ou=Users,dc=example,dc=com\u0026#34; filter=\u0026#34;(employeeNumber=*)\u0026#34; 例 ： \u0026lt;what\u0026gt; 子句中也可以包含属性来进行过滤，多个属性可以通过符号 “,” 分割\ntext 1 2 attrs=\u0026lt;attribute list\u0026gt; access to attrs=userPassword,shadowLastChange 例：通过使用单个 attribute 与使用value选择器来过滤出特定的属性\ntext 1 2 3 attrs=\u0026lt;attribute\u0026gt; val[.\u0026lt;style\u0026gt;]=\u0026lt;regex\u0026gt; access to dn.children=\u0026#34;cn=people,dc=stanford,dc=edu\u0026#34; attrs=suPrivilegeGroup val.regex=\u0026#34;^stanford:.+\u0026#34; Notes： “ * ” 代表 dn=*\nWHO \u0026lt;who\u0026gt; 部分标识被授权访问的角色，例如被授权访问的用户\nNotes：who 代表的是一个实体，例如用户，组，域，二不是访问的条目\n下表总结了who拥有的角色\n标识符 访问实体 * 所有，包括匿名和经过身份验证的用户 anonymous 匿名用户（未验证） users 通过身份验证的用户 self 与目标条目关联的用户 dn[.\u0026lt;basic style\u0026gt;]=\u0026lt;regex\u0026gt; 匹配正则表达式的用户 dn.\u0026lt;scope-style\u0026gt;=\u0026lt;DN\u0026gt; DN范围内的用户 例：who子句中的 dnattr 仅适用于符合ldap语法的DN的属性，而不是其他属性\ntext 1 2 dnattr=\u0026lt;dn-valued attribute name\u0026gt; by *dnattr*=orgaAdmin ACCESS access 类型可以是下列之一：\n级别 权限 描述 none 0 禁止访问 disclose d 检测信息是否存在 auth dx 需要验证 compare cdx 需要比较 search scdx 需要应用搜索过滤器 read rscdx 需要读搜索结果 write wrscdx 需要修改/重命名 manage mwrscdx 需要管理 其中权限字段字母意思为：\nw：对记录或属性的写访问。 r：对记录或属性的读访问。 s：对记录或属性的搜索访问。 c：访问对记录或属性运行比较操作。 x：访问对记录或属性执行服务器端身份验证操作。 d：访问记录或属性是否存在的信息 (d 代表“disclose” )。 0：不允许访问记录或属性。这相当于 wrscxd 。 m：管理权限 例 ：最简单的access策略，限制所有人都可以读\ntext 1 access to * by * read 例 ：下面示例为，对于条目所属者可以修改自己，匿名用户需要登陆，所有人可以读，对于 by anonymous auth 仅仅是声明了匿名用户可以登录，而读的属性是下面那条声明的。\ntext 1 2 3 4 access to * by self write by anonymous auth by * read 例 ：使用 SSF Security Strength Factor 进行认证，SSF表示为openldap中保护的强度，与密钥长度有关\n下列带包了，如果ssf强度大于128的将允许被修改自己，大于64的匿名用户可以读操作\ntext 1 2 3 4 access to * by ssf=128 self write by ssf=64 anonymous auth by ssf=64 users read 例 ：下面示例为 DN示例，这将代表了人员必须有所有用户搜索 dc=example,dc=com 子目录，并且对 dn.children=\u0026quot;dc=com 的子目录有读权限，而这里 dc=example,dc=com 为 dc=com 子目录，所以说如果要读 目录 dc=example,dc=com 此时没有权限，因为ACL策略是按照顺序进行的，匹配到后就结束了\ntext 1 2 3 4 access to dn.children=\u0026#34;dc=example,dc=com\u0026#34; by * search access to dn.children=\u0026#34;dc=com\u0026#34; by * read 例 ：还需要注意的一点是，所有列出的策略结尾都会隐式增加一条 access to * by * none 即，当没有通过who子句，也会被拒绝。\n例如该策略将允许 dc=example,dc=com 子目录，并且属性 homePhone 的属于自己条目的可以写入，可以进行搜索，并包含 dn=dc=example,dc=com 下的所有子条目，并且网络客户端为10开头的ip都可以读，其他的将被隐式策略拒绝\ntext 1 2 3 4 5 6 7 8 access to dn.subtree=\u0026#34;dc=example,dc=com\u0026#34; attrs=homePhone by self write by dn.children=\u0026#34;dc=example,dc=com\u0026#34; search by peername.regex=IP=10\\..+ read access to dn.subtree=\u0026#34;dc=example,dc=com\u0026#34; by self write by dn.children=\u0026#34;dc=example,dc=com\u0026#34; search by anonymous auth 例 ：有些场景下需要只有组内成员才能删除自己租的条目对象，这个时候就需要用到 dnattr 作为 who 子句的选择器；下面的策略表示只有符合这个域中的成员才能删除自己的DN，这里attrs是必须的\ntext 1 2 access to attrs=member,entry by dnattr=member selfwrite ACL的排序 ACL的顺序也非常重要，如果不配置，将按照默认的固定顺序进行，但是也可以控制顺序的先后，通过为每个值添加 {x} x 为数字，这样可以做到ACL的排序\n例如下列策略，在执行时是由 slapd 服务自动维护的\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 olcAccess: to attrs=member,entry by dnattr=member selfwrite olcAccess: to dn.children=\u0026#34;dc=example,dc=com\u0026#34; by * search olcAccess: to dn.children=\u0026#34;dc=com\u0026#34; by * read # 在没指定顺序时，将按照下列顺序执行 olcAccess: {0}to attrs=member,entry by dnattr=member selfwrite olcAccess: {1}to dn.children=\u0026#34;dc=example,dc=com\u0026#34; by * search olcAccess: {2}to dn.children=\u0026#34;dc=com\u0026#34; by * read 比如说在生成好的策略进行动态修改时，例如下列所示，此时时修改顺序将是乱的\ntext 1 2 3 4 5 6 changetype: modify delete: olcAccess olcAccess: to dn.children=\u0026#34;dc=example,dc=com\u0026#34; by * search - add: olcAccess olcAccess: to dn.children=\u0026#34;dc=example,dc=com\u0026#34; by * write 为了确保可以修改到指定的条目，需要指定对应条目的index，如果并不确定index是多少，可以通过 /etc/openldap/slapd.d/ 对应的文件查看\ntext 1 2 3 4 5 6 changetype: modify delete: olcAccess olcAccess: {1} - add: olcAccess olcAccess: {1}to dn.children=\u0026#34;dc=example,dc=com\u0026#34; by * write 访问控制示例 关闭匿名访问 创建一个目录管理员组，属于该组的人员都具有管理目录的权限 创建一个配置管理员组，属于该组的人员都具有配置 openldap 的权限 创建一个复制权限用户 ，该账号用于高可用之间的复制 创建一个搜索用户，用于 linux 集中账号认证时的，搜索 提示：\n目录管理员组：操作目录树的管理员都属于目录管理员组。 配置管理员组：可以操作服务器配置的（slapd.ldif）属于配置管理员组 关闭匿名访问 首先slapd默认是允许匿名用户登录的，现在关闭匿名用户登录 在全局部分配置如下（通常全局在配置文件前几行）\ntext 1 olcDisallows: bind_anon 初始化一些组与用户 创建RootDN与一些组，这里创建了三个组 ：目录管理员组 dirGroup，配置管理员组 confGroup ，与高级管理员组 adminGroup 。\n注：\n这些是在初始化安装好 openldap 环境进行的，而不是存在数据的环境 这里创建的组使用的 groupOfUniqueNames bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.10 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: ou=tvb,dc=test,dc=com objectClass: organizationalUnit ou: tvb dn: cn=adminGroup,ou=tvb,dc=test,dc=com objectClass: groupOfUniqueNames cn: adminGroup uniqueMember: uid=admin,ou=tvb,dc=test,dc=com dn: cn=dirGroup,ou=tvb,dc=test,dc=com objectClass: groupOfUniqueNames cn: dirGroup uniqueMember: uid=searchUser,ou=tvb,dc=test,dc=com dn: cn=confGroup,ou=tvb,dc=test,dc=com objectClass: groupOfUniqueNames cn: confGroup uniqueMember: uid=syncUser,ou=tvb,dc=test,dc=com EOF 初始化目录管理员用户 syncUser ，配置管理员用户 searchUser ，高级管理员用户 admin ，密码均为111\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.10 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: uid=syncUser,ou=tvb,dc=test,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: posixAccount uid: syncUser cn: syncUser uidNumber: 10006 gidNumber: 10002 userPassword: {SSHA}QnB7dO98+hoCUgiaAYaiJWnDzlhn2Tn6 homeDirectory: /home/syncUser loginShell: /bin/bash sn: syncUser givenName: syncUser memberOf: cn=confGroup,ou=tvb,dc=test,dc=com dn: uid=searchUser,ou=tvb,dc=test,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: posixAccount uid: searchUser cn: searchUser uidNumber: 10005 gidNumber: 10001 homeDirectory: /home/searchUser loginShell: /bin/bash userPassword: {SSHA}QnB7dO98+hoCUgiaAYaiJWnDzlhn2Tn6 sn: searchUser givenName: searchUser memberOf: cn=dirGroup,ou=tvb,dc=test,dc=com #cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.10 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: uid=admin,ou=tvb,dc=test,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: posixAccount uid: admin cn: admin uidNumber: 0 gidNumber: 0 homeDirectory: /home/admin loginShell: /bin/bash userPassword: {SSHA}QnB7dO98+hoCUgiaAYaiJWnDzlhn2Tn6 sn: admin givenName: admin memberOf: cn=adminGroup,ou=tvb,dc=test,dc=com dn: uid=admin1,ou=tvb,dc=test,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: posixAccount uid: admin1 cn: admin uidNumber: 0 gidNumber: 0 homeDirectory: /home/admin loginShell: /bin/bash userPassword: {SSHA}QnB7dO98+hoCUgiaAYaiJWnDzlhn2Tn6 sn: admin givenName: admin memberOf: cn=adminGroup,ou=tvb,dc=test,dc=com EOF 创建权限：管理员用户组有所有权限 这句意思是筛选出dn下的所有不包含uniqueMember的用户，即找到dn=cn=adminGroup,ou=tvb,dc=test,dc=com 在上面我们把起规划为一个组，然后检查他的属性 uniqueMember ，* 表示递归，这里不用填，所有的 uniqueMember 均为最底层 [2]\ntext 1 by set=\u0026#34;[cn=adminGroup,ou=tvb,dc=test,dc=com]/uniqueMember* \u0026amp; user\u0026#34; manage 创建权限：普通用户仅允许修改自己的密码 这里使用属性选择器进行属性筛选\nbash 1 2 3 4 5 6 olcAccess: to attrs=\u0026#34;userPassword\u0026#34; by set=\u0026#34;[cn=adminGroup,ou=tvb,dc=test,dc=com]/uniqueMember \u0026amp; user\u0026#34; manage # 自己可以修改 by self write # 登录时候需要读，必须设置该选项 by * read 配置数据库的权限 对于客户端请求过来的权限，一般通过Frontend settings段来配置，这也可以理解是全局的ACL，对于后面的ACL会覆盖全局的ACL\nldif # # Configuration database # dn: olcDatabase=config,cn=config objectClass: olcDatabaseConfig olcDatabase: config olcAccess: to * by set=\u0026#34;[cn=adminGroup,ou=tvb,dc=test,dc=com]/uniqueMember \u0026amp; user\u0026#34; manage by * none 完整的ACL配置部分\nNotes：slapd会根据配置的ACL顺序进行检测，匹配到后就不在继续了，所以在配置时，what 部分（选择器范围）要从小到大，这样可以控制范围就正确，正如上面例子，用户只能修改自己密码，属性选择器一旦写在DN选择器下面，将用远不被执行\nldif olcAccess: to attrs=\u0026#34;userPassword\u0026#34; by set=\u0026#34;[cn=adminGroup,ou=tvb,dc=test,dc=com]/uniqueMember \u0026amp; user\u0026#34; manage by self write by * read olcAccess: to dn.subtree=\u0026#34;dc=test,dc=com\u0026#34; by set=\u0026#34;[cn=adminGroup,ou=tvb,dc=test,dc=com]/uniqueMember \u0026amp; user\u0026#34; manage by dn.children=\u0026#34;dc=test,dc=com\u0026#34; read by anonymous auth by * read Troubleshooting 默认情况下，获取schema是没有权限的，此时需要添加权限使其可以读取schema权限。\n注：在创建用户和组是需要查看schema权限\nslapd.ldif 文件中添加：\ntext 1 2 3 4 5 6 7 8 dn: olcDatabase=frontend,cn=config ... olcAccess: to dn.subtree=\u0026#34;\u0026#34; by * read or olcAccess: to dn.subtree=\u0026#34;dn: cn=schema,cn=config\u0026#34; by * read 填写配置后重新生成配置文件报类似如下错误\ntext 1 2 3 4 5 $ slapadd -n0 -F /etc/openldap/slapd.d -l kerberos.ldif SYNTAX 1.3.6.1.4.1.1466.115.121.1.26)): empty AttributeDescription slapadd: could not parse entry (line=1) _# 6.36% eta none elapsed none spd 18.6 M/s Closing DB... 问题原因：如上配置权限换行方式不可，官网给出的解决方法 [1]\nInsufficient access (50)\n问题原因：没有权限\nReference ​[1] msg00054\n​[2] Groups of Groups\n​[3] ACLs\n​[4] Controls or what to do after a match\n​[5] LDAP Access Control\n","permalink":"https://www.161616.top/ch7-acl/","summary":"openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 Overview 访问控制 (Access Control) 是对目录树中的IDT访问的权限控制。主要指 “谁” 应该能够 “访问记录” 在 “什么条件下” 他们应该能看到多少这样的记录，这些将是本节中阐述的问题 。\nOpenLDAP控制目录数据访问的主要方法是 通过访问控制列表 (Access Control List)。使 slapd 服务端在处理来自客户端的请求时，会评估客户端是否具有访问所请求的 DIT 的权限。要执行此计算，slapd 将依次计算LDIF 中配置的每个ACL策略，以检查客户端是否有权限访问该 DIT。","title":"理解ldap - OpenLDAP访问控制(ACL)"},{"content":" openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 LDAP复制概述 openldap的复制 ( replication) 是可以将 LDAP DIT (Directory Information Tree) 同步更新复制到一个或多个LDAP (“ slapd ”) 系统，主要是用于实现备份或提升性能场景。在 openldap中，需要注意的一点是 “复制” 级别属于DIT级别而非LDAP服务级别运行。因此，在运行的一个服务 (slapd) 中的多个DIT，每一个DIT都可以被复制到不同的其他服务中 (slapd) 。本章节只讲述 openldap 2.4+ 的四种复制模式。\n注意：在 openldap 2.4- 提供的复制功能，属于一个额外的守护进程 slurpd。仅适用于（2.3之前版本）。\nopenldap的复制模式 在openldap 2.4+ 中，提供了四种复制模式：\nDelta-syncrepl ：2.3+ N-Way Multi-Provider：2.4+ MirrorMode：2.4+ Syncrepl Proxy Mode slurpd：2.3-，这种模式将不再本章节中阐述 下面将由简到易来阐述四种复制模式\nDelta-syncrepl Delta-syncrepl 模式是基于日志模式 syncrepl 的一种变种模式，主要是为了解决openldap同步机制中的一些缺点。由于传统的同步机制是基于对象的同步机制，即当对象上的任何一个属性发生改变，每一个 comsumer 都会触发获取一次完整的对象（例如对象存在100个属性），这种模式存在以下特点：\n优点：对象发生改变时无需注意改变次数，仅需要结果即可，类似于kubernetes list-watch 机制 缺点：开支过大，例如存在102,400个对象，每个对象大小1KB，当跑脚本批量更改所有对象的其中一个属性时（2Byte），每个comsumer将要触发的同步数据将为100MB数据，来更改200KB数据，外加TCP/IP协议的开销 Delta-syncrepl 的诞生就是为了解决 syncrepl 机制的缺点\nNote:\nsyncrepl 就是传统的 povider-comsumer/master-slave 模型 Delta-syncrepl 需要注意的一点就是，当两边数据完全不同（或为空）将使用 syncrepl 同步完成后切换为 Delta-syncrepl 模式 对于 Delta-syncrepl 与 syncrepl 模式来说可以产生多种变种模型，push 与 pull\n主从拉取模式 master-slave 拉取模式将按照下图所示的步骤进行同步\n图：master-slave拉取模式 Source：https://www.zytrax.com/books/ldap/ch7/\n如图所示\n(1) 与 (3) 为 slapd服务的 master 与 slave 角色 （编号与角色按顺序对应）\n(4) 是master上的对象条目， (5) 为slave要拉去的数据\n(2) 为同步的过程\n(6) 与 (7) 分别为 master 与 slave 上需要配置的配置选项\n(A-E) 为同步的步骤\n在图中可以看出，同步时提供者master会分配一个cookie (SyncCookie) 给消费者slave，本质上来说，cookie中包含了更改序列号，指发送给这个消费者的最后一个修改（时间戳），通过对比检查点，即同步会话发生开启时，将slave最后收到的cookie发送给master，以获得同步的限制，第一次时将会全量同步所有记录。\n主从推送模式 图：master-slave推送模式 Source：https://www.zytrax.com/books/ldap/ch7/\n如图所示\n(1) 与 (3) 为 slapd服务的 master 与 slave 角色 （编号与角色按顺序对应）\n(4) 是master上的对象条目， (5) 为slave要拉去的数据\n(2) 为同步的过程\n(6) 与 (7) 分别为 master 与 slave 上需要配置的配置选项\n(A-E) 为同步的步骤\n在该模式下，slave可以为多个，提供者master没有维护slave的信息，即任意slave都可以申请同步，只需要满足安全限制即可，同步请求本质时一个搜索，因为配置中会定义 DN，范围，过滤条件等信息。\n并且在这种模式下，cookie的维护有提供者master维护，master定期发送一个同步cookie。同步的机制类似于拉去模式，并且该模式下只要申请了同步，那么这个链接会永久被维护，也就是图中 persist 代表的意义，除非master, slave网络发生故障。\n主从配置的缺点：\n多节点访问：如果所有或大多数客户端都需要更新DIT，则所有客户端必须访问同一个服务 (slave) 进行正常读访问，而另一个服务（master）进行更新。或者，客户端始终访问主服务。后一种情况下，slave仅提供备份功能 弹性。由于只存在一个服务为更新的节点，这意味着存在单点故障问题 主从模式的配置 两种模式配置起来非常相似，只是所遇到场景的不同而需要不同的应用方法\n关停服务进行配置 在安装openldap部分，ldif文件中有配置database部分，这将代表了生成的配置文件，例如下面database部分的配置\nldif # # Backend database definitions # # 这里是数据库的参数配置 dn: olcDatabase=mdb,cn=config objectClass: olcDatabaseConfig # 使用的数据库引擎是mdb objectClass: olcMdbConfig olcDatabase: mdb # Suffix 为数据库的后缀，每个数据库至少一个，在搜索时-D 后面的域后缀为dc=test,dc=com将被pass到这里 olcSuffix: dc=test,dc=com # 指不收前面配置的权限控制的管理员账户，拥有最最高权限 olcRootDN: cn=admin,dc=test,dc=com # 特权账户的登录密码 olcRootPW: {SSHA}xU9xFym/s7rawpmzpsYE+Q1qPsVPOwDw olcDbDirectory:\t/var/lib/ldap # 这是索引属性，下面是默认的属性 # 下列注释行意思为 # olcDbIndex: default pres,eq # olcDbIndex: uid # olcDbIndex: cn,sn pres,eq,sub # olcDbIndex: objectClass eq # pres,eq 为 present equality # 第二行意思为，为uid属性类型维护默认索引集 # 第三行意思为，为cn,sn属性维护pres,eq,sub索引集 # 索引集类型有 pres,eq,approx,sub,none olcDbIndex: objectClass eq,pres olcDbIndex: uid,ou,cn,mail,surname,givenname eq,pres,sub # 配置从缓冲区写入磁盘的，两个参数分别为多少kbyte大小的数据自上次（第二个参数）分钟则发生一次写入 olcDbCheckpoint: 1024 10 而在openldap中所有的同步模式的机制都是上面所示的database的子集，即需要拥有上述的配置，例如下列配置\nNotes：openldap中数据库可以有多个，因为openldap是基于对象而不是基于服务\nldif # # Backend database definitions # dn: olcDatabase={1}mdb,cn=config objectClass: olcDatabaseConfig objectClass: olcMdbConfig olcDatabase: mdb olcSuffix: dc=test,dc=com olcRootDN: cn=admin,dc=test,dc=com olcDbDirectory:\t/var/lib/ldap olcDbIndex: objectClass eq,pres olcDbCheckpoint: 1024 10 olcDbIndex: uid,ou,cn,mail,surname,givenname eq,pres,sub olcRootPW: {SSHA}xU9xFym/s7rawpmzpsYE+Q1qPsVPOwDw # 为dn为dn: olcDatabase={1}mdb,cn=config的数据库配置同步 dn: olcOverlay=syncprov,olcDatabase={1}mdb,cn=config objectclass: olcSyncProvConfig objectClass: olcOverlayConfig olcOverlay: syncprov olcSpCheckpoint: 100 10 dn: olcDatabase={2}mdb,cn=config objectClass: olcDatabaseConfig objectClass: olcMdbConfig olcDatabase: mdb olcSuffix: dc=test1,dc=com olcRootDN: cn=admin,dc=test1,dc=com olcDbDirectory: /var/lib/ldap olcDbIndex: objectClass eq,pres olcDbCheckpoint: 1024 10 olcDbIndex: uid,ou,cn,mail,surname,givenname eq,pres,sub olcRootPW: {SSHA}xU9xFym/s7rawpmzpsYE+Q1qPsVPOwDw 配置说明：\nolcOverlay 属性是必须值 olcSyncProvConfig 属性是基于 olcOverlay 后添加的属性 必须开启module syncprov 才可以使用 上面的是master的配置，slave配置如下：\nldif # # Backend database definitions # dn: olcDatabase=mdb,cn=config objectClass: olcDatabaseConfig objectClass: olcMdbConfig olcDatabase: mdb olcSuffix: dc=test,dc=com olcRootDN: cn=admin,dc=test,dc=com olcRootPW: {SSHA}xU9xFym/s7rawpmzpsYE+Q1qPsVPOwDw olcDbDirectory:\t/var/lib/ldap olcDbIndex: objectClass eq,pres olcDbIndex: uid,ou,cn,mail,surname,givenname eq,pres,sub olcDbCheckpoint: 1024 10 olcSyncRepl: rid=002 provider=ldap://10.0.0.10:389/ bindmethod=simple binddn=\u0026#34;cn=admin,dc=test,dc=com\u0026#34; credentials=111 searchbase=\u0026#34;dc=test,dc=com\u0026#34; scope=sub schemachecking=on type=refreshAndPersist retry=\u0026#34;30 5 300 3\u0026#34; interval=00:00:05:00 当配置完成后，可以看到master上已经收到slave申请的同步请求了\ntext 1 2 3 4 5 6 Nov 8 21:28:15 ldap slapd[65362]: conn=1001 fd=11 ACCEPT from IP=10.0.0.3:45860 (IP=0.0.0.0:389) Nov 8 21:28:15 ldap slapd[65362]: conn=1001 op=0 BIND dn=\u0026#34;cn=admin,dc=test,dc=com\u0026#34; method=128 Nov 8 21:28:15 ldap slapd[65362]: conn=1001 op=0 BIND dn=\u0026#34;cn=admin,dc=test,dc=com\u0026#34; mech=SIMPLE ssf=0 Nov 8 21:28:15 ldap slapd[65362]: conn=1001 op=0 RESULT tag=97 err=0 text= Nov 8 21:28:15 ldap slapd[65362]: conn=1001 op=1 SRCH base=\u0026#34;dc=test,dc=com\u0026#34; scope=2 deref=0 filter=\u0026#34;(objectClass=*)\u0026#34; Nov 8 21:28:15 ldap slapd[65362]: conn=1001 op=1 SRCH attr=* + 不停服务进行配置 openldap提供了一个api ldapi:/// 可以在外部进行更改正在运行的服务，下面配置为master不停服务进行配置\n首先找到对应的db配置看是属于哪个块，例如这里为 olcDatabase={2}mdb\nls /etc/openloda/slapd.d/cn\\=config/olcDatabase\\=\\{2\\}mdb.ldif 那么接下来的dn就需要指定这个db\nbash 1 2 3 4 5 6 7 cat \u0026lt;\u0026lt; EOF | ldapadd -Y EXTERNAL -H ldapi:/// dn: olcOverlay=syncprov,olcDatabase={2}mdb,cn=config objectClass: olcOverlayConfig objectClass: olcSyncProvConfig olcOverlay: syncprov olcSpSessionLog: 100 EOF 同理接下来配置slave，由于slave配置的dn为已经存在的，需要进行修改，同理这里也需要找到对应的 dn olcDatabase={2}mdb,cn=config\nNotes: ldif也可以向yaml那样属于另外一个定义可以使用符号 “ - ” 分割\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 cat \u0026lt;\u0026lt; EOF | ldapadd -Y EXTERNAL -H ldapi:/// dn: olcDatabase={2}mdb,cn=config changetype: modify add: olcSyncRepl olcSyncRepl: rid=001 provider=ldap://10.0.0.10:389/ bindmethod=simple binddn=\u0026#34;cn=admin,dc=test,dc=com\u0026#34; credentials=111 searchbase=\u0026#34;dc=test,dc=com\u0026#34; scope=sub schemachecking=on type=refreshAndPersist retry=\u0026#34;30 5 300 3\u0026#34; interval=00:00:05:00 MirrorMode 镜像模式实际上属于master-slave的一个变种，是为master-slave模式提供了两端一致性的保障，就是两端互为porvider与comsumer，即一个HA)解决方案\n图：镜像模式在多数据中心的应用架构图 Source：https://www.openldap.org/doc/admin24/replication.html#MirrorMode\n镜像模式的配置 镜像模式配置与主从模式完全相同，并且工作模式也是相同的，下面仅提供热配置的方式，这种配置完后重启服务也是生效的\n首先提供porvider部分的配置，因为两端是相同的，只需要修改ServerID即可\nbash 1 2 3 4 5 6 7 8 9 10 11 12 cat \u0026lt;\u0026lt; EOF | ldapadd -Y EXTERNAL -H ldapi:/// dn: olcOverlay=syncprov,olcDatabase={2}mdb,cn=config objectClass: olcOverlayConfig objectClass: olcSyncProvConfig olcOverlay: syncprov olcSpSessionLog: 100 - dn: cn=config changetype: modify replace: olcServerID olcServerID: 0 EOF 下面是comsumer部分的配置，因为两端是相同的，注意修改 rid , credentials , provider 值为正确的值， provider 由多个组成\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 cat \u0026lt;\u0026lt; EOF | ldapadd -Y EXTERNAL -H ldapi:/// dn: olcDatabase={2}mdb,cn=config changetype: modify add: olcSyncRepl olcSyncRepl: rid=001 provider=ldap://10.0.0.10:389/ bindmethod=simple binddn=\u0026#34;cn=admin,dc=test,dc=com\u0026#34; credentials=111 searchbase=\u0026#34;dc=test,dc=com\u0026#34; scope=sub schemachecking=on type=refreshAndPersist retry=\u0026#34;30 5 300 3\u0026#34; interval=00:00:05:00 - add: olcMirrorMode olcMirrorMode: TRUE EOF N-way Multi-Master 在 openldap 2.4+ 引入了 多路多主 (N-way Multi-Master ) 模式。该模式中任何数量的主机都可以彼此同步。由于 多路多主模式也是属于 Syncrepl 模式的变种，这里不过多阐述同步原理了。\nNotes：该模式中，所有master上的时钟同步是必须的\n下图为 N-way Multi-Master 模式的架构图，在此模式下所有的节点即为master又为slave，所有的节点的slave都为集群数量减一个。\n图：N-way Multi-Master模式 Source：https://www.openldap.org/doc/admin24/replication.html#MirrorMode\n如图所示\n(1) (2) (3) 为 slapd服务，因为使用 syncrepl 复制技术需要为每个服务提供一个唯一的 olcServerID/ServerID 在整个集群中。 (5) (6) (7) 为 (1) (2) (3) 服务 syncprov overlay 的配置 (4) 是为 (1) (2) (3) 提供了 provider syncprov overlay 的配置 此模式中每个节点发生了写入动作后，都会同步至集群中另外的所有节点上 Notes：这种模式下不支持增量同步\nsyncrepl N-Way Multi-Master配置 这种模式与最基础的 syncrepl 是类似的，不在过多阐述\n首先提供porvider部分的配置，注意修改ServerID即可，多个节点之间必须唯一\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 cat \u0026lt;\u0026lt; EOF | ldapadd -Y EXTERNAL -H ldapi:/// dn: olcOverlay=syncprov,olcDatabase={2}mdb,cn=config objectClass: olcOverlayConfig objectClass: olcSyncProvConfig olcOverlay: syncprov olcSpSessionLog: 100 EOF cat \u0026lt;\u0026lt; EOF | ldapadd -Y EXTERNAL -H ldapi:/// dn: cn=config changetype: modify replace: olcServerID olcServerID: 0 EOF 下面是comsumer部分的配置，因为两端是相同的，注意修改 rid , credentials , provider 值为正确的值\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 cat \u0026lt;\u0026lt; EOF | ldapadd -Y EXTERNAL -H ldapi:/// dn: olcDatabase={2}mdb,cn=config changetype: modify add: olcSyncRepl olcSyncRepl: {0}rid=001 provider=ldap://10.0.0.10:389/ bindmethod=simple binddn=\u0026#34;cn=admin,dc=test,dc=com\u0026#34; credentials=111 searchbase=\u0026#34;dc=test,dc=com\u0026#34; scope=sub schemachecking=on type=refreshAndPersist retry=\u0026#34;30 5 300 3\u0026#34; interval=00:00:05:00 olcSyncRepl: {1}rid=002 provider=ldap://10.0.0.10:389/ bindmethod=simple binddn=\u0026#34;cn=admin,dc=test,dc=com\u0026#34; credentials=111 searchbase=\u0026#34;dc=test,dc=com\u0026#34; scope=sub schemachecking=on type=refreshAndPersist retry=\u0026#34;30 5 300 3\u0026#34; interval=00:00:05:00 ... - add: olcMirrorMode olcMirrorMode: TRUE EOF Syncrepl Proxy Syncrepl Proxy 模式就是 Syncrepl ，只不过通过acl控制slave为只读模式，因为配置相同，将不重复阐述\n图：Syncrepl Proxy模式 Source：https://www.openldap.org/doc/admin24/replication.html#Syncrepl%20Proxy\n创建指定用户同步 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 cat \u0026lt;\u0026lt; EOF | ldapadd -x -W -H ldap://10.0.0.20 -D cn=admin,dc=cylon,dc=org dn: cn=123123,ou=group,dc=cylon,dc=org objectClass: top objectClass: posixGroup gidNumber: 10011 cn: mygroup EOF cat \u0026lt;\u0026lt; EOF | ldapadd -x -W -H ldap://10.0.0.20 -D cn=admin,dc=cylon,dc=org dn: uid=rpuser,dc=cylon,dc=org objectClass: simpleSecurityObject objectclass: account uid: rpuser description: Replication User userPassword: root1234 EOF Reference ​[1] replication\n​[2] Chapter 7 Replication \u0026amp; Referral\n","permalink":"https://www.161616.top/ch6-replication/","summary":"openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 LDAP复制概述 openldap的复制 ( replication) 是可以将 LDAP DIT (Directory Information Tree) 同步更新复制到一个或多个LDAP (“ slapd ”) 系统，主要是用于实现备份或提升性能场景。在 openldap中，需要注意的一点是 “复制” 级别属于DIT级别而非LDAP服务级别运行。因此，在运行的一个服务 (slapd) 中的多个DIT，每一个DIT都可以被复制到不同的其他服务中 (slapd) 。本章节只讲述 openldap 2.4+ 的四种复制模式。","title":"理解ldap配置 - OpenLDAP中的4种复制机制"},{"content":" openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 OpenLDAP TLS/SSL 配置 对于 TLS/SSL 方向的内容不过多阐述了，这里只阐述openldap TLS/SSL 配置方向的内容\nopenldap提供了两种方式进行 TLS/SSL 认证\n自动模式：客户端通过 ldaps://hostname/ 形式的URL访问slapd，slapd默认为636端口提供 TLS 会话 主动定义：slapd标准端口389支持 TLS/SSL ，客户端通过显式配置 TLS/SSL 也可以使用 URL格式ldap://hostname/ 进行访问，需要注意的是，在同步时如果使用 ldap:// 格式URL需要指定参数 starttls=yes 或者 starttls=critical 使用 ldaps:// 则不需要指定该参数 生成自签名证书 创建CA证书\nbash 1 2 3 4 5 6 7 8 9 10 openssl genrsa -out cakey.key 2048 openssl req -new -x509 \\ -key cakey.key \\ -out cacert.crt \\ -days 3650 \\ -subj \u0026#34;/C=HK/ST=HK/O=TVB/OU=bigbigchannl/CN=tvb-ca\u0026#34; touch index.txt echo \u0026#34;01\u0026#34; \u0026gt; serial 生成证书请求\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 openssl genrsa -out openldap.key 2048 openssl req -new \\ -key openldap.key \\ -out openldap.csr \\ -days 3650 \\ -subj \u0026#34;/C=HK/ST=HK/O=TVB/OU=bigbigchannl/CN=10.0.0.4\u0026#34; openssl ca \\ -in openldap.csr \\ -cert cacert.crt \\ -keyfile cakey.key \\ -out openldap.crt \\ -days 3650 启用openldap TLS认证 slapd TLS 部分配置是在全局部分\n修改服务端参数 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # cn=config base（全局部分） dn: cn=config changetype: modify # Security - TLS section add: olcTLSCertificateFile # cafile olcTLSCertificateFile: /certs/ldapscert.pem - add: olcTLSCertificateKeyFile # ca key olcTLSCertificateKeyFile: /certs/keys/ldapskey.pem - # 证书 olcTLSCertificateFile: \u0026#34;/etc/openldap/certs/openldap.crt\u0026#34; olcTLSCertificateKeyFile: \u0026#34;/etc/openldap/certs/openldap.key\u0026#34; - # the following directive is the default but # is explicitly included for visibility reasons add: olcTLSVerifyClient olcTLSVerifyClient: never 修改客户端参数 命令行指令相关配置 客户端时指 ladpadd，ladpsearch 相关配置文件，默认为 /etc/openldap/ldap.conf\ntext 1 2 3 4 5 6 7 8 9 10 URI\tldap://ldap.example.com ldaps://10.0.0.4:636 #SIZELIMIT\t12 #TIMELIMIT\t15 #DEREF\tnever TLS_CACERTDIR\t/etc/openldap/certs TLS_CACERT /etc/openldap/certs/cacert.crt TLS_CERT /etc/openldap/certs/openldap.csr TLS_KEY /etc/openldap/certs/openldap.key 修改服务要监听的地址\ntext 1 SLAPD_URLS=\u0026#34;ldapi:/// ldaps:///\u0026#34; 同步时相关配置 在同步时仅仅需要修改slave部分配置即可\nldif syncrepl rid=000 type=RefreshandPersist provider=ldaps://ldap-master.example.com bindmethod=simple searchbase=\u0026#34;dc=example,dc=com\u0026#34; retry=\u0026#34;5 3 300 +\u0026#34; attrs=\u0026#34;*,+\u0026#34; binddn=\u0026#34;....\u0026#34; credentials=.... Troubleshooing 错误 Re: OpenLDAP/TLS main: TLS init def ctx failed: -207\nOpenLDAP/TLS main: TLS init def ctx failed: -207 how to configure tls and ldap 错误 Re: slapadd: could not parse entry (line=13)\n可能原因，上下文存在空行 错误: openssl slapd tls init def ctx failed: -1 ；证书的路径，文件名，权限是否正确\n验证证书 openssl verify -CAfile {ca.crt} {.crt}\nRE: main: TLS init def ctx failed: -1 Reference：\nChapter 15. LDAP Security\n","permalink":"https://www.161616.top/ch05-openldap-ssl/","summary":"openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 OpenLDAP TLS/SSL 配置 对于 TLS/SSL 方向的内容不过多阐述了，这里只阐述openldap TLS/SSL 配置方向的内容\nopenldap提供了两种方式进行 TLS/SSL 认证\n自动模式：客户端通过 ldaps://hostname/ 形式的URL访问slapd，slapd默认为636端口提供 TLS 会话 主动定义：slapd标准端口389支持 TLS/SSL ，客户端通过显式配置 TLS/SSL 也可以使用 URL格式ldap://hostname/ 进行访问，需要注意的是，在同步时如果使用 ldap:// 格式URL需要指定参数 starttls=yes 或者 starttls=critical 使用 ldaps:// 则不需要指定该参数 生成自签名证书 创建CA证书","title":"理解ldap配置 - OpenLDAP使用SSL/TLS通信安全"},{"content":" text 1 2 3 4 5 6 7 8 9 10 11 curl -XPOST https://api.telegram.org/bot977657989:AAF0QE88WhxRIdpFLOYO_9ldLun5VtpfCWw/getUpdates curl -X POST \\ -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#39;{\u0026#34;chat_id\u0026#34;: \u0026#34;850233746\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;This is a test from curl\u0026#34;, \u0026#34;disable_notification\u0026#34;: true}\u0026#39; \\ https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage curl -X POST \\ -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#39;{\u0026#34;chat_id\u0026#34;: \u0026#34;850233746\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;This is a test from curl\u0026#34;, \u0026#34;disable_notification\u0026#34;: true}\u0026#39; \\ https://api.telegram.org/bot1009139816:AAGTmFsJDkH9H3E0OVoFi4GyvYp0uMctvcE/sendMessage https://api.telegram.org/bot721202655:AAG_kN1IHP93Wmnd90RRaJC-dK9tKQHddRA/sendMessage\njson 1 2 3 4 5 { \u0026#34;chat_id\u0026#34;: \u0026#34;-383641009\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;This is a test from curl\u0026#34;, \u0026#34;disable_notification\u0026#34;: true } alertmanager发送的消息类型如下：\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 { \u0026#34;receiver\u0026#34;: \u0026#34;webhook\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;InstanceDown\u0026#34;, \u0026#34;instance\u0026#34;: \u0026#34;192.168.8.32:9110\u0026#34;, \u0026#34;job\u0026#34;: \u0026#34;node\u0026#34;, \u0026#34;role\u0026#34;: \u0026#34;api\u0026#34;, \u0026#34;service\u0026#34;: \u0026#34;node\u0026#34; }, \u0026#34;annotations\u0026#34;: { \u0026#34;description\u0026#34;: \u0026#34;192.168.8.32:9110: job node has been down vlaue==0\u0026#34;, \u0026#34;summary\u0026#34;: \u0026#34;192.168.8.32:9110: has been down\u0026#34; }, \u0026#34;startsAt\u0026#34;: \u0026#34;2019-07-25T21:55:13.352482605+08:00\u0026#34;, \u0026#34;endsAt\u0026#34;: \u0026#34;0001-01-01T00:00:00Z\u0026#34;, \u0026#34;generatorURL\u0026#34;: \u0026#34;http://zy-tw-tianxia-prod-prometheus01:19090/graph?g0.expr=up+%3D%3D+0\\u0026g0.tab=1\u0026#34; }, { \u0026#34;status\u0026#34;: \u0026#34;firing\u0026#34;, \u0026#34;labels\u0026#34;: { \u0026#34;alertname\u0026#34;: \u0026#34;InstanceDown\u0026#34;, \u0026#34;instance\u0026#34;: \u0026#34;192.168.8.34:9110\u0026#34;, \u0026#34;job\u0026#34;: \u0026#34;node\u0026#34;, \u0026#34;role\u0026#34;: \u0026#34;api\u0026#34;, \u0026#34;service\u0026#34;: \u0026#34;node\u0026#34; }, \u0026#34;annotations\u0026#34;: { \u0026#34;description\u0026#34;: \u0026#34;192.168.8.34:9110: job node has been down vlaue==0\u0026#34;, \u0026#34;summary\u0026#34;: \u0026#34;192.168.8.34:9110: has been down\u0026#34; }, \u0026#34;startsAt\u0026#34;: \u0026#34;2019-07-25T21:53:13.352482605+08:00\u0026#34;, \u0026#34;endsAt\u0026#34;: \u0026#34;0001-01-01T00:00:00Z\u0026#34;, \u0026#34;generatorURL\u0026#34;: \u0026#34;http://zy-tw-tianxia-prod-prometheus01:19090/graph?g0.expr=up+%3D%3D+0\\u0026g0.tab=1\u0026#34; }], \u0026#34;groupLabels\u0026#34;: { \u0026#34;alertname\u0026#34;: \u0026#34;InstanceDown\u0026#34; }, \u0026#34;commonLabels\u0026#34;: { \u0026#34;alertname\u0026#34;: \u0026#34;InstanceDown\u0026#34;, \u0026#34;job\u0026#34;: \u0026#34;node\u0026#34;, \u0026#34;role\u0026#34;: \u0026#34;api\u0026#34;, \u0026#34;service\u0026#34;: \u0026#34;node\u0026#34; }, \u0026#34;commonAnnotations\u0026#34;: {}, \u0026#34;externalURL\u0026#34;: \u0026#34;http://zy-tw-tianxia-prod-prometheus01:9093\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;4\u0026#34;, \u0026#34;groupKey\u0026#34;: \u0026#34;{}:{alertname=\\\u0026#34;InstanceDown\\\u0026#34;}\u0026#34; } https://www.qikqiak.com/post/prometheus-operator-custom-alert/\ntelegram在文本消息中插入换行符 尝试\u0026rsquo;％0D\u0026rsquo;或\u0026rsquo;％0A\u0026rsquo; 参考地址：https://stackoverrun.com/cn/q/8787508\ntelegram api 使用\nhttps://stackoverrun.com/cn/q/8787508\n","permalink":"https://www.161616.top/telegram-bot-send-post-json/","summary":"text 1 2 3 4 5 6 7 8 9 10 11 curl -XPOST https://api.telegram.org/bot977657989:AAF0QE88WhxRIdpFLOYO_9ldLun5VtpfCWw/getUpdates curl -X POST \\ -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#39;{\u0026#34;chat_id\u0026#34;: \u0026#34;850233746\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;This is a test from curl\u0026#34;, \u0026#34;disable_notification\u0026#34;: true}\u0026#39; \\ https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage curl -X POST \\ -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#39;{\u0026#34;chat_id\u0026#34;: \u0026#34;850233746\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;This is a test from curl\u0026#34;, \u0026#34;disable_notification\u0026#34;: true}\u0026#39; \\ https://api.telegram.org/bot1009139816:AAGTmFsJDkH9H3E0OVoFi4GyvYp0uMctvcE/sendMessage https://api.telegram.org/bot721202655:AAG_kN1IHP93Wmnd90RRaJC-dK9tKQHddRA/sendMessage\njson 1 2 3 4 5 { \u0026#34;chat_id\u0026#34;: \u0026#34;-383641009\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;This is a test from curl\u0026#34;, \u0026#34;disable_notification\u0026#34;: true } alertmanager发送的消息类型如下：","title":"telegram接收altermanager消息"},{"content":" openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 什么是schema schema又称为数据模型，是openldap中用于来指定一个条目所包含的对象类(objectClass)以及每个对象类所包含的属性值(attribute)，其中属性值又包含必要属性和可选属性。\nNotes：拥有schema的数据代表该数据是结构化数据，无论他是什么格式，甚至于是一个连续的字符串\n如何理解schema 不管是在学习OpenLDAP时还是学习数据库时，都会遇到一个很迷糊的Schema的概念。\n在数据库中，对数据库的设计可以称之为schema。即schema约束了数据库的设计结构，并提供了整个数据库的描述。schema仅展示数据库的设计，如表字段的类型，表与表之间的关联。\n在ldap中schema与database中的schema一样，如列出的schema中，这些代表了对应的ldap结构的设计。\nwhat-is-a-schema\nschema overview\ntext 1 2 3 4 5 6 7 8 9 10 11 12 olcObjectClasses: ( 0.9.2342.19200300.100.4.19 NAME \u0026#39;simpleSecurityObject\u0026#39; DESC \u0026#39;RFC1274: simple security object\u0026#39; SUP top AUXILIARY MUST userPassword ) # 必须包含的属性 # # RFC 1274 + RFC 2247 olcAttributeTypes: ( 0.9.2342.19200300.100.1.25 NAME ( \u0026#39;dc\u0026#39; \u0026#39;domainComponent\u0026#39; ) # 表示属性名称 DESC \u0026#39;RFC1274/2247: domain component\u0026#39; EQUALITY caseIgnoreIA5Match # 相等性匹配 SUBSTR caseIgnoreIA5SubstringsMatch # 字符串匹配 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) # 表示数据类型 schema包含什么 在openldap的schema中，有四个重要的元素：\nObjectclass：Objectclass定义了一个类别，这个类别会被不同的目录（在LDAP中就是一个Entry）用到，它说明了该目录应该有哪些属性，哪些属性是必须的，哪些又是可选的。一个objectclass的定义包括名称（NAME），说明（DESC），类型（STRUCTURAL或AUXILARY），表示是结构型的还是辅助型的），必须属性（MUST），可选属性（MAY）等信息。\nAttribute：Attribute就是一个上面Objectclass中可能包含的属性，对其的定义包括名称，数据类型，单值还是多值以及匹配规则等。后面用具体的例子来说明。\nSyntax：syntax是LDAP中的 “语法”，其实就是LDAP中会用到的数据类型和数据约束，这个语法是遵从X.500中数据约束的定义的。其定义需要有一个ID（遵从X.500）以及说明（DESP）\nMatching Rules：是用来指定某属性的匹配规则，实际上就是定义一个特殊的Syntax的别名，让LDAP服务器可以识别，并对定义的属性进行匹配。\nSchema Files OpenLDAP随附了一些schema，在/usr/local/etc/openldap/ chema 目录中\n文件 说明 core.schema OpenLDAP core (required) cosine.schema Cosine 和 Internet X.500 inetorgperson.schema 在添加账号时,额外的objectClass. misc.schema nis.schema 网络信息服务(FYI),也是一种集中账号管理实现. openldap.schema OpenLDAP Project(experimental) dyngroup.schema 定义动态组时使用的schema,包括要使用sudo.schema时,也需要它 ppolicy.schema 需要做密码策略时,需要导入此schema sudo.schema 定义sudo规则 ","permalink":"https://www.161616.top/ch4-schema/","summary":"openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 什么是schema schema又称为数据模型，是openldap中用于来指定一个条目所包含的对象类(objectClass)以及每个对象类所包含的属性值(attribute)，其中属性值又包含必要属性和可选属性。\nNotes：拥有schema的数据代表该数据是结构化数据，无论他是什么格式，甚至于是一个连续的字符串\n如何理解schema 不管是在学习OpenLDAP时还是学习数据库时，都会遇到一个很迷糊的Schema的概念。\n在数据库中，对数据库的设计可以称之为schema。即schema约束了数据库的设计结构，并提供了整个数据库的描述。schema仅展示数据库的设计，如表字段的类型，表与表之间的关联。\n在ldap中schema与database中的schema一样，如列出的schema中，这些代表了对应的ldap结构的设计。\nwhat-is-a-schema\nschema overview\ntext 1 2 3 4 5 6 7 8 9 10 11 12 olcObjectClasses: ( 0.","title":"理解ldap配置 - OpenLDAP架构与Schema设计"},{"content":" openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 ldapsearch 查询api ldapsearch ldapsearch命令参数说明 语法\ntext 1 ldapsearch [options] filter [attributes] 参数 说 明 -W 指定密码，交互式，不需要在命令上写密码 -w 指定密码，需要命令上指定密码 -H ldapapi -D 所绑定的服务器的DN，如cn=admin,dc=etiantian,dc=org -f -f: filename.ldif文件 -b -b 指定作为查询节点而不是默认的 -LLL 以LDIF格式打印响应，不带注释 -x 简单的认证 简单的搜索 最简单的在查询ldap条目的最简单方法是使用带有 “-x” 选项进行简单身份验证，并使用 “-b” 指定搜索域。\nbash 1 $ ldapsearch -x -b \u0026lt;search_base\u0026gt; -H \u0026lt;ldap_host\u0026gt; 例如向 10.0.0.3 上openldap服务查询，该命令需要服务器接受匿名身份验证，这将可以查询而无需绑定管理员帐户\nbash 1 $ ldapsearch -x -b \u0026#34;dc=test,dc=com\u0026#34; -H ldap://10.0.0.3 使用管理员账户进行搜索 使用管理员帐户进行搜索，必须使用backend配置的 RootDN 并在命令行使用 “-D” 选项 和 “-W” ，如果要使用非交互式认证，使用选项 “-w”\nbash 1 $ ldapsearch -x -b \u0026lt;search_base\u0026gt; -H \u0026lt;ldap_host\u0026gt; -D \u0026lt;bind_dn\u0026gt; -W 例如，上章在安装时配置了RootDN：“ cn=admin,dc=test,dc=com ”。如果要使用此帐户执行搜索，可以使用命令\nbash 1 $ ldapsearch -x -b \u0026#34;dc=test,dc=com\u0026#34; -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -W 使用过滤器进行搜索 上述讲到的查询方法，是进行全局查询，会输出所有的条目，这样浪费了时间和资源，在大多数情况下，都希望查询以在特定的目录树中查找特定对象。而 ”过滤器“ filter 就是为了解决这个问题的。\n要使用过滤器搜索，需要在 ldapsearch 命令的末尾附加 filter 公式：\u0026lt;object_class\u0026gt;=\u0026lt;object_value\u0026gt;\nbash 1 $ ldapsearch \u0026lt;previous_options\u0026gt; \u0026#34;(object_type)=(object_value)\u0026#34; \u0026lt;optional_attributes\u0026gt; 例如查找所有对象，可以使用过滤器 objectclass 并且值为 ”*“ ，\nbash 1 $ ldapsearch -x -b \u0026lt;search_base\u0026gt; -H \u0026lt;ldap_host\u0026gt; -D \u0026lt;bind_dn\u0026gt; -W \u0026#34;objectclass=*\u0026#34; 查找特定的用户 如果查询目录树上的所有用户账户，如上一章初始化数据时，初始化的用户账户类为 ”posixAccount“ ，可以通过 ”posixAccount“ 来缩小查询范围\nbash 1 ldapsearch -x -b \u0026lt;search_base\u0026gt; -H \u0026lt;ldap_host\u0026gt; -D \u0026lt;bind_dn\u0026gt; -W \u0026#34;objectclass=posixAccount\u0026#34; 如果每个条目输出的内容多的情况下，也可以输出的属性值来进一步缩小搜索范围，例如只需要 uid 与 gidNumber\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $ ldapsearch -x -b \u0026#34;dc=test,dc=com\u0026#34; -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 \u0026#34;objectclass=posixAccount\u0026#34; uid gidNumber # extended LDIF # # LDAPv3 # base \u0026lt;dc=test,dc=com\u0026gt; with scope subtree # filter: objectclass=posixAccount # requesting: uid gidNumber # # user01, group, test.com dn: uid=user01,ou=group,dc=test,dc=com uid: user01 gidNumber: 10001 # cylon, group, test.com dn: uid=cylon,ou=group,dc=test,dc=com uid: cylon gidNumber: 10001 # search result search: 2 result: 0 Success # numResponses: 3 # numEntries: 2 使用运算符进行筛选搜索 ldapsearch 可以使用多个过滤器，使用多个过滤器用运算符 “AND” 进行分割，需要注意的一点是，多个过滤器必须将所有条件括在括号中，并在查询的开头加一个 “\u0026amp;” 字。\nbash 1 $ ldapsearch \u0026lt;previous_options\u0026gt; \u0026#34;(\u0026amp;(\u0026lt;condition_1\u0026gt;)(\u0026lt;condition_2\u0026gt;)...)\u0026#34; 例如，查找具有 “objectclass=posixAccount” 与 “uid=cylon” 的条目，您将运行以下查询\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 $ ldapsearch -x -b \u0026#34;dc=test,dc=com\u0026#34; -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 \u0026#34;(\u0026amp;(objectclass=posixAccount)(uid=cylon))\u0026#34; # extended LDIF # # LDAPv3 # base \u0026lt;dc=test,dc=com\u0026gt; with scope subtree # filter: (\u0026amp;(objectclass=posixAccount)(uid=cylon)) # requesting: ALL # # cylon, group, test.com dn: uid=cylon,ou=group,dc=test,dc=com objectClass: posixAccount objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person homeDirectory: /home/cylon loginShell: /bin/bash uid: cylon cn: cylon userPassword:: e1NTSEF9MnB2RTRDNnh5OGRrbVcyYUQvZUVvY1Zhamc4QnVqV1c= uidNumber: 10005 gidNumber: 10001 sn: cylon # search result search: 2 result: 0 Success # numResponses: 2 # numEntries: 1 也可以使用或运算符 ” OR “，使多个 “OR” 运算符，需要所有条件括在括号内，并使用符号 “ | ” 写在过滤条件开头。\nbash 1 $ ldapsearch \u0026lt;previous_options\u0026gt; \u0026#34;(|(\u0026lt;condition_1\u0026gt;)(\u0026lt;condition_2\u0026gt;)...)\u0026#34; 例如，搜索 uid=user01 或者 uid=zhangsan 的用户\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 $ ldapsearch -x -H ldap://10.0.0.3 -w 111 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -b \u0026#34;dc=test,dc=com\u0026#34; \u0026#39;(|(uid=user01)(uid=zhangsan))\u0026#39; # extended LDIF # # LDAPv3 # base \u0026lt;dc=test,dc=com\u0026gt; with scope subtree # filter: (|(uid=user01)(uid=zhangsan)) # requesting: ALL # # user01, group, test.com dn: uid=user01,ou=group,dc=test,dc=com objectClass: posixAccount objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person homeDirectory: /home/user01 loginShell: /bin/bash uid: user01 cn: user01 uidNumber: 10004 gidNumber: 10001 userPassword:: e1NTSEF9aEpwSUlWeGoxcVM5ZzA1cVVsZ0crbzdNTzE0RVhiRlE= sn: user01 givenName: user01 # search result search: 2 result: 0 Success # numResponses: 2 # numEntries: 1 也可以使用 “非” 运算符 ” ! “，使多个 “!” 运算符，需要所有条件括在括号内，并使用符号 “ ! ” 写在过滤条件开头。\nbash 1 $ ldapsearch \u0026lt;previous_options\u0026gt; \u0026#34;(!(\u0026lt;condition_1\u0026gt;)(\u0026lt;condition_2\u0026gt;)...)\u0026#34; 例如，匹配 uid 不为 zhangsan 的用户\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 $ ldapsearch -x -H ldap://10.0.0.3 -w 111 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -b \u0026#34;dc=test,dc=com\u0026#34; \u0026#39;(!(uid=zhangsan))\u0026#39; # extended LDIF # # LDAPv3 # base \u0026lt;dc=test,dc=com\u0026gt; with scope subtree # filter: (!(uid=user01)) # requesting: ALL # # test.com dn: dc=test,dc=com objectClass: top objectClass: organizationalUnit objectClass: extensibleObject description: US Organization ou: people dc: test # group, test.com dn: ou=group,dc=test,dc=com objectClass: organizationalUnit ou: group # tech, group, test.com dn: cn=tech,ou=group,dc=test,dc=com objectClass: posixGroup gidNumber: 10001 cn: tech memberUid: user01 memberUid: cylon # cylon, group, test.com dn: uid=cylon,ou=group,dc=test,dc=com objectClass: posixAccount objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person homeDirectory: /home/cylon loginShell: /bin/bash uid: cylon cn: cylon userPassword:: e1NTSEF9MnB2RTRDNnh5OGRrbVcyYUQvZUVvY1Zhamc4QnVqV1c= uidNumber: 10005 gidNumber: 10001 sn: cylon # search result search: 2 result: 0 Success # numResponses: 5 # numEntries: 4 使用多个过滤条件 多个过滤条件可以使用 () 括起来所有的过滤条件\nbash 1 $ ldapsearch \u0026lt;previous_options\u0026gt; \u0026#34;(|(\u0026lt;condition_1\u0026gt;)(\u0026lt;condition_2\u0026gt;)...)\u0026#34; 使用通配符进行搜索 除了运算符，还有一种高校过滤器就是通配符 “*” 这使得 ldapsearch 过滤器拥有一些正则表达式功能\n使用通配符，只需要对应的查询条件字符串结尾或开头加上 “*” 即可\nbash 1 2 3 $ ldapsearch \u0026lt;previous_options\u0026gt; \u0026#34;(object_type)=*(object_value)\u0026#34; $ ldapsearch \u0026lt;previous_options\u0026gt; \u0026#34;(object_type)=(object_value)*\u0026#34; 例如查询 uid 以 “c” 开头的用户\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 $ ldapsearch -x -H ldap://10.0.0.3 -w 111 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -b \u0026#34;dc=test,dc=com\u0026#34; \u0026#39;(uid=c*)\u0026#39; # extended LDIF # # LDAPv3 # base \u0026lt;dc=test,dc=com\u0026gt; with scope subtree # filter: (uid=c*) # requesting: ALL # # cylon, group, test.com dn: uid=cylon,ou=group,dc=test,dc=com objectClass: posixAccount objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person homeDirectory: /home/cylon loginShell: /bin/bash uid: cylon cn: cylon userPassword:: e1NTSEF9MnB2RTRDNnh5OGRrbVcyYUQvZUVvY1Zhamc4QnVqV1c= uidNumber: 10005 gidNumber: 10001 sn: cylon # search result search: 2 result: 0 Success # numResponses: 2 # numEntries: 1 扩展过滤器 可以由符合 “ : ”隔的其他过滤器，例如区分大小写与不区分大小写\nCaseIgnoreMatch 不区分大小写 cn:caseExactMatch 区分大小写 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 $ ldapsearch -x -H ldap://10.0.0.3 -w 111 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -b \u0026#34;dc=test,dc=com\u0026#34; \u0026#34;cn:CaseIgnoreMatch:=USER01\u0026#34; # extended LDIF # # LDAPv3 # base \u0026lt;dc=test,dc=com\u0026gt; with scope subtree # filter: cn:CaseIgnoreMatch:=USER01 # requesting: ALL # # user01, group, test.com dn: uid=user01,ou=group,dc=test,dc=com objectClass: posixAccount objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person homeDirectory: /home/user01 loginShell: /bin/bash uid: user01 cn: user01 uidNumber: 10004 gidNumber: 10001 userPassword:: e1NTSEF9aEpwSUlWeGoxcVM5ZzA1cVVsZ0crbzdNTzE0RVhiRlE= sn: user01 givenName: user01 # search result search: 2 result: 0 Success # numResponses: 2 # numEntries: 1 例如类似值搜索\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 $ ldapsearch -x -H ldap://10.0.0.3 -w 111 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -b \u0026#34;dc=test,dc=com\u0026#34; \u0026#34;givenName~=usr01\u0026#34; # extended LDIF # # LDAPv3 # base \u0026lt;dc=test,dc=com\u0026gt; with scope subtree # filter: givenName~=usr011 # requesting: ALL # # user01, group, test.com dn: uid=user01,ou=group,dc=test,dc=com objectClass: posixAccount objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person homeDirectory: /home/user01 loginShell: /bin/bash uid: user01 cn: user01 uidNumber: 10004 gidNumber: 10001 userPassword:: e1NTSEF9aEpwSUlWeGoxcVM5ZzA1cVVsZ0crbzdNTzE0RVhiRlE= sn: user01 givenName: user01 # search result search: 2 result: 0 Success # numResponses: 2 # numEntries: 1 查找openldap服务配置 ldapsearch 命令有一种高级用法是查询 slapd 服务的配置。要进行这种搜索，必须使用选项 “-Y” 并指定 关键字 “EXTERNAL” 作为身份验证机制。\nbash 1 $ ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config 例如\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 $ ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config SASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 # extended LDIF # # LDAPv3 # base \u0026lt;cn=config\u0026gt; with scope subtree # filter: (objectclass=*) # requesting: ALL # # config dn: cn=config objectClass: olcGlobal cn: config olcArgsFile: /var/run/openldap/slapd.args olcDisallows: bind_anon olcLogLevel: stats olcPidFile: /var/run/openldap/slapd.pid olcTLSCACertificatePath: /etc/openldap/certs olcTLSCertificateFile: \u0026#34;OpenLDAP Server\u0026#34; olcTLSCertificateKeyFile: /etc/openldap/certs/password # schema, config dn: cn=schema,cn=config objectClass: olcSchemaConfig cn: schema olcObjectIdentifier: OLcfg 1.3.6.1.4.1.4203.1.12.2 ... # {1}collective, schema, config dn: cn={1}collective,cn=schema,cn=config objectClass: olcSchemaConfig cn: {1}collective ... # {-1}frontend, config dn: olcDatabase={-1}frontend,cn=config objectClass: olcDatabaseConfig objectClass: olcFrontendConfig olcDatabase: {-1}frontend # {0}config, config dn: olcDatabase={0}config,cn=config objectClass: olcDatabaseConfig olcDatabase: {0}config olcAccess: {0}to attrs=userPassword,shadowLastChange by dn.children=\u0026#34;cn=admi n,dc=test,dc=com\u0026#34; write by anonymous auth by self write by * none olcAccess: {1}to * by dn.base=\u0026#34;gidNumber=0+uidNumber=0,cn=peercred,cn=external ,cn=auth\u0026#34; manage by group.exact=\u0026#34;cn=configadmin,ou=admin,dc=seal,dc=com\u0026#34; wr ite by * none # {1}monitor, config dn: olcDatabase={1}monitor,cn=config objectClass: olcDatabaseConfig olcDatabase: {1}monitor olcAccess: {0}to * by dn.base=\u0026#34;gidNumber=0+uidNumber=0,cn=peercred,cn=external ,cn=auth\u0026#34; read by dn.base=\u0026#34;cn=Manager,dc=my-domain,dc=com\u0026#34; read by * none # {2}mdb, config dn: olcDatabase={2}mdb,cn=config objectClass: olcDatabaseConfig objectClass: olcMdbConfig olcDatabase: {2}mdb olcDbDirectory: /var/lib/ldap olcSuffix: dc=test,dc=com olcRootDN: cn=admin,dc=test,dc=com olcRootPW: {SSHA}xU9xFym/s7rawpmzpsYE+Q1qPsVPOwDw olcDbCheckpoint: 1024 10 olcDbIndex: objectClass eq,pres olcDbIndex: uid,ou,cn,mail,surname,givenname eq,pres,sub # search result search: 2 result: 0 Success # numResponses: 20 # numEntries: 19 Notes：这种查询命令需要直接在slapd服务对应节点上运行。\n这类搜索也可以使用过滤器，例如指定搜索数据库的配置\nbash 1 $ ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config \u0026#34;(objectclass=olcDatabaseConfig)\u0026#34; 更新API ldapmodify ldapmodify 有两个参数来指定如何修改数据：\nreplace 要修改的字段 changetype: modify 模式为修改模式 dn 对那个条目进行搜索，RootDN的后缀，对于每个条目例如，user 为 uid=\u0026lt;\u0026gt;,ou=\u0026lt;\u0026gt;,dc=test,dc=com 例如下面命令是将 cylon用户的uid更改为10010\nbash 1 2 3 4 5 6 $ cat \u0026lt;\u0026lt; EOF | ldapmodify -r -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: uid=cylon,ou=Group,dc=test,dc=com changetype: modify replace: uidNumber uidNumber: 10010 EOF 删除API ldapdelete 删除API与更新API类似，不过内容只需要一个 dn （这个dn是隐式的，不用单独声明字段），例如删除用户cylon\nbash 1 2 3 cat \u0026lt;\u0026lt; EOF | ldapdelete -r -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 uid=cylon,ou=Group,dc=test,dc=com EOF 使用 ldapmodify 删除条目，只要吧 changetype: delete 在加上显式声明的 dn 也可以删除条目，例如\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 $ ldapsearch -x -H ldap://10.0.0.3 -w 111 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -b \u0026#34;dc=test,dc=com\u0026#34; uid=cylon # extended LDIF # # LDAPv3 # base \u0026lt;dc=test,dc=com\u0026gt; with scope subtree # filter: uid=cylon # requesting: ALL # # cylon, group, test.com dn: uid=cylon,ou=group,dc=test,dc=com objectClass: posixAccount objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person homeDirectory: /home/cylon loginShell: /bin/bash uid: cylon cn: cylon userPassword:: e1NTSEF9MnB2RTRDNnh5OGRrbVcyYUQvZUVvY1Zhamc4QnVqV1c= uidNumber: 10005 gidNumber: 10001 sn: cylon # search result search: 2 result: 0 Success # numResponses: 2 # numEntries: 1 $ cat \u0026lt;\u0026lt; EOF | ldapmodify -r -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: uid=cylon,ou=Group,dc=test,dc=com changetype: delete EOF deleting entry \u0026#34;uid=cylon,ou=Group,dc=test,dc=com\u0026#34; $ ldapsearch -x -H ldap://10.0.0.3 -w 111 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -b \u0026#34;dc=test,dc=com\u0026#34; uid=cylon # extended LDIF # # LDAPv3 # base \u0026lt;dc=test,dc=com\u0026gt; with scope subtree # filter: uid=cylon # requesting: ALL # # search result search: 2 result: 0 Success # numResponses: 1 插入API ldapadd ldapapp 使用起来比较复杂，在添加时，区分与RootDN，子条目，并且属性相关都需要配置对\n下列时增加一个用户\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: uid=cylon,ou=Group,dc=test,dc=com objectClass: posixAccount objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person homeDirectory: /home/cylon loginShell: /bin/bash uid: cylon cn: cylon userPassword: {SSHA}2pvE4C6xy8dkmW2aD/eEocVajg8BujWW uidNumber: 10005 gidNumber: 10001 sn: cylon EOF Reference ​[1] Managing Entries ldapmodify and ldapdelete\n​[2] How To Search LDAP using ldapsearch\n","permalink":"https://www.161616.top/ch3-commandline/","summary":"openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 ldapsearch 查询api ldapsearch ldapsearch命令参数说明 语法\ntext 1 ldapsearch [options] filter [attributes] 参数 说 明 -W 指定密码，交互式，不需要在命令上写密码 -w 指定密码，需要命令上指定密码 -H ldapapi -D 所绑定的服务器的DN，如cn=admin,dc=etiantian,dc=org -f -f: filename.","title":"理解ldap - OpenLDAP客户端命令行使用"},{"content":" openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 生产服务器硬件配置需求 ldap服务对系统环境的要求不高，一般在生产场景，ldap服务应该最少是两台，这样某一台物理服务器岩机才不会因单点问题影响生产业务故障，对于硬件要求，本质上openldap使用硬件资源并不大，网上有两个帖子提出了openldap的硬件需求：\n2003年openldap官网留言，我想安装一个 LDAP 服务器来验证邮件服务器的用户，目前有200个用户需要多少内存和CPU？[1] 1GHZ PIII/512MB 足以 运行于Ubuntu LXC 之上的openldap，用户150,000，sladp进程常驻内存为200-300MB，mdb数据库文件大小为377MB，10 并发平均响应时间为 9-11 毫秒 [13] 操作系统：Centos7/8 64bit。\n操 作 系 统 其 它 CentOS-7.6 当前很稳定且免费的Linux版本。 网卡及IP资源\n名 称 接 口 IP 用途 ldap主服务器01 eth0 10.0.0.17 外部管理IP，用于WAN数据转发。 eth1 10.0.0.17 备用管理IP，用于LAN内数据转发。 ldap从服务器02 eth0 10.0.0.8 管理IP，用于LAN数据转发。 eth1 10.0.0.18 外部管理IP，用于WAN数据转发。 Tips：内外网IP分配可采用最后8位相同的方式，这样使于管理。\nopenldap master服务安装 CentOS/Redhat 安装OpenLDAP组件\nbash 1 2 3 4 5 6 yum install -y \\ openldap \\ openldap-servers \\ openldap-clients \\ openldap-devel \\ compat-openldap Ubuntu18.04/22.04/20.04\nbash 1 sudo apt -y install slapd ldap-utils 默认OpenLDAP服务所使用的端口为389，此端口采用明文传输数据，数据信息得不到保障。所以可以通过配置CA及结合TLS/SASL实现数据加密传输，所使用端口为636。\n编译安装\ntext 1 2 3 4 5 6 yum install -y \\ libtool-ltdl \\ libtool-ltdl-devel \\ gcc \\ openssl \\ openssl-devel Openldap依赖的相关软件：\nhttp://www.openldap.org/doc/admin24/install.html openldap参数配置优化 openldap配置文件分为五部分\nsladp进程配置部分 frontend：是一个特殊的 olcDatabaseConfig 配置提供权限认证 database：存储的真实引擎 backend：backend在slapd中不是真是的数据库，而是提供的一种转发方式 openldap目录布局 /etc/openldap/slapd.d/*： /etc/openldap/slapd.ldif配置信息生成的文件，每修改一次配置信息，这里的东西就要重新生成。 /var/lib/ldap/*：OpenLDAP的数据文件。 /usr/share/openldap-servers/DB_CONFIG.example：模板数据库配置文件。 /usr/share/openldap-servers/slapd.ldif：默认模板配置文件。 默认OpenLdap服务所使用的端口为389，此端口采用明文传输数据，数据信息得不到保障。所以可以通过配置CA及结合TLS/SSL实现数据加密传输，所使用端口为636。\ntext 1 cp /usr/share/openldap-servers/slapd.ldif /etc/openldap/ 指定密码，不提示\ntext 1 2 $ slappasswd -s 111 {SSHA}QnB7dO98+hoCUgiaAYaiJWnDzlhn2Tn6 ldif # 指定要搜索的后缀\rolcSuffix: dc=cylon,dc=org\r# rootdn，使用这个dn可以登录服务器\rolcRootDN: cn=admin,dc=cylon,dc=org\rolcDbDirectory: /var/lib/ldap\r# 指定ldapserver管理员密码==\rolcRootPW: {SSHA}QnB7dO98+hoCUgiaAYaiJWnDzlhn2Tn6 日志及缓存参数。 text 1 2 3 4 5 6 7 8 # 设置日志级别，记录日志信息方便调试 stats 256（日志连接/操作/结果） olcLogLevel: stats # 设置ldap可以缓存的记录数 olcDbCacheSize: 1000 # checkpoint 可以吧内存中的数据写会数据文件的操作，此设置表示每达到2048kb或者10分钟执行一次 olcDbCheckpoint: 1024 10 开启扩展schema openldap自带一些ldif文件 [10]，LDIF 是 LDAP Data Interchange Format 的缩写，是作为存储与LDAP中 ” 文本格式 “ 的数据交换格式，每个条目代表的存入LDAP中记录的属性，记录之间用空行分隔，每行都是 “属性:值” 的格式，例如我们存入LDAP中一个记录，其属性有\ndn (distinguished name) 用于标识目录中名称的唯一标识符 dc (domain component) 表示域组成，例如域名 www.mydomain.com 那么dc 应该配置为DC=www,DC=mydomain,DC=com ou (organizational unit) 这是指用户的组织，这里也可以代表用户组，例如 OU=Lawyer,OU=Developer cn (common name) 表示查询的个体对象的名称，这里可以代表用户名，服务名等，例如 cn=cylon 具有多个属性条目，在ldap中代表一条记录，例如\ntext 1 2 3 dn: cn=The Postmaster,dc=example,dc=com objectClass: organizationalRole cn: The Postmaster 而ldif文件就是定义这些属性的文件，下面是openldap安装后默认的ldif文件说明：\ncollective.ldif ：Collective Attribute 组成LDAP条目的共享属性\ncorba.ldif：Common Object Request Broker Architecture 的缩写，旨在促进部署在不同平台上的系统的通信 [2]\ncosine.ldif：Cooperation for Open Systems Interconnection Networking in Europe的缩写，用于给 cosine 与 Internet X.500 模式项目提供LDAP中的属性格式 [3]\nduaconf.ldif：Directory User Agents 的缩写，DUA是协议客户端，是向ldap或Internet X.500 DSA发起请求的一端，这是为DUA客户端提供了通用配置 [4]\ndyngroup.ldif：Dynamic Group的缩写，动态组是LDAP中的一种组，与静态组相反，DG是以URL形式为搜索条件来隐式定义一组用户。[5]\n静态组 Static Groups 使用一组DN显式定义一组用户 inetorgperson.ldif：inetOrgPerson类是 RFC2798中 定义的类。在LDAP中为 user 的父类，比如说用户密码，登录时间等属性，更多可以参考 [6]\njava.ldif：用于java的一些属性\nmisc.ldif：Miscellaneous 的简写，这里主要是一些电子邮件相关属性\nnis.ldif：Network Information Service 的缩写，NIS是一种发现机制，这里是一种server-client的目录属性，例如网络中的主机名，用户名之类 [7]\nopenldap.ldif：\npmi.ldif：Privilege Management Infrastructure 的缩写，是基于x.509的授权访问控制模型，这里是提供了基于PMI的一些属性\nppolicy.ldif：password policy 的缩写，提供了增强的密码管理功能，例如账户到期时间，锁定等\ntext 1 2 3 4 5 6 7 8 9 10 11 12 include: file:///etc/openldap/schema/collective.ldif # OpenLDAP的核心schema必须 include: file:///etc/openldap/schema/corba.ldif # include: file:///etc/openldap/schema/cosine.ldif include: file:///etc/openldap/schema/duaconf.ldif include: file:///etc/openldap/schema/dyngroup.ldif include: file:///etc/openldap/schema/inetorgperson.ldif include: file:///etc/openldap/schema/java.ldif include: file:///etc/openldap/schema/misc.ldif include: file:///etc/openldap/schema/nis.ldif include: file:///etc/openldap/schema/openldap.ldif include: file:///etc/openldap/schema/pmi.ldif include: file:///etc/openldap/schema/ppolicy.ldif 授权及安全参数配置 ldif access to dn=\u0026#34;cn=subschema\u0026#34; by * read\raccess to by self write by dn subtree=\u0026#34;ou=sysusers,dc=test,dc=com\u0026#34; read\rby anonymous auth 关于更多权限管理的说明，可以参考官方手册第八章 [12]\n提示:\n参数在文件中的先后位置不能随意动。\n空行和以“#”开头的注释行将被忽略。如果一行以空格开头，它将被认为是接着前一行的（即使前一行是注释）。\n配置syslog记录ldap服务日志配置syslog 记录ldap服务日志，默认级别为256；\nsh 1 2 echo \u0026#39;local4.* /var/log/slapd.log\u0026#39; \u0026gt;\u0026gt; /etc/rsyslog.conf local4.* /var/log/slapd.log 配置LDAP数据库存放路径 注意：\nslapd.conf中设定了LDAP数据库格式为hdb，存储路径/var/1ib/ldap\ntext 1 2 3 4 cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG chown ldap.ldap /var/lib/ldap/DB_CONFIG chmod 700 /var/lib/ldap/DB_CONFIG 整合的配置 ldif #\r# See slapd-config(5) for details on configuration options.\r# This file should NOT be world readable.\r#\rdn: cn=config\robjectClass: olcGlobal\rcn: config\rolcArgsFile: /var/run/openldap/slapd.args\rolcPidFile: /var/run/openldap/slapd.pid\rolcLogLevel: stats\rolcDisallows: bind_anon\r#\r# TLS settings\r#\rolcTLSCACertificatePath: /etc/openldap/certs\rolcTLSCertificateFile: \u0026#34;OpenLDAP Server\u0026#34;\rolcTLSCertificateKeyFile: /etc/openldap/certs/password\r#\r# Do not enable referrals until AFTER you have a working directory\r# service AND an understanding of referrals.\r#\r#olcReferral: ldap://root.openldap.org\r#\r# Sample security restrictions\r#\tRequire integrity protection (prevent hijacking)\r#\tRequire 112-bit (3DES or better) encryption for updates\r#\tRequire 64-bit encryption for simple bind\r#\r#olcSecurity: ssf=1 update_ssf=112 simple_bind=64\r#\r# Load dynamic backend modules:\r# - modulepath is architecture dependent value (32/64-bit system)\r# - back_sql.la backend requires openldap-servers-sql package\r# - dyngroup.la and dynlist.la cannot be used at the same time\r#\r#dn: cn=module,cn=config\r#objectClass: olcModuleList\r#cn: module\r#olcModulepath:\t/usr/lib/openldap\r#olcModulepath:\t/usr/lib64/openldap\r#olcModuleload: accesslog.la\r#olcModuleload: auditlog.la\r#olcModuleload: back_dnssrv.la\r#olcModuleload: back_ldap.la\r#olcModuleload: back_mdb.la\r#olcModuleload: back_meta.la\r#olcModuleload: back_null.la\r#olcModuleload: back_passwd.la\r#olcModuleload: back_relay.la\r#olcModuleload: back_shell.la\r#olcModuleload: back_sock.la\r#olcModuleload: collect.la\r#olcModuleload: constraint.la\r#olcModuleload: dds.la\r#olcModuleload: deref.la\r#olcModuleload: dyngroup.la\r#olcModuleload: dynlist.la\r#olcModuleload: memberof.la\r#olcModuleload: pcache.la\r#olcModuleload: ppolicy.la\r#olcModuleload: refint.la\r#olcModuleload: retcode.la\r#olcModuleload: rwm.la\r#olcModuleload: seqmod.la\r#olcModuleload: smbk5pwd.la\r#olcModuleload: sssvlv.la\r#olcModuleload: syncprov.la\r#olcModuleload: translucent.la\r#olcModuleload: unique.la\r#olcModuleload: valsort.la\r#\r# Schema settings\r#\rdn: cn=schema,cn=config\robjectClass: olcSchemaConfig\rcn: schema\rinclude: file:///etc/openldap/schema/core.ldif\rinclude: file:///etc/openldap/schema/collective.ldif\rinclude: file:///etc/openldap/schema/corba.ldif\rinclude: file:///etc/openldap/schema/cosine.ldif\rinclude: file:///etc/openldap/schema/duaconf.ldif\rinclude: file:///etc/openldap/schema/dyngroup.ldif\rinclude: file:///etc/openldap/schema/inetorgperson.ldif\rinclude: file:///etc/openldap/schema/java.ldif\rinclude: file:///etc/openldap/schema/misc.ldif\rinclude: file:///etc/openldap/schema/nis.ldif\rinclude: file:///etc/openldap/schema/openldap.ldif\rinclude: file:///etc/openldap/schema/pmi.ldif\rinclude: file:///etc/openldap/schema/ppolicy.ldif\r#\r# Frontend settings\r#\r# 这里是对前端权限的配置，通常默认，不添加权限\rdn: olcDatabase=frontend,cn=config\robjectClass: olcDatabaseConfig\robjectClass: olcFrontendConfig\rolcDatabase: frontend\r#\r# Sample global access control policy:\r#\tRoot DSE: allow anyone to read it\r#\tSubschema (sub)entry DSE: allow anyone to read it\r#\tOther DSEs:\r#\tAllow self write access\r#\tAllow authenticated users read access\r#\tAllow anonymous users to authenticate\r#\r#olcAccess: to dn.base=\u0026#34;\u0026#34; by * read\r#olcAccess: to dn.base=\u0026#34;cn=Subschema\u0026#34; by * read\r#olcAccess: to *\r#\tby self write\r#\tby users read\r#\tby anonymous auth\r#\r# if no access controls are present, the default policy\r# allows anyone and everyone to read anything but restricts\r# updates to rootdn. (e.g., \u0026#34;access to * by * read\u0026#34;)\r#\r# rootdn can always read and write EVERYTHING!\r#\r#\r# Configuration database\r#\r# 这里是对后端数据库权限的配置\rdn: olcDatabase=config,cn=config\robjectClass: olcDatabaseConfig\rolcDatabase: config\rolcAccess: to attrs=userPassword,shadowLastChange\rby dn.children=\u0026#34;cn=admin,dc=test,dc=com\u0026#34; write\rby anonymous auth\rby self write\rby * none\rolcAccess: to * by dn.base=\u0026#34;gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth\u0026#34; manage\rby group.exact=\u0026#34;cn=configadmin,ou=admin,dc=seal,dc=com\u0026#34; write\rby * none\r#\r# Server status monitoring\r#\rdn: olcDatabase=monitor,cn=config\robjectClass: olcDatabaseConfig\rolcDatabase: monitor\rolcAccess: to * by dn.base=\u0026#34;gidNumber=0+uidNumber=0,cn=peercred,cn=external,c\rn=auth\u0026#34; read by dn.base=\u0026#34;cn=Manager,dc=my-domain,dc=com\u0026#34; read by * none\r#\r# Backend database definitions\r#\r# 这里是数据库的参数配置\rdn: olcDatabase=mdb,cn=config\robjectClass: olcDatabaseConfig\r# 使用的数据库引擎是mdb\robjectClass: olcMdbConfig\rolcDatabase: mdb\r# Suffix 为数据库的后缀，每个数据库至少一个，在搜索时-D 后面的域后缀为dc=test,dc=com将被pass到这里\rolcSuffix: dc=test,dc=com\r# 指不收前面配置的权限控制的管理员账户，拥有最最高权限\rolcRootDN: cn=admin,dc=test,dc=com\r# 特权账户的登录密码\rolcRootPW: {SSHA}xU9xFym/s7rawpmzpsYE+Q1qPsVPOwDw\rolcDbDirectory:\t/var/lib/ldap\r# 这是索引属性，下面是默认的属性\r# 下列注释行意思为\r# olcDbIndex: default pres,eq\r# olcDbIndex: uid\r# olcDbIndex: cn,sn pres,eq,sub\r# olcDbIndex: objectClass eq\r# pres,eq 为 present equality\r# 第二行意思为，为uid属性类型维护默认索引集\r# 第三行意思为，为cn,sn属性维护pres,eq,sub索引集\r# 索引集类型有 pres,eq,approx,sub,none\rolcDbIndex: objectClass eq,pres\rolcDbIndex: uid,ou,cn,mail,surname,givenname eq,pres,sub\r# 配置从缓冲区写入磁盘的，两个参数分别为多少kbyte大小的数据自上次（第二个参数）分钟则发生一次写入\rolcDbCheckpoint: 1024 10 更多配置文件选项说明可以参考 [14]\n生成配置文件 修改配置文件后需要重新生成配置文件\ntext 1 slapadd -n 0 -F /etc/openldap/slapd.d -l /etc/openldap/slapd.ldif 生成配置文件时的错误\nbash 1 2 3 4 $ slapadd -n 0 -F /etc/openldap/slapd.d -l /etc/openldap/slapd.ldif 63678619 str2entry: entry -1 has no dn slapadd: could not parse entry (line=31) _######## 43.66% eta none elapsed none spd 7.0 M/s 对于 slapd.ldif 需要注意下列事项\n行首 # 为注释 行尾不能有任何空白，这里也是很难排查的一个点 对于不同的属性需要放对位置快否则会报错，例如 olcDbCacheSize: 1000 是hdb配置，mdb添加会报错，有明显提示 如果生产配置失败后，修改配置文件后再次生成需要删除 rm -fr slapd.d/* 例如下面就是一个有提示的典型例子\nbash 1 2 63678513 Entry (cn=config), attribute \u0026#39;olcDbCacheSize\u0026#39; not allowed slapadd: dn=\u0026#34;cn=config\u0026#34; (line=1): (65) attribute \u0026#39;olcDbCacheSize\u0026#39; not allowed 下面的报错比较不明显，通常删除 rm -fr slapd.d/* 后重试\nbash 1 2 slapadd: could not add entry dn=\u0026#34;cn=config\u0026#34; (line=1): _### 17.92% eta none elapsed none spd 6.3 M/s 为LDAP初始化数据 部署完成后就是访问，ldap了，此时向OpenLDAP 搜索会发现没有内容\ntext 1 2 3 4 $ ldapsearch -LLL -x -W -H ldap://10.0.0.4 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -b \u0026#34;dc=test,dc=com\u0026#34; \u0026#34;(uid=*)\u0026#34; Enter LDAP Password: No such object (32) Reference http://www.openldap.org/faq/data/cache/1.html http://www.openldap.org/doc/admin24/appendix-common-errors.html 创建Root条目 在第一次部署好openldap中，实际上是没有任何条目的，此时是无法存入数据，有人说在 database setting中配置了 olcRootDN ，这里是指标识用哪个数据库的（即那个root存入哪里），而不是一个具体的root dn，所以需要手动创建一个\n例如下列，是创建一个RootDN\nbash 1 2 3 4 5 6 7 8 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: dc=test,dc=com objectclass: top objectClass: organizationalUnit objectclass: extensibleObject description: US Organization ou: people EOF 这里需要注意几点：\ndn: dc=test,dc=com 是指定存储的地方，如果在database配置中为配置 olcRootDN 则报错不会被存储 由于创建的的是root，所以ou的 objectClass 会报错，需要用一个 extensibleObject 才可以创建 [15] 创建子域 此时因为有了RootDN，可以指定dn为子域了，并且 objectClass: organizationalUnit 可以单独使用\n这里子域其实可以理解为二级域名了，在公司中也可以为子公司\nbash 1 2 3 4 5 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: ou=group,dc=test,dc=com objectClass: organizationalUnit ou: group EOF 创建组 这里使用 posixGroup Portable Operating System Interface of UNIX 的简写，这里可以理解为Linux用户管理的标准，包含一些标准的属性，类似于 /etc/group\n下面创建一个gid为10001的组，组名为tech\nbash 1 2 3 4 5 6 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: cn=tech,ou=group,dc=test,dc=com objectClass: posixGroup gidNumber: 10001 cn: tech EOF 创建用户 创建用户user01\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: uid=user01,ou=Group,dc=test,dc=com objectClass: posixAccount objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person homeDirectory: /home/user01 loginShell: /bin/bash uid: user01 cn: user01 uidNumber: 10004 gidNumber: 10001 userPassword: {SSHA}hJpIIVxj1qS9g05qUlgG+o7MO14EXbFQ sn: user01 givenName: user01 创建用户cylon\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 cat \u0026lt;\u0026lt; EOF | ldapadd -x -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -w 111 dn: uid=cylon,ou=Group,dc=test,dc=com objectClass: posixAccount objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person homeDirectory: /home/cylon loginShell: /bin/bash uid: cylon cn: cylon userPassword: {SSHA}2pvE4C6xy8dkmW2aD/eEocVajg8BujWW uidNumber: 10005 gidNumber: 10001 sn: cylon EOF 如上面所示，objectClass [11] 表示这个ldap条目拥有的属性，可以看到 posixAccount，inetOrgPerson 等都是导入的schema文件，其中 uidNumber ，userPassword 都是 NIS 定义的，cn 则是 core定义的\n提示:\n以上信息中ldap 用户1：user01密码 111 用户2：cylon 密码 123456 这些原始信息是如何获得的？ 检查初始化测试数据。 查询导入的结果，默认查的是所有数据\ntext 1 ldapsearch -LLL -w 111 -x -H ldap://10.0.0.3 -D \u0026#34;cn=admin,dc=test,dc=com\u0026#34; -b \u0026#34;dc=test,dc=com\u0026#34; 备份ldap数据库数据 bash 1 2 ldapadd -x -H ldap://cylon.org -D \u0026#34;cn=admin,dc=cylon,dc=org\u0026#34; -W -f base.ldif \u0026gt;base.ldif ldapadd -x -H ldap://cylon.org -D \u0026#34;cn=admin,dc=cylon,dc=org\u0026#34; -W -f test.ldif \u0026gt;test.ldif Reference ​[1] msg00281\n​[2] Object Request Broker Architecture\n​[3] Cooperation for Open Systems Interconnection Networking in Europe\n​[4] DUA\n​[5] DynamicGroup\n​[6] InetOrgPerson\n​[7] Network Information Service\n​[8] Privilege Management Infrastructure\n​[9] Password Policy\n​[10] LDAP Data Interchange Format\n​[11] Object Class\n​[12] 8. Access Control\n​[13] openldap system requirements for virtualization\n​[14] slapdconf2\n​[15] attribute dc is not allowed\n","permalink":"https://www.161616.top/ch2-install/","summary":"openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 生产服务器硬件配置需求 ldap服务对系统环境的要求不高，一般在生产场景，ldap服务应该最少是两台，这样某一台物理服务器岩机才不会因单点问题影响生产业务故障，对于硬件要求，本质上openldap使用硬件资源并不大，网上有两个帖子提出了openldap的硬件需求：\n2003年openldap官网留言，我想安装一个 LDAP 服务器来验证邮件服务器的用户，目前有200个用户需要多少内存和CPU？[1] 1GHZ PIII/512MB 足以 运行于Ubuntu LXC 之上的openldap，用户150,000，sladp进程常驻内存为200-300MB，mdb数据库文件大小为377MB，10 并发平均响应时间为 9-11 毫秒 [13] 操作系统：Centos7/8 64bit。\n操 作 系 统 其 它 CentOS-7.6 当前很稳定且免费的Linux版本。 网卡及IP资源","title":"理解ldap - OpenLDAP安装"},{"content":" openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 什么是目录服务？ ==目录是一类为了浏览和搜索数据而设计的特殊的数据库==，例如：为人所熟知的微软公 司的活动目录（active directory）就是目录数据库的一种，目录服务是按照==树状形式==存储信息的，目录包含基于属性的描述性信息，并且支持高级的过滤功能。\nhttp://www.openldap.org/doc/admin24/intro.html 一般来说，目录不支持大多数事务型数据库所支持的高吞吐量和复杂的更新操作。目录进行更新操作，可以说是要么全部，要么都不的原子操作。目录服务适合的业务应用在于提供大量的查询和搜索操作，而不是大量的写入操作。Ldap可以说是活动目录在linux系统上的一个开源实现。\n为了保证目录数据的可用性和可靠性，在确保使用目录服务提供快速查询和搜索操作的同时，目录服务还提供了==主从服务器同步目录数据信息的能力==，这相当于传统的MySQL数据库的主从同步功能一样，可以最大限度的确保基于目录业务的服务持续可用性与提供并发查询能力，微软公司的活动目录（active directory）就有主域和备份域的说法。\n广义的目录服务概念，可以用多种不同的方式来提供目录服务。不同的目录所允许存储的信息是不同的，在信息如何被引用、查询、更新以及防止未经授权的访问等问题上，不同的目录服务的处理方式也有诸多的不同。\n例如：一些目录服务是本地的，只提供受限的服务（比如，单机上的finger服务）。另一些服务是大范围的（global），提供广阔得多的服务（比如面向整个因特网），大范围的服务通常是分布式的，这也就意味着数据是分布在多台机器上的，这些机器一起来提供目录服务，典型的大范围服务定义一个统一的名称空间（namespace）来给出一个相同的数据视图（data view），而不管你相对于数据所在的位置。DNS是一个典型的大范围分布式目录服务的例子。\nhttp://www.openldap.org/faq/data/cache/595.html 什么是ldap？ 目录服务有两个国际标准，分别是X.500和LDAP。X.500是ITU定义的目录标准，而LDAP是基于TCP/IP的目录访问协议，是Intemet上目录服务的通用访问协议。\nLDAP是 Lightweight Directory Access Protocol（轻量级目录访问协议）的缩写。正如它的名字所表明的那样，它是一个轻量级的目录访问协议，特指基于X.500的目录访问协议的简化版本。LDAP运行在 TCP/IP 或者其他的面向连接的传输服务之上。LDAP完整的技术规范由RFC2251“The Lightweight Directory Access Protocol（v3）”和其他几个在RFC3377中定义的文档组成。\nLDAP是轻量目录访问协议（Lielhtweiglht Directory Access Protocol）的缩写。 LDAP标准实际上是在X.500标准基础上产生的一个简化版本。 什么是X.500？ X.500由ITU-T和ISO定义，它实际上不是一个协议，而是由一个==协议族==组成，包括了从X.501到X.525等一系列非常完整的目录服务协议。\n从技术上来说，LDAP是一个到X.500目录服务的目录访问协议，X.500是一个OSI目录服务。最初，LDAP客户端通过网关访问X.500目录服务。网关在客户端和网关之间运行LDAP，而X.500目录访问协议（Directory Access Protocol，DAP），位于这个网关和X.500服务器之间。\nLDAP是一个重量级的协议，在整个OSI协议栈上进行操作，而且需要占用大量的计算资源。而LDAP被设计为在TCP/IP层上操作，以小得多的代价实现了大多数LDAP的功能。\n虽然LDAP仍旧可以通过网关访问X.500目录服务器，但是现在通常都是在X.500服务器上直接实现LDAP。\n单独的LDAP守护程序openldap slapd，可以被看做是一个轻量级的X.500目录服务器。也就是说，它没有实现X.500完整的DAP协议。作为一个轻量级的目录服务器，slapd实现的仅仅是X.500模型的一个子集。\nLDAP中的常用名词缩写及含义。\nLDAP基本概念中的常用名词缩写及含义\n关 键字 英文全称 含 义 dc Domain Component 城名的部分，其格式是将完整的城名分成几部分，如域名为exaple.com变成dc=exaple,dc=com uid User Id 用户ID，如 “oldboy”。 ou Organization Unit 组织单位，类似于Limux文件系统中的子目录，它是一个容器对象，组织单位可以包含其他各种对象（包括其他组织单元），如“tech，rongjunfeng，bingge” cn Common Name 公共名称，如 “Thowas Johansson” sn Surnase 姓，如 “Johansson” dn Distinguished Name 唯一辨别名，类似于Linux文件系统中的绝对路径，每个对象都有一个唯一的名称，如uid=tos,ou=market,dc=example,dc=com，在一个目录树中 总是唯一的。 rdn Relative dn 相对辨别名，类似于文件系统中的相对路径，它是与目录树结构无关的部分，如uid=tom或cn=Thoeas Johansscn。 c Country 国家，如“CN”或“US”等 o organizatione 组织名，如“Example，Inc.”。 LDAP目录服务的特点 LDAP（openldap）目录服务具有下列特点：\nLDAP是一个跨平台的、标准的协议，近几年来得到了业界广泛的认可。 LDAP的结构用树型结构来表示，而不是用表格。因此不用SQL语句维护了。 LDAP提供了静态数据的快速查询方式，但在更新数据方面并不擅长。 LDAP服务可以使用基于“推”或“拉”的复制信息技术，用简单的或基于安全证书的安全认证，复制部分或全部数据，既保证了数据的安全性，又提高了数据的访问效率： LDAP是一个安全的协议，LDAPv3支持SASL（Simple Authentication and Security Layer）、SSL（Secure Socket Layer）和TLS（Transport Layer Security），使用认证来确保事务的安全，另外，LDAP提供了不同层次的访问控制，以限制不同用户的访问权限。 LDAP支持异类数据存储，LDAP存储的数据可以是文本资料、二进制图片等。 Client/Server 模型：Server端用于存储树，Client端提供操作目录信息树的工具，通过这些工具，可以将数据库的内容以文本格式（LDAP数据交换格式，LDIF）呈现在我们的面前。 LDAP是一种开放Internet 标准，LDAP协议是跨平台的的Interent协议，它是基于X.500标准的，与X.500不同，LDAP支持TCP/IP（即可以分布式部署）。 LDAP的目录结构 LDAP目录服务是通过目录数据库来存储网络信息来提供目录服务的。为了方便用户迅速查找和定位信息，目录数据库是以目录信息树（Directory Infornation Tree，缩写为DIT）为存储方式的树型存储结构，目录信息树及其相关概念构成了LDAP协议的信息模型。\n在LDAP中，目录是按照树型结构组织一一目录信息树（Directory Information Tree简写DIT），DIT是一个主要进行读操作的数据库。 DIT由条目（Entry）组成，条目相当于关系数据库中的表的记录： 条目是具有分辨名DN（Distinguished Name）的属性-值对（Attribute-value，简称AV） 的集合。\n在UNIX文件系统中，最项层是根目录（root），LDAP目录通常也用ROOT做根，通常称为BaseDN。\n因为历史（X.500）的原因，LDAP目录用OU（Organization Unit）从逻辑上把数据分开来。Ou也是一种条目\u0026ndash;==容器条目==。Ou下面即是真正的用户条目。\n什么是dn?\nDN，Distinguished Name，即分辨名。\n在LDAP中，一个条目的分辨名叫做“DN”，DN是该条目在整个树中的唯一名称标识，DN相当于关系数据席表中的关键字（Primary Key）；它是一个识别属性，通常用于检索。\nDN的两种设置\n基于cn（姓名），cn=test,ou=auth,dc=cylon,dc=org，最常见的cn是从 /etc/group 转来的条目。.\n基于uid（User ID），uid=test,ou=auth,dc=cylon,dc=org 最常见的uid是 /etc/passwd 转来的条目。\nBase DN\nLDAP目录树的最顶部就是根，也就是Base DN.\nLDIF格式\nLDIF格式是用于LDAP数据导入、导出的格式。LDIF是LDAP数据库信息的一种文本格式。BDB。\n什么样的信息可以存储在目录当中？\nLDAP的信息模型是基于条目的（entry）。一个条目就是一些具有全局唯一的标识名（Distinguished Name，简写做==DN==）的属性的集合。DN用于无二义性的指代一个唯一的条目，条目的每一个属性都有一个类型（type），一个或者多个值（value），类型（type）往往是特定字符串的简写，比如用 cn 指代 commpn name，或者 mail 指代电子邮件地址。值（value）的语法依赖于类型（type），比如，类型为cn的属性可能包含值Babs Jensen。 类型为mail的属性可能包含值 babs@example.com 。类型为jpegPhoto的属性可能包含二进制格式的JPEG图象。\n下面是LDAP中条目信息的例子；就相当于数据库表中的两行记录：\nLDAP允许你通过使用一种叫做 objectClass的特殊属性来控制哪些属性是条目所必须的，哪些属性是条目可选的，objectClass 属性的值是由条目所必须遵从的方案（schema） 来建义的。\n通过下面命令可以取出上面的内容。\nsh 1 ldapsearch -LLL -w oldboy -x -H ldap://10.0.0.20 -D \u0026#34;cn=admin,dc=cylon,dc=org\u0026#34; -b \u0026#34;dc=cylon,dc=org\u0026#34; uid=\u0026#34;*\u0026#34; 信息在目录中是如何组织的？\n在LDAP中，条目是按树状的层次结构组织的，传统上，这个结构往往是地理界限或者组织界限的反映。代表国家的条目位于整个目录树的顶层。之下的条目则代表各个州以及国家性的组织，再下面的条目则代表着组织单位、个人打印机、文件，或者你所能想到的其他东西，图1.1显示了按照传统命名方式组织的LDAP目录信息树。\n图1.1：LDAP目录树（传统命名方式） 目录树也可以按照因特网域名结构组织。因为它允许按照DNS对目录服务进行定位，这种命名方式正变得越来越受欢迎。图1.2显示了按照域名进行组织的一个LDAP目录树的例子。\nhttp://www.openldap.org/doc/admin24/intro.html root -\u0026gt; 顶级域 -\u0026gt; 二级 -\u0026gt; ou -\u0026gt; uid\n图1-2：LDAP目录树（域名命名方式） 例：dn:cn=testlb,ou=People,ou=accounts,dc=cylon,dc=org\n另外，LDAP允许你通过使用一种叫做objectClass的特殊属性来控制哪些属性是条目所必须的，哪些属性是条目可选的。objectClass属性的值是由条目所必须遵从的方案 （schema）来定义的。.\n（3）信息是如何械引用的？\n一个条目是通过它的标识名来引用的。而标识名是由相对标识名（Relative Distinguished Name或者RDN）和它的父条目名连在一起构成的。比如，在因特网命名的例子中，Barbara Jensen条目有相对标识名 uid=babs 和标识名为：uid=babs,ou=People,de=example,dc=com 。\nLDIF数据文件介绍 目录数据文件内容讲解\nLDIF（LDAP Data Interchangep-omat）轻量级目录交换格式）一种ASCII文件格式，用来交换数据并使得在LDAP服务器间交换数据成为可能。\nLDIF文件最常用的功能就是向目录导入或修改信息，这些信息的格式需要按照LDAP中架构（schema）格式组织，如果不符合其要求的格式就会出现错误。\nLDIF文件的特点：\n通过空行来分割一个条目或定义。 以#开始的行为注释。 所有属性的赋值方法为：\u0026quot;属性: 属性值\u0026quot; 例如，dn:dn=cylon,dc=org 属性可以被重复赋值。例如objectclass就可以有多个，每个属性独立一行。 每行的结尾不允许有空格。 在LDAP的每条记录中必须包含一个objectclass属性，且其需要赋子至少一个值。objectclass属性有等级之分，属性相当于变量，它可以被自定义赋值，值不能相同。\n目录数据展现的树形结构圆。以上ldif数据文件表现个内容为如下树形结构：ldap C/S工具呈现的。\nLDAP是想样工作的？ LDAP目录服务是基于 C/S模式的。一个或者多个LDAP服务器包含着组成整个目录信息树（DIT）的数据，客户端连接到服务器并且发出一个请求（request），然后服务器要么以一个回答（answer）子以回应，要么给出一个指针，客户可以通过此指针获取到所需的数据（通常，该指针是指向另一个LDAP服务器），无论客户端连到哪个LDAP服务器，它看到的都是同一个目录视图（view）.这是LDAP这类全局目录服务的一个重要特征。\nLDAP的几个重要配置模式 LDAP服务的几个重要功能：\n★★★基本的目录查询服务。 目录查询代理服务。 异机复制数据（即主从同步）。 本地基本的目录查询服务 在这种配置模式下，你的slapd只为你的本地域提供目录服务。它不会以任何方式与别的目录服务器交互。这种配置模式如图1所示。\n本地配置模式 带有指针（Referrals）的本地目录服务。 即目录查询代理服务，类似DNS的转发服务器。\n在这种配置模式下，你为你的本地域运行一个LDAP服务器，并且将它配置成为当客户的请求超出你的本地域的处理能力的时候能够返回一个指针，该指针指向一个具备处理客户请求能力的更高级的服务器的地址。你可以自己运行这一服务，也可以使用别人已提供给你的一个。这种配置模式如图2所示。：\n如果你想运行本地目录服务并且参与全属的目录，那么运行这种模式。例如：openldap作为微软活动目录的代理查询服务。\ntext 1 referral \u0026lt;URI\u0026gt; 该指令指定了一个指针，当服务器的slapd找不到一个本地的数据库来处理一个请求的时候，它把该指针回传给客户。\n示例：\ntext 1 referral ldap://root.openldap.org 这将把非本地的请求“推”到OpenLDAP的根服务器上。“聪明的” LDAP客户会向反馈回来的指针所指的服务器重新发出请求。但是得注意大多数客户仅仅知道怎么样处理简单的LDAP的URL，其中包含主机部分和可选的DN部分。\n同多复制的目录服务。 slarpd守护程序是用来将主slapd上的改变传播到一个或多个从属的slapd上。一个master-slave 类型的配置示例如图3所示。\n这种配置模式可以和前面的两种配置模式之一合起来使用，在前面的两种情况中，单独的slapd不能提供足够的可用性和可靠性。\n简单易用的同步复制目录方案。 利用inotify+ldap客户端命令方案，或者通过定时任务加上ldap客户端命令方案！举MYSQL同步的例子说明！\n分布式的目录服务。 在这种配置模式下，本地的服务被分制成为多个更小的服务，每一个都可能被复制，并且通过上级（superior）或者下级（subordinate）指针（referral）粘合起来。在实际工作中，使用主从同步集群比较多一些，跨机房就是通过openvpn同步。\nldap企业架构逻辑图案例：ldap+haproxy/nginx/hearbeat集群高可用，验证的时候不跨机房。\nLDAP服务的应用领域 LDAP目录服务，适合那些需要从不同的地点读取信息，但是不需要经常更新的业务信息最为有用。\nLDAP的应用主要涉及以下几种类型。I\n信息安全类：数字证书管理、授权管理、单点登录。 科学计算类：DCE（Distributed Computing Environment，分布式计算环境）、UDDI（Universal Description，Discovery and Integration，统一描述、发现和集成协议）。 网络资源管理类：MAIL系统、DNS系统、网络用户管理、电话号码簿。 电子政务资源管理类：内网组织信息服务、电子政务目录体系、人口基础库、法人基础库。 在工作中，常用ldap作为公司入职后的所有员工账号等的基础信息库，例如：邮件账号，电脑登陆账号、办公平台账号、共享服务账号、SVN账号、VPN账号、服务器的账号，无线网登陆账号等的公共账号登陆信息库。可以理解为企业的活动目录一样，另外，LDAP也可以和微软活动目录打通。\n画一个内网AD域，和机房的LDAP服务打通的架构图！\nLDAP服务的常见开源产品 Openldap是LDAP最好的开源实现，在其Openldap许可证下发行，并且已经被包含在众多流行的linux发行版中\n官方网站为 openldap.org\n官方man手册 http://www.openldap.org/software/man.cgi\nopenldap UI ldap的客户端管理接口有很多，有b/s结构的web的，也有c/s结构的，我们以b/s结构的 ldap-account-manager-3.7.tar.gz 软件为例进行讲解\nB/S 架构的 LDAP Account Manager C/S 架构的 LDAP Admin LDAP Account Manager LAM是php服务需要安装lamp环境\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 yum install -y\\ nginx \\ php \\ php-ldap \\ php-gd \\ php-fpm rpm -qa \\ nginx \\ php \\ php-ldap \\ php-gd \\ php-fpm 下载解压配置ldap客户端软件 下载地址：https://www.ldap-account-manager.org/lamcms/releases\ntext 1 2 3 4 5 6 7 8 cp config.cfg_sample config.cfg cp lam.conf_sample lam.conf sed -i \u0026#39;s@cn=Manager@cn=admin@g\u0026#39; lam.conf sed -i \u0026#39;s@dc=my-domain@dc=cylon@g\u0026#39; lam.conf sed -i \u0026#39;s@dc=com@dc=org@g\u0026#39; lam.conf diff lam.conf_sample lam.conf 提示： 此处改的地方很多\n配置nginx text 1 chown nginx.nginx /var/www/html/ldap -R text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; location / { root /var/www/html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } location ~ \\.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } } LAM使用 步骤1：打开浏览器或者在前面的访问基础上刷新：http:/10.0.0.20/ldap，正常会出现界面如下图所示：\n步骤2：点击右上角的“LAMconfiguration”链接进行配置。\n步骤3：点击上图中的 “Edit general settings” 链接进行配置\n由于系统的密码为字母 “lam”，太简单了，我们先把密码改掉，其他的配置，我们根据需求以后可以再改，注意：这里是系统设置的权限密码，非页面上登录的密码。\n步骤5：上文点OK后就会到重新登录的界面进行登录，如下图：\n提示：\n请牢记，这是 ==ldap管理员用户admin及密码==，非前面设置的系统内部配置密码，ldap 管理员用户admin及密码是在配置openldap中生成的\n步骤6：初始化ldap数据库的域。\n步骤7：命令查看下刚才的操作帮我们建立了什么？。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ ldapsearch -LLL -w 123 -x -H ldap://10.0.0.20 -D \u0026#34;cn=admin,dc=cylon,dc=org\u0026#34; -b \u0026#34;dc=cylon,dc=org\u0026#34; \u0026#34;(uid=chau)\u0026#34; dn: uid=chau,ou=People,dc=cylon,dc=org objectClass: posixAccount objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person homeDirectory: /home/chau loginShell: /bin/bash cn: cylon chau uidNumber: 10006 gidNumber: 10000 userPassword:: e1NTSEF9c0YzZzBSbnFBU1NoWEVBWEFSS1lPdG41WjZRR1pxNCs= sn: chau givenName: cylon uid: chau 步骤8：继续添加ldap账号和组。\ntext 1 2 3 4 5 dn: cn=tech,ou=group,dc=cylon,dc=org objectClass: posixGroup description:: 5oqA5pyv6Y0o gidNumber: 10001 cn: tech 添加用户\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ ldapsearch -LLL -w 123 -x -H ldap://10.0.0.20 -D \u0026#34;cn=admin,dc=cylon,dc=org\u0026#34; -b \u0026#34;dc=cylon,dc=org\u0026#34; \u0026#34;(uid=cylon)\u0026#34; dn: uid=cylon,ou=People,dc=cylon,dc=org objectClass: posixAccount objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person homeDirectory: /home/chau loginShell: /bin/bash cn: cylon chau uidNumber: 10006 userPassword:: e1NTSEF9c0YzZzBSbnFBU1NoWEVBWEFSS1lPdG41WjZRR1pxNCs= sn: chau givenName: cylon uid: cylon gidNumber: 10001 通过web接口管理ldap的配置完成！也已经初始化了用户组及用户\nphpldapadmin sh 1 2 3 4 5 6 7 8 9 10 11 yum install -y \\ php \\ php-fpm \\ php-ldap \\ php-gd \\ php-mbstring \\ php-pear \\ php-bcmath \\ php-xml \\ phpldapadmin \\ nginx nginx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; root /usr/share/phpldapadmin/htdocs; location / { index index.html index.htm index.php; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } location ~ \\.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } } ","permalink":"https://www.161616.top/ch1-understanding-ldap/","summary":"openldap合集 ch1 理解ldap - 什么是ldap ch2 理解ldap - OpenLDAP安装 ch3 理解ldap - OpenLDAP客户端命令行使用 ch4 理解ldap - OpenLDAP架构与Schema设计 ch5 理解ldap - OpenLDAP使用SSL/TLS通信安全 ch6 理解ldap - OpenLDAP中的4种复制机制 ch7 理解ldap - OpenLDAP访问控制(ACL) ch8 理解ldap - OpenLDAP备份与恢复策略 ch9 理解ldap - openldap中的一些高级配置 ch10 理解ldap - Linux系统接入OpenLDAP做认证后端 ch11 理解ldap - 使用SSSD接入OpenLDAP实现身份验证 什么是目录服务？ ==目录是一类为了浏览和搜索数据而设计的特殊的数据库==，例如：为人所熟知的微软公 司的活动目录（active directory）就是目录数据库的一种，目录服务是按照==树状形式==存储信息的，目录包含基于属性的描述性信息，并且支持高级的过滤功能。\nhttp://www.openldap.org/doc/admin24/intro.html 一般来说，目录不支持大多数事务型数据库所支持的高吞吐量和复杂的更新操作。目录进行更新操作，可以说是要么全部，要么都不的原子操作。目录服务适合的业务应用在于提供大量的查询和搜索操作，而不是大量的写入操作。Ldap可以说是活动目录在linux系统上的一个开源实现。\n为了保证目录数据的可用性和可靠性，在确保使用目录服务提供快速查询和搜索操作的同时，目录服务还提供了==主从服务器同步目录数据信息的能力==，这相当于传统的MySQL数据库的主从同步功能一样，可以最大限度的确保基于目录业务的服务持续可用性与提供并发查询能力，微软公司的活动目录（active directory）就有主域和备份域的说法。\n广义的目录服务概念，可以用多种不同的方式来提供目录服务。不同的目录所允许存储的信息是不同的，在信息如何被引用、查询、更新以及防止未经授权的访问等问题上，不同的目录服务的处理方式也有诸多的不同。\n例如：一些目录服务是本地的，只提供受限的服务（比如，单机上的finger服务）。另一些服务是大范围的（global），提供广阔得多的服务（比如面向整个因特网），大范围的服务通常是分布式的，这也就意味着数据是分布在多台机器上的，这些机器一起来提供目录服务，典型的大范围服务定义一个统一的名称空间（namespace）来给出一个相同的数据视图（data view），而不管你相对于数据所在的位置。DNS是一个典型的大范围分布式目录服务的例子。\nhttp://www.openldap.org/faq/data/cache/595.html 什么是ldap？ 目录服务有两个国际标准，分别是X.500和LDAP。X.500是ITU定义的目录标准，而LDAP是基于TCP/IP的目录访问协议，是Intemet上目录服务的通用访问协议。\nLDAP是 Lightweight Directory Access Protocol（轻量级目录访问协议）的缩写。正如它的名字所表明的那样，它是一个轻量级的目录访问协议，特指基于X.500的目录访问协议的简化版本。LDAP运行在 TCP/IP 或者其他的面向连接的传输服务之上。LDAP完整的技术规范由RFC2251“The Lightweight Directory Access Protocol（v3）”和其他几个在RFC3377中定义的文档组成。","title":"理解ldap - 什么是ldap"},{"content":"上一章提到了 RBD 块设备相关的基本配置，这章主要描述 RBD 操作部分\nceph块设备接口（RDB） Ceph块设备，也称为RADOS块设备（简称RBD），是一种基于RADOS存储系统支持超配（thin-provisioned）、可伸缩的条带化数据存储系统，它通过librbd库与OSD进行交互。RBD为KVM等虑拟化技术和云OS（如OpenStack和CloudStack）提供高性能和无限可扩展性的存储后端，这些系统依赖于libvirt和QEMU实用程序与RBD进行集成。\n客户端基于librbd库即可将RADOS存储集群用作块设备，不过，用于rbd的存储池需要实现启用rbd功能并进行初始化。例如，下面的命令创建rbddata的存储池，在启动rbd功能后对其进行初始化。\nsh 1 2 3 4 ceph osd pool create rbdpool 64 # 创建存储池 rbd pool init -p rbdpool # rbd create rbdpool/img1 --size 2G rbd ls -p rbdpool 不过rbd存储池并不能直接用于块设备，而是需要事先在其中按需创建影响（image）\nRBD是建立在librados之上的客户端，全程为Rados Block Devices，是对rados存储服务做抽象，将rados位于存储池当中所提供的存储空间，使用对象式存储数据的能力的机制。\nrbd虚拟磁盘设备要想使linux内核所识别使用，要求linux在内核级支持Ceph，内核级有一个Ceph模块，专门用来驱动Ceph设备的。这个驱动就叫librbd。此模块自己是一个客户端，能够连接至RBD服务上，检索出他所管理的镜像文件。从而镜像文件完全可以被linux主机作为一块完整的硬盘来使用。\n创建存储池镜像\n--object-size 任何数据存储在存储池中都被切分为固定大小的对象，对象通常称之为条带，切分的对象大小，默认4M --image-feature 指定镜像文件的特性，每种特性代表镜像文件能支持哪些种功能，可被单独启用或禁用。 layering(+), 分层克隆 exclusive-lock 排它锁，是否支持分布式排它锁，已用来限制一个镜像文件同时只能被一个客户端使用。 object-map(+*) 对象映射,是否支持对象位图，主要用于加速导入、导出及已用容量统的。 fast-diff(+*), 快速比较，主要用于做快照之间的比较 deep-flatten(+-) , 深层展评 journaling 日志，用户在修改image数据时是否记录日志。 shared image --no-progress：不显示创建过程 sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ rbd help create usage: rbd create [--pool \u0026lt;pool\u0026gt;] [--image \u0026lt;image\u0026gt;] [--image-format \u0026lt;image-format\u0026gt;] [--new-format] [--order \u0026lt;order\u0026gt;] [--object-size \u0026lt;object-size\u0026gt;] [--image-feature \u0026lt;image-feature\u0026gt;] [--image-shared] [--stripe-unit \u0026lt;stripe-unit\u0026gt;] [--stripe-count \u0026lt;stripe-count\u0026gt;] [--data-pool \u0026lt;data-pool\u0026gt;] [--journal-splay-width \u0026lt;journal-splay-width\u0026gt;] [--journal-object-size \u0026lt;journal-object-size\u0026gt;] [--journal-pool \u0026lt;journal-pool\u0026gt;] [--thick-provision] --size \u0026lt;size\u0026gt; [--no-progress] \u0026lt;image-spec\u0026gt; Create an empty image. ceph osd pool create kube 64 64 ceph osd pool application enable kube rbd rbd pool init kube rbd create --size 2G --pool kube --image vol01 # 使用各个选项 rbd create --size 2G kube/vol02 # \u0026lt;image-spec\u0026gt; 获取对象更详细信息\nPARENT 父镜像\nFMT 格式，一般只有v2\nPROT\nLOCK\nsh 1 2 3 4 5 6 7 8 9 10 11 12 $ rbd ls -l --pool kube NAME SIZE PARENT FMT PROT LOCK vol01 5 GiB 2 $ rbd ls -l --pool kube --format json --pretty-format [ { \u0026#34;image\u0026#34;: \u0026#34;vol01\u0026#34;, \u0026#34;size\u0026#34;: 5368709120, \u0026#34;format\u0026#34;: 2 } ] 获取镜像文件更详细一部镜像信息，可以使用rbd info命令\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 $ rbd info kube/vol01 rbd image \u0026#39;vol01\u0026#39;: size 5 GiB in 1280 objects order 22 (4 MiB objects) id: 5e386b8b4567 block_name_prefix: rbd_data.5e386b8b4567 format: 2 features: layering, exclusive-lock, object-map, fast-diff, deep-flatten op_features: flags: create_timestamp: Mon Jun 24 05:42:00 2019 $ rbd info --pool kube --image vol01 rbd image \u0026#39;vol01\u0026#39;: size 5 GiB in 1280 objects order 22 (4 MiB objects) id: 5e386b8b4567 block_name_prefix: rbd_data.5e386b8b4567 format: 2 features: layering, exclusive-lock, object-map, fast-diff, deep-flatten op_features: flags: create_timestamp: Mon Jun 24 05:42:00 2019 $ rbd info --pool kube vol01 rbd image \u0026#39;vol01\u0026#39;: # 镜像文件是谁 size 5 GiB in 1280 objects # 镜像总体是多大空间，被分割成多少个对象 order 22 (4 MiB objects) # 顺序是22，顺序是指块大小的标识序号 # 如64k开始64m结束，4M大小正好排在第22位 id: 5e386b8b4567 # 镜像文件自身id block_name_prefix: rbd_data.5e386b8b4567 format: 2 # 镜像文件格式 features: layering, exclusive-lock, object-map, fast-diff, deep-flatten # 启动的特性 op_features: flags: create_timestamp: Mon Jun 24 05:42:00 2019 # object-map, fast-diff, deep-flatten种特性在被linux客户端作为磁盘加载到内核中使用时是不被支持的。需将其禁用掉 禁用或启用某种特性 rbd featrue\nsh 1 2 3 rbd feature disable|enable rbd feature disable kube/vol01 object-map fast-diff deep-flatten sh 1 2 3 4 5 6 7 8 9 10 11 $ rbd info --pool kube vol01 rbd image \u0026#39;vol01\u0026#39;: size 5 GiB in 1280 objects order 22 (4 MiB objects) id: 5e386b8b4567 block_name_prefix: rbd_data.5e386b8b4567 format: 2 features: layering, exclusive-lock op_features: flags: create_timestamp: Mon Jun 24 05:42:00 2019 Ceph客户端使用块设备步骤\n创建出镜像文件。 在镜像文件上禁用默认启用的五个特性中的后三个。 在客户端上使用指定命令以指定的用户的身份连入客户端。 配置客户端上yum仓库文件 ceph命令默认是以admin为账号的，使用--user指定其他用户。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ ceph -s 2019-06-25 10:48:11.283 7f999daa3700 -1 auth: unable to find a keyring on /etc/ceph/ceph.client.admin.keyring,/etc/ceph/ceph.keyring,/etc/ceph/keyring,/etc/ceph/keyring.bin,: (2) No such file or directory 2019-06-25 10:48:11.283 7f999daa3700 -1 monclient: ERROR: missing keyring, cannot use cephx for authentication [errno 2] error connecting to the cluster $ ceph --user kube -s cluster: id: 69fb9b55-3fb5-42d0-8cf7-239a3b569791 health: HEALTH_WARN 1 pool(s) full application not enabled on 1 pool(s) services: mon: 3 daemons, quorum stor01,stor02,stor03 mgr: stor01(active), standbys: stor04 mds: cephfs-1/1/1 up {0=stor02=up:active} osd: 8 osds: 8 up, 8 in rgw: 1 daemon active data: pools: 9 pools, 352 pgs objects: 254 objects, 3.7 KiB usage: 49 GiB used, 51 GiB / 100 GiB avail pgs: 352 active+clean 需要操作系统支持ceph.ko模块\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ modinfo ceph filename: /lib/modules/3.10.0-957.12.1.el7.x86_64/kernel/fs/ceph/ceph.ko.xz license: GPL description: Ceph filesystem for Linux author: Patience Warnick \u0026lt;patience@newdream.net\u0026gt; author: Yehuda Sadeh \u0026lt;yehuda@hq.newdream.net\u0026gt; author: Sage Weil \u0026lt;sage@newdream.net\u0026gt; alias: fs-ceph retpoline: Y rhelversion: 7.6 srcversion: 43DA49DF11334B2A5652931 depends: libceph intree: Y vermagic: 3.10.0-957.12.1.el7.x86_64 SMP mod_unload modversions signer: CentOS Linux kernel signing key sig_key: 2C:7C:17:70:5C:86:D4:20:80:50:D3:F5:54:56:9A:7B:D3:BF:D1:BF sig_hashalgo: sha256 显示设备map信息\ntext 1 2 3 $ rbd showmapped id pool image snap device 0 kube vol01 - /dev/rbd0 显示设备挂在到哪里\nsh 1 2 3 4 5 6 $ rbd showmapped id pool image snap device 0 kube vol01 - /dev/rbd0 NAME SIZE PARENT FMT PROT LOCK vol01 5 GiB 2 excl 卸载挂载\nsh 1 2 3 $ umount /dev/rbd0 # 需要先卸载rbd挂载 $ rbd unmap /dev/rbd0 $ rbd showmapped 调整镜像文件空间容量\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ rbd help resize usage: rbd resize [--pool \u0026lt;pool\u0026gt;] [--image \u0026lt;image\u0026gt;] --size \u0026lt;size\u0026gt; [--allow-shrink] [--no-progress] \u0026lt;image-spec\u0026gt; Resize (expand or shrink) image. Positional arguments \u0026lt;image-spec\u0026gt; image specification (example: [\u0026lt;pool-name\u0026gt;/]\u0026lt;image-name\u0026gt;) Optional arguments -p [ --pool ] arg pool name --image arg image name -s [ --size ] arg image size (in M/G/T) [default: M] --allow-shrink permit shrinking # 允许收缩，不要收缩到已有数据占用容量、以下 --no-progress disable progress output sh 1 2 3 4 5 6 $ rbd resize -s 10G kube/vol01 Resizing image: 100% complete...done. $ rbd ls -p kube -l NAME SIZE PARENT FMT PROT LOCK vol01 10 GiB 删除镜像文件\n镜像文件被删除是不可恢复的\nsh 1 2 3 4 5 $ rbd rm kube/vol01 Removing image: 100% complete...done. $ rbd ls -p kube -l $ rbd回收站trash\n将镜像挪入回收站中\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 $ rbd ls -p kube -l NAME SIZE PARENT FMT PROT LOCK vol01 2 GiB 2 vol02 2 GiB 2 $ rbd trash move kube/vol01 $ rbd ls -p kube -l NAME SIZE PARENT FMT PROT LOCK vol02 2 GiB 2 $ rbd trash list -p kube 5ebf6b8b4567 vol01 从trash恢复镜像\ntext 1 2 3 4 5 6 7 8 $ rbd trash list -p kube 5ebf6b8b4567 vol01 $ rbd trash restore -p kube --image vol01 --image-id 5ebf6b8b4567 $ rbd ls -p kube vol01 vol02 如何对镜像文件做快照、克隆\n快照是什么？\n快照是一种能够瞬间生成数据访问目录，能够支持备份技术的一种数据管理手段。有全量快照与增量快照两种。全量快照一般使用镜像分离技术实现，而增量快照则由COW写时拷贝快照技术ROW写时重定向。 写时复制是指，当写的时候，将数据复制到快照上去修改源数据；写时重定向是指，写的时候直接去写快照，改变快照上的数据，源卷上的数据不变。\nsnap相关命令\n命令 说明 snap create (snap add) 创建快照 snap limit clear 清除快照数量限制 snap limit set 设定一个镜像文件上所能够创建的快照问价你的上线 snap list (snap ls) 列出快照 snap protect 保护快照 snap purge 清理快照 snap remove (snap rm) 移除快照 snap rename 重命名快照 snap rollback (snap revert) 回滚快照，做快照恢复 snap unprotect 解除保护，protect的相反操作。 为镜像创建快照\nrbd snap create只需为哪个镜像创建什么快照就可以了\nsh 1 2 3 4 5 6 rbd snap create kube/vol01@snap1 $ rbd snap list kube/vol01 SNAPID NAME SIZE TIMESTAMP 4 snap1 2 GiB Thu Jun 27 14:00:36 2019 快照恢复\n首先先将服务停止（拆除）\nsh 1 2 umount /rdb # 卸载挂载 rbd unmap /dev/rbd0 # 拆除连接 回到管理节点做回滚操作\n使用rbd snap rollback\nsh 1 2 $ rbd snap rollback kube/vol01@snap1 Rolling back to snapshot: 100% complete...done. 回到客户端再一次将映射挂载访问\nsh 1 2 3 4 5 6 7 rbd --user kube map /dev/rbd0 # 映射 mount /dev/rbd0 /rdb # 挂载 # 查看数据 $ ls lost+found passwd 删除快照\nsh 1 rbd snap rm kube.vol01@snap1 限制镜像文件所能够创建的快照数量\nsh 1 2 3 4 5 6 rbd snap limit set kube/vol01 --limit 2 $ rbd snap create kube/vol01@snap03 rbd: failed to create snapshot: (122) Disk quota exceeded rbd snap limit clear kube/vol01 # 清除限制 多层快照技术\n对原始镜像文件做一次快照，将此快照置于保护模式下。快照所能访问的数据一定是做快照那一刻原始卷上的所有数据的。就算修改原始卷内容，也不会影响快照访问。基于快照再做一层快照。而二级快照是可以当镜像来用的。因此基于保护快照可以创建N个克隆。每一个克隆都可以按照自己的意愿去做希望所做的后续修改操作。\n模板池，提供克隆模板的。\n工作流程\n创建镜像 对镜像做快照 对快照做保护 对快照做克隆 在管理断创建快照，将此快照当做保护模式下的快照，当做创建克隆时的模板\nsh 1 2 3 4 5 6 7 8 9 10 $ rbd snap create kube/vol01@clonetpl1 $ rbd snap ls rbd: image name was not specified $ rbd snap ls kube/vol01 SNAPID NAME SIZE TIMESTAMP 4 snap1 2 GiB Thu Jun 27 14:00:36 2019 5 snap02 2 GiB Thu Jun 27 14:41:25 2019 8 clonetpl1 10 GiB Thu Jun 27 20:43:48 2019 置入保护模式\nsh 1 2 3 $ rbd snap protect kube/vol01@clonetpl1 rbd snap unprotect kube/vol01:clonetpl1 # 解除保护 给快照再次做快照，称之为clone。==可支持跨存储池进行克隆==\nsh 1 2 3 4 $ rbd clone kube/vol01@clonetpl1 test/myimg01 # 是一个镜像，可被当做真正镜像使用 $ rbd ls -p test myimg01 rbd chlidren显示一个快照的子项\nsh 1 2 3 $ rbd children kube/vol01@clonetpl1 kube/myimg01 test/myimg01 数据展平 flatten\n当vol01被删除时，他的上层快照myimg01就无根了，因此需要做数据展平，已确保他所应用的每一个底层快照上的所有数据都被复制到当前镜像中。\nsh 1 2 $ rbd flatten kube/myimg01 Image flatten: 100% complete...done. ","permalink":"https://www.161616.top/ch03-2-rbd-management/","summary":"上一章提到了 RBD 块设备相关的基本配置，这章主要描述 RBD 操作部分\nceph块设备接口（RDB） Ceph块设备，也称为RADOS块设备（简称RBD），是一种基于RADOS存储系统支持超配（thin-provisioned）、可伸缩的条带化数据存储系统，它通过librbd库与OSD进行交互。RBD为KVM等虑拟化技术和云OS（如OpenStack和CloudStack）提供高性能和无限可扩展性的存储后端，这些系统依赖于libvirt和QEMU实用程序与RBD进行集成。\n客户端基于librbd库即可将RADOS存储集群用作块设备，不过，用于rbd的存储池需要实现启用rbd功能并进行初始化。例如，下面的命令创建rbddata的存储池，在启动rbd功能后对其进行初始化。\nsh 1 2 3 4 ceph osd pool create rbdpool 64 # 创建存储池 rbd pool init -p rbdpool # rbd create rbdpool/img1 --size 2G rbd ls -p rbdpool 不过rbd存储池并不能直接用于块设备，而是需要事先在其中按需创建影响（image）\nRBD是建立在librados之上的客户端，全程为Rados Block Devices，是对rados存储服务做抽象，将rados位于存储池当中所提供的存储空间，使用对象式存储数据的能力的机制。\nrbd虚拟磁盘设备要想使linux内核所识别使用，要求linux在内核级支持Ceph，内核级有一个Ceph模块，专门用来驱动Ceph设备的。这个驱动就叫librbd。此模块自己是一个客户端，能够连接至RBD服务上，检索出他所管理的镜像文件。从而镜像文件完全可以被linux主机作为一块完整的硬盘来使用。\n创建存储池镜像\n--object-size 任何数据存储在存储池中都被切分为固定大小的对象，对象通常称之为条带，切分的对象大小，默认4M --image-feature 指定镜像文件的特性，每种特性代表镜像文件能支持哪些种功能，可被单独启用或禁用。 layering(+), 分层克隆 exclusive-lock 排它锁，是否支持分布式排它锁，已用来限制一个镜像文件同时只能被一个客户端使用。 object-map(+*) 对象映射,是否支持对象位图，主要用于加速导入、导出及已用容量统的。 fast-diff(+*), 快速比较，主要用于做快照之间的比较 deep-flatten(+-) , 深层展评 journaling 日志，用户在修改image数据时是否记录日志。 shared image --no-progress：不显示创建过程 sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ rbd help create usage: rbd create [--pool \u0026lt;pool\u0026gt;] [--image \u0026lt;image\u0026gt;] [--image-format \u0026lt;image-format\u0026gt;] [--new-format] [--order \u0026lt;order\u0026gt;] [--object-size \u0026lt;object-size\u0026gt;] [--image-feature \u0026lt;image-feature\u0026gt;] [--image-shared] [--stripe-unit \u0026lt;stripe-unit\u0026gt;] [--stripe-count \u0026lt;stripe-count\u0026gt;] [--data-pool \u0026lt;data-pool\u0026gt;] [--journal-splay-width \u0026lt;journal-splay-width\u0026gt;] [--journal-object-size \u0026lt;journal-object-size\u0026gt;] [--journal-pool \u0026lt;journal-pool\u0026gt;] [--thick-provision] --size \u0026lt;size\u0026gt; [--no-progress] \u0026lt;image-spec\u0026gt; Create an empty image.","title":"Ceph RBD - 关于RBD的操作与管理"},{"content":"什么是对象存储 对象存储是一种以非结构化格式（称为对象），简单来说，对象存储是一种将文件存储为对象而不是数据块的存储架构。它是一种将非结构化数据存储在跨位置分布的结构化平面文件系统中的方法。在这种格式中，文件空间由元数据标签组成，支持简单的 API 来描述、读取、删除和定位对象。因此，您可以通过 API 协议直接访问任何设备上保存的数据。此类元数据标签包括有助于更好地识别和分类数据的唯一标识符。\n这些元数据标签是高度可定制的，让您可以在需要时通过跟踪和索引文件来轻松组织、访问和检索所有数据。对象存储服务可以在设备级、系统级甚至接口级实现。作为对象存储的数据可确保数据可用性、可搜索性并增强数据安全性，因为它可以保护数据免遭意外删除或损坏。\n什么是 CEPH 中的对象存储 在知道了对象存储不能作为文件系统磁盘由操作系统直接访问，只可以通过应用程序级别的 API 进行访问。Ceph是一个分布式对象存储系统，通过一个 “网关服务” 来提供对象存储接口，这个服务被称为 RADOS Gateway ( RGW )，RGW是构建在 Ceph RADOS 之上，通过在 librados 之构建出的一个库 librgw，实际上是一个 Civetweb 的服务，rados gateway 内嵌在里面，RGW 为应用程序提供兼容 RESTful 的 S3/Swift 的 API 接口，以在 Ceph 集群中以对象的形式存储数据。Ceph 还支持多租户对象存储，可通过 RESTful API 访问。除此之外，RGW 还支持 Ceph 管理 API，可用于使用本机 API 调用来管理 Ceph 存储集群。\nlibrados 是一个构建在 RADOS 集群和 Ceph 集群的中间层，通过这个库，提供了允许用户应用程序通过C、C++、Java、Python 和 PHP绑定直接访问 Ceph 存储集群。Ceph 对象存储还具有多站点 (MultiSite) 能力，即提供灾难恢复的解决方案。\n图：Ceph RGW Structure\rSource：https://docs.ceph.com/en/octopus/radosgw/\n安装rgw\nrgw 包 ceph-radosgw\nceph中的对象存储是使用HTTP服务与Ceph群集进行交互的（radosgw）由radosgw 提供与OpenStack Swift和Amazon S3兼容的接口，因此radosgw具有自己的管理用户。\n![](../../../images/ceph object store/1ae399f8fa9af1042d3e1cbf31828f14eb3fe01a6eb3352f88c3d2a04ac4dc50.png)\nceph-deploy rgw create cn01\n修改默认端口\n创建用户\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ radosgw-admin user create --uid demo --display-name \u0026#34;seal\u0026#34; { \u0026#34;user_id\u0026#34;: \u0026#34;demo\u0026#34;, \u0026#34;display_name\u0026#34;: \u0026#34;seal\u0026#34;, \u0026#34;email\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;suspended\u0026#34;: 0, \u0026#34;max_buckets\u0026#34;: 1000, \u0026#34;subusers\u0026#34;: [], \u0026#34;keys\u0026#34;: [ { \u0026#34;user\u0026#34;: \u0026#34;demo\u0026#34;, \u0026#34;access_key\u0026#34;: \u0026#34;9REMQZ4789I85QJFW93I\u0026#34;, \u0026#34;secret_key\u0026#34;: \u0026#34;akYcu5ncvgMxCljTNGrGTHUBbMzjJmETfxtBW7SX\u0026#34; } ], radosgw-admin user list\nERROR: S3 error: 416 (InvalidRange)\n解决办法：\n将其他对象存储后台驻留程序（OSD）添加到群集或将“mon_max_pg_per_osd”的默认值增加到300以上。\n修改配置文件（管理节点操作）：\n$ vim ceph.conf\n[mon] mon allow pool delete = true mon_max_pg_per_osd = 300\n$ ceph-deploy \u0026ndash;overwrite-conf config push c720181 c720182 c720183\n重启mon服务（集群mon节点都要操作）：\nsystemctl restart ceph-mon.target\n416\nset default pg_num and pgp_num to lower value(8 for example), or set mon_max_pg_per_osd to a high value in ceph.conf\n416\n403 (SignatureDoesNotMatch) 修改配置文件 signature_v2 = True\ntext 1 2 3 4 5 6 s3cmd ls s3cmd ls s3://demob # 创建bucket s3cmd mb s3://demob # 上传目录 s3cmd put /etc/ s3://demob/etc --recursive ","permalink":"https://www.161616.top/ch05-1-rgw/","summary":"什么是对象存储 对象存储是一种以非结构化格式（称为对象），简单来说，对象存储是一种将文件存储为对象而不是数据块的存储架构。它是一种将非结构化数据存储在跨位置分布的结构化平面文件系统中的方法。在这种格式中，文件空间由元数据标签组成，支持简单的 API 来描述、读取、删除和定位对象。因此，您可以通过 API 协议直接访问任何设备上保存的数据。此类元数据标签包括有助于更好地识别和分类数据的唯一标识符。\n这些元数据标签是高度可定制的，让您可以在需要时通过跟踪和索引文件来轻松组织、访问和检索所有数据。对象存储服务可以在设备级、系统级甚至接口级实现。作为对象存储的数据可确保数据可用性、可搜索性并增强数据安全性，因为它可以保护数据免遭意外删除或损坏。\n什么是 CEPH 中的对象存储 在知道了对象存储不能作为文件系统磁盘由操作系统直接访问，只可以通过应用程序级别的 API 进行访问。Ceph是一个分布式对象存储系统，通过一个 “网关服务” 来提供对象存储接口，这个服务被称为 RADOS Gateway ( RGW )，RGW是构建在 Ceph RADOS 之上，通过在 librados 之构建出的一个库 librgw，实际上是一个 Civetweb 的服务，rados gateway 内嵌在里面，RGW 为应用程序提供兼容 RESTful 的 S3/Swift 的 API 接口，以在 Ceph 集群中以对象的形式存储数据。Ceph 还支持多租户对象存储，可通过 RESTful API 访问。除此之外，RGW 还支持 Ceph 管理 API，可用于使用本机 API 调用来管理 Ceph 存储集群。\nlibrados 是一个构建在 RADOS 集群和 Ceph 集群的中间层，通过这个库，提供了允许用户应用程序通过C、C++、Java、Python 和 PHP绑定直接访问 Ceph 存储集群。Ceph 对象存储还具有多站点 (MultiSite) 能力，即提供灾难恢复的解决方案。\n图：Ceph RGW Structure\rSource：https://docs.ceph.com/en/octopus/radosgw/\n安装rgw\nrgw 包 ceph-radosgw","title":"Ceph对象存储概述"},{"content":"Ceph专门提供了文件系统接口CephFS。CephFS是略微不同于RBD的架构形式，在基础的RADOS Cluster存储集群的基础之上，需要额外运行一个守护进程MDS MetaDataServer 元数据服务器。\nRADOS Cluster自己是一个对象存储服务，他无法管理传统文件系统上分离去管理元数据和数据的功能。（而且元数据还拥有权限、用户属组、时间戳等。）RADOS Cluster自身是无法实现这个功能的。MDS专用于模拟传统文件系统所应该具有的将数据和元数据分离存储的方式而专门提供的一个服务。MDS只用来管理元数据。\nMDS需要工作一个守护进程，客户端必须通过套接字的方式，每一次访问文件时，先去联系到MDS，来获取文件的元数据信息，再到RADOS Cluster以对象方式，将对象模拟成传统文件系统的块，来加载文件数据。\n如是挂在CephFS系统的客户端，需先联系到MDS，识别文件系统的各种信息。客户端向挂载目录路径下的任何一个读写操作就相当于由挂载时的内核驱动模块联系到对应的MDS，而后再由客户端之上的模块来联系到RADOS Cluster。为了能够支持CephFS文件系统，也需要内核级模块Ceph.ko模块。挂载过程机制为将内核模块作为文件系统内核的客户端，与文件系统的守护进程进行通讯，必要时将用户数据存取转为对应集群的存储操作。\n对于Rados存储集群来讲，存储集群所有数据都会被放在存储池当中，而CephFS管理其数据和元数据分别放置在不同的存储池中。所有元数据都是由MDS管理的。MDS也是客户端，连入CephFS的的metadata，专门用于存储元数据的存储池。\ncephfs逻辑\n元数据是一类很密集的IO访问。对原数据存储池的操作是放置在存储池当中的，但在本地会使用内存（高速缓存）中完成，过断时间同步到metadata pool中。而数据直接写入data存储池中\nmeatdata pool只能对mds访问，其他任何客户端时不能被访问的。客户端对元数据的访问必须经由MDS来实现。\n当客户端打开一个文本时，首先请求客户端的inode（传统文件系统采用inode来保存文件的元数据）。获得相应授权以后从mds中接收到inode，inode中标示文件的数据究竟放置在data pool的那些对象中。返回对象编号给客户端，客户端基于对象编号访问所有的对象，将数据加载到。\nceph mds stat\nmds: 2 up:standby 当没有文件系统时，无法进行选举，故所有的都为standby\nCephFS Client访问CephFS集群的方式\n客户端挂载CephFS， 基于内核文件系统完成挂载ceph、libcephfs 用户空间文件系统（FUSE Filesystem in USErspace）：libcephfs与ceph集群进行交互。 ==激活CephFS步骤==\n激活CephFS MDS，至少有一个节点运行ceph-mds守护进程\nceph-deploy命令完成的 创建存储池：metadata-pool、data-pool\nbash 1 2 ceph osd pool create cephfs_metadata 8 8 ceph osd pool create cephfs_data 8 8 激活文件系统：\nceph fs new \u0026lt;name\u0026gt; {metadata-pool-name} {pool-name} ceph fs status {filesystem-name} ceph fs stat 获得必要授权，才能使用服务\nceph auth get-or-create client.fsclient mon 'allow r' mds 'allow rw' osd 'allow rwx pool=cephfs-data' -o ceph.client.fsclient.keyring 客户端挂载\n客户端挂载时key和用户名要分开两个不通选项来指定的。 text 1 2 3 4 5 6 7 8 9 $ ceph-authtool -p -n client.fsclient ceph.client.fsclient.keyring AQATNhZdmHEBIBAA52a2MESQJS8mMScxPzRkIA== $ ceph auth print-key client.fsclient AQATNhZdmHEBIBAA52a2MESQJS8mMScxPzRkIA== ceph auth print-key client.fsclient \u0026gt;fsclient.key # 此key文件是被客户端使用的。需要复制到客户端 scp fsclient.key root@172.18.0.6:/etc/ceph 确保安装ceph-common客户端 确保内核中有ceph模块ceph.ko，此模块必须存在才能使用Ceph客户端 确定可获取到Ceph集群的配置文件ceph.conf。 sh 1 2 3 4 5 6 7 8 9 10 $ mount -t ceph stor01:6789,stor02:6789,stor03:6789:/ /data -o name=fsclient,secretfile=/etc/ceph/fsclient.key # stat 跟挂载点可查看文件系统类型 $ stat -f /data 文件：\u0026#34;/data\u0026#34; ID：cfb80ce541d5e304 文件名长度：255 类型：ceph 块大小：4194304 基本块大小：4194304 块：总计：1061 空闲：1061 可用：1061 Inodes: 总计：0 空闲：-1 如需开机自启动需写入/etc/fstab下\ntext 1 2 3 4 stor01:6789,stor02:6789,stor03:6789:/ /data ceph name=fsclient,secretfile=/etc/ceph/fsclient.key,_netdev,noatime 0 0 umount /data mount -a # 自动挂载 如内核级没有提供ceph模块，就无法基于内核文件系统形式挂载和使用ceph客户端， 此时就只能使用ceph-fuse客户端。\nfuse不要求内核级必须有相应的内核模块，但要求在用户空间安装一个程序包ceph-fuseceph以用户空间形式的文件系统逻辑来挂载ceph。无需ceph-common并且一样需提供用于认证的客户端账号。\nsh 1 2 3 4 $ ceph-fuse -n client.fsclient -m stor01:6789,stor02:6789,stor03:6789 /data 2019-06-29 16:09:14.537 7f42c9515c00 -1 init, newargv = 0x55baceded440 newargc=7 ceph-fuse[50438]: starting ceph client ceph-fuse[50438]: starting fuse 注：内核只要支持应该使用内核级，用户空间级的性能上比不上内核级文件系统。\n开机自动挂载\ntext 1 none /data fuse.ceph ceph.id=fsclient,ceph.conf=/etc/ceph/ceph.conf,_netdev,default 0 0 CephFS工作模型 文件元数据的工作负载通常是一类小而密集的IO请求，因此很难实现类似数据读写IO那样的扩展方式。\n分布式文件系统业界提供了将名称空间分割治理的解决方案，通过将文件系统根树及其热点子树分别存储于不同的元数据服务器进行负载均衡，从而赋予了元数据存储线性扩展的可能\n静态子树分区，固定的不灵活 静态hash分区，对文件名或目录进行hash运算 惰性混编分区，将静态hash和传统文件方式混合使用 动态子树分区， Multi MDS 多主MDS模式是指CephFS将整个文件系统的名称空间切分为多个子树并配置到多个MDS之上，不过，读写操作的负载均衡策略分别是子树切分和目录副本\n将写操作负载较重的目录切分成多个子目录以分散负载 为读操作负载较重的目录创建多个副本以均衡负载 子树分区和迁移的决策是一个同步过程，各MDS每10秒钟做一次独立的迁移决策，每个MDS并不存在一个一致的名称空间视图，且MDS集群也不存在一个全局调度器负责统一的调度决策\n各MDS彼此间通过交换心跳信息（HeartBeat，简称HB）及负载状态来确定是否要进行迁移、如何分区名称空间，以及是否需要目录切分为子树等\n管理员也可以配置CephFS负载的计算方式从而影响MDS的负载决策，目前，CephFS支持基于CPU负载、文件系统负载及混合此两种的决策机制 动态子树分区依赖于共享存储完成热点负载在MDS间的迁移，于是Ceph把MDS的元数据存储于后面的RADOS集群上的专用存储池中，此存储池可由多个MDS共享\n·MIDS对元数据的访问并不直接基于RADOS进行，而是为其提供了一个基于内存的缓存区以缓存热点元数据，并且在元数据相关日志条目过期之前将一直存储于内存中\nCephFS使用元数据日志来解决容错问题 ·元数据日志信息流式存储于CephFS元数据存储池中的元数据日志文件上，类似于LFS（Log-Structured File System）和WAFL（Write Anywhere File Layout）的工作机制， ·CephFS元数据日志文件的体积可以无限增长以确保日志信息能顺序写入RADOS，并额外赋予守护进程修剪冗余或不相关日志条目的能力\n每个CephFS都会有一个易读的文件系统名称和一个称为FSCID标识符ID，并且每个CephFS默认情况下都只配置一个Active MDS守护进程\n一个MDS集群中可处于Active状态的MDS数量的上限由max_mds参数配置，它控制着可用的rank数量，默认值为1\nrank是指CephFS上可同时处于Active状态的MDS守护进程的可用编号，其范围从0到max mds-1 一个rank编号意味着一个可承载CephFS层级文件系统==目录子树==元数据管理功能的Active状态的ceph-mds守护进程编制，max_mds的值为1时意味着仅有一个0号rank可用。 刚启动的ceph-mds守护进程没有接管任何rank，它随后由MON按需进行分配 一个ceph-mds一次仅可占据一个rank，并且在守护进程终止时将其释放；一个rank只能被一个MDS所占用，被占用后其他MDS就不能再使用它了。 如果MDS名额数量少于进程数量，多余出来的进程只能处于备用模式。 一个rank可以处于下列三种状态中的某一种： Up：rank已经由某个ceph-mds守护进程接管 Failed：rank未被任何ceph-mds守护进程接管 Damaged：rank处于损坏状态，其元数据处于崩溃或丢失状态；在管理员修复问题并对其运行ceph mds repaired”命令之前，处于Damaged状态的rank不能分配给其它任何MDS守护进程 当新添加osd时，数据会将pg分配到新的osd上，如果此时影响集群访问。可以添加flag nobackfill：禁止数据回填 ，norebalance：禁止重平衡数据。在执行集群维护或者停机时，可以使用该flag\ntext 1 2 ceph osd set nobackfill # 增加 ceph osd unset nobackfill # 取消 ","permalink":"https://www.161616.top/ch04-1-cephfs/","summary":"Ceph专门提供了文件系统接口CephFS。CephFS是略微不同于RBD的架构形式，在基础的RADOS Cluster存储集群的基础之上，需要额外运行一个守护进程MDS MetaDataServer 元数据服务器。\nRADOS Cluster自己是一个对象存储服务，他无法管理传统文件系统上分离去管理元数据和数据的功能。（而且元数据还拥有权限、用户属组、时间戳等。）RADOS Cluster自身是无法实现这个功能的。MDS专用于模拟传统文件系统所应该具有的将数据和元数据分离存储的方式而专门提供的一个服务。MDS只用来管理元数据。\nMDS需要工作一个守护进程，客户端必须通过套接字的方式，每一次访问文件时，先去联系到MDS，来获取文件的元数据信息，再到RADOS Cluster以对象方式，将对象模拟成传统文件系统的块，来加载文件数据。\n如是挂在CephFS系统的客户端，需先联系到MDS，识别文件系统的各种信息。客户端向挂载目录路径下的任何一个读写操作就相当于由挂载时的内核驱动模块联系到对应的MDS，而后再由客户端之上的模块来联系到RADOS Cluster。为了能够支持CephFS文件系统，也需要内核级模块Ceph.ko模块。挂载过程机制为将内核模块作为文件系统内核的客户端，与文件系统的守护进程进行通讯，必要时将用户数据存取转为对应集群的存储操作。\n对于Rados存储集群来讲，存储集群所有数据都会被放在存储池当中，而CephFS管理其数据和元数据分别放置在不同的存储池中。所有元数据都是由MDS管理的。MDS也是客户端，连入CephFS的的metadata，专门用于存储元数据的存储池。\ncephfs逻辑\n元数据是一类很密集的IO访问。对原数据存储池的操作是放置在存储池当中的，但在本地会使用内存（高速缓存）中完成，过断时间同步到metadata pool中。而数据直接写入data存储池中\nmeatdata pool只能对mds访问，其他任何客户端时不能被访问的。客户端对元数据的访问必须经由MDS来实现。\n当客户端打开一个文本时，首先请求客户端的inode（传统文件系统采用inode来保存文件的元数据）。获得相应授权以后从mds中接收到inode，inode中标示文件的数据究竟放置在data pool的那些对象中。返回对象编号给客户端，客户端基于对象编号访问所有的对象，将数据加载到。\nceph mds stat\nmds: 2 up:standby 当没有文件系统时，无法进行选举，故所有的都为standby\nCephFS Client访问CephFS集群的方式\n客户端挂载CephFS， 基于内核文件系统完成挂载ceph、libcephfs 用户空间文件系统（FUSE Filesystem in USErspace）：libcephfs与ceph集群进行交互。 ==激活CephFS步骤==\n激活CephFS MDS，至少有一个节点运行ceph-mds守护进程\nceph-deploy命令完成的 创建存储池：metadata-pool、data-pool\nbash 1 2 ceph osd pool create cephfs_metadata 8 8 ceph osd pool create cephfs_data 8 8 激活文件系统：\nceph fs new \u0026lt;name\u0026gt; {metadata-pool-name} {pool-name} ceph fs status {filesystem-name} ceph fs stat 获得必要授权，才能使用服务","title":"Ceph文件系统概述"},{"content":"prometheus 监控nginx的模块 nginx-module-vts\n下载后配置\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 http{ vhost_traffic_status_zone; vhost_traffic_status_zone shared:vhost_traffic_status:32m; # 设置共享内存大小 server { vhost_traffic_status_filter_by_set_key $status $server_name; # 计算详细的http状态代码的流量 location /status { vhost_traffic_status_display; # 设置了该指令，则可以访问如下： vhost_traffic_status_display_format html; vhost_traffic_status off; ## 启用或禁用模块工作 } } } 不想统计流量的server区域禁用vhost_traffic_statu off\n例： 计算upstream后端响应时间 nginx_upstream_responseMsec{upstream=“group1”}\n一个完整的配置文件：nginx.conf 关于GEO相关：GeoIP.dat file format #162 ngx如何配置GEO：GeoIP discontinuation; Upgrade to GeoIP2 with nginx on CentOS 删除所zone内存中的数据\nbash 1 curl localhost/status/control?cmd=delete\u0026amp;group=* ","permalink":"https://www.161616.top/prome-nginx-module-vts/","summary":"prometheus 监控nginx的模块 nginx-module-vts\n下载后配置\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 http{ vhost_traffic_status_zone; vhost_traffic_status_zone shared:vhost_traffic_status:32m; # 设置共享内存大小 server { vhost_traffic_status_filter_by_set_key $status $server_name; # 计算详细的http状态代码的流量 location /status { vhost_traffic_status_display; # 设置了该指令，则可以访问如下： vhost_traffic_status_display_format html; vhost_traffic_status off; ## 启用或禁用模块工作 } } } 不想统计流量的server区域禁用vhost_traffic_statu off\n例： 计算upstream后端响应时间 nginx_upstream_responseMsec{upstream=“group1”}\n一个完整的配置文件：nginx.conf 关于GEO相关：GeoIP.dat file format #162 ngx如何配置GEO：GeoIP discontinuation; Upgrade to GeoIP2 with nginx on CentOS 删除所zone内存中的数据\nbash 1 curl localhost/status/control?","title":"用于监控nginx的exporter：nginx-module-vts"},{"content":"如果需要与osd打交道，需要通过mon检索集群运行图才能够访问的客户端，通常经由mon认证后才能访问ceph存储。Ceph本身实现了数据服务的认证访问和授权控制机制，CephX协议来实现\nCephX Protocol\nCephX本身只负责认证和授权检测，不处理通讯过程是否加密。一般来讲需要与moniotr交互的客户端组件（OSD、RBD、RGW等）一般而言都经由CephX认证\nCephX认证机制 Ceph使用cephx协议对客户端进行身份认证\n每个MON都可以对客户端进行身份验正并分发密钥，不存在单点故障和性能瓶颈。（在集群模式下，任何一个monitor在实现认证时是无状态的，每个monitor都能完成身份检验的任务） MON会返回用于身份验正的数据结构，其包含获取Ceph服务时用到的session key session key通过客户端密钥进行加密，需事先有一个预共享秘钥存在 客户端使用session key向MON请求所需的服务。session key只是拿来做中间通讯使用。 MON向客户端提供一个ticket，用于向实际处理数据的OSD等验正客户端身份。 MON和OSD共享同一个secret，因此OSD会信任由MON发放的ticket ticket存在有效期限 注意：\nCephX身份验正功能仅限制Ceph的各组件之间，它不能扩展到其它非Ceph组件。 它并不解决数据传输加密的问题。 为了实现Cephx认证，Ceph服务器端一定会为每一个客户端事先生成一个密码\n认证与授权 无论Ceph客户端时何类型，Ceph都会在存储池中将所有的数据存储为对象\nCeph用户需要拥有存储池访问权限才能读取和写入数据\nCeph用户必须拥有执行全年才能使用Ceph管理命令\n相关概念\n用户\n用户是指个人或系统参与者（例如应用） 通过创建用户，可以控制谁（或哪个参与者）能够访问Ceph存储集群、以及可访问的存储池及存储池中的数据。 Ceph支持多种类型的用户，单可管理的用户都属于Client类型 区分用户种类的原因在于，mon、osd、mds等系统组件也使用cephx协议，但它们非为客户端 通过点号来分隔用户类型和用户名，格式为TYPE.ID，例如：client.admin等 授权\n使能（Capabilities）\nCeph基于\u0026quot;使能(caps)\u0026ldquo;来描述用户可针对MON、OSD或MDS使用的权限范围或级别。 通用语法格式：daemon-type'allow caps[...] MON使能 包括r、w、x和allow profile cap 例如：mon allow rwx，以及mon allow profile osd\u0026rsquo;等。 OSD使能 包括r、w、x、class-read、class-write和profile osd 此外，OSD使能还允许进行存储池和名称空间设置。如为指定表示对所有OSD所有存储池都获得相关授权 MDS使能 只需要allow，或留空 各使能的意义\nallow\n需先于守护进程的访问设置指定 仅对MDS表示rw之意，其它的表示字面意义 r：读取权限，访问MON以检索CRUSH时依赖此使能\nw：对象写入权限\nx：调用类方法（读取和写入）的能力，以及在MON上执行auth操作的能力。\nclass-read：x能力的子集，授予用户调用类读取方法的能力\nclass-write：x的子集，授予用户调用类写入方法的能力\n·*：授予用户对特定守护进程/存储池的读取、写入和执行权限，以及执行管理命令的能力。\nprofile osd\n授予用户以某个OSD身份连接到其他OSD或监视器的权限 授予OSD权限，使OSD能够处理复制检测信号流量和状态报告 profile mds\n授予用户以某个MDS身份连接到其他MDS或监视器的权限 profile bootstrap-osd\n授予用户引导OSD的权限 授权给部署工具，使其在OSD加入集群时，引导OSD时有权添加密钥 profile bootstrap-mds\n授予用户引导元数据服务器的权限 授权给部署工具，使其在引导元数据服务器时有权添加密钥 用户管理 keyring 保存在ceph集群内部的整体信息，统一的账号存储文件，存放多个用户及其秘钥信息\nkeyring file：单独被导出的文件，如ceph.client.admin.keyring\nCeph集群管理员能够直接在Ceph集群中创建、更新和删除用户·创建用户时，可能需要将密钥分发到客户端，以便将密钥添加到==密钥环==\n列出用户\n命令：ceph auth list 用户标识：TYPE.ID，因此，osd.0表示OSD类型的用户 （osd专用于系统参与者不是真正客户端用户）。，用户ID为0 检索特定用户\n命令：ceph auth get TYPE.ID或者ceph auth export TYPE.ID 添加用户\nceph auth add：规范方法，它能够创建用户、生成密钥并添加指定的caps ceph auth get-or-create：简便方法，创建用户并返回密钥文件格式的密钥信息，或者在用户存在时返回用户名及密钥文件格式的密钥信息\ntext 1 2 3 4 5 6 7 8 9 $ ceph auth add client.testuser mon \u0026#39;allow r\u0026#39; osd \u0026#39;allow rw pool=rbdpool\u0026#39; added key for client.testuser $ ceph auth get client.testuser exported keyring for client.testuser [client.testuser] key = AQBuyw5d+GcJBxAAYF3dkO4qcAA/B/gOt91T1Q== caps mon = \u0026#34;allow r\u0026#34; caps osd = \u0026#34;allow rw pool=rbdpool\u0026#34; ceph auth get-or-create-key：简便方法，创建用户并返回密钥信息，或者在用户存在时返回密钥信息\n注意：典型的用户至少对Ceph monitor 具有读取功能，并对Ceph OSD具有读取和写入功能；另外，用户的OSD权限通常应该限制为只能访问特定的存储池，否则，他将具有访问集群中所有存储池的权限\n列出用户秘钥\nceph auth print-key {TYPE.ID} 导入用户\nceph auth import 需要指定秘钥环 修改用户caps\nceph auth caps 会覆盖用户现有的caps，因此建立事先使用ceph auth get {TYPE.ID}命令查看用户的caps 若是为添加caps，则无需先指定现有的caps 命令格式：ceph auth caps {TYPE.ID} daemon 'allow [r|w|x|*|...] [{pool=pool-name}] text 1 2 3 4 5 6 7 8 9 $ ceph auth caps client.testuser mon \u0026#39;allow rw\u0026#39; osd \u0026#39;allow rw pool=rbdpool\u0026#39; updated caps for client.testuser $ ceph auth get client.testuser exported keyring for client.testuser [client.testuser] key = AQBuyw5d+GcJBxAAYF3dkO4qcAA/B/gOt91T1Q== caps mon = \u0026#34;allow rw\u0026#34; caps osd = \u0026#34;allow rw pool=rbdpool\u0026#34; 删除用户\nceph auth del {TYPE.ID} keyring keyring是一个集合，能够同时存储secret、password、keys、certificates并且使得他们能够被某一个应用程序能用的文件的集合。\n任何一个客户端在联系monitor时，会查找本地适用于当前应用程序的keyring文件，以获取自己能够认证的ceph集群的认证信息。\nceph-authtool用来创建、修改、查看keyring文件的内容\n访问Ceph集群时，客户端会于本地查找密钥环，默认情况下，Ceph会使用以下四个密钥环名称预设密钥环\n/etc/ceph/{cluster-name}.{user-name}.keyring: 保存单个用户的keyring /etc/ceph/cluster.keyring： 保存多个用户的keyring /etc/ceph/keyring /etc/ceph/keyring.bin：二进制格式，被编码后 {cluster-name}是为集群名称，{user-name}是为用户表示{TYPE.ID} client.admin用户的在名为ceph的集群上的密钥环文件名为ceph.client.admin.keyring\n管理keyring 创建keyring\nceph auth add等命令添加的用户还需要额外使用ceph-authtool命令为其创建用户秘钥 ceph客户端通过keyring文件查找用户名并检索秘钥，命令：ceph-authtool --create-keyring /path/to/keyring 注意\nkeyring文件一般应该保存于/etc/ceph目录中，以便客户端能自动查找 创建包含多个用户的keyring文件时，应该使用`cluster-name.keyring``为文件名 创建仅包含单个用户的kerying文件时，应该使用cluster-name.user-name.keyring作为文件名 将用户添加至keyring\n可将某个用户从包含多个用户的keyring中导出，并保存于一个专用的keyring文件， ==命令==：ceph auth get TYPE.ID -o /etc/ceph/cluster-name.user-name.keyring 也可将用户的keyring合并至一个统一的keyring文件中， ==命令==：ceph-authtool /etc/ceph/cluster-name.keyring -import-key /etc/ceph/cluster-\rname.user-name.keyring 使用ceph-authtool命令管理用户 ceph-authtool 命令可以直接创建用户、授权caps并创建keyring\ntext 1 2 3 4 ceph-authtool keyringfile [-C | --create-keyring] [-n | --name entityname] [--gen-key] [-a | --add-key base64_key] [--cap | --caps capfile] 命令选项\n-C，--create-keyring：创建一个新的密钥环，覆盖任何现有的密钥环文件 --gen-key：将为指定的实体名生成新的密钥 --add-key：将为密钥环添加编码密钥 --cap subsystem capability 将设置给定子系统cap文件的功能 --caps capfile 将为所有子系统设置与给定密钥关联的所有功能 注意：此种方式添加的用户仅存在于keyring文件中，管理员还需要额外将其添加至Ceph集群上\n命令：ceph auth add {TYPE.ID} -i /PATH/TO/keyring 创建k8s使用的账号\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 $ ceph auth get-or-create client.kube mon \u0026#39;allow r\u0026#39; osd \u0026#39;allow * pool=kube\u0026#39; [client.kube] key = AQAwfA9dff8nGRAAMsF1VlKRPS/NEFOnB057OQ== $ ceph auth get client.kube exported keyring for client.kube [client.kube] key = AQAwfA9dff8nGRAAMsF1VlKRPS/NEFOnB057OQ== caps mon = \u0026#34;allow r\u0026#34; caps osd = \u0026#34;allow * pool=kube\u0026#34; $ ceph auth get client.kube -o ./ceph.client.kube.keyring exported keyring for client.kube text 1 2 3 4 5 ceph-authtool --create-keyring cluster.keyring ceph-authtool cluster.keyring --import-keyring ./ceph.client.kube.keyring ceph-authtool cluster.keyring --import-keyring ./ceph.client.admin.keyring ","permalink":"https://www.161616.top/ch07-1-cephx/","summary":"如果需要与osd打交道，需要通过mon检索集群运行图才能够访问的客户端，通常经由mon认证后才能访问ceph存储。Ceph本身实现了数据服务的认证访问和授权控制机制，CephX协议来实现\nCephX Protocol\nCephX本身只负责认证和授权检测，不处理通讯过程是否加密。一般来讲需要与moniotr交互的客户端组件（OSD、RBD、RGW等）一般而言都经由CephX认证\nCephX认证机制 Ceph使用cephx协议对客户端进行身份认证\n每个MON都可以对客户端进行身份验正并分发密钥，不存在单点故障和性能瓶颈。（在集群模式下，任何一个monitor在实现认证时是无状态的，每个monitor都能完成身份检验的任务） MON会返回用于身份验正的数据结构，其包含获取Ceph服务时用到的session key session key通过客户端密钥进行加密，需事先有一个预共享秘钥存在 客户端使用session key向MON请求所需的服务。session key只是拿来做中间通讯使用。 MON向客户端提供一个ticket，用于向实际处理数据的OSD等验正客户端身份。 MON和OSD共享同一个secret，因此OSD会信任由MON发放的ticket ticket存在有效期限 注意：\nCephX身份验正功能仅限制Ceph的各组件之间，它不能扩展到其它非Ceph组件。 它并不解决数据传输加密的问题。 为了实现Cephx认证，Ceph服务器端一定会为每一个客户端事先生成一个密码\n认证与授权 无论Ceph客户端时何类型，Ceph都会在存储池中将所有的数据存储为对象\nCeph用户需要拥有存储池访问权限才能读取和写入数据\nCeph用户必须拥有执行全年才能使用Ceph管理命令\n相关概念\n用户\n用户是指个人或系统参与者（例如应用） 通过创建用户，可以控制谁（或哪个参与者）能够访问Ceph存储集群、以及可访问的存储池及存储池中的数据。 Ceph支持多种类型的用户，单可管理的用户都属于Client类型 区分用户种类的原因在于，mon、osd、mds等系统组件也使用cephx协议，但它们非为客户端 通过点号来分隔用户类型和用户名，格式为TYPE.ID，例如：client.admin等 授权\n使能（Capabilities）\nCeph基于\u0026quot;使能(caps)\u0026ldquo;来描述用户可针对MON、OSD或MDS使用的权限范围或级别。 通用语法格式：daemon-type'allow caps[...] MON使能 包括r、w、x和allow profile cap 例如：mon allow rwx，以及mon allow profile osd\u0026rsquo;等。 OSD使能 包括r、w、x、class-read、class-write和profile osd 此外，OSD使能还允许进行存储池和名称空间设置。如为指定表示对所有OSD所有存储池都获得相关授权 MDS使能 只需要allow，或留空 各使能的意义\nallow\n需先于守护进程的访问设置指定 仅对MDS表示rw之意，其它的表示字面意义 r：读取权限，访问MON以检索CRUSH时依赖此使能\nw：对象写入权限\nx：调用类方法（读取和写入）的能力，以及在MON上执行auth操作的能力。\nclass-read：x能力的子集，授予用户调用类读取方法的能力\nclass-write：x的子集，授予用户调用类写入方法的能力\n·*：授予用户对特定守护进程/存储池的读取、写入和执行权限，以及执行管理命令的能力。\nprofile osd\n授予用户以某个OSD身份连接到其他OSD或监视器的权限 授予OSD权限，使OSD能够处理复制检测信号流量和状态报告 profile mds\n授予用户以某个MDS身份连接到其他MDS或监视器的权限 profile bootstrap-osd","title":"Ceph安全 - CephX"},{"content":"初识Ceph Ceph 是一个开源分布式存储系统系统，它不是一种单一的存储，而是面向云提供一种统一存储平台，包含块存储 RBD, 文件存储 CephFS, 以及对象存储 RGW，这种存储的出现允许用户拜托供应商的绑定，它可以提供块存储到 “云平台”，也可以提供对象存储到 “应用”，并支持理论上的无限扩展性，数千客户端访问 PB 甚至 EB 级别的数据\nSAN VS Ceph 与传统 SAN 存储相比，Ceph 客户端会计算他们所需的数据所在的位置，这消除了存储系统中需要在“中心化查找”的瓶颈。 这使得 Ceph 集群可以在不损失性能的情况下进行扩展。\nCeph 集群架构组成 Ceph 集群核心是 RADOS，而基于 RADOS，构建出多种类型存储，块存储, 文件系统, 对象存储，而一个基础的 Ceph 集群的组件由 \u0026ldquo;Ceph monitor\u0026rdquo; 与 \u0026ldquo;Ceph OSD Daemon\u0026rdquo; 组成\nCeph Monitor（进程名称为 ceph-mon，下文中以 ceph-mon 代表 Ceph Monitor） 维护集群映射的主副本。 ceph集群中的monitor，可确保 ceph-mon 守护进程在失败时的高可用性。客户端从 ceph-mon 检索集群映射的副本。 Ceph OSD Daemon 检查”自身“及”其他“ OSD 的状态并报告给 Monitor。 Ceph 中的常见术语 Application 用于使用 Ceph 集群的任何 Ceph 外部的应用程序\nBlock Device 也称为 “RADOS 块设备” 或 ”RBD“ ，协调基于块的数据存储的工具，Ceph块设备拆分基于块的应用程序数据 成“块”。 RADOS 将这些块存储为对象。 Ceph 块 设备协调这些对象的存储 存储集群。\n也称为 “RADOS Block Device” 或 “RBD”。一种用于协调 Ceph 中基于块的数据的存储的软件。 Ceph 块设备将基于块的应用程序数据拆分为 “Chunk”。 RADOS 将这些块存储为对象。\nChunk 与 Block 是两种不同的概念\nChunk 存储是类似于 Key-Value 存储和对象存储，是一种结构化数据，并固定大小的块\nBlock 通常被提到的上下文是作为硬件接口提供的，通常代表硬件裸设备\n所以说 Block Device 是将数据划分为固定大小的 Chunk，存储在 Block 上。\nMGR (Manager) Ceph Manager 又称为 Ceph Manager Daemon，进程名称为 ceph-mgr, 是与 Ceph Monitoring 一起运行的守护进程，用于提供监视以及与外部监视和管理系统的接口。自 Luminous 版本 (12) 起，ceph-mgr 没有运行的情况下 Ceph 集群无法正常运行。\nMON (Monitor) Ceph Monitor 维护集群状态影视的守护进程，这些“集群状态”包括 Monitor map、Manager map、OSD map 和 CRUSH map。 Ceph 集群必须至少包含三个正在运行的 Monitor，才能实现冗余和高可用性。\nOSD Ceph Object Storage Daemon，又被称为 OSD, 在 “research and industry” 中 OSD 表示 ”对象存储设备“，而 Ceph 社区将 OSD 称为 OSD daemon，用于与逻辑磁盘交互的进程。\nOSD fsid 用于标识 OSD 的唯一标识符。它可以在 OSD 路径中名为 osd_fsid 的文件中找到。术语 “fsid” 与 “uuid” 互换使用\nOSD id 定义 OSD 的 integer，它是在创建每个 OSD 期间由监视器生成的。\nHybrid OSD 指同时拥有 HDD 和 SSD 的 OSD\nCluster Map 由 monitor map、OSD Map、PG Map、MDS Map 和 CRUSH Map 组成的一组 Map，它们共同报告 Ceph 集群的状态。有关详细信息。\nCRUSH CRUSH Controlled Replication Under Scalable Hashing 可扩展散列下的受控复制，Ceph 用于计算对象存储位置的算法。\nDAS DAS Direct-Attached Storage 直接附加存储，无需访问网络直接连接计算机的存储。例如 SSD\nLVM tags Logical Volume Manager tags 逻辑卷管理器标签，LVM “卷” 和 “组” 的可扩展元数据。它们用于存储有关设备及其与 OSD 关系的 Ceph 特定信息。\nPGs (Placement Groups) “放置组” 是每个逻辑 Ceph Pool 的子集。放置组执行将对象（作为一个组）放置到 OSD 中的功能。 Ceph 在内部以“放置组粒度”来管理数据：这比管理单个RADOS 对象的扩展性将更好。具有较大数量放置组的集群比具有较少数量放置组的其他相同集群具有更好的平衡性。\nPools 池是用于存储对象的逻辑分区。\nRADOS Reliable Autonomic Distributed Object Store 可靠的自动分布对象存储，RADOS 是为可变大小的对象提供可扩展服务的对象存储。 RADOS 对象存储是 Ceph 集群的核心组件。\nBlock Storage 块存储是 Ceph支持的三种存储类型之一。 Ceph 块存储指的是块存储 结合使用时的相关服务和功能 集合\nCeph File System Ceph File System (CephFS) 是一个兼容 POSIX 的文件系统，构建在 RADOS 之上，可根据按需部署\nMDS (Metadata Server) Ceph MetaData Server daemon MDS，构建在 RADOS 之上，存储所有文件的元数据作为”文件系统“类型的存储提供给用户，运行的程序名为 ceph-mds，故 也是是否使用 CephFS 的标记\nRGW (Radow Gateway) Ceph 提供兼容 Amazon S3 RESTful API 和 OpenStack Swift API 的组件，可根据按需部署\nRealm 是位于对象存储中的上下文，领域 (Realm) 是一个全局唯一的命名空间，由一个或多个区域组组成。\nZone 是位于对象存储中的上下文，区域 (zone) 是由一个或多个 RGW 实例组成的逻辑组。\u0026ldquo;zone\u0026rdquo; 的配置状态存储在 \u0026quot;\u0026quot; 中\nPeriod 是位于对象存储中的上下文，Period 是 Realm 的配置状态。该 Period 存储多站点配置的配置状态。当 Period 被更新时，“epoch” 被认为已经改变。\nCephX CephX Ceph authentication protocol；CephX 是用于对用户和守护进程进行身份验证。 CephX 的运行方式类似于 Kerberos，但它没有单点故障。\nSecrets Secret 是用户访问是需要提供的身份验证的系统用于执行数字身份验证的凭据。\nCeph存储集群组成 Ceph 存储集群由多种类型的守护进程组成：\nCeph Monitor Ceph OSD Daemon Ceph Manager Ceph Metadata Server OSD (Object Storage Device) 对象存储设备 OSD (Object Storage Device) 通常是指==单独的磁盘设备==，每个 Ceph Node 上有一个或多个o OSD，每一个 OSD 是真正存放数据的地方（在文件系统中同样概念为文件与目录）；OSD不是主机。由多个 Ceph Node 组合起来称为 RADOS Cluster\n为了使每个 OSD 能够被单独使用和管理，每个 OSD 都会有一个单独的专用的守护进程被称为 ceph-osd。OSD 本身用来存储数据，还包括数据复制、恢复、数据重新均衡、提供监视信息给mon和mgr\n一个集群至少有 3个 Ceph OSDs，已确保高可用。选择 OSD 冗余时，应自己指定故障率，故障转移率或故障容忍率。OSD级别故障就在OSD级别冗余，主机级别就跨主机冗余，故障率是机架级别就机架冗余。 在做crush运行图的设定时是应该自己指定的。\n(MGR Manager) Mgr (Manager) 集群元数据服务器，维护集群映射的主副本。Ceph 监视器集群可确保监视器守护进程发生故障时的高可用性。存储集群客户端从 Ceph Monitor 检索集群映射的副本。\nmonitor在每一次读数据都是实时查询的，故monitor不适用频繁周期性采集数据的监控操作。在Ceph新版中引入新组建mgr，（早期Ceph版本是没有mgr的）用来专门维护查询类操作，将查询操作按照自己内部空闲方式缓存下来，一旦有监控可及时响应。\nmgr是在一类节点上运行的守护进程，一般为两个活以上节点，此类守护进程被称为ceph-mgr。主要功能在于跟踪运行时的指标数据。如磁盘使用率、CPU使用率。以及集群当前状态，此状态不是内部运行状态，而是查询做监控时的状态。包括存储空间利用率、当前性能指标、节点负载（系统级）等\nMON (Monitor) 一个 Ceph 集群内除了存储节点之外，还有另外一种节点 Monitor ，用来管理整个集群的，如有多少个节点，每个节点上有多少个OSD，每个OSD是否健康，他会持有整个集群的运行图（运行状态）。mon是用来集中维护集群元数据而非文件元数据。为了维护整个集群能够正常运行而设定的节点，离开此节点集群内部就无法协调。\n在一个主机上运行的守护进程 ceph-mon，守护进程扮演、监视着整个集群所有组件的角色，被称为集群运行图的持有者cluster map（整个集群有多少存储池，每个池中有多少PG，每个PG映射哪个OSD，有多少OSD等等）集群运行图 Cluster Map\nmonitor负责维护整个进群的认证信息并实行认证，认证协议叫 ==CephX== 协议（ceph内部的认证协议）。monitor用来维护认证信息并实行认证。认证中心 monitor自身是无状态的，所以实现均衡认证负载。\nmonitor的高可用自己内部直接使用POSIX协议来实现数据冗余，monitor也是节点级冗余，为了确保各节点数据是强一致的，每个节点都可写，写完后会同步到其他节点。为了避免同时写导致的冲突，使用了分布式一致性协议，monitor就是使用POSIX协议来进行分布式协作的。\nMDS (Metadata Server) ==Ceph Metadata Server== 用来代表ceph文件系统而提供的守护进程ceph-mds，如不使用CephFS，此进程是无需启动的。利用底层RADOS存储空间，将存储空间抽象成文件系统，来兼容POSIX file system 提供服务。\nNote: 一个基础的 Ceph 存储集群由，OSD, Monitor, Manager 组成\nCeph集群中其他概念 客户端接口 Ceph 存储集群提供了基础的对象数据存储服务，客户端可基于RADOS协议和 librados API 直接与存储系统交互进行对象数据存取 Librados Librados提供了访问 RADOS 在存储集群支持 “异步” 通信的 API 接口，支持对集群中对象数据的的直接并行访问，用户可通过支持的编程语言开发自定义客户端程序通过RADOS协议与存储系统进行交互 客户端应用程序必须与librados绑定方可连接到RADOS存储集群，因此，用户必须实现安装librados及其依赖后才能编写使用librados的应用程序。 librados API本身使用 C++ 编写的，它额外支持C、Python、Java、和PHP等开发接口 当然，并非所有用户都有能力自定义开发接口以接入RADOS存储集群的需要，为此，Ceph也原生提供了几个较高级别的各户端接口，它们分别是 RADOS GateWay (RGW), Reliable Block Device (RBD) 和 MDS (MetaData Server)，分别为用户提供 RESTFUL、块和 POSIX 文件系统接口 管理节点 (Admin Host) Ceph通常是分布式集群，为了便于去管理维护整个集群，通常在Ceph集群中找一个专门的节点用来当管理节点。此节点可以连接至每一个节点用来管理节点上的Ceph守护进程。\nCeph的管理接口是一系列命令行工具，例如 rados, ceph, rbd 等命令，管理员可以从某个特定的MON节点执行管理操作，但也有人更倾向于使用专用的管理节点。\nNote: 在早期 (Ceph-deploy) 部署的集群，通常管理节点是必要的，但使用 cephadm 部署的集群，实际上管理管理节点可以在任何位置\nPool Ceph所提供的存储空间（没有目录之类一说）是将“所有对象都是存储在数据平面上”，因此，所有对象都不能同名。RADOS 将他的存储空间切分为多个分区以便好进行管理。每一个分区叫做一个“存储池”。存储池的大小是取决于底层的存储空间的。与真正意义上的分区不是一回事。在Ceph中每一个存储池存放的数据了也可能会太大，所以存储池也可以进一步划分（可选）。被称为名称空间（先切分为存储池，每个存储池可进一步被划分成名称空间） 两级逻辑组件。\n第三级 PG 每一个存储池内部会有多个PG（Placement Groups 规置组）存在。Pool 与 pg 都是抽象的概念。\nObject 对象是自带元数据的组件，\n对象id，每一个对象应有一个对象ID在集群内部来引用对象 数据 元数据 key vlaue类型的数据 这些类型打包成一起存储，被称为一个对象。RADOS集群会吧真正存储的每一个文件，切分成N个对象来进行存储的。（切分个数与默认对象切分大小有关）。\n每一个对象都是被单独管理的，都拥有自己的标识符。因此， 同一个文件的object有可能被映射到不同的PG上，PG提交给主机的，由主机负责将对象存储在磁盘（OSD）被存储在不同的OSD上。\n文件存储到RADOS集群 一般要接入RADOS集群必须通过客户端来实现(LIBRADOS、RBD、CephFS、RADOSGW)，才能接入到集群中来。\n当将文件存入Ceph中时，需要通过某一类客户端接入，客户端接入时，需要借助于 Ceph 存储API接口将其切分为固定大小的存储对象(Date object)，此数据对象究竟被放置在哪个 OSD 上存放这中间是靠 crush 来完成的。数据对象被存放在哪个存储池上是固定的。存储池需创建才可使用。但PG是虚拟的中间层。\n","permalink":"https://www.161616.top/ch01-1-ceph-acquaintance/","summary":"初识Ceph Ceph 是一个开源分布式存储系统系统，它不是一种单一的存储，而是面向云提供一种统一存储平台，包含块存储 RBD, 文件存储 CephFS, 以及对象存储 RGW，这种存储的出现允许用户拜托供应商的绑定，它可以提供块存储到 “云平台”，也可以提供对象存储到 “应用”，并支持理论上的无限扩展性，数千客户端访问 PB 甚至 EB 级别的数据\nSAN VS Ceph 与传统 SAN 存储相比，Ceph 客户端会计算他们所需的数据所在的位置，这消除了存储系统中需要在“中心化查找”的瓶颈。 这使得 Ceph 集群可以在不损失性能的情况下进行扩展。\nCeph 集群架构组成 Ceph 集群核心是 RADOS，而基于 RADOS，构建出多种类型存储，块存储, 文件系统, 对象存储，而一个基础的 Ceph 集群的组件由 \u0026ldquo;Ceph monitor\u0026rdquo; 与 \u0026ldquo;Ceph OSD Daemon\u0026rdquo; 组成\nCeph Monitor（进程名称为 ceph-mon，下文中以 ceph-mon 代表 Ceph Monitor） 维护集群映射的主副本。 ceph集群中的monitor，可确保 ceph-mon 守护进程在失败时的高可用性。客户端从 ceph-mon 检索集群映射的副本。 Ceph OSD Daemon 检查”自身“及”其他“ OSD 的状态并报告给 Monitor。 Ceph 中的常见术语 Application 用于使用 Ceph 集群的任何 Ceph 外部的应用程序\nBlock Device 也称为 “RADOS 块设备” 或 ”RBD“ ，协调基于块的数据存储的工具，Ceph块设备拆分基于块的应用程序数据 成“块”。 RADOS 将这些块存储为对象。 Ceph 块 设备协调这些对象的存储 存储集群。","title":"Ceph概念 - 初识Ceph"},{"content":"关于存储池 从某种意义上来讲，RADOS所提供的存储空间的管理接口，不应该将其放置在同一个平面当中，因此将其切割成多个不同的\u0026quot;逻辑存储空间\u0026quot;，称之为存储池。\nRADOS存储集群提供的基础存储服务需要由\u0026quot;存储池（pool）\u0026ldquo;分割为逻辑存储区域，此类的逻辑区域亦是对象数据的名称空间。\n实践中，管理员可以为特定的应用程序存储不同类型数据的需求分别创建专用的存储池，例如rbd存储池，rgw存储池等，也可以为某个项目或某个用户创建专有的存储池。 存储池还可以再进一步细分为一至多个名称空间（namespace）。同一个存储池内，无论属于哪个、哪些名称空间，数据都是被存储池中的PG进行存放，虽然处于不同名称空间，但可能处于同一个PG之上。 客户端(包括rbd和rgw等）存取数据时，需要事先指定存储池名称、用户名和密钥等信息完成认证，而后将一直维持与其指定的存储池的连接，于是也可以吧存储池看做是客户端的IO接口。 存储池类型\n副本池（replicated）：任何一个数据对象存储在此类存储池中其冗余机制是通过创建多个数据对象副本来实现的，而副本数量是用户在创建存储池时指定。如，创建存储池时没指定类型，就是副本池，默认副本数量为3个（1主两从），统称副本数量。把每个对象在集群中存储为多个副本，其中存储于主OSD的为主副本，副本数量在创建存储池时由管理员指定；副本池类型为Ceph为默认的存储池类型。但是此存储池是非常浪费存储空间的。副本池对读IO有很好的附带表现 纠删码池（erasure code）：使用校验码可计算回数据。把各对象存储为N=K+M个块，其中，K为数据块数量，M为编码块数量，因此存储池的尺寸为K+M；纠删码块的数据就 是允许冗余的级别。如4+2，即允许最多两个数据块丢失。不是所有的应有都能支持纠删码池，如rbd必须使用副本池。radosGW可以使用纠删码池。 副本池IO\n将一个数据对象存储为多副本 写入操作时，Ceph客户端使用CRUSH算法来计算对象的PG ID和Primary OSD 主OSD根据设定的副本教、对象的名称、存储池名称和集群运行图（Cluster Map）计算出PG的各铺助OSD，而后由主OSD将数据同步给这些辅助OSD。 对于有着三个副本的存储池来讲，任何一个PG都会选择三个OSD，因此，副本池所关联的OSD数量通常与冗余量相同。OSD，成为一个活动集。如图所示，其中一个OSD为主OSD负责读写操作，另外两个OSD负责从主OSD同步数据。当三个副本都存完，才能的到存储完成的消息的。客户的只需与主OSD通信，同步过程是OSD内部自行实现的。\n纠删码池IO\n纠删码是一种前向纠错码（FEC）代码\n通过将K块的数据转换为N块，假设N=K+M，则其中的M代表纠删码算法添加的额外活冗余的块数量以提供冗余机制（即编码块），而N则表示在纠删码编码之后要创建的块的总数，其可以故障的块数为M（即N-K）个。 类似于RAID5 纠删码池减少了确保数据持久性所需的磁盘空间量，但计算量上却比副本存储池要更贵一些 RGW可以使用纠删码池，但RDB不支持。\n例如，把包含数据ABCDEFGHI的对象NYAN保存到存储池中，假设纠删码算法会将内容分割为三个数据块：第一个包含ABC，第二个为DEF，最后一个为GHI，并为这三个数据块额外创建两个编码块：第四个YXY和第五个GQC，此时纠删码算法会通过计算哪家出GHI、和YXY\n副本池所有从OSD没有次序之分，只有主和从两类角色之分，各个从没有次序。纠删码池是有次序的，这个顺序代表数据拼凑起来的数据。因此，每个分片应指定其在整个数据文件的偏移量是多少。\n归置组 归置组（PlacementGroup）是用于跨OSD将数据存储在某个存储池中的内部数据结构。\n相对于存储池来说，PG是一个虚拟组件，它是对象映射到存储池时使用的虚拟的层 出于规模伸缩及性能方面的考虑，Ceph将存储池细分为归置组，吧每个单独的对象映射到归置组，并将归置组分配给一个主OSD 存储池由一系列的归置组组成，而CRUSH算法则根据集群运行图和集群状态，将各PG均匀、伪随机地分布到急群众的OSD之上。 若某OSD失败或需要对集群进行重新平衡，Ceph则移动或复制整个归置组而无需单独寻址每个对象。 归置组在OSD守护进程和Ceph客户端之间生成了一个中间层，CRUSH算法负责将每个对象动态映射到一个归置组，然后再将每个归置组动态映射到一个或多个OSD守护进程，从而能够支持在新的OSD设备上线时动态进行数据重新平衡。\n归置组作用\n在存储池中存放100w数据对象，而使用100个归置组，一组内存放1w对象。归置组是从新平衡和恢复时的基本单元。使得数据单元不至于以文件或对象为单位。\n归置组计数 归置组的数量有管理员在创建存储池是指定，而后由crush负责创建和使用\n通常，PG的数量应该是数据的合理粒度的子集 例如，一个包含256个PG的存储池意味着每个PG包含大约1/256的存储池数据 当需要将PG从一个OSD移动到另一个OSD时，PG的数量会对性能产生影响 PG数量过少，Ceph将不得不同时移动相当数量的数据，其产生的网络负载将对集群的正常性能输出产生负面影响。 而在过多的PG数量场景中在移动极少量的数据时，Ceph将会占用过多的CPU和RAM，从而对集群的计算资源产生负面影响。 PG数量在集群分发数据和重新平衡时扮演着重要作用 在所有OSD之间进行数据持久存储及完成数据分布会需要较多的归置组，但是它们的数量应该减少到最大性能所需的最小数量值，以节省CPU和内存资源 一般说来，对于有着超过50个OSD的RADOS集群，建议每个OSD大约有50-100个PG以平衡资源使用，取的更好的数据持久性和数据分布，更大规模的集群中，每个OSD大约可持有100-200个PG 至少应该使用多少个PG，可通过下面的公式计算后，将其值以类似于四舍五入到最近的2的N次幂 (Total OSDs * PGPerOSD/Replication factor =\u0026gt; Total PGs ) 可以使用的PG数量 = 总OSD数量* 每个OSD可以有多少个PG/复制因子（副本数量） 一个RADOS集群上可能会存在多个存储池，因此管理员还需要考虑所有存储池上的PG分布后每个OSD需要映射的PG数量 PG数量一定是2的N次方倍，这样进行hash计算时，速度才会更快。Ceph要求每一个OSD上最多不能超过256个PG 归置组状态 依据PG当前的工作特性活工作进程所阶段，它总是处于某个活某些个“状态中”，最常见的状态应该为active+clean\nPG的常见状态\nActive 主OSD和各辅助OSD均处于就绪状态，可正常服务于客户端IO请求 一般Peering操作过程完成后即会转入Active状态 Clean 主OSD和各辅助OSD均处于就绪状态，所有对象的副本数量均符合期望，并且PG的活动集和上行集为同一组OSD。 活动集(Acting Set)：由PG当前的主OSD和所有的处于活动状态的辅助OSD组成，这组OSD负责执行此PG上数据对象的存取操作I/O 上行集(Up Set)，根据CRUSH的工作方式，集群拓扑架构的变动将可能导致PG相应的OSD变动活扩展至其他的OSD之上，这个新的OSD集也成为PG的“（Up Set）”，其映射到的新OSD集可能不分地与原有OSD集重合，也可能会完全不相干；上行集OSD需要从当前的活动集OSD上复制数据对象，在所有对象同步完成后，上行集便成为新的活动集，而PG也将转为“活动（active）”状态。 Peering 如数据不一致，需将数据复制过去，这个复制数据过程就称之为对等过程。 一个PG中的所有OSD必须就它们持有的数据对象状态达成一致，而“对等（Peering）”即为其OSD从不一致转为一致的过程。 Degraded 在某OSD标记为“down”时，所有映射到此OSD的PG即转入“降级（degraded）”状态 此OSD重新启动并完成Perring操作后，PG将重新转回clean 一旦OSD标记为down的时间超过5分钟，它将被标记出集群，而后Ceph将对降级状态的PG启动回复操作，直到所有因此而降级的PG重回clean状态 在其内部OSD上某对象不可用活悄然崩溃时，PG也会被标记为降级状态，知道对象从某个权威副本上正确恢复。 Stale 过期 每个OSD都要周期性的向RADOS集群中的监视器报告其作为主OSD所持有的所有PG的最新统计数据，因任何原因导致某个主OSD无法正常向监视器发送此类报告，或者由其他OSD报告某个OSD已经down掉，则所有以此OSD的PG将立刻被标记为stale状态。 Undersized PG中的副本数少于其存储池定义的个数时即转入undersized状态，回复和回填操作在随后会启动已修复其副本为期望值 Scrubbing 一致性保障的非常重要机制，文件完整性检查 各OSD还需要周期性的检查其所持有的数据对象的完整性，以确保所有对等OSD上的数据一致；处于此类检查过程中的PG便会被标记为scrubbing状态，这也通常被称作light scrubs、shallow scrubs或者simply scrubs。 另外，PG还需偶尔需要进行deep scrubs检查以确保同一对象在相关的各OSD上能按位匹配，此时PG将处于scrubbing+deep状态。 Recovering 恢复 添加一个新的OSD至存储集群中或某OSD宕掉时，PG则由可能会被CRUSH重新映射进而将持有与此不同的OSD集，而这些处于内部数据同步过程中的PG则被标记为recovering状态； Backfilling 回填 新OSD加入存储集群后，Ceph则会进入数据重新均衡的状态，即一些数据对象会在进程后台从现有OSD移到新的OSD之上，此操作过程为backfill。 CRUSH 把对象直接映射到OSD之上会导致二者之间的紧密耦合关系，变动底层OSD就会牵一发而动全身，因此在OSD设备变动时不可避免地对整个集群产生扰动\n于是Ceph将一个对象映射进RADOS集群的过程分为两步\n先是以一致性哈希算法将对象名称映射到PG 而后是将PG ID基于CRUSH算法映射到OSD 此两个过程都以“实时计算”的方式完成，而非畅通的查表方式，从而有效规避了任何组件被”中心化“的可能性，使得集群规模扩展不在受限。\n这个实时计算操作用到的算法就是CRUSH\nControlled Replication Uder Scalable Hashing 他是一种数据分布式算法，类似于一致性哈希算法，用于为RADOS存储集群控制数据分布。 客户端IO的简要工作流程 存取对象时，客户端从Ceph监视器检索出集群运行图，绑定到指定的存储池，并对存储池上PG内的对象执行IO操作。\n存储池的CRUSH规则集和PG的数量是决定Ceph如何放置数据的关键性因素 基于最新版本的集群运行图，客户端能够了解到集群中的所有监视器和OSD以及他们各自的当前状态。 不过，客户端对目标对象的位置却一无所知。 执行对象的存取操作时，客户端需要输入的是和对象标识和和存储池名。至于存储到哪个PG、被映射到哪个OSD则是由算法来完成的。\n客户端需要在存储池中存储命名对象时，他将对象名称、对象名册的hash码、存储池中的PG数量和存储池名称作为输入，而后由CRUSH计算出PG的ID及此PG的主OSD。 通过将对象的标识进行一致性hash运算得到的哈希值与PG位图掩吗进行“与”运算得到目标PG，从而得出目标PG的ID（pg_id），完成有Object至PG的映射。 而后，CRUSH算法便将以此pg_id、CRUSH运行图和归置规则（Placement Rules）为输入参数再次进行计算，并输出一个确定且有序的目标存储向量列表（OSD列表），从而完成从PG至OSD的映射。 Ceph客户端使用以下步骤来计算PG ID\n客户端输入存储池名称及对象名称。例如pool = pool1以及object-id = obj1 获取对象名称并通过一致性hash算法对其进行hash运算，即hash(o)，其中o为对象名称 将计算出的对象标识hash码与PG位图掩吗进行“与”运算获得目标PG的标识符，即PG ID，例如1701 计算公式为pgid=func(hash(o)\u0026amp;m,r)其中，变量o是对象标识符，变量m是当前存储池中PG的位图掩吗，变量r是指复制因子，用于确定目标PG中OSD数量。 CRUSH根据集群运行图计算出与目标PG对应的有序的OSD集合，并确定出其主OSD 客户端渠道存储池名称对应的数字表示，例如存储池“pool1”的数字表示11 客户端将存储池的ID添加到PG ID，例如，11.1701 客户端通过直接与PG映射到的主OSD通信来执行诸如写入、读取或删除之类的对象操作。 ","permalink":"https://www.161616.top/ch08-1-ceph-crush/","summary":"关于存储池 从某种意义上来讲，RADOS所提供的存储空间的管理接口，不应该将其放置在同一个平面当中，因此将其切割成多个不同的\u0026quot;逻辑存储空间\u0026quot;，称之为存储池。\nRADOS存储集群提供的基础存储服务需要由\u0026quot;存储池（pool）\u0026ldquo;分割为逻辑存储区域，此类的逻辑区域亦是对象数据的名称空间。\n实践中，管理员可以为特定的应用程序存储不同类型数据的需求分别创建专用的存储池，例如rbd存储池，rgw存储池等，也可以为某个项目或某个用户创建专有的存储池。 存储池还可以再进一步细分为一至多个名称空间（namespace）。同一个存储池内，无论属于哪个、哪些名称空间，数据都是被存储池中的PG进行存放，虽然处于不同名称空间，但可能处于同一个PG之上。 客户端(包括rbd和rgw等）存取数据时，需要事先指定存储池名称、用户名和密钥等信息完成认证，而后将一直维持与其指定的存储池的连接，于是也可以吧存储池看做是客户端的IO接口。 存储池类型\n副本池（replicated）：任何一个数据对象存储在此类存储池中其冗余机制是通过创建多个数据对象副本来实现的，而副本数量是用户在创建存储池时指定。如，创建存储池时没指定类型，就是副本池，默认副本数量为3个（1主两从），统称副本数量。把每个对象在集群中存储为多个副本，其中存储于主OSD的为主副本，副本数量在创建存储池时由管理员指定；副本池类型为Ceph为默认的存储池类型。但是此存储池是非常浪费存储空间的。副本池对读IO有很好的附带表现 纠删码池（erasure code）：使用校验码可计算回数据。把各对象存储为N=K+M个块，其中，K为数据块数量，M为编码块数量，因此存储池的尺寸为K+M；纠删码块的数据就 是允许冗余的级别。如4+2，即允许最多两个数据块丢失。不是所有的应有都能支持纠删码池，如rbd必须使用副本池。radosGW可以使用纠删码池。 副本池IO\n将一个数据对象存储为多副本 写入操作时，Ceph客户端使用CRUSH算法来计算对象的PG ID和Primary OSD 主OSD根据设定的副本教、对象的名称、存储池名称和集群运行图（Cluster Map）计算出PG的各铺助OSD，而后由主OSD将数据同步给这些辅助OSD。 对于有着三个副本的存储池来讲，任何一个PG都会选择三个OSD，因此，副本池所关联的OSD数量通常与冗余量相同。OSD，成为一个活动集。如图所示，其中一个OSD为主OSD负责读写操作，另外两个OSD负责从主OSD同步数据。当三个副本都存完，才能的到存储完成的消息的。客户的只需与主OSD通信，同步过程是OSD内部自行实现的。\n纠删码池IO\n纠删码是一种前向纠错码（FEC）代码\n通过将K块的数据转换为N块，假设N=K+M，则其中的M代表纠删码算法添加的额外活冗余的块数量以提供冗余机制（即编码块），而N则表示在纠删码编码之后要创建的块的总数，其可以故障的块数为M（即N-K）个。 类似于RAID5 纠删码池减少了确保数据持久性所需的磁盘空间量，但计算量上却比副本存储池要更贵一些 RGW可以使用纠删码池，但RDB不支持。\n例如，把包含数据ABCDEFGHI的对象NYAN保存到存储池中，假设纠删码算法会将内容分割为三个数据块：第一个包含ABC，第二个为DEF，最后一个为GHI，并为这三个数据块额外创建两个编码块：第四个YXY和第五个GQC，此时纠删码算法会通过计算哪家出GHI、和YXY\n副本池所有从OSD没有次序之分，只有主和从两类角色之分，各个从没有次序。纠删码池是有次序的，这个顺序代表数据拼凑起来的数据。因此，每个分片应指定其在整个数据文件的偏移量是多少。\n归置组 归置组（PlacementGroup）是用于跨OSD将数据存储在某个存储池中的内部数据结构。\n相对于存储池来说，PG是一个虚拟组件，它是对象映射到存储池时使用的虚拟的层 出于规模伸缩及性能方面的考虑，Ceph将存储池细分为归置组，吧每个单独的对象映射到归置组，并将归置组分配给一个主OSD 存储池由一系列的归置组组成，而CRUSH算法则根据集群运行图和集群状态，将各PG均匀、伪随机地分布到急群众的OSD之上。 若某OSD失败或需要对集群进行重新平衡，Ceph则移动或复制整个归置组而无需单独寻址每个对象。 归置组在OSD守护进程和Ceph客户端之间生成了一个中间层，CRUSH算法负责将每个对象动态映射到一个归置组，然后再将每个归置组动态映射到一个或多个OSD守护进程，从而能够支持在新的OSD设备上线时动态进行数据重新平衡。\n归置组作用\n在存储池中存放100w数据对象，而使用100个归置组，一组内存放1w对象。归置组是从新平衡和恢复时的基本单元。使得数据单元不至于以文件或对象为单位。\n归置组计数 归置组的数量有管理员在创建存储池是指定，而后由crush负责创建和使用\n通常，PG的数量应该是数据的合理粒度的子集 例如，一个包含256个PG的存储池意味着每个PG包含大约1/256的存储池数据 当需要将PG从一个OSD移动到另一个OSD时，PG的数量会对性能产生影响 PG数量过少，Ceph将不得不同时移动相当数量的数据，其产生的网络负载将对集群的正常性能输出产生负面影响。 而在过多的PG数量场景中在移动极少量的数据时，Ceph将会占用过多的CPU和RAM，从而对集群的计算资源产生负面影响。 PG数量在集群分发数据和重新平衡时扮演着重要作用 在所有OSD之间进行数据持久存储及完成数据分布会需要较多的归置组，但是它们的数量应该减少到最大性能所需的最小数量值，以节省CPU和内存资源 一般说来，对于有着超过50个OSD的RADOS集群，建议每个OSD大约有50-100个PG以平衡资源使用，取的更好的数据持久性和数据分布，更大规模的集群中，每个OSD大约可持有100-200个PG 至少应该使用多少个PG，可通过下面的公式计算后，将其值以类似于四舍五入到最近的2的N次幂 (Total OSDs * PGPerOSD/Replication factor =\u0026gt; Total PGs ) 可以使用的PG数量 = 总OSD数量* 每个OSD可以有多少个PG/复制因子（副本数量） 一个RADOS集群上可能会存在多个存储池，因此管理员还需要考虑所有存储池上的PG分布后每个OSD需要映射的PG数量 PG数量一定是2的N次方倍，这样进行hash计算时，速度才会更快。Ceph要求每一个OSD上最多不能超过256个PG 归置组状态 依据PG当前的工作特性活工作进程所阶段，它总是处于某个活某些个“状态中”，最常见的状态应该为active+clean\nPG的常见状态\nActive 主OSD和各辅助OSD均处于就绪状态，可正常服务于客户端IO请求 一般Peering操作过程完成后即会转入Active状态 Clean 主OSD和各辅助OSD均处于就绪状态，所有对象的副本数量均符合期望，并且PG的活动集和上行集为同一组OSD。 活动集(Acting Set)：由PG当前的主OSD和所有的处于活动状态的辅助OSD组成，这组OSD负责执行此PG上数据对象的存取操作I/O 上行集(Up Set)，根据CRUSH的工作方式，集群拓扑架构的变动将可能导致PG相应的OSD变动活扩展至其他的OSD之上，这个新的OSD集也成为PG的“（Up Set）”，其映射到的新OSD集可能不分地与原有OSD集重合，也可能会完全不相干；上行集OSD需要从当前的活动集OSD上复制数据对象，在所有对象同步完成后，上行集便成为新的活动集，而PG也将转为“活动（active）”状态。 Peering 如数据不一致，需将数据复制过去，这个复制数据过程就称之为对等过程。 一个PG中的所有OSD必须就它们持有的数据对象状态达成一致，而“对等（Peering）”即为其OSD从不一致转为一致的过程。 Degraded 在某OSD标记为“down”时，所有映射到此OSD的PG即转入“降级（degraded）”状态 此OSD重新启动并完成Perring操作后，PG将重新转回clean 一旦OSD标记为down的时间超过5分钟，它将被标记出集群，而后Ceph将对降级状态的PG启动回复操作，直到所有因此而降级的PG重回clean状态 在其内部OSD上某对象不可用活悄然崩溃时，PG也会被标记为降级状态，知道对象从某个权威副本上正确恢复。 Stale 过期 每个OSD都要周期性的向RADOS集群中的监视器报告其作为主OSD所持有的所有PG的最新统计数据，因任何原因导致某个主OSD无法正常向监视器发送此类报告，或者由其他OSD报告某个OSD已经down掉，则所有以此OSD的PG将立刻被标记为stale状态。 Undersized PG中的副本数少于其存储池定义的个数时即转入undersized状态，回复和回填操作在随后会启动已修复其副本为期望值 Scrubbing 一致性保障的非常重要机制，文件完整性检查 各OSD还需要周期性的检查其所持有的数据对象的完整性，以确保所有对等OSD上的数据一致；处于此类检查过程中的PG便会被标记为scrubbing状态，这也通常被称作light scrubs、shallow scrubs或者simply scrubs。 另外，PG还需偶尔需要进行deep scrubs检查以确保同一对象在相关的各OSD上能按位匹配，此时PG将处于scrubbing+deep状态。 Recovering 恢复 添加一个新的OSD至存储集群中或某OSD宕掉时，PG则由可能会被CRUSH重新映射进而将持有与此不同的OSD集，而这些处于内部数据同步过程中的PG则被标记为recovering状态； Backfilling 回填 新OSD加入存储集群后，Ceph则会进入数据重新均衡的状态，即一些数据对象会在进程后台从现有OSD移到新的OSD之上，此操作过程为backfill。 CRUSH 把对象直接映射到OSD之上会导致二者之间的紧密耦合关系，变动底层OSD就会牵一发而动全身，因此在OSD设备变动时不可避免地对整个集群产生扰动","title":"Ceph算法 - crush"},{"content":"初识Ceph Ceph 是一个开源分布式存储系统系统，它不是一种单一的存储，而是面向云提供一种统一存储平台，包含块存储 RBD, 文件存储 CephFS, 以及对象存储 RGW，这种存储的出现允许用户拜托供应商的绑定，它可以提供块存储到 “云平台”，也可以提供对象存储到 “应用”，并支持理论上的无限扩展性，数千客户端访问 PB 甚至 EB 级别的数据\nSAN VS Ceph 与传统 SAN 存储相比，Ceph 客户端会计算他们所需的数据所在的位置，这消除了存储系统中需要在“中心化查找”的瓶颈。 这使得 Ceph 集群可以在不损失性能的情况下进行扩展。\nCeph 集群架构组成 Ceph 集群核心是 RADOS，而基于 RADOS，构建出多种类型存储，块存储, 文件系统, 对象存储，而一个基础的 Ceph 集群的组件由 \u0026ldquo;Ceph monitor\u0026rdquo; 与 \u0026ldquo;Ceph OSD Daemon\u0026rdquo; 组成\nCeph Monitor（进程名称为 ceph-mon，下文中以 ceph-mon 代表 Ceph Monitor） 维护集群映射的主副本。 ceph集群中的monitor，可确保 ceph-mon 守护进程在失败时的高可用性。客户端从 ceph-mon 检索集群映射的副本。 Ceph OSD Daemon 检查”自身“及”其他“ OSD 的状态并报告给 Monitor。 Ceph 中的常见术语 Application 用于使用 Ceph 集群的任何 Ceph 外部的应用程序\nBlock Device 也称为 “RADOS 块设备” 或 ”RBD“ ，协调基于块的数据存储的工具，Ceph块设备拆分基于块的应用程序数据 成“块”。 RADOS 将这些块存储为对象。 Ceph 块 设备协调这些对象的存储 存储集群。\n也称为 “RADOS Block Device” 或 “RBD”。一种用于协调 Ceph 中基于块的数据的存储的软件。 Ceph 块设备将基于块的应用程序数据拆分为 “Chunk”。 RADOS 将这些块存储为对象。\nChunk 与 Block 是两种不同的概念\nChunk 存储是类似于 Key-Value 存储和对象存储，是一种结构化数据，并固定大小的块\nBlock 通常被提到的上下文是作为硬件接口提供的，通常代表硬件裸设备\n所以说 Block Device 是将数据划分为固定大小的 Chunk，存储在 Block 上。\nMGR (Manager) Ceph Manager 又称为 Ceph Manager Daemon，进程名称为 ceph-mgr, 是与 Ceph Monitoring 一起运行的守护进程，用于提供监视以及与外部监视和管理系统的接口。自 Luminous 版本 (12) 起，ceph-mgr 没有运行的情况下 Ceph 集群无法正常运行。\nMON (Monitor) Ceph Monitor 维护集群状态影视的守护进程，这些“集群状态”包括 Monitor map、Manager map、OSD map 和 CRUSH map。 Ceph 集群必须至少包含三个正在运行的 Monitor，才能实现冗余和高可用性。\nOSD Ceph Object Storage Daemon，又被称为 OSD, 在 “research and industry” 中 OSD 表示 ”对象存储设备“，而 Ceph 社区将 OSD 称为 OSD daemon，用于与逻辑磁盘交互的进程。\nOSD fsid 用于标识 OSD 的唯一标识符。它可以在 OSD 路径中名为 osd_fsid 的文件中找到。术语 “fsid” 与 “uuid” 互换使用\nOSD id 定义 OSD 的 integer，它是在创建每个 OSD 期间由监视器生成的。\nHybrid OSD 指同时拥有 HDD 和 SSD 的 OSD\nCluster Map 由 monitor map、OSD Map、PG Map、MDS Map 和 CRUSH Map 组成的一组 Map，它们共同报告 Ceph 集群的状态。有关详细信息。\nCRUSH CRUSH Controlled Replication Under Scalable Hashing 可扩展散列下的受控复制，Ceph 用于计算对象存储位置的算法。\nDAS DAS Direct-Attached Storage 直接附加存储，无需访问网络直接连接计算机的存储。例如 SSD\nLVM tags Logical Volume Manager tags 逻辑卷管理器标签，LVM “卷” 和 “组” 的可扩展元数据。它们用于存储有关设备及其与 OSD 关系的 Ceph 特定信息。\nPGs (Placement Groups) “放置组” 是每个逻辑 Ceph Pool 的子集。放置组执行将对象（作为一个组）放置到 OSD 中的功能。 Ceph 在内部以“放置组粒度”来管理数据：这比管理单个RADOS 对象的扩展性将更好。具有较大数量放置组的集群比具有较少数量放置组的其他相同集群具有更好的平衡性。\nPools 池是用于存储对象的逻辑分区。\nRADOS Reliable Autonomic Distributed Object Store 可靠的自动分布对象存储，RADOS 是为可变大小的对象提供可扩展服务的对象存储。 RADOS 对象存储是 Ceph 集群的核心组件。\nBlock Storage 块存储是 Ceph支持的三种存储类型之一。 Ceph 块存储指的是块存储 结合使用时的相关服务和功能 集合\nCeph File System Ceph File System (CephFS) 是一个兼容 POSIX 的文件系统，构建在 RADOS 之上，可根据按需部署\nMDS (Metadata Server) Ceph MetaData Server daemon MDS，构建在 RADOS 之上，存储所有文件的元数据作为”文件系统“类型的存储提供给用户，运行的程序名为 ceph-mds，故 也是是否使用 CephFS 的标记\nRGW (Radow Gateway) Ceph 提供兼容 Amazon S3 RESTful API 和 OpenStack Swift API 的组件，可根据按需部署\nRealm 是位于对象存储中的上下文，领域 (Realm) 是一个全局唯一的命名空间，由一个或多个区域组组成。\nZone 是位于对象存储中的上下文，区域 (zone) 是由一个或多个 RGW 实例组成的逻辑组。\u0026ldquo;zone\u0026rdquo; 的配置状态存储在 \u0026quot;\u0026quot; 中\nPeriod 是位于对象存储中的上下文，Period 是 Realm 的配置状态。该 Period 存储多站点配置的配置状态。当 Period 被更新时，“epoch” 被认为已经改变。\nCephX CephX Ceph authentication protocol；CephX 是用于对用户和守护进程进行身份验证。 CephX 的运行方式类似于 Kerberos，但它没有单点故障。\nSecrets Secret 是用户访问是需要提供的身份验证的系统用于执行数字身份验证的凭据。\n存储上下文中的锁定是您能够与硬件连接的最小尺寸。每当您从磁盘读取或写入磁盘时，无论需要读取多少个块，您都会读取此数量的次数。默认 NTFS 块大小（也称为簇大小、分配单元）为 4096 字节 (4KB)。如果您有一个正好 4096 字节长的文件，那么您将从磁盘读取一个块。如果是 4097 字节，那么您会读取两个块。您无法读取部分块，因此即使文件实际上没有消耗整个块，存储文件系统也会清空该块的其余部分。查看实际情况的一个简单方法是在硬盘驱动器上创建一个空白文本文件，查看属性以及“大小”（0 字节）和“磁盘大小”（4096 字节）之间的差异。\nCeph是一个对象（object）式存储系统，它把每一个待管理的 在（例如一个文件）切分为一到多个固定大小的（ceph底层管理机制）对象数据，在Ceph之上，每一个对象，是一个基础的原子管理单元（不可再切分的单元），并以其为原子单元完成数据存取\n对象数据的底层存储服务是由多个主机（host）组成的存储集群，该集群也就称之为RADOS（Reliable Automatic Distributed Object Store）存储集群，即可靠、自动化、分布式对象存储系统。 librados是RADOS存储集群的API，它支持C、C++、Java、Python、Ruby和PHP等编程语言。 存储设备\nDAS 直接附加存储：IDE、SATA、SCSI、SAS、USB NAS 网络附加存储（文件系统级别）：NFS、CIFS SAN 存储区域网络，与NAS不同的是，SAN提供的接口是块级别的接口。多数使用SCSI FC SAN ISCSI（internet SCSI） 数据是由元数据和内容（数据组成）组成，而元数据是确保路由的。传统文件系统ext有inode信息存储在元数据区，一部分空间存元数据，一部分空间存数据。数据称为数据块 data block。 当客户端试图访问某个文件时，根据给定文件路径或文件名层层查找，查询inode表。inode中记录的有在数据块空间中那些编号块存放当前文件数据。\n元数据找到关联数据的存储路由表，也记录了当前文件的属性信息、如权限、属组等。这是一个标准的POSIX文件系统所应该具有的基本兼容的属性。\n如将数据分散到多个节点上去存储时，就不能按照传统的机制在一个分区上组织元数据和数据，我们只能讲数据和元数据分开存放。每一个节点只提供存储空间，元数据存放在固定节点。\n在存数据时，客户端将请求按照指定大小规模，每一块当做一个独立的文件，然后进行路由和调度，从而完成分散存储的目的。从而利用多个节点的网络和磁盘IO的存储能力。当用户访问时，需要联系元数据服务器，由元数据服务器返回相关数据 信息后从并行从这些节点上加载到数据块，而后根据元数据给出逻辑，由客户端组合起来，就可得到完整数据。\n元数据和数据服务器分离在不通额主机之上，还要完成服务器的角色划分。在传统以Google为代表的设计体系结构当中，存放元数据的服务器被称之为名称服务器（NameNode）。存放数据的服务器被称为DataNode。\n文件系统的元数据是一类非常密集、但IO量非常小的数据。因此为了高性能、高效通常需要存储在内存当中。为此需要一种机制同步存储在磁盘上，当文件被修改元数据也会被修改。大量的修改操作都是随机的。随机IO通常会非常慢。为了能够高效的使非常密集的文件元数据的修改操作能够快速高效的存在磁盘上，一般不会直接修改元数据的。而是将修改操作的操作请求记录下来。\n将随机IO转成顺序IO能够快速同步磁盘进行持久保存。此方法缺点在宕机恢复时，只能通过回放记录在文件中的指令才能将数据还原回来。故其速度很慢。\n因此将所有数据的持久存储都存放在第三方存储服务上，\n对于数据存储级别的高可用思路\n节点级冗余 数据级冗余 对每一个块 做副本 分片级冗余 primary shard replica shard HDFS Hadoop FileSystem\n对象存储 每一文件对象，在存储文件时，每一个对象自身大小不固定，是按照文件自身大小来存储的。不区分数据与元数据。每一个数据流自带数据和元数据。数据和数据流是存放在一起的。因此，每一个数据流就叫一个数据对象。数据是直接存放在磁盘之上的，而不在区分数据和元数据。每一对象都有自己自我管理的格式。\nCeph在存数据时，为了避免有一个元数据中心服务器成为整个系统的瓶颈所在，采用了计算方式来解决问题。在存储文件时，对文件做Hash计算映射到对应节点上去。Ceph没有中心节点，没有元数据服务器，任何对象存取都是通过实时计算得到的\n从根本上来讲，Ceph在底层是RADOS的存储集群，由多个节点组成。其存储服务只是API叫做librados，Ceph在接口之上提供了几个抽象接口以便可以在传统意义上使用存储服务的逻辑实现存储功能。除用户自己在librados之上还开发了3个接口\ncephfs RDB 将Ceph所提供的存储空间模拟成一个个块设备使用的，每个块设备成为一个image，相当于磁盘。块接口，相当于传统硬盘 RADOSGW 更抽象的，能跨互联网使用的对象存储。与Ceph内部对象不一样。Ceph内部的对象是固定大小的存储块，通常只在Ceph集群中使用，基于RPC协议工作。基于互联网的云存储，是基于resetful风格的接口提供的文件存储。每一个文件是一个对象，文件大小各不相同。 crush Ceph内部通过计算方式完成对象路由的计算综合性算法。\nCeph存储集群组成 Ceph存储集群，一般可归置为几个组件组成\nosd object stroage device 对象存储设备 每个主机上有多个osd，每一个osd通常是指==单独的磁盘设备==。osd是真正存放数据的地方。如果使用files会是一个目录。对象存储设备而不是主机。多个主机组合起来称为RADOS Cluster（RADOS存储集群）\n为了使每个osd能够被单独使用和管理，每个osd都会有一个单独的专用的守护进程ceph-osd。osd本身用来存储数据，还包括数据复制、恢复、数据重新均衡、提供监视信息给mon和mgr\n一个集群至少有3个Ceph OSDs，已确保高可用。选择OSD冗余时，应自己指定故障率，故障转移率或故障容忍率。OSD级别故障就在OSD级别冗余，主机级别就跨主机冗余，故障率是机架级别就机架冗余。 在做crush运行图的设定时是应该自己指定的。\nmonitor 集群元数据服务器 集群内除了存储节点之外，还有另外一种节点 monitor （monitor监视器），用来管理整个集群的，如有多少个节点，每个节点上有多少个OSD，每个OSD是否健康，他会持有整个集群的运行图（运行状态）。mon是用来集中维护集群元数据而非文件元数据。为了维护整个集群能够正常运行而设定的节点，离开此节点集群内部就无法协调。\n在一个主机上运行的守护进程 ceph-mon，守护进程扮演、监视着整个集群所有组件的角色，被称为集群运行图的持有者cluster map（整个集群有多少存储池，每个池中有多少PG，每个PG映射哪个OSD，有多少OSD等等）集群运行图 Cluster Map\nmonitor负责维护整个进群的认证信息并实行认证，认证协议叫==CephX==协议（ceph内部的认证协议）。monitor用来维护认证信息并实行认证。认证中心 monitor自身是无状态的，所以实现均衡认证负载。\nmonitor的高可用自己内部直接使用POSIX协议来实现数据冗余，monitor也是节点级冗余，为了确保各节点数据是强一致的，每个节点都可写，写完后会同步到其他节点。为了避免同时写导致的冲突，使用了分布式一致性协议，monitor就是使用POSIX协议来进行分布式协作的。\nmgr manager monitor在每一次读数据都是实时查询的，故monitor不适用频繁周期性采集数据的监控操作。在Ceph新版中引入新组建mgr，（早期Ceph版本是没有mgr的）用来专门维护查询类操作，将查询操作按照自己内部空闲方式缓存下来，一旦有监控可及时响应。\nmgr是在一类节点上运行的守护进程，一般为两个活以上节点，此类守护进程被称为ceph-mgr。主要功能在于跟踪运行时的指标数据。如磁盘使用率、CPU使用率。以及集群当前状态，此状态不是内部运行状态，而是查询做监控时的状态。包括存储空间利用率、当前性能指标、节点负载（系统级）等\nmds (非必要组件) ==Ceph Metadata Server== 用来代表ceph文件系统而提供的守护进程ceph-mds，如不使用CephFS，此进程是无需启动的。利用底层RADOS存储空间，将存储空间抽象成文件系统，来兼容POSIX file system 提供服务。\nmgr ods mon才是一个完整的RADOS Cluster\n管理节点 Admin Host Ceph通常是分布式集群，为了便于去管理维护整个集群，通常在Ceph集群中找一个专门的节点用来当管理节点。此节点可以连接至每一个节点用来管理节点上的Ceph守护进程。\nCeph的场馆用管理接口是一句命令行工具程序，例如rados ceph rbd等命令，管理员可以从某个特定的MON节点执行管理操作，单也有人更倾向于使用专用的管理节点\n事实上，专用的管理节点有助于在Ceph相关的程序升级或硬件维护期间为管理员提供一个完整的、独立的并隔离于存储集群之外的操作系统，从而避免因重启意外中断而导致维护操作异常中断。\npool Ceph把他所提供的存储空间（没有目录之类一说）所有对象都是存储在数据平面上，因此，所有对象都不能同名。RADOS将他的存储空间切分为多个分区以便好进行管理。每一个分区叫做一个存储池。存储池的大小是取决于底层的存储空间的。与真正意义上的分区不是一回事。在Ceph中每一个存储池存放的数据了也可能会太大，所以存储池也可以进一步划分（可选）。被称为名称空间（先切分为存储池，每个存储池可进一步被划分成名称空间） 两级逻辑组件。\n第三级 PG 每一个存储池内部会有多个PG（placement groups 规置组）存在。pool与pg都是抽象的概念。\nobject 对象是自带元数据的组件，\n对象id，每一个对象应有一个对象ID在集群内部来引用对象 数据 元数据 key vlaue类型的数据 这些类型打包成一起存储，被称为一个对象。RADOS集群会吧真正存储的每一个文件，切分成N个对象来进行存储的。（切分个数与默认对象切分大小有关）。\n每一个对象都是被单独管理的，都拥有自己的标识符。因此， 同一个文件的object有可能被映射到不同的PG上，PG提交给主机的，由主机负责将对象存储在磁盘（OSD）被存储在不同的OSD上。\n文件存储到RADOS集群 一般要接入RADOS集群必须通过客户端来实现(LIBRADOS、RBD、CephFS、RADOSGW)，才能接入到集群中来。\n当将文件存入Ceph中时，需要通过某一类客户端接入，客户端接入时，需要借助于Ceph存储API接口将其切分为固定大小的存储对象(Date object)，此数据对象究竟被放置在哪个OSD上存放这中间是靠crush来完成的。数据对象被存放在哪个存储池上是固定的。存储池需创建才可使用。但PG是虚拟的中间层。\nPG作用\ncrush算法第一步 任何一个对象被存进RADOS集群中时，一定是向某一个存储池请求的。而后将对象的名字做一致性哈希计算（系列计算），计算完之后，通过顺时针找到落在最近PG。对象是归类在PG中的。每一个对象一定属于某个存储池内的某个PG（但PG不是真实存在的）。接下来还需将PG存到OSD上。\ncrush算法第二步 需将PG根据存储池的冗余副本数量和存储池的类型找到足量的OSD来存。是以PG为单位进行管理的，所以被称为主PG或活动PG和副本PG，一个PG中的所有对象是统一被管理的（存放在同一个OSD），如果要基于副本管理，写需写入主OSD，由主OSD同步到从OSD之上（同步过程是OSD自己内部管理）。但存储池和crush算法一定要能够确定出哪个是主的那些是从。一般来讲，传统的存储池是1主2从的存储。故其空间利用率是极低的。\nCeph也支持另外的存储池，纠删码存储池，类似于RAID5。存储数据时也会选出多个OSD来，多个OSD不是做副本方式存储，而是将数据切成多块之后每一个OSD上存一部分，另外一些OSD存放校验码，任何一个OSD坏了可以用校验码来计算出来。\n故Ceph整体使用分为两部 将对象映射给PG，将PG映射给OSD 此过程是由crush算法来完成的。\n最核心的组建RADOS Cluster以对象化的方式吧每一个文件切分成固定大小对象基于对象进行管理的。每一个对象要被基于crush算法实时计算之后映射到集群中某一个OSD之上。而每个集群上有多少个OSD，各个处于什么状态是由monitor负责维护和管理的，甚至监视器要维护整个集群的状态，如OSD、PG等等。故monitor是整个集群的元数据服务器。而不是文件元数据服务器。Ceph没有文件元数据服务器。所有数据访问都是通过实时计算路由的。因此整个集群可以无限制扩展\n集群运行图 数据抽象接口（客户端中间层） Ceph存储集群提供了基础的对象数据存储服务，客户端可基于RADOS协议和librados API直接与存储系统交互进行对象数据存取 Librados Librados提供了访问RAD0OS在存储集群支持异步通信的API接口，支持对集群中对象数据的的直接并行访问，用户可通过支持的编程语言开发自定义客户端程序通过RADOS协议与存储系统进行交互 客户端应用程序必须与librados绑定方可连接到RADOS存储集群，因此，用户必须实现安装librados及其依赖后才能编写使用librados的应用程序。 librados API本身使用C++编写的，它额外支持C、Python、Java、和PHP等开发接口 当然，并非所有用户都有能力自定义开发接口以接入RADOS存储集群的需要，为此，Ceph也原生提供了几个较高级别的各户端接口，它们分别是RADOS GateWay（RGW）、Reliable Block Device（RBD）和MDS（MetaData Server），分别为用户提供RESTful、块和POSIX文件系统接口。 ","permalink":"https://www.161616.top/ch01-2-cloud-base/","summary":"初识Ceph Ceph 是一个开源分布式存储系统系统，它不是一种单一的存储，而是面向云提供一种统一存储平台，包含块存储 RBD, 文件存储 CephFS, 以及对象存储 RGW，这种存储的出现允许用户拜托供应商的绑定，它可以提供块存储到 “云平台”，也可以提供对象存储到 “应用”，并支持理论上的无限扩展性，数千客户端访问 PB 甚至 EB 级别的数据\nSAN VS Ceph 与传统 SAN 存储相比，Ceph 客户端会计算他们所需的数据所在的位置，这消除了存储系统中需要在“中心化查找”的瓶颈。 这使得 Ceph 集群可以在不损失性能的情况下进行扩展。\nCeph 集群架构组成 Ceph 集群核心是 RADOS，而基于 RADOS，构建出多种类型存储，块存储, 文件系统, 对象存储，而一个基础的 Ceph 集群的组件由 \u0026ldquo;Ceph monitor\u0026rdquo; 与 \u0026ldquo;Ceph OSD Daemon\u0026rdquo; 组成\nCeph Monitor（进程名称为 ceph-mon，下文中以 ceph-mon 代表 Ceph Monitor） 维护集群映射的主副本。 ceph集群中的monitor，可确保 ceph-mon 守护进程在失败时的高可用性。客户端从 ceph-mon 检索集群映射的副本。 Ceph OSD Daemon 检查”自身“及”其他“ OSD 的状态并报告给 Monitor。 Ceph 中的常见术语 Application 用于使用 Ceph 集群的任何 Ceph 外部的应用程序\nBlock Device 也称为 “RADOS 块设备” 或 ”RBD“ ，协调基于块的数据存储的工具，Ceph块设备拆分基于块的应用程序数据 成“块”。 RADOS 将这些块存储为对象。 Ceph 块 设备协调这些对象的存储 存储集群。","title":"Cloud基础设施 - 初识Ceph"},{"content":"exporter是一个独立运行的采集程序，其中的功能需要有这三部分\n自身是HTTP服务器，可以相应从外部发过来的HTTP GET请求。 自身需要运行在后台，并可以定期触发抓取本地的监控数据。 返回给prometheus_server的内容是要符合prometheus规定的metrics类型的key-Value promethes监控中对于采集过来的数据统一称为metrice数据\nMetrics，为某个系统某个服务做监控、做统计，就需要用到Metrics.\nmetrics是一种对采样数掘的总称（metrics并不代表某一种具体的数据格式是一种对于度星计算单位的抽象）\nmetrics的几种主要的类型\nMETRIC TYPES prometheus客户端库提供4中metric类型\ncounter 计数器，累加指标 gauge 测量指标 summary 概略 histogram 直方图 counter Counter计数器，累加的指标数据，随时间逐步增加，如程序运行次数、运行错误发生总数。如网卡流量，代表持续增加的数据包或者传输字节的累加值\n比如对用户访问量的采样数据\n我们的产品被用户访问一次就是1过了10分钟后积累到100\n过一天后累积到20000\n一周后积累到100000-150000\ngo 1 2 3 4 5 6 7 8 test = prometheus.NewSummaryVec( prometheus.SummaryOpts{ Name: \u0026#34;zhangsan\u0026#34;, Help: \u0026#34;username\u0026#34;, Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, }, []string{\u0026#34;service\u0026#34;}, ) text 1 2 3 4 5 6 7 # HELP zhangsan username # TYPE zhangsan summary zhangsan{service=\u0026#34;aaa\u0026#34;,quantile=\u0026#34;0.5\u0026#34;} 0.4933459627210085 zhangsan{service=\u0026#34;aaa\u0026#34;,quantile=\u0026#34;0.9\u0026#34;} 0.884503005897664 zhangsan{service=\u0026#34;aaa\u0026#34;,quantile=\u0026#34;0.99\u0026#34;} 0.9939536606026 zhangsan_sum{service=\u0026#34;aaa\u0026#34;} 3145.830341777395 zhangsan_count{service=\u0026#34;aaa\u0026#34;} 6308 在每次执行后值会累加\ntext 1 2 3 4 5 6 7 # HELP zhangsan username # TYPE zhangsan summary zhangsan{service=\u0026#34;aaa\u0026#34;,quantile=\u0026#34;0.5\u0026#34;} 0.4933459627210085 zhangsan{service=\u0026#34;aaa\u0026#34;,quantile=\u0026#34;0.9\u0026#34;} 0.884503005897664 zhangsan{service=\u0026#34;aaa\u0026#34;,quantile=\u0026#34;0.99\u0026#34;} 0.9945251964629818 zhangsan_sum{service=\u0026#34;aaa\u0026#34;} 3165.232975252529 zhangsan_count{service=\u0026#34;aaa\u0026#34;} 6347 gauge 最简单的度量指标，只有一个简单的返回值，或者叫瞬时状态，例如，我们想衡量一个特处理队列中任务的个数。\n用更简单的方式举个例子\n例如：如果我要监控覆盘容量或者内存的使用量，那么就应该使用Gauges的metrics格式来度量，因为硬盘的容量或者内存的使用量是随着时间的推移不断的瞬时，没有规则的变化。这种变化没有规律，当前是多少，采集回来的就是多少，既不能肯定是一直持续增长，也不能肯定是一直降低，是多少就是多少这种就是Gauges使用类型的代表。\n如图所示CPU的上下厚动就是采集使用Gauge形式的metrics数据，没有规律是多少就得到多少，然后显示出来。\ngauge代表采集的是一个单一的数据，此数据可增可减，如内存使用情况，cpu使用情况等。\ngo 1 2 3 4 gaugetest = prometheus.NewGauge(prometheus.GaugeOpts{ Name: \u0026#34;lisi\u0026#34;, Help: \u0026#34;password\u0026#34;, }) text 1 2 3 # HELP lisi password # TYPE lisi gauge lisi 0.4578056215001356 text 1 2 3 # HELP lisi password # TYPE lisi gauge lisi 0.44668242347036363 https://github.com/lwhile/workDoc\nsummary 概略 histogram 比例型的估算数值 Histogram 统计数据的分布情况。比如最小值，最大值，中间值，还有中位数，75百分位，90百分位，95百分位，98百分位，99百分位，和99.9百分位的值（percentiles）。\n这是一种特殊的metrics数据类型，代表的是一种近似的百分比估算数值\n比如我们在企业工作中经常接触这种数据\nHttp.response_time HTTP响应时间\n代表的是一次用户HTTP请求在系统传输和执行过程中总共花费的时间\nnginx中的也会记录这一项数值在日志中\n那么问题来了\n我们做一个假设\n如果我们想通过监控的方式抓取当天的nginx accoss.log，并且想监控用户的访问时间我们应该怎么做呢？同学们肯定很容易想到 简单制把日志每行的http_response_time 数值统统采集下来期然后计算一下总的平均值即可。那么假如我们采集到今天一天的访问量是100万次然后把这100万次的http_response_time 全都加一超然后除以100万最后得出来一个值\n0.05秒=50毫秒\n这个数据的意文大么？\n假如今天中午1：00的时候发生了一次线上故障系统整体的访问变得非常缓慢大部分的用户请求时间都达到了0.5~1秒作用但是这一段时间只持续了5分钟，总的一天的平均值并不能表现得出来我们如何在 1:00 ~ 1:05 的时候实现报警呢？\n在举个例子：\n就算我们一天下来线上没有发生故障大部分用户的响应时间都在0.05秒（通过总时间/总次数得出）但是我们不要忘了任何系统中都一定存在慢请求就是有一少部分的用户请求时间会比总的平均值大很多甚至接近5秒10秒的也有（这种情况很普遍因为各种因素可能是软件本身的bug也可能是系统的原因更有可能是少部分用户的使用途径中出现了问题）\n那么我们的监控需要发现和报警这种少部分的特殊状况，用总平均能获得吗？\n如果采用总平均的方式，那不管发生什么特殊情况，因为大部分的用户响应都是正常的你永远也发现不了少部分的问题所以Histogram的metrics类型在这种时候就派上用场了通过histogram类型（prometheus中其实提供了一个基于histogram算法的函数可以直接使用）可以分别统计出全部用户的单应时间中 ~=0.05秒的量有多少0 ~ 0.05秒的有多少，\u0026gt;2秒的有多少\u0026gt;10秒的有多少\n我们就可以很清晰的看到当前我们的系统中必于基本正常状态的有多少百分比的用户（或者是请求）多少处于速度极快的用户，多少处于慢请求或者有问题的请求\nmetrics的类型其实还有另外的类型\n但是在我们大米运维的课程中我们最主要使用的就是counter ganga 和 histogram\nkv的数据形式 对于采集回来的数据类型再往细了说必须要以一种具体的数据格式供我们查看和使用那么我们来看一下一个exporter 给我们果集来的服务器上的k/v形式metrics数据当一个exporter被安装和运行在被监控的服务器上后，使用简单的curl命令就可以看到exporer帮我们采集到metrics数据的样子，以k/v的形式展现和保存\nbash 1 curl localhost:9100/metrics ","permalink":"https://www.161616.top/prometheus-golang_client/","summary":"exporter是一个独立运行的采集程序，其中的功能需要有这三部分\n自身是HTTP服务器，可以相应从外部发过来的HTTP GET请求。 自身需要运行在后台，并可以定期触发抓取本地的监控数据。 返回给prometheus_server的内容是要符合prometheus规定的metrics类型的key-Value promethes监控中对于采集过来的数据统一称为metrice数据\nMetrics，为某个系统某个服务做监控、做统计，就需要用到Metrics.\nmetrics是一种对采样数掘的总称（metrics并不代表某一种具体的数据格式是一种对于度星计算单位的抽象）\nmetrics的几种主要的类型\nMETRIC TYPES prometheus客户端库提供4中metric类型\ncounter 计数器，累加指标 gauge 测量指标 summary 概略 histogram 直方图 counter Counter计数器，累加的指标数据，随时间逐步增加，如程序运行次数、运行错误发生总数。如网卡流量，代表持续增加的数据包或者传输字节的累加值\n比如对用户访问量的采样数据\n我们的产品被用户访问一次就是1过了10分钟后积累到100\n过一天后累积到20000\n一周后积累到100000-150000\ngo 1 2 3 4 5 6 7 8 test = prometheus.NewSummaryVec( prometheus.SummaryOpts{ Name: \u0026#34;zhangsan\u0026#34;, Help: \u0026#34;username\u0026#34;, Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, }, []string{\u0026#34;service\u0026#34;}, ) text 1 2 3 4 5 6 7 # HELP zhangsan username # TYPE zhangsan summary zhangsan{service=\u0026#34;aaa\u0026#34;,quantile=\u0026#34;0.5\u0026#34;} 0.","title":"prometheus golang_client开发Exporter"},{"content":"Ubuntu屏幕分辨率无1920 1080 xrandr\n没有1920X1080分辨率，所以手动添加一个1080P分辨率，先输入“cvt 1920 1080”命令，查询一下1080P分辨率的有效扫描频率\n然后使用 xrandr 命令新建一种输出分辨率\nbash 1 sudo xrandr --newmode \u0026#34;1920x1080_60.00\u0026#34; 173.00 1920 2048 2248 2576 10801083 1088 1120 -hsync +vsync bash 1 sudo xrandr --addmode Virtual1 \u0026#34;1920x1080_60.00\u0026#34; Ubuntu 制作图标 进入 /usr/share/applications/下 创建文件\nbash 1 2 3 4 5 6 7 8 9 cylon@cylon-PC:/usr/share/applications$ cat goland.desktop [Desktop Entry] Encoding=UTF-8 Name=Goland Exec=goland.sh Icon=/home/lc/goland/bin/goland.svg Terminal=false Type=Application Categories=Development; 主题目录： /usr/share/themes/\n图标主题目录： /usr/share/icons/\n","permalink":"https://www.161616.top/ubuntu-configration/","summary":"Ubuntu屏幕分辨率无1920 1080 xrandr\n没有1920X1080分辨率，所以手动添加一个1080P分辨率，先输入“cvt 1920 1080”命令，查询一下1080P分辨率的有效扫描频率\n然后使用 xrandr 命令新建一种输出分辨率\nbash 1 sudo xrandr --newmode \u0026#34;1920x1080_60.00\u0026#34; 173.00 1920 2048 2248 2576 10801083 1088 1120 -hsync +vsync bash 1 sudo xrandr --addmode Virtual1 \u0026#34;1920x1080_60.00\u0026#34; Ubuntu 制作图标 进入 /usr/share/applications/下 创建文件\nbash 1 2 3 4 5 6 7 8 9 cylon@cylon-PC:/usr/share/applications$ cat goland.desktop [Desktop Entry] Encoding=UTF-8 Name=Goland Exec=goland.sh Icon=/home/lc/goland/bin/goland.svg Terminal=false Type=Application Categories=Development; 主题目录： /usr/share/themes/\n图标主题目录： /usr/share/icons/","title":"ubuntu相关配置"},{"content":"下载中文字体\nbash 1 apt-get install ttf-arphic-uming xfonts-intl-chinese 替换goland的汉化包，两个jar包。\n","permalink":"https://www.161616.top/deepin-goland/","summary":"下载中文字体\nbash 1 apt-get install ttf-arphic-uming xfonts-intl-chinese 替换goland的汉化包，两个jar包。","title":"deepin下安装goland中文字体显示全是方块"},{"content":"全局配置选项\ntext 1 2 3 4 5 6 7 scrape_interval: 采集生命周期 scrape_timeout: 采集超时时间 evaluation_interval: 告警评估周期 rule_files 监控告警规则 scrape_config: 被监控端 altering 检查配置文件语法\ntext 1 2 3 $ promtool check config \\etc\\prometheus.yml Checking \\etc\\prometheus.yml SUCCESS: 0 rule files found 100 - (node_memory_MemFree_bytes+node_memory_Cached_bytes+node_memory_Buffers_bytes) \\ node_memory_MemTotal_bytes * 100\n计算剩余空间\nnode_filesystem_free_bytes{mountpoint=\u0026quot;\u0026quot;,fstype=~\u0026ldquo;ext4|xfs\u0026rdquo;} \\ node_filesystem_size_bytes{mountpoint=\u0026quot;\u0026quot;,fstype=~\u0026ldquo;ext4|xfs\u0026rdquo;} * 100\n查看使用的百分比\n100-node_filesystem_free_bytes{mountpoint=\u0026quot;\u0026quot;,fstype=~\u0026ldquo;ext4|xfs\u0026rdquo;} \\ node_filesystem_size_bytes{mountpoint=\u0026quot;\u0026quot;,fstype=~\u0026ldquo;ext4|xfs\u0026rdquo;} * 100\nprometheus使用influxdb [Prometheus endpoints support in InfluxDB | InfluxData Documentation](https:\\docs.influxdata.com\\influxdb\\v1.7\\supported_protocols\\prometheus)\n[Configuration | Prometheus](https:\\prometheus.io\\docs\\prometheus\\latest\\configuration\\configuration)\n配置文件参考\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 global: alerting: alertmanagers: - static_configs: - targets: rule_files: scrape_configs: - job_name: \u0026#39;prometheus1\u0026#39; file_sd_configs: - files: [\u0026#39;\\data\\sd_config\\test.yml\u0026#39;] refresh_interval: 5s relabel_configs: - action: replace source_labels: [\u0026#39;prometheous1\u0026#39;] regex: (.*) replacement: $1 target_label: ids - action: keep source_labels: [\u0026#34;job\u0026#34;] - job_name: \u0026#39;k8s_master\u0026#39; file_sd_configs: - files: [\u0026#39;\\data\\sd_config\\master.yml\u0026#39;] refresh_interval: 5s remote_write: - url: \u0026#34;http:\\\\localhost:8086\\api\\v1\\prom\\write?db=prometheus\u0026#34; remote_read: - url: \u0026#34;http:\\\\localhost:8086\\api\\v1\\prom\\read?db=prometheus\u0026#34; influxdb使用\nInfluxDB学习之InfluxDB的基本操作 | Linux大学\n查看所有表\nsql 1 SHOW MEASUREMENTS sql 1 select * from up https:\\github.com\\kubernetes\\kubernetes\\tree\\master\\cluster\\addons\\prometheus\n文件主要包括一下几个部分\nPrometheus的安装 包括 rbac service configmap Prometheus-metrics 获取资源对象 node-exporter 获取工作节点资源信息 alertmanager 告警 安装顺序\nprometheus-rbac.yaml Prometheus访问apiserver的授权 prometheus-configmap.yaml 管理Prometheus的配置文件 prometheus-service.yaml 将Prometheus端口暴漏 prometheus-statefulset.yaml\n原因为 prometheus-statefulset.yaml中的accessModes不能为ReadWriteOnce prometheus\u0026quot;is invalid: spec: Forbidden: updates to statefulset spec for fields other than \u0026lsquo;replicas\u0026rsquo;, \u0026rsquo;template\u0026rsquo;, and \u0026lsquo;updateStrategy\u0026rsquo; are forbidden\n日志中报错pod has unbound immediate persistentvolumeclaims back-off restarting failed container\n错误原因：动态绑定至其他sc上，查看kubectl describe pvc prometheus-data-prometheus-0 -n kube-systempvc中报错storageclass.storage.k8s.io \u0026quot;nfs\u0026quot; not found\nprometheus时间不一致问题\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 spec: containers: - name: myweb image: harbor/tomcat:8.5-jre8 volumeMounts: - name: host-time mountPath: /etc/localtime ports: - containerPort: 80 volumes: - name: host-time hostPath: path: /etc/localtime 部署应用时，单独读取主机的“/etc/localtime”文件，即创建pod时同步时区，无需修改镜像，但是每个应用都要单独设置。\nprometheus server down\n编辑prometheus-statusfullset.yaml 修改其配置localhost 改为 127.0.0.1\ntext 1 time=\u0026#34;2020-11-20T18:44:48+08:00\u0026#34; level=error msg=\u0026#34;subset not found for kube-system/prometheus-server\u0026#34; providerName=kubernetescrd ingress=promserver namespace=kube-system 查找原因kubernetes svc 匹配错误 service endpoint没有匹配到内容\ntext 1 2 3 4 5 6 7 8 9 10 11 12 $ kubectl describe svc prometheus-server -n kube-system Name: prometheus-server Namespace: kube-system Labels: \u0026lt;none\u0026gt; Annotations: Selector: monitor=prometheus Type: ClusterIP IP: 10.110.116.203 Port: \u0026lt;unset\u0026gt; 9090/TCP TargetPort: 9090/TCP Endpoints: \u0026lt;none\u0026gt; Session Affinity: None Events: \u0026lt;none\u0026gt; 修改后正常\n数学理论基础实现的\n配置文件\nyaml 1 - job_name: \u0026#39;prometheus\u0026#39; 首先定义任务名称 prometheus的客户端主要有两种方式采集\npull 主动输影的形式 push 被动推送的形式 put put指的是客户端（被监控机器）先安装各类已有exporters（由社区组积或企业开发的监控客户端插件）在系统上之后，exporter以守护进程的模式运行并开始采集数据\nexporter 本身也是一个htp_server 可以对http请求作出响应返回数据（KV metrics）\nprometheua用pull 这种主动拉的方式（HTTP get）去访问每个节点上exporter并采样回需要的数据\npush： push指的是在客户端（或者服务端）安装这个官方提供的pushgateway插件然后，使用我们自行开发的各种脚本把监控数据组织成K/V的形式，metrics形式发送给pushgateway之后 puahgataway会再推送给prometheus\n这种是一种被动的数据采集模式\n","permalink":"https://www.161616.top/prometheus-install/","summary":"全局配置选项\ntext 1 2 3 4 5 6 7 scrape_interval: 采集生命周期 scrape_timeout: 采集超时时间 evaluation_interval: 告警评估周期 rule_files 监控告警规则 scrape_config: 被监控端 altering 检查配置文件语法\ntext 1 2 3 $ promtool check config \\etc\\prometheus.yml Checking \\etc\\prometheus.yml SUCCESS: 0 rule files found 100 - (node_memory_MemFree_bytes+node_memory_Cached_bytes+node_memory_Buffers_bytes) \\ node_memory_MemTotal_bytes * 100\n计算剩余空间\nnode_filesystem_free_bytes{mountpoint=\u0026quot;\u0026quot;,fstype=~\u0026ldquo;ext4|xfs\u0026rdquo;} \\ node_filesystem_size_bytes{mountpoint=\u0026quot;\u0026quot;,fstype=~\u0026ldquo;ext4|xfs\u0026rdquo;} * 100\n查看使用的百分比\n100-node_filesystem_free_bytes{mountpoint=\u0026quot;\u0026quot;,fstype=~\u0026ldquo;ext4|xfs\u0026rdquo;} \\ node_filesystem_size_bytes{mountpoint=\u0026quot;\u0026quot;,fstype=~\u0026ldquo;ext4|xfs\u0026rdquo;} * 100\nprometheus使用influxdb [Prometheus endpoints support in InfluxDB | InfluxData Documentation](https:\\docs.influxdata.com\\influxdb\\v1.7\\supported_protocols\\prometheus)\n[Configuration | Prometheus](https:\\prometheus.io\\docs\\prometheus\\latest\\configuration\\configuration)\n配置文件参考\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 global: alerting: alertmanagers: - static_configs: - targets: rule_files: scrape_configs: - job_name: \u0026#39;prometheus1\u0026#39; file_sd_configs: - files: [\u0026#39;\\data\\sd_config\\test.","title":"prometheus传统架构安装"},{"content":"Cobra功能 简单子命令cli 如 kubectl verion kubectl get\n自动识别-h，\u0026ndash;help 帮助\n更过参考官方手册：https://github.com/spf13/cobra\nkubectl get pod --all-namespaces\nget 代表命令（command） pod 代表事务（args） --all-namespaces 代表标识（flag） command 代表动作， Args 代表事务， flags 代表动作的修饰符。 使用Cobra 使用cobra需要main.go或和cmd/cmd.go(非固定，根据官方手册说明操作的)，来创建需要添加的命令。\ncobra不需要构造函数，只需要创建命令即可\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 rootCmd = \u0026amp;cobra.Command{ Use: \u0026#34;db \u0026#34;, Short: \u0026#34;test1\u0026#34;, Long: `this is a test123`, Run: func(cmd *cobra.Command, args []string) { log.Println(cfgFile, port) }, } func Execute() { if err := rootCmd.Execute(); err != nil { log.Fatal(err) os.Exit(1) } } 还需要在init()方法中定义flag和handle等配置。\ngo 1 2 3 4 func init() { rootCmd.PersistentFlags().StringVar(\u0026amp;cfgFile, \u0026#34;c\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;config file (default /etc/php.ini)\u0026#34;) rootCmd.PersistentFlags().IntVar(\u0026amp;port, \u0026#34;p\u0026#34;, 3306, \u0026#34;config file (default /etc/php.ini)\u0026#34;) } 创建main.go，在其初始化cobra\ngo 1 2 3 4 5 6 7 package main import \u0026#34;your_app/cmd\u0026#34; func main() { cmd.Execute() } 使用flag 标志是args来控制动作command的操作方式的。\n**Persistent Flags：**全局性flag 可用于它所分配的命令以及该命令下的每个命令。在根上分配标志作为全局flag。\nLocal Flags：局部性flag 在本args分配一个标志，该标志仅适用于该特定命令。\nRequired flags：必选flag，flag默认是可选的。如果希望命令在未设置flag时报告错误，请将其标记为required\ngo 1 2 rootCmd.Flags().StringVarP(\u0026amp;cfgFile, \u0026#34;config\u0026#34;, \u0026#34;c\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;config file (require)\u0026#34;) rootCmd.MarkFlagRequired(\u0026#34;config\u0026#34;) 使用子命令\ngo 1 2 3 4 5 6 7 8 9 10 testCmd = \u0026amp;cobra.Command{ Use: \u0026#34;zhangsan\u0026#34;, Short: \u0026#34;child command\u0026#34;, Long: `this is a child command`, Run: func(cmd *cobra.Command, args []string) { fmt.Println(\u0026#34;root \u0026gt; zhangsan\u0026#34;) }, } rootCmd.AddCommand(testCmd) ","permalink":"https://www.161616.top/go-cobra/","summary":"Cobra功能 简单子命令cli 如 kubectl verion kubectl get\n自动识别-h，\u0026ndash;help 帮助\n更过参考官方手册：https://github.com/spf13/cobra\nkubectl get pod --all-namespaces\nget 代表命令（command） pod 代表事务（args） --all-namespaces 代表标识（flag） command 代表动作， Args 代表事务， flags 代表动作的修饰符。 使用Cobra 使用cobra需要main.go或和cmd/cmd.go(非固定，根据官方手册说明操作的)，来创建需要添加的命令。\ncobra不需要构造函数，只需要创建命令即可\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 rootCmd = \u0026amp;cobra.Command{ Use: \u0026#34;db \u0026#34;, Short: \u0026#34;test1\u0026#34;, Long: `this is a test123`, Run: func(cmd *cobra.Command, args []string) { log.Println(cfgFile, port) }, } func Execute() { if err := rootCmd.","title":"Go每日一库 - cobra"},{"content":"包获取：go get -u github.com/gorhill/cronexpr\n创建一个定时任务\ngo 1 expr, err = cron.Parse(\u0026#34;* * * * *\u0026#34;); 获得任务的下次执行时间\ngo 1 nextTime = expr.Next(now) 完整代码\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; cron \u0026#34;github.com/gorhill/cronexpr\u0026#34; ) type CronJob struct { expr *cron.Expression nextTime time.Time //expr.now } func main() { var ( cronJob *CronJob expr *cron.Expression now time.Time scheduleTable map[string]*CronJob // key 任务的名称 ) scheduleTable = make(map[string]*CronJob) now = time.Now() expr = cron.MustParse(\u0026#34;*/5 * * * * * *\u0026#34;) cronJob = \u0026amp;CronJob{ expr: expr, nextTime: expr.Next(now), } scheduleTable[\u0026#34;job1\u0026#34;] = cronJob expr = cron.MustParse(\u0026#34;*/10 * * * * * *\u0026#34;) cronJob = \u0026amp;CronJob{ expr: expr, nextTime: expr.Next(now), } // 将任务注册到调度表中 scheduleTable[\u0026#34;job2\u0026#34;] = cronJob // 调度协程 go func() { var( _now time.Time cname string cronjob *CronJob ) for { _now = time.Now() for cname, cronjob = range scheduleTable { if cronjob.nextTime.Before(_now) || cronjob.nextTime.Equal(_now) { go func(name string) { fmt.Println(\u0026#34;exec\u0026#34;, name) }(cname) cronjob.nextTime = cronjob.expr.Next(_now) fmt.Println(\u0026#34;next exec time: \u0026#34;,cronjob.nextTime) } } select { case \u0026lt;-time.NewTimer(100 * time.Millisecond).C: //睡眠 } } }() time.Sleep(time.Minute*3) } ","permalink":"https://www.161616.top/cronexpr/","summary":"包获取：go get -u github.com/gorhill/cronexpr\n创建一个定时任务\ngo 1 expr, err = cron.Parse(\u0026#34;* * * * *\u0026#34;); 获得任务的下次执行时间\ngo 1 nextTime = expr.Next(now) 完整代码\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; cron \u0026#34;github.","title":"Go每日一库 - cronexpr"},{"content":"Kubernetes集群的构成 Master Node (Control plane) Master 是整个 Kubernetes 集群构成的基础，它负责整个集群的管理，例如处理集群的状态；组件包含 API Server, Controller manager, Scheduller, Etcd\nAPI server API 服务器是 Master 的统一前端入口，负责集群内其他组件的 协调 与 通信。该组件用于定义集群的状态。可以通过命令行, HTTP API, 第三方托管平台（dashboard, Rancker, Kuboard等）与 Kubernetes API 进行交互。\nScheduler 调度程序 Scheduler 负责根据可用资源来决定如何去部署容器，部署到哪里？确保所有 Pod（容器组）都分配给某一组节点。\nController Manager Controller manager，又分为Controller 和 Manager，Controller的组要作用是用于协调各种控制器(Deployment, Daemonset\u0026hellip;)，这些控制器可确保在节点发生故障时采取适当的措施。而 Manager 则管理的众多Controller；更一般地说，CM 负责随时将集群的当前状态调整到所需状态（Kubernetes设计基石）。\netcd etcd 是控制平面内的一个组件，他提供了 Kubernetes 资源的存储，并为集群内组件提供了 Watch 的功能，这将意味着，etcd 在 kubernetes 集群中作为存储与分布式协调的功能。\nWorker nodes 每个集群中至少需要存在一个工作节点，但是通常会有大量的节点；而工作节点包括的组件不限于 Kubelet, Kube-proxy, CNI Plugin。\nKubelet kubelet是工作节点中管理运行时的组件，负责整个Pod （容器组）进程的生命周期\nKube-proxy Kube-proxy 为整个集群内提供了 service 的功能，如果这个组件无法正常工作，那么整个集群内的网络通信将不能正常，因为 service 是作为集群内服务的访问入口，包含 Kubernetes API service。\nCNI CNI (Container Network Interface) 是 Kubernetes 集群中提供跨节点通信的一个组件，通常来说，它是一个独立于容器运行时（Docker等）的网络插件规范，旨在实现容器之间和容器与宿主机之间的网络连接。\n一个 CNI 基本的功能就是去管理网络设备的生命周期，例如，生成网卡，添加IP地址，注销网卡，而构成这些网络功能的则有操作系统来提供的，例如网络隧道(VxLAN)，而还存在一种情况就是三层网络，这时CNI会作为一个路由器来分发路由。除此之外，CNI还提供了更多的功能，例如数据包加密，网络策略，服务网格，网络加速，IPAM等功能\n通过二进制安装Kubernetes cluster 硬件配置推荐 在选择节点数量时，需要考虑集群的用途，通常情况下建议至少使用 3 个节点 (APIServer)，对于控制平面其他组件来说，通常最少为两个即可，因为HA架构中工作节点总是需要一个即可\n要运行 apiserver 和 etcd，您需要一台具有 2 个内核和 2GB RAM 的 机器，用于中小型集群，更大规模集群可能需要更多的核心。工作节点必须有足够的资源来托管您的应用程序，并且可以有不同的配置。\netcd 硬件配置推荐 Kuberentes 中工作节点需要启动一个或多个 etcd 实例，通常官方推荐运行奇数个 etcd 实例，因为一个 3 实例时有两个活跃就具备了集群的 quorum ，同理五个实例时存在3个实例活跃即可，7=\u0026gt;4 等，通常集群规模不大于9，否则存在同步时的延迟。\n集群的部署模式 单独托管的 etcd 集群 master节点同台主机上托管 etcd 集群 通过 kubeadm 生成的 etcd 集群 在这里我们使用二进制方式最小化部署，即 单节点的 etcd 集群，与同时为 control plane 与 worker 一体托管在单台物理/虚拟机之上的 Kubernetes 集群\n题外话：kubeadm 和 二进制究竟有什么区别？\n实际上 kubeadm 和 二进制本质上并没有什么区别，如果非要说存在区别的话，那么就是其 Procss Manager 不同，\n一个是由 systemd/system v 或其他操作系统维护的1 id的进程进行维护； kubeadm 部署的 kubelet 将 由 kubelet 进程自己来监控，当 pod 崩溃时重启该 pod，kubelete 也无法对他们进行健康检查。静态 pod 始终绑定在某一个 kubelet，并且始终运行在同一个节点上，可以通过在 master 节点上查看 /etc/kubernetes/manifests 目录 配置证书 通常情况下，大家都知道安装 kubernetes 集群需要证书，但是不知道为什么需要证书，这里需要了解 Kubernetes 认证机制，也就是“用户”在kubernetes集群中被称为什么\nKubernetes 中用户被分为几类：\nX.509 证书 静态 Token 文件 Bootstrap Tokens Service Account Tokens kubernetes 使用了客户端证书方式进行认证，这是作为外部用户的一种方式，这些证书会被默认生成对应的内部用户，所以需要准备证书文件，在本文中一切准备文件都是由脚本生成，不做基础的配置讲解。\n对于 X.509 证书，需要注意有几点\nkubernetes 中，对于证书的用户，CN就是用户名，O 就是组织。 对于证书认证来说，客户端证书，也就是说客户端必须在可信任列表中，也就是 subject_name 中允许的 对于 Kubernetes 组件来说，通常情况下需要包含 Kubernetes API service 的所有名称（短域名+完整域名） kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster kubernetes.default.svc.cluster.local 如果你希望使用IP，而不是域名，也可以对证书中sub_name增加对应域名\n对于组件间认证，目前 Kubernetes 中基本上组件间认证都有 kubeconfig 完成，而 kubelet，kube-controller-manager 这两个组件，由于提供的功能不同，可能存在其他的认证方式；例如 kubelet 所作的事情是 “监听Pod资源进行部署” 那么这个时候与 APIServer 通讯使用了 kubeconfig，在例如 kubelet 要被 APIServer 签发时，此时认证是使用的 bootstrap token 进行的，而这个kubeconfig 就是 签发后生成的内容，本质上来说，kubelet 没有客户端证书，只有token，而例如 kube-scheduler 是有客户端证书，但是需要生产 kubeconfig 文件\n签发kubelet 给 kubelet 签发证书主要由两部分组成，一种是 kube-controller-manager 自动签发，一种是 kubectl certifcate approve 手动签发，这里就有必要知道 kubelet 的认证的流程：\nkubelet启动时会找 kubeconfig ，如果没有进入下一部 没有 kubeconfig，会使用 bootstraps 进行认证，此时会被颁发客户端证书 在通过后，kubelet 会根据签发证书 生成 \u0026ndash;kubeconfig 指定的 kubeconfig 文件 下次后，kubelet 会根据这个 kubeconfig 同 Kubernetes API 进行 验证 了解了 kubelet 的签发过程，就明白在二进制部署时，为什么需要做一个 clusterrolebinding ？\n因为Kubernetes API 为 kubelet 的 bootstraps-token 使用的是用户 system:node-bootstrapper ，所以要想让这个用户可以访问 API 资源，那么就需要为这个用户绑定上集群角色（用户/组），这就需要执行一个命令\nbash 1 $ kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers 此时再去查看证书颁发，这时因为有权限访问 API 资源了， 所以获得了证书的被签发\nbash 1 2 3 $ kubectl get csr NAME AGE REQUESTOR CONDITION node-csr-a-MREQ1IybB0U5M8RP5FasSjckQOZiCoCYlf8ipDwx8 5m11s system:bootstrapper Pending kube-dns 的部署 coredns部署\nsh 1 2 3 4 $ mkdir coredns \u0026amp;\u0026amp; cd coredns $ wget https://raw.githubusercontent.com/coredns/deployment/master/kubernetes/coredns.yaml.sed $ wget https://raw.githubusercontent.com/coredns/deployment/master/kubernetes/deploy.sh $ bash deploy.sh -i 10.96.0.10 -r \u0026#34;10.96.0.0/12\u0026#34; -s -t coredns.yaml.sed |kubectl apply -f - 开启IPVS 内核开启IPVS功能否则降级\nsh 1 2 3 4 5 6 7 8 9 10 cat \u0026gt; /etc/sysconfig/modules/ipvs.modules \u0026lt;\u0026lt; EOF #!/bin/bash ipvs_mods_dir=\u0026#34;/usr/lib/modules/$(uname -r)/kernel/net/netfilter/ipvs\u0026#34; for i in \\$(ls \\$ipvs_mods_dir | grep -o \u0026#34;^[^.]*\u0026#34;); do /sbin/modinfo -F filename \\$i \u0026gt;/dev/null if [ \\$? -eq 0 ]; then /sbin/modprobe -- \\$i fi done EOF or\nyaml 1 2 3 4 5 6 7 8 9 10 11 cat \u0026gt; /etc/sysconfig/modules/ipvs.modules \u0026lt;\u0026lt;EOF #!/bin/bash ipvs_modules=\u0026#34;ip_vs ip_vs_lc ip_vs_wlc ip_vs_rr ip_vs_wrr ip_vs_lblc ip_vs_lblcr ip_vs_dh ip_vs_sh ip_vs_fo ip_vs_nq ip_vs_sed ip_vs_ftp nf_conntrack_ipv4\u0026#34; for kernel_module in \\${ipvs_modules}; do /sbin/modinfo -F filename \\${kernel_module} \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 if [ $? -eq 0 ]; then /sbin/modprobe \\${kernel_module} fi done EOF chmod 755 /etc/sysconfig/modules/ipvs.modules \u0026amp;\u0026amp; bash /etc/sysconfig/modules/ipvs.modules \u0026amp;\u0026amp; lsmod | grep ip_vs Troubleshooting apiserver报错 没有kubeappserver用户 text 1 Failed at step USER spawning No such process text 1 2 3 Mar 2 04:54:56 node02 systemd: controller-manager.service: main process exited, code=exited, status=1/FAILURE Mar 2 04:54:56 node02 kube-controller-manager: Get http://127.0.0.1:8443/api/v1/namespaces/kube-system/configmaps/extension-apiserver-authentication: net/http: HTTP/1.x transport connection broken: malformed HTTP response \u0026#34;\\x15\\x03\\x01\\x00\\x02\\x02\u0026#34; Mar 2 04:54:56 node02 systemd: Unit controller-manager.service entered failed state. Unable to connect to the server: net/http: HTTP/1.x transport connection broken: malformed HTTP resp - kevin_loving的博客 - CSDN博客\n可能是启动单元文件有问题，手动启动后正常\ntext 1 2 Mar 2 09:44:51 node02 kube-controller-manager: W0302 09:44:51.672404 39926 client_config.go:554] error creating inClusterConfig, falling back to default config: unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined node \u0026ldquo;xxxx\u0026rdquo; not found 如下面问题，可能原因为 kubelet 正式还未签发\ntext 1 2 3 4 5 Nov 14 16:31:03 master01 kubelet: E1114 16:31:03.677280 8587 kubelet.go:2270] node \u0026#34;master01\u0026#34; not found 380 19810 kubelet.go:2292] node \u0026#34;master-machine\u0026#34; not found May 12 23:45:11 master-machine kubelet: E0512 23:45:11.415099 19810 kubelet.go:2292] node \u0026#34;master-machine\u0026#34; not found May 12 23:45:11 master-machine kubelet: E0512 23:45:11.460097 19810 certificate_manager.go:434] Failed while requesting a signed certificate from the master: cannot create certificate signing request: certificatesigningrequests.certificates.k8s.io is forbidden: User \u0026#34;system:anonymous\u0026#34; cannot create resource \u0026#34;certificatesigningrequests\u0026#34; in API group \u0026#34;certificates.k8s.io\u0026#34; at the cluster scope User \u0026ldquo;system:anonymous\u0026rdquo; cannot list resource xxx 原因为：kubelet 正式还未签发\ntext 1 k8s.io/client-go/informers/factory.go:135: Failed to list *v1.CSIDriver: csidrivers.storage.k8s.io is forbidden: User \u0026#34;system:anonymous\u0026#34; cannot list resource \u0026#34;csidrivers\u0026#34; in API group \u0026#34;storage.k8s.io\u0026#34; at the cluster scope 问题：\u0026quot;master01\u0026quot; is forbidden: User \u0026quot;system:anonymous\u0026quot; 原因：用户未授权\n需要注意的是，这里使用的是 bootstrap 证书进行授权，所以绑定的用户必须为 bootstrap 证书授权的用户或组。\nbash 1 $ kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers The kube-apiserver has several requirements to enable TLS bootstrapping: Authenticating the bootstrapping kubelet to the system:bootstrappers group [2]\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 root@debian-template:~# kubectl get clusterrole system:node-bootstrapper -oyaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: annotations: rbac.authorization.kubernetes.io/autoupdate: \u0026#34;true\u0026#34; creationTimestamp: \u0026#34;2024-11-23T04:26:57Z\u0026#34; labels: kubernetes.io/bootstrapping: rbac-defaults name: system:node-bootstrapper resourceVersion: \u0026#34;54\u0026#34; selfLink: /apis/rbac.authorization.k8s.io/v1/clusterroles/system%3Anode-bootstrapper uid: 6f39c3aa-e46d-413f-8834-35c8832c328a rules: - apiGroups: - certificates.k8s.io resources: - certificatesigningrequests verbs: - create - get - list - watch 报错如下\ntext 1 2 3 master01 kubelet: E1114 17:16:33.581116 8559 kubelet.go:2270] node \u0026#34;master01\u0026#34; not found failed to ensure node lease exists, will retry in 6.4s, error: leases.coordination.k8s.io \u0026#34;master01\u0026#34; is forbidden: User \u0026#34;system:anonymous\u0026#34; cannot get resource \u0026#34;leases\u0026#34; in API group \u0026#34;coordination.k8s.io\u0026#34; in the namespace \u0026#34;kube-node-lease\u0026#34; 容器内 dial tcp 10.96.0.1:443: connect: connection timed out 问题1： flannela访问 dial tcp 10.96.0.1:443: connect: connection timed out\n问题2：open /run/flannel/subnet.env: no such file or directory\n问题3： Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized\n解决：检查kube-proxy组件\ntext 1 Nov 14 17:30:04 master01 kubelet: E1114 17:30:04.097261 1160 kubelet.go:2190] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized text 1 2 3 4 $ kubectl logs kube-flannel-ds-dvsv7 -n kube-system .... E1114 23:22:20.984238 1 main.go:243] Failed to create SubnetManager: error retrieving pod spec for \u0026#39;kube-system/kube-flannel-ds-dvsv7\u0026#39;: Get \u0026#34;https://10.96.0.1:443/api/v1/namespaces/kube-system/pods/kube-flannel-ds-dvsv7\u0026#34;: dial tcp 10.96.0.1:443: connect: connection timed out $ kubectl get csr 显示No Resources Found的解决记录 问题：kubectl get csr 显示No Resources Found的解决记录\n解决：需要先将 bootstrap token 文件中的 kubelet-bootstrap 用户赋予 system:node-bootstrapper 角色，然后 kubelet 才有权限创建认证请求\n授予KUBE-APISERVER对KUBELET API的访问权限 kubelet启动启动时，--kubeletconfig 使用参数对应的文件是否存在 如果不存在--bootstrap-kubeconfig指定的kubeconfig文件向kube-apiserver发送CSR请求\nkube-apiserver 收到 CSR 请求后，对其中的 token 进行认证，认证通过后将请求的用户设置为system:bootstrap:\u0026lt;Token ID\u0026gt;，组设置为 ，system:bootstrappers此操作称为Bootstrap Token Auth。\n默认这个用户和组没有创建CSR的权限，kubelet没有启动，错误日志如下：\ntext 1 Unable to register node \u0026#34;master-machine\u0026#34; with API server: nodes is forbidden: User \u0026#34;system:anonymous\u0026#34; cannot create resource \u0026#34;nodes\u0026#34; in API group \u0026#34;\u0026#34; at the cluster scope 解决方法是：创建一个集群角色绑定，绑定一个组：system:bootstrapper 和一个clusterrole system:node-bootstrapper\ntext 1 $ kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers 测试授权\nbash 1 2 3 4 5 6 7 kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers system:serviceaccount:default:default kubectl create clusterrolebinding firewalld-default --clusterrole=system:aggregate-to-admin --user=system:serviceaccount:default:default system:aggregate-to-admin kube flannel cant get cidr although podcidr available on node text 1 2 3 E0729 06:56:09.253632 1 main.go:330] Error registering network: failed to acquire lease: node \u0026#34;master-machine\u0026#34; pod cidr not assigned I0729 06:56:09.253682 1 main.go:447] Stopping shutdownHandler... W0729 06:56:09.253809 1 reflector.go:436] github.com/flannel-io/flannel/subnet/kube/kube.go:403: watch of *v1.Node ended with: an error on the server (\u0026#34;unable to decode an event from the watch stream: context canceled\u0026#34;) has prevented the request from succeeding 参考：kube flannel cant get cidr although podcidr available on node 原因为 CIDR无法分配\nnetwork plugin is not ready: cni config uninitialized bash 1 Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized 没有安装网络插件，安装任意网络插件后恢复。\nmvcc: required revision has been compacted text 1 kube-apiserver: W1120 17:18:20.199950 70454 watcher.go:207] watch chan error: etcdserver: mvcc: required revision has been compacted 这里是etcd返回的错误，被apiserver视为警告，在注释中有这么一句话\nIf the context is \u0026ldquo;context.Background/TODO\u0026rdquo;, returned \u0026ldquo;WatchChan\u0026rdquo; will not be closed and block until event is triggered, except when server returns a non-recoverable error (e.g. ErrCompacted).\n如果这个上下文返回WatchChan将在下次事件被触发前不会被关闭或阻塞，除非服务器返回一个ErrCompacted （不可恢复）\n对于etcd 对 Revision 有如下说明\netcd对每个kv的revision 都会保留一个压缩周期的值，例如每5分钟收集一次最新revision，当压缩周期达到时，将从历史记录中后去最后一个修订版本，例如为100，此时会压缩，压缩成功会重置计数器，并以最新的revision和新的历史记录进行开始，压缩失败将在5分钟后重试--auto-compaction-retention=10 是配置压缩周期的 每多少个小时键值存储运行定期压缩。\n如果最新的revision已被修订，etcd返回一个 ErrCompacted 表示已修订，此时表示为不可恢复状态\n返回错误时表示这个watch被关闭，为了优雅的关闭chan，kubernetes会对这个watch错误进行返回，而 ErrCompacted 本质上不算错误\ngo 1 2 3 4 5 6 7 8 9 10 wch := wc.watcher.client.Watch(wc.ctx, wc.key, opts...) for wres := range wch { if wres.Err() != nil { err := wres.Err() // If there is an error on server (e.g. compaction), the channel will return it before closed. logWatchChannelErr(err) wc.sendError(err) return } ... Reference ​[1] Maintenance\n​[2] kubelet-tls-bootstrapping\n","permalink":"https://www.161616.top/kubernetes-install-with-binary-files/","summary":"Kubernetes集群的构成 Master Node (Control plane) Master 是整个 Kubernetes 集群构成的基础，它负责整个集群的管理，例如处理集群的状态；组件包含 API Server, Controller manager, Scheduller, Etcd\nAPI server API 服务器是 Master 的统一前端入口，负责集群内其他组件的 协调 与 通信。该组件用于定义集群的状态。可以通过命令行, HTTP API, 第三方托管平台（dashboard, Rancker, Kuboard等）与 Kubernetes API 进行交互。\nScheduler 调度程序 Scheduler 负责根据可用资源来决定如何去部署容器，部署到哪里？确保所有 Pod（容器组）都分配给某一组节点。\nController Manager Controller manager，又分为Controller 和 Manager，Controller的组要作用是用于协调各种控制器(Deployment, Daemonset\u0026hellip;)，这些控制器可确保在节点发生故障时采取适当的措施。而 Manager 则管理的众多Controller；更一般地说，CM 负责随时将集群的当前状态调整到所需状态（Kubernetes设计基石）。\netcd etcd 是控制平面内的一个组件，他提供了 Kubernetes 资源的存储，并为集群内组件提供了 Watch 的功能，这将意味着，etcd 在 kubernetes 集群中作为存储与分布式协调的功能。\nWorker nodes 每个集群中至少需要存在一个工作节点，但是通常会有大量的节点；而工作节点包括的组件不限于 Kubelet, Kube-proxy, CNI Plugin。\nKubelet kubelet是工作节点中管理运行时的组件，负责整个Pod （容器组）进程的生命周期\nKube-proxy Kube-proxy 为整个集群内提供了 service 的功能，如果这个组件无法正常工作，那么整个集群内的网络通信将不能正常，因为 service 是作为集群内服务的访问入口，包含 Kubernetes API service。","title":"使用二进制文件构建k8s集群"},{"content":"先看代码\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package main import ( \u0026#34;fmt\u0026#34; ) func main() { var a = \u0026#34;hello world\u0026#34; var b = \u0026#34;中\u0026#34; fmt.Println([]rune(a)) fmt.Println([]rune(b)) fmt.Println([]byte(b)) } go源码中的定义\ngo 1 2 3 4 5 6 7 8 // byte is an alias for uint8 and is equivalent to uint8 in all ways. It is // used, by convention, to distinguish byte values from 8-bit unsigned // integer values. type byte = uint8 // rune is an alias for int32 and is equivalent to int32 in all ways. It is // used, by convention, to distinguish character values from integer values. type rune = int32 byte是uint8、rune为uint32，一个仅限于ascii码的值，一个支持更多的值。rune比byte能表达更多的数。\ngolang默认使用utf8编码，一个中文占用3字节，一个utf8数字占用1字节，utf8字母占用1字节\n","permalink":"https://www.161616.top/golang-byte-and-rune/","summary":"先看代码\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package main import ( \u0026#34;fmt\u0026#34; ) func main() { var a = \u0026#34;hello world\u0026#34; var b = \u0026#34;中\u0026#34; fmt.Println([]rune(a)) fmt.Println([]rune(b)) fmt.Println([]byte(b)) } go源码中的定义\ngo 1 2 3 4 5 6 7 8 // byte is an alias for uint8 and is equivalent to uint8 in all ways. It is // used, by convention, to distinguish byte values from 8-bit unsigned // integer values.","title":"Go byte与rune区别"},{"content":"bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象，os.stdin就是实现了这个接口\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) var buff *bufio.Reader func main() { buff = bufio.NewReader(os.Stdin) str, err := buff.ReadString(\u0026#39;\\n\u0026#39;) if err == nil { fmt.Printf(\u0026#34;input was :%s\u0026#34;, str) } } ReadString(byte) 遇到byte后返回，包含已读到的和byte，如果在读到之前遇到错误，返回读取的信息及该错误\n在写文件时。可以写入缓冲区来可以提升磁盘性能\n","permalink":"https://www.161616.top/go-bufio/","summary":"bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象，os.stdin就是实现了这个接口\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) var buff *bufio.Reader func main() { buff = bufio.NewReader(os.Stdin) str, err := buff.ReadString(\u0026#39;\\n\u0026#39;) if err == nil { fmt.Printf(\u0026#34;input was :%s\u0026#34;, str) } } ReadString(byte) 遇到byte后返回，包含已读到的和byte，如果在读到之前遇到错误，返回读取的信息及该错误\n在写文件时。可以写入缓冲区来可以提升磁盘性能","title":"Go每日一库 - bufio缓冲区的终端输入"},{"content":"概述 etcd 是兼具一致性和高可用性的键值数据库，为云原生架构中重要的基础组件，由CNCF 孵化托管。etcd 在微服务和 Kubernates 集群中不仅可以作为服务注册与发现，还可以作为 key-value 存储的中间件。\n先决条件 运行的 etcd 集群个数成员为奇数。 etcd 是一个 leader-based 分布式系统。确保主节点定期向所有从节点发送心跳，以保持集群稳定。 保持稳定的 etcd 集群对 Kubernetes 集群的稳定性至关重要。因此，请在专用机器或隔离环境上运行 etcd 集群，以满足所需资源需求]。 确保不发生资源不足。\n集群的性能和稳定性对网络和磁盘 IO 非常敏感。任何资源匮乏都会导致心跳超时，从而导致集群的不稳定。不稳定的情况表明没有选出任何主节点。在这种情况下，集群不能对其当前状态进行任何更改，这意味着不能调度新的 pod。 相关术语 Raft：etcd所采用的保证分布式系统强一致性的算法。 Node：节点 ，Raft状态机的一个实例，具有唯一标识。 Member： 成员，一个etcd实例。承载一个Node，且可为客户端请求提供服务。 Cluster：集群，由多个Member构成可以协同工作的etcd集群。 Peer：同伴，Cluster中其他成员。 Proposal ：提议，一个需要完成 raft 协议的请求(例如写请求，配置修改请求)。 Client： 向etcd集群发送HTTP请求的客户端。 WAL：预写式日志，etcd用于持久化存储的日志格式。 snapshot：etcd防止WAL文件过多而设置的快照，存储etcd数据状态。 Proxy：etcd的一种模式，为etcd集群提供反向代理服务。 Leader：Raft算法中通过竞选而产生的处理所有数据提交的节点。 Follower：竞选失败的节点作为Raft中的从属节点，为算法提供强一致性保证。 Candidate：当Follower超过一定时间接收不到Leader的心跳时转变为Candidate开始竞选。 Term：某个节点成为Leader到下一次竞选时间，称为Ubuntu一个Term。 Index：数据项编号。Raft中通过Term和Index来定位数据。 ETCD 部署 源码安装 基于master分支构建etcd\nbash 1 2 3 git clone https://github.com/etcd-io/etcd.git cd etcd ./build # 如脚本格式为dos的，需要将其格式修改为unix，否则报错。 启动命令\n--listen-client-urls 于 --listen-peer-urls 不能为域名\n--listen-client-urls 于 --advertise-client-urls\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ./etcd --name=etcd \\ --data-dir=/var/lib/etcd/ \\ --listen-client-urls=https://10.0.0.1:2379 \\ --listen-peer-urls=https://10.0.0.1:2380 \\ --advertise-client-urls=https://hketcd:2379 \\ --initial-advertise-peer-urls=https://hketcd:2380 \\ --cert-file=\u0026#34;/etc/etcd/pki/server.crt\u0026#34; \\ --key-file=\u0026#34;/etc/etcd/pki/server.key\u0026#34; \\ --client-cert-auth=true \\ --trusted-ca-file=\u0026#34;/etc/etcd/pki/ca.crt\u0026#34; \\ --auto-tls=false \\ --peer-cert-file=\u0026#34;/etc/etcd/pki/peer.crt\u0026#34; \\ --peer-key-file=\u0026#34;/etc/etcd/pki/peer.key\u0026#34; \\ --peer-client-cert-auth=true \\ --peer-trusted-ca-file=\u0026#34;/etc/etcd/pki/ca.crt\u0026#34; \\ --peer-auto-tls=false 其他方式 CentOS 可以使用 yum install etcd -y Ubuntu 可以预构建的二进制文件 安装报错 certificate: x509: certificate specifies an incompatible key usage\n原因：此处证书用于serverAuth 与clientAuth，缺少clientAuth导致\n解决：extendedKeyUsage=serverAuth， clientAuth\nbash 1 2 WARNING: 2020/11/12 14:11:42 grpc: addrConn.createTransport failed to connect to {0.0.0.0:2379 \u0026lt;nil\u0026gt; 0 \u0026lt;nil\u0026gt;}. Err: connection error: desc = \u0026#34;transport: authentication handshake failed: remote error: tls: bad certificate\u0026#34;. Reconnecting... {\u0026#34;level\u0026#34;:\u0026#34;warn\u0026#34;,\u0026#34;ts\u0026#34;:\u0026#34;2020-11-12T14:11:46.415+0800\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;embed/config_logging.go:198\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;rejected connection\u0026#34;,\u0026#34;remote-addr\u0026#34;:\u0026#34;127.0.0.1:52597\u0026#34;,\u0026#34;server-name\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;error\u0026#34;:\u0026#34;tls: failed to verify client certificate: x509: certificate specifies an incompatible key usage\u0026#34;} 原因：证书使用的者不对。\n解决：查看subjectAltName 是否与请求地址一致。\nsh 1 error \u0026#34;tls: failed to verify client\u0026#39;s certificate: x509: certificate specifies an incompatible key usage\u0026#34;, ServerName \u0026#34;\u0026#34; 原因：ETCD_LISTEN_PEER_URLS 与 ETCD_LISTEN_CLIENT_URLS 不能用域名\ntext 1 error verifying flags, expected IP in URL for binding (https://hketcd:2380). See \u0026#39;etcd --help\u0026#39; error #0: x509: certificate has expired or is not yet valid\n原因：证书还未生效\n解决：因服务器时间不对导致，校对时间后正常\ntext 1 etcd: rejected connection from \u0026#34;x.x.x.x:1126\u0026#34; (error \u0026#34;tls: failed to verify client\u0026#39;s certificate: x509: certificate signed by unknown authority (possibly because of \\\u0026#34;crypto/rsa: verification error\\\u0026#34; while trying to verify candidate authority certificate \\\u0026#34;etcd-ca\\\u0026#34;)\u0026#34;, ServerName \u0026#34;\u0026#34;) 配置文件详解 config\netcdctl 使用 sh 1 2 3 4 5 6 7 8 9 etcdctl --key-file=/etc/etcd/pki/client.key \\ --cert-file=/etc/etcd/pki/client.crt \\ --ca-file=/etc/etcd/pki/ca.crt \\ --endpoint=\u0026#34;https://node01.k8s.test:2379\u0026#34; \\ cluster-health member 288506ee270a7733 is healthy: got healthy result from https://node03.k8s.test:2379 member 863156df9b1575d1 is healthy: got healthy result from https://node02.k8s.test:2379 member ff386de9dc0b3c40 is healthy: got healthy result from https://node01.k8s.test:2379 v3 版本客户端使用\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 export ETCDCTL_API=3 etcdctl --key=/etc/etcd/pki/client.key \\ --cert=/etc/etcd/pki/client.crt \\ --cacert=/etc/etcd/pki/ca.crt \\ --endpoints=\u0026#34;https://master.k8s:2379\u0026#34; \\ endpoint health etcdctl \\ --key=/etc/etcd/pki/client.key \\ --cert=/etc/etcd/pki/client.crt \\ --cacert=/etc/etcd/pki/ca.crt \\ --endpoints=\u0026#34;https://master.k8stx.com:2379\u0026#34; \\ endpoint status 日志独立 etcd日志默认输出到 /var/log/message 如果想独立日志为一个文件，可以使用rsyslogd过滤器功能，使etcd的日志输出到单独的文件内。\n新建/etc/rsyslog.d/xx.conf文件。 在新建文件内写入内容如下 text 1 2 3 if $programname == \u0026#39;etcd\u0026#39; then /var/log/etcd.log # 停止往其他文件内写入，如果不加此句，会继续往/var/log/message写入。 if $programname == \u0026#39;etcd\u0026#39; then stop 也可以\ntext 1 2 3 4 if ($programname == \u0026#39;etcd\u0026#39;) then { action(type=\u0026#34;omfile\u0026#34; file=\u0026#34;/var/log/etcd.log\u0026#34;) stop } ","permalink":"https://www.161616.top/etcd-install-bin/","summary":"概述 etcd 是兼具一致性和高可用性的键值数据库，为云原生架构中重要的基础组件，由CNCF 孵化托管。etcd 在微服务和 Kubernates 集群中不仅可以作为服务注册与发现，还可以作为 key-value 存储的中间件。\n先决条件 运行的 etcd 集群个数成员为奇数。 etcd 是一个 leader-based 分布式系统。确保主节点定期向所有从节点发送心跳，以保持集群稳定。 保持稳定的 etcd 集群对 Kubernetes 集群的稳定性至关重要。因此，请在专用机器或隔离环境上运行 etcd 集群，以满足所需资源需求]。 确保不发生资源不足。\n集群的性能和稳定性对网络和磁盘 IO 非常敏感。任何资源匮乏都会导致心跳超时，从而导致集群的不稳定。不稳定的情况表明没有选出任何主节点。在这种情况下，集群不能对其当前状态进行任何更改，这意味着不能调度新的 pod。 相关术语 Raft：etcd所采用的保证分布式系统强一致性的算法。 Node：节点 ，Raft状态机的一个实例，具有唯一标识。 Member： 成员，一个etcd实例。承载一个Node，且可为客户端请求提供服务。 Cluster：集群，由多个Member构成可以协同工作的etcd集群。 Peer：同伴，Cluster中其他成员。 Proposal ：提议，一个需要完成 raft 协议的请求(例如写请求，配置修改请求)。 Client： 向etcd集群发送HTTP请求的客户端。 WAL：预写式日志，etcd用于持久化存储的日志格式。 snapshot：etcd防止WAL文件过多而设置的快照，存储etcd数据状态。 Proxy：etcd的一种模式，为etcd集群提供反向代理服务。 Leader：Raft算法中通过竞选而产生的处理所有数据提交的节点。 Follower：竞选失败的节点作为Raft中的从属节点，为算法提供强一致性保证。 Candidate：当Follower超过一定时间接收不到Leader的心跳时转变为Candidate开始竞选。 Term：某个节点成为Leader到下一次竞选时间，称为Ubuntu一个Term。 Index：数据项编号。Raft中通过Term和Index来定位数据。 ETCD 部署 源码安装 基于master分支构建etcd\nbash 1 2 3 git clone https://github.com/etcd-io/etcd.git cd etcd ./build # 如脚本格式为dos的，需要将其格式修改为unix，否则报错。 启动命令\n--listen-client-urls 于 --listen-peer-urls 不能为域名","title":"etcd二进制安装与配置"},{"content":"1 elasticsearch介绍 ES是一个基于Lucene实现的开源、分布式、基于Restful风格的全文本搜索引擎；此外，它还是一个分布式实时文档存储，其中每个文档的每个field均是被索引的数据，且可被搜索：也是一个带实时分析功能的分布式搜索引擎，能够扩展至数以百计的节点实时处理PB级的数据。\n1.1 elasticsearch基本组件 索引（index）：文档容器，换句话说，索引是具有类似属性的文档的集合。类似于表。索引名必须使用小写字母：\n类型（type）：类型是索引内部的逻辑分区，其意义完全取决于用户需求。一个索引内部可定义一个或多个类型。一般来说，类型就是拥有相同的域的文档的预定义。\n文档（document）：文档是Lucene索引和搜索的原子单位，它包含了一个或多个域。是域的容器：基于J50N格式表示。每个域的组成部分：一个名字，一个或多个值；拥有多个值的域，通常称为多值域：\n映射（mapping）：原始内容存储为文档之前需要事先进行分析，例如切词、过滤掉某些词等：映射用于定义此分析机制该如何实现；除此之外，ES还为映射提供了诸如将域中的内容排序等功能。\n1.2 es的集群组件 cluster: es的集群标识为集群名称：默认“elasticsearch”。节点就是靠此名字来决定加入到哪个集群中。一个节点只能属性于一个集群。\nNode：运行了单个es实例的主机即为节点。用于存储数据、参与集群索引及搜索操作。节点的标识靠节点名。\nShard：将索引切割成为的物理存储组件：但每一个shard都是一个独立且完整的索引；创建索引时，ES默认将其分割为5个shard，用户也可以按需自定义，创建完成之后不可修改。\nshard有两种类型：primary shard和replica。replica用于数据冗余及查询时的负载均衡。每个主shard的副本数量可自定义，且可动态修改。\n1.3 es的工作过程 es节点启动时默认通过多播方式或单播方式在TCP协议的9300端口查找同一集群中的其他节点，并与之建立通讯。判断是否为同一集群的标准为集群名称，集群中的所有节点会选举出一个主节点负责管理整个集群状态，以及在集群范围内决定各shared的分布方式。站在用户使用中角度而言，每个节点都可以接收并相应各类请求，无需区分哪个为主节点。\n当集群中某一节点为down状态时，主节点会读取集群状态信息，并启动修复过程。此过程中主节点会检查所有可用shared的主shared，并确定主shared是否存在。各种shared及对应副本shared是否对数。此时集群状态会转换为yellow。\nes集群有三种状态：green、red（不可用）、yellow（修复状态）。\n当某状态为down的节点上存在主shared，此时需要从各副本shared中找一个提升为主shared。在yellow状态时，各副本shared均处于未分配模式。此时各个副本都不可用，只能使用主shared。集群虽可基于各组shared进行查询，但此时吞吐能力有限。yellows属于残破不全状态。\n接下来的过程中，主节点将会查找所有的冗余shard，并将其配置为主shared。如果某shared的副本数量少于配置参数的数量，会自动启动复制过程，并为其建立副本shared。直至所有条件都满足从yellow转换至green。\nes工作过程中主节点会周期性检查各节点是否处于可用状态，任意节点不可用时，修复模式都会启动，此时集群将会进入重新均衡过程。\n2 安装elasticsearch 下载地址：http://www.elastic.co/cn/downloads/elasticsearch\n官方文档中写明对es每个版本对jdk的要求。\nhttps://www.elastic.co/guide/en/elasticsearch/reference/current/index.html\n2.1 常用es配置文件说明 修改配置文件：/etc/elasticsearch/elasticsearch.yml\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 cluster.name: test1 #←集群名称，同一网段下可以有多个集群，通过名称区分不同的集群。 node.name: node-1 #←节点名 node.master: true #←是否有资格被选举成为node node.data: true # es5中已废弃 index.number_of_shards: 5 #←默认索引分片个数，默认为5片。 index.number_of_replicas: 1\t#←默认索引副本个数，默认为1个副本 path.conf: /path/to/conf #←文件的存储路径 path.data: /path/to/data #←索引数据的存储路径，多个存储路径，用逗号隔开 path.work: /path/to/work #←日志文件的存储路径 path.plugins: /path/to/plugins #←插件的存放路径 network.bind_host: 192.168.0.1 #←绑定的ip地址 network.publish_host: 192.168.0.1 #←设置其它节点和该节点交互的ip地址 network.host: 192.168.0.1 #←同时设置bind_host和publish_host transport.tcp.port: 9300 #←节点之间交互的tcp端口，默认是9300。 http.port: 9200 #←设置对外服务的http端口，默认为9200。 transport.tcp.compress: true #←设置是否压缩tcp传输时的数据，默认false不压缩 http.max_content_length: 100mb #←内容的最大容量，默认100mb http.enabled: false #←使用http协议对外提供服务，默认为true开启。 #←默认为local即为本地文件系统，可以设置为本地文件系统， # 分布式文件系统，hadoop的HDFS，和amazon的s3服务器等。 gateway.type: local gateway.expected_nodes: 2 #←集群中节点的数量，默认为2。 discovery.zen.ping.timeout: 3s #←自动发现其它节点时ping连接超时时间，默认为3秒\tdiscovery.zen.ping.multicast.enabled: false #←打开多播发现节点，默认是true。 # 设置集群中master节点的初始列表，可以通过这些节点来自动发现新加入集群的节点。 discovery.zen.ping.unicast.hosts: [\u0026#34;host1\u0026#34;, \u0026#34;host2:port\u0026#34;, \u0026#34;host3[portX-portY]\u0026#34;] # 当你无法关闭系统的swap的时候，建议把这个参数设为true。 # 防止在内存不够用的时候，elasticsearch的内存被交换至交换区，导致性能骤降 bootstrap.mlockal1: true 参考：https://www.cnblogs.com/hanyouchun/p/5163183.html 修改启动脚本设置启动参数： /usr/share/elasticsearch/bin/elasticsearch\nsh 1 2 export JAVA_HOME=/usr/local/jdk export PATH=$JAVA_HOME/bin:$PATH es2.x支持方式\nsh 1 export ES_HEAP_SIZE=256m es5.x支持方式\nsh 1 2 3 $ cat /etc/elasticsearch/jvm.options -Xms128m -Xmx128m 2.2 安装head插件 官方网站：https://github.com/mobz/elasticsearch-head#running-with-built-in-server\n对于elasticsearch2.x\nsh 1 elasticsearch/bin/plugin install file:///root/elasticsearch-head.zip 对于Elasticsearch 5.x，head不支持网站插件。作为独立服务器运行。 https://www.cnblogs.com/alice626/p/6206722.html\nhttps://www.cnblogs.com/xing901022/p/6030296.html\nhttps://www.cnblogs.com/valor-xh/p/6293689.html\nhttps://github.com/mobz/elasticsearch-head，github上下载压缩包。\n因为head为独立启动的，所以随便放置任意目录即可。\n2.2.1 安装node.js https://nodejs.org/en/download/\n解压后设置环境变量\nsh 1 2 export NODE_HOME=/usr/local/node-v6.9.1-linux-x64 export PATH=$PATH:$NODE_HOME/bin 检查环境变量\nsh 1 2 3 4 $ node -v v8.11.2 $ npm -v 5.6.0 2.2.2 安装grunt sh 1 2 3 npm install grunt-cli $ grunt -version grunt-cli v1.2.0 2.2.3 修改head配置 head/Gruntfile.js，增加hostname属性，设置为*\njs 1 2 3 4 5 6 7 8 9 10 connect: { server: { options: { port: 9100, hostname: \u0026#39;*\u0026#39;, base: \u0026#39;.\u0026#39;, keepalive: true } } } 修改连接地址 head/_site/app.js\njs 1 2 3 4 this.base_uri = this.config.base_uri || this.prefs.get(\u0026#34;app-base_uri\u0026#34;) || \u0026#34;http://10.0.0.12:19200\u0026#34;; 2.2.4 运行head 在head目录下，执行npm install 下载以来的包：\nsh 1 nmp install 出现如下报错\nsh 1 2 3 4 5 6 7 8 9 10 11 Please report this full log at https://github.com/Medium/phantomjs npm ERR! Darwin 15.0.0 npm ERR! argv \u0026#34;/usr/local/bin/node\u0026#34; \u0026#34;/usr/local/bin/npm\u0026#34; \u0026#34;install\u0026#34; npm ERR! node v4.4.3 npm ERR! npm v3.10.9 npm ERR! code ELIFECYCLE npm ERR! phantomjs-prebuilt@2.1.14 install: `node install.js` npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the phantomjs-prebuilt@2.1.14 install script \u0026#39;node install.js 解决方法\nsh 1 npm install phantomjs-prebuilt@2.1.14 --ignore-scripts 运行head\nsh 1 grunt server \u0026amp; 设置http对外提供服务\n2.3 安装bigdesk https://github.com/hlstudio/bigdesk\n下载bigdesk\nsh 1 2 3 4 5 6 7 8 9 $ cd bigdesk-master $ ll bigdesk_es2.png LICENSE NOTICE plugin-descriptor.properties README.md 【_site】 进入_site目录\nsh 1 python -m SimpleHTTPServer port 8000 marven、kopf\n2.x plugin 访问 9200/_plugin/head\n3 ES使用 3.1 es api介绍 elasticsearch支持的是Restful风格的API接口\nrestuful介绍： http://www.ruanyifeng.com/blog/2014/05/restful_api.html?bsh_bid=516759003\n四类API：\n检查集群、节点、索引等健康与否，以及获取其相应状态。 管理集群、节点、索引及元数据。 执行CRUD操作。 执行高级操作，例如paging,filtering等。 3.2 es api使用 E5访问接口：tcp/9200\nsh 1 curl -X [VERB] [PROTOCOL]://HOST:PORT/[PATH]?[QUERY_STR] -d \u0026#39;\u0026lt;BODY\u0026gt;\u0026#39; VERB：GET,PUT,DELETE等。 PROTOCOL：http,https。 QUERY_STRING：查询参数，例如？pretty表示用易读的JSON格式输出。 BODY：请求的主体。 3.2.1 查看es工作状态 sh 1 2 3 4 5 6 7 8 9 10 11 12 13 $ curl http://10.0.0.12:19200/?pretty { \u0026#34;name\u0026#34; : \u0026#34;node-1\u0026#34;, \u0026#34;cluster_name\u0026#34; : \u0026#34;test1\u0026#34;, \u0026#34;version\u0026#34; : { \u0026#34;number\u0026#34; : \u0026#34;2.0.0\u0026#34;, \u0026#34;build_hash\u0026#34; : \u0026#34;de54438d6af8f9340d50c5c786151783ce7d6be5\u0026#34;, \u0026#34;build_timestamp\u0026#34; : \u0026#34;2015-10-22T08:09:48Z\u0026#34;, \u0026#34;build_snapshot\u0026#34; : false, \u0026#34;lucene_version\u0026#34; : \u0026#34;5.2.1\u0026#34; }, \u0026#34;tagline\u0026#34; : \u0026#34;You Know, for Search\u0026#34; } 查看集群工作状态，?v查看详细信息。\nsh 1 2 3 4 $ curl 127.0.0.1:19200/_cat/nodes?v ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name 10.0.0.9 27 90 1 0.07 0.03 0.05 mdi - node-1 10.0.0.12 46 91 0 0.00 0.01 0.05 mdi * node-2 health：查看当前集群健康状态的相关信息\nsh 1 2 3 4 5 6 $ curl 127.0.0.1:19200/_cat/health?pretty 1526839934 02:12:14 test green 2 2 0 0 0 0 0 0 - 100.0% $ curl 127.0.0.1:19200/_cat/health?v epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent 1526841115 02:31:55 test green 2 2 0 0 0 0 0 0 - 100.0% 3.2.2 自定义查询 sh 1 2 3 $ curl 127.0.0.1:19200/_cat/nodes?h=ip,name,port,uptime,heap.current 10.0.0.9 node-1 19300 40.7m 107.2mb mdi 10.0.0.12 node-2 19300 57m 89.6mb mdi sh 1 2 3 $ curl 127.0.0.1:19200/_cat/master?v id host ip node CorsR-_VSN2YHj5DCRXiKg 10.0.0.12 10.0.0.12 node-2 官方文档：https://www.elastic.co/guide/en/elasticsearch/reference/6.2/index.html\n3.2.3 state api json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 curl 127.0.0.1:19200/_cluster/state/nodes?pretty { \u0026#34;cluster_name\u0026#34; : \u0026#34;test\u0026#34;, \u0026#34;compressed_size_in_bytes\u0026#34; : 276, \u0026#34;nodes\u0026#34; : { \u0026#34;cplyIE3WS3a4sKRhjYQPoQ\u0026#34; : { \u0026#34;name\u0026#34; : \u0026#34;node-1\u0026#34;, \u0026#34;ephemeral_id\u0026#34; : \u0026#34;axJ2wLcZRxyMMfLP7J54HA\u0026#34;, \u0026#34;transport_address\u0026#34; : \u0026#34;10.0.0.9:19300\u0026#34;, \u0026#34;attributes\u0026#34; : { } }, \u0026#34;CorsR-_VSN2YHj5DCRXiKg\u0026#34; : { \u0026#34;name\u0026#34; : \u0026#34;node-2\u0026#34;, \u0026#34;ephemeral_id\u0026#34; : \u0026#34;UTGxkPRYSRO_wU_URX_taw\u0026#34;, \u0026#34;transport_address\u0026#34; : \u0026#34;10.0.0.12:19300\u0026#34;, \u0026#34;attributes\u0026#34; : { } } } } curl 127.0.0.1:19200/_cluster/state/nodes?pretty curl 127.0.0.1:19200/_nodes/state?pretty https://www.elastic.co/guide/en/elasticsearch/reference/6.2/cluster-state.html\n3.3 crud api 3.3.1 创建索引 json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 curl -X PUT -H \u0026#34;Content-Type: application/json\u0026#34; http://127.0.0.1:19200/students/NO/1?pretty -d \u0026#39; { \u0026#34;first_name\u0026#34;: \u0026#34;jing\u0026#34;, \u0026#34;last_name\u0026#34; : \u0026#34;guo\u0026#34;, \u0026#34;gender\u0026#34; : \u0026#34;male\u0026#34;, \u0026#34;age\u0026#34; :\u0026#34;25\u0026#34;, \u0026#34;courses\u0026#34;: \u0026#34;xianglong shiba zhang\u0026#34; }\u0026#39; { \u0026#34;_index\u0026#34; : \u0026#34;students\u0026#34;, \u0026#34;_type\u0026#34; : \u0026#34;NO\u0026#34;, \u0026#34;_id\u0026#34; : \u0026#34;1\u0026#34;, \u0026#34;_version\u0026#34; : 1, \u0026#34;result\u0026#34; : \u0026#34;created\u0026#34;, \u0026#34;_shards\u0026#34; : { \u0026#34;total\u0026#34; : 2, \u0026#34;successful\u0026#34; : 1, \u0026#34;failed\u0026#34; : 0 }, \u0026#34;_seq_no\u0026#34; : 0, \u0026#34;_primary_term\u0026#34; : 1 } json 1 2 3 4 5 6 7 \u0026#39;{ \u0026#34;first_name\u0026#34;: \u0026#34;rong\u0026#34;, \u0026#34;last_name\u0026#34; : \u0026#34;huang\u0026#34;, \u0026#34;gender\u0026#34; : \u0026#34;female\u0026#34;, \u0026#34;age\u0026#34; :23, \u0026#34;courses\u0026#34;: \u0026#34;luoying shenjian zhang\u0026#34; }\u0026#39; sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 curl -XGET http://127.0.0.1:19200/students/NO/1?pretty { \u0026#34;_index\u0026#34; : \u0026#34;students\u0026#34;, \u0026#34;_type\u0026#34; : \u0026#34;NO\u0026#34;, \u0026#34;_id\u0026#34; : \u0026#34;1\u0026#34;, \u0026#34;_version\u0026#34; : 2, \u0026#34;found\u0026#34; : true, \u0026#34;_source\u0026#34; : { \u0026#34;first_name\u0026#34; : \u0026#34;rong\u0026#34;, \u0026#34;last_name\u0026#34; : \u0026#34;huang\u0026#34;, \u0026#34;gender\u0026#34; : \u0026#34;female\u0026#34;, \u0026#34;age\u0026#34; : 23, \u0026#34;courses\u0026#34; : \u0026#34;luoying shenjian zhang\u0026#34; } } 3.3.2 更新文档 sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 curl -X POST -H \u0026#34;Content-Type: application/json\u0026#34; http://127.0.0.1:19200/students/NO/1/_update?pretty -d \u0026#39;{ \u0026#34;doc\u0026#34;: { \u0026#34;age\u0026#34;: 18 } }\u0026#39; { \u0026#34;_index\u0026#34; : \u0026#34;students\u0026#34;, \u0026#34;_type\u0026#34; : \u0026#34;NO\u0026#34;, \u0026#34;_id\u0026#34; : \u0026#34;1\u0026#34;, \u0026#34;_version\u0026#34; : 3, \u0026#34;result\u0026#34; : \u0026#34;updated\u0026#34;, \u0026#34;_shards\u0026#34; : { \u0026#34;total\u0026#34; : 2, \u0026#34;successful\u0026#34; : 1, \u0026#34;failed\u0026#34; : 0 }, \u0026#34;_seq_no\u0026#34; : 2, \u0026#34;_primary_term\u0026#34; : 1 } 3.3.3 删除文档 sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 curl -X DELETE http://10.0.0.12:19200/students/NO/1?pretty { \u0026#34;_index\u0026#34; : \u0026#34;students\u0026#34;, \u0026#34;_type\u0026#34; : \u0026#34;NO\u0026#34;, \u0026#34;_id\u0026#34; : \u0026#34;1\u0026#34;, \u0026#34;_version\u0026#34; : 4, \u0026#34;result\u0026#34; : \u0026#34;deleted\u0026#34;, \u0026#34;_shards\u0026#34; : { \u0026#34;total\u0026#34; : 2, \u0026#34;successful\u0026#34; : 2, \u0026#34;failed\u0026#34; : 0 }, \u0026#34;_seq_no\u0026#34; : 3, \u0026#34;_primary_term\u0026#34; : 2 } 3.3.4 删除索引 sh 1 2 3 4 5 6 7 8 9 curl http://10.0.0.12:19200/_cat/indices green open test1 cBL6BqIqStyr2a8O2n8Bdw 5 1 0 0 2.5kb 1.2kb green open students g04VDxTUTEOLmdooHuuRpA 5 1 1 0 26.5kb 13.2kb curl -X DELETE http://10.0.0.12:19200/test1 {\u0026#34;acknowledged\u0026#34;:true} curl http://10.0.0.12:19200/_cat/indices green open students g04VDxTUTEOLmdooHuuRpA 5 1 1 0 26.5kb 13.2kb 3.4 查询数据 Query API Query DSL:JS0N based language for building complex queries。\n用户实现诸多类型的查询操作，比如，simple term query,phrase,range boolean,fuzzy等：\nES的查询操作执行分为两个阶段：\n分散阶段： 合并阶段： 列出一个索引的所有文档，较小级别时的演示说明\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 curl http://10.0.0.12:19200/_search?pretty { \u0026#34;took\u0026#34; : 15, #←执行时长 \u0026#34;timed_out\u0026#34; : false, #←是否超时 \u0026#34;_shards\u0026#34; : { #←操作设计到多少个分片 \u0026#34;total\u0026#34; : 5, \u0026#34;successful\u0026#34; : 5, \u0026#34;skipped\u0026#34; : 0, \u0026#34;failed\u0026#34; : 0 }, \u0026#34;hits\u0026#34; : { #←查询结果 \u0026#34;total\u0026#34; : 2, \u0026#34;max_score\u0026#34; : 1.0, \u0026#34;hits\u0026#34; : [ { \u0026#34;_index\u0026#34; : \u0026#34;students\u0026#34;, \u0026#34;_type\u0026#34; : \u0026#34;NO\u0026#34;, \u0026#34;_id\u0026#34; : \u0026#34;2\u0026#34;, \u0026#34;_score\u0026#34; : 1.0, #←文档平分，没有加权 \u0026#34;_source\u0026#34; : { \u0026#34;first_name\u0026#34; : \u0026#34;jing\u0026#34;, \u0026#34;last_name\u0026#34; : \u0026#34;guo\u0026#34;, \u0026#34;gender\u0026#34; : \u0026#34;male\u0026#34;, \u0026#34;age\u0026#34; : \u0026#34;25\u0026#34;, \u0026#34;courses\u0026#34; : \u0026#34;xianglong shiba zhang\u0026#34; } }, { \u0026#34;_index\u0026#34; : \u0026#34;students\u0026#34;, \u0026#34;_type\u0026#34; : \u0026#34;NO\u0026#34;, \u0026#34;_id\u0026#34; : \u0026#34;1\u0026#34;, \u0026#34;_score\u0026#34; : 1.0, \u0026#34;_source\u0026#34; : { \u0026#34;first_name\u0026#34; : \u0026#34;rong\u0026#34;, \u0026#34;last_name\u0026#34; : \u0026#34;huang\u0026#34;, \u0026#34;gender\u0026#34; : \u0026#34;female\u0026#34;, \u0026#34;age\u0026#34; : 23, \u0026#34;courses\u0026#34; : \u0026#34;luoying shenjian zhang\u0026#34; } } ] } } 默认情况下，只返回前十个。\njson 1 2 3 curl -H \u0026#39;content-type: application/json\u0026#39; http://10.0.0.12:19200/_search?pretty -d \u0026#39;{ \u0026#34;query\u0026#34;: {\u0026#34;match_all\u0026#34;: {}} }\u0026#39; 3.5 多索引、多类型查询 /_search：所有索引： /INDEX_NAME/_search：单索引： /INDEX1,INDEX2/_search：多索引： /s*,t*/_search： /students/class1/_search：单类型搜索 /students/class1,class2/_search：多类型搜索\n3.6 插入测试数据 sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 curl -H \u0026#39;content-type: application/json\u0026#39; \\ http://10.0.0.12:19200/test1/NO/1?pretty -d \u0026#39;{ \u0026#34;name\u0026#34;:\u0026#34;zhou yu\u0026#34;, \u0026#34;age\u0026#34;: \u0026#34;25\u0026#34;, \u0026#34;weapon\u0026#34;: \u0026#34;duan jian\u0026#34; }\u0026#39; curl -H \u0026#39;content-type: application/json\u0026#39; \\ http://10.0.0.12:19200/test1/NO/2?pretty -d \u0026#39;{ \u0026#34;name\u0026#34;:\u0026#34;zhangliang\u0026#34;, \u0026#34;age\u0026#34;: 31, \u0026#34;weapon\u0026#34;: \u0026#34;fu zhou\u0026#34; }\u0026#39; curl -H \u0026#39;content-type: application/json\u0026#39; \\ http://10.0.0.12:19200/test1/NO/3?pretty -d \u0026#39;{ \u0026#34;name\u0026#34;:\u0026#34;zhang liao\u0026#34;, \u0026#34;age\u0026#34;: \u0026#34;31\u0026#34;, \u0026#34;weapon\u0026#34;: \u0026#34;dao\u0026#34; }\u0026#39; curl -H \u0026#39;content-type: application/json\u0026#39; \\ http://10.0.0.12:19200/test1/NO/4?pretty -d \u0026#39;{ \u0026#34;name\u0026#34;:\u0026#34;zhang fei\u0026#34;, \u0026#34;age\u0026#34;: \u0026#34;30\u0026#34;, \u0026#34;weapon\u0026#34;: \u0026#34;zhang ba she mao\u0026#34; }\u0026#39; curl -H \u0026#39;content-type: application/json\u0026#39; \\ http://10.0.0.12:19200/test1/NO/5?pretty -d \u0026#39;{ \u0026#34;name\u0026#34;: \u0026#34;zhu ge liang\u0026#34;, \u0026#34;age\u0026#34;: \u0026#34;18\u0026#34;, \u0026#34;weapon\u0026#34;: \u0026#34;yu shan\u0026#34;, \u0026#34;intro\u0026#34;: \u0026#34;31 years old\u0026#34; }\u0026#39; 可以看出，默认按照字符串匹配。文档中的每个域存储为特定类型而非字符串。\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 { \u0026#34;hits\u0026#34;: { \u0026#34;total\u0026#34;: 3, \u0026#34;max_score\u0026#34;: 0.87546873, \u0026#34;hits\u0026#34;: [ { \u0026#34;_index\u0026#34;: \u0026#34;test1\u0026#34;, \u0026#34;_type\u0026#34;: \u0026#34;NO\u0026#34;, \u0026#34;_id\u0026#34;: \u0026#34;3\u0026#34;, \u0026#34;_score\u0026#34;: 0.87546873, \u0026#34;_source\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;zhang liao\u0026#34;, \u0026#34;age\u0026#34;: \u0026#34;31\u0026#34;, \u0026#34;weapon\u0026#34;: \u0026#34;dao\u0026#34; } }, { \u0026#34;_index\u0026#34;: \u0026#34;test1\u0026#34;, \u0026#34;_type\u0026#34;: \u0026#34;NO\u0026#34;, \u0026#34;_id\u0026#34;: \u0026#34;2\u0026#34;, \u0026#34;_score\u0026#34;: 0.87546873, \u0026#34;_source\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;zhangliang\u0026#34;, \u0026#34;age\u0026#34;: 31, \u0026#34;weapon\u0026#34;: \u0026#34;fu zhou\u0026#34; } } , { \u0026#34;_index\u0026#34;: \u0026#34;test1\u0026#34;, \u0026#34;_type\u0026#34;: \u0026#34;NO\u0026#34;, \u0026#34;_id\u0026#34;: \u0026#34;5\u0026#34;, \u0026#34;_score\u0026#34;: 0.2876821, \u0026#34;_source\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;zhu ge liang\u0026#34;, \u0026#34;age\u0026#34;: \u0026#34;18\u0026#34;, \u0026#34;weapon\u0026#34;: \u0026#34;yu shan\u0026#34;, \u0026#34;intro\u0026#34;: \u0026#34;31 years old\u0026#34; } } ] } 指定域搜索做精确匹配\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 \u0026#34;hits\u0026#34;: [ { \u0026#34;_index\u0026#34;: \u0026#34;test1\u0026#34;, \u0026#34;_type\u0026#34;: \u0026#34;NO\u0026#34;, \u0026#34;_id\u0026#34;: \u0026#34;3\u0026#34;, \u0026#34;_score\u0026#34;: 0.87546873, \u0026#34;_source\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;zhang liao\u0026#34;, \u0026#34;age\u0026#34;: \u0026#34;31\u0026#34;, \u0026#34;weapon\u0026#34;: \u0026#34;dao\u0026#34; } } , { \u0026#34;_index\u0026#34;: \u0026#34;test1\u0026#34;, \u0026#34;_type\u0026#34;: \u0026#34;NO\u0026#34;, \u0026#34;_id\u0026#34;: \u0026#34;4\u0026#34;, \u0026#34;_score\u0026#34;: 0.87546873, \u0026#34;_source\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;zhang fei\u0026#34;, \u0026#34;age\u0026#34;: \u0026#34;30\u0026#34;, \u0026#34;weapon\u0026#34;: \u0026#34;zhang ba she mao\u0026#34; } } ] 查看指定类型的mapping示例 curl \u0026ldquo;http://10.0.0.12:19200/test1/_mapping/NO?pretty\u0026rdquo; mapping定义了整个文档中的数据是被当做何种类型对待的。\njson 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 { \u0026#34;test1\u0026#34; : { \u0026#34;mappings\u0026#34; : { \u0026#34;NO\u0026#34; : { \u0026#34;properties\u0026#34; : { \u0026#34;age\u0026#34; : { \u0026#34;type\u0026#34; : \u0026#34;text\u0026#34;, \u0026#34;fields\u0026#34; : { \u0026#34;keyword\u0026#34; : { \u0026#34;type\u0026#34; : \u0026#34;keyword\u0026#34;, \u0026#34;ignore_above\u0026#34; : 256 } } }, \u0026#34;intro\u0026#34; : { \u0026#34;type\u0026#34; : \u0026#34;text\u0026#34;, \u0026#34;fields\u0026#34; : { \u0026#34;keyword\u0026#34; : { \u0026#34;type\u0026#34; : \u0026#34;keyword\u0026#34;, \u0026#34;ignore_above\u0026#34; : 256 } } }, \u0026#34;name\u0026#34; : { \u0026#34;type\u0026#34; : \u0026#34;text\u0026#34;, \u0026#34;fields\u0026#34; : { \u0026#34;keyword\u0026#34; : { \u0026#34;type\u0026#34; : \u0026#34;keyword\u0026#34;, \u0026#34;ignore_above\u0026#34; : 256 } } }, \u0026#34;weapon\u0026#34; : { \u0026#34;type\u0026#34; : \u0026#34;text\u0026#34;, \u0026#34;fields\u0026#34; : { \u0026#34;keyword\u0026#34; : { \u0026#34;type\u0026#34; : \u0026#34;keyword\u0026#34;, \u0026#34;ignore_above\u0026#34; : 256 } } } } } } } } ES中搜索的数据广义上可被理解为两类：\ntypes:exact full-text 精确值：指未经加工的原始值；在搜索时进行精确匹配。NOTEBOOK≠notebook\nfull-text：用于引用文本中数据；判断文档在多大程序上匹配查询请求；即评估文档与用户请求查询的相关度。\n为了完成ful1-text搜素，ES必须首先分析文本，并创建出倒排素引：倒排素引中的数据还需进行“正规化”为标准格式：如全部小写、负数变为单数、trees改为tree等。\n创建倒排索引需要先分词再normalization。分词加正规化的操作及为分析。\n分析需要由分析器(analyzer)进行。分析器由三个组件构成：字符过滤器、分词器、分词过滤器。\nES内置的分析器：\nStandard analyzer：ES的默认分析器，适用于多种语言，基于Unicode方式进行分析。 Simple analyzer：简单分析器，根据所有的字母进行分析。 Wirtespace analyzer：只把空白字符当做单词分割。 Language analyzer：为多种语言分别提供各种各样的分析器。 分析器不仅在创建素引时用到；在构建查询时也会用到。构建索引时的分析器，分析用户查询并构建成查询语句的分词方式。两种方式需要保持一致。\n[2018-09-21 05:24:03,381][INFO ][cluster.routing.allocation.decider] [node-1] low disk watermark [85%] exceeded on [JWxQxMkeTj6BkmIx-6DsXg][node-1][/var/lib/elasticsearch/my-application/nodes/0] free: 1gb[11.2%], replicas will not be assigned to this node\n4 ES错误 ES索引yellow状态 查看片状态\nsh 1 curl 172.16.16.18:9200/_cat/shards sh 1 2 3 4 5 6 7 8 9 10 11 logstash-webapi-2018.10.30 0 p STARTED 1 6.1kb 127.0.0.1 6wPQIP5 logstash-webapi-2018.10.30 0 p STARTED 3 16.4kb 127.0.0.1 6wPQIP5 logstash-webapi-2018.10.30 0 r UNASSIGNED logstash-webapi-2018.10.30 0 p STARTED 1824 1mb 127.0.0.1 6wPQIP5 logstash-webapi-2018.10.30 0 p UNASSIGNED logstash-webapi-2018.10.30 0 p STARTED 272 363.4kb 127.0.0.1 6wPQIP5 logstash-webapi-2018.10.30 0 p UNASSIGNED logstash-webapi-2018.10.30 0 p STARTED 0 5.1kb 127.0.0.1 6wPQIP5 logstash-webapi-2018.10.30 0 p UNASSIGNED logstash-webapi-2018.10.30 0 p STARTED 4888 2.4mb 127.0.0.1 6wPQIP5 logstash-webapi-2018.10.30 0 p UNASSIGNED yellow表示所有主分片可用，但不是所有副本分片都可用，最常见的情景是单节点时，由于es默认是有1个副本，主分片和副本不能在同一个节点上，所以副本就是未分配unassigned\nsh 1 2 3 4 5 6 7 PUT 172.16.16.18:9200/logstash -d \u0026#39; { \u0026#34;settings\u0026#34;:{ \u0026#34;number_of_shards\u0026#34;:1, \u0026#34;number_of_replicas\u0026#34;:0 } }\u0026#39; 参考网址：elasticsearch集群健康为黄色\n","permalink":"https://www.161616.top/es/","summary":"1 elasticsearch介绍 ES是一个基于Lucene实现的开源、分布式、基于Restful风格的全文本搜索引擎；此外，它还是一个分布式实时文档存储，其中每个文档的每个field均是被索引的数据，且可被搜索：也是一个带实时分析功能的分布式搜索引擎，能够扩展至数以百计的节点实时处理PB级的数据。\n1.1 elasticsearch基本组件 索引（index）：文档容器，换句话说，索引是具有类似属性的文档的集合。类似于表。索引名必须使用小写字母：\n类型（type）：类型是索引内部的逻辑分区，其意义完全取决于用户需求。一个索引内部可定义一个或多个类型。一般来说，类型就是拥有相同的域的文档的预定义。\n文档（document）：文档是Lucene索引和搜索的原子单位，它包含了一个或多个域。是域的容器：基于J50N格式表示。每个域的组成部分：一个名字，一个或多个值；拥有多个值的域，通常称为多值域：\n映射（mapping）：原始内容存储为文档之前需要事先进行分析，例如切词、过滤掉某些词等：映射用于定义此分析机制该如何实现；除此之外，ES还为映射提供了诸如将域中的内容排序等功能。\n1.2 es的集群组件 cluster: es的集群标识为集群名称：默认“elasticsearch”。节点就是靠此名字来决定加入到哪个集群中。一个节点只能属性于一个集群。\nNode：运行了单个es实例的主机即为节点。用于存储数据、参与集群索引及搜索操作。节点的标识靠节点名。\nShard：将索引切割成为的物理存储组件：但每一个shard都是一个独立且完整的索引；创建索引时，ES默认将其分割为5个shard，用户也可以按需自定义，创建完成之后不可修改。\nshard有两种类型：primary shard和replica。replica用于数据冗余及查询时的负载均衡。每个主shard的副本数量可自定义，且可动态修改。\n1.3 es的工作过程 es节点启动时默认通过多播方式或单播方式在TCP协议的9300端口查找同一集群中的其他节点，并与之建立通讯。判断是否为同一集群的标准为集群名称，集群中的所有节点会选举出一个主节点负责管理整个集群状态，以及在集群范围内决定各shared的分布方式。站在用户使用中角度而言，每个节点都可以接收并相应各类请求，无需区分哪个为主节点。\n当集群中某一节点为down状态时，主节点会读取集群状态信息，并启动修复过程。此过程中主节点会检查所有可用shared的主shared，并确定主shared是否存在。各种shared及对应副本shared是否对数。此时集群状态会转换为yellow。\nes集群有三种状态：green、red（不可用）、yellow（修复状态）。\n当某状态为down的节点上存在主shared，此时需要从各副本shared中找一个提升为主shared。在yellow状态时，各副本shared均处于未分配模式。此时各个副本都不可用，只能使用主shared。集群虽可基于各组shared进行查询，但此时吞吐能力有限。yellows属于残破不全状态。\n接下来的过程中，主节点将会查找所有的冗余shard，并将其配置为主shared。如果某shared的副本数量少于配置参数的数量，会自动启动复制过程，并为其建立副本shared。直至所有条件都满足从yellow转换至green。\nes工作过程中主节点会周期性检查各节点是否处于可用状态，任意节点不可用时，修复模式都会启动，此时集群将会进入重新均衡过程。\n2 安装elasticsearch 下载地址：http://www.elastic.co/cn/downloads/elasticsearch\n官方文档中写明对es每个版本对jdk的要求。\nhttps://www.elastic.co/guide/en/elasticsearch/reference/current/index.html\n2.1 常用es配置文件说明 修改配置文件：/etc/elasticsearch/elasticsearch.yml\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 cluster.","title":"elasticsearch安装"},{"content":"Java日志多由多行行组成，初始行之后的每一行以空格开头，如下例所示：在这种情况下，需要在将事件数据output之前处理多行事件。\ntext 1 2 3 4 5 [ERROR] - 2018-09-21 04:19:57.685 (SocketTransfer.java:73) - Socket交互遇到错误 Exception in thread \u0026#34;main\u0026#34; java.lang.NullPointerException at com.example.myproject.Book.getTitle(Book.java:16) at com.example.myproject.Author.getBookTitles(Author.java:25) at com.example.myproject.Bootstrap.main(Bootstrap.java:14) 配置multiline参数如下：Multiline codec plugin\n参数 说明 pattern 指定正则表达式。与指定正则表达式匹配的行被视为前一行的延续或新多行事件的开始。 what previous或next。该previous值指定与pattern选项中的值匹配的行是上一行的一部分。该next值指定与pattern选项中的值匹配的行是以下行的一部分 negate true或false（默认为false）。如果true，与pattern模式不匹配的消息将构成多行过滤器的匹配what并将应用。 sh 1 2 3 4 5 6 7 8 9 10 11 12 input { file { path =\u0026gt; \u0026#34;/root/apache-tomcat-8.5.34/logs/catalina.out\u0026#34; type =\u0026gt; \u0026#34;sk\u0026#34; start_position =\u0026gt; \u0026#34;beginning\u0026#34; codec =\u0026gt; multiline { pattern =\u0026gt; \u0026#34;^\\[\u0026#34; negate =\u0026gt; \u0026#34;true\u0026#34; what =\u0026gt; \u0026#34;previous\u0026#34; } } } 如上列日志格式为\nsh 1 [日志级别] - 日期 java类 - 错误报错 一般情况下，我们不需要将对其grok，因grok会大量影响性能。如上列格式需要对每一列进行grok\nsh 1 2 # 日期 TESTTIME %{YEAR}\\-%{MONTHNUM}\\-%{MONTHDAY} %{HOUR}:%{MINUTE}:%{SECOND} 根据以上格式写出如下的正则\ntextstash filter {\rgrok {\rmatch =\u0026gt; {\r\u0026#39;message\u0026#39; =\u0026gt; \u0026#34;\\[\\s?%{LOGLEVEL:lev}\\] - %{TESTTIME:date}\\s+\\((?\u0026lt;\\class\u0026gt;.*\\.java\\:\\d+)\\)\\s\\-\\s(?\u0026lt;\\msg\u0026gt;.*)\u0026#34;\r}\r}\rmutate { remove_field =\u0026gt; \u0026#34;message\u0026#34;\r}\r} 匹配到的日志格式\nsh 1 2 3 4 5 6 7 8 9 10 11 { \u0026#34;@timestamp\u0026#34; =\u0026gt; \u0026#34;2018-10-05T13:16:27.263Z\u0026#34;, \u0026#34;@version\u0026#34; =\u0026gt; \u0026#34;1\u0026#34;, \u0026#34;path\u0026#34; =\u0026gt; \u0026#34;/root/apache-tomcat-8.5.34/logs/catalina.out\u0026#34;, \u0026#34;host\u0026#34; =\u0026gt; \u0026#34;node02\u0026#34;, \u0026#34;type\u0026#34; =\u0026gt; \u0026#34;sk\u0026#34;, \u0026#34;lev\u0026#34; =\u0026gt; \u0026#34;DEBUG\u0026#34;, \u0026#34;date\u0026#34; =\u0026gt; \u0026#34;2018-10-04 12:48:56.142\u0026#34;, \u0026#34;class\u0026#34; =\u0026gt; \u0026#34;ObjectDaoImpl.java:219\u0026#34;, \u0026#34;msg\u0026#34; =\u0026gt; \u0026#34;queryPage with hql:select t.fplxdm,t.fpdm,t.fphm,t.fpcbh,f.ipdz,f.dkh,f.jqbh from ( select \u0026#39;026\u0026#39; as fplxdm,fpdm,fphm,kprq,fpcbh,jqbh from pj_zzspdz_fpmx where kprq\u0026gt;\u0026#39;20180801000000\u0026#39; and jqbh=\u0026#39;499099915361\u0026#39; and qmbz=\u0026#39;N\u0026#39;) t,dj_fwqxx f where t.jqbh=f.jqbh order by t.kprq\u0026#34; } ","permalink":"https://www.161616.top/elk-collect-java/","summary":"Java日志多由多行行组成，初始行之后的每一行以空格开头，如下例所示：在这种情况下，需要在将事件数据output之前处理多行事件。\ntext 1 2 3 4 5 [ERROR] - 2018-09-21 04:19:57.685 (SocketTransfer.java:73) - Socket交互遇到错误 Exception in thread \u0026#34;main\u0026#34; java.lang.NullPointerException at com.example.myproject.Book.getTitle(Book.java:16) at com.example.myproject.Author.getBookTitles(Author.java:25) at com.example.myproject.Bootstrap.main(Bootstrap.java:14) 配置multiline参数如下：Multiline codec plugin\n参数 说明 pattern 指定正则表达式。与指定正则表达式匹配的行被视为前一行的延续或新多行事件的开始。 what previous或next。该previous值指定与pattern选项中的值匹配的行是上一行的一部分。该next值指定与pattern选项中的值匹配的行是以下行的一部分 negate true或false（默认为false）。如果true，与pattern模式不匹配的消息将构成多行过滤器的匹配what并将应用。 sh 1 2 3 4 5 6 7 8 9 10 11 12 input { file { path =\u0026gt; \u0026#34;/root/apache-tomcat-8.5.34/logs/catalina.out\u0026#34; type =\u0026gt; \u0026#34;sk\u0026#34; start_position =\u0026gt; \u0026#34;beginning\u0026#34; codec =\u0026gt; multiline { pattern =\u0026gt; \u0026#34;^\\[\u0026#34; negate =\u0026gt; \u0026#34;true\u0026#34; what =\u0026gt; \u0026#34;previous\u0026#34; } } } 如上列日志格式为","title":"ELK收集java日志"},{"content":"Logstash通过网络将syslog消息作为事件读取。使用用 UDPSocket, TCPServer 和\nLogStash::Filters::Grok 来实现\n配置示例 sh 1 2 3 4 5 input { syslog{ port =\u0026gt; \u0026#34;514\u0026#34; } } 配置客户端，在修改linux主机/etc/rsyslog.conf\nsh 1 *.* @@host:port 配置说明 参数 说明 * 类型 * 级别 @ udp @@ tcp host 可为主机名或ip地址 注：收集到的数据，本身就以及是rsyslog格式了，无需再进行grok\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { \u0026#34;message\u0026#34; =\u0026gt; \u0026#34;(root) CMD (/bin/echo 1111 \u0026gt;\u0026gt;/root/1.txt)\\n\u0026#34;, \u0026#34;@version\u0026#34; =\u0026gt; \u0026#34;1\u0026#34;, \u0026#34;@timestamp\u0026#34; =\u0026gt; \u0026#34;2018-10-05T12:13:01.000Z\u0026#34;, \u0026#34;host\u0026#34; =\u0026gt; \u0026#34;10.0.0.16\u0026#34;, \u0026#34;priority\u0026#34; =\u0026gt; 78, \u0026#34;timestamp\u0026#34; =\u0026gt; \u0026#34;Oct 5 20:13:01\u0026#34;, \u0026#34;logsource\u0026#34; =\u0026gt; \u0026#34;node02\u0026#34;, \u0026#34;program\u0026#34; =\u0026gt; \u0026#34;CROND\u0026#34;, \u0026#34;pid\u0026#34; =\u0026gt; \u0026#34;92923\u0026#34;, \u0026#34;severity\u0026#34; =\u0026gt; 6, \u0026#34;facility\u0026#34; =\u0026gt; 9, \u0026#34;facility_label\u0026#34; =\u0026gt; \u0026#34;clock\u0026#34;, \u0026#34;severity_label\u0026#34; =\u0026gt; \u0026#34;Informational\u0026#34; } 收集到的数据需要对时间进行格式化\n官方date插件说明：Date filter plugin\nsh 1 2 3 4 5 6 7 filter { date { match =\u0026gt; [\u0026#34;timestamp\u0026#34; , \u0026#34;MMM dd HH:mm:ss\u0026#34;] target =\u0026gt; \u0026#34;timestamp\u0026#34; \u0026#34;timezone\u0026#34; =\u0026gt; \u0026#34;Asia/Shanghai\u0026#34; } } 格式化完后的时间如下示例\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { \u0026#34;message\u0026#34; =\u0026gt; \u0026#34;(root) CMD (/bin/echo 1111 \u0026gt;\u0026gt;/root/1.txt)\\n\u0026#34;, \u0026#34;@version\u0026#34; =\u0026gt; \u0026#34;1\u0026#34;, \u0026#34;@timestamp\u0026#34; =\u0026gt; \u0026#34;2018-10-05T12:09:01.000Z\u0026#34;, \u0026#34;host\u0026#34; =\u0026gt; \u0026#34;10.0.0.16\u0026#34;, \u0026#34;priority\u0026#34; =\u0026gt; 78, \u0026#34;timestamp\u0026#34; =\u0026gt; \u0026#34;2018-10-05T12:09:01.000Z\u0026#34;, \u0026#34;logsource\u0026#34; =\u0026gt; \u0026#34;node02\u0026#34;, \u0026#34;program\u0026#34; =\u0026gt; \u0026#34;CROND\u0026#34;, \u0026#34;pid\u0026#34; =\u0026gt; \u0026#34;92825\u0026#34;, \u0026#34;severity\u0026#34; =\u0026gt; 6, \u0026#34;facility\u0026#34; =\u0026gt; 9, \u0026#34;facility_label\u0026#34; =\u0026gt; \u0026#34;clock\u0026#34;, \u0026#34;severity_label\u0026#34; =\u0026gt; \u0026#34;Informational\u0026#34; } 说明：此处格式化完后时间与当前时区不符，相差8小时。这里不影响，在kibana中显示的为当前时区\n","permalink":"https://www.161616.top/collect-syslog/","summary":"Logstash通过网络将syslog消息作为事件读取。使用用 UDPSocket, TCPServer 和\nLogStash::Filters::Grok 来实现\n配置示例 sh 1 2 3 4 5 input { syslog{ port =\u0026gt; \u0026#34;514\u0026#34; } } 配置客户端，在修改linux主机/etc/rsyslog.conf\nsh 1 *.* @@host:port 配置说明 参数 说明 * 类型 * 级别 @ udp @@ tcp host 可为主机名或ip地址 注：收集到的数据，本身就以及是rsyslog格式了，无需再进行grok\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { \u0026#34;message\u0026#34; =\u0026gt; \u0026#34;(root) CMD (/bin/echo 1111 \u0026gt;\u0026gt;/root/1.txt)\\n\u0026#34;, \u0026#34;@version\u0026#34; =\u0026gt; \u0026#34;1\u0026#34;, \u0026#34;@timestamp\u0026#34; =\u0026gt; \u0026#34;2018-10-05T12:13:01.000Z\u0026#34;, \u0026#34;host\u0026#34; =\u0026gt; \u0026#34;10.","title":"ELK收集syslog"},{"content":" pipline def timestr() { script { return sh(script: \u0026#39;date +%Y%m%d%H%M%S\u0026#39;, returnStdout: true).trim()\r}\r}\rdef dockerImage\rpipeline{\ragent any\renvironment {\rtime = timestr()\rregistry = \u0026#34;xxx.com/payapp-test\u0026#34;\rregistryhub = \u0026#34;txhub.xxx.com\u0026#34;\rappName = \u0026#34;api\u0026#34;\r}\roptions {\rtimeout(time: 1, unit: \u0026#39;HOURS\u0026#39;)\rbuildDiscarder(logRotator(numToKeepStr: \u0026#39;15\u0026#39;))\rdisableConcurrentBuilds()\r}\rstages{\rstage(\u0026#34;Pull Code\u0026#34;){\rsteps{\rgit branch: \u0026#39;testing\u0026#39;, credentialsId: \u0026#39;422fb2c7-4d58-440a-98a4-e242b66f3800\u0026#39;, url: \u0026#39;http://gitlab.fgry45iy.com:90/pay/payGateway.git\u0026#39;\r}\r}\rstage(\u0026#34;Maven Package\u0026#34;){\rsteps{\rwithEnv([\u0026#39;PATH+EXTRA=/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/apache-maven-3.6.2/bin:/usr/local/maven/bin:/root/bin\u0026#39;]) {\rsh \u0026#34;mvn package\u0026#34;\r}\r}\r}\r// stage(\u0026#39;Building Image\u0026#39;) {\r// steps{\r// script {\r// dockerImage = docker.build( registry + \u0026#34;/\u0026#34; + appName + \u0026#34;:$BUILD_NUMBER\u0026#34;)\r// }\r// }\r// }\r// stage(\u0026#39;Push Images To Registry\u0026#39;) {\r// steps {\r// script {\r// dockerImage.push()\r// }\r// }\r// }\r// stage(\u0026#39;update\u0026#39;) {\r// steps {\r// sh \u0026#34;\u0026#34;\u0026#34;\r// curl -k --cert /root/ca/ca.crt --key /root/ca/ca.key -X PUT -H \u0026#39;Content-Type: application/yaml\u0026#39; --data \u0026#34;\r// apiVersion: apps/v1\r// kind: Deployment\r// metadata:\r// name: tyapi-api-deploy\r// namespace: pay\r// spec:\r// replicas: 2\r// selector:\r// matchLabels:\r// app: pay-api\r// template:\r// metadata:\r// labels:\r// app: pay-api\r// spec:\r// containers:\r// - name: pay-jv2-api\r// image: txhub.99xyp.com/payapp-test/api:$BUILD_NUMBER\r// ports:\r// - name: payapi\r// containerPort: 8081\r// \u0026#34; https://47.156.81.22:6443/apis/apps/v1/namespaces/pay/deployments/tyapi-api-deploy\r// \u0026#34;\u0026#34;\u0026#34;\r// }\r// }\r}\r// post {\r// cleanup {\r// echo \u0026#39;I have finished, delete dir\u0026#39;\r// deleteDir()\r// }\r// }\r} ","permalink":"https://www.161616.top/jenkins-docker/","summary":"pipline def timestr() { script { return sh(script: \u0026#39;date +%Y%m%d%H%M%S\u0026#39;, returnStdout: true).trim()\r}\r}\rdef dockerImage\rpipeline{\ragent any\renvironment {\rtime = timestr()\rregistry = \u0026#34;xxx.com/payapp-test\u0026#34;\rregistryhub = \u0026#34;txhub.xxx.com\u0026#34;\rappName = \u0026#34;api\u0026#34;\r}\roptions {\rtimeout(time: 1, unit: \u0026#39;HOURS\u0026#39;)\rbuildDiscarder(logRotator(numToKeepStr: \u0026#39;15\u0026#39;))\rdisableConcurrentBuilds()\r}\rstages{\rstage(\u0026#34;Pull Code\u0026#34;){\rsteps{\rgit branch: \u0026#39;testing\u0026#39;, credentialsId: \u0026#39;422fb2c7-4d58-440a-98a4-e242b66f3800\u0026#39;, url: \u0026#39;http://gitlab.fgry45iy.com:90/pay/payGateway.git\u0026#39;\r}\r}\rstage(\u0026#34;Maven Package\u0026#34;){\rsteps{\rwithEnv([\u0026#39;PATH+EXTRA=/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/apache-maven-3.6.2/bin:/usr/local/maven/bin:/root/bin\u0026#39;]) {\rsh \u0026#34;mvn package\u0026#34;\r}\r}\r}\r// stage(\u0026#39;Building Image\u0026#39;) {\r// steps{\r// script {\r// dockerImage = docker.","title":"jenkins pipeline docker方式"},{"content":"1 logstash概述 logstash是基于Jruby语言研发的server/agent结构的日志收集工具，可以在任何一个能产生日志的服务器上部署其agent。agent能同时监控多个产生日志的服务，并把每个服务所产生的日志信息收集并发送给logstash server端。由于收集到的日志信息是分散的，logstash有专门的节点用来将所有的节点收集到的日志信息按时间序列合并在一起形成一个序列，基于这一个序列请求写入并存储在elasticsearch集群中。\nkibana是基于node.js开发的elasticsearch的图形用户接口。它可以将用户的搜索语句发送给ES，有ES完成搜索，并且将结果返回。kibana可以将返回的数据，用非常直观的方式（画图）来做趋势展示的。\nlogstash基于多种数据获取机制，TCP/UDP协议、文件、syslog、windows EventLogs及STDIN等。同时也支持多种数据输出机制。获取到数据后，它支持对数据执行过滤、修改等操作。\nlogstash基于JRuby语言研发，需要运行在JVM虚拟机之上。工作于agent/server模型。\n1.1 logstash工作机制 在每一个产生日志的节点之上部署一个agent，这个agent通常称为shipper。shipper负责收集日志，并且发送给server端。但是在发送给server端时，为了避免server端可能无法应付大量节点同时发来的信息，会在server端和agent端之间放置一个消息队列，通常称之为broker。这个消息队列比较常见的使用redis。各agent将数据发送至broker，logstash服务器会从broker中依次取出日志拿来在本地做过滤、修改等操作。在完修改后将其发送至ES集群。\n对于logstash而言，server端也是插件式的工作的模式，与ES的不同在于，ES的插件是可有可无的。logstash的除核心外的所有功能都是基于插件来完成的。\n1.2 logstash工作流程 logstash的工作模式类似于管道，从指定位置读入数据，而后过滤数据，最后将其输出到指定位置。事实上logstash的工作流程为：input | filter | output。如无需对数据额外处理，filter可省略。\n1.3 logstash插件分类 input plugin：收集数据 codec plugin：编码插件 filter plugin：过滤 output plugin：输出插件 2 logstash安装下载 官网地址：https://www.elastic.co/cn/products../../images/Logstash\n3 logstash的配置使用 /etc../../images/Logstash/conf.d/路径下所有以.conf结尾的文件都被当做配置文件使用。对于logstash的配置文件定义，就是定义插件。从哪得到数据。向哪里输出数据。\n3.1 logstash的基本配置框架 sh 1 2 3 4 5 6 7 8 9 10 11 input{ .... } filter{ .... } output{ .... } 简单的\nsh 1 2 3 4 5 6 7 8 input{ stdin {} } output { stdout{ codec =\u0026gt;rubydebug } } 检查配置文件语法是否正确logstash -f /etc../../images/Logstash/conf.d/sample.conf --configtest\nsh 1 2 3 4 5 6 7 8 9 10 $ logstash -f /etc../../images/Logstash/conf.d/sample.conf hellow owrd Settings: Default pipeline workers: 1 Pipeline main started { \u0026#34;message\u0026#34; =\u0026gt; \u0026#34;hellow owrd\u0026#34;, #←信息的完整内容。logstash的信息是在input → filter → output之间流动的。 \u0026#34;@version\u0026#34; =\u0026gt; \u0026#34;1\u0026#34;, #←版本号。 \u0026#34;@timestamp\u0026#34; =\u0026gt; \u0026#34;2018-06-04T15:02:47.912Z\u0026#34;, #←事件发生时的时间戳。 \u0026#34;host\u0026#34; =\u0026gt; \u0026#34;test\u0026#34; #←时间发生的主机。 } 3.2 logstash支持的数据类型 Array：[iteml,item2,\u0026hellip;] Boolean：true,false Bytes Codec：编码器。 Hash: key =\u0026gt;value Number Password： Path：文件系统路径。 String：字符串。 logstash还支持字段引用，使用[]进行引用\n3.3 支持的条件判断 ==，!=，\u0026lt;，\u0026lt;=，\u0026gt;，\u0026gt;= 正则匹配=~ !~ in,not in and,or\n4 Logstash常用的插件 logstash是高度插件化的，主要分为input codec filter output插件：\n4.1 input插件 4.1.1 File 从指定的文件中读取事件流。其工作特性类似于tail -1f，能够将日志文件尾部的一行不断的读取出来。\nlogstash使用FileWatch（ruby gem库）机制来监听文件变化。filewatch是linux内核中提供的一种功能。filewatch库支持以glob方式展开文件名。因此可以监听多个文件。并且可以将每个文件读取的位置及状态信息保存在.sincedb数据库中。即使重启logstash后也不会从头读取文件的。.sincedb记录了每个被监听的文件的inode,major number,minor nubmer,pos。默认情况下位于启动logstash服务的用户家目录下。也可以自行定义位置。另外，file插件还可以自动识别日志的滚动（日志分割）操作。\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 input{ file{ path =\u0026gt; [\u0026#39;/var/log/messages\u0026#39;] type =\u0026gt; \u0026#34;system\u0026#34; start_postition =\u0026gt; \u0026#34;beginning\u0026#34; } } output{ stdout{ codec =\u0026gt; rubydebug } } Logstash file\n4.1.2 UDP logstash支持通过一个简单的协议来读取信息，如UDP、TCP，说明对方只要通过TCP协议发送时间也是支持的。或者哪怕通过syslog收集事件也是可以的。\nlogstash通过UDP协议通过网络连接来读取信息，其唯一必须制定的参数为port，用于指定自己监听的端口，agent可以向服务器端端口发送日志信息。而每个被收集数据的主机使用守护进程向server端发送事件。\nsh 1 2 3 4 5 6 7 8 input{ udp{ prot =\u0026gt; 25826 queue_size =\u0026gt; 2000 #←队列大小 workers =\u0026gt; 2 #←启动的工作线程数来接收发送的事件。 buffer_size =\u0026gt; #←接收缓冲区的大小 } } UDP插件实现\ncollectd：基于C语言开发的一款高度插件式的主机性能监控程序，以守护进程方式运行，能够收集系统性能相关的各种数据；并且能够基于各种存储机制将收集的结果存储下来。collectd本身所收集到的数据可以通过自身的network插件，将自己在本机所收集到的数据发送至其他主机。\ncollectd在epel源中\nsh 1 yum install collectd -y collectd的大量.so文件为其模块（插件），每一个模块分别实现每一种相应的功能。其配置文件 /etc/collectd.conf\nsh 1 2 3 4 5 6 7 8 9 10 11 12 Hostname \u0026#34;node3.magedu.com\u0026#34; LoadPlugin syslog LoadPlugin cpu # cpu性能数据 LoadPlugin df # 磁盘空间使用情况 LoadPlugin interface # 网络接口 LoadPlugin 1oad # 服务器负载 LoadPlugin memory # 内存 LoadPlugin network \u0026lt;Plugin network\u0026gt; \u0026lt;server \u0026#34;10.0.0.19\u0026#34; \u0026#34;25826\u0026#34;\u0026gt; #10.0.0.19是logstash主机的地址，25826是其监听的udp端口 \u0026lt;/server\u0026gt; \u0026lt;/Plugin sh 1 2 3 4 5 6 7 8 9 10 11 input udp { port =\u0026gt; \u0026#34;25826\u0026#34; codec =\u0026gt; collectd {} type =\u0026gt; \u0026#34;collectd\u0026#34; } output{ stdout{ codec =\u0026gt; rubydebug } } 4.1.3 redis redis插件主要作用在于从redis中获取数据。它支持redis中的两种方式；支持redis channel（发布频道）以及lists两种方式。logstash从redis读数据，可以基于列表方式进行。也可以基于channel方式进行。也就是说redis可以仅工作在正常模式下，不用启用channel机制，也一样能够使logstash从中获取数据。\n对于logstash而言，一般要求redis版本在2.6.0之后的版本。\n4.2 filter插件 filter插件主要用于将event通过output发出之前对其实现某些处理功能。对所收集到的数据当中，期望能够基于某种特定格式进行拆分、组合、过滤其中某些信息，都可以使用filter插件实现。在过滤前后如果有需要，都可以调用codec插件，事先基于某种形式对消息做编码。将处理过数据存往任何可存储位置，需要用到特定于存储位置的输出插件。logstash有众多filter过滤器。\n4.2.1 grok gork是logstash中最重要的插件之一，主要用于分析并结构化文本数据。从web服务器日志读取的日志事件是遵循同种格式的，如：我们只想统计有多少访问IP，但是获取的是一行信息。为了能够方便随后的统计操作，需要将每读到的任何一行日志实现切好。使得对日志分析能够得以进行。目前是logstash中将非结构化日志数据转化为结构化的可查询数据的不二之选。\n目前支持处理 syslog httpd nginx 等。默认情况下，logstash提供了120种默认grok模式。这些模式定义在patterns目录下，每个模式都有默认的名称。\nsh 1 rpm -ql logstash|grep \u0026#34;patterns$\u0026#34;; 4.2.2 gork语法格式 logstash中grok的语法格式是使用%{}括起的一组信息使用:隔开，其中左半段称之为SYNTAX由半段称之为SEMANTIC。其中SYNTAX表示grok模板预定义模式中已有的模式名称，SEMANTIC，匹配到的文本的自定义的标识符。\nsh 1 %{SYNTAX:SEMANTIC} 例:\nsh 1 2 3 10.0.0.1 GET /index.html 30 0.23 %{IP:client_ip} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration} 10.0.0.1 GET /index.html 30 0.23 { \u0026ldquo;message\u0026rdquo; =\u0026gt; \u0026ldquo;10.0.0.1 GET /index.html 30 0.23\u0026rdquo;, \u0026ldquo;@version\u0026rdquo; =\u0026gt; \u0026ldquo;1\u0026rdquo;, \u0026ldquo;@timestamp\u0026rdquo; =\u0026gt; \u0026ldquo;2018-05-31T10:34:37.150Z\u0026rdquo;, \u0026ldquo;host\u0026rdquo; =\u0026gt; \u0026ldquo;mysql\u0026rdquo;, \u0026ldquo;client_ip\u0026rdquo; =\u0026gt; \u0026ldquo;10.0.0.1\u0026rdquo;, \u0026ldquo;method\u0026rdquo; =\u0026gt; \u0026ldquo;GET\u0026rdquo;, \u0026ldquo;request\u0026rdquo; =\u0026gt; \u0026ldquo;/index.html\u0026rdquo;, \u0026ldquo;bytes\u0026rdquo; =\u0026gt; \u0026ldquo;30\u0026rdquo;, \u0026ldquo;duration\u0026rdquo; =\u0026gt; \u0026ldquo;0.23\u0026rdquo; }\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 input { stdin {} } filter { grok { match =\u0026gt; { \u0026#34;message\u0026#34; =\u0026gt; \u0026#34;%{IP:client_ip} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration}\u0026#34; } } } output { stdout { codec =\u0026gt; rubydebug } } 自定义grok模式\ngrok模式是基于正则表达式的模式编写，其元字符与其他用到正则表达式的工具awk\\sed\\grep\\pcre差别不大\nnginx log匹配方式\nsh 1 2 3 NGUSERNAME [a-zA-Z\\.\\@\\-\\+_%]+ NGUSER %{NGUSERNAME} NGINXACCESS %{IPORHOST:clientip} - %{NOTSPACE:remote_user} \\[%{HTTPDATE:timestamp}\\] \\\u0026#34;(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})\\\u0026#34; %{NUMBER:response} (?:%{NUMBER:bytes}|-) %{QS:referrer} %{QS:agent} %{NOTSPACE:http_x_forwarded_for} LOGSTASH+ELASTICSEARCH+KIBANA处理NGINX访问日志 - CSDN博客\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 input { file { path =\u0026gt; [\u0026#39;/var/log/nginx/access.log\u0026#39;] type =\u0026gt; \u0026#34;nginxlog\u0026#34; start_position =\u0026gt; \u0026#34;beginning\u0026#34; } } filter { grok { match =\u0026gt; { \u0026#34;message\u0026#34; =\u0026gt; \u0026#34;%{NGINXACCESS}\u0026#34; } } } output { stdout\t{ codec =\u0026gt; rubydebug } } action 将数据输出到至elasticsearch中后基于什么方式做操作 hosts es主机集群列表。数组 index 存储在es的哪个索引当中\nlogstash在使用redis做输入或输出插件时，有两种数据类型，用来保存logstash输出的数据list channel\nES5.4.0 记录 - CSDN博客\n18-elasticsearch集群健康为黄色 - CSDN博客\ncurl -X PUT 172.16.16.18:9200/gongsufanghua-work_record/_settings -d \u0026lsquo;{ \u0026ldquo;index\u0026rdquo; : { \u0026ldquo;number_of_replicas\u0026rdquo; : 0 } }\u0026rsquo;\nlogstash-input-jdbc同步mysql数据到elasticsearch - 运维学习之路 - SegmentFault 思否\nElasticsearch yellow unassigned_shards 恢复 replicas 节点恢复 - CSDN博客\nlogstash mysql 准实时同步到 elasticsearch - 简书\n5 conditionals 当需要在特定条件下过滤或输出事件。可以使用条件条件判断语句。Logstash中的条件查看和行为与编程语言中的条件相同。条件语句支持if，else if以及else和可以被嵌套。\nconditionals\n5.1 语法 sh 1 2 3 4 5 6 7 if EXPRESSION { ... } else if EXPRESSION { ... } else { ... } logstash支持以下比较运算符：\n== !=、 \u0026lt; \u0026gt; \u0026lt;= \u0026gt;= regexp：=~!~（检查右边的模式与左边的字符串值） 包含：in not in 布尔运算符是： and or nand xor（异或） 支持的一元运算符是：! 注：条件判断语句不包括数学运算符，如下写法logstash会报错\nsh 1 2 3 if [seg_num]\u0026lt;[total_seg]-1 { ... } 5.2 logstash常用条件判断语句 为了避免多个input的数据提交到Elasticsearch后共存于一个索引中。使用index参数来为每个不同的type建立单独的索引。\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 input{ file{ path=\u0026gt; \u0026#34;/usr/local/elasticsearch-2.3.2/logs/elasticsearch.log.*\u0026#34; type=\u0026gt; \u0026#34;elasticsearch\u0026#34; start_position=\u0026gt; \u0026#34;beginning\u0026#34; } file{ path=\u0026gt; \u0026#34;/var/log/secure\u0026#34; type=\u0026gt; \u0026#34;secure\u0026#34; start_position=\u0026gt; \u0026#34;beginning\u0026#34; } } output{ if [type] == \u0026#34;elasticsearch\u0026#34; { elasticsearch { hosts=\u0026gt; [\u0026#34;192.168.44.129:9200\u0026#34;] index=\u0026gt; \u0026#34;elasticsearch-%{+YYYY-MM}\u0026#34; } } if [type] == \u0026#34;secure\u0026#34; { elasticsearch { hosts=\u0026gt; [\u0026#34;192.168.44.129:9200\u0026#34;] index=\u0026gt; \u0026#34;secure-%{+YYYY-MM}\u0026#34; } } } 使用in运算符来测试字段是否包含特定字符串，键或（对于列表）元素：\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 filter { if [foo] in [foobar] { mutate { add_tag =\u0026gt; \u0026#34;field in field\u0026#34; } } if [foo] in \u0026#34;foo\u0026#34; { mutate { add_tag =\u0026gt; \u0026#34;field in string\u0026#34; } } if \u0026#34;hello\u0026#34; in [greeting] { mutate { add_tag =\u0026gt; \u0026#34;string in field\u0026#34; } } if [foo] in [\u0026#34;hello\u0026#34;, \u0026#34;world\u0026#34;, \u0026#34;foo\u0026#34;] { mutate { add_tag =\u0026gt; \u0026#34;field in list\u0026#34; } } if [missing] in [alsomissing] { mutate { add_tag =\u0026gt; \u0026#34;shouldnotexist\u0026#34; } } if !(\u0026#34;foo\u0026#34; in [\u0026#34;hello\u0026#34;, \u0026#34;world\u0026#34;]) { mutate { add_tag =\u0026gt; \u0026#34;shouldexist\u0026#34; } } } 在单个条件中指定多个表达式：\nsh 1 2 3 4 5 if [loglevel] == \u0026#34;ERROR\u0026#34; and [deployment] == \u0026#34;production\u0026#34; { pagerduty { ... } } ","permalink":"https://www.161616.top/logstash/","summary":"1 logstash概述 logstash是基于Jruby语言研发的server/agent结构的日志收集工具，可以在任何一个能产生日志的服务器上部署其agent。agent能同时监控多个产生日志的服务，并把每个服务所产生的日志信息收集并发送给logstash server端。由于收集到的日志信息是分散的，logstash有专门的节点用来将所有的节点收集到的日志信息按时间序列合并在一起形成一个序列，基于这一个序列请求写入并存储在elasticsearch集群中。\nkibana是基于node.js开发的elasticsearch的图形用户接口。它可以将用户的搜索语句发送给ES，有ES完成搜索，并且将结果返回。kibana可以将返回的数据，用非常直观的方式（画图）来做趋势展示的。\nlogstash基于多种数据获取机制，TCP/UDP协议、文件、syslog、windows EventLogs及STDIN等。同时也支持多种数据输出机制。获取到数据后，它支持对数据执行过滤、修改等操作。\nlogstash基于JRuby语言研发，需要运行在JVM虚拟机之上。工作于agent/server模型。\n1.1 logstash工作机制 在每一个产生日志的节点之上部署一个agent，这个agent通常称为shipper。shipper负责收集日志，并且发送给server端。但是在发送给server端时，为了避免server端可能无法应付大量节点同时发来的信息，会在server端和agent端之间放置一个消息队列，通常称之为broker。这个消息队列比较常见的使用redis。各agent将数据发送至broker，logstash服务器会从broker中依次取出日志拿来在本地做过滤、修改等操作。在完修改后将其发送至ES集群。\n对于logstash而言，server端也是插件式的工作的模式，与ES的不同在于，ES的插件是可有可无的。logstash的除核心外的所有功能都是基于插件来完成的。\n1.2 logstash工作流程 logstash的工作模式类似于管道，从指定位置读入数据，而后过滤数据，最后将其输出到指定位置。事实上logstash的工作流程为：input | filter | output。如无需对数据额外处理，filter可省略。\n1.3 logstash插件分类 input plugin：收集数据 codec plugin：编码插件 filter plugin：过滤 output plugin：输出插件 2 logstash安装下载 官网地址：https://www.elastic.co/cn/products../../images/Logstash\n3 logstash的配置使用 /etc../../images/Logstash/conf.d/路径下所有以.conf结尾的文件都被当做配置文件使用。对于logstash的配置文件定义，就是定义插件。从哪得到数据。向哪里输出数据。\n3.1 logstash的基本配置框架 sh 1 2 3 4 5 6 7 8 9 10 11 input{ .... } filter{ .... } output{ .... } 简单的\nsh 1 2 3 4 5 6 7 8 input{ stdin {} } output { stdout{ codec =\u0026gt;rubydebug } } 检查配置文件语法是否正确logstash -f /etc.","title":"Logstash使用"},{"content":"冒泡排序 图 https://www.cnblogs.com/onepixel/articles/7674659.html\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package main import ( \u0026#34;fmt\u0026#34; ) func bubbleSort(slice []int) []int { for n := 0; n \u0026lt;= len(slice); n++ { for i := 1; i \u0026lt; len(slice)-n; i++ { if slice[i] \u0026lt; slice[i-1] { slice[i], slice[i-1] = slice[i-1], slice[i] } } } return slice } func main() { var arr = [...]int{99, 51, 41, 2, 31} var rarr = bubble(arr[:]) fmt.Println(rarr) } 比较排序 go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package main import ( \u0026#34;fmt\u0026#34; ) func selectionSort(slice []int) []int { for n := 0; n \u0026lt;= len(slice); n++ { fmt.Println(slice) fmt.Println(\u0026#34;#####################\u0026#34;) for i := n + 1; i \u0026lt; len(slice); i++ { if slice[n] \u0026gt; slice[i] { slice[n], slice[i] = slice[i], slice[n] fmt.Println(slice) } } fmt.Println(\u0026#34;---------------\u0026#34;) } return slice } func main() { var arr = [...]int{99, 51, 41, 2, 31} var rarr = selectionSort(arr[:]) fmt.Println(rarr) } 插入排序 思路：将数组拆分为一个有序的，一个无序的。初始时下标0永远为有序数组。\n建立循环，从下标1开始到数组的长度，每个都与前一个进行对比，如果比前一个值小，就互相换位，当当前值比上一个值大时，说明当前值之前都是已经排序好的数组。就退出。\n例子：如该数组 [...]int{10, 56, 4, 654, 8, 997}，\n第一次循环时，n=1 i=1 56 \u0026gt; 10 跳出。\n第二次循环时，{10, 56, 4, 654, 8, 997} n=2 i=n=2， 4\u0026lt;56 互换，值为 {10, 4, 56, 654, 8, 997} ，内部循环继续进行，i=1 4\u0026lt;10互换为 [4 10 56 654 8 997] 。内部循环结束，条件为i\u0026lt;0\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package main import ( \u0026#34;fmt\u0026#34; ) func insertSort(arr []int) { for n := 1; n \u0026lt; len(arr); n++ { for i := n; i \u0026gt; 0; i-- { if arr[i] \u0026gt; arr[i-1] { break } arr[i], arr[i-1] = arr[i-1], arr[i] fmt.Println(arr) } } } func main() { var array = [...]int{10, 56, 4, 654, 8, 997} insertSort(array[:]) fmt.Println(array) } 快速排序 思路：以一个基准数将数组拆分为两个，一边大于这个数，一边小于这个数。从数组第0个开始，首先先记录此基准数的下标和值 {312, 84, 543, 5, 100, 23} ，k=0 v=312。需要传入一个从哪里开始到哪里的位置。这里基准数为第一个，顾循环位置就从0+1开始 循环完之后为 84 84 543 5 100 23\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package main import ( \u0026#34;fmt\u0026#34; ) func qsort(arr []int, start int, end int) { if start \u0026gt;= end { return } key := start value := arr[start] //记录当前基准值位置 fmt.Println(arr) for n := start + 1; n \u0026lt;= end; n++ { // a[n] \u0026lt; arr[start] if arr[n] \u0026lt; value { arr[key] = arr[n] //就将a[n]挪至arr[key]所在位置 arr[n] = arr[key+1] //a[n]空缺了，将arr[key]向后移动一位 // 理论上现在值为这个 {84, 84, 543, 5, 100, 23} key++ //key的位置改变了1位，key++ //最后在将进位后的arr[key] = 之前保存的value即为 // value=321 {84, 312, 543, 5, 100, 23} //这样完成了替换 } } arr[key] = value fmt.Println(\u0026#34;---------------------------\u0026#34;) //一轮循环后该数组为[84 5 100 23 312 543] //将基准数两边的数进行进行排序 此时 key=3 start=0 左边为 start-key-1 qsort(arr, start, key-1) qsort(arr, key+1, end) //右边为key+1-end } func main() { var array = [...]int{312, 84, 543, 5, 100, 23} qsort(array[:], 0, len(array)-1) fmt.Println(array) } ","permalink":"https://www.161616.top/go-datasort/","summary":"冒泡排序 图 https://www.cnblogs.com/onepixel/articles/7674659.html\ngo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package main import ( \u0026#34;fmt\u0026#34; ) func bubbleSort(slice []int) []int { for n := 0; n \u0026lt;= len(slice); n++ { for i := 1; i \u0026lt; len(slice)-n; i++ { if slice[i] \u0026lt; slice[i-1] { slice[i], slice[i-1] = slice[i-1], slice[i] } } } return slice } func main() { var arr = [.","title":"Go数组排序算法"},{"content":"Jenkins服务端：centos6.8\n客户端：windows server2012 windows10\n工具：cwRsync\n注：复制为jenkins工作目录到网站目录，无需服务端。\n配置安装slave端 所用的插件：Copy Data To Workspace Plugin\n配置windows节点 \\1. 主界面-\u0026gt;【系统管理】-\u0026gt;【管理节点】-\u0026gt;【新建节点】，进行节点的添加：\n\\2. 输入节点名称，选择【Permanent Agent】。如果添加过slave的话会出现【复制现有节点】操作\n\\3. 配置节点的详细信息\n此处配置需要注意的有以下几个方面\n【# of executors】：建议不要超过CPU核心数，一般不要写特别大。\n【远程工作目录】：master将代码库中的代码复制到slave时，存放的临时目录，如slave的daemon服务也会放在此目录。一个job一个文件夹。\n【用法】：选择【只允许运行绑定到这台机器的Job】，此模式下，Jenkins只会构建哪些分配到这台机器的Job。这允许一个节点专门保留给某种类型的Job。例如，在Jenkins上连续的执行测试，你可以设置执行者数量为1，那么同一时间就只会有一个构建，一个实行者不会阻止其它构建，其它构建会在另外的节点运行。\n【启动方式】：选择【Launch agent via Java Web Start】，以windows服务的方式启动，这个为最好配置的。注意：2.x版本的默认没有这个选项，需要单独开启。\n\\4. 配置slave端并且添加至windows服务\n在点击保存后，在node列表中会存在此列表默认是未连通状态\n点击进入详情页面会提示slave端的安装方法，此处讲解下载文件方式。\n【Launch】：浏览器下载文件方式\n【Run from agent command line】：从远端代理命令运行\n注意：这是java服务，每个slave端必须安装jdk后才可运行。\nxml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 \u0026lt;jnlp codebase=\u0026#34;http://10.0.0.11:8080/jenkins/computer/test/\u0026#34; spec=\u0026#34;1.0+\u0026#34;\u0026gt; \u0026lt;information\u0026gt; \u0026lt;title\u0026gt;Agent for test\u0026lt;/title\u0026gt; \u0026lt;vendor\u0026gt;Jenkins project\u0026lt;/vendor\u0026gt; \u0026lt;homepage href=\u0026#34;https://jenkins-ci.org/\u0026#34;/\u0026gt; \u0026lt;/information\u0026gt; \u0026lt;security\u0026gt; \u0026lt;all-permissions/\u0026gt; \u0026lt;/security\u0026gt; \u0026lt;resources\u0026gt;\u0026lt;j2se version=\u0026#34;1.8+\u0026#34;/\u0026gt; \u0026lt;jar href=\u0026#34;http://10.0.0.11:8080/jenkins/jnlpJars/remoting.jar\u0026#34;/\u0026gt; \u0026lt;/resources\u0026gt; \u0026lt;application-desc main-class=\u0026#34;hudson.remoting.jnlp.Main\u0026#34;\u0026gt; \u0026lt;argument\u0026gt;c55442e04b03c2fc721ec718b70646c234b4c79a678ff10ccadc59541dbb843\u0026lt;/argument\u0026gt; \u0026lt;argument\u0026gt;test1\u0026lt;/argument\u0026gt; \u0026lt;argument\u0026gt;-workDir\u0026lt;/argument\u0026gt; \u0026lt;argument\u0026gt;d:\\jenkins\u0026lt;/argument\u0026gt; \u0026lt;argument\u0026gt;-internalDir\u0026lt;/argument\u0026gt; \u0026lt;argument\u0026gt;remoting\u0026lt;/argument\u0026gt; \u0026lt;argument\u0026gt;-url\u0026lt;/argument\u0026gt; \u0026lt;argument\u0026gt;http://10.0.0.11:8080/jenkins/\u0026lt;/argument\u0026gt; \u0026lt;/application-desc\u0026gt;\u0026lt;/jnlp\u0026gt; 注意：每个slave的内容都不一样至。多个slave需要多次下载或修改此内容\n安装出现如下错误的原因，没有权限，使用管理员方式运行。\n这种文件右键没有管理员方式运行的菜单，打开【任务管理器】-\u0026gt;【运行】-\u0026gt;【以管理员方式运行】\n卸载系统服务方式:\na sc delete jenkinsslave-c__jenkins 安装完成后slave设置的远端目录会生成如下文件\n返回master的节点列表里，发现此处已经连接上了。\n新建工程 选择自由构建方式。\n【Restrict where this project can be run】：限制运行此项目的节点为刚才设置node时标签填写的windows。\n下载安装插件后会出现此选项。实测，填写路径没什么卵用。\n此处选择执行windows命令\n注：此处存在以下问题。\n1、此处如果代码库中不存在此文件，或更新后此文件被删除，那么使用xcopy会存在代码库中的文件以删除，而slave node文件夹中的文件还存在。无法清除。解决方法使用rsync –delete 或执行脚本文件进行判断。\n2、如slave node中需要存在代码库中不存在的文件，使用rsync会将需要存在的文件删除。\n3、此处无环境变量，执行命令需要使用全路径，不能存在中文和空格\nbat 1 2 3 $ rsync -avz ./ /cygdrive/c/test1/ --delete --exclude=.svn xcopy /y /e /r ./ /cygdrive/c/test1/ ","permalink":"https://www.161616.top/jenkins-in-windows/","summary":"Jenkins服务端：centos6.8\n客户端：windows server2012 windows10\n工具：cwRsync\n注：复制为jenkins工作目录到网站目录，无需服务端。\n配置安装slave端 所用的插件：Copy Data To Workspace Plugin\n配置windows节点 \\1. 主界面-\u0026gt;【系统管理】-\u0026gt;【管理节点】-\u0026gt;【新建节点】，进行节点的添加：\n\\2. 输入节点名称，选择【Permanent Agent】。如果添加过slave的话会出现【复制现有节点】操作\n\\3. 配置节点的详细信息\n此处配置需要注意的有以下几个方面\n【# of executors】：建议不要超过CPU核心数，一般不要写特别大。\n【远程工作目录】：master将代码库中的代码复制到slave时，存放的临时目录，如slave的daemon服务也会放在此目录。一个job一个文件夹。\n【用法】：选择【只允许运行绑定到这台机器的Job】，此模式下，Jenkins只会构建哪些分配到这台机器的Job。这允许一个节点专门保留给某种类型的Job。例如，在Jenkins上连续的执行测试，你可以设置执行者数量为1，那么同一时间就只会有一个构建，一个实行者不会阻止其它构建，其它构建会在另外的节点运行。\n【启动方式】：选择【Launch agent via Java Web Start】，以windows服务的方式启动，这个为最好配置的。注意：2.x版本的默认没有这个选项，需要单独开启。\n\\4. 配置slave端并且添加至windows服务\n在点击保存后，在node列表中会存在此列表默认是未连通状态\n点击进入详情页面会提示slave端的安装方法，此处讲解下载文件方式。\n【Launch】：浏览器下载文件方式\n【Run from agent command line】：从远端代理命令运行\n注意：这是java服务，每个slave端必须安装jdk后才可运行。\nxml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 \u0026lt;jnlp codebase=\u0026#34;http://10.0.0.11:8080/jenkins/computer/test/\u0026#34; spec=\u0026#34;1.0+\u0026#34;\u0026gt; \u0026lt;information\u0026gt; \u0026lt;title\u0026gt;Agent for test\u0026lt;/title\u0026gt; \u0026lt;vendor\u0026gt;Jenkins project\u0026lt;/vendor\u0026gt; \u0026lt;homepage href=\u0026#34;https://jenkins-ci.","title":"Jenkins在windows平台自动化构建代码"},{"content":"在Kubernetes之上，在节点级提供一个存储卷的方式来持久存储数据的逻辑，这种只具备一定程度上的持久性。为了实现更强大的持久性，应该使用脱离节点而存在的共享存储设备。 为此Kubernetes提供了不同类型的存储卷。\n大多数和数据存储服务相关的应用，和有状态应用几乎都是需要持久存储数据的。容器本身是有生命周期的，为了使容器终结后可以将其删除，或者编排至其他节点上去运行。意味着数据不能存储在容器本地。一旦Pod故障就会触发重构。如果将数据放置在Pod自有的容器内名称空间中，数据随着Pod终结而结束。为了突破Pod生命周期的限制，需要将数据放置在Pod自有文件系统之外的地方。\n存储卷\n对Kubernetes来讲，存储卷不属于容器，而属于Pod。因此，在Kubernetes中同一个Pod内的多个容器可共享访问同一组存储卷。\nPod底部有一个基础容器， ==pause==，但是不会启动。pause是基础架构容器。创建Pod时pause时Pod的根，所有Pod，包括网络命名空间等分配都是分配给pause的。在Pod中运行的容器是pause的网络名称空间的。容器在挂载存储卷时，实际上是复制pause的存储卷。\n因此为了真的实现持久性，存储卷应为宿主机挂载的外部存储设备的存储卷。如果需要实现跨节点持久，一般而言需要使用脱离节点本地的网络存储设备（ceph、glusterfs、nfs）来实现。节点如果需要使用此种存储的话，需要可以驱动相应存储设备才可以（在节点级可以访问相应网络存储设备）。\nk8s之上可使用的存储卷 Kubernetes支持的存储卷类型\nempryDir：只在节点本地使用的，用于做临时目录，或当缓存使用。一旦Pod删除，存储卷一并被删除。empryDir背后关联的宿主机目录可以使宿主机的内存。 hostPath：使宿主机目录与容器建立关联关系。 网络存储 传统的SAN（iSCSI，FC）NAS（常见用法协议 NFS,cifs,http）设备所构建的网络存储设备。 分布式存储（分机系统或块级别），glusterfs，ceph(rbd ceph的块接口存储)，cephfs等。 云存储：EBS（弹性块存储）亚马逊 ,Azure Disk 微软。此模型只适用于Kubernetes集群托管在其公有云之上的场景。 使用kubectl explain pod.spec.volumes查看Kubernetes所支持的存储类型。\nemptyDir 语法\nemptyDir medium 媒介类型 empty string （disk 默认） or memory sizeLimit 空间上限 定义完存储卷之后，需要在container当中使用volumeMounts指明挂载哪个或哪些个存储卷\nyaml 1 2 3 4 5 - container - mountPath 挂载路径 - name 挂载那个卷 - readOnly 是否只读挂载 - subPath 是否挂载子路径之下 yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 apiVersion: v1 kind: Pod metadata: name: my-nginx namespace: default spec: containers: - name: busybox image: busybox imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 command: [\u0026#34;tail\u0026#34;] volumeMounts: # 指明挂载哪一个存储卷 - name: html mountPath: /data/web/html # 指明挂载到容器的哪个路径下 volumes: - name: html emptyDir: {} # 表示空映射，都使用默认值，大小不限制，使用磁盘空间，而不是不定义 在Kubernetes中 $()是变量引用\ngitRepo 将git仓库当做存储卷来使用，其实并不是Pod将git仓库当存储卷来使用。只不过是在Pod创建时，会自动连接到git仓库之上（此链连接依赖于宿主机上有git命令来完成），由宿主机驱动，将git仓库中的内容clone到本地来，并且将其作为存储卷挂载至Pod之上。\n==gitRepo是建立在emptyDir之上==。所不同的在于，将所指定仓库的内容clone下来并放至空目录中。因此主容器将此目录当做服务于用户的数据来源。需要注意的是，在此处做的修改是不会同步到git仓库中去的。\n如果git仓库在Pod运行过程中内容发生改变，Pod之内的存储卷内容是不会随之改变的。\ngitRepo 卷示例：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 apiVersion: v1 kind: Pod metadata: name: my-git namespace: default spec: containers: - name: git image: nginx imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 volumeMounts: - name: git-volumes mountPath: /data/web/html volumes: - name: git-volumes gitRepo: repository: \u0026#34;https://github.com/potester/test-k8s.git\u0026#34; revision: \u0026#34;master\u0026#34; 查看Pod内容器挂载的目录\nsh 1 2 3 4 5 6 $ kubectl exec -it my-git -- ls -l /data/web/html/test-k8s/ total 16 nginx.svc redis.svc svc-redis test.yaml hostPath hostPath 将Pod所在宿主机之上、脱离Pod中容器名称空间之外的宿主机的文件系统的某一目录与Pod建立关联关系。在Pod被删除时，此存储卷是不会被删除的。\nhostPath在一定程度上拥有持久的特性，但这种持久只是节点级的持久，在被跨节点调度时，这些数据还是会丢失的。\nVolumes - Kubernetes\ntype\nDirectoryOrCreate 挂载路径在宿主机上是已存在的目录，如目录不存在则创建此目录。 Directory 挂载路径在宿主机上必须已存在的目录。 FileOrCreate 文件或创建新的空文件。 File 必须存在此文件，将其挂载至容器中。 Socket 必须是socket类型的文件。 CharDevice 必须是一个字符类型的设备文件。 BlockDevice 块类型的设备文件。 hostPath 卷示例：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 apiVersion: v1 kind: Pod metadata: name: pod-hostPath namespace: default spec: containers: - name: myapp image: nginx:1.8 volumeMounts: - name: html mountPath: /usr/share/nginx/html/ # 挂载路径 volumes: # 定义存储卷 - name: html hostPath: path: /data/html type: DirectoryOrCreate NFS，对于nfs存储卷来讲 path -required- nfs服务器导出路径， readOnly 只读 true or false 默认false server [required] 服务器地址 yaml 1 2 3 4 5 volumes: - name: html nfs: path: /data/volumes/ server: nfs01.test.com PVC使用逻辑 在Pod中只需定义存储卷，定义时只需指定使用存储卷大小，这个存储卷叫PVC类型的存储卷。PVC存储卷必须与当前名称空间中的PVC建立直接绑定关系，而PVC必须与PV建立绑定关系，而PV是某个真实存储设备上的存储空间。所以PV和PVC是kubernetes系统之上的抽象的标准资源。PV和PVC之间的关系，在PVC不被调用时是空载的。\n对于PV类型的资源的使用\nPVC语法 PVC是标准K8S资源，也有自己所属的属组。\nkubectl explain pvc\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: v1 kind: pvc metadata: name: k8s-pvc namespaces: default spec: accessModes # 访问模型 resources # 资源限制，至少多少G selector # 标签选择器，必须要选择哪个PV建立关联关系 storageClassName # 存储类名称 volumeMode # 后端存储卷模式。 volumeName # 存储卷名称，指后端PersistentVolume PVC选择的模式\n使用存储卷名称，一对一绑定了（精确选择）， 选择器选定 如不指定名称，会从大量符合条件的PV选一个。 类型限制，volumeMode，那一类型的PV可以被当前claim所使用。 在Pod中使用当前名称空间已经存在的PVC exportfs -arv kubectl explain pods.spec.volumes.persistentVolumeClaim，PV和PVC的关联是一对一的，一旦被使用（状态为banding）\nyaml 1 2 3 4 volmes persistentVolumeClaim claimName # pvc名称 readOnly # 要不要只读 定义PV时一定不要加名称空间，PV是集群级别的，不属于名称空间，但==PVC是属于名称空间级别==的。PVC并不属于节点(node)，PVC是标准的Kubernetes资源，它存储在etcd当中。只有Pod才需要运行在节点之上，所有其他资源基本都是保存在集群状态存储（apiserver的存储）etcd当中。\n在Kubernetes新版本中，只要PV还被PVC绑定，就不支持删除。\n定义accessMode时需要注意存储设备，有些存储设备不支持多路读写与多路只读，只支持单路读写。\naccessMode []string accessMode可定义多个参数 ReadWriteOnce 单路读写，可简写为RWO ReadOnlyMany 多路只读，ROX ReadWriteMany 多路读写操作 RWX capacity 用来指定存储空间的大小，需要使用资源访问模型来定义。 capacity resources.md persistentVolumeReclaimPolicy 回收策略 Persistent Volumes Retain 保留。 Recycle 回收，将数据删除，并且把PV置为空闲状态可以让其他设备绑定。 delete 删除PV yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: v1 kind: PersistentVolume metadata: name: pv01 labels: name: pv01 type: ssd spec: nfs: path: /data/pv01 server: 192.168.1.2 accessModes: [\u0026#34;ReadWriteMany\u0026#34;,\u0026#34;ReadWriteOnce\u0026#34;] capacity: storage: 200Mi PVC\nspec accessModes PVC也需要定义accessModes，此accessModes模式要求一定是PV的accessModes的子集才可以被匹配到。 resources 如指定，PV一定要满足（大于、等于）PVC此值才能被使用。 requests []map此处是与PV不一样之处，需要要求有多大空间 PVC的使用\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 apiVersion: v1 kind: PersistentVolume metadata: name: pv01 labels: name: pv-test1 spec: accessModes: - ReadWriteOnce capacity: storage: 2Gi nfs: path: /data/v1 server: 10.0.0.11 --- apiVersion: v1 kind: PersistentVolume metadata: name: pv02 labels: name: pv-test2 spec: accessModes: - ReadWriteOnce capacity: storage: 2Gi nfs: path: /data/v2 server: 10.0.0.11 --- apiVersion: v1 kind: PersistentVolume metadata: name: pv03 labels: name: pv-test3 spec: accessModes: - ReadWriteOnce capacity: storage: 2Gi nfs: path: /data/v3 server: 10.0.0.11 查看PV和PVC\nsh 1 2 3 4 5 6 7 8 9 $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv01 2Gi RWO Retain Bound default/pvc1 5h45m pv02 2Gi RWO Retain Available 5h45m pv03 2Gi RWO Retain Available 5h45m $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pvc1 Bound pv01 2Gi RWO 5h11m StorageClass 在PVC申请时，未必就有现成的PV能正好符合PVC在申请中指定的条件，为此Kubernetes设计了一种工作逻辑，能够让PVC在申请PV时不针对某个PV进行。可以针对某个存储类（Kubernetes之上的标准资源之一）StorageClass，借助此资源层，来完成资源分配的。\nStorageClass可以理解为，事先把众多的存储设备当中所提供好的现有可用存储空间（尚未做成PV的存储空间）进行分类（根据综合服务质量、IO性能等）。定义好存储类之后，当PVC再去申请PV时，不针对某个PV直接进行，而是针对存储了进行。必须让存储设备支持Restful风格的请求创建接口，用户可以通过Restful风格的请求1.在磁盘上划分刚好符合PVC大小的分区。2 编辑/etc/export文件，将分区挂载至本地某个目录上。3. 动态创建PV去绑定之前动态导出的空间。\nnfs 动态\nexternal-storage/nfs-client at master · kubernetes-incubator/external-storage · GitHub\nDocker(二十九)k8s 创建动态存储，基于nfs 的storageclass-洒脱，是云谈风轻的态度-51CTO博客\n通过consul、confd，动态为prometheus添加监控目标和告警规则\n使用Prometheus建设Kubernetes的监控告警系统\n","permalink":"https://www.161616.top/k8s-volumes/","summary":"在Kubernetes之上，在节点级提供一个存储卷的方式来持久存储数据的逻辑，这种只具备一定程度上的持久性。为了实现更强大的持久性，应该使用脱离节点而存在的共享存储设备。 为此Kubernetes提供了不同类型的存储卷。\n大多数和数据存储服务相关的应用，和有状态应用几乎都是需要持久存储数据的。容器本身是有生命周期的，为了使容器终结后可以将其删除，或者编排至其他节点上去运行。意味着数据不能存储在容器本地。一旦Pod故障就会触发重构。如果将数据放置在Pod自有的容器内名称空间中，数据随着Pod终结而结束。为了突破Pod生命周期的限制，需要将数据放置在Pod自有文件系统之外的地方。\n存储卷\n对Kubernetes来讲，存储卷不属于容器，而属于Pod。因此，在Kubernetes中同一个Pod内的多个容器可共享访问同一组存储卷。\nPod底部有一个基础容器， ==pause==，但是不会启动。pause是基础架构容器。创建Pod时pause时Pod的根，所有Pod，包括网络命名空间等分配都是分配给pause的。在Pod中运行的容器是pause的网络名称空间的。容器在挂载存储卷时，实际上是复制pause的存储卷。\n因此为了真的实现持久性，存储卷应为宿主机挂载的外部存储设备的存储卷。如果需要实现跨节点持久，一般而言需要使用脱离节点本地的网络存储设备（ceph、glusterfs、nfs）来实现。节点如果需要使用此种存储的话，需要可以驱动相应存储设备才可以（在节点级可以访问相应网络存储设备）。\nk8s之上可使用的存储卷 Kubernetes支持的存储卷类型\nempryDir：只在节点本地使用的，用于做临时目录，或当缓存使用。一旦Pod删除，存储卷一并被删除。empryDir背后关联的宿主机目录可以使宿主机的内存。 hostPath：使宿主机目录与容器建立关联关系。 网络存储 传统的SAN（iSCSI，FC）NAS（常见用法协议 NFS,cifs,http）设备所构建的网络存储设备。 分布式存储（分机系统或块级别），glusterfs，ceph(rbd ceph的块接口存储)，cephfs等。 云存储：EBS（弹性块存储）亚马逊 ,Azure Disk 微软。此模型只适用于Kubernetes集群托管在其公有云之上的场景。 使用kubectl explain pod.spec.volumes查看Kubernetes所支持的存储类型。\nemptyDir 语法\nemptyDir medium 媒介类型 empty string （disk 默认） or memory sizeLimit 空间上限 定义完存储卷之后，需要在container当中使用volumeMounts指明挂载哪个或哪些个存储卷\nyaml 1 2 3 4 5 - container - mountPath 挂载路径 - name 挂载那个卷 - readOnly 是否只读挂载 - subPath 是否挂载子路径之下 yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 apiVersion: v1 kind: Pod metadata: name: my-nginx namespace: default spec: containers: - name: busybox image: busybox imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 command: [\u0026#34;tail\u0026#34;] volumeMounts: # 指明挂载哪一个存储卷 - name: html mountPath: /data/web/html # 指明挂载到容器的哪个路径下 volumes: - name: html emptyDir: {} # 表示空映射，都使用默认值，大小不限制，使用磁盘空间，而不是不定义 在Kubernetes中 $()是变量引用","title":"Kubernetes存储卷"},{"content":"secret、configMap特殊类型的存储卷，多数情况下不是为Pod提供存储空间来用的，而是给管理员或用户提供了从集群外部向Pod内部应用注入配置信息的方式。\n工作实现\nconfigMap 在集群内部存在一个名称空间，在名称空间当中拥有一个可正常运行的Pod，当镜像启动时使用的配置文件在做镜像之前就确定了，并且做完镜像就不能修改了。除非在做镜像时使用entryPoint脚本去接受用户启动容器时传入环境变量进来，将环境变量的数据替换到配置文件中去，从而使应用程序在启动之前就能获得一个新的配置文件而后得到新的配置。当需要修改配置文件时是很麻烦的。而配置中心只需将集中的配置文件修改，并通知给相应进程，让其重载配置文件。而Kubernetes的应用也存在此类问题，当配置文件修改后就需要更新整个镜像。因此无需将配置信息写死在镜像中。而是引入一个新的资源，这个资源甚至是整个Kubernetes集群上的一等公民（标准的K8S资源）。这个资源被叫做configMap\nconfigMap当中存放的配置信息，随后启动每一个Pod时，Pod可以共享使用同一个configMap资源，这个资源对象可以当存储卷来使用，也可以从中基于环境变量方式从中获取到一些数据传递给环境变量，注入到容器中去使用。 因此configMap扮演了Kubernetes中的配置中心的功能。但是configMap是明文存储数据的。因此和configMap拥有同样功能的标准资源secret就诞生了。与configMap所不同之处在于，secret中存放的数据是用过编码机制进行存放的。\n核心作用：让配置信息从镜像中解耦，从而增强应用的可移植性与复用性。使一个镜像文件可以为应用程序运行不同配置的环境而工作。简单来讲，一个configMap就是一系列配置数据的集合。这些数据可以注入到Pod对象中的容器所使用。\n在configMap中，所有的配置信息都保存为key value格式。V只是代表了一段配置信息，可能是一个配置参数，或整个配置文件信息都是没有问题的。\n配置容器化应用的方式\n自定义命令行参数 args [] 把配置文件直接陪进镜像； 环境变量 Cloud Native的应用程序一般可直接通过环境变量加载配置 通过entrypoint脚本来预处理变量为配置文件中的配置信息。 存储卷 配置文件注入方式：\n将configMap做存储卷 使用env docker config\ncontioners env name 变量名 value 变量值 valueFrom 数据不是一个字符串，而是引用另外一个对象将其传递给这个变量。 configMapKeyRef configMap中的某个键 fieldRef 某个字段。此资源可以是Pod自身的字段。如metadata.labels status.hostIP status.podIP resourceFieldRef 资源需求和资源限制。 secreKeyRef 引用secre configMap无需复杂描述，因此没有spec字段\ntext 1 2 3 4 apiVersion kind data binaryData 一般情况下data与binaryData只使用其中一种。 创建简单的configMap还可以使用 kubectl create configMap来创建。如需要长期使用，可以定义为配置清单文件。\ntext 1 2 3 kubectl create configmap nginx-config \\ --from-literal=nginx_port=80 \\ --from-literal=servername=test.com 使用文件创建\ntext 1 kubectl create configmap nginx-conf --from-file=www.conf 将configMap注入到Pod中。\n方式1：使用环境变量方式注入\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 env: - name: NGINX_SERVER_PORT valueFrom: configMapKeyRef: name: nginx-config key: nginx_port optional # 如为true表示必须拥有此key - name: NGINX_SERVER_NAME valueFrom: configMapKeyRef: name: nginx-config key: server_name 当使用环境变量方式注入时，只在系统启动时有效，如使用存储卷方式定义的，是可以实时更新的。\n方式2：\nyaml 1 2 3 4 5 6 7 8 9 10 containers: - name: nginx-configMap volumeMounts: - name: nginxconfig mountPaht: /etc/nginx/con.d/ readOnly: true # 不需要容器去修改他的内容 volumes: - name: nginxconfig configMap: # volume类型 name: nginx-conf k8s 节点为了运行Pod，而获取镜像，镜像如果托管在必须认证才能获取的私有仓库上时。node节点上的kubelet需能自动完成认证。\ndocker-registry docker私有仓库认证信息使用 generic tls 证书私钥\nspec\nimagePullSecrets Pod在创建时，如果要连到私有仓库需要做认证，此处的secret包含了让kubelet去连接私有仓库的账号和密码。此账号密码是secret对象提供的， 必须是专用对象。 创建方法\ntext 1 kubectl create secret type(docker-registry|generic|tls) Name 查询\ntext 1 kubectl get secret passwd -o yaml env方式载入secret\nyaml 1 2 3 4 5 6 7 env: - name: MYSQL_ROOT_PASSWD valueFrom: secretKeyRef: name: root-pwd key: passwd optional # 如为true表示必须拥有此key ","permalink":"https://www.161616.top/k8s-cm/","summary":"secret、configMap特殊类型的存储卷，多数情况下不是为Pod提供存储空间来用的，而是给管理员或用户提供了从集群外部向Pod内部应用注入配置信息的方式。\n工作实现\nconfigMap 在集群内部存在一个名称空间，在名称空间当中拥有一个可正常运行的Pod，当镜像启动时使用的配置文件在做镜像之前就确定了，并且做完镜像就不能修改了。除非在做镜像时使用entryPoint脚本去接受用户启动容器时传入环境变量进来，将环境变量的数据替换到配置文件中去，从而使应用程序在启动之前就能获得一个新的配置文件而后得到新的配置。当需要修改配置文件时是很麻烦的。而配置中心只需将集中的配置文件修改，并通知给相应进程，让其重载配置文件。而Kubernetes的应用也存在此类问题，当配置文件修改后就需要更新整个镜像。因此无需将配置信息写死在镜像中。而是引入一个新的资源，这个资源甚至是整个Kubernetes集群上的一等公民（标准的K8S资源）。这个资源被叫做configMap\nconfigMap当中存放的配置信息，随后启动每一个Pod时，Pod可以共享使用同一个configMap资源，这个资源对象可以当存储卷来使用，也可以从中基于环境变量方式从中获取到一些数据传递给环境变量，注入到容器中去使用。 因此configMap扮演了Kubernetes中的配置中心的功能。但是configMap是明文存储数据的。因此和configMap拥有同样功能的标准资源secret就诞生了。与configMap所不同之处在于，secret中存放的数据是用过编码机制进行存放的。\n核心作用：让配置信息从镜像中解耦，从而增强应用的可移植性与复用性。使一个镜像文件可以为应用程序运行不同配置的环境而工作。简单来讲，一个configMap就是一系列配置数据的集合。这些数据可以注入到Pod对象中的容器所使用。\n在configMap中，所有的配置信息都保存为key value格式。V只是代表了一段配置信息，可能是一个配置参数，或整个配置文件信息都是没有问题的。\n配置容器化应用的方式\n自定义命令行参数 args [] 把配置文件直接陪进镜像； 环境变量 Cloud Native的应用程序一般可直接通过环境变量加载配置 通过entrypoint脚本来预处理变量为配置文件中的配置信息。 存储卷 配置文件注入方式：\n将configMap做存储卷 使用env docker config\ncontioners env name 变量名 value 变量值 valueFrom 数据不是一个字符串，而是引用另外一个对象将其传递给这个变量。 configMapKeyRef configMap中的某个键 fieldRef 某个字段。此资源可以是Pod自身的字段。如metadata.labels status.hostIP status.podIP resourceFieldRef 资源需求和资源限制。 secreKeyRef 引用secre configMap无需复杂描述，因此没有spec字段\ntext 1 2 3 4 apiVersion kind data binaryData 一般情况下data与binaryData只使用其中一种。 创建简单的configMap还可以使用 kubectl create configMap来创建。如需要长期使用，可以定义为配置清单文件。\ntext 1 2 3 kubectl create configmap nginx-config \\ --from-literal=nginx_port=80 \\ --from-literal=servername=test.com 使用文件创建","title":"kubernetes概念 - configMap"},{"content":"基于web的UI前端，认证是由Kubernetes完成的。登陆dashboard的密码是k8s的账号和密码，和dashboard自身没有关系。dashboard自身不做认证。\ntext 1 kubectl patch svc kubernetes-dashboard -p\u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;type\u0026#34;:\u0026#34;NodePort\u0026#34;}}\u0026#39;-n kube-system 如使用域名访问，CN一定要与域名保持一致。\ntext 1 2 3 4 (umask 077; openssl genrsa -out dashboard.key 2048) openssl req -new -key dashboard.key -out dashboard.csr -subj \u0026#34;/O=test/CN=dashboard\u0026#34; openssl req -in dashboard.csr -noout -text openssl x509 -req -in dashboard.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out dashboard.crt -days 3650 Certificate Attributes\n要想穿透集群边界，从集群外访问集群内部某一服务或Pod上的容器的应用，有两种方式 nodePort、NodeBlanc 或ingress\ntext 1 2 3 4 5 kubectl create secret generic \\ dashboard-cert \\ -n kube-system \\ --from-file=dashboard.crt=./dashboard.crt \\ --from-file=dashboard.key=./dashboard.key dashboard运行在Pod中时，当用户通过浏览器来进行登陆时，所提供的认证证书必须时serviceaccount，\ntext 1 kubectl create serviceaccount dashboard-admin -n kube-system 通过rolebindding吧对应的dashboard-admin和集群管理员建立起绑定关系，否则无法透过rbac的权限检查。\n指明serviceaccount时，必须指明是哪个名称空间的的哪个账号，格式：namespace:serviceaccount\ntext 1 kubectl create clusterrolebinding dashboard-cluster-admin --clusterrole=cluster-admin --serviceaccount=kube-system:dashboard-admin 使serviceaccount用户，能够有权限访问整个集群级别的资源。\n查询dashboard-admin的secret，这个是自动生成的。\ntext 1 kubectl get secret $(kubectl get secret -n kube-system|grep dashboard-admin-token|awk \u0026#39;{print $1}\u0026#39;) -n kube-system -o jsonpath={.data.token}|base64 -d kubectl config set-credentials 命令，用户的认证方式，既可以使用证书方式，也可以使用token认证。\n在apply之前先将证书做成secret，apply操作会将其加载成为apply操作对外提供https服务时使用的证书。此操作作为serviceaccount认证是没有关系的，只不过是被dashboard用来做https证书的。如果不提供证书，dashboard会自动生成新的证书。\nkubernetes dashboard延长自动超时注销 方法1：部署清单时，修改yaml文件，添加 container.Args 增加 --token-ttl=43200 其中43200是设置自动超时的秒数。也可以设置 token-ttl=0 以完全禁用超时。\n方法2：操作已经部署的配置，kubectl edit deployment -n kube-system kubernetes-dashboard，新增上面参数到 args 中\n","permalink":"https://www.161616.top/k8s-dashboard/","summary":"基于web的UI前端，认证是由Kubernetes完成的。登陆dashboard的密码是k8s的账号和密码，和dashboard自身没有关系。dashboard自身不做认证。\ntext 1 kubectl patch svc kubernetes-dashboard -p\u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;type\u0026#34;:\u0026#34;NodePort\u0026#34;}}\u0026#39;-n kube-system 如使用域名访问，CN一定要与域名保持一致。\ntext 1 2 3 4 (umask 077; openssl genrsa -out dashboard.key 2048) openssl req -new -key dashboard.key -out dashboard.csr -subj \u0026#34;/O=test/CN=dashboard\u0026#34; openssl req -in dashboard.csr -noout -text openssl x509 -req -in dashboard.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out dashboard.crt -days 3650 Certificate Attributes\n要想穿透集群边界，从集群外访问集群内部某一服务或Pod上的容器的应用，有两种方式 nodePort、NodeBlanc 或ingress\ntext 1 2 3 4 5 kubectl create secret generic \\ dashboard-cert \\ -n kube-system \\ --from-file=dashboard.","title":"kubernetes概念 - Dashboard"},{"content":"IngressController比较独特，它与DaemonSet、Deployment、Repliacaset不同，DaemonSet、Deployment等控制器是作为ControllerManager的子组件存在的。Ingress Controller是独立运行的一组Pod资源，通常是拥有七层代理、调度能力的应用程序。\n通常在使用IngressController时有三种选择Nginx、Traefik、Envoy。\nIngressController nginx运行在Pod中，其配置文件是在Pod中。后端代理的Pod随时会发生变动，IngressController需要watch API当中的后端Pod资源的改变。IngressController自身无法识别目前符合自己关联的（条件的）被代理的Pod资源有哪些，IngressController需借助service来实现。\n因此要想定义一个对应的调度功能，还需要创建service，此service通过label selector关联至每一个upstream服务器组，通过此service资源关联至后端的Pod。此service不会被当做被代理时的中间节点，它仅仅是为Pod做分类的。此service关联的Pod，就将其写入upstream中。\n在Kubernetes中有一种特殊资源叫做Ingress，当Pod发生改变时，其servcie对应的资源也会发生改变， 依赖于IngressResource将变化结果反应至配置文件中。\nIngress定义期望IngressController如何创建前段代理资源（虚拟主机、Url路由映射），同时定义后端池（upstream）。upstream中的列表数量，是通过service获得。\nIngress可以通过编辑注入到IngressController中，并保存为配置文件，且Ingress发现service选定的后端Pod资源发生改变，此改变会及时反映至Ingress中，Ingress将其注入到前端调度器Pod中，并触发Pod中的container主进程（nginx）重载配置文件。\n要想使用Ingress功能，需要有service对某些后端资源进行分类，而后Ingress通过分类识别出Pod的数量和IP地址信息，并将反映结果生成配置信息注入到upstream中。\nIngressController根据自身需求方式来定义前端，而后根据servcie收集到的后端Pod IP定义成upstream server，将这些信息反映在Ingress server当中，由Ingress动态注入到IngressController当中。\nIngress也是标准的Kubernetes资源，定义Ingress时同样类似于Pod方式来定义。使用kubectl explain Ingress查看帮助。\nspec rules 规则，对象列表 host 主机调度 虚拟主机而非url映射 http paths 路径调度 backend path backend 定义被调度的后端主机，靠service定义，找到后端相关联的Pod资源。 serviceName 后端servcie名称，即用来关联Pod资源的service。 servicePort IngressController部署\nnamespace.yaml 创建名称空间 configmap.yaml 为nginx从外部注入配置的 rbac.yaml 定义集群角色、授权。必要时让IngressController拥有访问他本身到达不了的名称空间的权限\ningress.yaml\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress-myapp namespace: defualt # 与deployment和要发布的service处在同一名称空间内 annotations: kubernetes.io/ingress.class: \u0026#34;nginx\u0026#34; spec: rules: - host: test.baidu.com # 这个是外部访问域名，service映射到主机节点地址上 http: paths: - pach: backend: serviceName: myapp servicePort: 80 证书是不能直接贴入ingress中的，需要将其转为特殊格式 secret， secret是标准的Kubernetes对象，c可以直接注入到Pod中被Pod所引用。\nKubernetes的负载均衡问题(Nginx Ingress) - ericnie - 博客园\nInstallation Guide - NGINX Ingress Controller\n","permalink":"https://www.161616.top/k8s-ingress/","summary":"IngressController比较独特，它与DaemonSet、Deployment、Repliacaset不同，DaemonSet、Deployment等控制器是作为ControllerManager的子组件存在的。Ingress Controller是独立运行的一组Pod资源，通常是拥有七层代理、调度能力的应用程序。\n通常在使用IngressController时有三种选择Nginx、Traefik、Envoy。\nIngressController nginx运行在Pod中，其配置文件是在Pod中。后端代理的Pod随时会发生变动，IngressController需要watch API当中的后端Pod资源的改变。IngressController自身无法识别目前符合自己关联的（条件的）被代理的Pod资源有哪些，IngressController需借助service来实现。\n因此要想定义一个对应的调度功能，还需要创建service，此service通过label selector关联至每一个upstream服务器组，通过此service资源关联至后端的Pod。此service不会被当做被代理时的中间节点，它仅仅是为Pod做分类的。此service关联的Pod，就将其写入upstream中。\n在Kubernetes中有一种特殊资源叫做Ingress，当Pod发生改变时，其servcie对应的资源也会发生改变， 依赖于IngressResource将变化结果反应至配置文件中。\nIngress定义期望IngressController如何创建前段代理资源（虚拟主机、Url路由映射），同时定义后端池（upstream）。upstream中的列表数量，是通过service获得。\nIngress可以通过编辑注入到IngressController中，并保存为配置文件，且Ingress发现service选定的后端Pod资源发生改变，此改变会及时反映至Ingress中，Ingress将其注入到前端调度器Pod中，并触发Pod中的container主进程（nginx）重载配置文件。\n要想使用Ingress功能，需要有service对某些后端资源进行分类，而后Ingress通过分类识别出Pod的数量和IP地址信息，并将反映结果生成配置信息注入到upstream中。\nIngressController根据自身需求方式来定义前端，而后根据servcie收集到的后端Pod IP定义成upstream server，将这些信息反映在Ingress server当中，由Ingress动态注入到IngressController当中。\nIngress也是标准的Kubernetes资源，定义Ingress时同样类似于Pod方式来定义。使用kubectl explain Ingress查看帮助。\nspec rules 规则，对象列表 host 主机调度 虚拟主机而非url映射 http paths 路径调度 backend path backend 定义被调度的后端主机，靠service定义，找到后端相关联的Pod资源。 serviceName 后端servcie名称，即用来关联Pod资源的service。 servicePort IngressController部署\nnamespace.yaml 创建名称空间 configmap.yaml 为nginx从外部注入配置的 rbac.yaml 定义集群角色、授权。必要时让IngressController拥有访问他本身到达不了的名称空间的权限\ningress.yaml\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress-myapp namespace: defualt # 与deployment和要发布的service处在同一名称空间内 annotations: kubernetes.","title":"kubernetes概念 - ingress"},{"content":"Deployment当中借助于ReplicaSet进行更新的策略反映在Deployment的对象定义所需字段可使用kubectl explain deploy，Deployment属于extension群组。在1.10版本中它被移至到apps群组。他与ReplicaSet相比增加了几个字段。\nstratgy 重要字段，定义更新策略，它支持两种策略 重建式更新 Recreate与滚动更新RollingUpdate，如果type为RollingUpdate，那么RollingUpdate的策略还可以使用RollingUpdate来定义，如果type为Recreate，那么RollingUpdate字段无效。 默认值为RollingUpdate\nstratgy.RollingUpdate控制RollingUpdate更新力度\nmaxSurge 对应的更新过程当中，最多能超出目标副本数几个。有两种取值方式，为直接指定数量和百分比。在使用百分比时，在计算数据时如果不足1会补位1个。 maxUnavailable 最多有几个副本不可用。 revisionHistoryLimit 滚动更新后，在历史当中最多保留几个历史版本，默认10。\n在使用Deployment创建Pod时，Deployment会自动创建ReplicaSet，而且Deployment名称是使用Pod模板的hash值，此值是固定的。\nDeployment在实现更新应用时，可以通过编辑配置文件来实现，使用kubectl apply -f更改每次变化。每次的变化通过吧变化同步至apiserver中，apiserver发现其状态与etcd不同，从而改变etcd值来实现修改其期望状态，来实现现有状态去逼近期望状态。\nkubectl explain deploy\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 apiVersion: apps/v1 kind: Deployment metadata: name: app-deploy namespace: default spec: replicas: 2 selector: matchLabels: app: deploy release: canary template: metadata: labels: app: deploy release: canary spec: containers: - name: my-deploy image: node01:5000/busybox:v1 ports: - name: http containerPort: 80 command: [\u0026#34;/bin/sh\u0026#34;,\u0026#34;-c\u0026#34;,\u0026#34;/bin/httpd -f -h /tmp\u0026#34;] 使用kubectl apply 声明式更新、创建资源对象。\n将上述资源配置清单的replicaSet数量改为3个后，可以看到数量增加为3，而对应的hash值没变化。\nsh 1 2 3 4 5 6 7 $ kubectl apply -f deploy.yaml deployment.apps/app-deploy configured $ kubectl get pods NAME READY STATUS RESTARTS AGE app-deploy-5b8db6bc7d-bkldv 1/1 Running 0 4s app-deploy-5b8db6bc7d-r2pcv 1/1 Running 0 5h5m app-deploy-5b8db6bc7d-wgbbg 1/1 Running 0 5h5m 由于Deployment是构建在ReplicaSet之上，对Pod做扩展、缩容是很方便的。处理动态修改资源配置清单外，还可以使用kubectl patch（打补丁）进行操作。\npatch操作是对对象的JSON内容进行打补丁，-p选项值为JSON格式，其建值需以引号引起。\n语法\ntext 1 kubectl patch -p 提供补丁\ntext 1 2 3 4 5 6 7 8 $ kubectl patch deployment app-deploy -p \u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;replicas\u0026#34;:4}}\u0026#39; deployment.extensions/app-deploy patched $ kubectl get pods NAME READY STATUS RESTARTS AGE app-deploy-58d74fd87f-555jm 1/1 Running 0 15m app-deploy-58d74fd87f-kkwz9 1/1 Running 0 15m app-deploy-58d74fd87f-vzthx 1/1 Running 0 15m app-deploy-58d74fd87f-zwdn5 1/1 Running 0 4s text 1 kubectl patch deployment app-deploy -p \u0026#39;{\u0026#34;spec\u0026#34;: {\u0026#34;strategy\u0026#34;: {\u0026#34;rollingUpdate\u0026#34;: {\u0026#34;maxSurge\u0026#34;:1,\u0026#34;maxUnavailable\u0026#34;:0 }}}}\u0026#39; 更新版本\n在更新完成后使用 kubectl get rs 查看可看到有两个 rs版本，所不同的是，镜像版本不同和可用的数量为0，但这个对应的模板会保留，随时等待回滚。\ntext 1 2 3 4 $ kubectl get rs -o wide NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR app-deploy-58d74fd87f 3 3 3 76s my-deploy node01:5000/busybox:v2 app=deploy,pod-template-hash=58d74fd87f,release=canary app-deploy-5b8db6bc7d 0 0 0 5h10m my-deploy node01:5000/busybox:v1 app=deploy,pod-template-hash=5b8db6bc7d,release=canary 更新版本可以使用可使用 kubectl set image进行更新\n语法\ntext 1 kubectl set image [-f filename | type name] container=version text 1 kubectl set image deployment app-deploy my-deploy=node01:5000/busybox:v4 \u0026amp;\u0026amp; kubectl rollout pause deployment app-deploy 此时可以看到rs保留多个版本\ntext 1 2 3 4 $ kubectl get rs -o wide NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR app-deploy-58d74fd87f 0 0 0 34m my-deploy node01:5000/busybox:v2 app=deploy,pod-template-hash=58d74fd87f,release=canary app-deploy-7fbc8b6df 3 3 3 25m my-deploy node01:5000/busybox:v4 app=deploy,pod-template-hash=7fbc8b6df,release=canary 使用 kubectl rollout pause可以暂停更新。\ntext 1 kubectl rollout pause type typename 使用resume可恢复暂停操作\ntext 1 kubectl rollout resume type typename Deployment版本滚动的历史保留 可使用 kubectl rollout history 查看滚动历史\ntext 1 2 3 4 5 $ kubectl rollout history deployment app-deploy deployment.extensions/app-deploy REVISION CHANGE-CAUSE 1 [none] 2 [none] 版本回滚 使用 kubectl rollout undo默认是回滚至上一个版本\n语法\ntext 1 kubectl rollout undo type typename --to-revision=n 回滚至指定版本\ntext 1 kubectl rollout undo deployment app-deploy --to-revision=1 回滚后查询当前rs版本\ntext 1 2 3 4 $ kubectl get rs -o wide NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR app-deploy-58d74fd87f 3 3 3 108m my-deploy node01:5000/busybox:v2 app=deploy,pod-template-hash=58d74fd87f,release=canary app-deploy-7fbc8b6df 0 0 0 99m my-deploy node01:5000/busybox:v4 app=deploy,pod-template-hash=7fbc8b6df,release=canary Deployment回滚演示\n","permalink":"https://www.161616.top/kubenetes-deployment/","summary":"Deployment当中借助于ReplicaSet进行更新的策略反映在Deployment的对象定义所需字段可使用kubectl explain deploy，Deployment属于extension群组。在1.10版本中它被移至到apps群组。他与ReplicaSet相比增加了几个字段。\nstratgy 重要字段，定义更新策略，它支持两种策略 重建式更新 Recreate与滚动更新RollingUpdate，如果type为RollingUpdate，那么RollingUpdate的策略还可以使用RollingUpdate来定义，如果type为Recreate，那么RollingUpdate字段无效。 默认值为RollingUpdate\nstratgy.RollingUpdate控制RollingUpdate更新力度\nmaxSurge 对应的更新过程当中，最多能超出目标副本数几个。有两种取值方式，为直接指定数量和百分比。在使用百分比时，在计算数据时如果不足1会补位1个。 maxUnavailable 最多有几个副本不可用。 revisionHistoryLimit 滚动更新后，在历史当中最多保留几个历史版本，默认10。\n在使用Deployment创建Pod时，Deployment会自动创建ReplicaSet，而且Deployment名称是使用Pod模板的hash值，此值是固定的。\nDeployment在实现更新应用时，可以通过编辑配置文件来实现，使用kubectl apply -f更改每次变化。每次的变化通过吧变化同步至apiserver中，apiserver发现其状态与etcd不同，从而改变etcd值来实现修改其期望状态，来实现现有状态去逼近期望状态。\nkubectl explain deploy\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 apiVersion: apps/v1 kind: Deployment metadata: name: app-deploy namespace: default spec: replicas: 2 selector: matchLabels: app: deploy release: canary template: metadata: labels: app: deploy release: canary spec: containers: - name: my-deploy image: node01:5000/busybox:v1 ports: - name: http containerPort: 80 command: [\u0026#34;/bin/sh\u0026#34;,\u0026#34;-c\u0026#34;,\u0026#34;/bin/httpd -f -h /tmp\u0026#34;] 使用kubectl apply 声明式更新、创建资源对象。","title":"kubernetes概念 - Kubenetes Deployment"},{"content":"Kubernetes资源清单 类别 名称 工作负载型资源（workload） 运行应用程序，对外提供服务：Pod、ReplicaSet、Deployment、StatefulSet、DaemonSet、Job、Cronjob （ReplicationController在v1.11版本被废弃） 服务发现及负载均衡 service、Ingress 配置与存储 Volume、CSI（容器存储接口 特殊类型存储卷 ConfigMap（当配置中心来使用的资源类型）、Secret（保存敏感数据）、DownwardAPI（把外部环境中的信息输出给容器） 集群级资源 Namespace、Node、Role、ClusterRole、RoleBinding（角色绑定）、ClusterRoleBinding（集群角色绑定） 元数据型资源 HPA、PodTemplate（Pod模板，用于让控制器创建Pod时使用的模板）、LimitRange（用来定义硬件资源限制的） Kubernetes配置清单使用说明 在Kubernetes中创建资源时，除了命令式创建方式，还可以使用yaml格式的文件来创建符合我们预期期望的pod，这样的yaml文件我们一般称为资源清单。资源清单由很多属性或字段所组成。\n以yawl格式输出pod的详细信息。\n资源清单格式 yaml 1 kubectl get pod clients -o yaml Pod资源清单常用字段讲解 在创建资源时，apiserver仅接收JSON格式的资源定义。在使用kubectl run命令时，自动将给定内容转换成JSON格式。yaml格式提供配置清单，apiserver可自动将其转为JSON格式，（yaml可无损转为json），而后再提交。使用资源配置请清单可带来复用效果。\nPod资源配置清单由五个一级字段组成，通过kubectl create -f yamlfile就可以创建一个Pod\napiVersion: 说明对应的对象属于Kubernetes的哪一个API群组名称和版本。给定apiVersion时由两部分组成group/version，group如果省略表示core（核心组）之意。使用kubectl api-versions获得当前系统所支持的apiserver版本。alpha 内测版、beta 公测版、stable 稳定版\nkind: 资源类别，用来指明哪种资源用来初始化成资源对象时使用。\nmetadata: 元数据，内部嵌套很多2级、3级字段。主要提供以下几个字段。\nname，在同一类别当中name必须是唯一的。\nnamespace 对应的对象属于哪个名称空间，name受限于namespace，不同的namespace中name可以重名。\nlables key-value数据，对于key名称及value，最多为63个字符，value，可为空。填写时只能使用字母、数字、_、-、.，只能以字母或数字开头及结尾。\nannotations 资源注解。与label不同的地方在于，它不能用于挑选资源对象，仅用于为对象提供“元数据”。对键值长度没有要求。在构建大型镜像时通常会用其标记对应的资源对象的元数据\nspec: specification，定义接下来创建的资源对象应该满足的规范（期望的状态 disired state）。spec是用户定义的。不同的资源类型，其所需要嵌套的字段各不相同。如果某一字段属性标记为required表示为必选字段，剩余的都为可选字段，系统会赋予其默认值。如果某一字段标记为Cannot be updated，则表示为对象一旦创建后不能改变字段值。可使用kubectl explain pods.spec查看详情。\ncontainers [required]object list\nname [string] 定义容器名称\nimage [string] 启动Pod内嵌容器时所使用的镜像。可是顶级、私有、第三方仓库镜像。\nimagePulLPolicy [string] 镜像获取的策略，可选参数Always（总是从仓库下载，无论本地有无此镜像）、Never（从不下载，无论本地有无此镜像）、IfNotPresent（本地存在则使用，不存在则从仓库拉去镜像）。如果tag设置为latest，默认值则为Always，非latest标签，默认值都为IfNotPresent。\nports []object 定义容器内要暴露的端口时，可以暴露多个端口，每个端口应该由多个属性来定义（端口名称、端口号、协议）。注意暴露端口仅仅是提供额外信息的，并不能限制系统是否真能暴露。 command Entrypoint array，运行的程序\nargs 向Entrypoint传递参数，官方对command和args的对比说明Define a Command and Arguments for a Container - Kubernetes\nnodeSelector map[string]string 节点标签选择器，确定Pod只运行在哪个或哪类节点上。\nlivenessProbe [object] 存活性验证\nexec 执行容器中存在的用户自定义命令。 command []string 运行命令来探测是否执行成功。 httpGet tcpSocket failureThreshold 确定失败的探测的失败次数，默认值3，最小值1。 periodSeconds 周期间隔时长。默认10秒。 timeoutSeconds 超时时长，默认1秒。 initialDelaySeconds [integer] 初始化延迟探测时间，默认容器启动时立刻探测。 readinessProbe 就绪性探测\nstatus: 资源的当前状态 current state。Kubernetes用于确保每一个资源定义完后，让其当前状态无限向目标状态转移，从而满足用户期望。从此角度来看，status是只读的，有Kubernets集群自行维护。 Kubernetes内嵌格式说明\n获取pod资源的配置清单帮助 语法格式\nsh 1 kubectl explain pod.lev1.lev2... 示例\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 $ kubectl explain pod.spec KIND: Pod VERSION: v1 RESOURCE: spec {Object} DESCRIPTION: Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status PodSpec is a description of a pod. FIELDS: activeDeadlineSeconds\t{integer} Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer. affinity\t{Object} If specified, the pod\u0026#39;s scheduling constraints automountServiceAccountToken\t{boolean} AutomountServiceAccountToken indicates whether a service account token should be automatically mounted. containers\t{[]Object} -required- List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated. hostname\t{string} Specifies the hostname of the Pod If not specified, the pod\u0026#39;s hostname will be set to a system-defined value. imagePullSecrets\t{[]Object} .... .... .... volumes\t{[]Object} List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes 资源清单定义 yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 apiVersion: v1 kind: Pod metadata: name: test-pod namespace: default labels: app: myapp-redis tier: frontend spec: containers: - name: redis-app image: redis imagePullPolicy: IfNotPresent ports: - name: redis containerPort: 6379 - name: busybox image: busybox command: - \u0026#34;/bin/sh\u0026#34; - \u0026#34;-c\u0026#34; - \u0026#34;sleep 3600\u0026#34; 从yaml文件加载创建资源\nsh 1 kubectl create -f pod.yaml 从yaml文件加载删除资源\nsh 1 kubectl delete -f pod.yaml 标签选择器的使用 Label是Kubernetes中极具特色的功能之一，是附加在对象之上的键值对，每一个资源可存在多个标签，每一个标签都是一组键值对。每一个标签都可以被标签选择器进行匹配度检查，从而完成资源挑选。Label既可以在对象创建时指定，可以在资源创建啊之后使用命令来管理（添加、修改、删除）。\nLabel可基于简单且直接的标准将Pod多个较小的分组。而service也需要识别标签对其识别并管控，或关联到的资源。最资源设定标签后，还可以使用标签来查看、删除等对其执行相应管理操作。\n⚠ 注意：在定义标签时，资源标签其标签名称key及value的值必须小于等于63个字符，value，可为空。填写时只能使用字母、数字、_、-、.，只能以字母或数字开头及结尾。 在定义键名时也可以使用键前缀(DNS域名)，加前缀总长度不能超过253个字符。\n对标签进行过滤 kubectl get pods常用参数说明\n选项 说明 -l 大S -L，label-columns=[] 接受以逗号分隔的标签列表，这些标签将作为列显示。名字区分大小写。 \u0026ndash;show-labels 在最后一列打印标签。 \u0026ndash;show-labels 显示Pod标签\nsh 1 2 3 4 $ kubectl get pods --show-labels NAME READY STATUS RESTARTS AGE LABELS nginx-7cdbd8cdc9-8blzc 1/1 Running 0 39s pod-template-hash=7cdbd8cdc9,run=nginx test-pod 2/2 Running 0 17h app=myapp-redis,tier=frontend -L 获取显示指定类别的资源对象时，对每个资源对象显示其标签值。\nsh 1 2 3 4 $ kubectl get pods --show-labels -L=run,app NAME READY STATUS RESTARTS AGE RUN APP LABELS nginx-7cdbd8cdc9-8blzc 1/1 Running 0 11m nginx pod-template-hash=7cdbd8cdc9,run=nginx test-pod 2/2 Running 0 17h myapp-redis app=myapp-redis,tier=frontend -l 标签过滤\nsh 1 2 3 4 5 6 7 8 $ kubectl get pods --show-labels NAME READY STATUS RESTARTS AGE LABELS nginx-7cdbd8cdc9-8blzc 1/1 Running 0 14m pod-template-hash=7cdbd8cdc9,run=nginx test-pod 2/2 Running 0 17h app=myapp-redis,tier=frontend $ kubectl get pods --show-labels -l app NAME READY STATUS RESTARTS AGE LABELS test-pod 2/2 Running 0 17h app=myapp-redis,tier=frontend 资源对象打标签 kubectl label语法\nsh 1 kubectl label -f filename|typename key1=value1 ... keyn valuen 对已有Pod添加标签\nsh 1 2 $ kubectl label pod nginx-7cdbd8cdc9-8blzc app=nginx-demo pod/nginx-7cdbd8cdc9-8blzc labeled 修改已有Label值得Pod，需要使用 --overwrite\nsh 1 2 3 4 5 $ kubectl label pod nginx-7cdbd8cdc9-8blzc app=nginx-demo error: \u0026#39;app\u0026#39; already has a value (nginx-demo), and --overwrite is false $ kubectl label pod nginx-7cdbd8cdc9-8blzc app=nginx-demo1 --overwrite pod/nginx-7cdbd8cdc9-8blzc labeled 使用复杂格式标签选择器 标签选择器\nKubernetes支持的标签选择器有两类，第一类是基于等值关系的标签选择器，另一类是基于集合关系的标签选择器。\n基于等值关系的选择器\n基于等值关系的标签选择器的操作费无非就是等值关系判断的的符号，如：= == !=\nsh 1 2 3 4 5 6 7 8 9 10 11 $ kubectl get pods --show-labels -l app=nginx-demo1 NAME READY STATUS RESTARTS AGE LABELS nginx-7cdbd8cdc9-8blzc 1/1 Running 0 6h app=nginx-demo1,pod-template-hash=7cdbd8cdc9,run=nginx $ kubectl get pods --show-labels -l app,tier NAME READY STATUS RESTARTS AGE LABELS test-pod 2/2 Running 5 23h app=myapp-redis,tier=frontend $ kubectl get pods --show-labels -l app=myapp-redis,tier=frontend NAME READY STATUS RESTARTS AGE LABELS test-pod 2/2 Running 5 23h app=myapp-redis,tier=frontend 基于集合关系的标签选择器。于集合关系的标签选择器。\n基于集合关系就是如下几种类型来进行判断\nkey in(value1,value2..,valueN) key notin(value1,value2,..valueN) 不具有此键也表示符合条件。 key !key 不存在此键的资源 sh 1 2 3 4 5 6 $ kubectl get pods --show-labels -l \u0026#34;app in (nginx-demo1,redus)\u0026#34; NAME READY STATUS RESTARTS AGE LABELS nginx-7cdbd8cdc9-8blzc 1/1 Running 0 6h15m app=nginx-demo1,pod-template-hash=7cdbd8cdc9,run=nginx $ kubectl get pods --show-labels -l \u0026#34;app notin (nginx-demo1,redus)\u0026#34; NAME READY STATUS RESTARTS AGE LABELS test-pod 2/2 Running 6 23h app=myapp-redis,tier=frontend 可以使用标签的不止是Pod，各种对象都可以打标签，包括Node。当节点有标签后，在添加资源时，就可以让资源对节点有倾向性。\nsh 1 2 3 4 $ kubectl get nodes --show-labels NAME STATUS ROLES AGE VERSION LABELS node02.k8s.test Ready [none] 3d1h v1.13.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=node02.k8s.test node03.k8s.test Ready [none] 3d1h v1.13.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=node03.k8s.test Pod的生命周期 常见的Pod状态：\nPending 挂起，请求创建Pod时，发现条件不满足，调度尚未完成 running Failed Success Unkown，未知状态。Pod的状态是Apiserver与运行Pod的节点上的Kubelet通信获取状态信息的。当Node节点上Kubelet进程发生故障。Apiserver无法获取Pod信息。\nPod的创建过程：\n用户创建Pod时，将请求提交给Apiserver，Apiserver将创建请求的目标状态保存在etcd中，而后apiserver请求scheduler进行调度（负责挑选出合适的节点来运行Pod）。并将调度结果保存至etcd的Pod资源信息中。随后目标节点kubelet通过apiserver的状态变化，拿到用户所提交的创建清单。根据清单在当前节点上创建并运行Pod，并将当前结果状态发送给apiserver，由apiserver将此状态信息存至etcd中。\nrestartPolicy: 重启策略 Always 总是重启, OnFailure 只有其状态为错误时重启，正常终止时不重启, Never 从不重启. Default to Always.\n容器的重启策略\nPod在被调度至某一节点之上时，只要此节点存在，Pod不会被重新调度，只会重启。除非Pod删除或Pod存在节点故障才会被重新调度。\nPod的终止过程\n在kubenetes集群中，Pod代表在Kubernetes集群节点上运行的程序或进程，是向用户提供服务的主要单位。当在提交删除一个Pod时，不会直接kill删除的，而是向Pod内的每一个容器发送TEAM终止信号，使Pod中容器正常终止。终止默认有30秒宽限期。宽限期结束，依然无法终止，会重新发送kill信号，强行进行终止。\nkubernetes中的探测方式 所谓的容器探测无非就是，在容器中设置一些探针或传感器来获取相应的数据。作为其存活与否、就绪与否的标准。目前来讲Kubernetes所支持的存货性探测方式和就绪行探测方式都是一样的。\nKubernetes中的探针类型有三种ExecAction，TCP套接字探针 TCPSocketAction ，HTTPGetAction，使用kubectl explain pod.containers.xxx获取帮助信息。\nlivenessProbe实例 yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 apiVersion: v1 kind: Pod metadata: name: liveness-pod namespace: default spec: containers: - name: liveness-container image: busybox imagePullPolicy: IfNotPresent command: [\u0026#34;/bin/sh\u0026#34;,\u0026#34;-c\u0026#34;,\u0026#34;touch /tmp/healthy; sleep 20; rm -fr /tmp/healthy; sleep 3600\u0026#34;] livenessProbe: exec: command: [\u0026#34;test\u0026#34;,\u0026#34;-e\u0026#34;,\u0026#34;/tmp/healthy\u0026#34;] initialDelaySeconds: 3 periodSeconds: 2 successThreshold: 1 在创建后使用describe查看错误\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 $ kubectl describe pod liveness-pod Name: liveness-pod Namespace: default Priority: 0 PriorityClassName: [none] Node: node03.k8s.test/10.0.0.17 Start Time: Thu, 10 Jan 2019 18:36:27 +0800 Labels: [none] Annotations: [none] Status: Running IP: 10.244.1.9 Containers: liveness-container: Container ID: docker://10062f9026968e664fbb128ddb33a14e30efc848e914fe12d769bab9180ab21a Image: busybox Image ID: docker-pullable://busybox@sha256:7964ad52e396a6e045c39b5a44438424ac52e12e4d5a25d94895f2058cb863a0 Port: [none] Host Port: [none] Command: /bin/sh -c touch /tmp/healthy; sleep 20; rm -fr /tmp/healthy; sleep 3600 State: Running Started: Thu, 10 Jan 2019 18:39:17 +0800 Last State: Terminated Reason: Error Exit Code: 137 Started: Thu, 10 Jan 2019 18:38:22 +0800 Finished: Thu, 10 Jan 2019 18:39:17 +0800 Ready: True Restart Count: 3 Liveness: exec [test -e /tmp/healthy] delay=3s timeout=1s period=2s #success=1 #failure=3 Environment: [none] Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-ml2gd (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-ml2gd: Type: Secret (a volume populated by a Secret) SecretName: default-token-ml2gd Optional: false QoS Class: BestEffort Node-Selectors: [none] Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s 可以看到根据定义的检测规则，Pod在不停的重启。\ntext 1 2 3 $ kubectl get pods NAME READY STATUS RESTARTS AGE liveness-pod 1/1 Running 8 17m readinessProbe，就绪性探测实例 编写yaml文件，使用HTTPAction探针进行就绪性探测\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: v1 kind: Pod metadata: name: http-pod namespace: default spec: containers: - name: http-container image: httpd imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 readinessProbe: httpGet: port: http path: /index.html initialDelaySeconds: 2 periodSeconds: 3 使用资源配置清单创建Pod，并查看其状态\ntext 1 2 3 4 5 6 7 $ kubectl create -f http.yaml pod/http-pod created $ kubectl get pods NAME READY STATUS RESTARTS AGE http-pod 1/1 Running 0 6s 查看此Pod的就绪性。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 $ kubectl describe pod http-pod Name: http-pod Namespace: default Priority: 0 PriorityClassName: [none] Node: node02.k8s.test/10.0.0.16 Start Time: Fri, 11 Jan 2019 11:34:36 +0800 Labels: [none] Annotations: [none] Status: Running IP: 10.244.0.18 Containers: http-container: Container ID: docker://562525c9498153ed0285d6fdaa03b822efca470194341f12e5f510ae9e93f570 Image: httpd Image ID: docker-pullable://httpd@sha256:a613d8f1dbb35b18cdf5a756d2ea0e621aee1c25a6321b4a05e6414fdd3c1ac1 Port: 80/TCP Host Port: 0/TCP State: Running Started: Fri, 11 Jan 2019 11:34:38 +0800 Ready: True Restart Count: 0 Readiness: http-get http://:http/index.html delay=2s timeout=1s period=3s #success=1 #failure=3 Environment: [none] Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-ml2gd (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-ml2gd: Type: Secret (a volume populated by a Secret) SecretName: default-token-ml2gd Optional: false QoS Class: BestEffort Node-Selectors: [none] Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 11s default-scheduler Successfully assigned default/http-pod to node02.k8s.test Normal Pulled 9s kubelet, node02.k8s.test Container image \u0026#34;httpd\u0026#34; already present on machine Normal Created 9s kubelet, node02.k8s.test Created container Normal Started 9s kubelet, node02.k8s.test Started container 手动接入Pod内，将探测的文件删除。此时查看Pod的就绪性如下。提示404\nsh 1 kubectl exec -it http-pod -- /bin/bash 此时，Pod就绪的容器量为0个，也就是说，容器中httpd进程正常，但是web页面不存在。\ntext 1 2 3 $ kubectl get pods NAME READY STATUS RESTARTS AGE http-pod 0/1 Running 0 3h39m text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 $ kubectl describe pod http-pod Name: http-pod Namespace: default Priority: 0 PriorityClassName: [none] Node: node02.k8s.test/10.0.0.16 Start Time: Fri, 11 Jan 2019 11:34:36 +0800 Labels: [none] Annotations: [none] Status: Running IP: 10.244.0.18 Containers: http-container: Container ID: docker://562525c9498153ed0285d6fdaa03b822efca470194341f12e5f510ae9e93f570 Image: httpd Image ID: docker-pullable://httpd@sha256:a613d8f1dbb35b18cdf5a756d2ea0e621aee1c25a6321b4a05e6414fdd3c1ac1 Port: 80/TCP Host Port: 0/TCP State: Running Started: Fri, 11 Jan 2019 11:34:38 +0800 Ready: False Restart Count: 0 Readiness: http-get http://:http/index.html delay=2s timeout=1s period=3s #success=1 #failure=3 Environment: [none] Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-ml2gd (ro) Conditions: Type Status Initialized True Ready False ContainersReady False PodScheduled True Volumes: default-token-ml2gd: Type: Secret (a volume populated by a Secret) SecretName: default-token-ml2gd Optional: false QoS Class: BestEffort Node-Selectors: [none] Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 3h39m default-scheduler Successfully assigned default/http-pod to node02.k8s.test Normal Pulled 3h39m kubelet, node02.k8s.test Container image \u0026#34;httpd\u0026#34; already present on machine Normal Created 3h39m kubelet, node02.k8s.test Created container Normal Started 3h39m kubelet, node02.k8s.test Started container Warning Unhealthy 0s (x10 over 27s) kubelet, node02.k8s.test Readiness probe failed: HTTP probe failed with statuscode: 404 lifecycle 生命周期，用来定义启动后和终止前钩子\nlifecycle\npostStart Pod在创建启动之后立即执行的操作。如果执行失败，容器会终止，被重启，重启与否取决于重启策略。\npreSTop Pod在终止之前立即被执行的命令。命令执行完毕后，Pod才会被终止。\n⚠ 注意：注意两个command的执行顺序containers.command是定义容器的command，containers.postStart.exec.command是定义容器启动后初始化操作 许多资源支持内嵌字段定义其使用的标签选择器： matchLabels：直接给定key value service只支持此类 matchExpressions：基于给定的表达式定义使用的标签选择器{key:\u0026quot;KEY\u0026quot;,operator:\u0026quot;OPERATOR\u0026quot;,values:[VAL1,VAL2,...]。\n操作符： In，Notin：values必须为非空列表 Exists，NotExists：values必须为空列表。\nPod控制器都是内嵌Pod模板，\nPod控制器去管理Pod中间层，并确保每一个Pod资源始终处于定义、或所期望的目标状态。当Pod状态出现故障，首先尝试重启容器，\nPod控制器有多种类型\nReplicaSet：ReplicaSet被称为新一代的ReplicationController，它的核心作用在于代用户创建指定数量的副本，并确保Pod副本一直处于满足用户期望数量的状态，多退少补。还支持自动扩缩容机制。 主要有三个组件组成\n用户期望Pod副本数量 标签选择器，以便选定由自己管理控制的Pod副本。 Pod资源模板 通过标签选择器选到的标签副本数量低于指定数量，会使用Pod资源模板完成Pod资源的新建。\n帮助用户管理无状态的Pod资源，并确保精确反应用户所定义的目标数量，但是Kubernetes不建议直接使用ReplicaSet\nDeployment\nDeployment工作于ReplicaSet之上，一个Deployment可以管理多个rs，但是存活的（），通常保留历史版本中的10个。Deployment通过控制ReplicaSet来控制Pod。Deployment除了支持ReplicaSet所支持的功能，还支持滚动更新、回滚等机制，而且还提供了声明式配置的功能。是用来管理无状态应用的目前最佳的Pod控制器。\nDeployment能提供滚动式自定义、自控制的更新。 Deployment在更新时可控制更新节奏和更新逻辑。 声明式配置在创建资源时，可以基于声明逻辑来定义，所有更新的资源可以随时重新进行声明，只要资源支持动态运行时修改，就可以随时改变在apiserver上定义的目标期望状态。\nPod副本数量是有可能大于节点数的，并且数量本身彼此间没有任何精确对应关系。 DaemonSet\n用于确保集群的每一个节点或指定条件的节点上只运行一个特定的Pod副本，通常用于实现系统级的后台任务。\nJob 只能执行一次性的作业 CronJob 周期性运行作业，每次运行都有正常退出的时间 不需要持续后台运行。如果前一次任务没有完成，下一次时间点又到了，CronJob还需要处理此类问题\n区别\nJob和CronJob与DaemonSet和Deployment显著区别就在于，Job和CronJob不需要持续后台运行。\nDeployment只能用于管控无状态应用。常用于只关注群体，而不必关注个体的场景。\nStatefulSet能够实现管理有状态应用，而且每一个应用，每一个Pod副本都是被单独管理的。他拥有自己的独有标识和独有的数据集，一旦节点发生故障，在加进来之前需要进行初始化操作。\nStatefulSet提供了封装控制器，将需要人为手动做的操作、复杂的执行逻辑，定义成脚本，放置在StatefulSet Pod模板的定义当中。每次节点故障，通过脚本可自动恢复状态。\nReplicaSet的使用\nReplicaSet定义方式可以使用kubectl explain rs查看帮助，ReplicaSet使用的apps组中的v1，而不在是core组。内嵌字段与Pod类似，而spec中定义时最核心的只有3个 relicas副本数量、selector 标签选择器 templates Pod模板，对于templates而言，其内部就是Pod模板。\n使用ReplicaSet创建Pod\nlabels中的标签，必须符合selector中的选择标准。否则创建的Pod是无用的，创建Pod都不够relicas定义的数量，它将会永久创建下去。\n在Pod模板中起的Pod名称是没有用的，它会自动以控制器的名称后跟一串随机串，来作为Pod名称来创建。\n当Pod控制器数量超出用户期望数量，会随机删除其中一个Pod\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ kubectl get pods --show-labels NAME READY STATUS RESTARTS AGE LABELS rs-5xw7l 1/1 Terminating 0 40m app=myapp,release=canary,test=1a rs-6zx6b 1/1 Running 0 40m app=myapp,release=canary,test=1a test-pod 2/2 Running 0 46m app=myapp,release=canary $ kubectl label pods test-pod release=canary pod/test-pod labeled $ kubectl get pods --show-labels NAME READY STATUS RESTARTS AGE LABELS rs-5xw7l 1/1 Terminating 0 40m app=myapp,release=canary,test=1a rs-6zx6b 1/1 Running 0 40m app=myapp,release=canary,test=1a test-pod 2/2 Running 0 46m app=myapp,release=canary $ kubectl get pods --show-labels NAME READY STATUS RESTARTS AGE LABELS rs-6zx6b 1/1 Running 0 42m app=myapp,release=canary,test=1a test-pod 2/2 Running 0 47m app=myapp,release=canary ReplicaSet动态规模的扩容、缩容\n修改了控制器后，Pod资源并不会随之更改，因为Pod资源足额就不会被重建，只有重建的Pod资源的版本才是新版本的。\nPod Preset Pod Preset 是一种 API 资源，在 Pod 创建时，用户可以用它将额外的将运行时需求信息注入 Pod内。 使用标签选择算符来指定 Pod Preset 所适用的 Pod。\n在集群中启动Pod Preset 在集群中使用 Pod Preset，必须确保以下几点：\n需要确保你使用的是kubernetes 1.8版本以上 已启用 API 类型 settings.k8s.io/v1alpha1/podpreset 已启用准入控制器 PodPreset apiserver添加参数 --enable-admission-plugins --runtime-config bash 1 2 --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,PodPreset \\ --runtime-config=settings.k8s.io/v1alpha1=true 创建一个PodPreset\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 apiVersion: settings.k8s.io/v1alpha1 kind: PodPreset metadata: name: time-preset namespace: default spec: selector: matchLabels: volumeMounts: - mountPath: /etc/localtime name: time volumes: - name: time hostPath: path: /etc/localtime env: - name: ENVOY_END value: envoy-1.15 特定Pod禁用Pod Preset 在 Pod 的 .spec 中添加形如 podpreset.admission.kubernetes.io/exclude: \u0026quot;true\u0026quot; 的注解\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 apiVersion: apps/v1 kind: Deployment metadata: name: podpreset-deply labels: app: podpreset-deply spec: replicas: 1 selector: matchLabels: app: podpreset-deply template: metadata: name: podpreset-deply labels: app: podpreset-deply annotations: podpreset.admission.kubernetes.io/exclude: \u0026#34;true\u0026#34; spec: containers: - name: envoy-end image: sealloong/envoy-end imagePullPolicy: IfNotPresent restartPolicy: Always Reference openshift kubernetes-pod preset ","permalink":"https://www.161616.top/kubernetes-pod-controller/","summary":"Kubernetes资源清单 类别 名称 工作负载型资源（workload） 运行应用程序，对外提供服务：Pod、ReplicaSet、Deployment、StatefulSet、DaemonSet、Job、Cronjob （ReplicationController在v1.11版本被废弃） 服务发现及负载均衡 service、Ingress 配置与存储 Volume、CSI（容器存储接口 特殊类型存储卷 ConfigMap（当配置中心来使用的资源类型）、Secret（保存敏感数据）、DownwardAPI（把外部环境中的信息输出给容器） 集群级资源 Namespace、Node、Role、ClusterRole、RoleBinding（角色绑定）、ClusterRoleBinding（集群角色绑定） 元数据型资源 HPA、PodTemplate（Pod模板，用于让控制器创建Pod时使用的模板）、LimitRange（用来定义硬件资源限制的） Kubernetes配置清单使用说明 在Kubernetes中创建资源时，除了命令式创建方式，还可以使用yaml格式的文件来创建符合我们预期期望的pod，这样的yaml文件我们一般称为资源清单。资源清单由很多属性或字段所组成。\n以yawl格式输出pod的详细信息。\n资源清单格式 yaml 1 kubectl get pod clients -o yaml Pod资源清单常用字段讲解 在创建资源时，apiserver仅接收JSON格式的资源定义。在使用kubectl run命令时，自动将给定内容转换成JSON格式。yaml格式提供配置清单，apiserver可自动将其转为JSON格式，（yaml可无损转为json），而后再提交。使用资源配置请清单可带来复用效果。\nPod资源配置清单由五个一级字段组成，通过kubectl create -f yamlfile就可以创建一个Pod\napiVersion: 说明对应的对象属于Kubernetes的哪一个API群组名称和版本。给定apiVersion时由两部分组成group/version，group如果省略表示core（核心组）之意。使用kubectl api-versions获得当前系统所支持的apiserver版本。alpha 内测版、beta 公测版、stable 稳定版\nkind: 资源类别，用来指明哪种资源用来初始化成资源对象时使用。\nmetadata: 元数据，内部嵌套很多2级、3级字段。主要提供以下几个字段。\nname，在同一类别当中name必须是唯一的。\nnamespace 对应的对象属于哪个名称空间，name受限于namespace，不同的namespace中name可以重名。\nlables key-value数据，对于key名称及value，最多为63个字符，value，可为空。填写时只能使用字母、数字、_、-、.，只能以字母或数字开头及结尾。\nannotations 资源注解。与label不同的地方在于，它不能用于挑选资源对象，仅用于为对象提供“元数据”。对键值长度没有要求。在构建大型镜像时通常会用其标记对应的资源对象的元数据\nspec: specification，定义接下来创建的资源对象应该满足的规范（期望的状态 disired state）。spec是用户定义的。不同的资源类型，其所需要嵌套的字段各不相同。如果某一字段属性标记为required表示为必选字段，剩余的都为可选字段，系统会赋予其默认值。如果某一字段标记为Cannot be updated，则表示为对象一旦创建后不能改变字段值。可使用kubectl explain pods.spec查看详情。\ncontainers [required]object list\nname [string] 定义容器名称\nimage [string] 启动Pod内嵌容器时所使用的镜像。可是顶级、私有、第三方仓库镜像。\nimagePulLPolicy [string] 镜像获取的策略，可选参数Always（总是从仓库下载，无论本地有无此镜像）、Never（从不下载，无论本地有无此镜像）、IfNotPresent（本地存在则使用，不存在则从仓库拉去镜像）。如果tag设置为latest，默认值则为Always，非latest标签，默认值都为IfNotPresent。","title":"kubernetes概念 - Kubernetes Pod控制器"},{"content":"Overview kube-scheduler 是kubernetes控制平面的核心组件，其默认行为是将 pod 分配给节点，同时平衡Pod与Node间的资源利用率。通俗来讲就是 kube-scheduler 在运行在控制平面，并将工作负载分配给 Kubernetes 集群。\n本文将深入 Kubernetes 调度的使用，包含：”一般调度”，”亲和度“，“污点与容忍的调度驱逐”。最后会分析下 Scheduler Performance Tuning，即微调scheduler的参数来适应集群。\n简单的调度 NodeName [1] 最简单的调度可以指定一个 NodeName 字段，使Pod可以运行在对应的节点上。如下列资源清单所示\nyaml 1 2 3 4 5 6 7 8 9 apiVersion: v1 kind: Pod metadata: name: netpod spec: containers: - name: netbox image: cylonchau/netbox nodeName: node01 通过上面的资源清单Pod最终会在 node01上运行。这种情况下也会存在很多的弊端，如资源节点不足，未知的nodename都会影响到Pod的正常工作，通常情况下，这种方式是不推荐的。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $ kubectl describe pods netpod Name: netpod Namespace: default ... QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Pulling 86s kubelet Pulling image \u0026#34;cylonchau/netbox\u0026#34; Normal Pulled 17s kubelet Successfully pulled image \u0026#34;cylonchau/netbox\u0026#34; Normal Created 17s kubelet Created container netbox Normal Started 17s kubelet Started container netbox $ kubectl get pods netpod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES netpod 1/1 Running 0 48m 192.168.0.3 node01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 通过上面的输出可以确定，通过 NodeName 方式是不经过 scheduler 调度的\nnodeSelector [2] label 是 kubernetes中一个很重要的概念，通常情况下，每一个工作节点都被赋予多组 label ,可以通过命令查看对应的 label 。\nbash 1 2 3 $ kubectl get node node01 --show-labels NAME STATUS ROLES AGE VERSION LABELS node01 Ready \u0026lt;none\u0026gt; 15h v1.18.20 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node01,kubernetes.io/os=linux 而 nodeSelector 就是根据这些 label ，来选择具有特定一个或多个标签的节点。例如，如果需要在一组特定的节点上运行pod，可以设置在 “PodSpec” 中定义nodeSelector 为一组键值对：\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: apps/v1 kind: Deployment metadata: name: netpod-nodeselector spec: selector: matchLabels: app: netpod replicas: 2 template: metadata: labels: app: netpod spec: containers: - name: netbox image: cylonchau/netbox nodeSelector: beta.kubernetes.io/os: linux 对于上面的pod来讲，Kubernetes Scheduler 会找到带有 beta.kubernetes.io/os: linux标签的节点。对于更多kubernetes内置的标签，可以参考 [3]\n对于标签选择器来说，最终会分布在具有标签的节点上\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 kubectl describe pod netpod-nodeselector-69fdb567d8-lcnv6 Name: netpod-nodeselector-69fdb567d8-lcnv6 Namespace: default ... QoS Class: BestEffort Node-Selectors: beta.kubernetes.io/os=linux Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 8m18s default-scheduler Successfully assigned default/netpod-nodeselector-69fdb567d8-lcnv6 to node01 Normal Pulling 8m17s kubelet Pulling image \u0026#34;cylonchau/netbox\u0026#34; Normal Pulled 7m25s kubelet Successfully pulled image \u0026#34;cylonchau/netbox\u0026#34; Normal Created 7m25s kubelet Created container netbox Normal Started 7m25s kubelet Started container netbox 节点亲和性 [4] 对于使用了调度功能的系统来说，亲和度 （Affinity）是个很常见的概念，通常亲和度发生在并行（parallel ）环境中；在这种环境下，亲和度提供了在一个节点上运行pod可能比在其他节点上运行更有效，而计算亲和度通常由多种条件组成。一般情况下，亲和度分为“软亲和与硬亲和\n软亲和，Soft Affinity，是调度器尽可能将任务保持在同一个节点上。这只是一种尝试；如果不可行，则将进程迁移到另一个节点 硬亲和，Hard affinity，硬亲和度是强行将任务绑定到指定的节点上 而在kubernetes中也支持亲和度的概念，而亲和度是与 nodeSelector 配合形成的一个算法。其中硬亲和被定义为requiredDuringSchedulingIgnoredDuringExecution；软亲和被定义为 preferredDuringSchedulingIgnoredDuringExecution\n硬亲和性（requiredDuringSchedulingIgnoredDuringExecution）：必须满足条件，否则调度程序无法调度 Pod。 软亲和性 （preferredDuringSchedulingIgnoredDuringExecution）：scheduler 将查找符合条件的节点。如果没有满足要求的节点将忽略这条规则，scheduler 将仍会调度 Pod。 Node Affinity Node Affinity参数说明 调度程序会更倾向于将 pod 调度到满足该字段指定的亲和性表达式的节点，但它可能会选择违反一个或多个表达式的节点。最优选的节点是权重总和最大的节点，即对于满足所有调度要求（资源请求、requiredDuringScheduling 亲和表达式等）的每个节点，通过迭代该字段的元素来计算总和如果节点匹配相应的matchExpressions，则将“权重”添加到总和中；具有最高和的节点是最优选的。\n如果在调度时不满足该字段指定的亲和性要求，则不会将 Pod 调度到该节点上。如果在 pod 执行期间的某个时间点不再满足此字段指定的亲和性要求（例如，由于更新），系统可能会或可能不会尝试最终将 pod 从其节点中逐出。\naffinity 范围应用于 Pod.Spec 下，参数如下：\nnodeAffinity：node亲和度相关根配置 preferredDuringSchedulingIgnoredDuringExecution：软亲和 preference (required)：选择器 matchExpressions：匹配表达式，标签可以指定部分 key (\u0026lt;string\u0026gt; -required-)： operator (\u0026lt;string\u0026gt; -required-)：# 与一组 key-values的运算方式。 In, NotIn, Exists, DoesNotExist, Gt, Lt。 values (\u0026lt;[]string\u0026gt;)： matchFields： 匹配字段 key (\u0026lt;string\u0026gt; -required-)： operator (\u0026lt;string\u0026gt; -required-)：# 与一组 key-values的运算方式。 In, NotIn, Exists, DoesNotExist, Gt, Lt。 values (\u0026lt;[]string\u0026gt;)： weight (required)：范围为 1-100，具有最高和的节点是最优选的 requiredDuringSchedulingIgnoredDuringExecution：硬亲和 nodeSelectorTerms matchExpressions： key (\u0026lt;string\u0026gt; -required-)： operator (\u0026lt;string\u0026gt; -required-)：# 与一组 key-values的运算方式。 In, NotIn, Exists, DoesNotExist, Gt, Lt。 values (\u0026lt;[]string\u0026gt;)： matchFields： key (\u0026lt;string\u0026gt; -required-)： operator (\u0026lt;string\u0026gt; -required-)：一组 key-values 的运算方式。 In, NotIn, Exists, DoesNotExist, Gt, Lt。 values (\u0026lt;[]string\u0026gt;)： Notes: matchFields使用的是资源清单的字段（kubectl get node -o yaml），而matchExpressions匹配的是标签\nNode Affinity示例 上面的介绍了解到了Kubernetes中相对与 nodeSelector可以更好表达复杂的调度需求：节点亲和性，使用PodSpec中的字段 .spec.affinity.nodeAffinity 指定相关 affinity 配置。\nYAML 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 apiVersion: apps/v1 kind: Deployment metadata: name: netpod-nodeselector spec: selector: matchLabels: app: netpod replicas: 2 template: metadata: labels: app: netpod spec: containers: - name: netbox image: cylonchau/netbox affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: app operator: In values: - test 上面的清单表明，当节点存在 app: test 标签时，会调度到对应的Node上，如果没有节点匹配这些条件也不要紧，会根据普通匹配进行调度。\n当硬策略和软策略同时存在时的情况，根据设置的不同，硬策略优先级会高于软策略，哪怕软策略权重设置为100\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 apiVersion: apps/v1 kind: Deployment metadata: name: netpod-nodeselector spec: selector: matchLabels: app: netpod replicas: 2 template: metadata: labels: app: netpod spec: containers: - name: netbox image: cylonchau/netbox affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: app operator: In values: - test requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: topology.kubernetes.io/zone operator: In values: - antarctica-east1 - antarctica-west1 下面是报错信息\nbash 1 Warning FailedScheduling 4s (x3 over 24s) default-scheduler 0/2 nodes are available: 2 node(s) didn\u0026#39;t match node selector. Pod亲和性 [4] pod亲和性和反亲和性是指根据节点上已运行的Pod的标签而不是Node标签来限制Pod可以在哪些节点上调度。例如：X 满足一个或多个运行 Y 的条件，这个时候 Pod满足在X中运行。其中 X 为拓扑域，Y 则是规则。\nNotes：官方文档中不推荐pod亲和度在超过百个节点的集群中使用该功能 [5]\nPod亲和性配置 Pod亲和性和反亲和性与Node亲和性类似，affinity 范围应用于 Pod.Spec.podAffinity 下，这里不做重复复述，可以参考Node亲和性参数说明部分。\ntopologyKey，==不允许是空值==，该值将影响Pod部署的位置，影响范围为，与亲和性条件匹配的对应的节点中的什么拓扑，topologyKey的拓扑域由label标签决定。\n除了 topologyKey 之外，还有标签选择器 labelSelector 与 名称空间 namespaces 可以作为同级的替代选项\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 apiVersion: apps/v1 kind: Deployment metadata: name: netpod-podaffinity spec: selector: matchLabels: app: podaffinity replicas: 1 template: metadata: labels: app: podaffinity spec: containers: - name: podaffinity image: cylonchau/netbox affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - netpod topologyKey: zone 如果没有Pod匹配到规则，则pending状态\nbash 1 Warning FailedScheduling 59s (x2 over 59s) default-scheduler 0/2 nodes are available: 2 node(s) didn\u0026#39;t match pod affinity rules, 2 node(s) didn\u0026#39;t match pod affinity/anti-affinity. Pod Anti-Affinity 在某些场景下，部分节点不应该有很多资源，即某些节点不想被调度。例如监控运行节点由于其性质，不希望该节点上有很多资源，或者因节点配置的不同，配置较低节点不希望调度很多资源；在这种情况下，如果将符合预期之外的Pod调度过来会降低其托管业务的性能。这种情况下就需要 反亲和度（Anti-Affinity）来使Pod远离这组节点\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 apiVersion: apps/v1 kind: Deployment metadata: name: netpod-podaffinity spec: selector: matchLabels: app: podaffinity replicas: 1 template: metadata: labels: app: podaffinity spec: containers: - name: podaffinity image: cylonchau/netbox affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - netpod topologyKey: zone Taints And Tolerations [6] Taints 亲和度和反亲和度虽然可以阻止Pod在特定节点上运行，但还存在一个问题，就是亲和度和反亲和度需要声明运行的节点或者是不想运行的节点，如果忘记声明，还是会被调度到对应的Node上。Kubernetes还提供了一种驱逐Pod的方法，就是污点（Taints）与容忍（Tolerations）。\n创建一个污点\nbash 1 2 3 kubectl taint nodes node1 key1=value1:NoSchedule $ kubectl taint nodes mon01 role=monitoring:NoSchedule 删除一个污点，\nbash 1 kubectl taint nodes node1 key1=value1:NoSchedule- 除了 NoSchedule ，还有 PreferNoSchedule 与 NoExecute\nPreferNoSchedule ：类似于软亲和性的属性，尽量去避免污点，但不是强制的。 NoExecute 表示，当Pod还没在节点上运行时，并且存在至少一个污点时生效，此时Pod不会被调度到该节点；当Pod已经运行在节点上时，并且存在至少一个污点时生效，Pod将会从节点上被驱逐。 Tolerations 当Node有污点时，在调度时会自动被排除。当调度在受污染的节点上执行Predicate部分时将失败，而容忍度则是使 pod 具有对该节点上的污点进行容忍，即拥有容忍度的Pod可以调度到有污点的节点之上。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 apiVersion: apps/v1 kind: Deployment metadata: name: netpod-podaffinity spec: selector: matchLabels: app: podaffinity replicas: 1 template: metadata: labels: app: podaffinity spec: containers: - name: podaffinity image: cylonchau/netbox tolerations: - key: \u0026#34;role\u0026#34; operator: \u0026#34;Equal\u0026#34; value: \u0026#34;monitoring\u0026#34; effect: \u0026#34;NoSchedule\u0026#34; affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - netpod topologyKey: zone 容忍度中存在一个特殊字段 TolerationSeconds ，表示容忍的时间，默认不会设置，即永远容忍污点。设置成0或者负数表示理解驱逐。==仅在污点为 NoExecute 时生效==\noperator 属性有两个值 Exists 和 Equal\n如果 operator 为 Exists，则无需 value 属性，因为判断的是有污点的情况下。\n如果 operator 为 Equal，则表示 key 与 value 之间的关系是 $key=value$\n空 key，并且operator为 Exists，将匹配到所有，即容忍所有污点\n空 effect 匹配所有 effect ，即匹配所有污点；这种情况下加上条件的话，可以容忍所有类型的污点\n驱逐 [7] 当污点设置为 NoExecute这种情况下会驱逐Pod，驱逐条件又如下几个：\n不容忍污点的 pod 会立即被驱逐 容忍污点但未配置 tolerationSeconds 属性的会保持不变，即该节点与Pod保持绑定 容忍指定污点的 pod 并且配置了tolerationSeconds 属性，节点与Pod绑定状态仅在配置的时间内。 Kubernetes内置了一些污点，此时 Controller 会自动污染节点：\nnode.kubernetes.io/not-ready: Node故障。对应 NodeCondition 的Ready = False。\nnode.kubernetes.io/unreachable：Node控制器无法访问节点。对应 NodeCondition Ready= Unknown。\nnode.kubernetes.io/memory-pressure：Node内存压力。\nnode.kubernetes.io/disk-pressure：Node磁盘压力。\nnode.kubernetes.io/pid-pressure：Node有PID压力。\nnode.kubernetes.io/network-unavailable：Node网络不可用。\nnode.kubernetes.io/unschedulable：Node不可调度。\nNotes：Kubernetes node.kubernetes.io/not-ready 属性和 node.kubernetes.io/unreachable 属性添加容差时效 tolerationSeconds=300。即在检测到其中问题后，Pod 将保持绑定5分钟。\n优先级和抢占 kubernetes中也为Pod提供了优先级的机制，有了优先级机制就可以在并行系统中提供抢占机制，有了抢占机制后，当还未调度时，高优先级Pod会比低优先级Pod先被调度，在资源不足时，低优先级Pod可以被高优先级Pod驱逐。\n优先级功能由 PriorityClasses 提供。PriorityClasses 是作为集群级别资源而不是命名空间级别资源，只是用来声明优先级级别。\nvalue 作为优先级级别，数字越大优先级级别越高。而 name 是这个优先级的名称，与其他资源name值相似，值的内容需要符合DNS域名约束。\nglobalDefault 是集群内默认的优先级级别，仅只有一个 PriorityClass 可以设置为 true\nyaml 1 2 3 4 5 6 7 apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority value: 1000000 globalDefault: false description: \u0026#34;This priority class should be used for XYZ service pods only.\u0026#34; Notes:\n如果集群内不存在任何 PriorityClass ，则存在的Pod的优先级都为0 当对集群设置了 globalDefault=true 后，不会改变已经存在的 Pod 的优先级。仅对于 PriorityClass globalDefault=true 后创建的 Pod。 如果删除了 PriorityClass ，存在还是使用的这个 PriorityClass 的Pod保持不变，新创建的Pod无法使用这个 PriorityClass 。 非抢占 当 preemptionPolicy: Never 时，Pod不会抢占其他Pod，但不可调度时，会一直在调度队列中等待调度，直到满足要求才会被调度。==非抢占式pod仍可能被其他高优先级的pod抢占==\nNotes：preemptionPolicy在Kubernetes v1.24 [stable]\npreemptionPolicy 是作为非抢占的配置，默认参数为 PreemptLowerPriority；表示了允许高优先级Pod抢占低优先级Pod。如果 preemptionPolicy: Never，代表Pod是非抢占式的。\n下列是一个非抢占式的配置样例\nyaml 1 2 3 4 5 6 7 8 apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority-nonpreempting value: 1000000 preemptionPolicy: Never globalDefault: false description: \u0026#34;This priority class will not cause other pods to be preempted.\u0026#34; 当配置了优先级后，优先级准入控制器会使用 priorityClassName 中配置的对应的 PriorityClass 的 value值来填充当前Pod的优先级，如果没有找到对应抢占策略，则拒绝。\n下面是在Pod中配置优先级的示例\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: v1 kind: Pod metadata: name: nginx labels: env: test spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent priorityClassName: high-priority 抢占 当创建Pod之后，Pod会进入队列并等待被调度。scheduler 从队列中选择一个 Pod 并尝试将其调度到一个节点上。如果没有找到满足 Pod 的所有指定要求的 Node，则为 Pending 的 Pod 触发抢占。当Pod在找合适的节点时，即试图抢占一个节点，会在这个Node中删除一个或多个优先级低于当前Pod的Pod，使当前Pod能够被调度到对应的Node上。当低优先级Pod被驱逐后，当前Pod可以被调度到该Node，这个过程被称为抢占 preemption。\n而提供可驱逐资源的Node成为被提名Node（nominated Node ），在当Pod抢占到一个Node时，其 nominatedNodeName 会被标注为这个 Node的名称，当然标注后也不一定，一定是被抢占到这个Node之上，例如，当前Pod在等待驱逐低优先级Pod的过程中，有其他节点变成可用节点 FN 时，这个时候Pod会被抢占到这个节点。\nReference ​[1] 创建一个会被调度到特定节点上的 Pod\n​[2] nodeSelector\n​[3] labels annotations taints\n​[4] affinity and anti-affinity\n​[5] inter pod affinity and anti affinity\n​[6] taint and toleration\n​[7] evictions\n​[8] pod priority preemption\n","permalink":"https://www.161616.top/kubernetes-schedule/","summary":"Overview kube-scheduler 是kubernetes控制平面的核心组件，其默认行为是将 pod 分配给节点，同时平衡Pod与Node间的资源利用率。通俗来讲就是 kube-scheduler 在运行在控制平面，并将工作负载分配给 Kubernetes 集群。\n本文将深入 Kubernetes 调度的使用，包含：”一般调度”，”亲和度“，“污点与容忍的调度驱逐”。最后会分析下 Scheduler Performance Tuning，即微调scheduler的参数来适应集群。\n简单的调度 NodeName [1] 最简单的调度可以指定一个 NodeName 字段，使Pod可以运行在对应的节点上。如下列资源清单所示\nyaml 1 2 3 4 5 6 7 8 9 apiVersion: v1 kind: Pod metadata: name: netpod spec: containers: - name: netbox image: cylonchau/netbox nodeName: node01 通过上面的资源清单Pod最终会在 node01上运行。这种情况下也会存在很多的弊端，如资源节点不足，未知的nodename都会影响到Pod的正常工作，通常情况下，这种方式是不推荐的。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $ kubectl describe pods netpod Name: netpod Namespace: default .","title":"kubernetes概念 - kubernetes调度"},{"content":"在Kubernetes集群中，Pod是有生命周期的，为了能够给对应的客户端提供一个固定访问端点，因此在客户端与服务端（Pod之间）添加了一个固定中间层，这个中间层被称之为Service。Service的工作严重依赖于在Kubernetes集群之上，部署的附件Kubernetes DNS服务。较新版本使用的coreDNS，1.11之前使用的KubeDNS。\nservice的名称解析是强依赖于DNS附件的。因此在部署完Kubernetes后，需要部署CoreDNS或KubeDNS。 Kubernetes要想向客户端提供网络功能，依赖于第三方方案，在较新版本中，可通过CNI容器网络插件标准接口，来接入任何遵循插件标准的第三方方案。 Service从一定程度上来说，在每个节点之上都工作有一个组件Kube-proxy，Kube-proxy将始终监视apiserver当中，有关service资源的变动状态。此过程是通过Kubernetes中固有的请求方法watch来实现的。一旦有service资源的内容发生变动，kube-proxy都将其转换为当前节点之上的能够实现service资源调度至特定Pod之上的规则。\nservice实现方式 在Kubernetes中service的实现方式有三种模型。\nuserspace 用户空间，可以理解为，用户的请求。 1.1之前包括1.1使用此模型。 用户的请求到达当前节点的内核空间的iptables规则（service规则），由service转发至本地监听的某个套接字上的用户空间的kube-proxy，kube-proxy在处理完再转发给service，最终代理至service相关联的各个Pod，实现调度。\niptables 1.10- 客户端IP请求时，直接请求serviceIP，IP为本地内核空间中的service规则所截取，并直接调度至相关Pod。service工作在内核空间，由iptables直接调度。\nipvs 1.11默认使用，如IPVS没有激活，默认降级为iptables 客户端请求到达内核空间后，直接由ipvs规则直接调度至Pod网络地址范围内的相关Pod资源。\n使用清单创建service资源 SVC中的kubernetes service是集群中各Pod需要与Kubernetes集群apiserver联系时需要通过此svc地址联系。这个地址是集群内的apiserver服务地址。\nservice类型 ClusterIP 默认值，表示分配集群IP地址，仅用于集群内通信。自动分配地址，如需固定，需要指定相应地址，在创建后无法修改。当使用ClusterIP时，只有两个端口有用，port与targetPort\nNodePort 接入集群外部流量，默认分配的端口是30000~32767\nLoadBalancer 表示将Kubernetes部署在虚拟机上，虚拟机是工作在云环境中，云环境支持lbaas（负载均衡及服务的一键调用）。\nExternaName 表示将集群外部服务引用到集群内部中来，在集群内部直接使用。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 spec: ports: # 将哪个端口与后端容器端口建立关联关系。 - port # service对外提供服务的端口 name 指明port的名称 targetPort # 容器的端口 nodePort # 只有类型为NodePort时，才有必要用节点端口，否则此选项是无用的。 protocol 协议，默认TCP seletcor 关联到哪些Pod资源上 app: redis run: redis clusterIP: # clusterIP可以动态分贝可以不配置 type: ClusterIP yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: v1 kind: Service metadata: name: redis namespace: default spec: selector: run: redis clusterIP: 10.96.100.0 type: ClusterIP ports: - port: 6379 targetPort: 6379 service到Pod是有一个中间层的，service会先到endpoints资源(==标准的Kubernetes对象==)， 地址加端口，而后由endpoints关联至后端Pod。\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ kubectl describe svc redis Name: redis Namespace: default Labels: \u0026lt;none\u0026gt; Annotations: kubectl.kubernetes.io/last-applied-configuration: {\u0026#34;apiVersion\u0026#34;:\u0026#34;v1\u0026#34;,\u0026#34;kind\u0026#34;:\u0026#34;Service\u0026#34;,\u0026#34;metadata\u0026#34;:{\u0026#34;annotations\u0026#34;:{},\u0026#34;name\u0026#34;:\u0026#34;redis\u0026#34;,\u0026#34;namespace\u0026#34;:\u0026#34;default\u0026#34;},\u0026#34;spec\u0026#34;:{\u0026#34;clusterIP\u0026#34;:\u0026#34;10.96.100.0\u0026#34;,... Selector: run=redis Type: ClusterIP IP: 10.96.100.0 Port: \u0026lt;unset\u0026gt; 6379/TCP TargetPort: 6379/TCP Endpoints: 10.244.0.5:6379,10.244.1.4:6379 Session Affinity: None Events: \u0026lt;none\u0026gt; service创建完成后，只要kubernetes集群中的DNS是存在的，就可以直接解析其服务名每一个service创建完后，都会在集群DNS中动态添加一个资源记录（不止一个）。\n资源记录的默认格式为，==SVC_NAME.NS_NAME.DOMAIN.LTD.==，==DOMAIN.LTD== 默认是 ==svc.cluster.local==。故redis-svc的资源记录为redis.default.svc.cluster.local.\nnodePort SVC yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: v1 kind: Service metadata: name: redis1 namespace: default spec: selector: run: redis clusterIP: 10.96.100.1 type: NodePort ports: - port: 6380 targetPort: 6379 nodePort: 30001 ExternalName 在本地局域网环境中，但是在Kubernetes集群之外，或者在互联网之上的服务，我们==期望此服务让集群内的服务可访问到==，集群内部使用的都是私网地址，就算可以将请求路由出去，离开本地网络到外部，外部的相应报文也无法回到Kubernetes集群内网中。此时无法正常通信。\nExternalName用于实现，在集群中创建service， 此service端点不是本地Pod，而是service关联至外部服务上。当集群内部客户端区访问service时，由service通过层级转换，请求到外部的服务，外部报文响应给NodeIP，再由NodeIP转交至service，再由service转发至Pod，从而使Pod可访问集群外部服务。\nsh 1 2 3 4 5 $ kubectl explain svc.spec.externalName KIND: Service VERSION: v1 FIELD: externalName \u0026lt;string\u0026gt; Service在实现负载均衡时，还支持sessionAffinity会话粘性，默认情况下基于源IP做粘性的，ClientIP、None(默认)。\nsessionAffinity在service内部实现session保持。支持两种模式 ClusterIP None。设置ClusterIP为，将同一个客户端IP调度到同一个后端Pod。\n无头service (headless) 在访问service时，解析的应为service名称，每个service有其响应的service名称，解析至其ClusterIP，由service调度（dnat）至后端Pod，因此名称解析结果只会有一个ClusterIP。\n所谓headless service，即在解析Service IP时，==无Service ClusterIP，此时，解析服务名时会解析至后端Pod IP之上==，IP数量取决于Pod的数量。这种service被称为headless service。\n设置方式\nyaml 1 ClusterIP: none sh 1 2 3 4 $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 15h nginx ClusterIP None \u0026lt;none\u0026gt; 80/TCP 5s sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $ dig -t A nginx.default.svc.cluster.local. @10.96.0.10 ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.9.4-RedHat-9.9.4-74.el7_6.1 \u0026lt;\u0026lt;\u0026gt;\u0026gt; -t A nginx.default.svc.cluster.local. @10.96.0.10 ;; global options: +cmd ;; Got answer: ;; -\u0026gt;\u0026gt;HEADER\u0026lt;\u0026lt;- opcode: QUERY, status: NOERROR, id: 59245 ;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;nginx.default.svc.cluster.local. IN A ;; ANSWER SECTION: nginx.default.svc.cluster.local. 5 IN A 10.244.0.6 nginx.default.svc.cluster.local. 5 IN A 10.244.1.5 ;; Query time: 1 msec ;; SERVER: 10.96.0.10#53(10.96.0.10) ;; WHEN: 一 7月 08 13:24:47 CST 2019 ;; MSG SIZE rcvd: 154 $ kubectl get pods -o wide -l run=nginx NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-7db9fccd9b-h72bb 1/1 Running 0 14m 10.244.1.5 node01 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-7db9fccd9b-mrv95 1/1 Running 0 14m 10.244.0.6 node02 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; ","permalink":"https://www.161616.top/kubernetes-service/","summary":"在Kubernetes集群中，Pod是有生命周期的，为了能够给对应的客户端提供一个固定访问端点，因此在客户端与服务端（Pod之间）添加了一个固定中间层，这个中间层被称之为Service。Service的工作严重依赖于在Kubernetes集群之上，部署的附件Kubernetes DNS服务。较新版本使用的coreDNS，1.11之前使用的KubeDNS。\nservice的名称解析是强依赖于DNS附件的。因此在部署完Kubernetes后，需要部署CoreDNS或KubeDNS。 Kubernetes要想向客户端提供网络功能，依赖于第三方方案，在较新版本中，可通过CNI容器网络插件标准接口，来接入任何遵循插件标准的第三方方案。 Service从一定程度上来说，在每个节点之上都工作有一个组件Kube-proxy，Kube-proxy将始终监视apiserver当中，有关service资源的变动状态。此过程是通过Kubernetes中固有的请求方法watch来实现的。一旦有service资源的内容发生变动，kube-proxy都将其转换为当前节点之上的能够实现service资源调度至特定Pod之上的规则。\nservice实现方式 在Kubernetes中service的实现方式有三种模型。\nuserspace 用户空间，可以理解为，用户的请求。 1.1之前包括1.1使用此模型。 用户的请求到达当前节点的内核空间的iptables规则（service规则），由service转发至本地监听的某个套接字上的用户空间的kube-proxy，kube-proxy在处理完再转发给service，最终代理至service相关联的各个Pod，实现调度。\niptables 1.10- 客户端IP请求时，直接请求serviceIP，IP为本地内核空间中的service规则所截取，并直接调度至相关Pod。service工作在内核空间，由iptables直接调度。\nipvs 1.11默认使用，如IPVS没有激活，默认降级为iptables 客户端请求到达内核空间后，直接由ipvs规则直接调度至Pod网络地址范围内的相关Pod资源。\n使用清单创建service资源 SVC中的kubernetes service是集群中各Pod需要与Kubernetes集群apiserver联系时需要通过此svc地址联系。这个地址是集群内的apiserver服务地址。\nservice类型 ClusterIP 默认值，表示分配集群IP地址，仅用于集群内通信。自动分配地址，如需固定，需要指定相应地址，在创建后无法修改。当使用ClusterIP时，只有两个端口有用，port与targetPort\nNodePort 接入集群外部流量，默认分配的端口是30000~32767\nLoadBalancer 表示将Kubernetes部署在虚拟机上，虚拟机是工作在云环境中，云环境支持lbaas（负载均衡及服务的一键调用）。\nExternaName 表示将集群外部服务引用到集群内部中来，在集群内部直接使用。\nyaml 1 2 3 4 5 6 7 8 9 10 11 12 spec: ports: # 将哪个端口与后端容器端口建立关联关系。 - port # service对外提供服务的端口 name 指明port的名称 targetPort # 容器的端口 nodePort # 只有类型为NodePort时，才有必要用节点端口，否则此选项是无用的。 protocol 协议，默认TCP seletcor 关联到哪些Pod资源上 app: redis run: redis clusterIP: # clusterIP可以动态分贝可以不配置 type: ClusterIP yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: v1 kind: Service metadata: name: redis namespace: default spec: selector: run: redis clusterIP: 10.","title":"kubernetes概念 - Service"},{"content":"在整个Kubernetes集群来讲 apiserver是访问控制的唯一入口。如通过service或ingress暴露之后，是可以不通过apiserver接入的，只需要通过节点的nodePort或者ingress controller daemonset共享宿主机节点网络名称空间监听的宿主机网络地址（节点地址），直接接入。\n当请求到达APIServer时，会经历几个阶段，如图所示\n图：Kubernetes API 请求的请求处理步骤图\rSource：https://kubevious.io/blog/post/securing-kubernetes-using-pod-security-policy-admission-controller\r任何用户（sa与人类用户）在通过任何方式试图操作API资源时，必须要经历下列的操作：\nAuthentication，这个步骤在建立TLS连接后，验证包含，证书、密码，Token；可以指定多种认证，依次尝试每一个，直到其中一个认证成功。如果认证失败，此时客户端收到的是401。 Authorization，此步骤是在完成 Authentication 后确定了来源用户，此时用户的请求动作必须被授权。如bob用户对pod资源有 get , list 权限操作。如果 Admission Control：此步骤为图3，与 Authorization 不同的时，这里只要有任意准入控制器拒绝，则拒绝；多个准入控制器会按顺序执行 Refer to controlling access\n认证 Kubernetes是高度模块化设计的，因此其认证授权与准入控制是各自都通过插件的方式，可由用户自定义选择经由什么样的插件来完成何种控制逻辑。如对称秘钥认证方式、令牌认证。由于Kubernetes提供的是resetful方式的接口，其服务都是通过HTTP协议提供的，因此认证信息只能经由HTTP协议的认证首部进行传递，此认证首部通常被称作认证令牌(token)。\nssl认证，对于Kubernetes访问来讲，ssl证书能让客户端去确认服务器的身份，（要求服务端发送服务端证书，确认证书是否为认可的CA签署的。）在Kubernetes通信过程当中，重要的是服务器还需认证客户端的身份，因此==Kubectl也应有一个证书，并且此证书为server端所认可的CA所签署的证书==。并且客户端身份也要与证书当中标识的身份保持一致。双方需互相做双向证书认证。认证之后双方基于SSL会话实现加密通讯。\n注：kubernetes认证无需执行串行检查，用户经过任何一个认证插件通过后，即表示认证通过，无需再经由其他插件进行检查。\n授权 kubernetes的授权也支持多种授权插件来完成用户的权限检查，kubernetes 1.6之后开始支持基于RBAC的认证。除此只外还有基于节点的认证、webhook基于http回调机制，通过web的rest服务来实现认证的检查机制。最重要的是RBAC的授权检查机制。基于角色的访问控制，通常只有许可授权，没有拒绝授权。默认都是拒绝。\n在默认情况下，使用kubeadm部署Kubernetes集群是强制启用了RBAC认证的。\n准入控制 一般而言，准入控制本身只是用来定义对应授权检查完成之后的后续其他安全检查操作的。\n用户账号 一般而言用户账号大体上应具有以下信息\nuser 用户，一般而言由username与userid组成。\ngroup 用户组\nextra 用来提供额外信息\nAPI资源 k8sapiserver是分组的，向哪个组，哪个版本的哪个api资源对象发出请求必须进行标识，所有的请求资源通过url path进行标识的。如 /apis/apps/v1/，所有名称空间级别的资源在访问时一般都需指名namespaces关键词，并给出namespaces名称来获取 /apis/apps/v1/namespaces/default/ /apis/apps/v1/namespaces/default/nginx 。\n一个完整意义上的url 对象引用url格式 ==/apis/\u0026lt;GROUPS\u0026gt;/\u0026lt;VERSION\u0026gt;/namespaces/\u0026lt;NameSpace_name\u0026gt;/\u0026lt;Kind\u0026gt;/[/object_id]==\nbash 1 2 3 $ kubectl api-versions admissionregistration.k8s.io/v1beta1 ... Kubernetes中，所有的api都取决于一个根 /apis\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 $ curl -k --cert /etc/k8s/pki/apiserver-kubelet-client.crt --key /etc/k8s/pki/apiserver-kubelet-client.key https://localhost:6443/apis/apps/v1/namespaces/typay/deployments/nginx-ingress-controller { \u0026#34;kind\u0026#34;: \u0026#34;Deployment\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;apps/v1\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;nginx-ingress-controller\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;houtu\u0026#34;, \u0026#34;selfLink\u0026#34;: \u0026#34;/apis/apps/v1/namespaces/houtu/deployments/nginx-ingress-controller\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;dc8cbca7-fcab-49c5-a4f4-b44858bbf603\u0026#34;, \u0026#34;resourceVersion\u0026#34;: \u0026#34;225525\u0026#34;, \u0026#34;generation\u0026#34;: 4, \u0026#34;creationTimestamp\u0026#34;: \u0026#34;2019-11-21T12:47:33Z\u0026#34;, \u0026#34;labels\u0026#34;: { \u0026#34;k8s-app\u0026#34;: \u0026#34;nginx-ingress-controller\u0026#34; }, \u0026#34;annotations\u0026#34;: { \u0026#34;deployment.kubernetes.io/revision\u0026#34;: \u0026#34;4\u0026#34;, \u0026#34;kubectl.kubernetes.io/last-applied-configuration\u0026#34;: \u0026#34;{\\\u0026#34;apiVersion\\\u0026#34;:\\\u0026#34;extensions/v1beta1\\\u0026#34;,\\\u0026#34;kind\\\u0026#34;:\\\u0026#34;Deployment\\\u0026#34;,\\\u0026#34;metadata\\\u0026#34;:{\\\u0026#34;annotations\\\u0026#34;:{},\\\u0026#34;labels\\\u0026#34;:{\\\u0026#34;k8s-app\\\u0026#34;:\\\u0026#34;nginx-ingress-controller\\\u0026#34;},\\\u0026#34;name\\\u0026#34;:\\\u0026#34;nginx-ingress-controller\\\u0026#34;,\\\u0026#34;namespace\\\u0026#34;:\\\u0026#34;houtu\\\u0026#34;},\\\u0026#34;spec\\\u0026#34;:{\\\u0026#34;replicas\\\u0026#34;:1,\\\u0026#34;template\\\u0026#34;:{\\\u0026#34;metadata\\\u0026#34;:{\\\u0026#34;labels\\\u0026#34;:{\\\u0026#34;k8s-app\\\u0026#34;:\\\u0026#34;nginx-ingress-controller\\\u0026#34;}},\\\u0026#34;spec\\\u0026#34;:{\\\u0026#34;containers\\\u0026#34;:[{\\\u0026#34;args\\\u0026#34;:[\\\u0026#34;/nginx-ingress-controller\\\u0026#34;,\\\u0026#34;--default-backend-service=houtu/push-front\\\u0026#34;],\\\u0026#34;env\\\u0026#34;:[{\\\u0026#34;name\\\u0026#34;:\\\u0026#34;POD_NAME\\\u0026#34;,\\\u0026#34;valueFrom\\\u0026#34;:{\\\u0026#34;fieldRef\\\u0026#34;:{\\\u0026#34;fieldPath\\\u0026#34;:\\\u0026#34;metadata.name\\\u0026#34;}}},{\\\u0026#34;name\\\u0026#34;:\\\u0026#34;POD_NAMESPACE\\\u0026#34;,\\\u0026#34;valueFrom\\\u0026#34;:{\\\u0026#34;fieldRef\\\u0026#34;:{\\\u0026#34;fieldPath\\\u0026#34;:\\\u0026#34;metadata.namespace\\\u0026#34;}}}],\\\u0026#34;image\\\u0026#34;:\\\u0026#34;quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.26.1\\\u0026#34;,\\\u0026#34;livenessProbe\\\u0026#34;:{\\\u0026#34;httpGet\\\u0026#34;:{\\\u0026#34;path\\\u0026#34;:\\\u0026#34;/healthz\\\u0026#34;,\\\u0026#34;port\\\u0026#34;:10254,\\\u0026#34;scheme\\\u0026#34;:\\\u0026#34;HTTP\\\u0026#34;},\\\u0026#34;initialDelaySeconds\\\u0026#34;:10,\\\u0026#34;timeoutSeconds\\\u0026#34;:1},\\\u0026#34;name\\\u0026#34;:\\\u0026#34;nginx-ingress-controller\\\u0026#34;,\\\u0026#34;ports\\\u0026#34;:[{\\\u0026#34;containerPort\\\u0026#34;:80,\\\u0026#34;hostPort\\\u0026#34;:80},{\\\u0026#34;containerPort\\\u0026#34;:443,\\\u0026#34;hostPort\\\u0026#34;:443}],\\\u0026#34;readinessProbe\\\u0026#34;:{\\\u0026#34;httpGet\\\u0026#34;:{\\\u0026#34;path\\\u0026#34;:\\\u0026#34;/healthz\\\u0026#34;,\\\u0026#34;port\\\u0026#34;:10254,\\\u0026#34;scheme\\\u0026#34;:\\\u0026#34;HTTP\\\u0026#34;}}}],\\\u0026#34;hostNetwork\\\u0026#34;:true,\\\u0026#34;serviceAccountName\\\u0026#34;:\\\u0026#34;nginx-ingress-serviceaccount\\\u0026#34;,\\\u0026#34;terminationGracePeriodSeconds\\\u0026#34;:60}}}}\\n\u0026#34; } }, \u0026#34;spec\u0026#34;: { \u0026#34;replicas\u0026#34;: 1, \u0026#34;selector\u0026#34;: { \u0026#34;matchLabels\u0026#34;: { \u0026#34;k8s-app\u0026#34;: \u0026#34;nginx-ingress-controller\u0026#34; } }, \u0026#34;template\u0026#34;: { \u0026#34;metadata\u0026#34;: { \u0026#34;creationTimestamp\u0026#34;: null, \u0026#34;labels\u0026#34;: { \u0026#34;k8s-app\u0026#34;: \u0026#34;nginx-ingress-controller\u0026#34; } }, \u0026#34;spec\u0026#34;: { \u0026#34;containers\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;nginx-ingress-controller\u0026#34;, \u0026#34;image\u0026#34;: \u0026#34;quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.26.1\u0026#34;, \u0026#34;args\u0026#34;: [ \u0026#34;/nginx-ingress-controller\u0026#34;, \u0026#34;--default-backend-service=houtu/push-front\u0026#34; ], \u0026#34;ports\u0026#34;: [ { \u0026#34;hostPort\u0026#34;: 80, \u0026#34;containerPort\u0026#34;: 80, \u0026#34;protocol\u0026#34;: \u0026#34;TCP\u0026#34; }, { \u0026#34;hostPort\u0026#34;: 443, \u0026#34;containerPort\u0026#34;: 443, \u0026#34;protocol\u0026#34;: \u0026#34;TCP\u0026#34; } ], \u0026#34;env\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;POD_NAME\u0026#34;, \u0026#34;valueFrom\u0026#34;: { \u0026#34;fieldRef\u0026#34;: { \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;fieldPath\u0026#34;: \u0026#34;metadata.name\u0026#34; } } }, { \u0026#34;name\u0026#34;: \u0026#34;POD_NAMESPACE\u0026#34;, \u0026#34;valueFrom\u0026#34;: { \u0026#34;fieldRef\u0026#34;: { \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;fieldPath\u0026#34;: \u0026#34;metadata.namespace\u0026#34; } } } ], \u0026#34;resources\u0026#34;: { }, \u0026#34;livenessProbe\u0026#34;: { \u0026#34;httpGet\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;/healthz\u0026#34;, \u0026#34;port\u0026#34;: 10254, \u0026#34;scheme\u0026#34;: \u0026#34;HTTP\u0026#34; }, \u0026#34;initialDelaySeconds\u0026#34;: 10, \u0026#34;timeoutSeconds\u0026#34;: 1, \u0026#34;periodSeconds\u0026#34;: 10, \u0026#34;successThreshold\u0026#34;: 1, \u0026#34;failureThreshold\u0026#34;: 3 }, \u0026#34;readinessProbe\u0026#34;: { \u0026#34;httpGet\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;/healthz\u0026#34;, \u0026#34;port\u0026#34;: 10254, \u0026#34;scheme\u0026#34;: \u0026#34;HTTP\u0026#34; }, \u0026#34;timeoutSeconds\u0026#34;: 1, \u0026#34;periodSeconds\u0026#34;: 10, \u0026#34;successThreshold\u0026#34;: 1, \u0026#34;failureThreshold\u0026#34;: 3 }, \u0026#34;terminationMessagePath\u0026#34;: \u0026#34;/dev/termination-log\u0026#34;, \u0026#34;terminationMessagePolicy\u0026#34;: \u0026#34;File\u0026#34;, \u0026#34;imagePullPolicy\u0026#34;: \u0026#34;IfNotPresent\u0026#34; } ], \u0026#34;restartPolicy\u0026#34;: \u0026#34;Always\u0026#34;, \u0026#34;terminationGracePeriodSeconds\u0026#34;: 60, \u0026#34;dnsPolicy\u0026#34;: \u0026#34;ClusterFirst\u0026#34;, \u0026#34;serviceAccountName\u0026#34;: \u0026#34;nginx-ingress-serviceaccount\u0026#34;, \u0026#34;serviceAccount\u0026#34;: \u0026#34;nginx-ingress-serviceaccount\u0026#34;, \u0026#34;hostNetwork\u0026#34;: true, \u0026#34;securityContext\u0026#34;: { }, \u0026#34;schedulerName\u0026#34;: \u0026#34;default-scheduler\u0026#34; } }, \u0026#34;strategy\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;RollingUpdate\u0026#34;, \u0026#34;rollingUpdate\u0026#34;: { \u0026#34;maxUnavailable\u0026#34;: 1, \u0026#34;maxSurge\u0026#34;: 1 } }, \u0026#34;revisionHistoryLimit\u0026#34;: 2147483647, \u0026#34;progressDeadlineSeconds\u0026#34;: 2147483647 }, \u0026#34;status\u0026#34;: { \u0026#34;observedGeneration\u0026#34;: 4, \u0026#34;replicas\u0026#34;: 1, \u0026#34;updatedReplicas\u0026#34;: 1, \u0026#34;unavailableReplicas\u0026#34;: 1, \u0026#34;conditions\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;Available\u0026#34;, \u0026#34;status\u0026#34;: \u0026#34;True\u0026#34;, \u0026#34;lastUpdateTime\u0026#34;: \u0026#34;2019-11-21T12:47:33Z\u0026#34;, \u0026#34;lastTransitionTime\u0026#34;: \u0026#34;2019-11-21T12:47:33Z\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;MinimumReplicasAvailable\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;Deployment has minimum availability.\u0026#34; } ] } } bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ curl -k --cert /etc/k8s/pki/apiserver-kubelet-client.crt --key /etc/k8s/pki/apiserver-kubelet-client.key https://localhost:6443/api/v1/namespaces/houtu { \u0026#34;kind\u0026#34;: \u0026#34;Namespace\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;houtu\u0026#34;, \u0026#34;selfLink\u0026#34;: \u0026#34;/api/v1/namespaces/houtu\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;da736612-d112-4e38-8546-0f2b9169b92f\u0026#34;, \u0026#34;resourceVersion\u0026#34;: \u0026#34;201510\u0026#34;, \u0026#34;creationTimestamp\u0026#34;: \u0026#34;2019-11-21T08:33:38Z\u0026#34; }, \u0026#34;spec\u0026#34;: { \u0026#34;finalizers\u0026#34;: [ \u0026#34;kubernetes\u0026#34; ] }, \u0026#34;status\u0026#34;: { \u0026#34;phase\u0026#34;: \u0026#34;Active\u0026#34; } } 删除操作\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ curl X DELETE -k --cert /etc/k8s/pki/apiserver-kubelet-client.crt --key /etc/k8s/pki/apiserver-kubelet-client.key https://localhost:6443/apis/apps/v1/namespaces/default/deployments/nginx-test/ { \u0026#34;kind\u0026#34;: \u0026#34;Status\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: { }, \u0026#34;status\u0026#34;: \u0026#34;Success\u0026#34;, \u0026#34;details\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;nginx-test\u0026#34;, \u0026#34;group\u0026#34;: \u0026#34;apps\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;deployments\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;12c5184c-82bc-4f7d-8ec8-38a2439983cf\u0026#34; } } $ kubectl get deploy No resources found. 在Kubernetes之上，来自于那些地方的客户端需要和apiserver打交道\n集群外部客户端，通过apiserver对外通信的监听地址 集群之上的客户端，apiserver拥有一个在集群内工作地址，``kubectl get svc` 查看，kubernetes是将apiserver以service方式引入到集群内部，从而使得Pod直接请求集群上的apiserver的服务了。 Pod在请求apiserver上的服务是通过10.96.0.1来进行的。但是apiserver请求是需要做认证的。首先apiserver将自己证书传递给客户端，客户端去校验服务端(apiserver)身份。 服务器发给每个Pod客户端的时候，证书所标明的身份的地址为10.96.0.1，所以在apiserver上手动创建证书，必须要确保证书持有者名称能够解析到两条IP记录才可以。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 47h $ kubectl describe svc kubernetes Name: kubernetes Namespace: default Labels: component=apiserver provider=kubernetes Annotations: \u0026lt;none\u0026gt; Selector: \u0026lt;none\u0026gt; Type: ClusterIP IP: 10.96.0.1 Port: https 443/TCP TargetPort: 6443/TCP Endpoints: 172.31.71.50:6443 Session Affinity: None Events: \u0026lt;none\u0026gt; 类型分类\n对象类 deployment、namespace都属于对象类。 同一类型下的所有对象的集合在reset风格API下叫集合，在Kubernetes中被称之为列表（list）。 xx Kubernetes api账户有两类，真实的账户（人用的账户）userAccount与==Pod客户端serviceAccount==（Pod连接apiserver使用的账户）。\n每个Pod无论你定义与否，都会挂载一个存储卷，这就是Podservice认证时的认证信息，通过secret定义存储卷的方式关联到Pod上，从而使Pod内运行的应用，通次secret保存的认证信息，来连接apiserver并完成认证。\n在每一个名称空间当中都存在一个默认的secret，default-token-xxx，这是让当前名称空间当中所有的Pod资源试图去连接apiserver时，隐藏的、预制的一个认证信息。所以所有的Pod都能直接连接apiserver。此secret所包含的认证信息仅仅是获取当前Pod自身的属性。\n给Pod增加自定义服务账号。 serviceAccount也属于标准的Kubernetes资源，可以自行创建serviceAccount，由自定义Pod使用serviceAccountName去加载自定义serviceAccount。serviceAccount是一个可以使用命令行创建的简单资源对象，可以使用kubectl create serviceaccount，创建也可以使用资源清单进行创建。\n语法\ntext 1 kubectl create serviceaccount {Name} -o yaml --dry-run serveraccount本身不具备权限，可以使用rbac对serviceaccount授予权限\n创建完之后会自动生成一个token信息，用于让sa连接至当前系统认证的信息。注：认证不代表权限，可以登录、认证到Kubernetes但是做不了其余事情。所有的的操作权限靠授权实现的。\n在创建Pod中使用自定义的sa\nyaml 1 2 3 spec: containers: serviceAccountName: admin text 1 2 3 4 5 6 7 Volumes: default-token-4pj85: Type: Secret (a volume populated by a Secret) SecretName: default-token-4pj85 Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; 在定义好secret后，在定义Pod时，使用 imagePullSecrets 指明用哪个secret对象，secret对象中包含了认证私有regsi的账号和密码。\n在Pod当中也可以不使用imagePullSecrets来告诉Pod如何去下载镜像文件。而可以直接使用serviceAccountName。serviceAccountName相当于指定一个sa账号，而sa账号是可以附带认证到私有regsiry的secret信息的。Pod通过sa的Image pull secrets也能完成资源镜像下载时的认证。这样就不会在Pod资源清单中泄露出去secret使用的什么信息。\n使用kubectl describe sa admin\nPod获取私有镜像时的两种认证方式\n在Pod上直接使用imagePullSecrets字段指定认证使用的secret对象。 在Pod自定义serviceAccount，在serviceAccount附加此Pod获取镜像认证时使用的secret对象。 Reference controlling access\n","permalink":"https://www.161616.top/kubernetes-serviceaccount/","summary":"在整个Kubernetes集群来讲 apiserver是访问控制的唯一入口。如通过service或ingress暴露之后，是可以不通过apiserver接入的，只需要通过节点的nodePort或者ingress controller daemonset共享宿主机节点网络名称空间监听的宿主机网络地址（节点地址），直接接入。\n当请求到达APIServer时，会经历几个阶段，如图所示\n图：Kubernetes API 请求的请求处理步骤图\rSource：https://kubevious.io/blog/post/securing-kubernetes-using-pod-security-policy-admission-controller\r任何用户（sa与人类用户）在通过任何方式试图操作API资源时，必须要经历下列的操作：\nAuthentication，这个步骤在建立TLS连接后，验证包含，证书、密码，Token；可以指定多种认证，依次尝试每一个，直到其中一个认证成功。如果认证失败，此时客户端收到的是401。 Authorization，此步骤是在完成 Authentication 后确定了来源用户，此时用户的请求动作必须被授权。如bob用户对pod资源有 get , list 权限操作。如果 Admission Control：此步骤为图3，与 Authorization 不同的时，这里只要有任意准入控制器拒绝，则拒绝；多个准入控制器会按顺序执行 Refer to controlling access\n认证 Kubernetes是高度模块化设计的，因此其认证授权与准入控制是各自都通过插件的方式，可由用户自定义选择经由什么样的插件来完成何种控制逻辑。如对称秘钥认证方式、令牌认证。由于Kubernetes提供的是resetful方式的接口，其服务都是通过HTTP协议提供的，因此认证信息只能经由HTTP协议的认证首部进行传递，此认证首部通常被称作认证令牌(token)。\nssl认证，对于Kubernetes访问来讲，ssl证书能让客户端去确认服务器的身份，（要求服务端发送服务端证书，确认证书是否为认可的CA签署的。）在Kubernetes通信过程当中，重要的是服务器还需认证客户端的身份，因此==Kubectl也应有一个证书，并且此证书为server端所认可的CA所签署的证书==。并且客户端身份也要与证书当中标识的身份保持一致。双方需互相做双向证书认证。认证之后双方基于SSL会话实现加密通讯。\n注：kubernetes认证无需执行串行检查，用户经过任何一个认证插件通过后，即表示认证通过，无需再经由其他插件进行检查。\n授权 kubernetes的授权也支持多种授权插件来完成用户的权限检查，kubernetes 1.6之后开始支持基于RBAC的认证。除此只外还有基于节点的认证、webhook基于http回调机制，通过web的rest服务来实现认证的检查机制。最重要的是RBAC的授权检查机制。基于角色的访问控制，通常只有许可授权，没有拒绝授权。默认都是拒绝。\n在默认情况下，使用kubeadm部署Kubernetes集群是强制启用了RBAC认证的。\n准入控制 一般而言，准入控制本身只是用来定义对应授权检查完成之后的后续其他安全检查操作的。\n用户账号 一般而言用户账号大体上应具有以下信息\nuser 用户，一般而言由username与userid组成。\ngroup 用户组\nextra 用来提供额外信息\nAPI资源 k8sapiserver是分组的，向哪个组，哪个版本的哪个api资源对象发出请求必须进行标识，所有的请求资源通过url path进行标识的。如 /apis/apps/v1/，所有名称空间级别的资源在访问时一般都需指名namespaces关键词，并给出namespaces名称来获取 /apis/apps/v1/namespaces/default/ /apis/apps/v1/namespaces/default/nginx 。\n一个完整意义上的url 对象引用url格式 ==/apis/\u0026lt;GROUPS\u0026gt;/\u0026lt;VERSION\u0026gt;/namespaces/\u0026lt;NameSpace_name\u0026gt;/\u0026lt;Kind\u0026gt;/[/object_id]==\nbash 1 2 3 $ kubectl api-versions admissionregistration.k8s.io/v1beta1 ... Kubernetes中，所有的api都取决于一个根 /apis\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 $ curl -k --cert /etc/k8s/pki/apiserver-kubelet-client.","title":"kubernetes概念 - serviceaccount"},{"content":"RPM概述 RPM (Red Hat Package Manager)，几乎所有的 Linux 发行版本都使用这种形式的软件包管理安装、更新和卸载软件。对于最终用户来说，使用RPM所提供的功能来维护系统是比较容易和轻松的。安装、卸载和升级RPM软件包只需一条命令就可以搞定。RPM维护了一个所有已安装的软件包和文件的数据库，可以让用户进行查询和验证工作。在软件包升级过程中，RPM会对配置文件进行特别处理，绝对不会丢失以往的定制信息。对于程序员RPM可以让我们连同软件的源代码打包成源代码和二进制软件包供最终用户使用。\n一般而言制作一个RPM包包含以下几个步骤\n计划你想要建立什么 收集软件包 根据需要修补软件 计划升级旧有的包 创建可重现的软件构建 概述任何依赖关系 构建rpm 测试rpm（能否安装、升级） RPM capability 能力 运行或安装需要依赖于其他的RPM包本身或所提供的文件为基础的现象被称之为依赖关系。但在制作RPM包时，依赖关系有两类编译依赖与安装依赖。\n自身名字所包含的意义 它提供的文件也有可能被其他软件所依赖，文件本身也能识别成一种能力 编译依赖和安装依赖\n每一个RPM包都提供一种能够完成任务的功能，此种能力很可能被其他RPM所依赖，此能力大多数情况下和RPM名字是相同的。\n制作RPM包的纲要有如下四部\n设定RPM包制作的目录结构（制作车间） 将原材料（源码包、配置文件、补丁包）放置规划好的目录当中。 创建spec文件，指挥如何使用原材料将其制作成rpm包。 编译源代码生成rpm包 在一个特定的目录中提供如下5个子目录 redhat上默认在/usr/src/reahat BUILD 源代码解压以后放置的位置，仅需提供目录。 RPMS 放置制作完成后的RPM包 SOURCES 原材料放置目录（配置文件、源码包、补丁包） SPECS 放置spec文件（纲领性文件）的。 SRPMS SRC rpm包存放位置 RPM优缺点 优点：\n集中管理：RPM可以集中管理安装、升级和删除软件包，保证系统的干净和稳定。 精确控制：RPM提供详细的软件包信息，可以对软件包的安装路径、依赖关系、版本等进行精确控制，使得软件安装更加灵活便捷。 简单易用：RPM提供了一套完整的命令行工具和图形化管理工具，对于普通用户来说，使用起来非常方便。 更新机制：RPM可以根据用户需要进行更新，包括安全更新、功能更新和修复错误等，可以更好地保证系统安全与稳定性。 缺点：\n依赖管理：RPM虽然可以管理软件包的依赖关系，但其解决依赖的方式容易出现问题，可能会出现某些软件包的依赖关系无法解决的情况。 更新速度：由于需要对软件包进行依赖检查等操作，升级软件包可能需要较长时间，特别是当软件包依赖比较复杂时。 存在问题：有时候使用RPM安装的软件包出现问题，需要手动卸载并重新安装，这会导致一些无法预测的麻烦。 兼容性：RPM采用了特定的软件包管理标准，要求安装的软件包必须符合这些标准。因此，RPM可能不太适用于其他Linux系统或自定义的软件包格式。 SPEC文件 制作RPM软件包的关键在于编写SPEC软件包描述文件。要想制作一个rpm软件包就必须写一个软件包描述文件（SPEC）。这个文件中包含了软件包的诸多信息，如软件包的名字、版本、类别、说明摘要、创建时要执行什么指令、安装时要执行什么操作、以及软件包所要包含的文件列表等等。\nSPEC文件通常包括以下几个部分：\n头文件：包括软件包的名称、版本、发布号、授权等信息。\n%description：包括软件包的描述、依赖关系、构建环境等信息。\n%prep：指定源代码的来源和如何解压缩及准备源代码。\n%build：指定如何编译源代码。\n%install：指定如何安装编译好的软件包。\n%check：指定测试源代码的特定部分，通常是用来运行单元测试。\n%clean：指定清除构建过程中产生的临时文件和目录的方法。\n%files：指定哪些文件应该包括在最终的RPM文件中。\n%changelog：记录软件包的变更历史。\nSPEC文件中常用的宏变量 宏变量 说明 %{name} 软件包的名称，如 myapp。 %{version} 软件包的版本号，如 1.0.0。 %{release} 软件包的发布号，如 1。 %{buildroot} RPM 构建过程中的临时根目录目，模拟真实的rootfs。 %{_bindir} 系统安装二进制程序的目录，通常是 /usr/bin。 %{_docdir} man dbus 查看帮助的文档的目录，通常是 /usr/share/doc %{_datadir} 系统安装数据文件的目录，通常是 /usr/share。 %{_includedir} 系统安装头文件的目录，通常是 /usr/include。 %{_libdir} 系统安装库文件的目录，通常是 /usr/lib，64位为 /usr/lib64 %{_datarootdir} 系统安装数据的根目录，通常是 /usr/share。 %{_prefix} 软件的安装路径前缀，通常是 /usr。 %{_sysconfdir} 系统配置文件的目录，通常是 /etc。 %{_var} 系统的/var目录路径 通常情况下在封装包时使用这些宏变量，封装对应目录下的内容而不用考虑每种 Linux 系统的路径差异。例如，我们可以使用 %{_bindir} 来指定安装软件的二进制程序所在目录，而不需要考虑不同系统中二进制程序目录的具体路径，这样可以确保软件在不同系统中能够正确安装和运行。\nSPEC参数解释 文件头部分 参数 详解 Name 软件的名称，构成RPM文件的文件名构成之一 Version 软件的版本号，构成RPM文件的文件名构成之一 Release 该版本打包的次数说明，构成RPM文件的文件名构成之一 Group 软件开发团体名称 Source 软件的来源，可以是URl或者文件。可以有多个，如：\nSource: php-5.3.29.tar.gz\nSource1:php.ini\nSource2:php-fpm Patch 作为软件的补丁。 BuildRoot 设置编译时，临时存放中间文件的路径。 License 软件授权模式。一般使用GPL。 Requires 这个软件的依赖程序。 description 软件的尖端说明。这个是必须的。rpm -qi software name显示的基础说明。\nprep prepare的简写，此段的意思为，尚未进行设置或安装之前，你要编译完成的PRM帮你事先做的事情。一般情况有如下事项：\n进行软件的补丁相关工作。 寻找软件需要的目录是否存在。 事先创建软件所需要的目录，或事先进行的任务。 备份可能会替换的文件。 bash 1 2 id nginx || useradd nginx -s /sbin/nologin -M %setup -q setup 此选项类似于解压之类的工作，常用选项如下表所示：\n参数 说明 %setup 不加任何选项，仅将软件包打开。 %setup -n newdir 将软件包解压在newdir目录。 %setup -c 解压缩之前先产生目录。 %setup -b num 将第num个source文件解压缩。 %setup -T 不使用default的解压缩操作。 %setup -T -b 0 将第0个源代码文件解压缩。 %setup -c -n newdir 指定目录名称newdir，并在此目录产生rpm套件。 %setup -q 提取源码到 BUILD 目录; -q 指不显示输出（quietly） build 构建区域 所要执行的命令为生成软件包服务，如configure、make等操作。\nbash 1 2 ./configure make -j 4 install 安装区域 其中的命令在安装软件包时将执行，如make install命令。在spec文件中的make install后面加上DESTDIR=%{buildroot} DESTDIR是Makefile文件中定义的一个安装路径的变量，根据实际情况修改， 例如mysql和nginx的是DESTDIR，而php的是INSTALL_ROOT。\nbash 1 2 3 %install make INSTALL_ROOT=%{buildroot} install #\u0026lt;==php make install DESTDIR=%{buildroot} #\u0026lt;==nginx files 打包文件区域 定义哪些文件将被打包入RPM中，分为三类\u0026ndash;说明文档（doc），配置文件（config）及执行程序，还可定义文件存取权限，拥有者及组别。\n%defattr (-,root,root) 指定包装文件的属性，分别是(mode,owner,group)，- 表示默认值，对文本文件是0644，可执行文件是0755 %exclude 列出不想打包到rpm中的文件。 %dir 来指定空目录 %config 配置文件 %doc 文档 bash 1 2 3 %files %defattr(-,root,root,-) /usr/share/php-5.3.29/* 注：这里是在虚拟根目录下进行，千万不要写绝对路径，而应用宏或变量表示相对路径。\nchangelog 修改日志区域 语法：第一行是：* 星期 月 日 年 修改人 电子信箱；其中：星期、月份均用英文形式的前3个字母，用中文会报错。 接下来的行写的是修改了什么地方，一般以\u0026quot; - \u0026ldquo;号开始，可写多行。\nbash 1 2 3 * Tue Dec 29 1998 lc \u0026lt;lc.com@gmail.com\u0026gt; - minimum spec and patches changes for openssl - modified for openssl sources 附录：\n12个月简写 全称 简写 January Jan February Feb March Ma April Apr May - June - July - August Aug September Sept October Oct November Nov December Dec 一星期7日简写 全称 简写 Monday Mon Tuesday Tues Wednesday Wed Thurday Thur Friday Fri Saturday Sat Sunday Sun clean 清理区域 用来清理 build 后的临时文件,主要是怕这些旧的文件影响以后编译。主要是要删除 $RPM_BUILD_ROOT 和运行 make clean 。\nD-Bus0 Scriptlets 这些选项可以让你动态的使用 shell 脚本来控制安装和删除，\n%pre rpm安装前执行的脚本 %post rpm安装后执行的脚本 %preun rpm卸载前执行的脚本 %postun rpm卸载后执行的脚本 %preun 在升级的时候会执行， %postun在升级rpm包的时候不会执行\nbash 1 rpm -q --scripts packagename # 查看脚本的信息 SERVER_BIN_DIR\tCLIENT_BIN_DIR 示例nginx.spec bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 Name: nginx Version: 1.13.9 Release: 1%{?dist} Summary: nginx Group: nginx License: GPL URL: http://dangjian.chinamoblie.com Packager: nginx Vendor: nginx Source0: nginx-1.13.9.tar.gz Source1: nginx.service Requires: openssl-devel,pcre-devel BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot %description nginx %prep id nginx || useradd nginx -s /sbin/nologin -M %setup -q chmod +x ./configure %build ./configure --prefix=/usr/share/nginx \\ --sbin-path=/usr/sbin/nginx \\ --with-stream \\ --conf-path=/etc/nginx/nginx.conf \\ --error-log-path=/data/nginx/log/error.log \\ --pid-path=/data/nginx/run/nginx.pid \\ --lock-path=/data/nginx/lock/nginx.lock \\ --user=nginx --group=nginx \\ --with-http_ssl_module \\ --with-http_stub_status_module \\ --with-http_flv_module \\ --with-http_gzip_static_module \\ --http-log-path=/data/nginx/log/access.log --http-client-body-temp-path=/data/nginx/tmp/client/ \\ --http-proxy-temp-path=/data/nginx/tmp/proxy/ \\ --http-fastcgi-temp-path=/data/nginx/tmp/fcgi/ \\ --http-scgi-temp-path=/data/nginx/tmp/scgi \\ --http-uwsgi-temp-path=/data/nginx/tmp/uwsgi make %install make install DESTDIR=$RPM_BUILD_ROOT %{__install} -p -D %{SOURCE1} %{buildroot}/usr/lib/systemd/system/nginx.service %{__mkdir_p} /data/nginx/tmp/{client,uwsgi,scgi,fcgi,proxy} %files %defattr(-,root,root,-) %attr(0644,root,root) /usr/share/nginx/* %attr(0755,root,root) /usr/sbin/nginx %attr(0644,root,root) /etc/nginx/* %attr(0744,nginx,nginx) /data/nginx/* %attr(0744,root,root) /usr/lib/systemd/system/nginx.service %clean rm -rf $RPM_BUILD_DIR/%{name}-%{version} %pre id nginx || useradd nginx -s /sbin/nologin -M %preun systemctl stop nginx %postun rm -fr /data/nginx/ userdel nginx %changelog * Sun Aug 24 2015 LC 1.15-1 - package libiconv-1.15 rpmbuild rpmbuild 是用于构建 RPM 包的工具。RPM 是一种软件包管理格式，它可以简化软件的分发，使其在不同的 Linux 系统上易于安装和使用。rpmbuild 工具可以帮助我们构建这样的 RPM 包。\n参数 说明 -ba 既生成src.rpm又生成二进制rpm -bs 只生成src的rpm -bb 只生二进制的rpm -bp 执行到pre -bc 执行到 build段 -bi 执行install段 -bl 检测有文件没包含 rpmbuild 安装 rpm包并不仅仅限制于 Fedora/Redhat ，也可以使用在其他的发行版中\n对于 CentOS：\nbash 1 sudo yum install -y rpm-build redhat-rpm-config rpmdevtools 对于 Fedora：\nbash 1 sudo dnf install -y rpm-build redhat-rpm-config rpmdevtools 对于 Ubuntu：\nbash 1 sudo apt install -y rpm 查看默认宏\nbash 1 rpmbuild --showrc 创建 RPM 构建目录 要开始构建 RPM 包，您需要先创建 RPM 构建目录和相关文件。使用以下命令创建 RPM 构建目录和必要的子目录：\nbash 1 2 cd ~ mkdir -p rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} 上述命令创建了 5 个子目录：\nBUILD: 构建 RPM 包所需的源码和二进制文件存放的目录。 RPMS: 二进制 RPM 包保存的目录。 SOURCES: 存储软件包的源代码，rpmbuild 根据该代码创建 RPM 包。 SPECS: INCLUDE metadata and build instructions files for creating RPM files SRPMS: 用于存储源 RPM 包的目录。 rpmbuild示例：如何使用rpmbuild制作php rpm包 sh 1 mkdir -pv ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} php有一个依赖库，在yum源于epel源中都没有需要自己打包libiconv\n编写 libiconv 的spec文件\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 %define __os_install_post %{nil} %define debug_package %{nil} Name: libiconv Version: 1.15 Release: 1%{?dist} Summary: liconv Group: liconv License: GPL URL: http://www.test.net Packager: test Vendor: test Source0: libiconv-1.15.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot %description iconv %prep %setup -q %build ./configure --prefix=/usr/share/libiconv-1.15 \\ make %install make install DESTDIR=%{buildroot} %files %defattr(-,root,root,-) %attr(0655,root,root) /usr/share/libiconv-1.15/* %attr(0755,root,root) /usr/share/libiconv-1.15/bin/* %clean rm -rf $RPM_BUILD_DIR/%{name}-%{version} %post ln -sv /usr/share/libiconv-1.15/ /usr/share/libiconv %changelog * Sun Aug 24 2015 LC 1.15-1 - package libiconv-1.15 打包libiconv遇到的错误 bash 1 2 3 4 5 6 7 Binary file /root/rpmbuild/BUILDROOT/libiconv-1.15.el7.centos.x86_64/usr/share/libiconv-1.15/bin/iconv matches Found \u0026#39;/root/rpmbuild/BUILDROOT/libiconv-1.15-1.el7.centos.x86_64\u0026#39; in installed files; aborting error: Bad exit status from /var/tmp/rpm-tmp.6AgqPk (%install) RPM build errors: Bad exit status from /var/tmp/rpm-tmp.6AgqPk (%install) 问题原因：\n在rpm构建过程中，在％install阶段结束时，运行 /usr/lib/rpm/check-buildroot脚本以检查构建根目录中的文件。此脚本扫描构建根目录中的所有文件，以获取${RPM_BUILD_ROOT}路径的任何引用。\n也就是说libiconv已经编译完成。一般情况下，是可以正常使用的。所以不需要他检查构建根目录。\n解决方法：通过报错信息可以得到如下提示\nbash 1 2 RPM build errors: Bad exit status from /var/tmp/rpm-tmp.6AgqPk (%install) 根据保存信息查看文件的执行过程。/var/tmp/rpm-tmp.6AgqPk，并发现脚本在执行到/usr/lib/rpm/check-buildroot时停止了。也就是说，执行这个脚本检查构建根目录时$?不为0\nbash 1 2 3 4 5 cd \u0026#39;libiconv-1.15\u0026#39; make install DESTDIR=/root/rpmbuild/BUILDROOT/libiconv-1.15-1.el7.centos.x86_64 /usr/lib/rpm/check-buildroot 查看/usr/lib/rpm/check-buildroot脚本，发现只有此段检查才$?返回的是1。\nbash 1 2 3 4 5 test -s \u0026#34;$tmp\u0026#34; \u0026amp;\u0026amp; { cat \u0026#34;$tmp\u0026#34; echo \u0026#34;Found \u0026#39;$RPM_BUILD_ROOT\u0026#39; in installed files; aborting\u0026#34; exit 1 } || : 由于压根不知道 $tmp 在哪里传入的。又压根可以不检查构建根目录的。直接在 /usr/lib/rpm/check-buildroot 脚本最前面加上 exit 0 让构建RPM包时跳过此步骤。之后成功完成rpm构建。\n参考文档： pk\u0026rsquo;s Tech Page: Found \u0026lsquo;${RPM_BUILD_ROOT}\u0026rsquo; in installed files; aborting\nc++ - What does /usr/lib/rpm/check-buildroot do? - Stack Overflow\n编写spec文件 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 Name: php Version: 7.1.17 Release: 1%{?dist} Summary: php Group: php License: GPL URL: http://php.org Packager: php Vendor: php Source0: php-7.1.17.tar.bz2 Source1: php.ini BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot Requires: libiconv,zlib-devel,libxml2-devel,libjpeg-devel,libjpeg-turbo-devel,freetype-devel,libpng-devel,gd-devel,curl-devel,libxslt-devel,bzip2-devel,gmp-devel,readline-devel,mcrypt,mhash,libmcrypt-devel %description php %prep id nginx || useradd nginx -s /sbin/nologin -M %setup -q %build ./configure \\ --prefix=/usr/share/php-7.1.17 \\ --with-config-file-path=/etc/php/ \\ --exec-prefix=/usr \\ --bindir=/usr/bin \\ --sbindir=/usr/sbin \\ --mandir=/usr/share/man \\ --sysconfdir=/etc/php/ \\ --with-mysqli=mysqlnd \\ --with-iconv-dir=/usr/share/libiconv \\ --with-jpeg-dir \\ --with-png-dir \\ --with-zlib-dir \\ --with-libxml-dir \\ --enable-xml \\ --disable-rpath \\ --enable-safe-mode \\ --enable-bcmath \\ --enable-shmop \\ --enable-sysvsem \\ --enable-inline-optimization \\ --with-curl \\ --enable-mbregex \\ --enable-fpm \\ --enable-mbstring \\ --with-mcrypt \\ --with-gd \\ --enable-gd-native-ttf \\ --with-openssl \\ --with-mhash \\ --enable-pcntl \\ --enable-sockets \\ --with-xmlrpc \\ --enable-zip \\ --enable-soap \\ --enable-short-tags \\ --enable-zend-multibyte \\ --enable-static \\ --with-xsl \\ --with-fpm-user=nginx \\ --with-fpm-group=nginx make -j 4 %install rm -rf %{buildroot} make INSTALL_ROOT=%{buildroot} install %{__install} -p -D %{SOURCE1} %{buildroot}/etc/php/php.ini %files %defattr(-,root,root,-) /usr/share/php-7.1.17/* %attr(0744,root,root) /usr/bin/* %attr(0744,root,root) /usr/sbin/* /usr/share/man/* /etc/php/* %pre id nginx || useradd nginx -s /sbin/nologin -M %post cp /etc/php/php-fpm.conf.default /etc/php/php-fpm.conf cp /etc/php/php-fpm.d/www.conf.default /etc/php/php-fpm.d/www.conf %postun userdel nginx %changelog * Sun Aug 10 2018 lc zhoushilong - package php-7.1.71 构建PHP RPM包遇到的问题 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 RPM build errors: bogus date in %changelog: Sun Aug 10 2018 lc zhoushilong Explicit %attr() mode not applicaple to symlink: /root/rpmbuild/BUILDROOT/php-7.1.17-1.el7.centos.x86_64/usr/bin/phar Installed (but unpackaged) file(s) found: /.channels/.alias/pear.txt /.channels/.alias/pecl.txt /.channels/.alias/phpdocs.txt /.channels/__uri.reg /.channels/doc.php.net.reg /.channels/pear.php.net.reg /.channels/pecl.php.net.reg /.depdb /.depdblock /.filemap /.lock 解决方法如下：\n方法1：生成的rpm包里有前面在%files里添加的这个文件，如下：\nbash 1 /usr/local/php/.channels 方法2：下面是直接删除的解决办法，实践OK（视具体情况是删除还是添加选一个即可）\nbash 1 rm -rf %{buildroot}/{.channels,.depdb,.depdblock,.filemap,.lock} 方法三： /usr/lib/rpm/macros 修改宏\nbash 1 2 3 4 # 构建根目录中的未打包文件是否应终止构建？ %_unpackaged_files_terminate_build 1 # 把1改为0只警告 %__check_files %{_rpmconfigdir}/check-files %{buildroot} # 这一行，把这一行注释掉，然后重新编译 %__check_files说明：\nBuild configuration macros. Script gets packaged file list on input and buildroot as first parameter. Returns list of unpackaged files, i.e. files in $RPM_BUILD_ROOT not packaged. Note: Disable (by commenting out) for legacy compatibility.\n构建配置宏。 脚本在输入和buildroot上获取打包文件列表作为第一个参数。 返回未打包文件的列表，即 $ RPM_BUILD_ROOT 中未打包的文件。 注意：禁用（通过注释掉）旧版兼容性。\n打包报错 sh 1 2 3 4 5 6 7 8 + \u0026#39;%{__debug_install_post}\u0026#39; /var/tmp/rpm-tmp.o0N7t4: line 45: fg: no job control error: Bad exit status from /var/tmp/rpm-tmp.o0N7t4 (%install) RPM build errors: bogus date in %changelog: Sun Aug 24 2018 YB 1.14.2 Bad exit status from /var/tmp/rpm-tmp.o0N7t4 (%install) 解决：使用以下命令禁用check-buildroot\ntext 1 2 %define __arch_install_post %{nil} %define __os_install_post %{nil} 关闭调试信息：\ntext 1 %global debug_package %{nil} 参考网址 rpmbuild 之 /usr/lib/rpm/check-buildroot\nrpmbuild - how to disable check-buildroot?\n[实践OK]rpmbuild报error: Installed (but unpackaged) file(s) found的解决办法\nrpmbuild 打包rpm实战\nrpmbuild 中文手册\nCreating RPM packages :: Fedora Docs Site\nHow to create an RPM package/zh-cn - Fedora Project Wiki\nFedora Packaging Guidelines - Fedora Project Wiki\n","permalink":"https://www.161616.top/rpm-package/","summary":"RPM概述 RPM (Red Hat Package Manager)，几乎所有的 Linux 发行版本都使用这种形式的软件包管理安装、更新和卸载软件。对于最终用户来说，使用RPM所提供的功能来维护系统是比较容易和轻松的。安装、卸载和升级RPM软件包只需一条命令就可以搞定。RPM维护了一个所有已安装的软件包和文件的数据库，可以让用户进行查询和验证工作。在软件包升级过程中，RPM会对配置文件进行特别处理，绝对不会丢失以往的定制信息。对于程序员RPM可以让我们连同软件的源代码打包成源代码和二进制软件包供最终用户使用。\n一般而言制作一个RPM包包含以下几个步骤\n计划你想要建立什么 收集软件包 根据需要修补软件 计划升级旧有的包 创建可重现的软件构建 概述任何依赖关系 构建rpm 测试rpm（能否安装、升级） RPM capability 能力 运行或安装需要依赖于其他的RPM包本身或所提供的文件为基础的现象被称之为依赖关系。但在制作RPM包时，依赖关系有两类编译依赖与安装依赖。\n自身名字所包含的意义 它提供的文件也有可能被其他软件所依赖，文件本身也能识别成一种能力 编译依赖和安装依赖\n每一个RPM包都提供一种能够完成任务的功能，此种能力很可能被其他RPM所依赖，此能力大多数情况下和RPM名字是相同的。\n制作RPM包的纲要有如下四部\n设定RPM包制作的目录结构（制作车间） 将原材料（源码包、配置文件、补丁包）放置规划好的目录当中。 创建spec文件，指挥如何使用原材料将其制作成rpm包。 编译源代码生成rpm包 在一个特定的目录中提供如下5个子目录 redhat上默认在/usr/src/reahat BUILD 源代码解压以后放置的位置，仅需提供目录。 RPMS 放置制作完成后的RPM包 SOURCES 原材料放置目录（配置文件、源码包、补丁包） SPECS 放置spec文件（纲领性文件）的。 SRPMS SRC rpm包存放位置 RPM优缺点 优点：\n集中管理：RPM可以集中管理安装、升级和删除软件包，保证系统的干净和稳定。 精确控制：RPM提供详细的软件包信息，可以对软件包的安装路径、依赖关系、版本等进行精确控制，使得软件安装更加灵活便捷。 简单易用：RPM提供了一套完整的命令行工具和图形化管理工具，对于普通用户来说，使用起来非常方便。 更新机制：RPM可以根据用户需要进行更新，包括安全更新、功能更新和修复错误等，可以更好地保证系统安全与稳定性。 缺点：\n依赖管理：RPM虽然可以管理软件包的依赖关系，但其解决依赖的方式容易出现问题，可能会出现某些软件包的依赖关系无法解决的情况。 更新速度：由于需要对软件包进行依赖检查等操作，升级软件包可能需要较长时间，特别是当软件包依赖比较复杂时。 存在问题：有时候使用RPM安装的软件包出现问题，需要手动卸载并重新安装，这会导致一些无法预测的麻烦。 兼容性：RPM采用了特定的软件包管理标准，要求安装的软件包必须符合这些标准。因此，RPM可能不太适用于其他Linux系统或自定义的软件包格式。 SPEC文件 制作RPM软件包的关键在于编写SPEC软件包描述文件。要想制作一个rpm软件包就必须写一个软件包描述文件（SPEC）。这个文件中包含了软件包的诸多信息，如软件包的名字、版本、类别、说明摘要、创建时要执行什么指令、安装时要执行什么操作、以及软件包所要包含的文件列表等等。\nSPEC文件通常包括以下几个部分：\n头文件：包括软件包的名称、版本、发布号、授权等信息。\n%description：包括软件包的描述、依赖关系、构建环境等信息。\n%prep：指定源代码的来源和如何解压缩及准备源代码。\n%build：指定如何编译源代码。\n%install：指定如何安装编译好的软件包。\n%check：指定测试源代码的特定部分，通常是用来运行单元测试。\n%clean：指定清除构建过程中产生的临时文件和目录的方法。\n%files：指定哪些文件应该包括在最终的RPM文件中。\n%changelog：记录软件包的变更历史。\nSPEC文件中常用的宏变量 宏变量 说明 %{name} 软件包的名称，如 myapp。 %{version} 软件包的版本号，如 1.","title":"Unix归档模式cpio - 深入剖析与构建rpm包"},{"content":"Kubernetes API Object 在Kubernetes线群中，Kubernetes对象是持久化的实体（最终存入etcd 中的数据），集群中通过这些实体来表示整个集群的状态。前面通过kubectl来提交的资源清单文件，将我们的YAML文件转换成集群中的一个API对象的，然后创建的对应的资源对象。\nKubernetes API是一个以JSON为主要序列化方式的HTTP服务，除此之外支持Protocol Buffers序列化方式（主要用干集群内年件间的通信）。为了api的可扩展性，Kubemetes在不同的API路径（/api/v1或/apis/batch）下面支持了多个API版本，不同的API版本就味不同级别稳定性和支持。\nAlpha ：例如v1Alpha：默认情况下是禁用的，可以随时删除对功能的支持。 Beta：例如 v2beta1 默认是启用的，表示代码已经经过了很好的测试，但是对象的语义可能会在施后的版本中以不兼咨的方式更改 Stable：例如：v1 表示已经是稳定版本，也会出现在后续的很多版本中。 在Kubernetes集群中，一个API对象在Etcd 里的完整资源路径，是由：group （API组）、 version （API版本） 和 Resource API资源类型）三个部分组成。通过这种的结构，整个Kubernetes 中所有API对象，就可以用如下的树形结构表示出来：\nKubernetes API Object的使用 API对象组成查看：kubectl get --raw /\n通常，KubernetesAPI支持通过标准HTTP P0ST、PUT、DELETE 和 GET 在指定PATH路径上创建、更新、删除和检索操作，并使用JSON作为默认的数据交互格式。\n如要创建一个Deployment对象，那YAML文件的声明就需：\nyaml 1 2 apiVersion: apps/v1 # kind: Deployment Deployment就是这个API对象的资源类型（Resource），apps就是它的组（Group），v1就是它的版本（Version）。API Group、Version 和资源满唯一定义了一个HTTP路径，然后在kube-apiserver 对这个url进行了监听，然后把对应的请求传递给了对应的控制器进行处理。\nAPI对象参考文档\n授权插件分类 Node 由节点来认证。\nABAC 基于属性的访问控制，RBAC之前的授权控制的插件算法\nRBAC Role-based Access Control。\nWebhook 基于http的回调机制来实现访问控制。\nRBAC 基于角色的访问控制可以理解为，角色（role）反而是授权的机制，完成了权限的授予、分配等。角色是指一个组织或者任务工作中的位置，通常代表一种权利、资格、责任等。在基于角色的访问控制中还有一种术语叫做 ==许可==（permission）。\n简单来讲就如同上图描述，使用户去扮演这个角色，而角色拥有这个权限，所以用户拥有这个角色的权限。所以授权不授予用户而授予角色。\nRBAC 使用 rbac.authorization.k8s.ioAPI组来驱动鉴权操作，允许管理员通过 Kubernetes API 动态配置策略。\n在 1.8 版本中，RBAC 模式是稳定的并通过 rbac.authorization.k8s.io/v1 API 提供支持。\n启用RBAC 要使用用RBAC，需要在启动kube-apiserver时添加--authorization-mode=RBAC 参数。\nAPI概述 ==/apis/[group]/[version]/namespaces/[namespaces_name]/[kind][/object_id]== 在Kubernetes当中的RBAC在实现授权时，无非就是定义标准的角色，在角色上绑定权限。使用户扮演角色。将这些概念体现为：\nrole 标准的Kubernetes资源\noperations 允许那些对象.. objects 执行那些操作.. rolebinding 角色绑定\nuser 将那个用户(user account OR service account)\u0026hellip;\nrole 绑定在那个角色上\u0026hellip;\nRule：规则是一组属于不同API Group资源上的一组操作的集合\nGroup：用来关联多个账户，集群中有一些默认建的组，比如cluster-admin\nSubject：主题，对应集群中尝试操作的对象，集群中定义了3种类型的主题资源：\nuser account：用户，Kubernetes真正意义上User，而是使用证书的CN与O，加上kubeconfig上下文实现用户与组，这个用户是由外部独立服务进行管理的，对于用户的管理集群内部没有一个关联的资源对象，所以用户不能通过集群内部的API来进行管理。 service account：通过KubernetesAPI来管理的一些用户帐号，和namespace进行关联的，适用于集群内部运行的应用程序，需要通过API来完成权限认证，所以在集群内部进行权限操作。 在Kubern1etes之上资源分属于两种级别\ncluster\nnamespaces\n所以role和rolebinding是在名称空间级别，授予此名称空间范围内的许可权限的。除了role和rolebinding之外，集群还有另外两个组件：\ncluster role 集群角色。\ncluster rolebinding 集群角色绑定。\ncluster role当中定义的权限是相对于多个名称空间共有，如果使用rolebinding绑定，这个权限被限制为 用户仅能获取rolebinding所属名称空间上的所有权限。\n使用 kubeconfig 文件组织集群访问 ​\t在使用kubectl命令时是有使用配置文件的，配置文件是kubectl连接服务器认证文件。kubectl config 是专门用来管理kubectl的配置文件的。所有连接apiserver的客户端在认证时，如果基于配置文件来保存客户端的认证信息就应该将其配置配置为配置文件。\n​\tkubernetes组件除了apiserver都可以被称之问apiserver客户端。每个组件为了能够连接正确的集群。apiserver需提供正确的私钥、证书等认证时使用的信息需要将这些信息保存为一个配置文件，此配置文件被叫做kubeconfig。\n​\tkubeconfig 文件可以用来组织有关集群、用户、命名空间和身份认证机制的信息。kubectl 命令行工具使用 kubeconfig 文件来与集群的 API 服务器进行通信。\n​\t默认情况下 kubeconfig 在 $HOME/.kube 目录下查找名为 config 的文件。可以设置 环境变量KUBECONFIG 或者设置 kubectl --kubeconfig 来选择指定的kubeconfig\ncontext ​\tkubeconfig 的 context ，定义对访问参数进行分组。每个context都有三个参数：cluster、namespace 和 user。默认情况下，kubectl 命令行工具使用 namespace 参数设置的值与集群进行通信。默认为defaul。\nkubectl config get-contexts 查看拥有上下文 kubectl config use-context 选择上下文 kubectl config set-credentials 设置一个用户项 reference\nset-credentials\nkubeconfig\n角色的创建与管理 ​\tKubernetes的访问权限主要可以梳理为几个步骤\n创建用户：使用CA签发用户认证证书作为用户名称。\n创建权限组：创建role或clusterrole确定操作权限。\n绑定用户和权限组：创建rolebinding或clusterrolebinding将权限绑定在用户上。\n使用用户访问验证：切换kubeconfig。\nrole cluster role rolebinding cluster rolebinding都是标准的Kubernetes资源，可以通过kubectl explain查看，或kubectl create role 创建\nrole在限制资源范围时有三种方式：\nresources 资源类别，允许对这些类所有资源支持授权 操作。 resource Names 资源名称，表示对此类别当中，某个或某些特定资源执行操作。 Non-Resource URLs 非资源url，是一些不能定义为对象的资源，在Kubernetes中通常表示对某些资源所执行的一种操作，或某种特殊操作。 bash 1 2 3 4 5 6 7 8 ROLENAME=default-admin NS=default kubectl create clusterrole ${ROLENAME} \\ --verb=get,list \\ --resource=pods,deployments \\ -o yaml \\ -n default \\ --dry-run=client yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: pod-list namespace: kube-system rules: - apiGroups: - \u0026#34;\u0026#34; resources: - pods - deployments verbs: - get - list - apiGroups: # 表示对哪些api群组内的资源做操作。 - apps 将权限与角色绑定\nbash 1 2 3 4 5 6 7 8 9 USERNAME=scott ROLENAME=default-admin NS=default kubectl create clusterrolebinding ${ROLENAME} \\ --clusterrole=${ROLENAME} \\ --group=${ROLENAME} \\ -n ${NS} \\ -o yaml \\ --dry-run=client yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: podlist:testrbac namespace: kube-system roleRef: # 引用那个role apiGroup: rbac.authorization.k8s.io # 那个api之内的 kind: Role # 哪一类 name: pod-list # role名称，为了避免引用的是cluster role必须使用 此方式来定义 subjects: # 动作的执行主题 # 对于user group是 rbac.auth... 对于serviceaccount是\u0026#34;\u0026#34; - apiGroup: rbac.authorization.k8s.io kind: User name: testrbac # user并不是单独存在的用户资源 创建用户\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 USERNAME=cylon openssl genrsa -out ${USERNAME}.key 2048 o=default-admin openssl req -new \\ -key ${USERNAME}.key \\ -out ${USERNAME}.csr \\ -subj \u0026#34;/CN=${USERNAME}/O=${o}\u0026#34; \\ -days 3650 openssl x509 -req \\ -in ${USERNAME}.csr \\ -CA ca.crt \\ -CAkey ca.key \\ -out ${USERNAME}.crt \\ -days 360 配置kubeconfig\nbash 1 2 3 4 5 6 USERNAME=cylon kubectl config set-credentials ${USERNAME} \\ --client-certificate=/etc/kubernetes/pki/${USERNAME}.crt \\ --client-key=/etc/kubernetes/pki/${USERNAME}.key \\ --embed-certs=true \\ --kubeconfig=${USERNAME} # 输出到指定的配置文件，若不指定写入KUBECONFIG环境变量指定的路径 设置上下文\nbash 1 2 3 4 5 USERNAME=cylon kubectl config set-context ${USERNAME}@kubernetes \\ --cluster=kubernetes \\ --user=${USERNAME} --kubeconfig=${USERNAME} 设置集群\ntext 1 2 3 4 5 kubectl config set-cluster k8s \\ --certificate-authority=/etc/kubernetes/pki/ca.crt \\ --embed-certs=true \\ --server=https://127.0.0.1:6443 \\ --kubeconfig=${USERNAME} 在RBAC上进行授权时，允许我们存在3类组件 useraccount group serviceaccount\nuser 授权绑定时，cluster rolebinding 或 rolebinding 都可以绑定在user上，也可以绑定在group上，还可以绑定在service account上。 绑定在用户上，表示只授权一个用户扮演相关角色。\n绑定在在一个组上表示授权组内所有用户都在一个角色。所以想一次授权多个用户在一个名称空间中拥有一个权限可以定义为组。授权时做组授权。\n如果任何一个Pod在启动时以serviceaccount name作为使用的serviceAccount，Pod中的应用程序就拥有了它所授予的权限。\n创建Pod时可以给Pod指明一个属性。serviceAccount\n","permalink":"https://www.161616.top/kubernetes-rbac/","summary":"Kubernetes API Object 在Kubernetes线群中，Kubernetes对象是持久化的实体（最终存入etcd 中的数据），集群中通过这些实体来表示整个集群的状态。前面通过kubectl来提交的资源清单文件，将我们的YAML文件转换成集群中的一个API对象的，然后创建的对应的资源对象。\nKubernetes API是一个以JSON为主要序列化方式的HTTP服务，除此之外支持Protocol Buffers序列化方式（主要用干集群内年件间的通信）。为了api的可扩展性，Kubemetes在不同的API路径（/api/v1或/apis/batch）下面支持了多个API版本，不同的API版本就味不同级别稳定性和支持。\nAlpha ：例如v1Alpha：默认情况下是禁用的，可以随时删除对功能的支持。 Beta：例如 v2beta1 默认是启用的，表示代码已经经过了很好的测试，但是对象的语义可能会在施后的版本中以不兼咨的方式更改 Stable：例如：v1 表示已经是稳定版本，也会出现在后续的很多版本中。 在Kubernetes集群中，一个API对象在Etcd 里的完整资源路径，是由：group （API组）、 version （API版本） 和 Resource API资源类型）三个部分组成。通过这种的结构，整个Kubernetes 中所有API对象，就可以用如下的树形结构表示出来：\nKubernetes API Object的使用 API对象组成查看：kubectl get --raw /\n通常，KubernetesAPI支持通过标准HTTP P0ST、PUT、DELETE 和 GET 在指定PATH路径上创建、更新、删除和检索操作，并使用JSON作为默认的数据交互格式。\n如要创建一个Deployment对象，那YAML文件的声明就需：\nyaml 1 2 apiVersion: apps/v1 # kind: Deployment Deployment就是这个API对象的资源类型（Resource），apps就是它的组（Group），v1就是它的版本（Version）。API Group、Version 和资源满唯一定义了一个HTTP路径，然后在kube-apiserver 对这个url进行了监听，然后把对应的请求传递给了对应的控制器进行处理。\nAPI对象参考文档\n授权插件分类 Node 由节点来认证。\nABAC 基于属性的访问控制，RBAC之前的授权控制的插件算法\nRBAC Role-based Access Control。\nWebhook 基于http的回调机制来实现访问控制。\nRBAC 基于角色的访问控制可以理解为，角色（role）反而是授权的机制，完成了权限的授予、分配等。角色是指一个组织或者任务工作中的位置，通常代表一种权利、资格、责任等。在基于角色的访问控制中还有一种术语叫做 ==许可==（permission）。\n简单来讲就如同上图描述，使用户去扮演这个角色，而角色拥有这个权限，所以用户拥有这个角色的权限。所以授权不授予用户而授予角色。\nRBAC 使用 rbac.authorization.k8s.ioAPI组来驱动鉴权操作，允许管理员通过 Kubernetes API 动态配置策略。","title":"kubernetes概念 - RBAC"},{"content":"近期因 centos 6.x 默认 openssh 扫描存在大量漏洞，基于安全考虑，需要将 openssh_5.3p1 升级为最新版，网上查了很多教程，发现 openssh 存在大量依赖，不解决依赖问题很难保证其他服务政策。而 openssl 又被大量程序依赖。实在是头疼。最后发现一个不破坏各种依赖又可以完美升级的方案\n注：curl wget yum 等依赖 openssl gitlab依赖 openssh 因卸载 openssh 与 openssl 编译安装导致各种依赖程序被破坏，虽然最后升级成功，但是wget curl 和代码库被破坏。\n下载 openssh-7.7p 源码包 下载地址\n下载之后解压看 README 和 INSTALL\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 1. Prerequisites ---------------- A C compiler. Any C89 or better compiler should work. Where supported, configure will attempt to enable the compiler\u0026#39;s run-time integrity checking options. Some notes about specific compilers: - clang: -ftrapv and -sanitize=integer require the compiler-rt runtime (CC=clang LDFLAGS=--rtlib=compiler-rt ./configure) You will need working installations of Zlib and libcrypto (LibreSSL / OpenSSL) Zlib 1.1.4 or 1.2.1.2 or greater (earlier 1.2.x versions have problems): http://www.gzip.org/zlib/ libcrypto (LibreSSL or OpenSSL \u0026gt;= 1.0.1 \u0026lt; 1.1.0) LibreSSL http://www.libressl.org/ ; or OpenSSL http://www.openssl.org/ LibreSSL/OpenSSL should be compiled as a position-independent library (i.e. with -fPIC) otherwise OpenSSH will not be able to link with it. If you must use a non-position-independent libcrypto, then you may need to configure OpenSSH --without-pie. Note that because of API changes, OpenSSL 1.1.x is not currently supported. The remaining items are optional. 官方给出的文档中提到的先决条件openssh安装依赖 zlib1.1.4 并且 openssl\u0026gt;=1.0.1 版本就可以了。那么直接看当前系统的 openssl 版本是多少\ntext 1 2 3 4 5 6 $ openssl version OpenSSL 1.0.1e-fips 11 Feb 2013 $ rpm -q zlib zlib-1.2.3-29.el6.x86_64 $ rpm -q zlib-devel zlib-devel-1.2.3-29.el6.x86_64 发现自带的 openssl 版本符合 openssh-7.7p 的安装条件，自带的 zlib 也符合 openssh-7.7p 的依赖。那么就直接安装吧。\n打包OpenSSH text 1 2 3 4 5 6 7 8 9 10 11 mkdir -p /usr/src/redhat/{SOURCES,SPECS} cd /usr/src/redhat/SOURCES/ wget http://ftp.riken.jp/Linux/momonga/6/Everything/SOURCES/x11-ssh-askpass-1.2.4.1.tar.gz tar xf openssh-7.7p1.tar.gz cp openssh-7.7p1/contrib/redhat/openssh.spec /usr/src/redhat/SPECS/ chown sshd:sshd /usr/src/redhat/SPECS/ -R sed -i \u0026#39;s@%define no_gnome_askpass 0@%define no_gnome_askpass 1@g\u0026#39; /usr/src/redhat/SPECS/openssh.spec sed -i \u0026#39;s@%define no_x11_askpass 0@%define no_x11_askpass 1@g\u0026#39; /usr/src/redhat/SPECS/openssh.spec cp /usr/src/redhat/SOURCES/openssh-7.7p1.tar.gz ~/rpmbuild/SOURCES/ cd /usr/src/redhat/SPECS/ rpmbuild -ba openssh.spec 可以看到rpm包和yum安装的是一样的。\ntext 1 2 3 4 5 6 7 8 9 10 11 ├── RPMS │ └── x86_64 │ ├── openssh-7.7p1-1.el6.x86_64.rpm │ ├── openssh-clients-7.7p1-1.el6.x86_64.rpm │ ├── openssh-debuginfo-7.7p1-1.el6.x86_64.rpm │ └── openssh-server-7.7p1-1.el6.x86_64.rpm $ rpm -qa|grep openssh openssh-clients-5.3p1-117.el6.x86_64 openssh-5.3p1-117.el6.x86_64 openssh-server-5.3p1-117.el6.x86_64 直接替换安装rpm包\ntext 1 2 3 4 5 6 7 $ rpm -Uvh * Preparing... ########################################### [100%] 1:openssh ########################################### [ 25%] 2:openssh-clients ########################################### [ 50%] 3:openssh-server warning: /etc/ssh/sshd_config created as /etc/ssh/sshd_config.rpmnew ########################################### [ 75%] 4:openssh-debuginfo ########################################### [100%] 安装后查看各项依赖 openssl 的匀使用正常。这么安装比编译安装要好很多。\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $ sshd -V unknown option -- V OpenSSH_7.7p1, OpenSSL 1.0.1e-fips 11 Feb 2013 usage: sshd [-46DdeiqTt] [-C connection_spec] [-c host_cert_file] [-E log_file] [-f config_file] [-g login_grace_time] [-h host_key_file] [-o option] [-p port] [-u len] $ ssh -V OpenSSH_7.7p1, OpenSSL 1.0.1e-fips 11 Feb 2013 $ curl baidu.com -I HTTP/1.1 200 OK Date: Wed, 25 Apr 2018 16:37:49 GMT Server: Apache Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT ETag: \u0026#34;51-47cf7e6ee8400\u0026#34; Accept-Ranges: bytes Content-Length: 81 Cache-Control: max-age=86400 Expires: Thu, 26 Apr 2018 16:37:49 GMT Connection: Keep-Alive Content-Type: text/html $ wget -q baidu.com $ yum list \u0026gt;\u0026gt;/dev/null 测试 yum 安装，依赖 openssh 的是否会将 7.7p 替换为 5.3p\ntext 1 2 3 4 5 6 7 8 9 10 11 12 $ yum install openssh* Loaded plugins: fastestmirror, security Setting up Install Process Examining openssh-7.7p1-1.el6.x86_64.rpm: openssh-7.7p1-1.el6.x86_64 openssh-7.7p1-1.el6.x86_64.rpm: does not update installed package. Examining openssh-clients-7.7p1-1.el6.x86_64.rpm: openssh-clients-7.7p1-1.el6.x86_64 openssh-clients-7.7p1-1.el6.x86_64.rpm: does not update installed package. Examining openssh-debuginfo-7.7p1-1.el6.x86_64.rpm: openssh-debuginfo-7.7p1-1.el6.x86_64 openssh-debuginfo-7.7p1-1.el6.x86_64.rpm: does not update installed package. Examining openssh-server-7.7p1-1.el6.x86_64.rpm: openssh-server-7.7p1-1.el6.x86_64 openssh-server-7.7p1-1.el6.x86_64.rpm: does not update installed package. Error: Nothing to do ","permalink":"https://www.161616.top/upgrade-openssh-in-centos6/","summary":"近期因 centos 6.x 默认 openssh 扫描存在大量漏洞，基于安全考虑，需要将 openssh_5.3p1 升级为最新版，网上查了很多教程，发现 openssh 存在大量依赖，不解决依赖问题很难保证其他服务政策。而 openssl 又被大量程序依赖。实在是头疼。最后发现一个不破坏各种依赖又可以完美升级的方案\n注：curl wget yum 等依赖 openssl gitlab依赖 openssh 因卸载 openssh 与 openssl 编译安装导致各种依赖程序被破坏，虽然最后升级成功，但是wget curl 和代码库被破坏。\n下载 openssh-7.7p 源码包 下载地址\n下载之后解压看 README 和 INSTALL\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 1. Prerequisites ---------------- A C compiler. Any C89 or better compiler should work.","title":"CentOS 6.8升级OpenSSH7.7p"},{"content":"运维工具 OS Provisioning: PXE, Cobbler(repository,distritution, profile)\nPXE: dhcp, tftp, (http, ftp)\ndnsusq: dhcp, dns\nOS Config:\npuppet, saltstack, func\nTask Excute:\nfabric, func, saltstack\nDeployment:\nfabric\n管理主机，要想管理被管理节点，二者必须有安全管理通道。puppet、saltstack在管理被管节点时，每一个被管节点必须运行puppet agent，管理端进程与每一个被管节点的agent进程进行通信，通信时使用的HTTP协议。此种方式必须在被管节点安装应用程序的agent，远程接收到指令，并在本地负责执行相应的任务。\n根据远程管理时，是不是在每一个被管主机上安装agent端，分为两种puppet、func、saltstack；与无需agent端，ansible、fabric。依赖被管节点的ssh服务。而管理端需要知道对方主机上的账号密码。\nansible的组成 ansible核心 host invenory ：为了管控每个被管主机，每个主机在本地需要注册。用来定义由ansible远程配置管理的主机，每个主机的IP地址、掩码、SSH监听的地址、端口号、账号密码等。\ncore modules：ansible执行任何特定管理任务，都是不由ansible自身玩成的，而是通过模块完成的。\ncustom Modules：使用任何编程语言来编写模块。\nplaybooks：将主机要完成的多个任务，事先定义在文件中可以多次调用。\n1 ansible的特性 基于Python语言实现，由Paramiko, PyYAML和jiniia2三个关键模块 部署简单，agentless 默认使用SSH协议 主从模式： master:ansible, ssh client slave: ssh server 支持自定文模块：支持各种编程语言，支持Playbook，基于“模块”完成各种“任务”。 安装依赖于epel源\n配置文件：\ntext 1 2 /etc/ansible/ansible.cfg Invertory:/etc/ansible/hosts 2 ansible命令应用基础 语法: ansible \u0026lt;host-pattern\u0026gt; [-f forks] [-m module_name] [-a args]\n参数 说明 -f forks 启助的并发线程数。 -m module_name 要使用的模块。 -a args 模块特有的参数 查看模块 text 1 2 3 4 5 6 7 8 ansible-doc -l command Executes a command on a remote node composer Dependency Manager for PHP consul Add, modify \u0026amp; delete services within a consul cluster. consul_acl Manipulate Consul ACL keys and rules consul_kv Manipulate entries in the key/value store of a consul cluster. consul_session manipulate consul sessions copy Copies files to remote locations text 1 2 3 4 5 $ ansible-doc -s command - name: Executes a command on a remote node command: chdir:# Change into this directory before running the command. creates: A filename or (since 2.0) glob pattern, when it already exists, this step will *not* be run. 2.1 常见模块 command：命令模块，默认模块，用于在远程执行命令。\ntext 1 2 3 4 5 6 7 $ ansible 10.0.0.12 -m command -a \u0026#39;date\u0026#39; 10.0.0.12 | SUCCESS | rc=0 \u0026gt;\u0026gt; Wed May 2 14:40:57 CST 2018 $ ansible 10.0.0.12 -a \u0026#39;date\u0026#39; 10.0.0.12 | SUCCESS | rc=0 \u0026gt;\u0026gt; Wed May 2 14:41:03 CST 2018 cron\nstate：表示任务是添加还是移除。present表示必须提供的，absent表示必须不能提供的 present：安装、absent移除。可以不写，默认都是。state默认 present\ntext 1 ansible websrvs cron -a \u0026#39;minute=\u0026#34;*/10\u0026#34; job=\u0026#34;/bin/echo hellow\u0026#39; name-\u0026#34;test cron job\u0026#34;\u0026#39; user：指明创建用户的名字\ntext 1 ansible all -m user -a \u0026#39;name=user1\u0026#39; text 1 2 ansible all -m group -a \u0026#39;name=mysql gid=2000 system=yes\u0026#39; ansible all -m user -a \u0026#39;user=mysql group=mysql system=yes uid=3306\u0026#39; copy\nsrc本地原路径，可以使用相对和绝对路径 dest远程目标路径，必须使用绝对路径\ntext 1 ansible all -m copy -a \u0026#39;src=/etc/hosts dest=/tmp/h.ansible\u0026#39; owner=\u0026#39;root\u0026#39; mode=\u0026#39;644\u0026#39; content：取代src，表示直接用此处指定的信息生成为目标文件内容\ntext 1 2 3 4 5 ansible all -m copy -a \u0026#39;content=\u0026#34;test ansible\u0026#34; dest=/tmp/h.ansible\u0026#39; $ ll /tmp/ total 56 -rw-r--r--. 1 root root 12 May 2 14:50 h.ansible file\n设置文件属性，path指定文件路径，可以用name或dest替代。\n创建文件的符号链接\ntext 1 2 3 4 5 ansible all -m file -a \u0026#39;path=/tmp/h src=/tmp/h.ansible state=link\u0026#39; $ ll /tmp/ lrwxrwxrwx 1 root root 14 May 2 23:08 h -\u0026gt; /tmp/h.ansible -rw-r--r-- 1 root root 12 May 2 23:05 h.ansible text 1 2 3 4 5 6 ansible all -m file -a \u0026#39;name=/tmp/h.123 src=/tmp/h.ansible state=link\u0026#39; $ ll /tmp/ total 56 lrwxrwxrwx. 1 root root 14 May 2 14:53 h -\u0026gt; /tmp/h.ansible lrwxrwxrwx. 1 root root 14 May 2 15:00 h.123 -\u0026gt; /tmp/h.ansible 删除文件夹\ntext 1 ansible all -m file -a \u0026#39;path=/tmp/ state=absent\u0026#39; 创建文件夹\ntext 1 ansible all -m file -a \u0026#39;path=/tmp/test-create state=directory\u0026#39; state=file如果文件不存在，将失败，请使用copy或templates创建\ntext 1 ansible-doc -s file 修改文件权限\ntext 1 ansible test -m file -a \u0026#39;owner=mysql group=mysql mode=000 path=/tmp/tomcat.xml\u0026#39; service 指定运行状态\nenabled：是否开机自动启动，取值为true或false name：服务名称 state：状态，取位有started stopped restarted shell：在远程主机上运行命令。\nscript：将本地脚本复制到远程主机并运行\nyum 安装程序包\nname：指明要安装的程序包，可以带上版本号 state：present，latest安装 absent卸载 text 1 ansible test -m yum -a \u0026#39;name=zsh state=present\u0026#39; ","permalink":"https://www.161616.top/ansible/","summary":"运维工具 OS Provisioning: PXE, Cobbler(repository,distritution, profile)\nPXE: dhcp, tftp, (http, ftp)\ndnsusq: dhcp, dns\nOS Config:\npuppet, saltstack, func\nTask Excute:\nfabric, func, saltstack\nDeployment:\nfabric\n管理主机，要想管理被管理节点，二者必须有安全管理通道。puppet、saltstack在管理被管节点时，每一个被管节点必须运行puppet agent，管理端进程与每一个被管节点的agent进程进行通信，通信时使用的HTTP协议。此种方式必须在被管节点安装应用程序的agent，远程接收到指令，并在本地负责执行相应的任务。\n根据远程管理时，是不是在每一个被管主机上安装agent端，分为两种puppet、func、saltstack；与无需agent端，ansible、fabric。依赖被管节点的ssh服务。而管理端需要知道对方主机上的账号密码。\nansible的组成 ansible核心 host invenory ：为了管控每个被管主机，每个主机在本地需要注册。用来定义由ansible远程配置管理的主机，每个主机的IP地址、掩码、SSH监听的地址、端口号、账号密码等。\ncore modules：ansible执行任何特定管理任务，都是不由ansible自身玩成的，而是通过模块完成的。\ncustom Modules：使用任何编程语言来编写模块。\nplaybooks：将主机要完成的多个任务，事先定义在文件中可以多次调用。\n1 ansible的特性 基于Python语言实现，由Paramiko, PyYAML和jiniia2三个关键模块 部署简单，agentless 默认使用SSH协议 主从模式： master:ansible, ssh client slave: ssh server 支持自定文模块：支持各种编程语言，支持Playbook，基于“模块”完成各种“任务”。 安装依赖于epel源\n配置文件：\ntext 1 2 /etc/ansible/ansible.cfg Invertory:/etc/ansible/hosts 2 ansible命令应用基础 语法: ansible \u0026lt;host-pattern\u0026gt; [-f forks] [-m module_name] [-a args]","title":"ansible介绍"},{"content":"docker registry介绍 Registry用于保存docker镜像，包括镜像的层次结构和元数据，用户可自建Registry，也可使用官方的Docker Hub\n分类\nSponsor Registry：第三方的registry，供客户和Docker社区使用 Mirror Registry：第三方的registry，只让客户使用 Vendor Registry：由发布Docker镜像的供应商提供的registry Private Registry：通过设有防火墙和额外的安全层的私有实体提供的registry 一个docker Registry上拥有两种功能：\n提供镜像存储的仓库。 提供用户获取镜像时的认证功能。 同时提供当前服务器上所有可用镜像的搜索索引。 一个docker镜像仓库有仓库的名称，等同于yum的repostory。通常简称为repo。为了使的镜像和应用程序版本之间有意义上的关联关系。在docker一个仓库通常只存放一个应用程序的镜像。因此，这个仓库名就是应用程序名。通过给每个镜像额外添加一个组件叫tag，来标识每一个镜像。通常镜像名称:标签repo_name:tag才能唯一标识一个镜像。\n为了可以快速创建registry，docker专门提供了一个程序包 docker-distribution 。https://hub.docker.com/r/distribution/registry/ regustry自身运行在容器中，而容器的文件系统会随着容器生命周期终止而删除，因此需要给registry定义存储卷，使用网络存储。\n在yum的extras仓库有一个docker-registry的程序包。docker-distribution的主配置文件在 /etc/docker-distribution/registry/config.yml，所有上传的镜像存放在/var/lib/registry 。\nsh 1 2 3 4 5 6 7 8 9 10 11 12 $ yum info docker-registry Available Packages Name : docker-registry Arch : x86_64 Version : 0.9.1 Release : 7.el7 Size : 123 k Repo : extras/7/x86_64 Summary : Registry server for Docker URL : https://github.com/docker/docker-registry License : ASL 2.0 Description : Registry server for Docker (hosting/delivering of repositories and images). 配置docker registry访问 非 docker hub 必须给定registry的地址端口，如果不是顶层仓库还要给定用户名。 docker push 默认基于https工作的，而服务器端使用的http，两者不兼容，需要标记为非加密、非安全的docker registry。\nsh 1 2 3 $ docker push node02.com:5000/php The push refers to repository [node02.com:5000/php] Get https://node02.com:5000/v2/: http: server gave HTTP response to HTTPS client 编辑 /etc/docker/daemon.json 添加 insecure-registries ，并且名称一定要与仓库引用时使用的名称完全保持一致，多个以逗号分隔\nsh 1 2 3 { \u0026#34;insecure-registries\u0026#34;: [\u0026#34;node02.com:5000\u0026#34;] } push的镜像存放在 /var/lib/registry/ 下，V2指的是registry的协议版本\nsh 1 2 3 4 $ ll /var/lib/registry/docker/registry/v2/ total 0 drwxr-xr-x 3 root root 20 Aug 28 23:31 blobs drwxr-xr-x 3 root root 17 Aug 28 23:31 repositories push时镜像会分层次，每一层都单独推送，单独存放。产生的镜像层次存放在 php/_layers/sha256/ ，真正存放的路径为 /var/lib/registry/docker/registry/v2/blobs 下\nsh 1 2 3 4 5 6 7 8 9 10 11 12 $ ll /var/lib/registry/docker/registry/v2/repositories/php/_layers/sha256/ total 0 13bb1aa790b2a283bdeb26a9dd4afa0891e37252dd6f836e2bc8e1555903f7fd 256b176beaff7815db2a93ee2071621ae88f451bb1e198ca73010ed5bba79b65 3584183957db768fc11554dfd6b06ec41be02d7872cecb65aa5ba9f238c897e6 499f1709b835427d28bc4ddb1e7038a438f1a1272abfe5489d6c74cb69b51bec 6d33f059b806836d7e63f6f26f154b99a42abcc1d384da7569de593b8135f7fb 8158b516b87541f3641937087e8048977f48f9ced0bfaeb0bc007c1ea0d49b93 8fa12d754b796a48f42433fae8a8eee24b56679bba4e5648fa50b184622dd941 ca82288118de1328f65d428e6d2acc6a87ecf552ed5cc3698fde90cf76f3ebdb d393fc3ffa9b40bfbddd978604e0d5249b0bb1a6e4953142d8b2c80fcc85bcb4 d9f1ee7bf8cab99a7362c98708edd26c2f622f13b8c74c3cafd6197442c20609 通过api获取中镜像与标签\ntext 1 2 $ curl http://192.168.50.27:5000/v2/game/tags/list {\u0026#34;name\u0026#34;:\u0026#34;game\u0026#34;,\u0026#34;tags\u0026#34;:[\u0026#34;0123-151422\u0026#34;,\u0026#34;0124-162847\u0026#34;,\u0026#34;0124-164112\u0026#34;,\u0026#34;0125\u0026#34;]} 查看仓库中内容\ntext 1 2 $ curl -XGET http://192.168.50.27:5000/v2/_catalog {\u0026#34;repositories\u0026#34;:[\u0026#34;apiv1\u0026#34;,\u0026#34;game\u0026#34;,\u0026#34;php\u0026#34;,\u0026#34;tyapi\u0026#34;]} ","permalink":"https://www.161616.top/docker-registry/","summary":"docker registry介绍 Registry用于保存docker镜像，包括镜像的层次结构和元数据，用户可自建Registry，也可使用官方的Docker Hub\n分类\nSponsor Registry：第三方的registry，供客户和Docker社区使用 Mirror Registry：第三方的registry，只让客户使用 Vendor Registry：由发布Docker镜像的供应商提供的registry Private Registry：通过设有防火墙和额外的安全层的私有实体提供的registry 一个docker Registry上拥有两种功能：\n提供镜像存储的仓库。 提供用户获取镜像时的认证功能。 同时提供当前服务器上所有可用镜像的搜索索引。 一个docker镜像仓库有仓库的名称，等同于yum的repostory。通常简称为repo。为了使的镜像和应用程序版本之间有意义上的关联关系。在docker一个仓库通常只存放一个应用程序的镜像。因此，这个仓库名就是应用程序名。通过给每个镜像额外添加一个组件叫tag，来标识每一个镜像。通常镜像名称:标签repo_name:tag才能唯一标识一个镜像。\n为了可以快速创建registry，docker专门提供了一个程序包 docker-distribution 。https://hub.docker.com/r/distribution/registry/ regustry自身运行在容器中，而容器的文件系统会随着容器生命周期终止而删除，因此需要给registry定义存储卷，使用网络存储。\n在yum的extras仓库有一个docker-registry的程序包。docker-distribution的主配置文件在 /etc/docker-distribution/registry/config.yml，所有上传的镜像存放在/var/lib/registry 。\nsh 1 2 3 4 5 6 7 8 9 10 11 12 $ yum info docker-registry Available Packages Name : docker-registry Arch : x86_64 Version : 0.9.1 Release : 7.el7 Size : 123 k Repo : extras/7/x86_64 Summary : Registry server for Docker URL : https://github.","title":"docker Registry使用"},{"content":"对于docker来讲，作为容器运行的底层引擎，在组织及运行容器时每个容器内只运行一个程序及子程序。对于这个容器来讲，启动时依赖于 底层镜像联合挂载启动而成。 底层能够存储此类分层构建并联合挂载镜像的文件系统。最上层构建读写层。对于此读写层来说。所有对容器的操作都保存在最上层之上。而下层内容的操作需要使用写时复制。\nDocker镜像由多个只读层叠加而成，启动容器时，Docker会加载只读镜像层并在镜像栈顶部添加一个读写层，如果运行中的容器修改了现有的一个已经存在的文件，那该文件将会从读写层下面的只读层复制到读写层，该文件的只读版本仍然存在，只是已经被读写层中该文件的副本所隐藏，此即 写时复制（COW）机制。此机制对IO较高的应用在实现持久化存储时，势必对在底层应用数据存储时性能要求较高。要想绕过使用限制，可以使用存储卷机制。\nWhy Data Volume？\n宿主机的主机文件系统直接与容器内部的文件系统之上的某一访问路径建立绑定关系。\n在宿主机上目录和容器内文件系统建立绑定关系的目录相对于容器来讲被称为volume。容器内所有有效数据都是保存在存储卷，从而脱离容器自身文件系统。当容器关闭并删除时，只要不删除与宿主机与之绑定的存储目录，就能实现数据脱离容器的生命周期而持久化。docker的存储卷默认情况下使用其所在宿主机之上的本地文件系统目录的。\n关闭并重启容器，其数据不受影响；但删除Docker容器，则其更改将会全部丢失 存在的问题 存储于联合文件系统中，不易于宿主机访问； 容器间数据共享不便 删除容器其数据会丢失 解决方案：“卷（volume）” “卷”是容器上的一个或多个“目录”，此类目录可绕过联合文件系统，与宿主机上的某目录“绑定（关联）” 在docker中如果需要动刀存储卷时，不必要手动创建，Volume于容器初始化之时即会创建，由base image提供的卷中的数据会于此期间完成复制\nVolume的初衷是独立于容器的生命周期实现数据持久化，因此删除容器之时既不会删除卷，也不会对哪怕未被引用的卷做垃圾回收操作；\nData volumes ·卷为docker提供了独立于容器的数据管理机制 ·可以把“镜像”想像成静态文件，例如“程序”，把卷类比为动态内容，例如“数据 \u0026ldquo;；于是，镜像可以重用，而卷可以共享； ·卷实现了“程序（镜像）”和“数据（卷）”分离，以及“程序（镜像）”和“制作镜像的主机 \u0026ldquo;分离，用户制作镜像时无须再考虑镜像运行的容器所在的主机的环境；\nDocker有两种类型的卷，每种类型都在容器中存在一个挂载点，但其在宿主机上的位置有所不同；\nBind mount volume 绑定挂载卷 在宿主机指定一个特定路径，在容器内指定一个特定路径，二者已知路径建立关联关系。\na volume that points to a user-specified location on the host file system\nDocker-managed volume docker管理卷 指定容器内的挂载点，与之关联的是宿主机的目录由docker daemon引擎自行创建空目录，或者使用已存在目录与存储卷路径建立关联关系。\nthe Docker daemon creates managed volumes in a portion of the host\u0026rsquo;s file system that\u0026rsquo;s owned by Docker\n在容器中使用Volumes 为docker run命令使用一v选项即可使用Volume\nDocker-managed volume\nsh 1 2 docker run-it-name box1 -v /data busybox docker inspect-f {{.Mounts} box1 查看bbox1容器的卷、卷标识符及挂载的主机目录 Bind-mount Volume\nsh 1 2 docker run-it-v HOSTDIR:VOLUMEDIR--name box2 busybox docker inspect-f {{.Mounts}} box2 Sharing volumes There are two ways to share volumes between containers 多个容器的卷使用同一个主机目录，例如\nbash 1 2 $ docker run-it--namec1-v/docker/volumes/v1：/data busybox $ docker run-it--name c2-v/docker/volumes/v1：/data busybox 复制使用其它容器的卷，为docker run命令使用\u0026ndash;volumes-from选项\nsh 1 2 docker run-it--name box1 -v /docker/volumes/v1:/data busybox docker run-it--name box2 --volumes-from box1 busybox EXPOSE 用于为容器打开指定要监听的端口以实现与外部通信，并不会直接暴露，只是声明需要暴露的端口，在docker run -P时自动暴露端口\nSyntax\nsh 1 EXPOSE \u0026lt;port\u0026gt; [/\u0026lt;protocol\u0026gt;] [\u0026lt;port\u0026gt;[/\u0026lt;protocol\u0026gt;]..] 用于指定传输层协议，可为wp或udp二者之一，默认为TCP协议 EXPOSE指令可一次指定多个端口，例如\nsh 1 EXPOSE 11211/udp 11211/tcp ENV 用于为镜像定义所需的环境变量，并可被Dockerfile文件中位于其后的其它指令（如ENV、ADD、COPY等）所调用\n调用格式为Svariable_name或${variable_name}\nSyntax ENV \u0026lt;key\u0026gt; \u0026lt;value\u0026gt;或 ENV \u0026lt;key\u0026gt;=\u0026lt;value\u0026gt; .… 第一种格式中，之后的所有内容均会被视作其 \u0026lt;value\u0026gt; 的组成部分，因此，一次只能设置一个变量；\n第二种格式可用一次设置多个变量，每个变量为一个 \u0026lt;key\u0026gt;=\u0026lt;value\u0026gt; 的键值对，如果 \u0026lt;value\u0026gt; 中包含空格，可以以反斜线（）进行转义，也可通过对 \u0026lt;value\u0026gt; 加引号进行标识；另外，反斜线也可用于续行；\n定义多个变量时，建议使用第二种方式，以便在同一层中完成所有功能\n","permalink":"https://www.161616.top/docker-volume/","summary":"对于docker来讲，作为容器运行的底层引擎，在组织及运行容器时每个容器内只运行一个程序及子程序。对于这个容器来讲，启动时依赖于 底层镜像联合挂载启动而成。 底层能够存储此类分层构建并联合挂载镜像的文件系统。最上层构建读写层。对于此读写层来说。所有对容器的操作都保存在最上层之上。而下层内容的操作需要使用写时复制。\nDocker镜像由多个只读层叠加而成，启动容器时，Docker会加载只读镜像层并在镜像栈顶部添加一个读写层，如果运行中的容器修改了现有的一个已经存在的文件，那该文件将会从读写层下面的只读层复制到读写层，该文件的只读版本仍然存在，只是已经被读写层中该文件的副本所隐藏，此即 写时复制（COW）机制。此机制对IO较高的应用在实现持久化存储时，势必对在底层应用数据存储时性能要求较高。要想绕过使用限制，可以使用存储卷机制。\nWhy Data Volume？\n宿主机的主机文件系统直接与容器内部的文件系统之上的某一访问路径建立绑定关系。\n在宿主机上目录和容器内文件系统建立绑定关系的目录相对于容器来讲被称为volume。容器内所有有效数据都是保存在存储卷，从而脱离容器自身文件系统。当容器关闭并删除时，只要不删除与宿主机与之绑定的存储目录，就能实现数据脱离容器的生命周期而持久化。docker的存储卷默认情况下使用其所在宿主机之上的本地文件系统目录的。\n关闭并重启容器，其数据不受影响；但删除Docker容器，则其更改将会全部丢失 存在的问题 存储于联合文件系统中，不易于宿主机访问； 容器间数据共享不便 删除容器其数据会丢失 解决方案：“卷（volume）” “卷”是容器上的一个或多个“目录”，此类目录可绕过联合文件系统，与宿主机上的某目录“绑定（关联）” 在docker中如果需要动刀存储卷时，不必要手动创建，Volume于容器初始化之时即会创建，由base image提供的卷中的数据会于此期间完成复制\nVolume的初衷是独立于容器的生命周期实现数据持久化，因此删除容器之时既不会删除卷，也不会对哪怕未被引用的卷做垃圾回收操作；\nData volumes ·卷为docker提供了独立于容器的数据管理机制 ·可以把“镜像”想像成静态文件，例如“程序”，把卷类比为动态内容，例如“数据 \u0026ldquo;；于是，镜像可以重用，而卷可以共享； ·卷实现了“程序（镜像）”和“数据（卷）”分离，以及“程序（镜像）”和“制作镜像的主机 \u0026ldquo;分离，用户制作镜像时无须再考虑镜像运行的容器所在的主机的环境；\nDocker有两种类型的卷，每种类型都在容器中存在一个挂载点，但其在宿主机上的位置有所不同；\nBind mount volume 绑定挂载卷 在宿主机指定一个特定路径，在容器内指定一个特定路径，二者已知路径建立关联关系。\na volume that points to a user-specified location on the host file system\nDocker-managed volume docker管理卷 指定容器内的挂载点，与之关联的是宿主机的目录由docker daemon引擎自行创建空目录，或者使用已存在目录与存储卷路径建立关联关系。\nthe Docker daemon creates managed volumes in a portion of the host\u0026rsquo;s file system that\u0026rsquo;s owned by Docker\n在容器中使用Volumes 为docker run命令使用一v选项即可使用Volume","title":"docker Volume"},{"content":"Compose是一个定义和管理多容器的工具，使用Python语言编写。使用Compose配置文件描述多个容器应用的架构，比如使用 什么镜像、数据卷、网络、映射端口等；然后一条命令管理所有服务，比如启动、停止、重启等。\n1、Linux安装Compose 参考网址：Releases · docker/compose · GitHub\n下载二进制文件 bash 1 2 3 curl -L \\ https://github.com/docker/compose/releases/download/1.22.0/docker-compose-\\ `uname -s`-`uname -m` -o /usr/local/bin/docker-compose 对二进制文件添加可执行权限 bash 1 chmod +x /usr/local/bin/docker-compose 测试安装 docker-compose \u0026ndash;version\n也可以使用pip工具安装：pip install docker-compose\n2、使用compose 参考文档：Docker Compose | Docker Documentation\ncompose语法详解：Compose file version 3 reference | Docker Documentation Docker compose file 中文参考文档 - CSDN博客\n2.1 Compose常用命令选项 参数 介绍 build 构建或修改Dockerfile后重建服务 config 验证和查看compose文件语法\n-q,只验证配置，不输出。 当配置正确时，不输出任何内容，当文件配置错误，输出错误信息。\n--services,打印服务名，一行一个 create down 停止和删除容器、网络、卷、镜像，这些内容是通过docker-compose up命令创建的. 默认值删除 容器 网络。 logs 打印compose service日志输出。 ps 打印compose进程，-q只打印pid 更多参数参考：Docker-compose命令详解 - CSDN博客\n2.2 compose创建tomcat环境 Dockerfile\nbash 1 2 3 4 5 6 7 8 9 FROM centos MAINTAINER lc ADD jdk-8u144-linux-x64.tar.gz /usr/local ENV JAVA_HOME=/usr/local/jdk1.8.0_144 ADD apache-tomcat-8.5.32.tar.gz /usr/local/ RUN mv /usr/local/apache-tomcat-8.5.32 /usr/local/tomcat WORKDIR /usr/local/tomcat ENTRYPOINT [\u0026#34;bin/catalina.sh\u0026#34;,\u0026#34;run\u0026#34;] EXPOSE 8080 compose\nbash 1 2 3 4 5 6 7 8 9 10 version: \u0026#34;3\u0026#34; services: web: build: context: . dockerfile: \u0026#34;javafile\u0026#34; ports: - \u0026#34;80:8080\u0026#34; image: \u0026#34;tomcat\u0026#34; container_name: \u0026#34;tomcat\u0026#34; 验证是否\nbash 1 2 3 4 5 $ docker-compose ps Name Command State Ports ------------------------------------------------------------------ root_web_1 bin/catalina.sh run Up 0.0.0.0:32768-\u0026gt;8080/tcp $ 2.3 docker compose语法描述 关键词 解释 version compose版本 ","permalink":"https://www.161616.top/docker-compose/","summary":"Compose是一个定义和管理多容器的工具，使用Python语言编写。使用Compose配置文件描述多个容器应用的架构，比如使用 什么镜像、数据卷、网络、映射端口等；然后一条命令管理所有服务，比如启动、停止、重启等。\n1、Linux安装Compose 参考网址：Releases · docker/compose · GitHub\n下载二进制文件 bash 1 2 3 curl -L \\ https://github.com/docker/compose/releases/download/1.22.0/docker-compose-\\ `uname -s`-`uname -m` -o /usr/local/bin/docker-compose 对二进制文件添加可执行权限 bash 1 chmod +x /usr/local/bin/docker-compose 测试安装 docker-compose \u0026ndash;version\n也可以使用pip工具安装：pip install docker-compose\n2、使用compose 参考文档：Docker Compose | Docker Documentation\ncompose语法详解：Compose file version 3 reference | Docker Documentation Docker compose file 中文参考文档 - CSDN博客\n2.1 Compose常用命令选项 参数 介绍 build 构建或修改Dockerfile后重建服务 config 验证和查看compose文件语法\n-q,只验证配置，不输出。 当配置正确时，不输出任何内容，当文件配置错误，输出错误信息。\n--services,打印服务名，一行一个 create down 停止和删除容器、网络、卷、镜像，这些内容是通过docker-compose up命令创建的. 默认值删除 容器 网络。 logs 打印compose service日志输出。 ps 打印compose进程，-q只打印pid 更多参数参考：Docker-compose命令详解 - CSDN博客","title":"docker-compose使用"},{"content":"使用docker-compose构建LNMP环境 编写Dockerfile 这里采用的是先将nginx php打包为rpm包，然后做成镜像。与直接在容器里编译安装同理的。\nnginx Dockerfile\nbash 1 2 3 4 5 6 7 8 FROM centos MAINTAINER lc RUN yum install -y gcc gcc-c++ openssl-devel make pcre-devel ADD nginx-1.13.9-1.el7.centos.x86_64.rpm /tmp/ RUN cd /tmp/ \u0026amp;\u0026amp; rpm -ivh nginx-1.13.9-1.el7.centos.x86_64.rpm ADD nginx.conf /etc/nginx/ EXPOSE 80 CMD [\u0026#34;/usr/sbin/nginx\u0026#34;, \u0026#34;-g\u0026#34;, \u0026#34;daemon off;\u0026#34;] php Dockerfile\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 FROM centos MAINTAINER LC RUN curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo RUN yum install zlib-devel \\ libxml2-devel \\ libjpeg-devel \\ libjpeg-turbo-devel \\ freetype-devel \\ libpng-devel gd-devel \\ curl-devel \\ libxslt-devel \\ bzip2-devel \\ gmp-devel \\ readline-devel \\ mcrypt \\ mhash \\ openssl-devel \\ libmcrypt-devel -y COPY php-7.1.17-1.el7.centos.x86_64.rpm /tmp/ COPY libiconv-1.15-1.el7.centos.x86_64.rpm /tmp/ RUN rpm -ivh /tmp/libiconv-1.15-1.el7.centos.x86_64.rpm RUN rpm -ivh /tmp/php-7.1.17-1.el7.centos.x86_64.rpm ADD php-fpm.conf /etc/php/ CMD /usr/sbin/php-fpm \u0026amp;\u0026amp; tail -f /dev/null EXPOSE 9000 准备构建容器的配置文件 在nginx配置文件中增加解析php的语句\nnginx 1 2 3 4 5 location ~ .*\\.(php|php5)$ { fastcgi_pass php.com:9000;$\u0026lt;--这里使用link将php的ip解析过来 fastcgi_index index.php; include fastcgi.conf; } 修改php-fpm监听端口为外网通讯的ip\nbash 1 2 3 $ sed -i \u0026#34;s#listen = 127.0.0.1:900$listen = 0.0.0.0:900$g\u0026#34; php/php-fpm.conf $ cat php/php-fpm.conf |grep 9000 listen = 0.0.0.0:9000 注：此步骤可以在打包RPM时，使用%post在安装后进行修改，免去构建镜像的步骤\n准备RPM包 bash 1 2 3 4 5 6 7 8 9 10 11 $ ls -1 php libiconv-1.15-1.el7.centos.x86_64.rpm php-7.1.17-1.el7.centos.x86_64.rpm phpfile php-fpm.conf php.ini $ ls -1 nginx nginx-1.13.9-1.el7.centos.x86_64.rpm nginx.conf nginxfile 使用docker-compose一键构建镜像 编写docker-compose.yaml yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 version: \u0026#34;3\u0026#34; services: nginx: hostname: nginx build: context: ./nginx dockerfile: nginxfile expose: - \u0026#34;80\u0026#34; ports: - \u0026#34;80:80\u0026#34; links: - mysql - php:php.com volumes: - ./wwwroot:/usr/share/nginx/html/ php: hostname: \u0026#34;php\u0026#34; build: context: ./php dockerfile: phpfile ports: - \u0026#34;9000:9000\u0026#34; links: - mysql:mysql-db volumes: - ./wwwroot:/usr/share/nginx/html/ mysql: image: mysql:5.6 hostname: mysql ports: - \u0026#34;3306:3306\u0026#34; environment: MYSQL_ROOT_PASSWORD: \u0026#34;Zhang@123\u0026#34; MYSQL_USER: \u0026#34;test\u0026#34; MYSQL_PASSWORD: \u0026#34;test@123\u0026#34; 参考文档： https://hub.docker.com/_/mysql/\n检查docker-compose-yaml语法 bash 1 2 3 4 5 6 7 8 9 10 11 $ docker-compose config services: mysql: environment: MYSQL_PASSWORD: test@123 MYSQL_ROOT_PASSWORD: Zhang@123 MYSQL_USER: test hostname: mysql image: mysql:5.6 ports: ...... 注：在语法正确时，打印docker-compose.yaml内容，语法出错直接报问题所在位置。\n一键构建所有镜像 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $ docker-compose build mysql uses an image, skipping Building php Step 1/12 : FROM centos ---\u0026gt; 5182e96772bf ..... Step 12/12 : EXPOSE 9000 ---\u0026gt; Running in 3c5f3c46124c Removing intermediate container 3c5f3c46124c ---\u0026gt; 8791ad17224d Successfully built 8791ad17224d Successfully tagged lnmp_php:latest Building nginx Step : FROM centos ---\u0026gt; 5182e96772bf ..... Step 8/8 : CMD [\u0026#34;/usr/sbin/nginx\u0026#34;, \u0026#34;-g\u0026#34;, \u0026#34;daemon off;\u0026#34;] ---\u0026gt; Using cache ---\u0026gt; 8cca531a5c21 Successfully built 8cca531a5c21 Successfully tagged lnmp_nginx:latest 管理编排容器 bash 1 2 3 docker-compose up -d docker-compose down docker-compose ps 查看运行结果\n使用docker-compose一键构建tomcat集群 编写Dockerfile nginx Dockerfile bash 1 2 3 4 5 6 7 8 FROM centos MAINTAINER lc RUN yum install -y gcc gcc-c++ openssl-devel make pcre-devel ADD nginx-1.13.9-1.el7.centos.x86_64.rpm /tmp/ RUN cd /tmp/ \u0026amp;\u0026amp; rpm -ivh nginx-1.13.9-1.el7.centos.x86_64.rpm ADD nginx.conf /etc/nginx/ EXPOSE 80 CMD [\u0026#34;/usr/sbin/nginx\u0026#34;, \u0026#34;-g\u0026#34;, \u0026#34;daemon off;\u0026#34;] tomcat Dockerfile bash 1 2 3 4 5 6 7 8 9 FROM centos MAINTAINER lc ADD apache-tomcat-8.5.29.tar.gz /usr/share/ ADD jdk-8u161-linux-x64.tar.gz /usr/share/ RUN mv /usr/share/apache-tomcat-8.5.29 /usr/share/tomcat ENV JAVA_HOME=/usr/share/jdk1.8.0_161 WORKDIR /usr/share/tomcat ENTRYPOINT [\u0026#34;bin/catalina.sh\u0026#34;,\u0026#34;run\u0026#34;] EXPOSE 8080 准备配置文件 nginx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 upstream tomcat { server tomcat01:8080; server tomcat02:8080; } server { listen 81; location / { proxy_pass http://tomcat/; proxy_set_header Host $host; client_max_body_size 10m; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } # 在日志中加入如下配置，来证明访问是负载进行的。 log_format access \u0026#39;\u0026#34;$upstream_addr\u0026#34; $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;; access_log /data/nginx/log/access.log access; bash 1 2 mkdir ./webapps/ROOT echo jsp-test \u0026gt;webapps/ROOT/index.jsp 准备构建容器所需的软件 bash 1 2 3 apache-tomcat-8.5.29.tar.gz jdk-8u144-linux-x64.tar.gz tomcatfile 编写docker-compose文件 yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 version: \u0026#34;3\u0026#34; services: nginx: hostname: nginx build: context: ./nginx dockerfile: nginxfile ports: - 80:81 links: - tomcat01:tomcat01 - tomcat02:tomcat02 volumes: - ./webapps:/usr/share/nginx/html depends_on: - mysql - tomcat01 - tomcat02 tomcat01: hostname: tomcat01 build: context: ./tomcat dockerfile: tomcatfile links: - mysql:mysql-db volumes: - ./webapps:/usr/share/tomcat/webapps/ tomcat02: hostname: tomcat02 build: context: ./tomcat dockerfile: tomcatfile links: - mysql:mysql-db volumes: - ./webapps:/usr/share/tomcat/webapps/ mysql: hostname: mysql image: mysql:5.5 ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: wordpress MYSQL_USER: user MYSQL_PASSWORD: 123456 使用docker-compose一键构建镜像 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 $ docker-compose -f docker-compose.yml build mysql uses an image, skipping Building tomcat02 Step : FROM centos ---\u0026gt; 5182e96772bf Step 2/9 : MAINTAINER lc ---\u0026gt; Using cache ---\u0026gt; 111890e6d42e Step 3/9 : ADD apache-tomcat-8.5.29.tar.gz /usr/share/ ---\u0026gt; 46725ed86e2e Step 4/9 : ADD jdk-8u144-linux-x64.tar.gz /usr/share/ ---\u0026gt; 431ea9bb9918 Step 5/9 : RUN mv /usr/share/apache-tomcat-8.5.29 /usr/share/tomcat ---\u0026gt; Running in 22275e028633 Removing intermediate container 22275e028633 ---\u0026gt; 0f3919e97b50 Step 6/9 : ENV JAVA_HOME=/usr/share/jdk1.8.0_144 ---\u0026gt; Running in 60d616e0b9fc Removing intermediate container 60d616e0b9fc ---\u0026gt; 5255488c51ab Step 7/9 : WORKDIR /usr/share/tomcat ---\u0026gt; Running in 6b04d8f5524b Removing intermediate container 6b04d8f5524b ---\u0026gt; db88a81ec00a Step 8/9 : ENTRYPOINT [\u0026#34;bin/catalina.sh\u0026#34;,\u0026#34;run\u0026#34;] ---\u0026gt; Running in 189b6ee0ff4e Removing intermediate container 189b6ee0ff4e ---\u0026gt; 9b444829ed55 Step 9/9 : EXPOSE 8080 ---\u0026gt; Running in f2115bf03afb Removing intermediate container f2115bf03afb ---\u0026gt; 31c01ca93305 Successfully built 31c01ca93305 Successfully tagged lnmp_tomcat02:latest Building tomcat01 Step : FROM centos ---\u0026gt; 5182e96772bf Step 2/9 : MAINTAINER lc ---\u0026gt; Using cache ---\u0026gt; 111890e6d42e Step 3/9 : ADD apache-tomcat-8.5.29.tar.gz /usr/share/ ---\u0026gt; Using cache ---\u0026gt; 46725ed86e2e Step 4/9 : ADD jdk-8u144-linux-x64.tar.gz /usr/share/ ---\u0026gt; Using cache ---\u0026gt; 431ea9bb9918 Step 5/9 : RUN mv /usr/share/apache-tomcat-8.5.29 /usr/share/tomcat ---\u0026gt; Using cache ---\u0026gt; 0f3919e97b50 Step 6/9 : ENV JAVA_HOME=/usr/share/jdk1.8.0_144 ---\u0026gt; Using cache ---\u0026gt; 5255488c51ab Step 7/9 : WORKDIR /usr/share/tomcat ---\u0026gt; Using cache ---\u0026gt; db88a81ec00a Step 8/9 : ENTRYPOINT [\u0026#34;bin/catalina.sh\u0026#34;,\u0026#34;run\u0026#34;] ---\u0026gt; Using cache ---\u0026gt; 9b444829ed55 Step 9/9 : EXPOSE 8080 ---\u0026gt; Using cache ---\u0026gt; 31c01ca93305 Successfully built 31c01ca93305 Successfully tagged lnmp_tomcat01:latest Building nginx Step : FROM centos ---\u0026gt; 5182e96772bf Step 2/8 : MAINTAINER lc ---\u0026gt; Using cache ---\u0026gt; 111890e6d42e Step 3/8 : RUN yum install -y gcc gcc-c++ openssl-devel make pcre-devel ---\u0026gt; Using cache ---\u0026gt; 36090d81ef5e Step 4/8 : ADD nginx-1.13.9-1.el7.centos.x86_64.rpm /tmp/ ---\u0026gt; Using cache ---\u0026gt; cecd606c7619 Step 5/8 : RUN cd /tmp/ \u0026amp;\u0026amp; rpm -ivh nginx-1.13.9-1.el7.centos.x86_64.rpm ---\u0026gt; Using cache ---\u0026gt; 8c7b331fc175 Step 6/8 : ADD nginx.conf /etc/nginx/ ---\u0026gt; 84940ae84e06 Step 7/8 : EXPOSE 80 ---\u0026gt; Running in 94ac22711605 Removing intermediate container 94ac22711605 ---\u0026gt; ef10799c0844 Step 8/8 : CMD [\u0026#34;/usr/sbin/nginx\u0026#34;, \u0026#34;-g\u0026#34;, \u0026#34;daemon off;\u0026#34;] ---\u0026gt; Running in 039ea9de17f0 Removing intermediate container 039ea9de17f0 ---\u0026gt; d489d87223f8 Successfully built d489d87223f8 Successfully tagged lnmp_nginx:latest 测试访问结果\n查看nginx访问日志，发现是负载到每一台tomcat上的。\nbash 1 2 \u0026#34;172.24.0.5:8080\u0026#34; 10.0.0.1 - - [12/Aug/2018:17:52:39 +0000] \u0026#34;GET /index.jsp HTTP/1.1\u0026#34; 200 9 \u0026#34;-\u0026#34; \u0026#34;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.Safari/537.36\u0026#34; \u0026#34;-\u0026#34; \u0026#34;172.24.0.4:8080\u0026#34; 10.0.0.1 - - [12/Aug/2018:17:52:40 +0000] \u0026#34;GET /index.jsp HTTP/1.1\u0026#34; 200 9 \u0026#34;-\u0026#34; \u0026#34;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.Safari/537.36\u0026#34; \u0026#34;-\u0026#34; ","permalink":"https://www.161616.top/docker-compose-example/","summary":"使用docker-compose构建LNMP环境 编写Dockerfile 这里采用的是先将nginx php打包为rpm包，然后做成镜像。与直接在容器里编译安装同理的。\nnginx Dockerfile\nbash 1 2 3 4 5 6 7 8 FROM centos MAINTAINER lc RUN yum install -y gcc gcc-c++ openssl-devel make pcre-devel ADD nginx-1.13.9-1.el7.centos.x86_64.rpm /tmp/ RUN cd /tmp/ \u0026amp;\u0026amp; rpm -ivh nginx-1.13.9-1.el7.centos.x86_64.rpm ADD nginx.conf /etc/nginx/ EXPOSE 80 CMD [\u0026#34;/usr/sbin/nginx\u0026#34;, \u0026#34;-g\u0026#34;, \u0026#34;daemon off;\u0026#34;] php Dockerfile\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 FROM centos MAINTAINER LC RUN curl -o /etc/yum.","title":"docker-compose示例"},{"content":"一、使用前提 通用镜像未必与应用程序和配置是符合我们需要的。\n1.1 常见镜像制作方式 常见制作镜像方式有两种\n基于容器 基于镜像制作：编辑一个Dockerfile，而后根据此文件制作； 二、Dockerfile概述 Dockerfile只是构建Docker镜像的源代码，docker可以通过读取Dockerfile中的指令自动构建图像。Dockerfile是一个文本文档，其中包含用户可以在命令行上调用以组合图像的所有命令。用户可以使用 docker build 创建连续执行多个命令行指令的自动构建。\n2.1 Dockerfile的工作模式 基于Dockerfile制作镜像时，需在专用目录放置Dockerfile文件，并且文件首字母必须大写。基于Dockerfile中打包的文件必须奇基于工作目录往下走的路径。在打包镜像时，.dockeringore 文件本身与所有包含在 .dockeringore 文件中的路径，都不被打包进去。在Dockerfile制作环境为底层镜像启动容器时所能够提供的环境。\n2.2 环境变量替换 制作镜像中还可以使用环境变量。环境变量（使用ENV语句声明）也可以在某些指令中使用，因为Dockerfile环境变量在 Dockerfile 中以 $variable_name 或${variable_name}标记。\n语法还支持一些标准`bash修饰符\n${variable:-word} 表示如果设置了变量，那么结果将是该值。如果未设置变量，那么word将是结果。 ${variable+word} 表示如果设置了变量，则word将是结果，否则结果为空字符串。 三、Dockerfile指令说明 特别说明：Dockerfile中每一条指令都会生成一个新的镜像层。\nFROM FROM指令（最重要的一个），必须为Dockerfile文件开篇的第一个非注释行，用于为镜像文件构建过程指定基准镜像，后续的指令运行于此基准镜像所提供的运行环境。基准镜像可以是任何可用镜像文件，默认情况下，docker build会在docker主机上查找指定的镜像文件，在其不存在时，则会从 Docker Hub Registry 上拉取所需的镜像文件。如果找不到指定的镜像文件，docker build 会返回一个错误信息\nSyntax:\nsh 1 FROM repository[:tag] sh 1 FROM registry/repository[:tag] bash 1 FROM resository@[digest] #←相同名称时，可以使用resository@镜像hush码指定镜像。 参数 说明- reposotiry 某一个镜像的仓库，如redis镜像仓库。 registry docker镜像仓库，如docker hub，docker registry包含很多reposotiry，如nginx php tomcat等。不指定registry，默认从docker hub下载。 tag image的标签，为可选项，省略时默认为latest。 MAINTANIER(depreacted) MAINTANIER（depreacted）用于让Dockerfile制作者提供本人的详细信息。Dockerfile并不限制MAINTAINER指令可在出现的位置，但推荐将其放置于FROM指令之后。\nSyntax\nsh 1 MAINTAINER \u0026lt;authtor\u0026#39;s detail\u0026gt; \u0026lt;author\u0026rsquo;s detail\u0026gt; 可是任何文本信息，但约定俗成地使用作者名称及邮箱地址\nsh 1 MAINTAINER \u0026#34;lc \u0026lt;12399.com@gmail.com\u0026gt;\u0026#34; LABLE LABLE可以提供Key value信息，比MAINTANIER具有更宽泛的使用领域。LABLE让用户提供格式各样的元数据，都是键值格式。如果在LABEL值中包含空格，请使用引号和反斜杠。image可以有多个tag。您可以在一行上指定多个tag。docker17+增加此指令。\nSyntax:\nsh 1 LABEL \u0026lt;key\u0026gt;=\u0026lt;value\u0026gt; \u0026lt;key\u0026gt;=\u0026lt;value\u0026gt; \u0026lt;key\u0026gt;=\u0026lt;value\u0026gt;... sh 1 LABEL maintainer=\u0026#34;lc \u0026lt;12399.com@gmail.com\u0026gt;\u0026#34; COPY 用于从宿主机工作目录将文件复制至到镜像的文件系统中。\nSyntax:\nsh 1 2 COPY src...dest COPY \u0026#34;src\u0026#34;....\u0026#34;dest\u0026#34; 参数 说明 src 要复制的源文件或目录，支持使用通配符。 dest 目标路径，即正在创建的image的文件系统路径；建议为dest使用绝对路径，否则，COPY指定则以WORKDIR为其起始路径。 注意：在路径中有空白字符时，通常使用第二种格式。\n文件复制准则\nsrc 必须是build上下文中的路径，不能是其父目录中的文件。 如果src是目录，则其内部文件或子目录会被递归复制，但src目录自身不会被复制。 等同于 cp a/*而不是 cp -r a 如果指定了多个src，或在src中使用了通配符，则dest必须是一个目录，且必须以/结尾。 如果dest事先不存在，它将会被自动创建，这包括其父目录路径。 ADD ADD指令类似于COPY指令，ADD支持使用TAR文件和URL路径。\nadd官方解释：ADD\n如果src是以\nSyntax:\nsh 1 2 ADD src dest ADD [\u0026#34;src\u0026#34;…\u0026#34;dest\u0026#34;] 操作准则同COPY指令\n如果 \u0026lt;src\u0026gt; 为URL且 \u0026lt;dest\u0026gt; 不以 / 结尾，则 \u0026lt;src\u0026gt; 指定的文件将被下载并直接被创建为dest；如果dest以/结尾，则文件名URL指定的文件将被直接下载并保存为 dest/filename\n如果src是一个本地系统上的可识别的压缩格式（identity，gzip，bzip2或xz）的本地 tar存档，则将其解压缩为目录。，其行为类似于 tar -xf 命令；然而，从URL远程网址不会自动解压。\n如果src有多个，或其间接或直接使用了通配符，则dest必须是一个以 / 结尾的目录路径；如果dest不以 / 结尾，则其被视作一个普通文件，src的内容将被直接写入到dest。\nDockerfile中每一条指令都会生成一个新的镜像层。尽量避免很多指令\nWORKDIR 用于为Dockerfile中所有的 RUN、CMD、ENTRYPOINT、COPY 和 ADD 指定设定工作目录\nSyntax:\nsh 1 WORKDIR dirpath 在Dockerfile文件中，WORKDIR指令可出现多次，其路径也可以为相对路径，不过，其是相对此前一个WORKDR指令指定的路径。另外，WORKDIR也可满用由ENV指定定义的变量\n例如\nsh 1 2 WORKDIR /var/log WORKDIR $STATEPATH VOLUME 用于在image中创建一个挂载点目录，以挂载Docker host上的卷或其它容器上的卷，在Dockerfile中的镜像自动指定VOLUME时，一般只能指定挂载点，不能指定宿主机文件。被称之为docker管理的卷。\nSyntax:\nsh 1 VOLUME mountpoint sh 1 VOLUME [\u0026#34;mountpoint\u0026#34;] 如果挂载点目录路径下此前在文件存在，docker run命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中。\nEXPOSE 用于为容器打开指定要监听的端口以实现与外部通信，写在文件中的端口暴露并不会直接暴露，当docker run -P时不用声明端口，会自动读取镜像中指定要暴露的端口，动态分配到宿主机端口上。\nSyntax:\nsh 1 EXPOSE port[/protocol] [port[/protocol] protocol用于指定传输层协议，可为tcp或udp二者之一，默认为TCP协议。\nEXPOSE指令可一次指定多个端口，例如\nsh 1 EXPOSE 11211/udp 11211/tcp ENV 用于为镜像定义新需的环境变量，并可被Dockerfile文件中位于其后的其它指令（如ENV、ADD、COPY等）所调用格式为 $variable_name 或 ${variable_name}。在Dockerfile中所定义的所有环境变量，是可以在启动容器后直接在容器中使用的变量。在运行容器时更改ENV并不会影响docker build的值。\nSyntax:\nsh 1 ENV key value sh 1 ENV key1=value1 key2=value2.... 第一位格式中。key之后的所有内容均会被视作其value的组成部分，因此，一次只能设置一个变量。第二种格式可用一次设置多个变量，每个变量为一个 key=value 的键值对，如果value中包含空格，可以以反斜线（\\）进行转义，也可通过对value加引号递行标识；另外，反斜线也可用于续行。\n在定义多个变量时，建议使用第二种方式，以便在同一层中完成所有功能。\n运行命令。\nRUN 用于指定 dodker build 过程中运行的程序，其可以是任何命令。RUN可以运行多次的，如果多个命令彼此间有关联关系，建议在一条RUN中将多个Command写进来。\nSyntax:\nsh 1 2 RUN command RUN [\u0026#34;executable\u0026#34;，\u0026#34;param1\u0026#34;，\u0026#34;param2\u0026#34;] 第一种格式中，command通常是一个shell命令，且以 /bin/sh -c 来运行它，这意味着此进程在容器中的PID不为1，不能接枚Unix信号，因此，当使用 docker stop container 命令停止容器时，此进程接收不到SIGTERM信号；\n第二种语法格式中的参数是一个JSON格式的数组，其中executable为要运行的命令，后面的 paramN 为传递给命令的选项或参数；然而，此种格式指定的命令不会以 /bin/sh -c 来发起，因此常见的shell操作如变量替换以及通配符（，*等）替换将不会进行；不过，如果要运行的命令依赖于此shell特性的话，可以将其替换为奏似下面的格式。\nsh 1 RUN [\u0026#34;/bin/bash\u0026#34; , \u0026#34;-c\u0026#34;，\u0026#34;executable\u0026#34;, \u0026#34;param1\u0026#34;] CMD是在镜像运行为容器时没有指定默认运行命令时所运行的命令。 RUN是在基于Dockerfile构建镜像时要运行的命令。将在docker build中运行。\nCMD 类似于RUN指令，CMD指令也可用于运行任何命令或应用程序，不过，二者的运行时间点不同。RUN指令运行于镜像文件构建过程中，而CMD指令运行于基于Dockerfile构建出的新映像文件启动一个容器时\nCMD指令的首要目的在于为启动的容器指定默认要运行的程序，且其运行结束后，容器也将终止；不过，CMD指定的命令其可以被docker run的命令行选项所覆盖。在Dockerfile中可以存在多个CMD指令，但仅最后一个会生效。\nSyntax:\nsh 1 CMD command sh 1 CMD [\u0026#34;executable\u0026#34; , \u0026#34;param1\u0026#34; , \u0026#34;param2\u0026#34;] sh 1 CMD [\u0026#34;param1\u0026#34; , \u0026#34;param2\u0026#34;] 使用第一种方式默认使用bin/sh -c 前两种语法格式的意义同RUN 第三种则用于为ENTRYPOINT指令提供默认参数 ENTRYPOINT 在docker run时明明指定的运行命令为nginx，但是可以执行docker run -it --rm busybox ls /，这表示了更改了镜像中默认要运行的程序。没有运行默认程序，转而运行了指定的命令。\n对于自定义的镜像而言，默认在运行容器时运行的命令是可以被覆盖的。而不允许在运行命令是改变默认命令CMD就无法完成，而ENTRYPOINT可以做到\n类似CMD指令的功能，用于为容器指定默认运行程序，从而使得容器像是一个单独的可执行程序。但是与CMD不同的是，。由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖，而且，这些命令行参数会被当作参数传递给ENTRYPOINT指定的程序。当CMD和ENTRYPOINT同时存在时，CMD的内容会当做参数传给ENTRYPOINT。不过，docker run命令的\u0026ndash;entrypoint选项的参教可覆盖ENTRYPOINT指令指定的程序。\nSyntax:\nsh 1 2 ENTRYPOINT command ENTRYPOINT [\u0026#34;executable\u0026#34;，\u0026#34;paraml\u0026#34;，\u0026#34;param2\u0026#34;] docker run命令传入的命令参数会覆盖CMD指令的内容并且附加到ENTRYPOINT命令最后做为其参数使用，Dockerfile文件中也可以存在多个ENTRYPOINT指令，但仅有最后一个会生效\n当同时CMD与ENTRYPOINT，在运行容器时传入参数会覆盖CMD，除非使用--entrypoint选项否则ENTRYPOINT不能够被覆盖。\nsh 1 2 CMD [\u0026#34;/bin/httpd\u0026#34; , \u0026#34;-f\u0026#34; , \u0026#34;-h ${WEB00C_ROOT}\u0026#34; ] ENTRYPOINT [\u0026#34;/bin/sh\u0026#34;,\u0026#34;-c\u0026#34;] 为什么非要同时使用CMD与ENTRYPOINT？\n多数情况下ENTRYPOINT是用来指定一个shell，指定一个谁用来作为启动别的进程的服务进程。在命令行中的参数会当做他的子进程来启动。这样就可以灵活传参数被shell所解析了。 容器接受配置要靠环境变量，要想让应用镜像（如，nginx）在run时能够通过环境变量接受参数来决定他的配置文件（监听地址、端口、document_root），变量可以在启动容器时进行传递。 sh 1 2 3 4 5 6 7 8 #!/bin/sh cat \u0026gt;/etc/nginx/conf.d/www.conf \u0026lt;\u0026lt; EOF server{ server name ${HOSTNAME}; listen ${IP:-0.0.0.0}:{PORT:-80}; root ${NGX_DOC_ROOT:-/usr/share/nginx/html}; } EOF exec \u0026#34;$@\u0026#34; sh 1 2 3 4 5 6 7 FROM nginx:1.14-alpine LABEL maintainer=\u0026#34;lc \u0026lt;lc.com\u0026gt;\u0026#34; ENV NGX_DOC_ROOT=\u0026#34;/data/web/html/\u0026#34; ADD index.html ${NGX_DOC_ROOT} ADD entrypoint.sh /bin/ CMD [\u0026#34;/usr/sbin/nginx\u0026#34; , \u0026#34;-g\u0026#34; , \u0026#34;daemon off;\u0026#34; ] ENTRYPOINT [\u0026#34;/bin/entrypoint.sh\u0026#34;] USER 用于指定运行image时的或运行Dockerfile中任何RUN、CMD或ENTRYPOINT指令指定的程序时的用户名或UID，默认情况下，container的运行身份为root用户\nSyntax:\nsh 1 USER UID|UseName 需要注意的是，UID可以为任意教字，但实践中必须为 /etc/passwd 中某用户的有效UID，否则，docker run 命令将运行失败\n在基于某个镜像启动容器后，只要容器没转向后台（没停止），这个容器就不会停止。在docker引擎在判定容器健康与否并不是主进程能否提供服务，而仅仅判断进程是否运行。因此docker判断机制并不是真正意义上判断主进程的是否健康，需要其他工具来辅助确定。\nHEALTHCHECK HEALTHCHECK指令定义一个command，CMD 为固定关键词，CMD 后指定一个命令，这个命令用于检查容器主进程工作状态健康与否。即使主进程仍在运行，这也可以检测到陷入无限循环且无法处理新连接的Web服务器等情况。\nHEALTHCHECK指令有\nsh 1 2 HEALTHCHECK [OPTIONS] CMD command # 通过在容器内运行命令来检查容器运行状况 HEALTHCHECK NONE # 禁用任何的健康状态检查，包括默认的健康状态检测机制 可以在CMD之前出现的选项：\n参数 说明- --interval 间隔 s秒、m分钟、h小时，default:30s。 --timeout 执行command需要时间，比如curl一个地址，如果超过timeout秒则认为超时是错误的状态，此时每次健康检查的时间是timeout+interval秒。default:30s。 --start-period 在启动容器是，对主进程自我初始化较慢的情况下，default:0s。17.05引入 --retries=N 失败次数，default:3。 当指定了健康检测状态命令，检测请求发出时，响应值为如下3种情况：\n0: 成功，容器健康且随时可用。 1: 不健康，容器无法正常工作。 2: 预留值，无意义，可以自行定义。 |HEALTHCHECK\u0026ndash;start-period=3s CMD wget-0\u0026ndash;q http://${IP:-0.0.0.0}:10080/\nFor example\nsh 1 2 HEALTHCHECK--interval=5m --timeout=3s \\ CMD curl -f http://locdlhost/ || exit 1 SHELL SHELL指令允许覆盖用于shell命令形式的默认shell。在Linux上的默认shell是 [\u0026quot;/bin/sh\u0026quot;,\u0026quot;-c\u0026quot;] , 在Windows上是 [\u0026quot;cmd\u0026quot; , \u0026quot;/S\u0026quot; , \u0026quot;/C\u0026quot;] 。SHELL指令必须以JSON格式写入Dockerfile。\nSHELL指令可以多次出现。每个SHELL指令覆盖先前的SHELL指令，并影响所有后续指令。\nSyntax:\nsh 1 SHELL [\u0026#34;executable\u0026#34;,\u0026#34;parameters] STOHSIGNAL STOPSIGNAL指令设置将发送到容器的系统调用信号，以退出。此信号可以是与内核的系统调用表中的位置匹配的有效无符号数，例如9，或SIGNAME格式的信号名，例如SIGKILL。 语法：STOPSIGNAL signal\nARG ARG指令使用 --build-arg varname='value' 标志定义一个变量，用户可以在使用docker build命令在构建时将其传递给构建器。\n此功能使得一个Dockerfile能够适用于较多的不同场景，尤其是应用程序版本变换时。直接传参数就能确定应该基于Dockerfile制作哪个版本。如果用户指定了未在Dockerfile中定义的构建参数，则构建会输出警告。\nSyntax:\nsh 1 ARG name = default value Dockerfile中可以包括一个或多个ARG指令。 ARG指令可以选择性地包括默认值： sh 1 2 ARG version = 1.14 ARGuser = mageedu ONBUILD 用于在Dockerfile中定义一个触发器。Dockerfile用于build映像文件，此映像文件亦可作为base image被另一个Dockerfile用作FROM指令的参数，并以之构建新的映像文件\n在后面的这个Dockerfile中的FROM指令在build过程中被执行时，将会“触发”创建其base image的Dockerfile文件中的ONBUILD指令定义的触发器\nSyntax:\nsh 1 ONBUILD INSTRUCTION 注意：\n尽管任何指令都可注册成为触发器指令，但ONBUILD不能自我嵌套，且不会触发FROM和MAINTAINER指令。 使用包含ONBUILD指令的Dockerfle构建的镜像应该使用特殊的标签，例如ruby：2.0-onbuild 在ONBUILD指令中使用ADD或COPY指令应该格外小心，因为新构建过程的上下文在缺少指定的源文件时会失败。 四、构建php环境镜像 bash 1 2 3 4 5 6 7 8 9 10 11 12 FROM centos:6 MAINTAINER lc RUN yum install -y httpd php php-gd php-mysql mysql mysql-server ENV MYSQL_ROOT_PASSWORD 123456 RUN echo \u0026#34;\u0026lt;?php phpinfo()?\u0026gt;\u0026#34; \u0026gt; /var/www/html/index.php ADD start.sh /start.sh RUN chmod +x /start.sh ADD https://cn.wordpress.org/wordpress-4.7.4-zh_CN.tar.gz /var/www/html COPY wp-config.php /var/www/html/wordpress VOLUME [\u0026#34;/var/lib/mysql\u0026#34;] CMD /start.sh EXPOSE 80 3306 五、构建java环境镜像 sh 1 2 3 4 5 6 7 8 9 10 FROM centos MAINTAINER lc ADD jdk-8u144-linux-x64.tar.gz /usr/local ENV JAVA_HOME=/usr/local/jdk1.8.0_144 ADD apache-tomcat-8.5.32.tar.gz /usr/local/ RUN mv /usr/local/apache-tomcat-8.5.32 /usr/local/tomcat WORKDIR /usr/local/tomcat ENTRYPOINT [\u0026#34;bin/catalina.sh\u0026#34;,\u0026#34;run\u0026#34;] EXPOSE 8080 六、构建ssh环境镜像 bash 1 2 3 4 5 6 7 8 FROM centos MAINTAINER zhangsan ENV PWD 123 RUN yum install openssh openssh-server openssh-clients -y RUN echo $PWD|passwd --stdin root RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key CMD [\u0026#34;/usr/sbin/sshd\u0026#34;,\u0026#34;-D\u0026#34;] 6.1 测试ssh镜像 systemd启动\n构建完镜像使用systemctl启动发现没有sshd进程\nsh 1 2 3 4 5 6 $ ps PID TTY TIME CMD 1 pts/0 00:00:00 bash 18 pts/0 00:00:00 ps $ systemctl start ssh Failed to get D-Bus connection: Operation not permitted 此问题原因：systemd服务没有启动无法使用systemd启动\nFailed to get D-Bus connection: No connection to service manager\n故启动时一般使用 CMD [\u0026quot;/usr/sbin/sshd\u0026quot;,\u0026quot;-D\u0026quot;]\n","permalink":"https://www.161616.top/dockerfile/","summary":"一、使用前提 通用镜像未必与应用程序和配置是符合我们需要的。\n1.1 常见镜像制作方式 常见制作镜像方式有两种\n基于容器 基于镜像制作：编辑一个Dockerfile，而后根据此文件制作； 二、Dockerfile概述 Dockerfile只是构建Docker镜像的源代码，docker可以通过读取Dockerfile中的指令自动构建图像。Dockerfile是一个文本文档，其中包含用户可以在命令行上调用以组合图像的所有命令。用户可以使用 docker build 创建连续执行多个命令行指令的自动构建。\n2.1 Dockerfile的工作模式 基于Dockerfile制作镜像时，需在专用目录放置Dockerfile文件，并且文件首字母必须大写。基于Dockerfile中打包的文件必须奇基于工作目录往下走的路径。在打包镜像时，.dockeringore 文件本身与所有包含在 .dockeringore 文件中的路径，都不被打包进去。在Dockerfile制作环境为底层镜像启动容器时所能够提供的环境。\n2.2 环境变量替换 制作镜像中还可以使用环境变量。环境变量（使用ENV语句声明）也可以在某些指令中使用，因为Dockerfile环境变量在 Dockerfile 中以 $variable_name 或${variable_name}标记。\n语法还支持一些标准`bash修饰符\n${variable:-word} 表示如果设置了变量，那么结果将是该值。如果未设置变量，那么word将是结果。 ${variable+word} 表示如果设置了变量，则word将是结果，否则结果为空字符串。 三、Dockerfile指令说明 特别说明：Dockerfile中每一条指令都会生成一个新的镜像层。\nFROM FROM指令（最重要的一个），必须为Dockerfile文件开篇的第一个非注释行，用于为镜像文件构建过程指定基准镜像，后续的指令运行于此基准镜像所提供的运行环境。基准镜像可以是任何可用镜像文件，默认情况下，docker build会在docker主机上查找指定的镜像文件，在其不存在时，则会从 Docker Hub Registry 上拉取所需的镜像文件。如果找不到指定的镜像文件，docker build 会返回一个错误信息\nSyntax:\nsh 1 FROM repository[:tag] sh 1 FROM registry/repository[:tag] bash 1 FROM resository@[digest] #←相同名称时，可以使用resository@镜像hush码指定镜像。 参数 说明- reposotiry 某一个镜像的仓库，如redis镜像仓库。 registry docker镜像仓库，如docker hub，docker registry包含很多reposotiry，如nginx php tomcat等。不指定registry，默认从docker hub下载。 tag image的标签，为可选项，省略时默认为latest。 MAINTANIER(depreacted) MAINTANIER（depreacted）用于让Dockerfile制作者提供本人的详细信息。Dockerfile并不限制MAINTAINER指令可在出现的位置，但推荐将其放置于FROM指令之后。","title":"Dockerfile使用示例"},{"content":"Docker Overlay Network Overlay网络是指在不改变现有网络基础设施的前提下，通过某种约定通信协议，把二层报文封装在IP报文之上的新的数据格式。这样不但能够充分利用成熟的IP路由协议进程数据分发；而且在Overlay技术中采用扩展的隔离标识位数，能够突破VLAN的4000数量限制支持高达16M的用户，并在必要时可将广播流量转化为组播流量，避免广播数据泛滥。\n因此，Overlay网络实际上是目前最主流的容器跨节点数据传输和路由方案。\n要想使用Docker原生Overlay网络，需要满足下列任意条件\nDocker 运行在Swarm 使用键值存储的Docker主机集群 使用键值存储搭建Docker主机集群 使用键值存储的Docker主机集群，需满足下列条件：\n集群中主机连接到键值存储，Docker支持 Consul、Etcd和Zookeeper 集群中主机运行一个Docker守护进程 集群中主机必须具有唯一的主机名，因为键值存储使用主机名来标识集群成员 集群中linux主机内核版本在3.12+,支持VXLAN数据包处理，否则可能无法通行 部署docker内置的OverLAY网络 环境准备说明 host ip- node01 10.0.0.15 node02 10.0.0.16 安装Consul 下载地址：Download Consul\n启动命令\nbash 1 2 3 4 5 6 7 8 consul agent -server -bootstrap -ui -data-dir /data/docker/consul \\ -client=10.0.0.16 -bind=10.0.0.16 docker run -d -p 8400:8400 -p 8500:8500 -p 8600:53/udp -h consul progrium/consul -server -bootstrap -ui-dir /ui #-ui : consul 的管理界面 #-data-dir : 数据存储 配置docker链接consul bash 1 2 3 4 5 ExecStart=/usr/bin/dockerd \\ -H tcp://0.0.0.0:2375 \\ -H unix:///var/run/docker.sock \\ --cluster-store consul://10.0.0.16:8500 \\ --cluster-advertise 10.0.0.16:2375 创建 overlay网络 bash 1 docker network create -d overlay --subnet=10.0.2.1/24 overlay-net 这边自动回进行通步，因为使用的是同一个服务器发件。\nbash 1 2 3 4 5 6 7 $ docker network ls NETWORK ID NAME DRIVER SCOPE 5f3ff8aceaa8 bridge bridge local adb97c875132 docker_gwbridge bridge local 497fb0d5ea2f host host local 65e001b471fe none null local f72e6fcf1082 overlay-net overlay global 创建使用overlay网络的容器 bash 1 2 docker run -tid --name test2 --net=overlay-net centos docker run -tid --name test3 --net=overlay-net centos 进入查看ip信息。\nnode01\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 sh-4.2# ifconfig eth0: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1450 inet 10.0.2.3 netmask 255.255.255.0 broadcast 10.0.2.255 ether 02:42:0a:00:02:03 txqueuelen 0 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 eth1: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 172.18.0.2 netmask 255.255.0.0 broadcast 172.18.255.255 ether 02:42:ac:12:00:02 txqueuelen 0 (Ethernet) RX packets 3192 bytes 12218706 (11.6 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 2569 bytes 142308 (138.9 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo: flags=73\u0026lt;UP,LOOPBACK,RUNNING\u0026gt; mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 loop txqueuelen 1 (Local Loopback) RX packets 76 bytes 6960 (6.7 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 76 bytes 6960 (6.7 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 node02\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 sh-4.2# ifconfig eth0: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1450 inet 10.0.2.2 netmask 255.255.255.0 broadcast 10.0.2.255 ether 02:42:0a:00:02:02 txqueuelen 0 (Ethernet) RX packets 4 bytes 336 (336.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 4 bytes 336 (336.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 eth1: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 172.18.0.2 netmask 255.255.0.0 broadcast 172.18.255.255 ether 02:42:ac:12:00:02 txqueuelen 0 (Ethernet) RX packets 2753 bytes 12193675 (11.6 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 2345 bytes 130189 (127.1 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo: flags=73\u0026lt;UP,LOOPBACK,RUNNING\u0026gt; mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 loop txqueuelen 1 (Local Loopback) RX packets 78 bytes 6884 (6.7 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 78 bytes 6884 (6.7 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 测试网络\nbash 1 2 3 4 5 6 7 8 $ ping 10.0.2.3 PING 10.0.2.3 (10.0.2.3) 56(84) bytes of data. 64 bytes from 10.0.2.3: icmp_seq=1 ttl=64 time=0.349 ms $ ping 10.0.2.2 PING 10.0.2.2 (10.0.2.2) 56(84) bytes of data. 64 bytes from 10.0.2.2: icmp_seq=1 ttl=64 time=0.023 ms ","permalink":"https://www.161616.top/docker-cross-node-network/","summary":"Docker Overlay Network Overlay网络是指在不改变现有网络基础设施的前提下，通过某种约定通信协议，把二层报文封装在IP报文之上的新的数据格式。这样不但能够充分利用成熟的IP路由协议进程数据分发；而且在Overlay技术中采用扩展的隔离标识位数，能够突破VLAN的4000数量限制支持高达16M的用户，并在必要时可将广播流量转化为组播流量，避免广播数据泛滥。\n因此，Overlay网络实际上是目前最主流的容器跨节点数据传输和路由方案。\n要想使用Docker原生Overlay网络，需要满足下列任意条件\nDocker 运行在Swarm 使用键值存储的Docker主机集群 使用键值存储搭建Docker主机集群 使用键值存储的Docker主机集群，需满足下列条件：\n集群中主机连接到键值存储，Docker支持 Consul、Etcd和Zookeeper 集群中主机运行一个Docker守护进程 集群中主机必须具有唯一的主机名，因为键值存储使用主机名来标识集群成员 集群中linux主机内核版本在3.12+,支持VXLAN数据包处理，否则可能无法通行 部署docker内置的OverLAY网络 环境准备说明 host ip- node01 10.0.0.15 node02 10.0.0.16 安装Consul 下载地址：Download Consul\n启动命令\nbash 1 2 3 4 5 6 7 8 consul agent -server -bootstrap -ui -data-dir /data/docker/consul \\ -client=10.0.0.16 -bind=10.0.0.16 docker run -d -p 8400:8400 -p 8500:8500 -p 8600:53/udp -h consul progrium/consul -server -bootstrap -ui-dir /ui #-ui : consul 的管理界面 #-data-dir : 数据存储 配置docker链接consul bash 1 2 3 4 5 ExecStart=/usr/bin/dockerd \\ -H tcp://0.","title":"Docker跨宿主机网络通信"},{"content":" 参数 说明 -i, \u0026ndash;interactive 即使不是交互模式也保持stdin打开 -d, \u0026ndash;detach 后台运行容器并打印容器ID -t, \u0026ndash;tty 分配一个伪TTY 添加自定义主机映射\nbash 1 2 3 4 5 6 $ docker run -tid --add-host docker-node:10.0.0.1 centos 61d5824c720f1a32c743a3d0f434e17a7f6860dba1cb5559653a80c064da8073 $ docker exec 61d5824c720f1a32c cat /etc/hosts ff02::2\tip6-allrouters 10.0.0.1\tdocker-node 172.17.0.2\t61d5824c720f 添加linux功能\nlinux内核特性，提供权限访问控制。如需要特殊权限，不赋权限容器将不能正常运行。\n将容器pid写入一个文件内\nbash 1 2 3 4 $ docker run -itd --cidfile /tmp/pid centos 458d9f4b3cc51a4f0f3abffbc78c643b98a89eef3cdfe263e762ac05d3f5f47d $ cat /tmp/pid 458d9f4b3cc51a4f0f3abffbc78c643b98a89eef3cdfe263e762ac05d3f5f47d 将主机列表添加到容器中\nbash 1 --device list 设置自定义dns\nbash 1 2 3 4 5 $ docker run -it centos cat /etc/resolv.conf nameserver 10.0.0.2 nameserver 10.0.0.2 $ docker run -it --dns 8.8.8.8 centos cat /etc/resolv.conf nameserver 8.8.8.8 设置容器的环境变量\nbash 1 2 3 4 5 $ docker run -itd -e \u0026#34;TEST=abc\u0026#34; centos 2d3ef722737a0a034151060ef2d8e97b21feee7590917a0e921c21e864d18a47 $ docker attach 2d3ef722737a0 $ echo $TEST abc 暴露端口或指定范围的端口号\nbash 1 2 3 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f7fd3f365512 centos \u0026#34;/bin/bash\u0026#34; 5 seconds ago Up 5 seconds 8080/tcp wonderful_golick 为容器指定主机名\nbash 1 2 3 4 $ docker run -itd -h nginx centos bdcdf43bf4540d1a9bb794042c6d506c2680be0eab317002697a8049c5667716 $ docker exec bdcdf43bf4540 hostname nginx 为容器分配ip\n创建网络\nbash 1 2 $ docker network create --subnet=10.10.0.0/24 network_test 85d5f3e2cd09e2bd57bc68b56c9341f5b1d4cc1194641715937d8e197cca09f7 查看网络\nbash 1 2 3 4 5 6 $ docker network ls NETWORK ID NAME DRIVER SCOPE 6af203aae34e bridge bridge local b20cfc0864e6 host host local 85d5f3e2cd09 network_test bridge local c287f5c1181e none null local 删除网络\nbash 1 2 $ docker network rm network_test network_test 指定容器网络\nbash 1 2 $ docker run -idt --net=network_test --ip 10.10.0.3 -h network centos ad885aebe13fa244748c040121f849385ae5b3b8d243f76cb43695495f20c301 查看容器信息 json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 \u0026#34;Networks\u0026#34;: { \u0026#34;network_test\u0026#34;: { \u0026#34;IPAMConfig\u0026#34;: { \u0026#34;IPv4Address\u0026#34;: \u0026#34;10.10.0.3\u0026#34; }, \u0026#34;Links\u0026#34;: null, \u0026#34;Aliases\u0026#34;: [ \u0026#34;ad885aebe13f\u0026#34; ], \u0026#34;NetworkID\u0026#34;: \u0026#34;c1196614dc1eec93c34774f9498ea3254084e1\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;a1c491b6155b104ac70194d9b1fa7ab84b6d4\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;10.10.0.1\u0026#34;, \u0026#34;IPAddress\u0026#34;: \u0026#34;10.10.0.3\u0026#34;, \u0026#34;IPPrefixLen\u0026#34;: 24, \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:0a:0a:00:03\u0026#34; } link建立容器之间的连接\nbash 1 2 3 4 5 6 $ docker run -tid --name centos centos 8f6e13a26afe60ee2ac5335d419852d70580343f590c2500964f54020e54391c $ docker exec centos ifconfig eth0: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255 bash 1 2 3 4 5 6 7 8 9 10 11 12 $ docker run -tid --name link --link centos:nginx.org centos 8f1a542c6058caeeb8b59e02b86e7b9cabc2bc7784d71e355f5d5ca7a6738481 $ docker exec link cat /etc/hosts 127.0.0.1\tlocalhost ::1\tlocalhost ip6-localhost ip6-loopback fe00::0\tip6-localnet ff00::0\tip6-mcastprefix ff02::1\tip6-allnodes ff02::2\tip6-allrouters 172.17.0.2\t*nginx.org* 8f6e13a26afe centos 172.17.0.3\t8f1a542c6058 log-driver docker 容器默认日志保存位置 /var/lib/docker/containers/container-json.log\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ ls -1 /var/lib/docker/containers 8f1a542c6058caeeb8b59e02b86e7b9cabc2bc7784d71e355f5d5ca7a6738481 8f6e13a26afe60ee2ac5335d419852d70580343f590c2500964f54020e54391c $ ls -1 8f6e13a26afe60ee2ac5335d419852d70580343f590c2500964f54020e54391c-json.log checkpoints config.v2.json hostconfig.json hostname hosts mounts resolv.conf resolv.conf.hash| $ cat 8f6e..1c-json.log 可以在启动时将日志输出到指定位置。\n驱动 介绍 none 不输出日志 json-file Docker的默认日志记录驱动程序，格式为JSON。 syslog 将日志消息写入syslog journald 将日志消息写入journald。 journald守护程序必须在主机上运行。 gelf 将日志消息写入Graylog扩展日志格式（GELF）端点，例如Graylog或Logstash。 fluentd 将日志消息写入流利（正向输入）。流利的守护程序必须在主机上运行 awslogs 将日志消息写入Amazon CloudWatch Logs。 splunk 使用HTTP事件收集器将日志消息写入splunk etwlogs 将日志消息写为Windows事件跟踪（ETW）事件。仅适用于Windows平台。 gcplogs 将日志消息写入Google Cloud Platform（GCP）日志记录。 nats 用于Docker的nats NATS日志记录驱动程序。将日志条目发布到NATS服务器。 指定日志驱动测试 bash 1 docker run -tid --name nginx --log-driver syslog nginx bash 1 2 3 4 5 6 7 8 9 curl 172.17.0.4 # 新窗口查看日志可见到容器记录到宿主机的syslog中 $ tail -f /var/log/messages Jul 26 01:44:18 docker-node2 systemd: Started Session 8 of user root. Jul 26 01:44:18 docker-node2 systemd: Starting Session 8 of user root. Jul 26 01:45:20 docker-node2 5f95cf77c994[2032]: 172.17.0.1 \\ - - [25/Jul/2018:17:45:20 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 612 \u0026#34;-\u0026#34; \u0026#34;curl/7.29.0\u0026#34; \u0026#34;-\u0026#34; 挂载宿主机的分区到容器\nUse bind mounts | Docker Documentation\n将容器的端口映射到宿主机上\nbash 1 docker run -itd -p 8080:80 centos 将expose声明的所有端口映射到宿主机的随机端口\n-P 容器down掉自动重启\nbash 1 2 3 4 5 6 7 docker run -tid --name nginx --restart always nginx $ docker attach nginx ^C $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 07b2196fb6c5 nginx \u0026#34;nginx -g \u0026#39;daemon of…\u0026#34; About a minute ago Up 4 seconds 80/tcp nginx 设置文件描述符大小\nbash 1 2 3 4 5 6 7 docker run -itd --name test --ulimit nproc=1024 --ulimit nofile=1024 centos $ docker attach test $ ulimit -a open files (-n) 1024 max user processes (-u) 1024 资源限制 例1：限制cpu使用数量\nbash 1 docker run -tid --name cpu2 --cpus=2 centos 使用stress测试cpu使用情况\n测试机器为双核4G硬件资源\n1. 限制两颗CPU\nbash 1 2 3 4 5 6 $ stress -c 13 stress: info: [70] dispatching hogs: 13 cpu, 0 io, 0 vm, 0 hdd # 使用 docker stats 查看 docker stats cpu2 CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % 1eed573b906a cpu2 199.46% 792KiB / 3.686GiB 0.02% 在宿主机上top查看\ntext 1 2 %Cpu0 : 99.7 us, 0.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st 将cpu增加至4核，查看cpu状态 text 1 2 3 4 %Cpu0 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 : 99.7 us, 0.0 sy, 0.0 ni, 0.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 99.7 us, 0.0 sy, 0.0 ni, 0.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st 2. 限制1颗CPU\n限制容器只使用1核cpu。观察容器状态。发现容器将使用率均衡在其他核心上。\ntext 1 2 3 4 5 6 7 8 9 %Cpu0 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 : 30.4 us, 0.0 sy, 0.0 ni, 69.6 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 : 19.7 us, 0.0 sy, 0.0 ni, 80.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 49.2 us, 0.0 sy, 0.0 ni, 50.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu0 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 : 49.5 us, 0.0 sy, 0.0 ni, 50.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 49.7 us, 0.3 sy, 0.0 ni, 50.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st 结论：对于进程来说是没有 CPU 个数这一概念的，内核只能通过进程消耗的 CPU 时间片来统计出进程占用 CPU 的百分比。这也是我们看到的各种工具中都使用百分比来说明 CPU 使用率的原因。\n官方文档：Limit a container\u0026rsquo;s resources | Docker Documentation\n指定固定的 CPU\nbash 1 2 3 4 $ docker run -tid --name cpunum --cpuset-cpus=\u0026#34;2\u0026#34; stress 31d72f808e8992bcfbfead1d4d7a78e37235e1e42c8b2011b67a15d542829287 $ docker attach cpunum $ stress -c 4 top查看cpu状态，发现只有固定的一个cpu被使用\nbash 1 2 3 4 %Cpu0 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st 限制多个cpunum。\nbash 1 2 3 4 $ docker run -tid --name cpunum2 --cpuset-cpus=\u0026#34;0,3\u0026#34; stress b4cb513dad71aa9c6cc5578a70253c704dcde59a5b1306943a3967c414e2d9a9 $ docker attach cpunum2 $ stress -c 4 top查看cpu状态\ntext 1 2 3 4 %Cpu0 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st 设置CPU权重\n当 CPU 资源充足时，设置 CPU 的权重是没有意义的。只有在容器争用 CPU 资源的情况下， CPU 的权重才能让不同的容器分到不同的 CPU 用量。\u0026ndash;cpu-shares 选项用来设置 CPU 权重，它的默认值为 1024。我们可以把它设置为 2 表示很低的权重，但是设置为 0 表示使用默认值 1024。\nbash 1 2 docker run -tid --name cpu-test1 --cpuset-cpus=\u0026#34;0\u0026#34; --cpu-shares=512 stress docker run -tid --name cpu-test2 --cpuset-cpus=\u0026#34;0\u0026#34; --cpu-shares=0 stress 当只有test-1使用cpu资源时的CPU负载\nbash 1 2 1717c10e24ae cpu-test2 0.00% 376KiB / 3.686GiB 0.01% 8a1e1a944e2b cpu-test1 99.96% 680KiB / 3.686GiB 0.02% 当test-1与test-2争用资源时的CPU负载\ntext 1 2 1717c10e24ae cpu-test2 66.91% 668KiB / 3.686GiB 8a1e1a944e2b cpu-test1 33.37% 576KiB / 3.686GiB 两个容器分享一个 CPU，所以总量应该是 100%。具体每个容器分得的负载则取决于 \u0026ndash;cpu-shares 选项的设置！我们的设置分别是 512 和 1024，则它们分得的比例为 1:2。在本例中如果想让两个容器各占 50%，只要把 \u0026ndash;cpu-shares 选项设为相同的值就可以了。\n对容器资源的限制 /sys/fs/cgroup/\nbash 1 2 3 docker run -tid --name cpunum2 --cpuset-cpus=\u0026#34;0,3\u0026#34; stress $ cat cpuset/cpuset.cpus 0,3 ","permalink":"https://www.161616.top/docker-ma/","summary":"参数 说明 -i, \u0026ndash;interactive 即使不是交互模式也保持stdin打开 -d, \u0026ndash;detach 后台运行容器并打印容器ID -t, \u0026ndash;tty 分配一个伪TTY 添加自定义主机映射\nbash 1 2 3 4 5 6 $ docker run -tid --add-host docker-node:10.0.0.1 centos 61d5824c720f1a32c743a3d0f434e17a7f6860dba1cb5559653a80c064da8073 $ docker exec 61d5824c720f1a32c cat /etc/hosts ff02::2\tip6-allrouters 10.0.0.1\tdocker-node 172.17.0.2\t61d5824c720f 添加linux功能\nlinux内核特性，提供权限访问控制。如需要特殊权限，不赋权限容器将不能正常运行。\n将容器pid写入一个文件内\nbash 1 2 3 4 $ docker run -itd --cidfile /tmp/pid centos 458d9f4b3cc51a4f0f3abffbc78c643b98a89eef3cdfe263e762ac05d3f5f47d $ cat /tmp/pid 458d9f4b3cc51a4f0f3abffbc78c643b98a89eef3cdfe263e762ac05d3f5f47d 将主机列表添加到容器中\nbash 1 --device list 设置自定义dns\nbash 1 2 3 4 5 $ docker run -it centos cat /etc/resolv.","title":"docker容器管理"},{"content":"docker的四种网络模式 Bridge模式（默认） 当Docker进程启动时，会在宿主机上创建一个名为docker0的虚拟网桥，此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似，这样主机上的所有容器就通过交换机连在了一个二层网络中。\n默认ip段172.17.0.1/16；从docker0子网中分配一个IP给容器使用，并设置docker0的IP为容器的默认网关。在主机上创建一对虚拟网卡veth pair设备，Docker将veth pair设备的一端放在新创建的容器中，并命名为eth0（容器的网卡），另一端放在主机中，以vethxxx这样类似的名字命名，并将这个网络设备加入到docker0网桥中。可以通过brctl show命令查看。\n使用 docker run -p 时，docker实际是在iptables做了DNAT规则，实现端口转发功能。可以使用 iptables -t nat -nL 查看。\nhost模式 启动容器的时候使用host模式，那么这个容器将不会获得一个独立的Network Namespace，而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡，配置自己的IP等，而是使用宿主机的IP和端口。但是，容器的其他方面，如文件系统、进程列表等还是和宿主机隔离的。\nnone模式 使用none模式，Docker容器拥有自己的Network Namespace，但是，并不为Docker容器进行任何网络配置。也就是说，这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。\ncontainer模式 这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace，而不是和宿主机共享。新创建的容器不会创建自己的网卡，配置自己的 IP，而是和一个指定的容器共享 IP、端口范围等。同样，两个容器除了网络方面，其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。\n容器外部访问原理 bash 1 2 3 4 5 6 7 8 docker0: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 172.17.0.1 netmask 255.255.0.0 broadcast 0.0.0.0 inet6 fe80::42:a5ff:fe59:2034 prefixlen 64 scopeid 0x20\u0026lt;link\u0026gt; ether 02:42:a5:59:20:34 txqueuelen 0 (Ethernet) RX packets 56986 bytes 2746876 (2.6 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 64106 bytes 503304169 (479.9 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 bash 1 2 3 4 5 6 7 8 9 10 11 12 docker run -itd --name test_network -p 80:80 centos:6 $ iptables -t nat -nL Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0 MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:80 Chain DOCKER (2 references) target prot opt source destination RETURN all -- 0.0.0.0/0 0.0.0.0/0 DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80 配置桥接网络 下载网桥管理工具 bash 1 yum install bridge-utils -y 停止docker并删除docker0网桥 bash 1 2 ip link set dev docker0 down brctl delbr docker0 创建新桥接物理网络虚拟网桥test bash 1 2 3 4 5 brctl addbr test ip link set dev test up ip addr add 10.10.10.0/24 dev test #为br0分配物理网络中的ip地址 ip addr del 10.0.0.0/24 dev eth0 #将宿主机网卡的IP清空 brctl addif test eth0 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 $ docker run -itd --name centos centos:6 26ea6dde6564006f148e4977d131e671b578c2c5df313b1d872940a9e48f0309 $ docker attach centos $ ifconfig eth0 Link encap:Ethernet HWaddr 02:42:C0:A8:00:02 inet addr:192.168.0.2 Bcast:0.0.0.0 Mask:255.255.255.0 inet6 addr: fe80::42:c0ff:fea8:2/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:7 errors:0 dropped:0 overruns:0 frame:0 TX packets:7 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:578 (578.0 b) TX bytes:578 (578.0 b) 将docker0加入网桥中\ntext 1 2 3 $ brctl show bridge name\tbridge id\tSTP enabled\tinterfaces docker0\t8000.024206e55aa2\tno\tbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $ docker run -itd --net host centos 5237ae95c660a0354046f0e5ff839c9a4babda7c9743ca8f4d2338e6a8445e55 \u0026#34;Networks\u0026#34;: { \u0026#34;host\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;b2768c9e7cb0de16cde0626abbca8414ca80c96101531aa7842dfed7ee9fc884\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;645627ef6384f1f36e3cfeddd5d9346cfdf1e8997b597c2ec1edd8577e17d6f5\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;IPAddress\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;IPPrefixLen\u0026#34;: 0, \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;\u0026#34;, \u0026#34;DriverOpts\u0026#34;: null } } } } ] 查看网络模式\nbash 1 2 3 4 5 $ docker network ls NETWORK ID NAME DRIVER SCOPE a39cc87800d6 bridge bridge local b2768c9e7cb0 host host local 329e3d9d1c3f none null local tags: []\nisStarred: false\nisTrashed: false\n修改docker0默认ip\nbash 1 2 3 4 $ cat /etc/docker/daemon.json { \u0026#34;bip\u0026#34;: \u0026#34;192.168.100.1/24\u0026#34; } Reference Docker Centos7 下建立 Docker 桥接网络 - weifengCorp - 博客园 centos7 docker宿主机配置桥接物理网络终极实战-zhaoyfcomeon-成长之路-51CTO博客 docker自定义网桥 - CSDN博客 ","permalink":"https://www.161616.top/docker-network/","summary":"docker的四种网络模式 Bridge模式（默认） 当Docker进程启动时，会在宿主机上创建一个名为docker0的虚拟网桥，此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似，这样主机上的所有容器就通过交换机连在了一个二层网络中。\n默认ip段172.17.0.1/16；从docker0子网中分配一个IP给容器使用，并设置docker0的IP为容器的默认网关。在主机上创建一对虚拟网卡veth pair设备，Docker将veth pair设备的一端放在新创建的容器中，并命名为eth0（容器的网卡），另一端放在主机中，以vethxxx这样类似的名字命名，并将这个网络设备加入到docker0网桥中。可以通过brctl show命令查看。\n使用 docker run -p 时，docker实际是在iptables做了DNAT规则，实现端口转发功能。可以使用 iptables -t nat -nL 查看。\nhost模式 启动容器的时候使用host模式，那么这个容器将不会获得一个独立的Network Namespace，而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡，配置自己的IP等，而是使用宿主机的IP和端口。但是，容器的其他方面，如文件系统、进程列表等还是和宿主机隔离的。\nnone模式 使用none模式，Docker容器拥有自己的Network Namespace，但是，并不为Docker容器进行任何网络配置。也就是说，这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。\ncontainer模式 这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace，而不是和宿主机共享。新创建的容器不会创建自己的网卡，配置自己的 IP，而是和一个指定的容器共享 IP、端口范围等。同样，两个容器除了网络方面，其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。\n容器外部访问原理 bash 1 2 3 4 5 6 7 8 docker0: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 172.17.0.1 netmask 255.255.0.0 broadcast 0.0.0.0 inet6 fe80::42:a5ff:fe59:2034 prefixlen 64 scopeid 0x20\u0026lt;link\u0026gt; ether 02:42:a5:59:20:34 txqueuelen 0 (Ethernet) RX packets 56986 bytes 2746876 (2.6 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 64106 bytes 503304169 (479.","title":"Docker网络"},{"content":"关于vlan说明 Macvlan和ipvlan是Linux网络驱动程序，它们将底层或主机接口直接暴露给在主机中运行的VM或容器。\nMacvlan允许单个物理接口使用macvlan子接口具有多个mac和ip地址。这与使用vlan在物理接口上创建子接口不同。使用vlan子接口，每个子接口使用vlan属于不同的L2域，所有子接口都具有相同的mac地址。使用macvlan，每个子接口将获得唯一的mac和ip地址，并将直接暴露在底层网络中。Macvlan接口通常用于虚拟化应用程序，每个macvlan接口都连接到Container或VM。每个容器或VM可以直接从公共服务器获取dhcp地址，就像主机一样。这将有助于希望Container成为传统网络的客户使用他们已有的IP寻址方案。Macvlan有4种类型(Private, VEPA, Bridge, Passthru)。常用的类型是Macvlan网桥，它允许单个主机中的端点能够在没有数据包离开主机的情况下相互通信。对于外部连接，使用底层网络。下图显示了两个使用macvlan网桥相互通信以及外部世界的容器。两个容器将使用Macvlan子接口直接暴露在底层网络中。\n使用mavvlan构建docker网络 Macvlan，MACVLAN或MAC-VLAN允许您在单个物理接口上配置多个第2层（即以太网MAC）地址。 Macvlan允许您配置父物理以太网接口（也称为上层设备）的子接口（也称为从设备），每个接口都有自己唯一的（随机生成的）MAC地址，因此也有自己的IP地址。然后，应用程序、VM和容器可以绑定到特定的子接口，以使用自己的MAC和IP地址直接连接到物理网络。\nMavlan子接口不能直接与父接口通信，即VM不能直接与主机通信。如果需要VM主机通信，则应添加另一个macvlan子接口并将其分配给主机。\nMacvlan子接口使用 eth0.20@eth0 表示法来清楚地识别子接口及其父接口。子接口状态绑定到其父级状态。如果eth0关闭，则 eth0.20@eth0 也会关闭。\n配置macvlan先决条件 至少需要Linux内核版本3.9以上，建议使用4.0或更高版本。 环境准备 主机名 IP地址 地位 软件环境 物理机 10.0.0.1 物理机 windows10 网关 10.0.0.2 宿主机网关 vmvare网关 c1 10.0.0.3 容器01 docker c2 10.0.0.4 容器02 docker node01 10.0.0.15 宿主机01（vm虚拟机） centos 7.3/docker-ce1806 node02 10.0.0.16 宿主机02（vm虚拟机） centos 7.3/docker-ce1806 2.3 启动网卡混合模式 两台主机网卡使用桥接模式,网卡混杂模式开启全部允许。\n主机上配置的eth0网卡和创建的vlan网卡,均需要开启混杂模式。如果不开启混杂模式会导致macvlan网络无法访问外界,具体在不使用vlan时,表现为无法ping通路由,无法ping通同一网络内其他主机。\nsh 1 2 ip link set eth0 promisc on ip link set eth0 promisc off 开启后查看网卡状态\nsh 1 2 3 4 5 6 7 $ ip addr 2: eth0: \u0026lt;BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 00:0c:29:84:f3:29 brd ff:ff:ff:ff:ff:ff inet 10.0.0.15/24 brd 10.0.0.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::20c:29ff:fe84:f329/64 scope link valid_lft forever preferred_lft forever 其中BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP的PROMISC说明网卡eth0已开启成混杂模式。\n注：以上设置临时生效\n基于macvlan构建docker跨宿主机通讯 sh 1 2 3 4 5 docker network create \\ -d macvlan \\ --subnet=10.10.0.0/24 \\ --gateway=10.10.0.254 \\ -o parent=eth0 mvl1 说明：容器默认使用主机的DNS设置，因此无需配置DNS服务器。\n查看创建结果\nsh 1 2 3 4 5 $ docker network ls NETWORK ID NAME DRIVER SCOPE 3d2449dfe4b1 bridge bridge local 7110f9183457 host host local 9852fc2a7109 mvl1 macvlan local 在node01上运行容器\nsh 1 docker run -tid --name c1 --net mvl1 --ip 10.10.0.1 busybox 在node02上运行容器\nsh 1 docker run -tid --name c2 --net mvl1 --ip 10.10.0.2 busybox 在C1上平C2 检查结果\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 / # ifconfig eth0 Link encap:Ethernet HWaddr 02:42:0A:0A:00:01 inet addr:10.10.0.1 Bcast:10.10.0.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) / # ping 10.10.0.2 PING 10.10.0.2 (10.10.0.2): 56 data bytes 64 bytes from 10.10.0.2: seq=0 ttl=64 time=0.397 ms 64 bytes from 10.10.0.2: seq=1 ttl=64 time=0.278 ms 构建macvlan与宿主机同网段docker网络 在两台主机上分别创建docker网络\nsh 1 docker network create -d macvlan --subnet=10.0.0.0/24 --gateway=10.0.0.2 -o parent=eth0 mvl1 说明：\n--gateway为宿主机的网关，如宿主机为物理机则设置路由器的ip。 --subnet为宿主机所在网段。 在两台主机上分别创建容器\nsh 1 2 docker run -ti --net mvl1 --ip 10.0.0.4 busybox docker run -ti --net mvl1 --ip 10.0.0.3 busybox 测试网络连通情况\nping网关，结论：通。\nsh 1 2 3 / # ping 10.0.0.2 PING 10.0.0.2 (10.0.0.2): 56 data bytes 64 bytes from 10.0.0.2: seq=0 ttl=128 time=0.330 ms ping宿主机，结论：不通。\nsh 1 2 / # ping 10.0.0.15 PING 10.0.0.15 (10.0.0.15): 56 data bytes ping其他宿主机，结论：通。\nsh 1 2 3 / # ping 10.0.0.16 PING 10.0.0.16 (10.0.0.16): 56 data bytes 64 bytes from 10.0.0.16: seq=0 ttl=64 time=0.530 ms ping其他容器，结论：通。\nsh 1 2 3 / # ping 10.0.0.3 PING 10.0.0.3 (10.0.0.3): 56 data bytes 64 bytes from 10.0.0.3: seq=0 ttl=64 time=0.435 ms 带有VLAN的macvlan 说明 单个Docker主机网络接口只能作为一个macvlan或ipvlan网络的父接口。然而，一个macvlan，一个第2层域和每个物理接口一个子网是现代虚拟化解决方案中相当严重的限制。幸运的是，Docker主机子接口可以作为macvlan网络的父接口。这与VLAN的Linux实现完全一致，其中802.1Q中继连接上的每个VLAN都在物理接口的子接口上。\nvlan介绍 VLAN(Virtual Local Area Network)又称虚拟局域网，是指在局域网的基础上，采用网络管理软件构建的可跨越不同网段、不同网络的端到端的逻辑网络。\n一个VLAN组成一个逻辑子网，即一个逻辑广播域，它可以覆盖多个网络设备，允许处于不同地理位置的网络用户加入到一个逻辑子网中。使用VLAN功能后，能够将网络分割成多个广播域。\nLinux支持在物理网卡上创建vlan子接口。每个vlan子接口属于不同的二层域，所有的vlan子接口拥有相同的MAC地址。这点是和Macvlan子接口不同的地方。\nvlan范围说明\n范围 说明 0，4095 保留 仅限系统使用 用户不能查看和使用这些VLAN 1 正常 Cisco默认VLAN 用户能够使用该VLAN，但不能删除它 2-1001 正常 用于以太网的VLAN 用户可以创建、使用和删除这些VLAN 1002-1005 正常 用于FDDI和令牌环的Cisco默认VLAN 用户不能删除这些VLAN 1006-1024 保留 仅限系统使用 用户不能查看和使用这些VLAN 1025-4094 扩展 仅用于以太网VLAN 环境准备 主机名 IP地址 地位 软件环境 c1 10.10.0.1 容器01-02 docker c2 10.10.0.2 容器01-02 docker c3 10.10.0.3 容器02-01 docker c4 10.10.0.4 容器02-02 docker gateway01 10.0.0.253 容器01网关 gateway01 10.0.0.254 容器01网关 node01 10.0.0.15 宿主机01（vm虚拟机） centos 7.3/docker-ce1806 node02 10.0.0.16 宿主机02（vm虚拟机） centos 7.3/docker-ce1806 创建VLAN 为node01物理网卡创建macvlan子接口\nsh 1 2 ip link add link eth0 name eth0.100 type vlan id 100 ip link add link eth0 name eth0.200 type vlan id 200 启用macvlan\nsh 1 2 ip link set eth0.100 up ip link set eth0.200 up 设置macvlan的ip和网关\ntext 1 2 3 4 5 ip addr add 10.10.0.254/24 dev eth0.100 ip addr add 10.20.0.254/24 dev eth0.200 ip route add default via 10.10.0.254 dev eth0.100 ip route add default via 10.20.0.254 dev eth0.200 参考网址 Exploring Docker Networking – Host, None, and MACVLAN | raid-zero.com | Page 3\nDocker Networking: macvlans with VLANs – HiCube\n","permalink":"https://www.161616.top/docker-cross-node-network-macvlan/","summary":"关于vlan说明 Macvlan和ipvlan是Linux网络驱动程序，它们将底层或主机接口直接暴露给在主机中运行的VM或容器。\nMacvlan允许单个物理接口使用macvlan子接口具有多个mac和ip地址。这与使用vlan在物理接口上创建子接口不同。使用vlan子接口，每个子接口使用vlan属于不同的L2域，所有子接口都具有相同的mac地址。使用macvlan，每个子接口将获得唯一的mac和ip地址，并将直接暴露在底层网络中。Macvlan接口通常用于虚拟化应用程序，每个macvlan接口都连接到Container或VM。每个容器或VM可以直接从公共服务器获取dhcp地址，就像主机一样。这将有助于希望Container成为传统网络的客户使用他们已有的IP寻址方案。Macvlan有4种类型(Private, VEPA, Bridge, Passthru)。常用的类型是Macvlan网桥，它允许单个主机中的端点能够在没有数据包离开主机的情况下相互通信。对于外部连接，使用底层网络。下图显示了两个使用macvlan网桥相互通信以及外部世界的容器。两个容器将使用Macvlan子接口直接暴露在底层网络中。\n使用mavvlan构建docker网络 Macvlan，MACVLAN或MAC-VLAN允许您在单个物理接口上配置多个第2层（即以太网MAC）地址。 Macvlan允许您配置父物理以太网接口（也称为上层设备）的子接口（也称为从设备），每个接口都有自己唯一的（随机生成的）MAC地址，因此也有自己的IP地址。然后，应用程序、VM和容器可以绑定到特定的子接口，以使用自己的MAC和IP地址直接连接到物理网络。\nMavlan子接口不能直接与父接口通信，即VM不能直接与主机通信。如果需要VM主机通信，则应添加另一个macvlan子接口并将其分配给主机。\nMacvlan子接口使用 eth0.20@eth0 表示法来清楚地识别子接口及其父接口。子接口状态绑定到其父级状态。如果eth0关闭，则 eth0.20@eth0 也会关闭。\n配置macvlan先决条件 至少需要Linux内核版本3.9以上，建议使用4.0或更高版本。 环境准备 主机名 IP地址 地位 软件环境 物理机 10.0.0.1 物理机 windows10 网关 10.0.0.2 宿主机网关 vmvare网关 c1 10.0.0.3 容器01 docker c2 10.0.0.4 容器02 docker node01 10.0.0.15 宿主机01（vm虚拟机） centos 7.3/docker-ce1806 node02 10.0.0.16 宿主机02（vm虚拟机） centos 7.3/docker-ce1806 2.3 启动网卡混合模式 两台主机网卡使用桥接模式,网卡混杂模式开启全部允许。\n主机上配置的eth0网卡和创建的vlan网卡,均需要开启混杂模式。如果不开启混杂模式会导致macvlan网络无法访问外界,具体在不使用vlan时,表现为无法ping通路由,无法ping通同一网络内其他主机。\nsh 1 2 ip link set eth0 promisc on ip link set eth0 promisc off 开启后查看网卡状态\nsh 1 2 3 4 5 6 7 $ ip addr 2: eth0: \u0026lt;BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 00:0c:29:84:f3:29 brd ff:ff:ff:ff:ff:ff inet 10.","title":"macvlan实现docker跨宿主机访问"},{"content":"tomcat介绍 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器，Tomcat是Apache 软件基金会（Apache Software Foundation）的Jakarta 项目中的一个核心项目，它早期的名称为catalina，后来由Apache、Sun 和其他一些公司及个人共同开发而成，并更名为Tomcat。\njava体系结构 java程序设计语言（编程） java类文件格式 java api java vm java 纯面向对象语言\nobj：以指令为中心，围绕指令组织数据\n面向对象：以数据为中心，围绕数据组织指令\nTomcat核心组件：\nCatalina：servlet container在tomcat中用于实现servlet代码的被称作Catalina Coyote：一个http连接器，能够接受http请求并相应http请求的web服务器 jasper：JSP Engine jsp翻译器 Tomcat组成部分 Tomcat支持Servlet和JSP1的规范，它由一组嵌套的层次和组件组成，一般可分为以下四类：\n顶级组件：位于配置层次的顶级，并且彼此间有着严格的对应关系； 连接器：连接客户端（可以是浏览器或Web服务器）请求至Servlet容器， 容器：包含一组其它组件； 被嵌套的组件：位于一个容器当中，但不能包含其它组件； tomcat常见组件 服务器(server) 服务器(server)顶级组件：Tomcat的一个实例，通常一个JVM只能包含一个Tomcat实例；因此，一台物理服务器上可以在启动多个JVM的情况下在每一个JVM中启动一个Tomcat实例，每个实例分属于一个独立的管理端口。这是一个顶级组件。\nserver相关属性 className：用于实现此Server容器的完全限定类的名称，默认为 org.apache.cataliana.core.StandardServer;\nport：接收shutdown指令的端口，默认仅允许通过本机访问，默认为8005\nshutdown：发往此Server用于实现关闭tomcat实例的命令字符串，默认为SHUTDOWN\n服务(service) Service主要用于关联一个引擎和此引擎相关的连接器，每个连接器通过一个特定的端口和协议接收入站请求将其转发至关联的引擎进行处理，因此，Service要包含一个引擎（Engine）、一个或多个连接器（Connector）。 定义一个名为Catalina的Service，此名字也会产生相关的日志信息时记录在日志文件当中。\nxml 1 \u0026lt;Service name=\u0026#34;Catalina\u0026#34;\u0026gt; Service相关属性 className：用于实现service的类名，一般都是 org.apache.catalina.core.StandardService\nname：此服务的名称，默认为Catalina。\n连接器(connectors) 分析并接收用户请求，并把它转换成本地jsp文件代码的请求。负责连接客户端（可以是浏览器或Web服务器）请求至Servlet容器内的Web应用程序，通常指的是接收客户发来请求的位置及服务器端分配的端口。 默认端口通常是HTTP协议的8080，管理员也可以根据自己的需要改变此端口。一个引擎可以配置多个连接器，但这些连接器必须使用不同的端口。默认的连接器是基于HTTP/1.1的Coyote。同时，Tomcat也支持AJP、JServ和JK2连接器。\n进入Tomcat的请求可以根据Tomcat的工作模式分为如下两类：\nTomcat作为应用程序服务器：请求来自于前端的web服务器，这可能是Apache IIS Nginx。\nTomcat作为独立服务器：请求来自与web浏览器。\nTomcat应该考虑工作情形并为相应情形下的请求分别定义好需要的连接器才能正确接收来自于客户端的请求，一个引擎可以有一个或多个连接器，以适应多种请求方式。\n定义连接器可以使用多种属性，有些属性也只是适用于某种特定的连接器类型，\n一般来说，常见于server.xml中的连接器类型通常有4种：\nHTTP连接器 SSL连接器 AJP 1.3连接器 proxy连接器 xml 1 2 3 \u0026lt;Connector port=\u0026#34;8080\u0026#34; protocol=\u0026#34;HTTP/1.1\u0026#34; maxThreads=\u0026#34;150\u0026#34; connectionTimeout=\u0026#34;20000\u0026#34; redirecPort=\u0026#34;8443\u0026#34; /\u0026gt; Connector常见属性 定义连接器时可以配置的属性非常多，单通常定义HTTP连接器时必须定义的属性只有port，定义AJP连接器时必须定义的属性只有protocol，默认的协议为HTTP。\n属性 解释 address 指定连接器监听的地址，默认为所有地址，即0.0.0.0。 maxThread 支持的最大（线程）并发连接数，默认为200。 port 监听的端口，默认为0。 protocol 连接器使用的协议，默认为HTTP/1.1，定义AJP协议时通常为AJP/1.3。 redirectPort 如果某种连接器支持的协议是HTTP，当接收客户端发来的HTTPS请求时，则转发至此属性定义的端口。 connectionTimeout 等待客户端发送请求的超时时间.单位为毫秒，默认为60000，即1分钟。 enableLookups 是否通过request.getRemoteHost()进行DNS直询以获取客户端的主机名；默认为true。 acceptCount 设置等待队列的最大长度：通常在tomcat所有处理线程均处于繁忙时，新发来的请求将被放置于等待队列中。 minSpareThreads 最小空闲进程 下面是定义了多个属性的SSL简介器：\nxml 1 2 3 4 5 \u0026lt;Connector port=\u0026#34;8443\u0026#34; maxThread=\u0026#34;150\u0026#34; minSpareThreads=\u0026#34;25\u0026#34; maxSpareThreads=\u0026#34;75\u0026#34; enableLookups=\u0026#34;false\u0026#34; acceptCount=\u0026#34;100\u0026#34; debug=\u0026#34;0\u0026#34; scheme=\u0026#34;https\u0026#34; secure=\u0026#34;true\u0026#34; clientAuth=\u0026#34;false\u0026#34; sslProtocol=\u0026#34;TLS\u0026#34; /\u0026gt; 引擎(Engine) 引擎是指处理请求的Servlet引擎组件，即Catalina Servlet引擎，它从HTTPconnector接收请求并响应请求。它检查每一个请求的HTTP首部信息以辨别此请求应该发往哪个host或context，并将请求处理后的结果返回的相应的客户端。严格意义上来说，容器不必非得通过引擎来实现，它也可以是只是一个容器。如果Tomcat被配置成为独立服务器，默认引擎就是已经定义好的引擎。而如果Tomcat被配置为Apache Web服务器的提供Servlet功能的后端，默认引擎将被忽略，因为Web服务器自身就能确定将用户请求发往何处。一个引擎可以包含多个host组件。\nEngine组件: Engine是Servlet处理器的一个实例，即servlet引擎，默认为为定义在server.xml中的Catalina。 Engine需要的defaultHost属性来为其定义一个接收所有发往非明确定义虚拟主机的请求host组件。如前面示例中定义的：\nxml 1 \u0026lt;Engine name=\u0026#39;Catalina\u0026#39; defaultHost=\u0026#39;localhost\u0026#39;\u0026gt; Engine容器中可以包含Realm、Host、Listener和Valve子容器\nengine常用属性 属性 说明 defaultHost Tomcat支持基于FQDN的虚拟主机，这些虚拟主机可以在Engine容器中定义多个不同的Host组件来实现；单如果此引擎的连接器收到一个发往非明确定义虚拟主机的请求时则需要将此请求发往一个默认的虚拟主机进行处理，因此，在Engine中定义的多个虚拟主机的主机名称至少要有一个根defaultHost定义的书记名称同名。 name Engine组件的名称，用于日志和错误信息记录时区别不同的引擎。 jvmroute tomcat由众多组件组成，这些组件分别用于实现不同的功能。\nxml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 \u0026lt;server\u0026gt; \u0026lt;service\u0026gt; \u0026lt;connector /\u0026gt; \u0026lt;connector /\u0026gt; ... \u0026lt;engine\u0026gt; \u0026lt;host\u0026gt; \u0026lt;context /\u0026gt; .. \u0026lt;/host\u0026gt; .. \u0026lt;/engine\u0026gt; \u0026lt;/service\u0026gt; \u0026lt;/server\u0026gt; 主机(Host) 主机组件类似于Apache中的 虚拟主机，但在Tomcat中只支持基于FQDN的“虚拟主机”。一个引擎至少要包含一个主机组件。 位于Engine容器中，用于接收请求并进行相应处理的主机或虚拟主机\nxml 1 2 3 4 \u0026lt;Host name=\u0026#39;localhsot\u0026#39; appBase=\u0026#39;webapps\u0026#39; unpackWARs=\u0026#39;true\u0026#39; autoDeploy=\u0026#39;true\u0026#39; xmlValidation=\u0026#39;false\u0026#39; xmlNamespaceAware=\u0026#39;false\u0026#39;\u0026gt; \u0026lt;/Host\u0026gt; 常用属性 属性 说明 appBase 此host的webapps目录，即存放非归档的web应用程序的目录或归档后的WAR文件的目录路径。可以位用基于$CATALINA_HOME的相对路径。 autoDeploy 在Tomcat处于运行状态时放置于appBase目录中的应用程序文件是否自动进行deploy，默认认为true。 unpackWars 在启用此webapp时是否对WAR格式的归的文件先进行展开，默认为true。 虚拟主机定义示例 xml 1 2 3 4 5 6 \u0026lt;Engine name=\u0026#39;Catalina\u0026#39; defaultHost=\u0026#39;localhost\u0026#39;\u0026gt; \u0026lt;Host name=\u0026#39;localhost\u0026#39; appBase=\u0026#39;webapps\u0026#39;\u0026gt; \u0026lt;Context path=\u0026#39;\u0026#39; docBase=\u0026#39;ROOT\u0026#39; /\u0026gt; \u0026lt;Context path=\u0026#39;\u0026#39; docBase=\u0026#39;/web/www\u0026#39; reloadable=\u0026#39;true\u0026#39; crossContext=\u0026#39;true\u0026#39; /\u0026gt; \u0026lt;/Host\u0026gt; \u0026lt;/Engine\u0026gt; 主机别名定义 xml 1 2 3 \u0026lt;Host name=\u0026#39;localhost\u0026#39; appBase=\u0026#39;webapps\u0026#39;\u0026gt; \u0026lt;Alias\u0026gt;web.com\u0026lt;/Alias\u0026gt; \u0026lt;/Host\u0026gt; 上下文(Context) Context组件是 最内层次的组件，它表示Web应用程序本身。配置一个Context最主要的是指定Web应用程序的根目录，以便Servlet容器能够将用户请求发往正确的位置。Context组件也可包含自定义的错误页，以实现在用户访问发生错误时提供友好的提示信息。 context在某些意义上类似于apache中的路径别名，一个Context定义用于表示tomcat实例中的一个Web应用程序。\nxml 1 \u0026lt;Context path=\u0026#34;/web/bbs\u0026#34; docBase=\u0026#34;/data/web\u0026#34; /\u0026gt; 在tomcat6中每一个context定义也可以使用一个单独的XML文件进行，其文件的目录为$CATALINA_HOME/conf/\u0026lt;engine name\u0026gt;/\u0026lt;host name\u0026gt;，可以用于Context中的XML元素有Loader，Manager，Realm，Resources、WatchedResource。\n属性 说明 docBase 相应的web应用程序的存放位置。也可以使用相对路径，起始路径为此Context所属Host中appBase定义的路径。切记，docBase的路径名不能与相应host中appBase中定义的路径名有包含关系。如：如果appBase为deploy，而docBase决不能为deploy-bbs之类的名字。 path 相对与web服务器根路径而言的URI，如果为空\u0026rsquo;\u0026rsquo;，则表示为此webapp的根路径，如果context定义在一个单独的xml文件中，此属性不需要定义。 reloadable 是够允许重新加载此context相关的web应用程序的类，默认为false 被嵌套类(nested)组件 这类组件通常包含于容器类组件中以提供具有管理功能的服务，它们不能包含其它组件，但有些却可以由不同层次的容器各自配置。\n阀门(Valve) 用来拦截请求并在将其转至目标之前进行某种处理操作，类似于Servlet规范中定义的过滤器。Valve可以定义在任何容器类的组件中。Valve常被用来记录客户端请求、客户端IP地址和服务器等信息，这种处理技术通常被称作请求转储(request dumping)。请求转储valve记录请求客户端请求数据包中的HTTP首部信息和cookie信息文件中，响应转储valve则记录响应数据包首部信息和cookie信息至文件中。\nValve（阀门）类似于过滤器，用来拦截请求并在将其转至目标之前进行某种处理操作；它可以工作于Engine和Host/Context之间、Host和Context之间以及Context和Web应用程序的某资源之间。 Valve常被用来记录客户端请求、客户端IP地址和服务器等信息，这种处理技术通常被称作请求转储(request dumping)。请求转储valve记录请求客户端请求数据包中的HTTP首部信息和cookie信息文件中，响应转储valve则记录响应数据包首部信息和cookie信息至文件中。\n一个容器内可以建立多个Valve，而且Valve定义的次序也决定了它们生效的次序。不同类型的Value具有不同的处理能力，Tomcat中实现了多种不同的Valve：\nAccessLogValve：访问日志Valve。 ExtendedAccessValve：扩展功能的访问日志Valve。 JDBCAccessLogValve：通过JDBC将访问日志信息发送到数据库中。 RequestDumperValve：请求转储Valve。 RemoteAddrValve：基于远程地址的访问控制。 RemoteHostValve：基于远程主机名称的访问控制。 SemaphoreValve：用于控制Tomcat主机上任何容器上的并发访问数量。 JvmRouteBinderValve：在配置多个tomcat为以Apache通过mod_proxy或mod_jk作为前段的集群架构中，当期望停止某节点时，可以通过此valve将用记请求定向至备用节点，使用civalve必须使 JvmRouteSessionIDBinderListener。 ReplicationValve：专用于Tomcat集群架构中，可以在某个请求的session信息发生更改时触发session数据在各节点间进行复制。 SingleSignOn：将两个或多个需要对用户进行认证webapp在认证用户时连接在一起，即一次认证即可访问所有连接在一起的webapp。 ClusterSingleSingOn：对SingleSignOn的扩展，专用于Tomcat集群当中，需要结合ClusterSingleSignOnListener进行工作。 RemoteHostValve和RemoteAddrValve可以分别用来实现基于主机名称和基于IP地址的访问控制，控制本身可以通过allow或deny来进行定义，这有点类似与Apache httpd的访问控制功能：如下面的valve则实现了仅允许本机访问/probe。 xml 1 2 3 4 \u0026lt;Context path=\u0026#39;/porbe\u0026#39; docBase=\u0026#39;probe\u0026#39;\u0026gt; \u0026lt;Valve className=\u0026#39;org.apache.catalina.valves.RemoteAddrValve\u0026#39; allow=\u0026#34;127\\.0\\.0\\.1\u0026#34; /\u0026gt; \u0026lt;/Context\u0026gt; 常用相关属性说明 属性 说明 className 相关的java实现的类名，相应于分别应该为org.apache.catalina.valves.RemoteAddrValve allow 以逗号分开的允许访问的IP地址列表，支持正则表达式，因此。点号“.”用于IP地址时需要转义，仅定义allow项时，非明企鹅allow的地址均被deny。 deny 以逗号分开的禁止访问的IP地址列表，支持正则表达式。 日志记录器(Logger) 用于记录组件内部的状态信息，可被用于除Context之外的任何容器中。日志记录的功能可被继承，因此，一个引擎级别的Logger将会记录引擎内部所有组件相关的信息，除非某内部组件定义了自己的Logger组件。\n领域(Realm) Realm（领域）表示分配给这些用户的用户名，密码和角色（类似于Unix组）的\u0026quot;数据库\u0026quot;。一个Realm（领域）表示一个安全上下文，它是一个授权访问某个给定Context的用户列表和某用户所允许切换的角色相关定义的列表。因此Realm就像是一个用户和组相关的数据库。定义Realm是唯一必须要提供的是classname，它是Realm的国歌 LockOutRealm：提供锁定功能，以便在给定时间段内出现过多的失败认证尝试时提供用户锁定机制；\nJAASRealm：基于Java Authintication and Authorization Service实现用户认证。 JDBCRealm：通过JDBC访问某关系型数据库表实现用户认证。 JNDIRealm：基于JNDI使用目录服务实现认证信息的获取。 MemoryRealm：超找tomcat-user.xml文件实现用户信息的获取。 UserDatabaseRealm：基于UserDatabase文件(通常是tomcat-user.xml)实现用户认证，它实现是一 完全可更新和持久有效的MemoryRealm，因此能够跟标准的MemoryRealm兼容；它通过JNDI实现； 使用JDBC方式获取用户认证信息的配置 xml 1 2 3 4 5 6 7 8 9 10 \u0026lt;Ream className=\u0026#34;org.apache.catalina.realm.UserDatabaseRealm\u0026#34; resourceName=\u0026#34;UserDatabase\u0026#34; /\u0026gt; \u0026lt;!-- 使用UserDatabase的配置 --\u0026gt; \u0026lt;Ream className=\u0026#34;org.apache.catalina.realm.UserDatabaseRealm\u0026#34; debug=\u0026#34;99\u0026#34; driverName=\u0026#34;org.gjt.mm.mysql.Driver\u0026#34; connectionUrl=\u0026#34;jdbc:mysql//localhost/authority\u0026#34; userTable=\u0026#34;test\u0026#34; userNameCol=\u0026#34;user_name\u0026#34; userCredCol=\u0026#34;user_pass\u0026#34; userRoleTable=\u0026#34;user_roles\u0026#34; roleNameCol=\u0026#34;role_name\u0026#34; /\u0026gt; tomcat的运行模式 standalone 通过内置的web server(http connector)来接受客户端请求；\nproxy 由专门的web server服务客户端的http请求；\nin-process：不属于同一主机；\nnetwork：不属于不同主机\n每一次访问jsp文件时，jsp文件会被编译成字节码存放到磁盘，下次访问时直接加载字节码不会再访问了。\nserver.xml：主配置文件\ncontext.xml：每个webapp都可以有专用的配置文件，这些配置文件通常位于webapp应用程序目录下的WEB-INF目录中，用于定义会话管理器、JDBC等：conf/context.xml是为各webapp提供默认部署相关的配置；\ntomcat-user.xml用户认证的账号和密码配置文件。\ncatalina.policy：当使用-security选项启动tomcat实例时，会读取此配置文件来实现其安全运行策略；\ncatalina.properties：java属性的定义文件，用于设定类加载器路径等，以及一些JVM性能相关的调优参数；\nlogging.properties：日志相关的配置信息；\nTomcat连接器架构 基于Apache做为Tomcat前端的架构来讲，Apache通过mod_jk、mod_jk2或mod_proxy模块与后端的Tomcat进行数据交换。而对Tomcat来说，每个Web容器实例都有一个Java语言开发的连接器模块组件，在Tomcat6中，这个连接器是org.apache.catalina.Connector类。这个类的构造器可以构造两种类别的连接器：HTTP/1.1负责响应基于HTTP/HTTPS协议的请求，AJP/1.3负责响应基于AJP的请求。但可以简单地通过在server.xml配置文件中实现连接器的创建，但创建时所使用的类根据系统是支持APR(Apache Portable Runtime)而有所不同。\nAPR是附加在提供了通用和标准API的操作系统之上一个通讯层的本地库的集合，它能够为使用了APR的应用程序在与Apache通信时提供较好伸缩能力时带去平衡效用。\n同时，需要说明的是，mod_jk2模块目前已经不再被支持了，mod_jk模块目前还apache被支持，但其项目活跃度已经大大降低。因此，目前更常用 的方式是使用mod_proxy模块。\n如果支持APR：\nxml 1 2 HTTP/1.1：org.apache.cotote.http11.Http11AprProtocol AJP/1.3：org.apache.coyote.ajp.AjpAprProtocol 如果不支持APR：\nxml 1 2 HTTP/1.1: org.apache.coyote.http11.Http11Protocol AJP/1.3: org.apache.jk.server.JkCoyoteHandler 连接器协议：\nTomcat的Web服务器连接器支持两种协议：AJP和HTTP，它们均定义了以二进制格式在Web服务器和Tomcat之间进行数据传输，并提供相应的控制命令。\nAJP(Apache JServ Protocol)协议：目前正在使用的AJP协议的版本是通过JK和JK2连接器提供支持的AJP13，它基于二进制的格式在Web服务器和Tomcat之间传输数据，而此前的版本AJP10和AJP11则使用文本格式传输数据。\nHTTP协议：诚如其名称所表示，其是使用HTTP或HTTPS协议在Web服务器和Tomcat之间建立通信，此时，Tomcat就是一个完全功能的HTTP服务器，它需要监听在某端口上以接收来自于商前服务器的请求。\n下载安装tomcat Tomcat的官方站点\nhttp://tomcat.apache.org/\n要安装Tomcat，首先需要安装JDK。\njdk下载地址\nhttp://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html\n加载环境变量\nbash 1 2 3 4 cat \u0026gt;/etc/profile.d/java.sh \u0026lt;\u0026lt;EOF JAVA_HOME=/app/jdk export PATH=$JAVA_HOME/bin:$PATH EOF 查看java版本\ntext 1 2 3 4 $ java -version java version \u0026#34;1.8.0_144\u0026#34; Java(TM) SE Runtime Environment (build 1.8.0_144-b01) Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode) yum安装jdk 此方法不需要对path设置\ntext 1 yum install java-1.8.0-openjdk* -y 部署tomcat 官方网站：http://tomcat.apache.org/\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 $ tree /app/tomcat-8.5.16/ -L 1 /app/tomcat-8.5.16/ ├── bin ├── conf ├── lib ├── LICENSE ├── logs ├── NOTICE ├── RELEASE-NOTES ├── RUNNING.txt ├── temp ├── webapps └── work text 1 /app/tomcat-8.5.16/bin/startup.sh tomcat配置 tomcat的目录结构 bin：脚本及启动时用到的类\nlib：类库\nconf：配置文件\nlogs：日志文件\nwebapps：应用程序默认部署目录\nwork：工作目录\ntemp：临时文件目录\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ tree -L 1 /app/tomcat/ /app/tomcat/ ├── bin #\u0026lt;==脚本及启动时用到的类 ├── conf #\u0026lt;==配置文件 │ ├── Catalina │ ├── catalina.policy │ ├── catalina.properties #\u0026lt;==当使用-security选项启动tomcat实例时，会读取此配置文件来实现其安全运行策略； │ ├── context.xml #\u0026lt;== 每个webapp都可以有专用的配置文件，这些配置文件通常位于webapp应用程序目录下的WEB-INF目录中，用于定义会话管理器、JDBC等：conf/context.xml是为各webapp提供默认部署相关的配置 │ ├── logging.properties #\u0026lt;==日志相关的配置信息； │ ├── server.xml #\u0026lt;==主配置文件 │ ├── tomcat-users.xml #\u0026lt;==用户认证的账号和密码配置文件 │ └── web.xml ├── lib #\u0026lt;==类库 ├── logs #\u0026lt;==日志文件 ├── temp #\u0026lt;==临时文件目录 ├── webapps #\u0026lt;==应用程序默认部署目录 └── work #\u0026lt;==工作目录 java webapp组织结构 有特定的组织形式、层次型的目录结构：主要包含了servlet代码文件、JSP页面文件、类文件、部署描述符文件等；\n/usr/local/tomcat/webapps/app1/\n/webapp的根目录\nWEB-INF：当前webapp的私有资源目录，通常存放当前webapp自用的 web.xml\nMETA-INF：当前webapp的私有资源目录，通常存放当前webapp自用的 context.xml\nclasses：此webapp的私有类\nlib：此webapp的私有类，被大包围jar格式类。\nindex.jsp：webapp的主页\nwebapp归档格式：\n.war：webapp .jar：EJB的类 .rar：资源适配器 .ear：企业级应用程序 手动部署java程序 在webapp下创建特定格式的子目录。每个应用程序都会自动从tomcat conf目录下去读取web.xml和context.xml。如果没有提供默认的，就会从tomcat上继承一个公共的。如想对默认配置进行修改，建议复制web.xml到WEB-INF，context.xml到MATE-INF下修改专有配置。不会对全局进行影响。\ntext 1 mkdir -pv app/{lib,classes,WEB-INF,META-INF} jsp \u0026lt;%@ page language=\u0026#34;java\u0026#34; %\u0026gt;\r\u0026lt;%@ page import=\u0026#34;java.util.*\u0026#34; %\u0026gt;\r\u0026lt;html\u0026gt;\r\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;JSP Test Page\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt;\r\u0026lt;body\u0026gt;\u0026lt;% out.println(\u0026#34;Hellow, world.\u0026#34;); %\u0026gt;\u0026lt;/body\u0026gt;\r\u0026lt;html\u0026gt; java程序在运行时，index.jsp文档首先会被转为java文件。最后编译成class文件。这些编译结果会保存在java（tomcat）当中的work子目录。\n为了能使每一个类都是全局唯一的，所以每个公司所开发的java类（可能被公共调用的），都以自己公司的域名倒过来写 org.apache.jsp。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ tree /app/tomcat/work/ /app/tomcat/work/ └── Catalina └── localhost ├── app │ └── org │ └── apache │ └── jsp │ ├── index_jsp.class #\u0026lt;==最终被编译成java类，最终运行的 │ └── index_jsp.java #\u0026lt;==这是servlet文件，纯java编码 └── ROOT └── org └── apache └── jsp ├── index_jsp.class └── index_jsp.jav 部署 部署(deployment) webapp相关的操作：\ndeploy:部署，将webapp的源文件放置于目标目录、配置tomcat服务器能够基于context.xml文件中定义的路径来访问此webapp；将其特有类通过class loader装载至tomcat\ntomcat部署的两种方式\n自动部署autodeploy 手动部署 冷部署：把webapp复制到指定位置，而后才启动tomcat。\n热部署：在不停止tomcat的前提下进行的部分。需要通过部署工具来进行。常见的部署工具有 manager、ant脚本、tcd（tomcat client deployer）等。\nundeploy：反部署，停止webapp，并从tomcat实例拆除其部分文件和部署名。\nstop：停止，不再想用户提供服务。\nstart：启动处于“停止”状态的webapp\nredeploy：重新部署\ntomcat安全规范 telnet管理端口保护 修改默认的8005管理端口不易猜疑的端口（大于1024）或改为-1关闭端口。\n修改SHUTDOWN指令为其他字符串\nxml 1 \u0026lt;Server port=\u0026#39;-1\u0026#39; shutdown=\u0026#39;SHUTDOWN\u0026#39;\u0026gt; AJP连接端口保护 1、修改默认的ajp 8009端口为不易冲突的大于1024端口。\n2、通过iptables规则限制ajp端口访问的权限仅为线上机器。保护此端口的目的在于防止线下测试流量被mod_jk转发至线上tomcat服务器\nxml 1 \u0026lt;Connector port=\u0026#34;8528\u0026#34; protocol=\u0026#34;AJP/1.3\u0026#34; /\u0026gt; 禁用管理端 删除默认的tomcat安装目录 /conf/tomcat-user.xml文件。重启tomcat后将会自动生成新的文件。 删除tomcat目录webapps下默认的所有目录和文件 将tomcat应用根目录配置为tomcat安装目录以外的目录。 降权启动 tomcat启动用户权限必须为非root权限，尽量降低tomcat启动用户的目录访问权限；如果直接对外使用80端口，可通过普通账号启动后配置iptables规则进行转发。避免一旦tomcat服务被入侵，黑客直接获取高级用户权限危害整个server的安全。\n版本信息隐藏 修改 conf/web.xml，重定向403 404及500等错误指定的错误页面。 可以通过修改应用程序目录下的 WEB-INF/web.xml 下的配置进行错误页面的重定向。 xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 \u0026lt;error-page\u0026gt; \u0026lt;error-code\u0026gt;403\u0026lt;/error-code\u0026gt; \u0026lt;location\u0026gt;/forbidden.jsp\u0026lt;/location\u0026gt; \u0026lt;/error-page\u0026gt; \u0026lt;error-page\u0026gt; \u0026lt;error-code\u0026gt;404\u0026lt;/error-code\u0026gt; \u0026lt;location\u0026gt;/notfound.jsp\u0026lt;/location\u0026gt; \u0026lt;/error-page\u0026gt; \u0026lt;error-page\u0026gt; \u0026lt;error-code\u0026gt;500\u0026lt;/error-code\u0026gt; \u0026lt;location\u0026gt;/systembusy.jsp\u0026lt;/location\u0026gt; \u0026lt;/error-page\u0026gt; 在配置中对一些常见错误进行重定向，避免当出现错误时tomcat默认显示的错误页面暴露服务器和版本信息，必须确保程序很目录下的错误页面已经存在。\nserver header重写 在HTTP Connector配置中加入server的配置 server=\u0026quot;webserver\u0026quot; 。当tomcat HTTP端口直接提供web服务时此配置生效，加入此配置，将会替换http响应Server header部分的默认配配置，默认是 Apache-Coyote/1.1\n访问限制 通过配置限定访问的IP来源。配置信任IP的白名单，拒绝非白名单ip的访间。此配置主要是针对高 保密级别的系统。一般产品线不需要。\nxml 1 2 3 \u0026lt;Context path=\u0026#34;\u0026#34; docBasse=\u0026#34;/home/work/tomcat\u0026#34; debug=\u0026#34;0\u0026#34; reloadable=\u0026#34;false\u0026#34; crossContext=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;Valve className=\u0026#34;org.apache.catalina.valves.RemoteAddrValue\u0026#34; allow=\u0026#34;10.0.0.2,10.0.0.3\u0026#34; deny=\u0026#34;*.*.*.*\u0026#34; /\u0026gt; \u0026lt;/Context\u0026gt; 启动停止脚本权限回收 去除其他用户对tomcat的bin目录下 shutdown.sh、startup.sh、catalina.sh 的可执行权限。防止其他用户有上线的其他权限。\ntext 1 chmod -R 744 /app/tomcat/bin/* 访问日志规范格式 开启tomcat默认访间日志中的Referer和User-Agent记录\nxml 1 2 3 4 5 \u0026lt;Valve className=\u0026#34;org.apache.catalina.valvesAccessLogValve\u0026#34; directory=\u0026#34;logs\u0026#34; prefix=\u0026#34;localhost_access_log.\u0026#34; suffix=\u0026#34;.txt\u0026#34; pattern=\u0026#34;%h %l %u %t %r %s %b %(Referer}i %{User-Agent)i %D\u0026#34; resolveHosts=\u0026#34;false\u0026#34; /\u0026gt; 文件列表访问控制 $CATALINA_HOME/conf/web.xml或WEB-INF/web.xml 文件中default部分 listings 的配置必须为 ==false==。false为不列出目录列表，true为列出。默认false。\nxml 1 2 3 4 \u0026lt;init-param\u0026gt; \u0026lt;param-name\u0026gt;listings\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;false\u0026lt;/param-value\u0026gt; \u0026lt;/init-param\u0026gt; tomcat 应用程序 manager app webapp管理工具\nmanager配置 首先编辑/app/tomcat/conf/tomcat-users.xml文件，改完后不会立即生效，tomcat在启动时，将读取配置文件到内存中。需要重读授权表或重启。\ntext 1 2 \u0026lt;role rolename=\u0026#34;manager-gui\u0026#34;/\u0026gt; \u0026lt;user username=\u0026#34;admin\u0026#34; password=\u0026#34;1\u0026#34; roles=\u0026#34;manager-gui\u0026#34; /\u0026gt; ⚠ 请注意，对于Tomcat manager而言，需要分配您希望访问的功能所需的角色。 manager-gui：允许访问HTML GUI和状态页面（图形接口）\nmanager-script：允许访问文本界面和状态页面（命令行接口）\nmanager-jmx：允许访问JMX代理和状态页面（JMX用来远程监控java程序的监控接口）\nmanager-status：仅允许访问状态页面（JAVA虚拟机各种状态信息。只读，无法进行管理）\ntomcat8 403 text 1 2 3 4 5 \u0026lt;Context privileged=\u0026#34;true\u0026#34; antiResourceLocking=\u0026#34;false\u0026#34; docBase=\u0026#34;${catalina.home}/webapps/manager\u0026#34;\u0026gt; \u0026lt;Valve className=\u0026#34;org.apache.catalina.valves.RemoteAddrValve\u0026#34; allow=\u0026#34;^.*$\u0026#34; \\/\u0026gt; \u0026lt;/Context\u0026gt; host manager Virtual Hosts管理工具\ntext 1 2 3 44 \u0026lt;role rolename=\u0026#34;manager-gui\u0026#34;/\u0026gt; 45 \u0026lt;role rolename=\u0026#34;admin-gui\u0026#34; /\u0026gt; 46 \u0026lt;user username=\u0026#34;admin\u0026#34; password=\u0026#34;1\u0026#34; roles=\u0026#34;manager-gui,admin-gui\u0026#34;/\u0026gt; tomcat8 403 text 1 2 3 4 5 6 \u0026lt;Context antiResourceLocking=\u0026#34;false\u0026#34; privileged=\u0026#34;true\u0026#34; \u0026gt; \u0026lt;Valve className=\u0026#34;org.apache.catalina.valves.RemoteAddrValve\u0026#34; allow=\u0026#34;10.0.0.*\u0026#34; /\u0026gt; \u0026lt;Manager sessionAttributeValueClassNameFilter=\u0026#34;java\\.lang\\. (?:Boolean|Integer|Long|Number|String)|org\\.apache\\.catalina\\.filters\\.CsrfPreventionFilter\\$LruCache(?:\\$1)?|java\\.util\\.(?:Linked)?HashMap\u0026#34;/\u0026gt; \u0026lt;/Context\u0026gt; Server Status manager子组件\napache反向代理tomcat lamt client \u0026ndash;\u0026gt; http \u0026ndash;\u0026gt; httpd \u0026ndash;\u0026gt; reverse_proxy \u0026ndash;\u0026gt; http ajp \u0026ndash;\u0026gt; tomcat {http connector ajp connector}\nhttpd反代模块\n主：proxy_module 子：proxy_module_http proxy_module_ajp。 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 \u0026lt;VirtualHost *:80\u0026gt; DocumentRoot \u0026#34;/app/apache-2.4.28/docs/www\u0026#34; ServerName www.test.com ServerAlias test.com ErrorLog \u0026#34;logs/www.com-error_log\u0026#34; CustomLog \u0026#34;logs/www.com-access_log\u0026#34; common ProxyVia On ProxyRequests Off ProxyPreserveHost On \u0026lt;Proxy *\u0026gt; require all granted \u0026lt;/Proxy\u0026gt; ProxyPass / http://10.0.0.5:8080/ ProxyPassReverse / http://10.0.0.5:8080/ \u0026lt;/VirtualHost\u0026gt; ajp\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 \u0026lt;VirtualHost *:80\u0026gt; DocumentRoot \u0026#34;/app/apache-2.4.28/docs/www\u0026#34; ServerName www.test.com ServerAlias test.com ErrorLog \u0026#34;logs/www.com-error_log\u0026#34; CustomLog \u0026#34;logs/www.com-access_log\u0026#34; common ProxyVia On ProxyRequests Off ProxyPreserveHost On \u0026lt;Proxy *\u0026gt; require all granted \u0026lt;/Proxy\u0026gt; ProxyPass /index.php ! ProxyPass / ajp://10.0.0.5:8009/ ProxyPassReverse / ajp://10.0.0.5:8009/ \u0026lt;/VirtualHost\u0026gt; ","permalink":"https://www.161616.top/tomcat/","summary":"tomcat介绍 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器，Tomcat是Apache 软件基金会（Apache Software Foundation）的Jakarta 项目中的一个核心项目，它早期的名称为catalina，后来由Apache、Sun 和其他一些公司及个人共同开发而成，并更名为Tomcat。\njava体系结构 java程序设计语言（编程） java类文件格式 java api java vm java 纯面向对象语言\nobj：以指令为中心，围绕指令组织数据\n面向对象：以数据为中心，围绕数据组织指令\nTomcat核心组件：\nCatalina：servlet container在tomcat中用于实现servlet代码的被称作Catalina Coyote：一个http连接器，能够接受http请求并相应http请求的web服务器 jasper：JSP Engine jsp翻译器 Tomcat组成部分 Tomcat支持Servlet和JSP1的规范，它由一组嵌套的层次和组件组成，一般可分为以下四类：\n顶级组件：位于配置层次的顶级，并且彼此间有着严格的对应关系； 连接器：连接客户端（可以是浏览器或Web服务器）请求至Servlet容器， 容器：包含一组其它组件； 被嵌套的组件：位于一个容器当中，但不能包含其它组件； tomcat常见组件 服务器(server) 服务器(server)顶级组件：Tomcat的一个实例，通常一个JVM只能包含一个Tomcat实例；因此，一台物理服务器上可以在启动多个JVM的情况下在每一个JVM中启动一个Tomcat实例，每个实例分属于一个独立的管理端口。这是一个顶级组件。\nserver相关属性 className：用于实现此Server容器的完全限定类的名称，默认为 org.apache.cataliana.core.StandardServer;\nport：接收shutdown指令的端口，默认仅允许通过本机访问，默认为8005\nshutdown：发往此Server用于实现关闭tomcat实例的命令字符串，默认为SHUTDOWN\n服务(service) Service主要用于关联一个引擎和此引擎相关的连接器，每个连接器通过一个特定的端口和协议接收入站请求将其转发至关联的引擎进行处理，因此，Service要包含一个引擎（Engine）、一个或多个连接器（Connector）。 定义一个名为Catalina的Service，此名字也会产生相关的日志信息时记录在日志文件当中。\nxml 1 \u0026lt;Service name=\u0026#34;Catalina\u0026#34;\u0026gt; Service相关属性 className：用于实现service的类名，一般都是 org.apache.catalina.core.StandardService\nname：此服务的名称，默认为Catalina。\n连接器(connectors) 分析并接收用户请求，并把它转换成本地jsp文件代码的请求。负责连接客户端（可以是浏览器或Web服务器）请求至Servlet容器内的Web应用程序，通常指的是接收客户发来请求的位置及服务器端分配的端口。 默认端口通常是HTTP协议的8080，管理员也可以根据自己的需要改变此端口。一个引擎可以配置多个连接器，但这些连接器必须使用不同的端口。默认的连接器是基于HTTP/1.1的Coyote。同时，Tomcat也支持AJP、JServ和JK2连接器。\n进入Tomcat的请求可以根据Tomcat的工作模式分为如下两类：\nTomcat作为应用程序服务器：请求来自于前端的web服务器，这可能是Apache IIS Nginx。\nTomcat作为独立服务器：请求来自与web浏览器。\nTomcat应该考虑工作情形并为相应情形下的请求分别定义好需要的连接器才能正确接收来自于客户端的请求，一个引擎可以有一个或多个连接器，以适应多种请求方式。\n定义连接器可以使用多种属性，有些属性也只是适用于某种特定的连接器类型，\n一般来说，常见于server.xml中的连接器类型通常有4种：\nHTTP连接器 SSL连接器 AJP 1.3连接器 proxy连接器 xml 1 2 3 \u0026lt;Connector port=\u0026#34;8080\u0026#34; protocol=\u0026#34;HTTP/1.","title":"tomcat使用"},{"content":"修改logging.properties text 1 /usr/local/apache-tomcat-8.5.32/conf/logging.properties bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ############################################################ # Handler specific properties. # Describes specific configuration info for Handlers. ############################################################ 1catalina.org.apache.juli.AsyncFileHandler.level = FINE 1catalina.org.apache.juli.AsyncFileHandler.directory = /data/logs 1catalina.org.apache.juli.AsyncFileHandler.prefix = catalina. 2localhost.org.apache.juli.AsyncFileHandler.level = FINE 2localhost.org.apache.juli.AsyncFileHandler.directory = /data/logs 2localhost.org.apache.juli.AsyncFileHandler.prefix = localhost. 3manager.org.apache.juli.AsyncFileHandler.level = FINE 3manager.org.apache.juli.AsyncFileHandler.directory = /data/logs 3manager.org.apache.juli.AsyncFileHandler.prefix = manager. 4host-manager.org.apache.juli.AsyncFileHandler.level = FINE 4host-manager.org.apache.juli.AsyncFileHandler.directory = /data/logs 4host-manager.org.apache.juli.AsyncFileHandler.prefix = host-manager. java.util.logging.ConsoleHandler.level = FINE java.util.logging.ConsoleHandler.formatter = org.apache.juli.OneLineFormatter 替换命令\nbash 1 2 :%s#${catalina.base}/logs#/data/logs#g sed -i s#${catalina.base}/logs#/data/logs#g /usr/local/apache-tomcat-8.5.32/conf/logging.properties 修改catalina.sh bash 1 2 3 CATALINA_OUT=/data/logs/catalina.out JAVA_HOME=/usr/java/latest JAVA_OPTS=\u0026#34;${JAVA_OPTS} -Xms4096m -Xmx4096m -Xmn1024m -Xss1024K -XX:PermSize=4096m -XX:MaxPermSize=4096m\u0026#34; 增加如下：\ntext 1 CATALINA_OUT=/data/logs/catalina.out bash 1 2 org.apache.catalina.startup.Bootstrap \u0026#34;$@\u0026#34; start 2\u0026gt;\u0026amp;1 \\ | /usr/sbin/cronolog /data/logs/catalina.%Y-%m-%d.out \u0026gt;\u0026gt; /dev/null \u0026amp; ","permalink":"https://www.161616.top/tomcat-log-path/","summary":"修改logging.properties text 1 /usr/local/apache-tomcat-8.5.32/conf/logging.properties bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ############################################################ # Handler specific properties. # Describes specific configuration info for Handlers. ############################################################ 1catalina.org.apache.juli.AsyncFileHandler.level = FINE 1catalina.org.apache.juli.AsyncFileHandler.directory = /data/logs 1catalina.org.apache.juli.AsyncFileHandler.prefix = catalina. 2localhost.org.apache.juli.AsyncFileHandler.level = FINE 2localhost.org.apache.juli.AsyncFileHandler.directory = /data/logs 2localhost.org.apache.juli.AsyncFileHandler.prefix = localhost. 3manager.org.apache.juli.AsyncFileHandler.level = FINE 3manager.org.apache.juli.AsyncFileHandler.directory = /data/logs 3manager.org.apache.juli.AsyncFileHandler.prefix = manager. 4host-manager.org.apache.juli.AsyncFileHandler.level = FINE 4host-manager.","title":"tomcat修改日志目录"},{"content":"默认情况下，容器没有任何资源限制，因此几乎耗尽docker主机之上，内核可分配给当前容器的所有资源。可以使用主机内核调度程序允许的尽可能多的给定资源。在此基础上Docker provides提供了控制容器可以使用多少内存，CPU或块IO的方法，设置docker run命令的运行时配置标志。\n容器得以实现主要依赖于内核中的两个属性namespace cgroup。其中许多功能都要求您的内核支持Linux功能。要检查支持，可以使用docker info命令。\nMemory OOME\n在Linux主机上，如果内核检测到没有足够的内存来执行重要的系统功能，它会抛出OOME或Out of Memory Exception异常，并开始终止进程以释放内存资源。一旦发生OOME，任何进程都有可能被杀死，包括docker daemon自身在内。为此，Docker特地调整了docker daemon的OOM优选级，以免它被内核“正法”，但容器的优选级并未被调整。\n工作逻辑为\n在宿主机上跑有很多容器并包括系统级进程。系统级进程也包括docke daemon自身。当内核执行系统管理操作，如内核需要使用内存，发现可以内存已经为空，会启动评估操作，评估谁占用内存高。我们认为哪个资源占用内存高就该将其kill来释放内存空间。（需要注意的是占用内存高的进程也不一定被kill掉。A进程分配10G已使用5G，进程B分配1G已使用1G。A只使用50%内存，而B已经耗尽所有内存）。内核会提供这些进程进行评分，按照优先级逆序强制kill，直至可使用内存空间足够。此时内核就可以使用内存资源创建其他进程。\n每一个进程被计算之后会有一个oom scores，得分越高就会被优先kill。得分是由内存申请分配空间等一系列复杂计算得知。当进程得分最高也不能被kill掉时，如docker daemon，此时需要调整优先级。每一个进程有一个oom.adj的参数，将优先级调整越低，计算的分数就越少。\n在docker run时可以直接调整容器的OOM.adj参数。如果想限制容器能使用多少内存资源、或CPU资源，有专门的选项可以实现。非常重要的容器化应用需要在启动容器时调整其OOM.adj，还可以定义容器的策略，一旦被kill直接restart\nLimit a container\u0026rsquo;s resources\n限制一个容器能使用多少内存资源或CPU资源docker有专门的选项来实现\n-m 限制容器可用RAM空间。选项参数可以使用KB M G等作为接受单位使用。可单独使用。\n--memory-swap 设置容器可用交换分区大小。使用swap允许容器在容器耗尽可用的所有RAM时将多余的内存需求写入磁盘。--memory-swap是一个修饰符标志，只有在设置了\u0026ndash;memorys时才有意义。\n--memory-swap\n--memory-swap --memory 功能 正数S 正数 M 容器可用总空间为S，其中可用ram为M 0 正数 M相当于未设置swap（unset） unset（未设置） 正数 M 若主机（Docker Host）启用了swap，则容器的可用swap为 2*M -1 正数M 若主机（Docker Host）启用了swap，则容器可使用交换分区总空间大小为宿主机上的所有swap空间的swap资源 注意：在容器内使用free命令可以看到的swap空间并不具有其所展现出的空间指示意义。 \u0026ndash;memory-swappiness\n用来限定容器使用交换分区的倾向性。\n\u0026ndash;memory-reservation\n预留的内存空间\n\u0026ndash;oom-kill-disable\n禁止oom被kill掉\n默认情况下，每个容器对主机CPU周期的访问权限是不受限制的。可以设置各种约束来限制给定容器访问主机的CPU周期。大多数用户使用和配置默认CFS调度程序。在Docker 1.13及更高版本中，还可以配置实时调度程序。 CPU Limit a container\u0026rsquo;s resources\n内核中进程管理子系统当中最重要的组件为进程角度器scheduler，非实时优先级,有效范围为100-139[-20,19]。因此每个进程的默认优先级为120。实时优先级0-99。调度100-139之间的进程有个非常重要的调度器CFS scheduler（完全公平调度器），公平调度每一个进程在需要执行时，去分配scores到这个进程上。\n在各容器之间分配CPU资源选项：\n参数 说明 \u0026ndash;cpu-shares 限制CPU使用个数的参数按比例切分当前系统上可用cpu资源。\n例如：当前系统上运行2各容器，第一个为1024，第二个为512。这两个容器都尽可能多个使用CPU，会将CPU资源分3份，1024占2份，第二个容器占1份。可随时按比例调整CPU资源。 \u0026ndash;cups 指定容器可以使用的可用CPU资源量。例如，如果主机有两个CPU并且已设置\u0026ndash;cpus=\u0026ldquo;1.5\u0026rdquo;，则容器最多保证1.5个CPU。例如：4核CPU，4个使用总量为1.5而不是0使用100%，1使用50%。 \u0026ndash;cpuset-cpus 限制CPU使用范围的参数。限制容器可以使用的特定CPU或核心。当有多个CPU，则容器可以使用的以逗号分隔的列表或连字符分隔的CPU范围。第一个CPU编号为0.有效值可能是0-3 1,3使用第二个和第四个CPU。 docker pull lorel/docker-stress-ng\n","permalink":"https://www.161616.top/container-limit/","summary":"默认情况下，容器没有任何资源限制，因此几乎耗尽docker主机之上，内核可分配给当前容器的所有资源。可以使用主机内核调度程序允许的尽可能多的给定资源。在此基础上Docker provides提供了控制容器可以使用多少内存，CPU或块IO的方法，设置docker run命令的运行时配置标志。\n容器得以实现主要依赖于内核中的两个属性namespace cgroup。其中许多功能都要求您的内核支持Linux功能。要检查支持，可以使用docker info命令。\nMemory OOME\n在Linux主机上，如果内核检测到没有足够的内存来执行重要的系统功能，它会抛出OOME或Out of Memory Exception异常，并开始终止进程以释放内存资源。一旦发生OOME，任何进程都有可能被杀死，包括docker daemon自身在内。为此，Docker特地调整了docker daemon的OOM优选级，以免它被内核“正法”，但容器的优选级并未被调整。\n工作逻辑为\n在宿主机上跑有很多容器并包括系统级进程。系统级进程也包括docke daemon自身。当内核执行系统管理操作，如内核需要使用内存，发现可以内存已经为空，会启动评估操作，评估谁占用内存高。我们认为哪个资源占用内存高就该将其kill来释放内存空间。（需要注意的是占用内存高的进程也不一定被kill掉。A进程分配10G已使用5G，进程B分配1G已使用1G。A只使用50%内存，而B已经耗尽所有内存）。内核会提供这些进程进行评分，按照优先级逆序强制kill，直至可使用内存空间足够。此时内核就可以使用内存资源创建其他进程。\n每一个进程被计算之后会有一个oom scores，得分越高就会被优先kill。得分是由内存申请分配空间等一系列复杂计算得知。当进程得分最高也不能被kill掉时，如docker daemon，此时需要调整优先级。每一个进程有一个oom.adj的参数，将优先级调整越低，计算的分数就越少。\n在docker run时可以直接调整容器的OOM.adj参数。如果想限制容器能使用多少内存资源、或CPU资源，有专门的选项可以实现。非常重要的容器化应用需要在启动容器时调整其OOM.adj，还可以定义容器的策略，一旦被kill直接restart\nLimit a container\u0026rsquo;s resources\n限制一个容器能使用多少内存资源或CPU资源docker有专门的选项来实现\n-m 限制容器可用RAM空间。选项参数可以使用KB M G等作为接受单位使用。可单独使用。\n--memory-swap 设置容器可用交换分区大小。使用swap允许容器在容器耗尽可用的所有RAM时将多余的内存需求写入磁盘。--memory-swap是一个修饰符标志，只有在设置了\u0026ndash;memorys时才有意义。\n--memory-swap\n--memory-swap --memory 功能 正数S 正数 M 容器可用总空间为S，其中可用ram为M 0 正数 M相当于未设置swap（unset） unset（未设置） 正数 M 若主机（Docker Host）启用了swap，则容器的可用swap为 2*M -1 正数M 若主机（Docker Host）启用了swap，则容器可使用交换分区总空间大小为宿主机上的所有swap空间的swap资源 注意：在容器内使用free命令可以看到的swap空间并不具有其所展现出的空间指示意义。 \u0026ndash;memory-swappiness\n用来限定容器使用交换分区的倾向性。\n\u0026ndash;memory-reservation\n预留的内存空间\n\u0026ndash;oom-kill-disable\n禁止oom被kill掉\n默认情况下，每个容器对主机CPU周期的访问权限是不受限制的。可以设置各种约束来限制给定容器访问主机的CPU周期。大多数用户使用和配置默认CFS调度程序。在Docker 1.13及更高版本中，还可以配置实时调度程序。 CPU Limit a container\u0026rsquo;s resources\n内核中进程管理子系统当中最重要的组件为进程角度器scheduler，非实时优先级,有效范围为100-139[-20,19]。因此每个进程的默认优先级为120。实时优先级0-99。调度100-139之间的进程有个非常重要的调度器CFS scheduler（完全公平调度器），公平调度每一个进程在需要执行时，去分配scores到这个进程上。","title":"容器的资源限制"},{"content":"项目地址：https://github.com/weaveworks/weave\n注：weave公司与2024年关门\nweaves说明 Weave是由weaveworks公司开发的解决Docker跨主机网络的解决方案，它能够创建一个虚拟网络，用于连接部署在多台主机上的Docker容器，这样容器就像被接入了同一个网络交换机，那些使用网络的应用程序不必去配置端口映射和链接等信息。\n外部设备能够访问Weave网络上的应用程序容器所提供的服务，同时已有的内部系统也能够暴露到应用程序容器上。Weave能够穿透防火墙并运行在部分连接的网络上，另外，Weave的通信支持加密，所以用户可以从一个不受信任的网络连接到主机。\nweaves实现原理 weave launch初始化时会自动下载三个docker容器来辅助运行，并且创建linux网桥与docker网络\nweave 运行了三个容器：\nweave 是主程序，负责建立weave网络，收发数据，提供 DNS 服务等。 weavevolumes容器提供卷存储 weavedb容器提供数据存储 sh 1 2 3 4 5 6 $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE weaveworks/weavedb latest 15c78a9b1895 4 weeks ago 698B weaveworks/weaveexec 2.4.0 bf0c403ea58d 4 weeks ago 151MB weaveworks/weave 2.4.0 7aa67bc6bc43 4 weeks ago 96.7MB 自动创建网桥\nsh 1 2 3 4 5 $ brctl show bridge name\tbridge id\tSTP enabled\tinterfaces docker0\t8000.02426cf29450\tno\tdocker_gwbridge\t8000.02420cb2e439\tno\tweave\t8000.a2ec14f583ef\tno\tvethwe-bridge datapath：是一个openvswitch vethwe-datapath@vethwe-bridge：是veth pair vethwe-datapath：父设备是datapath vxlan-6784：是vxlan interface，其maste也是datapath，weave主机之间通过Vxlan节能型通信 sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 $ ifconfig datapath: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1376 inet6 fe80::e45d:12ff:fee2:9d69 prefixlen 64 scopeid 0x20\u0026lt;link\u0026gt; ether e6:5d:12:e2:9d:69 txqueuelen 1000 (Ethernet) RX packets 19 bytes 1060 (1.0 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 8 bytes 648 (648.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 docker0: flags=4099\u0026lt;UP,BROADCAST,MULTICAST\u0026gt; mtu 1500 inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255 ether 02:42:24:0d:54:06 txqueuelen 0 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 docker_gwbridge: flags=4099\u0026lt;UP,BROADCAST,MULTICAST\u0026gt; mtu 1500 inet 172.18.0.1 netmask 255.255.0.0 broadcast 172.18.255.255 inet6 fe80::42:52ff:fe25:3b18 prefixlen 64 scopeid 0x20\u0026lt;link\u0026gt; ether 02:42:52:25:3b:18 txqueuelen 0 (Ethernet) RX packets 1032 bytes 89148 (87.0 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 1032 bytes 89148 (87.0 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 eth0: flags=4419\u0026lt;UP,BROADCAST,RUNNING,PROMISC,MULTICAST\u0026gt; mtu 1500 inet 10.0.0.15 netmask 255.255.255.0 broadcast 10.0.0.255 inet6 fe80::20c:29ff:fe84:f329 prefixlen 64 scopeid 0x20\u0026lt;link\u0026gt; ether 00:0c:29:84:f3:29 txqueuelen 1000 (Ethernet) RX packets 97077 bytes 109615069 (104.5 MiB) RX errors 0 dropped 244 overruns 0 frame 0 TX packets 21805 bytes 3174138 (3.0 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo: flags=73\u0026lt;UP,LOOPBACK,RUNNING\u0026gt; mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 inet6 ::1 prefixlen 128 scopeid 0x10\u0026lt;host\u0026gt; loop txqueuelen 1 (Local Loopback) RX packets 1032 bytes 89148 (87.0 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 1032 bytes 89148 (87.0 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 vethwe-bridge: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1376 inet6 fe80::f056:b7ff:fe0f:c146 prefixlen 64 scopeid 0x20\u0026lt;link\u0026gt; ether f2:56:b7:0f:c1:46 txqueuelen 0 (Ethernet) RX packets 272 bytes 25496 (24.8 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 275 bytes 25670 (25.0 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 vethwe-datapath: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1376 inet6 fe80::c495:98ff:fec0:508d prefixlen 64 scopeid 0x20\u0026lt;link\u0026gt; ether c6:95:98:c0:50:8d txqueuelen 0 (Ethernet) RX packets 1032 bytes 89148 (87.0 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 1032 bytes 89148 (87.0 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 vxlan-6784: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 65470 ether 7a:a1:d9:e9:f7:39 txqueuelen 1000 (Ethernet) RX packets 513 bytes 372948 (364.2 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 520 bytes 379884 (370.9 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 weave: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1376 inet6 fe80::469:deff:fe6b:f186 prefixlen 64 scopeid 0x20\u0026lt;link\u0026gt; ether 06:69:de:6b:f1:86 txqueuelen 1000 (Ethernet) RX packets 19 bytes 1060 (1.0 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 8 bytes 648 (648.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 sh 1 2 3 4 5 $ docker network ls NETWORK ID NAME DRIVER SCOPE 0ca046b6232c bridge bridge local 776a38c5868e docker_gwbridge bridge local 51bfcaafee94 weave weavemesh local 自动创建docker网络weave\nsh 1 2 3 4 5 6 $ brctl show bridge name\tbridge id\tSTP enabled\tinterfaces docker0\t8000.0242240d5406\tno\tdocker_gwbridge\t8000.024252253b18\tno\tvethcb0a2e3 weave\t8000.0669de6bf186\tno\tvethwe-bridge vethwl95e206ea7 查看weave网络的信息dirver为\u0026quot;Driver\u0026quot;: \u0026quot;weavemesh\u0026quot;\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 $ docker network inspect weave [ { \u0026#34;Name\u0026#34;: \u0026#34;weave\u0026#34;, \u0026#34;Id\u0026#34;: \u0026#34;522dd1c8152750aa5862bdcc3c025bb07b9d66410f267503ae9c4305363d5a82\u0026#34;, \u0026#34;Created\u0026#34;: \u0026#34;2018-08-27T17:27:37.265691267+08:00\u0026#34;, \u0026#34;Scope\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;weavemesh\u0026#34;, \u0026#34;EnableIPv6\u0026#34;: false, \u0026#34;IPAM\u0026#34;: { \u0026#34;Driver\u0026#34;: \u0026#34;weavemesh\u0026#34;, \u0026#34;Options\u0026#34;: null, \u0026#34;Config\u0026#34;: [ { \u0026#34;Subnet\u0026#34;: \u0026#34;10.32.0.0/12\u0026#34; } ] }, \u0026#34;Internal\u0026#34;: false, \u0026#34;Attachable\u0026#34;: false, \u0026#34;Ingress\u0026#34;: false, \u0026#34;ConfigFrom\u0026#34;: { \u0026#34;Network\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;ConfigOnly\u0026#34;: false, \u0026#34;Containers\u0026#34;: {}, \u0026#34;Options\u0026#34;: { \u0026#34;works.weave.multicast\u0026#34;: \u0026#34;true\u0026#34; }, \u0026#34;Labels\u0026#34;: {} } ] Weave网络会在每个宿主机上创建一个网桥，每个容器通过veth pair连接到这个Weave 网桥。容器里面的veth网卡会获取到Weave网络分配给的IP地址和子网掩码。每当容器启动时，会创建两个网络接口。eth0if51 与docker_gwbridge 同属于一个网段。\nsh 1 2 3 4 5 6 7 8 9 10 11 12 1: lo: \u0026lt;LOOPBACK,UP,LOWER_UP\u0026gt; mtu 65536 qdisc noqueue qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 48: ethwe0@if49: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN\u0026gt; mtu 1376 qdisc noqueue link/ether 3e:78:8b:2e:c9:4b brd ff:ff:ff:ff:ff:ff inet 10.40.0.0/12 brd 10.47.255.255 scope global ethwe0 valid_lft forever preferred_lft forever 50: eth0@if51: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN\u0026gt; mtu 1500 qdisc noqueue link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0 valid_lft forever preferred_lft forever 其中ethwe0@if49，从名称上看出与weave相关，其对应的编号是48。我们从宿主机上面ip link进行查看，ethwe0@if49与vethwle9c9e24ce@if48是一对veth pair，而且被挂在了weave网桥上\nsh 1 2 3 4 5 6 7 8 49: vethwle9c9e24ce@if48: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1376 qdisc noqueue master weave state UP link/ether 1a:c5:52:37:66:72 brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet6 fe80::18c5:52ff:fe37:6672/64 scope link valid_lft forever preferred_lft forever 51: veth9c86c85@if50: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc noqueue master docker_gwbridge state UP link/ether da:57:cc:0c:7d:32 brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet6 fe80::d857:ccff:fe0c:7d32/64 scope link valid_lft forever preferred_lft forever sh 1 2 weave\t8000.a2ec14f583ef\tno\tvethwe-bridge vethwle9c9e24ce weave安装配置 项目地址：https://github.com/weaveworks/weave\n环境准备 环境要求：\nlinux内核版本为3.8以上 dockers版本为1.10.0或更高 主机名 IP地址 软件环境 node01 10.0.0.15 docker-1806 weare node02 10.0.0.16 docker-1806 weare 下载安装weave Weave不需要集中式的key-value存储，所以安装和运行都很简单。直接把Weave二进制文件下载到系统中就可以了。主从节点都需要安装。\nsh 1 2 3 wget -O /usr/local/bin/weave \\ https://github.com/weaveworks/weave/releases/download/v2.4.0/weave \u0026amp;\u0026amp; \\ chmod +x /usr/local/bin/weave Reference 1 Docker网络 Weave - Bigberg - 博客园\n2 End of an Era: Weaveworks Closes Shop Amid Cloud Native Turbulence\n","permalink":"https://www.161616.top/weave-over-host/","summary":"项目地址：https://github.com/weaveworks/weave\n注：weave公司与2024年关门\nweaves说明 Weave是由weaveworks公司开发的解决Docker跨主机网络的解决方案，它能够创建一个虚拟网络，用于连接部署在多台主机上的Docker容器，这样容器就像被接入了同一个网络交换机，那些使用网络的应用程序不必去配置端口映射和链接等信息。\n外部设备能够访问Weave网络上的应用程序容器所提供的服务，同时已有的内部系统也能够暴露到应用程序容器上。Weave能够穿透防火墙并运行在部分连接的网络上，另外，Weave的通信支持加密，所以用户可以从一个不受信任的网络连接到主机。\nweaves实现原理 weave launch初始化时会自动下载三个docker容器来辅助运行，并且创建linux网桥与docker网络\nweave 运行了三个容器：\nweave 是主程序，负责建立weave网络，收发数据，提供 DNS 服务等。 weavevolumes容器提供卷存储 weavedb容器提供数据存储 sh 1 2 3 4 5 6 $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE weaveworks/weavedb latest 15c78a9b1895 4 weeks ago 698B weaveworks/weaveexec 2.4.0 bf0c403ea58d 4 weeks ago 151MB weaveworks/weave 2.4.0 7aa67bc6bc43 4 weeks ago 96.7MB 自动创建网桥\nsh 1 2 3 4 5 $ brctl show bridge name\tbridge id\tSTP enabled\tinterfaces docker0\t8000.","title":"使用weave实现docker跨宿主机通讯"},{"content":"SSH命令详解 参数 说明 -1 强制使用ssh协议版本1； -2 强制使用ssh协议版本2； -4 强制使用IPv4地址； -6 强制使用IPv6地址； -A 开启认证代理连接转发功能； -a 关闭认证代理连接转发功能； -b 使用本机指定地址作为对应连接的源ip地址； -C 请求压缩所有数据； -F 指定ssh指令的配置文件； -f 后台执行ssh指令； -g 允许远程主机连接主机的转发端口； -i 指定身份文件； -l 指定连接远程服务器登录用户名； -N 不执行远程指令； -o 指定配置选项； -p 指定远程服务器上的端口； -q 静默模式； -X 开启X11转发功能； -x 关闭X11转发功能； -y 开启信任X11转发功能。 -D 指定本地 “动态” 应用程序级端口转发。这通过分配套接字来侦听本地端口，可选地绑定到指定的bind_address。只要与此端口建立连接，就会通过安全通道转发连接，然后使用应用程序协议确定从远程计算机连接的位置。 SSH端口转发实战 概述 在通常情况下，网络防火墙会阻碍你进行某些必要的网络传输。公司的安全策略可能不允许你把SSH密钥存储在某些主机上。或者，你也可能需要在原本安全的环境中运行一些不安全的网络应用程序。\nSSH提供了一个重要功能，称为转发forwarding或者称为隧道传输tunneling，它能够将其他 TCP 端口的网络数据通过 SSH 链接来转发，并且自动提供了相应的加密及解密服务。这一过程有时也被叫做tunneling，这是因为 SSH 为其他 TCP 链接提供了一个安全的通道来进行传输而得名。\nSSH端口转发主要用来解决如下两方面问题：\n突破防火墙的限制完成无法建立的 TCP 连接。 加密 Client 端至 Server 端之间的通讯数据。 开启ssh的端口转发功能 ssh端口转发功能默认是打开的。如需修改的话，修改后需要重启sshd服务才会生效。\nbash 1 AllowTcpForwarding yes 常用SSH转发类型 在ssh连接的基础上，指定ssh client或ssh server的某个端口作为源地址，所有发至该端口的数据包都会透过ssh连接被转发出去；至于转发的目标地址，目标地址既可以指定，也可以不指定，如果指定了目标地址，称为定向转发，如果不指定目标地址则称为动态转发：\n定向转发\n定向转发把数据包转发到指定的目标地址。目标地址不限定是ssh client 或 ssh server，既可以是二者之一，也可以是二者以外的其他机器。\n动态转发\n动态转发不指定目标地址，数据包转发的目的地是动态决定的。\n本地端口转发 本地转发中的本地是指将本地的某个端口(1024-65535)通过SSH隧道转发至其他主机的套接字，这样当我们的程序连接本地的这个端口时，其实间接连上了其他主机的某个端口，当我们发数据包到这个端口时数据包就自动转发到了那个远程端口上了\n语法\nsh 1 ssh -L [bind_address:]port:host:port ssh_host sh 1 ssh -fNC -L 3333:100.109.9.249:22 111.44.246.139 命令说明\n参数 说明 bind_address 表示客户端主机的ip，这是针对系统有多块网卡，不指定默认是127.0.0.1 port 本地主机指定监听的端口 host 远程主机的ip hostport 指定远程主机的端口，如果远程主机是HTTP，就是80，FTP（21）。 ssh_host 远程主机的ip，也可以是能够访问到远程主机的另一个ip -L表明是本地转发，此时TCP客户端与SSH客户端同在本地主机上。后面接着三个值，由冒号分开，分别表示：需要监听的本地端口，远程主机名或IP地址，及远程的转发目标端口号。\n远程端口转发 远程转发和本地很相似，原理也差不多，但是不同的是，本地转发是在本地主机指定的一个端口，而远程转发是由SSH服务器经由SSH客户端转发，连接至目标服务器上。本质一样，区别在于需要转发的端口是在远程主机上还是在本地主机上\n现在SSH就可以把连接从（39.104.112.253:80）转发到（10.0.0.10:85）。\n语法\nsh 1 ssh -R [bind_ip]:bind_port:host_ip:host_port sshserver 参数说明\n参数 说明 -R 表示远程转发。 bind_ip 表示绑定的远程主机iP bind_port 远程主机监听端口 host_ip 被访问的主机IP host_port 被访问主机的端口 sshserver 使用sshclient建立隧道的sshserver 动态端口转发 定向转发（包括本地转发和远程转发）的局限性是必须指定某个目标地址，如果需要借助一台中间服务器访问很多目标地址，一个一个地定向转发显然不是好办法，这时就要用的是ssh动态端口转发，它相当于建立一个SOCKS服务器。各种应用经由SSH客户端转发，经过SSH服务器，到达目标服务器，不固定端口。\n语法\nsh 1 ssh -fN -D [ssh client port] ssh server 参数说明\n参数 说明 -D 动态转发 -f 后台执行 -N 不执行任何命令 ssh client port 监听的端口 ssh server 当作sock5代理服务器(127.0.0.1) SSH端口转发实战 实验1：本地端口转发应用 实验环境 主机 公网IP 内网IP 地位 node01 39.104.116.253 172.24.104.10 client node02 112.35.76.212 172.16.16.18 mysql server 实现在生产内网里有一台mysql服务器（mysql Server），但是限制了只有本机上部署的应用才能直接连接此 mysql服务器。现在我们想临时从本地机器（mysql Client）直接连接到这个mysql服务器，只需要在mysql Client上运用本地端口转发。\n查看node01系统状态 sh 1 2 3 4 5 6 $ ps -ef|grep mysql root 21764 21724 0 23:15 pts/0 00:00:00 grep --color=auto mysql $ ss -lnt State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:22 *:* 查看node02系统状态\nsh 1 2 3 $ ps -ef|grep mysql mysql 2042 1 0 Aug30 ? /usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid root 18250 18194 0 23:15 pts/0 grep --color=auto mysql 在主机A上执行命令：ssh -Nf -L *:7000:127.0.0.1:3306 112.35.76.212\n需要注意的是，在选择端口号时要注意非管理员帐号是无权绑定 1-1023 端口的，所以一般是选用一个 1024-65535 之间的并且尚未使用的端口号即可。\nsh 1 2 3 4 5 $ ss -lnt State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:22 *:* LISTEN 0 128 *:7000 *:* LISTEN 0 128 :::7000 :::* 可以发现，node01的7000端口已被监听了，是被ssh（ssh就是客服端）监听的，和node02建立了一个SSH隧道，并且我们知道node01和node02是可以通信的，我们可以通过主机A的端口访问时，其实就是间接用主机B在访问。可以看到可以正常登陆mysql。\n注：mysql经过转发实际登陆mysql的用户组成的ip为sshserver登陆的ip。而不是sshclient的ip\n实验2：远程端口转发应用 内网通过路由器（SNAT）可以访问外网，而外网是无法访问到内网的，我们利用ssh来实现访问内网，要搭建这种环境，首先想到的是VMware的NAT模式，我将node03设置NAT模式。\n主机 公网IP 内网IP 地位 node01 112.35.26.104 172.24.104.10 sshserver node02 112.35.76.212 172.16.16.18 sshclient node03 172.16.16.11 mysql node04 客户端 在node02执行命令：ssh -Nf -R *:7706:172.16.16.11:3306 112.35.26.104，sshclient与sshserver建立隧道连接。\nsh 1 2 $ netstat -nt|grep 104 tcp 0 0 172.16.16.18:50676 112.35.26.104:22 ESTABLISHED sshserver上可见到监听的端口。\nsh 1 2 3 $ ss -lnt|grep 7706 LISTEN 0 128 127.0.0.1:7706 *:* LISTEN 0 128 ::1:7706 :::* 登陆环境进行测试\n翻墙命令 ssh -TfnN -D {Port} {User}@{Host}\ntext 1 ssh -TfnN -D 7070 root@195.133.11.43 ","permalink":"https://www.161616.top/ssh-client-application/","summary":"SSH命令详解 参数 说明 -1 强制使用ssh协议版本1； -2 强制使用ssh协议版本2； -4 强制使用IPv4地址； -6 强制使用IPv6地址； -A 开启认证代理连接转发功能； -a 关闭认证代理连接转发功能； -b 使用本机指定地址作为对应连接的源ip地址； -C 请求压缩所有数据； -F 指定ssh指令的配置文件； -f 后台执行ssh指令； -g 允许远程主机连接主机的转发端口； -i 指定身份文件； -l 指定连接远程服务器登录用户名； -N 不执行远程指令； -o 指定配置选项； -p 指定远程服务器上的端口； -q 静默模式； -X 开启X11转发功能； -x 关闭X11转发功能； -y 开启信任X11转发功能。 -D 指定本地 “动态” 应用程序级端口转发。这通过分配套接字来侦听本地端口，可选地绑定到指定的bind_address。只要与此端口建立连接，就会通过安全通道转发连接，然后使用应用程序协议确定从远程计算机连接的位置。 SSH端口转发实战 概述 在通常情况下，网络防火墙会阻碍你进行某些必要的网络传输。公司的安全策略可能不允许你把SSH密钥存储在某些主机上。或者，你也可能需要在原本安全的环境中运行一些不安全的网络应用程序。\nSSH提供了一个重要功能，称为转发forwarding或者称为隧道传输tunneling，它能够将其他 TCP 端口的网络数据通过 SSH 链接来转发，并且自动提供了相应的加密及解密服务。这一过程有时也被叫做tunneling，这是因为 SSH 为其他 TCP 链接提供了一个安全的通道来进行传输而得名。\nSSH端口转发主要用来解决如下两方面问题：\n突破防火墙的限制完成无法建立的 TCP 连接。 加密 Client 端至 Server 端之间的通讯数据。 开启ssh的端口转发功能 ssh端口转发功能默认是打开的。如需修改的话，修改后需要重启sshd服务才会生效。","title":"SSH客户端应用场景"},{"content":"对Oracle 检查ip合法性,就必须在服务器端的sqlnet.ora文件中设置如下参数\ntext 1 2 TCP.INVITED_NODES=(10.0.0.36,10.0.0.1,10.0.0.35) TCP.EXCLUDED_NODES=(10.0.0.2) 启动监听出现如下错误\ntext 1 2 3 4 5 6 7 8 9 10 11 $ lsnrctl status LSNRCTL for Linux: Version 11.2.0.1.0 - Production on 12-MAR-2018 18:32:13 Copyright (c) 1991, 2009, Oracle. All rights reserved. Connecting to (ADDRESS=(PROTOCOL=tcp)(HOST=)(PORT=1521)) TNS-12541: TNS:no listener TNS-12560: TNS:protocol adapter error TNS-00511: No listener Linux Error: 111: Connection refused 错误输出并没有打印详细的信息,从lisenter.ora,tnsnames.ora入手,但没有发现文件是错误的。最后检查sqlnet.ora,发现TCP.INVITED_NODES参数有如下约束是官方文档没有给出的\ntcp.invited_nodes需要满足如下条件才可成功启动监听\n1、需要设置参数TCP.VALIDNODE_CHECKING为YES才能激活该特性。 2、tcp.invited_nodes的值中一定要包括本机地址（127.0.0.1 / 10.0.0.36）或localhost，因为监听需要通过本机ip去访问监听，一旦禁止lsnrct将不能启动或停止监听。 3、不能设置ip段和通配符。 4、此方式只适合tcp/ip协议。 5、此方式是通过监听限制白名单的。 6、针对的是ip地址而不是其他（如用户名等）。 7、此配置适用于9i以上版本。本次踩坑是oracle11gr2。 8、修改配置后需要重启监听才可生效。 TCP.INVITED_NODES=(10.0.0.36,10.0.0.1)\n此时在启动监听不会出现报错了。而对与TCP.EXCLUDED_NODES参数并没有以上的限制，需要将禁止访问的ip传参即可。\n","permalink":"https://www.161616.top/oracle-tcp.validnode_checking/","summary":"对Oracle 检查ip合法性,就必须在服务器端的sqlnet.ora文件中设置如下参数\ntext 1 2 TCP.INVITED_NODES=(10.0.0.36,10.0.0.1,10.0.0.35) TCP.EXCLUDED_NODES=(10.0.0.2) 启动监听出现如下错误\ntext 1 2 3 4 5 6 7 8 9 10 11 $ lsnrctl status LSNRCTL for Linux: Version 11.2.0.1.0 - Production on 12-MAR-2018 18:32:13 Copyright (c) 1991, 2009, Oracle. All rights reserved. Connecting to (ADDRESS=(PROTOCOL=tcp)(HOST=)(PORT=1521)) TNS-12541: TNS:no listener TNS-12560: TNS:protocol adapter error TNS-00511: No listener Linux Error: 111: Connection refused 错误输出并没有打印详细的信息,从lisenter.ora,tnsnames.ora入手,但没有发现文件是错误的。最后检查sqlnet.ora,发现TCP.INVITED_NODES参数有如下约束是官方文档没有给出的\ntcp.invited_nodes需要满足如下条件才可成功启动监听\n1、需要设置参数TCP.VALIDNODE_CHECKING为YES才能激活该特性。 2、tcp.invited_nodes的值中一定要包括本机地址（127.0.0.1 / 10.0.0.36）或localhost，因为监听需要通过本机ip去访问监听，一旦禁止lsnrct将不能启动或停止监听。 3、不能设置ip段和通配符。 4、此方式只适合tcp/ip协议。 5、此方式是通过监听限制白名单的。 6、针对的是ip地址而不是其他（如用户名等）。 7、此配置适用于9i以上版本。本次踩坑是oracle11gr2。 8、修改配置后需要重启监听才可生效。 TCP.","title":"tcp.validnode_checking踩过的坑"},{"content":"jenkins配置如下 在Jenkins上添加了两个节点(Slave Node)，且为这两个节点设置了一个相同的标签 \u0026ldquo;windows\u0026rdquo;。创建了一个新Job – \u0026ldquo;test-windows\u0026rdquo;，选择的是”构建一个自由风格的软件项目”。并且为了使多个slave并行构建，我选择了\u0026quot;只允许绑定到这台机器的job”，在\u0026quot;Label Expression\u0026quot;中选择了\u0026quot;windows\u0026quot;。\n然而这种方式并不能实现多个slave并行操作。网上90%说的都不靠谱。\n在我使用的过程中，使用了label 去管理多个 Slave，给一个项目的构建指定了这个 label，会发现这个项目的多次构建，都使用同一个 Slave，并没有使用 label 里的其它 Slave去构建。\n查了很多资料才发现原来从 jenkins 的调度算法使用了一致性的哈希算法，jenkins根据添加的信息评测出优先级列表，选择优先级最高的Slave去构建，当最优slave不满足条件或者没有可用的 execut时，才会选用下一个slave。\n查了很多资料发现构造多配置项目可以选择构建时的slave。这样可以实现多slave并行构建。\nmulti configuration project比起构建自由风格的软件项目多个Configuration Matrix，在这里可以选择多个slave。这里选择lable的话，还是会使用默认算法从lable中选择最优slave进行构建。\n配置完成后再构建时，会同时在多个slave上进行并行构建\n禁止在master上运行job或和业务相关的操作\n将 [executors] 设置为0\n","permalink":"https://www.161616.top/jenkins-multi-slave-problem/","summary":"jenkins配置如下 在Jenkins上添加了两个节点(Slave Node)，且为这两个节点设置了一个相同的标签 \u0026ldquo;windows\u0026rdquo;。创建了一个新Job – \u0026ldquo;test-windows\u0026rdquo;，选择的是”构建一个自由风格的软件项目”。并且为了使多个slave并行构建，我选择了\u0026quot;只允许绑定到这台机器的job”，在\u0026quot;Label Expression\u0026quot;中选择了\u0026quot;windows\u0026quot;。\n然而这种方式并不能实现多个slave并行操作。网上90%说的都不靠谱。\n在我使用的过程中，使用了label 去管理多个 Slave，给一个项目的构建指定了这个 label，会发现这个项目的多次构建，都使用同一个 Slave，并没有使用 label 里的其它 Slave去构建。\n查了很多资料才发现原来从 jenkins 的调度算法使用了一致性的哈希算法，jenkins根据添加的信息评测出优先级列表，选择优先级最高的Slave去构建，当最优slave不满足条件或者没有可用的 execut时，才会选用下一个slave。\n查了很多资料发现构造多配置项目可以选择构建时的slave。这样可以实现多slave并行构建。\nmulti configuration project比起构建自由风格的软件项目多个Configuration Matrix，在这里可以选择多个slave。这里选择lable的话，还是会使用默认算法从lable中选择最优slave进行构建。\n配置完成后再构建时，会同时在多个slave上进行并行构建\n禁止在master上运行job或和业务相关的操作\n将 [executors] 设置为0","title":"jenkins多个slave遇到的坑"},{"content":"需求分析 在jenkins中没有找到构建前插件，每次构建时间很长，希望可以实现判断代码是否更新，如果没更细则停止构建步骤。\n实现步骤 在构建时执行shell命令，而jenkins提供的的环境变量可以实现此判断 https://wiki.jenkins.io/display/JENKINS/Conditional+BuildStep+Plugin\ntext 1 2 3 4 5 6 GIT_COMMIT The commit hash being checked out. GIT_PREVIOUS_COMMIT The hash of the commit last built on this branch, if any. GIT_PREVIOUS_SUCCESSFUL_COMMIT The hash of the commit last successfully built on this branch, if any. GIT_COMMIT 当前拉取版本的commit id GIT_PREVIOUS_COMMIT 最后在此分支上构建的 commit id GIT_PREVIOUS_SUCCESSFUL_COMMIT 最后在此分支上成功构建的 commit id号\ntext 1 2 3 4 5 6 7 8 #!/bin/bash if [ $GIT_PREVIOUS_SUCCESSFUL_COMMIT == $GIT_COMMIT ];then echo \u0026#34;no change，skip build\u0026#34; exit 0 else echo \u0026#34;git pull commmit id not equals to current commit id trigger build\u0026#34; fi 注意，不能使用-eq 只能使用 “==” 提交新版本后，构建提示如下：\ntext 1 2 $ git show|head -1 commit 27617e680d2e6bf00062700623792aef63926edd 在jenkins中执行构建\n","permalink":"https://www.161616.top/jenkins-checkcode/","summary":"需求分析 在jenkins中没有找到构建前插件，每次构建时间很长，希望可以实现判断代码是否更新，如果没更细则停止构建步骤。\n实现步骤 在构建时执行shell命令，而jenkins提供的的环境变量可以实现此判断 https://wiki.jenkins.io/display/JENKINS/Conditional+BuildStep+Plugin\ntext 1 2 3 4 5 6 GIT_COMMIT The commit hash being checked out. GIT_PREVIOUS_COMMIT The hash of the commit last built on this branch, if any. GIT_PREVIOUS_SUCCESSFUL_COMMIT The hash of the commit last successfully built on this branch, if any. GIT_COMMIT 当前拉取版本的commit id GIT_PREVIOUS_COMMIT 最后在此分支上构建的 commit id GIT_PREVIOUS_SUCCESSFUL_COMMIT 最后在此分支上成功构建的 commit id号\ntext 1 2 3 4 5 6 7 8 #!/bin/bash if [ $GIT_PREVIOUS_SUCCESSFUL_COMMIT == $GIT_COMMIT ];then echo \u0026#34;no change，skip build\u0026#34; exit 0 else echo \u0026#34;git pull commmit id not equals to current commit id trigger build\u0026#34; fi 注意，不能使用-eq 只能使用 “==” 提交新版本后，构建提示如下：","title":"jenkins检查代码 如没更新停止构建步骤"},{"content":"运行环境 服务器：centos6.8\n服务器oracle版本：oracle 11g R2 64位，字符集是ZHS32utf8。\n客户端：navicat 12x64 windows8.1x64\n问题分析 当在windows客户端使用sqlplus或navicat时如果数据库中文显示“????”\n这种情况是在客户端与服务器端字符集不一致时，从客户端输入了汉字信息。输入的这些信息即便是把客户端字符集更改正确，也无法显示汉字。\n解决方法：退出sqlplus,设置相应的环境变量NLS_LANG\nlinux：\ntext 1 export NLS_LANG=\u0026#34;SIMPLIFIED CHINESE_CHINA.ZHS16GBK\u0026#34; windows：\n出现问题 此时。系统cmd命令行使用sqlplus已经正常显示中文，但是navicat中依旧是？？？？\n图为cmd命令行访问sqlplus客户端查询\n图为navicat f6弹出的sqlplus客户端\n原因是因为Navicat Premium默认自带的instant client，但是其是base lite版本的（Basic Lite： Basic 的精简版本，其中仅带有英文错误消息和 Unicode、ASCII 以及西欧字符集支持），不支持中文字符集，而本文中的服务器端oracle恰好是中文字符集。自带版本不支持。此处需要去oracle官网下载相对应的版本。\nhttp://www.oracle.com/technetwork/database/database-technologies/instant-client/downloads/index.html\n将下载的文件解压覆盖navicat中的instantclient目录里的文件。\n此时连接oracle实例提示如下信息\n尽管我们下载了64位的版本。却提示如图信息。这是因为Navicat仅支持32位的，因此还需下载一个32位的客户端。替换到instantclient目录中\n替换完成后连接实例。f6使用sqlplus查询发现中文已经正常显示\n","permalink":"https://www.161616.top/sqlplus-windows/","summary":"运行环境 服务器：centos6.8\n服务器oracle版本：oracle 11g R2 64位，字符集是ZHS32utf8。\n客户端：navicat 12x64 windows8.1x64\n问题分析 当在windows客户端使用sqlplus或navicat时如果数据库中文显示“????”\n这种情况是在客户端与服务器端字符集不一致时，从客户端输入了汉字信息。输入的这些信息即便是把客户端字符集更改正确，也无法显示汉字。\n解决方法：退出sqlplus,设置相应的环境变量NLS_LANG\nlinux：\ntext 1 export NLS_LANG=\u0026#34;SIMPLIFIED CHINESE_CHINA.ZHS16GBK\u0026#34; windows：\n出现问题 此时。系统cmd命令行使用sqlplus已经正常显示中文，但是navicat中依旧是？？？？\n图为cmd命令行访问sqlplus客户端查询\n图为navicat f6弹出的sqlplus客户端\n原因是因为Navicat Premium默认自带的instant client，但是其是base lite版本的（Basic Lite： Basic 的精简版本，其中仅带有英文错误消息和 Unicode、ASCII 以及西欧字符集支持），不支持中文字符集，而本文中的服务器端oracle恰好是中文字符集。自带版本不支持。此处需要去oracle官网下载相对应的版本。\nhttp://www.oracle.com/technetwork/database/database-technologies/instant-client/downloads/index.html\n将下载的文件解压覆盖navicat中的instantclient目录里的文件。\n此时连接oracle实例提示如下信息\n尽管我们下载了64位的版本。却提示如图信息。这是因为Navicat仅支持32位的，因此还需下载一个32位的客户端。替换到instantclient目录中\n替换完成后连接实例。f6使用sqlplus查询发现中文已经正常显示","title":"windows上sqlplus客户端连接oralce数据库中文显示问题"},{"content":"jenkins github tag\n测试项目地址：GitHub - go-redis\\redis: Type-safe Redis client for Golang\n插件下载地址：[git-parameter](http:\\updates.jenkins-ci.org\\download\\plugins\\git-parameter)\npt-online-schema-change\nJenkins中配置gradle项目的坑 - doctorq - CSDN博客\n","permalink":"https://www.161616.top/jenkins-github-tag/","summary":"jenkins github tag\n测试项目地址：GitHub - go-redis\\redis: Type-safe Redis client for Golang\n插件下载地址：[git-parameter](http:\\updates.jenkins-ci.org\\download\\plugins\\git-parameter)\npt-online-schema-change\nJenkins中配置gradle项目的坑 - doctorq - CSDN博客","title":"jenkins github tag使用方式"},{"content":"haproxy 介绍 haproxy是一个开源的、高性能的基于TCP和HTTP应用代理的高可用的、负载均衡服务软件，它支持双机热备、高可用、负载均衡、虚拟主机、基于TCP和HTTP的应用代理、图形界面查看信息等功能。其配置简单、维护方便，而且拥有很好的对服务器节点的健康检查功能(相当于keepalived健康检查)，当其代理的后端服务器出现故障时，haproxy会自动的将该故障服务器摘除，当故障的服务器恢复后，haproxy还会自动将该服务器自动加入进来提供服务。\nLVS/NGINX对比 haproxy 特别适用于那些高负载、访问量很大，但又需要会话保持及七层应用代理的业务应用。haproxy运行在今天的普通的服务器硬件上，几乎不需要进行任何的优化就可以支持数以万计的并发连接。并且它的运行模式使得它可以很简单、轻松、安全的整合到各种己有的网站架构中，同时，haproxy的代理模式，可以使得所有应用服务器不会暴露到公共网络上，即后面的节点服务器不需要公网IP地址。\n从1.3版本起，haproxy软件引入了frontend,backend的功能，frontend (ad规则匹配)可以让运维管理人员根据任意HTTP请求头内容做规则匹配，然后把请求定向到相关的backend(这个是事先定义好的多个server pools，等待前端把请求转过来的服务器组)。通过frontend和backend，我们可以很容易的买现haproxy的各种7层应用代理功能。\nhaproxy代理模式 haproxy支持两种主要代理模式：\n1、基于4层的tcp应用代理(例如:可用于邮件服务、内部协议通信服务器、MySQL、HTTPS服务等)。\n2、基于7层的http代理。在4层tcp代理模式下，haproxy仅在客户端和服务器之间进行流量转发。但是在7层http代理模式下，haproxy会分析应用层协议，并且能通过允许、拒绝、交换、增加、修改或者删除请求(request)或者回应(response)里指定内容来控制协议。\n官方网站:www.haproxy.org\nhaproxy 解决方案拓扑图 haproxy L4负载均衡应用架构拓扑 haproxy软件的四层tcp应用代理非常优秀，且配置非常简单、方便，比LVS和Nginx的配置要简单很多，首先，配置haproxy不需要在RS端做任何特殊配置 (只要对应服务开启就OK)就可以实现应用代理，其次，haproxy的配置语法和增加虚拟主机功能等也比lvs/nginx简单。并且和商业版的NS (Netscaler)、F5, A10等负载均衡硬件的使用方法和在架构中的位置一模一样。下面是haproxy的Layer4层应用代理的拓扑结构图:\n说明:由于haproxy软件采用的是类NAT模式(本质不同)的应用代理，数据包来去都会经过haproxy，因此，在流量特别大的情况下(门户级别的流量)，其效率和性能不如LVS的DR模式负载均衡。\n在一般的中小型公司，建议采用haproxy做负载均衡，而不要使用LVS或Nginx。为什么强调中小型公司呢?换句话说，千万PV级别以下直接使用haproxy做负载均衡，会让我们负责维护的运维管理人员配置简单、快速、维护方便，出问题好排查。\nhaproxy L7负载均衡应用架构拓扑 haproxy软件的最大优势在于其7层的根据URL请求头应用过滤的功能以及sesson会话功能，在门户网站的高并发生产架构中，haproxy软件一般用在4层LVS负载均衡软件的下一层，或者像haproxy官方推荐的也可以挂在硬件负载均衡althon, NS, F5, A10下使用，其表现非常好。从2009年起taobao，京东商城的业务也大面积使用了haproxy作为7层CACHE应用代理。\n安装haproxy 模拟真实环境\n搭建合适的模拟环境是一个人学习能力的重要体现。例如：人类第一次上太空也没有真正的环境，但是想去太空就是要自己动手去搭建逼真的模拟环境。实验多了就是经验，自然就有解除生产环境的机会了。\n名称 接口 IP 用途 MASTER eth0 10.0.0.7 外网管理IP用于WAN数据转发 eth1 172.16.1.7 内网管理IP，用于LAN数据转发 eth2 10.0.10.7 用于服务器间心跳连接（直连） VIP 10.0.0.17 用于提供应用程序A挂载服务 BACKUP eth0 10.0.0.8 外网管理IP，用于WAN数据转发 eth1 172.16.1.8 内网管理IP，用于LAN数据转发 eth2 10.0.10.8 用于服务器间心跳连接（直连） VIP 10.0.0.8 用于提供应用程序B挂载服务 下载安装haproxy 下载地址：http://www.haproxy.org/download/\n文档地址：http://www.haproxy.org/download/1.7/doc/configuration.txt\n编译haproxy bash 1 2 3 4 make TARGET=linux2628 ARCH=x86_64 # \u0026lt;==64位编译配置 make TARGET=linux2628 ARCH=i386 # \u0026lt;==32位编译配置 make PREFIX=/app/haproxy-1.7.5 install ln -s /app/haproxy-1.7.5/ /app/haproxy 配置内核转发功能 bash 1 2 net.ipv4_forward=1 # \u0026lt;==基于NAT模式的负载均衡器都需要打开系统转发功能 sysctl -p haproxy启动命令 直接运行命令查看帮助\nbash 1 2 3 4 5 6 7 8 9 10 $ /app/haproxy/sbin/haproxy HA-Proxy version 1.7.5 2017/04/03 Copyright 2000-2017 Willy Tarreau \u0026lt;willy@haproxy.org\u0026gt; Usage : haproxy [-f \u0026lt;cfgfile|cfgdir\u0026gt;]* [ -vdVD ] [ -n \u0026lt;maxconn\u0026gt; ] [ -N \u0026lt;maxpconn\u0026gt; ] [ -p \u0026lt;pidfile\u0026gt; ] [ -m \u0026lt;max megs\u0026gt; ] [ -C \u0026lt;dir\u0026gt; ] [-- \u0026lt;cfgfile\u0026gt;*] -v displays version ; -vv shows known build options. -d enters debug mode ; -db only disables background mode. -dM[\u0026lt;byte\u0026gt;] poisons memory with \u0026lt;byte\u0026gt; (defaults to 0x50) ... 命令选项\n选项 说明 -D 以后台守护进程启动服务 -f 指定配置文件 -c 检查配置文件语法 -n 设置最大连接数，一般在配置文件中指定 -q 启动时不显示警告 -m 限制使用的内存量 -p 将pid写入文件 -sf 平滑重启 -st 强制重启 注意 -sf 和 -st 重启时需要指定配置文件\nhaproxy服务脚本 在下载解压目录中有默认的启动脚本\nbash 1 2 $ ll /root/tools/haproxy-1.7.5/examples/haproxy.init /root/tools/haproxy-1.7.5/examples/haproxy.init 自定义启动脚本\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #!/bin/sh BASE=/app/haproxy COMM=$BASE/sbin/haproxy PIDFILE=$BASE/var/run/haproxy.pid CONF_FILE=$BASE/conf/haproxy.conf1 case \u0026#34;$1\u0026#34; in \u0026#39;start\u0026#39;|\u0026#39;START\u0026#39;) if [ ! -f $PIDFILE ];then $COMM -f $CONF_FILE -D else echo \u0026#39;haproxy has been started\u0026#39; fi ;; \u0026#39;status\u0026#39;|\u0026#39;STATUS\u0026#39;) if [ ! -f $PIDFILE ];then echo \u0026#39;haproxy is not running\u0026#39; exit 1 fi for pid in $(cat $PIDFILE);do kill -0 $pid RETVAL=$? if [ $RETVAL == 0 ];then echo \u0026#39;process \u0026#39;$pid \u0026#39; not running\u0026#39; fi done echo \u0026#39;haproxy is running\u0026#39; ;; \u0026#39;restart\u0026#39;|\u0026#39;RESTART\u0026#39;) $COMM -f $CONF_FILE -sf $(cat $PIDFILE) ;; \u0026#39;stop\u0026#39;|\u0026#39;STOP\u0026#39;) kill $(cat $PIDFILE) rm -f $PIDFILE ;; \u0026#39;check\u0026#39;|\u0026#39;CHECK\u0026#39;) $COMM -f $CONF_FILE -c ;; *) echo \u0026#34;USAGE $0 start|stop|restart|status|check|\u0026#34; exit 1 ;; esac haproxy配置文件 haproxy 的默认配置文件在下载解压目录下\nbash 1 /root/tools/haproxy-1.7.5/examples aproxy的配置文件可以分为5部分\nglobal：全局配置参数段，主要用来控制haproxy启动前的进程及系统相关设置。\ndefault：配置一些默认参数，如果frontend，backend，listen等段未设置则使用default段配置。\nlisten：\nfrontend：用来匹配接受客户所请求的域名，uri等，并针对不同配置，做不同的请求处理。\nbackend：定义后端服务器集群，以及对后端 服务器的一切权重、队列、连接数等选项的设置。\n配置文件示例注释说明\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 global #\u0026lt;==全局配置, 用于设定义全局参数, 属于进程级的配置, 通常与操作系统配置有关 chroot /app/haproxy/var/chroot #\u0026lt;==运行路径 daemon #\u0026lt;==以守护方式运行haproxy user haproxy #\u0026lt;==运行haproxy用户/组, 或者使用关键字uid/gid group haproxy log\t127.0.0.1 local0 debug # 全局日志配置指定127.0.0.1:514的syslog服务中local0日志设备。 # 与记录日志的模式[err warning info debug] pidfile /app/haproxy/var/run/haproxy.pid maxconn\t2000 #设置每haproxy进程的最大并发连接数, 其等同于命令行选项“-n”; # “ulimit -n”自动计算的结果参照此参数设定. nbproc 1 #\u0026lt;==启动的haproxy进程数量, 只能用于守护进程模式。应该设置为cup核数 # ulimit-n 655350 # 设置每进程所能够打开的最大文件描述符数目。 # 默认情况其会自动进行计算, 因此不推荐修改此选项. defaults #\u0026lt;==默认配置 mode http #\u0026lt;==默认的模式【tcp:4层； http:7层； health:只返回OK】 log global #\u0026lt;==继承全局的日志定义输出 #option httplog #\u0026lt;==日志类别, httplog # 如果后端服务器需要记录客户端真实ip, 需要在HTTP请求中添加”X-Forwarded-For”字段; # 但haproxy自身的健康检测机制访问后端服务器时, 不应将记录访问日志。 # 可用except来排除127.0.0.0，即haproxy本身. #option forwardfor except 127.0.0.0/8 option forwardfor option httpclose # 开启http协议中服务器端关闭功能每个请求完毕后主动关闭http通道。 # 使得支持长连接，使得会话可以被重用，使得每一个日志记录都会被记录. option dontlognull #\u0026lt;==如果产生了一个空连接，那这个空连接的日志将不会记录. option redispatch\t#\u0026lt;==当与后端服务器的会话失败(服务器故障或其他原因)时, 把会话重新分发到其他健康的服务器上; 当故障服务器恢复时, 会话又被定向到已恢复的服务器上; retries 3 #\u0026lt;==在判定会话失败时的尝试连接的次数 option abortonclose #\u0026lt;==当haproxy负载很高时, 自动结束掉当前队列处理比较久的连接. timeout http-request 10s #\u0026lt;==默认http请求超时时间 timeout queue 1m\t#\u0026lt;==默认队列超时时间, 后端服务器在高负载时, 会将haproxy发来的请求放进一个队列中. timeout connect 5s\t#\u0026lt;==haproxy与后端服务器连接超时时间. timeout client 1m\t#\u0026lt;==客户端与haproxy连接后, 数据传输完毕, 不再有数据传输, 即非活动连接的超时时间. timeout server 1m\t#\u0026lt;==haproxy与后端服务器非活动连接的超时时间. timeout http-keep-alive 10s #\u0026lt;==默认新的http请求连接建立的超时时间，时间较短时可以尽快释放出资源，节约资源. timeout check 10s\t#\u0026lt;==心跳检测超时时间 maxconn 2000\t#\u0026lt;==最大并发连接数 #设置默认的负载均衡方式 #balance source #balnace leastconn listen admin_status # 统计页面配置, frontend和backend的组合体。 # 监控组的名称可按需自定义 mode http #\u0026lt;==监控运行模式 bind 0.0.0.0:80\t#\u0026lt;==统计页面访问端口 maxconn 10 #\u0026lt;==统计页面默认最大连接数 option httplog #\u0026lt;==#http日志格式 stats enable #\u0026lt;==开启web统计 stats hide-version #\u0026lt;==隐藏统计页面上的haproxy版本信息 stats refresh 30s\t#\u0026lt;==监控页面自动刷新时间 stats uri /admin?status #\u0026lt;==统计页面访问url stats auth admin:111\t#\u0026lt;==监控页面的用户和密码:admin, 可设置多个用户名 stats realm hellow world #\u0026lt;==统计页面密码框提示文本 stats admin if TRUE\t#\u0026lt;==手工启动/禁用后端服务器, 可通过web管理节点 #设置haproxy错误页面 #errorfile 400 /usr/local/haproxy/errorfiles/400.http #errorfile 403 /usr/local/haproxy/errorfiles/403.http #errorfile 408 /usr/local/haproxy/errorfiles/408.http #errorfile 500 /usr/local/haproxy/errorfiles/500.http #errorfile 502 /usr/local/haproxy/errorfiles/502.http #errorfile 503 /usr/local/haproxy/errorfiles/503.http #errorfile 504 /usr/local/haproxy/errorfiles/504.http option forwardfor #\u0026lt;==将用户的IP转发给监控的IP option httpchk HEAD /check.html HTTP/1.0 #\u0026lt;==http的健康检查 frontend WEB_SITE #\u0026lt;==vip bind *:80 mode http log global option httplog option httpclose \u0026lt;== http7层代理专用 default_backend WWW backend WWW #\u0026lt;==real server option forwardfor header X-REAL-IP option httpchk HEAD / HTTP/1.0 \u0026lt;==检查real server是否存活的方式，[get post head] server web1 10.0.0.3:80 check inter 2000 rise 30 fall 15 server web2 www.test.com check inter 2000 rise 30 fall 15 # inter为检查间隔 rise为连续30次检查成功则认为有效的。 # fall为连续15次检查失败则认为宕机 haproxy日志配置 CentOS 5.X\n编辑/etc/syslog.conf增加如下配置\nbash 1 local0.* /app/haproxy/logs/haproxy.log CentOS 6 \u0026amp; 7\nbash 1 2 local0.* -/app/haproxy/logs/haproxy.log # 将local0设备的日志定向到haproxy.log因为haproxy使用的local0 注：使用了local0设备需要将 local0 在 /var/log/message 里制空，否则会记录双份\nbash 1 *.info;mail.none;authpriv.none;cron.none;local0.none; /var/log/messages 修改/etc/sysconfig/syslog\nbash 1 2 3 #-r enables logging from remote machines # -x disables DNS lookups on messages recieved with -r SYSLOGD_OPTIONS=\u0026#34;-m 0 -r -c 2\u0026#34; 重启后生效\nbash 1 2 3 /etc/init.d/syslog restart # \u0026lt;== CentOS 5 /etc/init.d/rsyslog restart # \u0026lt;== CentOS 6 systemctl restart rsyslog # \u0026lt;== CentOS 7 http://www.cnblogs.com/aaa103439/p/3537163.html\nhttp://www.cnblogs.com/MacoLee/p/5853413.html\n基于权重的轮训round robin bash 1 2 3 server web1 10.0.0.2 check weight 3 server web1 10.0.0.3 check weight 1 for n in {0..20};do curl 10.0.02;sleep 1 done leastconn\u0026ndash;\u0026gt; 类似于 lvs中 的 wlc\n不过这里只考虑活动连接数，即选择活动连接数少的。另外，最好在长连接会话中使用，如sql,ldap\n负载模式实验 环境准备 IP 地位 10.0.0.2 haproxy 10.0.0.1 real server 1 10.0.0.3 real server 2 TCP负载模式配置 bash 1 2 3 4 5 6 7 8 frontend WEB_SITE #\u0026lt;==vip bind *:80 mode tcp log global default_backend WWW backend WWW #\u0026lt;==real server server web1 10.0.0.3:52113 check inter 2000 rise 3 fall 5 server web2 10.0.0.1:52113 check inter 2000 rise 3 fall 5 注意：listen可以看做是frontend与bankend的集合，顾如果使用tcp模式代理的话，不要开启web监控\nTCP代理所出现的问题 tcp模式开启web监控页面出现负载准问题 开启web监控页面的haproxy日志\nbash 1 2 3 4 5 6 May 19 22:41:02 127.0.0.1 haproxy[2579]: Connect from 192.168.2.1:50820 to 10.0.0.2:80 (WEB_SITE/TCP) May 19 22:41:02 127.0.0.1 haproxy[2579]: Connect from 192.168.2.1:50820 to 10.0.0.2:80 (WEB_SITE/TCP) May 19 22:41:02 127.0.0.1 haproxy[2578]: 192.168.2.1:50821 [19/May/2017:22:41:02.405] admin_status admin_status/\u0026lt;STATS\u0026gt; 0/0/0/0/1 200 16975 - - LR-- 0/0/0/0/0 0/0 \u0026#34;GET /admin?status HTTP/1.1\u0026#34; May 19 22:41:02 127.0.0.1 haproxy[2579]: Connect from 192.168.2.1:50822 to 10.0.0.2:80 (WEB_SITE/TCP) May 19 22:41:02 127.0.0.1 haproxy[2579]: Connect from 192.168.2.1:50822 to 10.0.0.2:80 (WEB_SITE/TCP) May 19 22:41:02 127.0.0.1 haproxy[2579]: 192.168.2.1:50823 [19/May/2017:22:41:02.816] admin_status admin_status/\u0026lt;STATS\u0026gt; 0/0/0/0/1 200 16996 - - LR-- 0/0/0/0/0 0/0 \u0026#34;GET /admin?status HTTP/1.1\u0026#34; 代理ssh负载出现如下问题 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that a host key has just been changed. The fingerprint for the RSA key sent by the remote host is 11:f2:f1:05:1e:80:0a:bf:9f:09:20:3f:02:e1:12:b8. Please contact your system administrator. Add correct host key in /root/.ssh/known_hosts to get rid of this message. Offending RSA key in /root/.ssh/known_hosts:1 RSA host key for [10.0.0.2]:80 has changed and you have requested strict checking. Host key verification failed. 出现此问题是因为，登陆ssh时使用的是vip，而真正的server是两个，当登陆第一台server时，将指纹保存到vip，当轮训到第二台时，因为已经保存过其他server的指纹了，会提示指纹改变。所以这个不算是问题\nL7代理实验 text 1 2 3 4 5 6 7 8 9 10 frontend WEB_SITE #\u0026lt;==vip bind *:80 mode http option httplog option httpclose log global default_backend WWW backend WWW #\u0026lt;==real server server web1 10.0.0.3:52113 check inter 2000 rise 3 fall 5 server web2 10.0.0.1:52113 check inter 2000 rise 3 fall 5 ACL ACL名称必须由 大小写字母、数字、”-“（破折号）、“_”（下划线）、“.”（点）和 ”:“（冒号）。 ACL名称区分大小写，这意味着 “my_acl” 和 “My_Acl” 是两个不同的ACL。ACL的数量没有强制限制。未使用的不影响性能，他们只是消耗少量的内存。\n注：在http代理模式中uri的请求会被分配到real server的实体路径中\nbash 1 acl \u0026lt;aclname\u0026gt; \u0026lt;criterion\u0026gt; [flags] [operator] \u0026lt;value\u0026gt; ... 参数说明：\n\u0026lt;aclname\u0026gt; \u0026lt;==ACL名称；\n\u0026lt;criterion\u0026gt;：测试标准，即对什么信息发起测试；测试方式可以由 [flags] 指定的标志进行调整；而有些测试标准也可以需要为其在之前指定一个操作符 [operator]；\nACL的flag：\n-i：在匹配所有后续模式时忽略大小写。 -f：从文件加载模式。 -m：使用特定的模式匹配方法 -n：禁止DNS解析 -M：像地图文件一样加载-f指向的文件。 \u0026ndash; ：强制结束标志。 当字符串看起来像其中一个标志时很有用。 -u：强制ACL的唯一ID \u0026lt;value\u0026gt;：acl测试条件支持的值有以下四类：\n整数或整数范围：如 1024:65535 表示从 1024~65535；仅支持使用正整数(如果出现类似小数的标识，其为通常为版本测试)，且支持使用的操作符有5个，分别为eq、ge、gt、le和lt； 字符串：支持使用 “-i” 以忽略字符大小写，支持使用 “\\” 进行转义；如果在模式首部出现了-i，可以在其之前使用“–”标志位； 正则表达式：其机制类同字符串匹配； IP地址及网络地址 常用的测试标准(criteria)\nbash 1 be_sess_rate(backend) \u0026lt;integer\u0026gt; 用于测试指定的backend上会话创建的速率(即每秒创建的会话数)是否满足指定的条件；常用于在指定backend上的会话速率过高时将用户请求转发至另外的backend，或用于阻止攻击行为。\n例如：\nbash 1 2 acl being_scanned be_sess_rate gt 50 # \u0026lt;==此方案是定义在backend里的 redirect location /error_pages/denied.html if being_scanned sd\nbash 1 fe_sess_rate(frontend) \u0026lt;integer\u0026gt; 用于测试指定的frontend(或当前frontend)上的会话创建速率是否满足指定的条件；\n常用于为frontend指定一个合理的会话创建速率的上限以防止服务被滥用。例如下面的例子限定入站邮件速率不能大于50封/秒，所有在此指定范围之外的请求都将被延时50毫秒。\ntext 1 2 3 4 5 6 7 8 frontend mail bind :25 mode tcp maxconn 500 acl too_fast fe_sess_rate ge 50 tcp-request inspect-delay 50ms tcp-request content accept if ! too_fast tcp-request content accept if WAIT_END hdr \u0026lt;string\u0026gt;\n用于测试请求报文中的所有首部或指定首部是否满足指定的条件；指定首部时，其名称不区分大小写，且在括号 “()” 中不能有任何多余的空白字符。测试服务器端的响应报文时可以使用 shdr()。例如下面的例子用于测试首部Connection的值是否为close。\ntext 1 acl url_bao hdr(Host) -i www.baidu.com method \u0026lt;string\u0026gt;\n测试HTTP请求报文中使用的方法。\ntext 1 2 3 4 5 6 7 8 front test use_backend front acl me method get default_backend back backend front server web01 10.0.0.1:8080 check port 8080 inter 5000 fall 5 backend back server w1 10.0.0.3:8080 check port 8080 inter 5000 fall 5 path_beg \u0026lt;string\u0026gt;\n用于测试请求的URL是否以指定的模式开头。\n下面的例子用于测试URL是否以/static、/images、/javascript或/stylesheets头。\ntext 1 acl url_static path_beg -i /static /images /javascript /stylesheets path_end \u0026lt;string\u0026gt;\n用于测试请求的URL是否以指定的模式结尾。\n例如，下面的例子用户测试URL是否以jpg、gif、png、css或js结尾\ntext 1 acl url_static path_end -i .jpg .gif .png .css .js hdr_beg \u0026lt;string\u0026gt;\n用于测试请求报文的指定首部的开头部分是否符合指定的模式。\n例如，下面的例子用记测试请求是否为提供静态内容的主机img、video、download或ftp。\ntext 1 acl host_static hdr_beg(host) -i img. video. download. ftp. 请求uri中包含static\ntext 1 acl timetask_req url_dir -i timetask 请求头长度\ntext 1 acl cl hdr_cnt(Content-length) eq 0 web stats 添加一个 frontend\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 frontend stats # 必填参数，默认无法访问 mode http # 统计页面访问端口 bind 0.0.0.0:1080 # 统计页面默认最大连接数 maxconn 10 # http日志格式 option httplog # 开启web统计 stats enable # 隐藏统计页面上的haproxy版本信息 stats hide-version # 监控页面自动刷新时间 stats refresh 30s # 统计页面访问url stats uri /admin?status # 监控页面的用户和密码:admin, 可设置多个用户名 stats auth admin:111 # 统计页面密码框提示文本,某些浏览器不适合中文 stats realm mCloud\\ Haproxy # 手工启动/禁用后端服务器, 可通过web管理节点 stats admin if TRUE socat动态操作haproxy socat是一个多功能的网络工具软件，名字来由是”Socket CAT”，功能与netcat类似，可以看做netcat的加强版。\n配置haproxy\nbash 1 2 stats socket /app/haproxy/var/run/haproxy.sock mode 600 level admin stats timeout 2m 安装socat\nbash 1 yum install socat -y 远程操作haproxy\nsocat帮助\nbash 1 echo help|socat stdio /app/haproxy/var/run/haproxy.sock 上线测试摘取集群节点\nbash 1 2 echo disable server back/w1 |socat stdio /app/haproxy/var/run/haproxy.sock echo enable server back/w1 |socat stdio /app/haproxy/var/run/haproxy.sock ","permalink":"https://www.161616.top/haproxy/","summary":"haproxy 介绍 haproxy是一个开源的、高性能的基于TCP和HTTP应用代理的高可用的、负载均衡服务软件，它支持双机热备、高可用、负载均衡、虚拟主机、基于TCP和HTTP的应用代理、图形界面查看信息等功能。其配置简单、维护方便，而且拥有很好的对服务器节点的健康检查功能(相当于keepalived健康检查)，当其代理的后端服务器出现故障时，haproxy会自动的将该故障服务器摘除，当故障的服务器恢复后，haproxy还会自动将该服务器自动加入进来提供服务。\nLVS/NGINX对比 haproxy 特别适用于那些高负载、访问量很大，但又需要会话保持及七层应用代理的业务应用。haproxy运行在今天的普通的服务器硬件上，几乎不需要进行任何的优化就可以支持数以万计的并发连接。并且它的运行模式使得它可以很简单、轻松、安全的整合到各种己有的网站架构中，同时，haproxy的代理模式，可以使得所有应用服务器不会暴露到公共网络上，即后面的节点服务器不需要公网IP地址。\n从1.3版本起，haproxy软件引入了frontend,backend的功能，frontend (ad规则匹配)可以让运维管理人员根据任意HTTP请求头内容做规则匹配，然后把请求定向到相关的backend(这个是事先定义好的多个server pools，等待前端把请求转过来的服务器组)。通过frontend和backend，我们可以很容易的买现haproxy的各种7层应用代理功能。\nhaproxy代理模式 haproxy支持两种主要代理模式：\n1、基于4层的tcp应用代理(例如:可用于邮件服务、内部协议通信服务器、MySQL、HTTPS服务等)。\n2、基于7层的http代理。在4层tcp代理模式下，haproxy仅在客户端和服务器之间进行流量转发。但是在7层http代理模式下，haproxy会分析应用层协议，并且能通过允许、拒绝、交换、增加、修改或者删除请求(request)或者回应(response)里指定内容来控制协议。\n官方网站:www.haproxy.org\nhaproxy 解决方案拓扑图 haproxy L4负载均衡应用架构拓扑 haproxy软件的四层tcp应用代理非常优秀，且配置非常简单、方便，比LVS和Nginx的配置要简单很多，首先，配置haproxy不需要在RS端做任何特殊配置 (只要对应服务开启就OK)就可以实现应用代理，其次，haproxy的配置语法和增加虚拟主机功能等也比lvs/nginx简单。并且和商业版的NS (Netscaler)、F5, A10等负载均衡硬件的使用方法和在架构中的位置一模一样。下面是haproxy的Layer4层应用代理的拓扑结构图:\n说明:由于haproxy软件采用的是类NAT模式(本质不同)的应用代理，数据包来去都会经过haproxy，因此，在流量特别大的情况下(门户级别的流量)，其效率和性能不如LVS的DR模式负载均衡。\n在一般的中小型公司，建议采用haproxy做负载均衡，而不要使用LVS或Nginx。为什么强调中小型公司呢?换句话说，千万PV级别以下直接使用haproxy做负载均衡，会让我们负责维护的运维管理人员配置简单、快速、维护方便，出问题好排查。\nhaproxy L7负载均衡应用架构拓扑 haproxy软件的最大优势在于其7层的根据URL请求头应用过滤的功能以及sesson会话功能，在门户网站的高并发生产架构中，haproxy软件一般用在4层LVS负载均衡软件的下一层，或者像haproxy官方推荐的也可以挂在硬件负载均衡althon, NS, F5, A10下使用，其表现非常好。从2009年起taobao，京东商城的业务也大面积使用了haproxy作为7层CACHE应用代理。\n安装haproxy 模拟真实环境\n搭建合适的模拟环境是一个人学习能力的重要体现。例如：人类第一次上太空也没有真正的环境，但是想去太空就是要自己动手去搭建逼真的模拟环境。实验多了就是经验，自然就有解除生产环境的机会了。\n名称 接口 IP 用途 MASTER eth0 10.0.0.7 外网管理IP用于WAN数据转发 eth1 172.16.1.7 内网管理IP，用于LAN数据转发 eth2 10.0.10.7 用于服务器间心跳连接（直连） VIP 10.0.0.17 用于提供应用程序A挂载服务 BACKUP eth0 10.0.0.8 外网管理IP，用于WAN数据转发 eth1 172.16.1.8 内网管理IP，用于LAN数据转发 eth2 10.0.10.8 用于服务器间心跳连接（直连） VIP 10.0.0.8 用于提供应用程序B挂载服务 下载安装haproxy 下载地址：http://www.haproxy.org/download/\n文档地址：http://www.haproxy.org/download/1.7/doc/configuration.txt\n编译haproxy bash 1 2 3 4 make TARGET=linux2628 ARCH=x86_64 # \u0026lt;==64位编译配置 make TARGET=linux2628 ARCH=i386 # \u0026lt;==32位编译配置 make PREFIX=/app/haproxy-1.","title":"详解haproxy"},{"content":"什么是存储引擎 在经清楚什么是存储引擎之前，我们先来个比喻，我们都知道录制一个视频文件，可以转换成不同的格式如mp4 avi wmv等，而存在我们电脑的磁盘上也会存在于不同类型的文件系统中如windows里常见的ntfs fat32，存在于linux常见的ext3 ext4 xfs，但是，给我们或者用户看到实际视频内容都是一样的。直观区别是，占用系统的空间大小与清晰程度可能不一样。\n那么数据库表里的数据存储在数据库里及磁盘上和上述的视频格式及存储磁盘文件系统格式特征类似，也有很多中存储方式。\n但是，对于用户和应用程序来说同样一张表的数据，无论用什么引擎来存储，用户看到的数据都是一样的。不同的引擎存储，引擎功能，占用的空间大小，读取性能等可能有区别。\nMySQL最常用的存储引擎为：MyISAM和InnoDB。全文索引：目前MySQL5.5版本，myisam和inondb都已经支持。\nMySQL存储引擎的架构 MySQL的存储引擎是MySQL数据库的重要组成部分，MySQL常用的表的引擎为MyISAM和InnoDB两种。MySQL的每种存储引擎在MySQL里是通过插件的方式使用的，MySQL可以同时支持多种存储引擎。下面是MySQL存储引擎体系结构简图：\nMyISAM引擎 MyISAM引擎是MySQL关系数据库管理系统的默认存储引擎（MySQL 5.5.5以前）。这种MySQL表存储结构从旧的ISAM代码扩展出许多有用的功能。在新版本MySQL中，InnoDB引擎由于其对事务参照完整性，以及更高的并发性等优点开始逐步的取代MyISAM引擎，\n“InnoDB is the default storage engine as of MySQL 5.5.5。MyISAM: The MySQL storage engine that is used the most in Web,data warehousing,and other application environments.MyISAM is supported in all MySQL configurations,an is the default storage engine prior to MySQL 5.5.5。”\n查看MySQL5.1数据库默认引擎\nbash 1 2 3 4 5 6 7 mysql\u0026gt; show create table test1\\G *************************** 1. row *************************** Table: test1 Create Table: CREATE TABLE `test1` ( `name` int(11) DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1 1 row in set (0.00 sec) 提示：MySQL 5.1数据库的默认存储引擎为MyISAM。\n每一个MyISAM的表都对应于硬盘上的三个文件。这三个文件有一样的文件名，但是有不同的扩展名指示其类型用途：\n.frm文件保存表的定义，这个文件并不是MyISAM引擎的一部分，而是服务器的一部分\n.MYD保存表的数据\n.MYI是表的索引文件。\n# MYD和MYI是MyISAM的关键点\n范例\nbash 1 2 3 4 $ ll /var/lib/mysql/mysql/ user.frm\t# 表的定义 user.MYD\t# data user.MYI\t# index MySQL系统的表多数属于MyISAM引擎\nbash 1 2 3 4 $ ls /var/lib/mysql/mysql/ columns_priv.frm help_keyword.frm proc.frm time_zone_leap_second.MYI columns_priv.MYD help_keyword.MYD proc.MYD time_zone.MYD columns_priv.MYI help_keyword.MYI proc.MYI time_zone.MYI “为什么MySQL5.5.5以前默认的是MyISAM引擎，而MySQL5.5.5以后默认是innodb”\n答：和互联网发展有关，互联网诞生之初。基本上已读为主，那时机器硬件性能低。设计数据库时需要占用资源少的数据库。5.5之后选择了innodb\n“为什么mysql库里内部表默认是myisam”\nweb2.0时代：以用户为中心的时代多数平台都是用户上传其他用户读）在这种时代myisam引擎就胜任不了了，顾mysql5。\nMyISAM引擎特点 至少掌握5点\n不支持事务 事务是指逻辑上的一组操作，组成这组操作的各个单元，要么全成功要么全失败\n表级锁定 数据更新时锁定整个表：其锁定机制是表级锁定，这虽然可以让锁定的实现成本很小但是也同时降低了其开发性能\n举例子：上厕所，还有很多小便坑，锁上外面的们，一个人上厕所，谁也去不了厕所了。\n表级锁定并发处理降低，但是提升了效率 举例：商场有小偷，不知道在哪，锁住一楼大门，然后逐一摸排\n缺点是，别人想出出不去了。但是对于保安来说是最有效的方法。\n读写互相阻塞 不仅会在写入的时候阻塞读取，myisam还会在读取的时候阻塞写入，但读本身并不会阻塞另外的读\n只会缓存索引 MyISAM可以通过key_buffer_size（只是myisam）缓存索引，以大大提高访问性能较少磁盘IO，但这个缓存区只会缓存索引，而不会缓存数据\n读取速度较快。占用资源相对少。\n不支持外键约束，但支持全文索引。\nMyISAM引擎是MySQL5.5.5 前默认的存储引擎 ( “is the default storage engine prior to MySQL 5.5.5” )。\nMyISAM引擎适用的生产业务场景 不需要事务支持的业务（例如转账就不行）。 一般为读数据比较多的应用，读写都频繁场景不适合，读多或者写多的都适合。 读写并发相对较低的业务（纯读纯写高并发也可以）（锁机制问题）。 数据修改相对少的业务（阻塞问题）。 已读为主的业务，例如：读数据库系统表、www blog 图片信息数据库 用户数据库 商品库等业务。 对数据一致性要求不是非常高的业务（MySQL 5.6前不支持事务）。 硬件资源比较差的机器都可以用myisam（占用资源少）。 使用读写分离的MySQL从库可以使用myisam（5年前提到较多） 小结：单一对数据库的操作都可以使用myisam，所谓单一就是尽量纯读，或纯写（insert update delete）等\nInnoDB引擎 Innodb引擎是MySQL数据库的另一个重要的存储引擎，正式成为MySQLAB所发行新版的标准，被包含在所有二进制安装包里。和其他的存储引擎相比，InnoDB引擎的优势是支持兼容ACID的事务（类似于PostgreSQL），以及参数完整性（即对外键的支持）。Oracle公司2005年手抽了Innobase。innobase采用双认证授权。它使用GNU发型，也允许其他想将InnoDB结合到商业软件的团体获得授权。\n更多参考 reman-5.5-en.html-chapter/storage-engines.html\n提示：只有test1.frm没有MyISAM对应的数据文件和索引文件了。\nbash 1 2 3 4 5 6 7 $ ll test/ ibdata1 ib_logfile0 # ib_logfile1\t# 这里是存放Innodb数据文件 ib_logfile2 # test1.frm\ttest2.frm InnoDB引擎特点 支持事务：支持4个事务隔离级别，支持多版本读。 行级锁定（更新时一般是锁定当前行）：通过索引实现，全表扫描仍然会是表锁，注意间隙锁的影响。 读写阻塞与事务隔离级别相关。 具有非常搞笑的缓存特性：能缓存索引，也能缓存数据。 整个表和主键以Cluster方式存储，组成一颗平衡树。 所有Secondary Index都会保存主键信息。 支持分区，表空间，类似oracle数据库 支持外键约束，5.5前不支持全文索引 和MyISAM引擎比，InnoDB对硬件资源要求更高 面试必问\ninnodb特点：面试必答项：\nrow-level locking full-text search indexs data caches index caches transactions 占用资源多 读写阻塞与事务隔离级别相关 外键 innodb引擎使用的生产业务场景 需要事务支持的业务（具有较好的事务特性）。 行级锁定对高并发有很好的适应能力，但需要确保查询是通过索引完成。 数据读写及更新都较为频繁的场景，如:BBS SNS 微博，微信等。 数据一致性要求较高的业务，例如：充值转账，银行卡转账。 硬件设备内存较大，可以利用InnoDB较好的缓存能力来提高内存利用率，尽可能减少磁盘IO text 1 2 3 4 5 6 7 8 9 10 11 12 innodb_additional_mem_pool_size = 4M innodb_buffer_pool_size = 32M innodb_data_file_path = ibdata1:128M:autoextend innodb_file_io_threads = 4 innodb_thread_concurrency = 8 innodb_flush_log_at_trx_commit = 2 innodb_log_buffer_size = 2M innodb_log_file_size = 4M innodb_log_files_in_group = 3 innodb_max_dirty_pages_pct = 90 innodb_lock_wait_timeout = 120 innodb_file_per_table = 0 共享表空间对应物理数据文件\nbash 1 2 3 $ ll test/ ibdata1 ib_logfile0 独立表空间对应物理数据文件\nbash 1 2 innodb_file_per_table innodb_data_home_dir=xxx innodb引擎调优精要 主键尽可能小，避免给Secondary index带来过大的空间负担\n建立有效索引避免全表扫描，因为会使用表锁。\n尽可能缓存所有的索引和数据，提高相应速度，减少磁盘IO消耗\n在大批量小插入的时候，尽量自己控制事务而不要使用autocommit自动提交。有开关可以控制提交方式；\n合理设置innodb_flush_log_at_trx_commit参数值，不要过度追求安全性。\n如果innodb_flush_log_at_trx_commit的值为0，log buffer每秒就会被刷写日志文件到磁盘，提交事务时候不做任何操作。\n避免主键更新。（字段的起名长短都会影响性能）。\nMySQL引擎特别说明 Feature MyISAM Memory InnoDB Archive NDB Storage limits 256TB RAM 64TB None 384EB Transactions No No Yes No Yes Locking granularity Table Table Row Row Row MVCC No No Yes No No Geospatial data type support Yes No Yes Yes Yes Geospatial indexing support Yes No Yes[a] No No B-tree indexes Yes Yes Yes No No T-tree indexes No No No No Yes Hash indexes No Yes No[b] No Yes Full-text search indexes Yes No Yes[c] No No Clustered indexes No No Yes No No Data caches No N/A Yes No Yes Index caches Yes N/A Yes No Yes Compressed data Yes[d] No Yes[e] Yes No Encrypted data[f] Yes Yes Yes Yes Yes Cluster database support No No No No Yes Replication support[g] Yes Yes Yes Yes Yes Foreign key support No No Yes No No Backup / point-in-time recovery[h] Yes Yes Yes Yes Yes Query cache support Yes Yes Yes Yes Yes Update statistics for data dictionary Yes Yes Yes Yes Yes [a] InnoDB support for geospatial indexing is available in MySQL 5.7.5 and higher. [b] InnoDB utilizes hash indexes internally for its Adaptive Hash Index feature. [c] InnoDB support for FULLTEXT indexes is available in MySQL 5.6.4 and higher. [d] Compressed MyISAM tables are supported only when using the compressed row format. Tables using the compressed row format with MyISAM are read only. [e] Compressed InnoDB tables require the InnoDB Barracuda file format. [f] Implemented in the server (via encryption functions). Data-at-rest tablespace encryption is available in MySQL 5.7 and higher. [g] Implemented in the server, rather than in the storage engine. [h] Implemented in the server, rather than in the storage engine. 参考手册：https://dev.mysql.com/doc/refman/5.5/en/storage-engines.html\n以上是myisam innodb和NDBCluster三个存储引擎是目前互联网公司应用比较多的存储引擎，特别是前两者，其他如 memory merge csv archive等存储引擎的使用场景都相对较少，初学的同学可以暂时忽略。更多可参考MySQL官方文档。\n如何确定MySQL服务器有那些引擎可用？ 在MySQL中使用显示引擎的命令得到一个可用引擎的列表\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 show engines\\G ... ... *************************** 5. row *************************** Engine: MyISAM Support: YES Comment: MyISAM storage engine Transactions: NO XA: NO Savepoints: NO *************************** 6. row *************************** Engine: InnoDB Support: DEFAULT Comment: Percona-XtraDB, Supports transactions, row-level locking, and foreign keys Transactions: YES XA: YES Savepoints: YES ... ... 10 rows in set (0.01 sec) 生产场景中批量更改MySQL引擎 一般来说这样的需求不多见，但偶尔也会有，在这里我们推荐使用sed对备份内容进行引擎转换的方式，当然，不要忘记修改my.cnf使之支持并能高效的使用对应的引擎\n方法1：MySQL命令语句修改\nbash 1 alter table test engine=innodb; 方法2：使用sed对备份内容进行引擎转换\nbash 1 2 3 # 此方法折腾数据不推荐使用 # 主从复制丛库换引擎，正式数据换不要用 sed -e \u0026#39;s#MyISAM#InnoDB#g\u0026#39; b.sql\u0026gt;b1.sql 方法3：mysql_convert_table_format命令修改\nbash 1 mysql_convert_table_format --host=$HOST --user=$USER --passsword=$PASS --socket=$SOCKET --type=$Engine $DB $TN 建表语句加上指定引擎：\nbash 1 2 3 create table test( id int not null ) engine=innodb default charset=utf8; 混合引擎和单独innodb引擎配置差别？\n有关MySQL引擎常见企业面试题 MySQL有那些存储引擎，各自有什么特点和区别？ 生产环境中如何选用MySQL的引擎 ​\t在一般的既有读又有写的业务中，建议使用innodb引擎，一句话尽可能多的使用innodb引擎。\n​\t纯读 纯写可用myisam。例如系统的MySQL库。\n不同引擎如何备份？混合引擎如何备份。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # myisam mysqldump -uroot -p111 -S/data/3306/mysql.sock \\ -A \\ -x \\ -B \\ -F \\ --master-data=2|gzip \u0026gt;back.sql # innodb mysqldump -uroot -p111 -S/data/3306/mysql.sock \\ -A \\ -x \\ -B \\ -F \\ --master-data=2 \\ --single-transaction|gzip \u0026gt;back.sql ","permalink":"https://www.161616.top/ch8-mysql-engine/","summary":"什么是存储引擎 在经清楚什么是存储引擎之前，我们先来个比喻，我们都知道录制一个视频文件，可以转换成不同的格式如mp4 avi wmv等，而存在我们电脑的磁盘上也会存在于不同类型的文件系统中如windows里常见的ntfs fat32，存在于linux常见的ext3 ext4 xfs，但是，给我们或者用户看到实际视频内容都是一样的。直观区别是，占用系统的空间大小与清晰程度可能不一样。\n那么数据库表里的数据存储在数据库里及磁盘上和上述的视频格式及存储磁盘文件系统格式特征类似，也有很多中存储方式。\n但是，对于用户和应用程序来说同样一张表的数据，无论用什么引擎来存储，用户看到的数据都是一样的。不同的引擎存储，引擎功能，占用的空间大小，读取性能等可能有区别。\nMySQL最常用的存储引擎为：MyISAM和InnoDB。全文索引：目前MySQL5.5版本，myisam和inondb都已经支持。\nMySQL存储引擎的架构 MySQL的存储引擎是MySQL数据库的重要组成部分，MySQL常用的表的引擎为MyISAM和InnoDB两种。MySQL的每种存储引擎在MySQL里是通过插件的方式使用的，MySQL可以同时支持多种存储引擎。下面是MySQL存储引擎体系结构简图：\nMyISAM引擎 MyISAM引擎是MySQL关系数据库管理系统的默认存储引擎（MySQL 5.5.5以前）。这种MySQL表存储结构从旧的ISAM代码扩展出许多有用的功能。在新版本MySQL中，InnoDB引擎由于其对事务参照完整性，以及更高的并发性等优点开始逐步的取代MyISAM引擎，\n“InnoDB is the default storage engine as of MySQL 5.5.5。MyISAM: The MySQL storage engine that is used the most in Web,data warehousing,and other application environments.MyISAM is supported in all MySQL configurations,an is the default storage engine prior to MySQL 5.5.5。”\n查看MySQL5.1数据库默认引擎\nbash 1 2 3 4 5 6 7 mysql\u0026gt; show create table test1\\G *************************** 1.","title":"ch08 - MySQL存储引擎"},{"content":"利用 mysql -e 参数查看 mysql 数据 bash 1 2 3 4 5 6 7 8 $ mysql -uroot -p111 -e \u0026#39;use test;show tables;\u0026#39; +------------------------------+ | Tables_in_test | +------------------------------+ | 33hao_activity | | 33hao_activity_detail | | 33hao_address | +------------------------------+ 利用 mysql -e 参数查看SQL线程执行状态\nbash 1 2 $ mysql -uroot -p111 -e \u0026#39;show processlist;\u0026#39; kill 12; 查看完整的线程状态，此参数才查看慢查询语句是非常有用\n解决方法：\nbash 1 2 3 4 root@localhost [test]\u0026gt;show variables like \u0026#39;%_timeout%\u0026#39;; # 设置 set global wait_timeout=60; set global interactive_timeout=60; 在配置文件里修改 text 1 2 set global wait_timeout=60; set global interactive_timeout=60; # 此参数设置后wait_timeout自动生效 其他方法 (1) PHP程序中，不适用持久链接，即 mysql_connect 而不是 pconnect（java调整连接池）\n(2) PHP程序执行完毕，应该显示调用 mysql_close()\n(3) 逐步分析MySQL的SQL查询及慢查询日志，找到查询过慢的SQL，优化。\n利用mysql -e查看mysql变量及性能状态\nbash 1 2 3 4 5 6 $ mysql -uroot -p111 -e \u0026#39;show variables;\u0026#39;|head -5 Variable_name Value auto_increment_increment 1 auto_increment_offset 1 autocommit ON automatic_sp_privileges ON 不重启数据库就该数据库参数\n要求：重启后还能生效\nbash 1 2 3 4 5 6 $ sed -n \u0026#39;122p\u0026#39; /etc/my.cnf key_buffer_size = 16M $ sed -i \u0026#39;s#key_buffer_size = 16M#key_buffer_size=32M#g\u0026#39; /etc/my.cnf $ sed -n \u0026#39;122p\u0026#39; /etc/my.cnf key_buffer_size=32M 生产环境常用重要命令小结\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # 查看数据库里正在执行的SQL语句，可能无法看完整SQL语句 show processlist; # 查看正在执行的完整SQL语句，完整显示 show full processlist; # 不重启数据库调整数据库参数，直接生效，重启后失效 set gloables key_buffer_size=1024*1024*32; # 查看数据库的配置参数信息，例如：my.cnf里参数的生效情况。 show variables; # 杀掉SQL线程的命令ID为线程号 kill id # 查看当前会话的数据库状态信息 show session status; # 查看整个数据库运行状态信息，很重要，要分析并要做好监控 show global status; # 显示innodb引擎的性能状态（早期show innodb status） show engine innodb status; 计算一天之内：MySQL数据库有多少 insert delect语句，有没有好办法？\n定时任务每天0点，show global status; 按天取出对比。\n按天分析binlog日志，获取数据库不同语句的频率。\nmysqladmin命令\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 修改密码 mysqladmin password 111 mysqladmin -uroot -p111 password 222 # 查看状态 mysqladmin -uroot -p111 status mysqladmin -uroot -p111 extended-status show global status mysqladmin -uroot -p111 -S /data/3306/mysql.sock -i status # 刷新日志 mysqladmin -uroot -p111 flush-logs mysqladmin -uroot -p111 processlist # 实时跟踪 mysqladmin -uroot -p111 processlist -i 1 # 关闭mysql mysqladmin -uroot -p111 -S /data/3306/mysql.sock shutdown mysqladmin -uroot -p111 -S /data/3306/mysql.sock variables show vaiables ","permalink":"https://www.161616.top/ch7-mysql-non-interact/","summary":"利用 mysql -e 参数查看 mysql 数据 bash 1 2 3 4 5 6 7 8 $ mysql -uroot -p111 -e \u0026#39;use test;show tables;\u0026#39; +------------------------------+ | Tables_in_test | +------------------------------+ | 33hao_activity | | 33hao_activity_detail | | 33hao_address | +------------------------------+ 利用 mysql -e 参数查看SQL线程执行状态\nbash 1 2 $ mysql -uroot -p111 -e \u0026#39;show processlist;\u0026#39; kill 12; 查看完整的线程状态，此参数才查看慢查询语句是非常有用\n解决方法：\nbash 1 2 3 4 root@localhost [test]\u0026gt;show variables like \u0026#39;%_timeout%\u0026#39;; # 设置 set global wait_timeout=60; set global interactive_timeout=60; 在配置文件里修改 text 1 2 set global wait_timeout=60; set global interactive_timeout=60; # 此参数设置后wait_timeout自动生效 其他方法 (1) PHP程序中，不适用持久链接，即 mysql_connect 而不是 pconnect（java调整连接池）","title":"ch07 - 实现和MySQL非交互式对话"},{"content":"错误日志 error log MySQL错误日志记录MySQL服务进程mysqld在启动/关闭或运行过程中遇到的错误信息\n错误日志配置\n在配置文件中调整方法，当然可以在启动时加入启动参数\nbash 1 2 [mysqld_safe] log-error=/data/3306/mysql_3306.err 启动MySQL命令里加入\nbash 1 2 3 4 5 6 7 8 9 10 /app/mysql/bin/mysqld_safe \\ --defaults-file=/data/3306/my.cnf \\ --log-error=/data/3306/mysql_3306.err MariaDB\u0026gt; show variables like \u0026#34;%log_error%\u0026#34;; +-------------------+---------------------------+ | Variable_name | Value\t| +-------------------+---------------------------+ | log_error | /data/3306/mysql_3306.err | +-------------------+---------------------------+ 遇到数据库启动不了\n先把日志文件备份并清空启动一下mysql服务后再查看日志文件，看报有什么错误\nbash 1 2 3 InnoDB: The error means mysqld does not have the access rights to InnoDB: the directory 然后查看mysql3306目录下文件权限\n普通查询日志 general query log 普通查询日志 (general query log)：记录客户端链接信息和执行的SQL语句信息。\n普通查询日志配置\nbash 1 2 3 4 5 6 7 MariaDB\u0026gt; show variables like \u0026#34;%general_log%\u0026#34;; +------------------+-----------+ | Variable_name | Value | +------------------+-----------+ | general_log | OFF | | general_log_file | lnmp.log | +------------------+-----------+ 临时生效\nbash 1 2 3 4 5 6 7 8 9 10 MariaDB\u0026gt; set global general_log_file = \u0026#39;/data/3306/log/mysql_query.log\u0026#39;; MariaDB\u0026gt; set global general_log=\u0026#39;on\u0026#39;; MariaDB\u0026gt; show variables like \u0026#39;%general_log%\u0026#39;; +-------------------+----------------------------------+ | Variable_name | Value | +-------------------+----------------------------------+ | general_log | ON | | general_log_file | /data/3306/log/mysql_query.log | +-------------------+----------------------------------+ 永久生效\nbash 1 2 3 $ grep general /data/3306/my.cnf general_log=on general_log_file=/data/3306/log/mysql_query.log 日志示例\nbash 1 2 3 4 5 6 7 8 9 10 cat log/mysql_query.log /app/mysql/bin/mysqld, Version: 5.5.54-MariaDB (MariaDB Server). started with: Tcp port: 3306 Unix socket: /data/3306/mysql.sock Time Id\tCommand Argument 170130 8:55:03 1 Connect root@localhost as anonymous on 1 Query select @@version_comment limit 1 170130 8:55:18 1 Query show variabales 170130 8:55:44 2 Connect root@localhost as anonymous on 2 Query KILL QUERY 1 2 Quit 慢查询日志 slow query log 慢查询日志 (slow query log)：记录执行时间超出指定值 long_query_time的SQL语句\n慢查询日志调整\nbash 1 2 3 long_query_time=1 log-slow-queries=/data/3306/log/mysql_slow.log log_queries_not_using_indexes 慢查询的设置，对于数据库SQL的优化非常重要\nbash 1 2 3 4 $ egrep que ../my.cnf long_query_time = 2 log-slow-queries = /data/3306/log/slow.log log_queries_not_using_indexes 利用慢查询进行优化解决方案 开启慢查询\nbash 1 2 3 long_query_time = 2 log-slow-queries = /data/3306/log/slow.log log_queries_not_using_indexes 慢查询日志切割\nbash 1 2 3 4 5 6 #!/bin/sh cd /data/3306/ \u0026amp;\u0026amp;\\ /bin/mv slow.log slow.log.$(date +%F)\u0026amp;\u0026amp;\\ mysqladmin -uroot -p111 -S /data/3306/mysql.sock flush-log 00 00 * * * /bin/sh /server/scripts/cut_slow_log.sh \u0026amp;\u0026gt; /dev/null 使用mysqlsla分析慢查询，定时发给相关人员信箱\n使用explain优化SQL语句（select） 抓SQL慢查询语句\nbash 1 2 show full processlist; mysql -uroot -p111 -S /data/3306/mysql.sock -e \u0026#34;show full processlist;\u0026#34;|egrep -vi \u0026#34;sleep\u0026#34; explain语句检查索引执行情况\nbash 1 2 explain select * from test where name=\u0026#39;123\u0026#39;\\G; explain select SQL_NO_CACHE * from test where name=\u0026#39;123\u0026#39;\\G 对需要建立索引的条件列建立索引\n大表不能高峰期建立索引，300w记录\n分析慢查询的工具mysqlsla\n二进制日志 binary log 二进制日志 (binary log)：用来记录mysql内部增删改等对mysql数据库有更新的内容的记录（对数据库的改动），对数据库查询的语句如：show、select开头的语句，不会被binlog日志记录，用于数据库的增量恢复，以及主从复制\nbash 1 2 3 4 5 6 $ ls mysql-bin.000002 mysql-bin.000004 mysql-bin.000001 mysql-bin.000003 mysql-bin.index 面试题：在MySQL数据库中，关于binlog日志，下列说法正确的是 ___ ?( A )\nA. 依靠足够长度的binlog日志和定期的全备，我们可以恢复任何时间点的单表数据。\nB. 以mysql主从同步为例，binlog中会记录主数据库进行的所有操作。\nC. 以mysql主从同步为例，binlog中会记录主数据库进行的所有查询操作。\nD. binlog通过cat和vi无法查看，但可以通过gedit查看\n开启mysql的binlog功能\nbash 1 2 3 log-bin=/data/3306/mysql-bin max_binlog_cache_size = 5M max_binlog_size = 20M mysqlbinlog工具解析binlog日志\n默认情况binlog日志是二进制格式的，不能使用查看文本工具的命令查看，例如 cat vi等\nbash 1 2 3 4 5 6 $ file mysql-bin.000004 mysql-bin.000004: MySQL replication log $ cat mysql-bin.000004 \u0012\u0004\u0004\u0004\u0002I\b.5.54-MariaDBlog*ȎX\u00138 p5ɎX\u0002\u0001D9\u0001\u0004\u001a\u0001\u0006\u0003std\u0004!testB 解析指定库的binlog\n范例：利用 mysqlbinlog -d 参数解析指定库的binlog日志\nbash 1 2 3 4 5 6 7 8 9 10 11 $ cat -n 10 1.sql 29 # at 313 # \u0026lt;=对应的位置 # #170130 13:03:49 2017年01月30日 13:03:49秒 对应的时间 # end_log_pos 结束的位置，对应下面开始的位置 30 #170130 13:03:49 server id 1 end_log_pos 341 Intvar 31 SET INSERT_ID=3000001/*!*/; 32 # at 341 33 #170130 13:03:49 server id 1 end_log_pos 543 Query thread_id=1 exec_time=0 error_code=0 34 use `test`/*!*/; 35 SET TIMESTAMP=1485752629/*!*/; 36 insert into test1(num1,num2,num3) values( NAME_CONST(\u0026#39;rand_num1\u0026#39;,3127167), NAME_CONST(\u0026#39;rand_num2\u0026#39;,3952885), NAME_CONST(\u0026#39;rand_num3\u0026#39;,382922)) 结论：mysqlbinlog 工具分库导出 binlog，如果使用 -d 参数，那更新数据时，必须有use database，才能分出指定库的binlog，例如：\nbash 1 2 use test; insert into test values (1,\u0026#39;test\u0026#39;); 官方资料：mysqlbinlog — Utility for Processing Binary Log Files\nbash 1 2 --database=db_name, -d db_name This option causes mysqlbinlog to output entries from the binary log (local log only) that occur while db_name is been selected as the default database by USE. 按照位置截取：精准，时间长\nbash 1 2 3 4 5 6 7 mysqlbinlog mysqlbin.000020 --start-position=200 --stop-position=450 -r pos.sql # 指定开始位置，不指定结束位置。结束位置为文件结尾 mysqlbinlog mysqlbin.000020 --start-position=200 -r pos.sql # 指定结束位置，不指定开始位置，开始位置为文件开头 mysqlbinlog mysqlbin.000020 --stop-position=450 -r pos.sql 按时间截取：模糊、不准，会丢失数据\nbash 1 2 3 4 mysqlbinlog mysql-bin.0020 \\ --start-datatime=\u0026#39;1912-10-10 10:10:10\u0026#39; \\ --stop-datetime=\u0026#39;2015-10-10 10:10:10\u0026#39; \\ -r time.sql mysqlbinlog命令 把 binlog 解析为sql语句（包含位置和时间点） -d 参数根据指定库拆分binlog（拆分单表binlog可通过SQL关键字过滤） 通过位置参数截取部分binlog：--start-position=111 --stop-position=500，精确定位取部分内容。 通过时间参数截取部分binglog：--start-datetime='2016-10-10 10:10:10' -r fileName，相当于重定向 “\u0026gt;” 解析 row 级别 binlog 日志方法 mysqlbinlog --base64-output=decode-rows -v mysql-bin.000004 binlog三种模式 Row Level\n不记录sql语句上下文相关信息，仅保存哪条记录被修改。\n优点：binlog中可以不记录执行的sql语句的上下文相关的信息，仅需要记录那一条记录被修改成什么了。所以rowlevel的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程，或function，以及trigger的调用和触发无法被正确复制的问题\n缺点：所有的执行的语句当记录到日志中的时候，都将以每行记录的修改来记录，这样可能会产生大量的日志内容,比如一条update语句，修改多条记录，则binlog中每一条修改都会有记录，这样造成binlog日志量会很大，特别是当执行alter table之类的语句的时候，由于表结构修改，每条记录都发生改变，那么该表每一条记录都会记录到日志中。\nStatement Level\n每一条会修改数据的sql都会记录在binlog中。\n优点：不需要记录每一行的变化，减少了binlog日志量，节约了IO，提高性能。(相比row能节约多少性能与日志量，这个取决于应用的SQL情况，正常同一条记录修改或者插入row格式所产生的日志量还小于Statement产生的日志量，但是考虑到如果带条件的update操作，以及整表删除，alter表等操作，ROW格式会产生大量日志，因此在考虑是否使用ROW格式日志时应该跟据应用的实际情况，其所产生的日志量会增加多少，以及带来的IO性能问题。)\n缺点：由于记录的只是执行语句，为了这些语句能在slave上正确运行，因此还必须记录每条语句在执行的时候的一些相关信息，以保证所有语句能在slave得到和在master端执行时候相同 的结果。另外mysql 的复制,像一些特定函数功能，slave可与master上要保持一致会有很多相关问题(如sleep()函数， last_insert_id()，以及user-defined functions(udf)会出现问题).\nMixed Level\n是以上两种level的混合使用，一般的语句修改使用statment格式保存binlog，如一些函数，statement无法完成主从复制的操作，则采用 row 格式保存binlog，MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式，也就是在Statement和Row之间选择一种。新版本的MySQL中队row level模式也被做了优化，并不是所有的修改都会以row level来记录，像遇到表结构变更的时候就会以statement模式来记录。至于update或者delete等修改数据的语句，还是会记录所有行的变更。\n生产环境如何选择binlog的模式\n互联网公司，使用MySQL的功能相对少（存储过程、触发器、函数），选择默认的语句模式。 公司如果用到使用MySQL的特殊功能（存储过程、触发器、函数）。则选择Mixed。 公司如果公道使用MySQL的特殊功能（存储过程、触发器、函数），又希望数据最大化一致，此时最好用row模式 设置MySQL binlog的格式 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 MariaDB \u0026gt;show global variables like \u0026#39;%binlog_format%\u0026#39;; +---------------+-----------+ | Variable_name | Value | +---------------+-----------+ | binlog_format | STATEMENT | +---------------+-----------+ MariaDB \u0026gt;set global binlog_format=\u0026#39;row\u0026#39;; MariaDB \u0026gt;show global variables like \u0026#39;%binlog_format%\u0026#39;; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | binlog_format | ROW | +---------------+-------+ 永久生效\nbash 1 2 3 4 log-bin=mysql-bin binlog_format=STATEMENT binlog_format=MIXED binlog_format=ROW 测试ROW模式下的binlog记录日志情况\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 22 # at 366 23 # at 1377 24 # at 2392 25 # at 3410 26 # at 4420 27 # at 5428 28 #170202 6:29:13 server id 1 end_log_pos 366 Table_map: `test`.`test1` mapped to number 35 25 # at 3410 26 # at 4420 27 # at 5428 28 #170202 6:29:13 server id 1 end_log_pos 366 Table_map: `test`.`test1` mapped to number 35 29 #170202 6:29:13 server id 1 end_log_pos 1377 Update_rows: table id 35 30 #170202 6:29:13 server id 1 end_log_pos 2392 Update_rows: table id 35 31 #170202 6:29:13 server id 1 end_log_pos 3410 Update_rows: table id 35 32 #170202 6:29:13 server id 1 end_log_pos 4420 Update_rows: table id 35 33 #170202 6:29:13 server id 1 end_log_pos 5428 Update_rows: table id 35 34 #170202 6:29:13 server id 1 end_log_pos 6002 Update_rows: table id 35 flags: STMT_END_F 35 ### UPDATE `test`.`test1` 36 ### WHERE 37 ### @1=1 38 ### @2=\u0026#39;3941282\u0026#39; 39 ### @3=\u0026#39;3149717\u0026#39; 40 ### @4=\u0026#39;3924740\u0026#39; 41 ### SET 42 ### @1=1 43 ### @2=\u0026#39;3941282\u0026#39; 44 ### @3=\u0026#39;test\u0026#39; 45 ### @4=\u0026#39;3924740\u0026#39; 46 ### UPDATE `test`.`test1` 47 ### WHERE 48 ### @1=2 49 ### @2=\u0026#39;174549\u0026#39; 50 ### @3=\u0026#39;9098525\u0026#39; 51 ### @4=\u0026#39;4968976\u0026#39; 52 ### SET 53 ### @1=2 54 ### @2=\u0026#39;174549\u0026#39; 55 ### @3=\u0026#39;test\u0026#39; 56 ### @4=\u0026#39;4968976\u0026#39; 57 ### UPDATE `test`.`test1` 58 ### WHERE 59 ### @1=3 60 ### @2=\u0026#39;7549308\u0026#39; 61 ### @3=\u0026#39;2839610\u0026#39; 62 ### @4=\u0026#39;1550126\u0026#39; 63 ### SET 64 ### @1=3 65 ### @2=\u0026#39;7549308\u0026#39; 66 ### @3=\u0026#39;test\u0026#39; 67 ### @4=\u0026#39;1550126\u0026#39; statement日志记录\nbash 1 2 3 4 5 6 7 8 9 10 11 # at 313 #170202 6:36:27 server id 1 end_log_pos 403 Query\tthread_id=1\texec_time=0\terror_code=0 use `test`/*!*/; SET TIMESTAMP=1485988587/*!*/; update test1 set num3=\u0026#39;aa1\u0026#39; /*!*/; # at 403 #170202 6:36:27 server id 1 end_log_pos 430 Xid = 11 COMMIT/*!*/; DELIMITER ; # End of log file 1197\nbash 1 ERROR 1197 (HY000) at line 4: Multi-statement transaction required more than \u0026#39;max_binlog_cache_size\u0026#39; bytes of storage; increase this mysqld variable and try again 原因：row 格式的 binlog 的特点：在 row 模式下，所有的执行的语句当记录到日志中的时候，都将以每行记录的修改来记录，这样可能会产生大量的日志内容。所以会造成binlog cache因为过小而中断。\n解决设置大的cache\n","permalink":"https://www.161616.top/ch04-mysql-log/","summary":"错误日志 error log MySQL错误日志记录MySQL服务进程mysqld在启动/关闭或运行过程中遇到的错误信息\n错误日志配置\n在配置文件中调整方法，当然可以在启动时加入启动参数\nbash 1 2 [mysqld_safe] log-error=/data/3306/mysql_3306.err 启动MySQL命令里加入\nbash 1 2 3 4 5 6 7 8 9 10 /app/mysql/bin/mysqld_safe \\ --defaults-file=/data/3306/my.cnf \\ --log-error=/data/3306/mysql_3306.err MariaDB\u0026gt; show variables like \u0026#34;%log_error%\u0026#34;; +-------------------+---------------------------+ | Variable_name | Value\t| +-------------------+---------------------------+ | log_error | /data/3306/mysql_3306.err | +-------------------+---------------------------+ 遇到数据库启动不了\n先把日志文件备份并清空启动一下mysql服务后再查看日志文件，看报有什么错误\nbash 1 2 3 InnoDB: The error means mysqld does not have the access rights to InnoDB: the directory 然后查看mysql3306目录下文件权限\n普通查询日志 general query log 普通查询日志 (general query log)：记录客户端链接信息和执行的SQL语句信息。","title":"ch04 - MySQL数据库服务日志类型"},{"content":"Linux文件数据同步方案 在讲解MySQL主从复制之前，先回忆下，前面将结果的普通文件（磁盘上的文件）的同步方法。\n文件级别的异机同步方案\nscp/sftp/nc命令可以实现远程数据同步。 搭建ftp/http/svn/nfs服务器，然后在客户端上也可以把数据同步到服务器。 搭建samba文件共享服务，然后在客户端上也可以把数据同步到服务器。 利用rsync/csync2/union等均可以实现数据同步。 提示：union可实现双向同步，csync2可实现多机同步。\n​\t以上文件同步方式如果结合定时任务或innotify sersync等功能，可以实现定时以及实时的数据同步。\n扩展思想：文件级别复制也可以利用mysql,mongodb等软件作为容器实现。\n扩展思想：程序向两个服务器同时写数据，双写就是一个同步机制。\n​\t特点：简单、方便、效率和文件系统级别要差一些，但是被同步的节点可以提供访问。\n软件的自身同步机制（mysql、oracle、mongdb、ttserver、redis\u0026hellip;..），文件放到数据库，听不到从库，再把文件拿出来。 文件系统级别的异机同步方案\ndrbd基于文件系统同步，相当于网络RAID1，可以同步几乎任何业务数据。\nmysql数据的官方推荐drbd同步数据，所有单点服务例如：NFS，MFS(DRBD)，MySQL等度可以用drbd做复制，效率很高，缺点：备机服务不可用。\n数据库同步方案\n自身同步机制：mysql relication，（逻辑的SQL重写）物理复制方法drbd（丛库不提供读写）。 第三方drbd MySQL主从复制概述 MySQL的主从复制方案，和上述文件及文件系统级别同步是类似的，都是数据的传输。只不过MySQL无需借助第三方工具，而是其自带的同步复制功能，另外一点，MySQL的主从复制并不是磁盘上文件直接同步，而是逻辑的binlog日志绒布到本地在应用执行的过程\nMySQL主从复制是一个异步的复制过程（虽然一般情况下感觉是实时的），数据将从一个MySQL数据库（Master）复制到另一个数据库（Slave），在 mater 与 Slave之 间实现整个主从复制的过程是由三个线程参与完成的。其中有两个线程( SQL和IO )在Slave端，另外一个线程（I/O）在Master端。\n要实现MySQL的主从复制，首先必须打开 Master 端的 binlog 记录功能，否则就无法实现。因为整个复制过程实际上就是Slave从Master端获取Binlog日志，然后在Slave上以相同顺序逐自获取 binlog 日志中所记录的各种SQL操作。\n要打开MySQL的binlog记录功能，可能通过在MySQL的配置文件 my.cnf 中的 mysqld 模块( [mysqld] )标识后的参数部分增加 “log-bin” 参数选项来实现，具体信息如下：\nbash 1 2 [mysqld] log-bin = /data/3307/mysql-bin 提示：log-bin需放置在[mysqld]标识后，否则会导致配置复制不成功。\nMySQL数据可支持单向、双向、链式级联等不同场景的复制。在复制过程中，一台服务器充当主服务器（Master），而一个或多个其他的服务器充当从服务器（Slave）。\n复制可以使单向：M==\u0026gt;S，也可以是双向 M\u0026lt;==\u0026gt;M，当然也可以多M环装同步等。\n如果设置了链式级联复制，那么，从（slave）服务器本身除了充当从服务器外，也会同时充当其下面从服务器的主服务器。链式级联复制类似 A==\u0026gt;B==\u0026gt;C==\u0026gt;D 的复制形式。\n下面是MySQL各种同步架构的逻辑图。\n单向主从复制逻辑图，次架构只能在Master端进行数据写入。官方给出Slave最多9，工作中不要超过5\n双向主主同步逻辑图，次架构可以再Master1端或Master2端进行数据写入\n线性级联单向双主同步逻辑图，此架构只能在Master1端进行数据写入\n缺陷：1 ==\u0026gt;3 之间会存在延迟\n环装级联单向多主同步逻辑图，任意一个点都可以写入数据。\n环装级联单向多主多从同步逻辑图，次架构只能在任意一个Master端进行数据写入。\n应对读比较多的情况，将所有的从做成负载均衡，三个主做负载均衡。如果其中一个主断掉，其从节点就成了旧数据\nMySQL官方同步架构图\nMySQL主从复制过程原理 下面简单描述下MySQL Replication的复制原理过程\n在 Slave 服务器上执行 start slave 命令开启主从复制开关，主从复制开始进行。 此时，Slave服务器的 I/O线程 会通过在Master上已经授权的复制用户权限请求连接 Master 服务器，并请求从指定 binlog 日志文件的指定位置（日志文件名和位置就是在配置主从复制服务时执行change master命令指定的）之后开始发送binlog日志内容。 Master服务器接受到来自 Slave服务器 I/O线程 的请求后，其上负责复制的I/O线程会根据Slave服务器I/O线程请求的信息分批读取指定 Binlog 日志文件指定位置之后的 Binlog 日志信息，然后返回给 Slave 端的 I/O线程。返回的信息中除了 Binlog 日志内容外，还有在Master服务器端记录的新的Binlog文件以及在新的Binlog中的下一个指定更新位置。 当Slave服务器的 I/O线程 发送的日志内容及日志文件及位置后，会将 Binlog日志 内容依次写入到 Slave 端自身的 Relay Log（即中继日志）(mysql-relay-bin_xxxxx）端新binlog日志时，能告诉master端服务器需要从新Binlog日志的指定文件及位置开始请求新的binlog日志内容 Slave服务器端的SQL线程会实时地检测本地 Relay Log 中 I/O线程 新增加的日志内容，然后及时地把 Relay Log 文件中的内容解析成SQL语句，并在自身 Slave服务器 上按解析SQL语句的位置顺序执行应用这些SQL语句，并记录当前应用中继日志的文件名及位置点在 **relay-log.info**中。 经过了上面的过程，就可以确保在Master端和Slave端执行了同样的SQL语句。当复制状态正常的情况下，Master端和Slave端的数据是完全一样的。当然，MySQL的复制机制也有一些特殊情况，具体请参考官方的说明。\n图：MySQL主从复制基本原理\nMySQL主从复制原理小结：\n主从复制是异步的逻辑SQL语句级的复制。 复制时，主库有一个I/O线程，从库有两个线程I/O和SQL线程。 实现主从复制的必要条件是主库要开启记录binlog功能。 作为复制的所有MySQL节点的server-id都不能相同 binlog文件只记录对数据库有关的SQL（来自主数据库内容的变更），不记录任何查询（select show）语句。 MySQL主从复制配置 环境准备 MySQL主从复制实践对环境的要求比较简单，可以是单机单数据库多实例的环境，也可以是两台服务器，每个机器上独立数据库的环境。\nbash 1 2 3 4 $ ss -lnt State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 \\*:3306 \\*:\\* LISTEN 0 128 \\*:3307 \\*:\\* 提示：这里把3306实例作为主库，3307实例作为从库，如果根据前面的内容配置了mysql多实例环境，直接开启多实例环境使用即可。\n定义主从复制需要的服务器角色\n主库及从库IP、端口信息：\nmaster:\t192.168.2.110:3306 slave:\t192.168.2.110:3307 这里的主从复制技术是针对前面的内容以单机数据库多实例环境来讲的。一般情况下，小企业在做常规的主从复制时，主从服务器多数在不同的机器上，并且监听的端口均为默认的3306。虽然不在同一个机器上，但是步骤和过程却是一样的。\n主库部分配置 设置server-id值并开启binlog功能参数\n根据前文介绍的MySQL主从复制原理我们知道，要实现主从复制，关键是要开启binlog日志功能，所以，首先来打开主库的binlog参数。\nbash 1 2 3 [mysqld] server-id=1\t# \u0026lt;=用于同步的每台机器或实例server-id都不能相同\tlog-bin=/data/3306/mysql-bin\t#\u0026lt;=该部分可省略 提示：\n上面的两个参数要放在my.cnf中的 [mysqld] 模块下，否则会出错。\nserver-id 的值使用服务器ip地址最后一个小数点后面数字如：19，目的是避免不同机器或实例ID重复（不适合多实例） 0 \u0026lt; server-id \u0026lt; 232-1的自然数\n要先在 my.cnf 配置文件中查找相关参数，并按要求修改。若不存在再添加参数。切记参数不能重复\n修改my.cnf配置后需要重启数据库，命令为: /data/3306/mysql restart，要确认真正重启了\n检查配置参数后的结果\nbash 1 2 3 $ grep -E \u0026#39;log-bin|server-id\u0026#39; my.cnf log-bin = /data/3306/mysql-bin server-id = 1 登陆数据库检查参数的更改情况（需重启）\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 mysql\u0026gt; show variables like \u0026#39;server_id\u0026#39;; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | server_id\t| 1 | +---------------+-------+ mysql\u0026gt; show variables like \u0026#39;log_bin\u0026#39;; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | log_bin | ON | +---------------+-------+ 在主库上建立用于主从复制的账号\n根据主从复制的原理，从库要想和主库同步，必须有一个可以连接主库的账号，并且这个账号是主库上创建的，权限是允许主库的从库连接并同步数据。\n登陆3306实例猪数据库\nbash 1 mysql -uroot -p111 -S /data/3306/mysql.sock 建立用于从库复制的账号rep：\nbash 1 2 3 4 5 6 grant replication slave on *.* to \u0026#39;rep\u0026#39;@\u0026#39;192.168.2.%\u0026#39; identified by \u0026#39;1234\u0026#39;; # relication slave为mysql同步的必须权限，此处不要授权all # *.*表示所有库所有表，也可以指定具体的库和表进行复制，例如shop.test # rep@\u0026#39;192.168.2.%\u0026#39;为同步账号。192.168.2.%为授权网段，使用%表示192.168.2.0网段以rep用户访问 # identified by \u0026#39;1234\u0026#39; 为密码。生产环境要使用复杂密码 mysql\u0026gt; flush privileges; 检查主库复制账号权限\nbash 1 2 3 mysql\u0026gt; show grants for rep@\u0026#39;192.168.2.%\u0026#39;\\G *************************** 1. row *************************** Grants for rep@192.168.2.%: GRANT REPLICATION SLAVE ON *.* TO \u0026#39;rep\u0026#39;@\u0026#39;192.168.2.%\u0026#39; IDENTIFIED BY PASSWORD \u0026#39;*A4B6157319038724E3560894F7F932C8886EBFCF\u0026#39; 实现对数据库锁表只读\n1. 对主数据库锁表只读（当钱窗口不要关闭）的命令：\nbash 1 2 flush table with read lock; # 锁表后新建窗口查看mysql此时是不能插入数据的 提示：在引擎不同的情况，这个锁表命令的时间会受下面参数的控制。锁表时，如果草果设置时间不操作会自动解锁。\n默认情况下自动解锁的市场参数值如下：\nbash 1 2 3 4 5 6 7 mysql\u0026gt; show variables like \u0026#39;%timeout%\u0026#39;; +----------------------------+----------+ | Variable_name | Value | +----------------------------+----------+ | interactive_timeout | 28800 | | wait_timeout | 28800 | +----------------------------+----------+ 2. 锁表后查看主库状态。可通过当前binlog日志文件名和二进制binlog日志偏移量来查看\n注意，show master status;命令显示的信息要记录在案，后面的从库导入全备后，继续和主库复制时就是要从这个位置开始。\nbash 1 2 3 4 5 6 7 8 mysql\u0026gt; show master status; +------------------+----------+--------------+------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | +------------------+----------+--------------+------------------+ | mysql-bin.000319 | 331 | | | +------------------+----------+--------------+------------------+ mysql -uroot -p111 -S /data/3306/mysql.sock -e \u0026#39;show master status;\u0026#39; 3. 锁表后，一定要单开一个新的SSH窗口，导出数据库的所有数据，如果数据量很大( 50GB+ )，并且允许停机，可以停库直接打包数据文件迁移，那样还快些\nbash 1 2 3 mysqldump -uroot -p111 \\ -S /data/3306/mysql.sock \\ --events -A -B|gzip \u0026gt;bak.`date +%F.sql.gz` 为了确保导出数据期间，数据库没有数据插入，导库完毕可以再检查下主库状态信息。\nbash 1 2 3 4 5 6 7 $ mysql -uroot -p111 -S /data/3306/mysql.sock -e \u0026#39;show master status;\u0026#39; +------------------+----------+--------------+------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | +------------------+----------+--------------+------------------+ | mysql-bin.000319 | 331 | | | +------------------+----------+--------------+------------------+ # 若无特殊情况，binlog文件及位置点和锁表后导出数据前是一致的，即没有变化的。 导出数据后，解锁主库，恢复可写，因为主库还要对外提供服务，不能一直锁定不让用户访问。\nbash 1 2 unlock tables; # 此时新窗口的写入语句会立刻写入数据 实际上做从库前，无论主库更新多少数据库，最后从库都可以从上面show master status的位置很快赶上主库的进度的。\n将导出数据迁移到从库\n常用scp rsync等，将备份的数据往异地拷贝。\n这里是多实例的主从配置，mysqldum p备份的3306实例的数据和要恢复的3307实例在一台机器上，因此无需异地复制拷贝了，\n1. 设置server-id并关闭binlog功能\n数据库的server-id一般在一套主从复制体系内是唯一的，这里从库的server-id要和主库及其他的从库不同，并且要注释掉从库的binlog参数，如果从库不做级联复制，并且不做备份用，就不要开启binlog，开启了反而会增加从库磁盘I/O等压力。\n如下两种情况需要打开 binlog 功能，记录数据更新的SQL语句\n级联同步 A=\u0026gt;B=\u0026gt;C中间的B时，就要开启binlog\n在从库做数据库备份，数据库备份必须要有全备和binlog日志，才是完整的备份。\nbash 1 2 3 $ grep -E \u0026#39;server-id|log-bin\u0026#39; my.cnf #log-bin = /data/3307/mysql-bin server-id = 3 提示：\n参数要放在 my.cnf 中的 [mysqld] 模块下，否则会出错。\nserver-id的值可使用服务器ip地址最后一个数字。\n要先在文件中查找相关参数按要求修改。若发现不存在，再添加参数，切记参数不能同步。\n修改完配置后需重启数据库。\n2. 登陆数据库查看参数改变情况\nbash 1 2 3 4 5 6 $ mysql -uroot -S /data/3307/mysql.sock -e \u0026#39;show variables like \u0026#34;log_bin\u0026#34;;\u0026#39;; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | log_bin | OFF | +---------------+-------+ 3. 将全备恢复到从库\nbash 1 mysql -uroot -p111 -S /data/3307/mysql.sock \u0026lt; bac.sql 提示：如果备份时使用了-A参数，则在还原数据到3307实例时，登陆3307实例的密码也回合3306主库一致，因为3307的授权表mysql也被覆盖了\n4. 登陆3307从库，配置复制参数\ntext 1 2 3 4 5 6 7 8 9 10 11 CHANGE MASTER TO MASTER_HOST=\u0026#39;192.168.2.110\u0026#39;, MASTER_PORT=3306, MASTER_USER=\u0026#39;rep\u0026#39;, MASTER_PASSWORD=\u0026#39;1234\u0026#39;, MASTER_LOG_FILE=\u0026#39;mysql-bin.000322\u0026#39;, MASTER_LOG_POS=188; CHANGE MASTER TO MASTER_LOG_FILE=\u0026#39;mysql-bin.000322\u0026#39;, MASTER_LOG_POS=107; 提示：字符串用单引号括起来，数值不用引号，密码需要。内容前后不能用空格\n主从复制是不是成功，其中最关键的为下面三项状态参数：\nbash 1 2 3 4 5 $ mysql -uroot -S /data/3307/mysql.sock -e \u0026#39;show slave status\\G \u0026#39;|grep -E \u0026#39;IO_Running|SQL_Running|Seconds_Behind\u0026#39; Slave_IO_Running: Yes Slave_SQL_Running: Yes Seconds_Behind_Master: 0 Slave_IO_Running: Yes，这个是I/O线程状态，I/O线程负责从从库去主库读取binlog日志，并写入从库的中继日志中，状态为Yes表示I/O线程工作正常。 Slave_SQL_Running: Yes，这个是SQL线程状态，SQL线程负责读取中继日志(relay-log)中的数据并转换为SQL语句应用到丛库数据库中，状态为Yes表示I/O线程工作正常。 Seconds_Behind_Master: 0，这个是在复制过程中，丛库比主库延迟的秒数，这个参数很重要，但企业里更准确的判断主从延迟的方法为：在主库写时间戳，然后从库读取时间戳和当前数据库时间的进行比较，从而认定是都延迟。 有关show slave status结果的说明。请参考MySQL手册。\nMySQL主从复制配置步骤小结 MySQL主从复制配置完整步骤如下：\n准备两台数据库环境或者单台多实例环境，确定能正常启动和登陆。 配置my.cnf文件：主库配置 log-bin 和 server-id 参数，从库配置 server-id ，该值不能和主库及其他从库一样，一般不开启从库log-bin功能。注意，配置参数后要重启才能生效。 登陆主库增加从库连接主库同步的账户，例如：rep，并授权replication slave同步的权限。 登陆主库，整库锁表 flush table with read lock（关闭窗口后失效，超时时间到了锁表也失效），然后show master status 查看 binlog 的位置状态。 新开窗口，在Linux命令行备份导出原有的数据库数据，并拷贝到丛库所在的服务器目录。如果数据库数据量很大，并且允许停机，可以停机打包，而不用mysqldump。 导出主库数据后，执行 unlock tablesl; 解锁主库。 把主库导出的数据库恢复到从库。 根据主库的show master status 查看到 binlog 的位置状态，在从库执行 change master to 语句 从库开启复制开关即执行 start slave。 从库 show slave status\\G 快速配置MySQL主从复制\n步骤\n安装好要配置从库的数据库，配置好 log-bin 和 server-id 参数 无需配置主从库 my.cnf 文件，主库 log-bin 和 server-id 参数默认就是配置好的。 登陆主库，增加从库链接主库同步的账户，例如：rep，并授权 replication slave 同步的权限。 使用在半夜通过定时任务备份 mysqldump 带 -x 和 --master-date=1 的命令及参数的全备数据，恢复到从库 在从库执行 change master to.. 语句，无需 binlog 文件及对应位置点。 从库开启同步开关，start slave 从库 show slave status\\G，检查同步状态，并在主库进行更新测试。 MySQL主从复制线程状态说明及用途 MySQL主从复制主库I/O线程状态说明 登陆主数据库查看MySQL线程的同步状态。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 mysql\u0026gt; show processlist\\G; *************************** 1. row *************************** Id: 7 User: rep Host: 192.168.2.110:39610 db: NULL Command: Binlog Dump Time: 57983 State: Master has sent all binlog to slave; waiting for binlog to be updated Info: NULL *************************** 2. row *************************** Id: 11 User: rep Host: 192.168.2.110:39614 db: NULL Command: Binlog Dump Time: 21942 State: Master has sent all binlog to slave; waiting for binlog to be updated Info: NULL *************************** 3. row *************************** Id: 16 User: root Host: localhost db: information_schema Command: Query Time: 0 State: NULL Info: show processlist 3 rows in set (0.00 sec) ERROR: No query specified # 上述两个从库，每个从库对应一个I/O线程 提示：上述状态的意思是线程已经从binlog日志读取所有更新，并已经发送到了从数据库服务器，线程现在为空闲状态，等待由主服务器上二进制日志中的新事件更新\n下表列出主服务器的 binlog Dump线程中State列的最常见状态。如果没有在主服务器上看见任何Binlog Dump线程，则说明复制没有在运行，二进制binlog日志由各种事件组成，一个事件会通常为一个更新加一些其他信息。\n状态 说明 Sending binlog event to slave 线程已经从二进制binlog读取了一个事件并且正将它发送到从服务器 Finished reading on binlog switching to next binlog 线程已经读完二进制binlog日志文件，并且正在打开下一个要发送到从服务器的binlog日志文件 Has sent all binlog to slave;waiting for binlof to be update 线程已经从binlog日志读取所有的更新并已经发送到了从数据库服务器。线程现在为空闲状态，等待由主服务器上二进制binlog日志中的新事件更新。 Waiting to finalize termination 线程停止时发生了一个简单的状态 登陆从数据库查看mysql线程工作状态，从库有两个线程，即I/O和SQL线程。\n下面是从I/O线程的状态。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 mysql\u0026gt; show processlist\\G *************************** 1. row *************************** Id: 7 User: system user Host: db: NULL Command: Connect Time: 33004 State: Waiting for master to send event Info: NULL *************************** 2. row *************************** Id: 8 User: system user Host: db: NULL Command: Connect Time: 3510 State: Slave has read all relay log; waiting for the slave I/O thread to update it Info: NULL *************************** 3. row *************************** 下表列出了从库服务器的I/O线程的state列的最常见的状态。该状态也出现在Slave_IO_State列，有show slave status;显示\n从库I/O线程工作状态 解释说明 Connecting to master 线程正试图连接主服务器 Checking master version 同主服务器之间建立连接后临时出现的状态 Registering slave on master Requesting binlog dump 建立同主服务器之间的连接后立即临时出现的状态。线程向主服务器发送一条请求，索取从请求的二进制binlog日志文件名和位置开始的二进制binlog日志的内容。 Waiting to reconnect after a failed binlogdump request 如果二进制binlog日志转储请求失败，线程进入睡眠状态，然后定期尝试重新连接。可以使用\u0026ndash;master-connect-retry选项指定重试之间的间隔。 Reconnect after a failed binlog dump request 线程正尝试重新连接主服务器。 Waiting for master to sent event 线程已经连接上主服务器，正等待二进制binlog日志事件到达。 Queueing master event to the relay log 线程已经读取一个事件，正将它复制到中继日志供SQL线程来处理 Reconnecting after failed master event read 线程正尝试重新连接主服务器。当连接重新建立后，状态变为 Waiting for master to send event 下面是从库SQL线程的状态\nbash 1 2 3 4 5 6 7 8 9 *************************** 2. row *************************** Id: 8 User: system user Host: db: NULL Command: Connect Time: 5460 State: Slave has read all relay log; waiting for the slave I/O thread to update it Info: NULL 从库SQL线程状态 解释说明 Reading event from the relay log 线程已经从中继日志读取一个事件，可以对事件进行处理了。 Has read all relay log;waiting for the slave I/O thread to update it 线程已经处理了中继日志文件中的所有事件，现在正等待I/O线程将新事件写入中级日志。 Waiting for slave mutex on exit 线程停止时发生的一个很简单的状态。 更多状态在mysql手册6.3章节\n查看MySQL线程同步状态的用途 故障1：主库show master status;没返回状态结果\nbash 1 2 mysql\u0026gt; show master status; Empty set (0.00 sec) 解答：上述问题原因是主库binlog功能没有开启或没生效\n故障2：出现Last_IO_Error:Got fatal error 1236 from master when reading date from binary log:\u0026rsquo;\u0026rsquo;\nbash 1 Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: \u0026#39;Could not find first log file name in binary log index file\u0026#39; 原因：\n主库停机导致binlog错误 从库执行change master命令时某一个参数的值多了空格，因而产生错误 解决方法\nbash 1 2 3 4 flush logs; show master status; slave start; show slave status \\G 故障3：Fatal error: The slave I/O thread stops because master and slave have equal MySQL server ids; bash 1 Last_IO_Error: Fatal error: The slave I/O thread stops because master and slave have equal MySQL server ids; these ids must be different for replication to work (or the --replicate-same-server-id option must be used on slave but this does not always make sense; please check the manual before using it). 原因sever-id与主一致，更改后重启服务器恢复。\n工作中MySQL从库停止复制故障案例 模拟重现故障的能力是运维人员最重要的能力。先从从库创建一个库，然后去主库创建同名的库来模拟数据冲突。\nbash 1 2 3 4 5 6 7 8 9 mysql\u0026gt; show slave status\\G; .... .... Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 1007 Last_SQL_Error: Error \u0026#39;Can\u0026#39;t create database \u0026#39;test123\u0026#39;; database exists\u0026#39; on query. Default database: \u0026#39;test123\u0026#39;. Query: \u0026#39;create database test123 default character set utf8\u0026#39; Replicate_Ignore_Server_Ids: Master_Server_Id: 1 对于该冲突，解决方法1：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 stop slave; # 临时停止同步开关 set global sql_slave_skip_counter=1;# 将同步指针想下移动一个，如果多次不同步，可以重复操作 start slave; mysql\u0026gt; show slave status\\G .... .... Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 1 对于普通的互联网业务，上述的移动指针的命令操作带来的问题不是很大。当然要确认不影响公司业务的前提下。\n若是在企业场景下，对当前业务来说，解决主从同步比主从不一致更重要，如果主从数据一致也是很重要的，那就再找个时间恢复下这个从库。\n主从数据不一致更重要还是保持主从同步持续状态更重要，则要根据业务选择。\n这样Slave就会和Master同步了，其关键点为：\nbash 1 2 3 Slave_IO_Running:Yes Slave_SQL_Running:Yes Senconds_Behind_Master # 是否为0 0表示已经同步状态 提示：``set global sql_slave_skip_counter=n` n取值\u0026gt;0，忽略执行N个更新。\n解决方法2：根据可以忽略的错误号事先在配置文件中配置，跳过指定的不同映像业务数据的错误，例如：\nbash 1 slave-skip-errors=1032,1062,1007 提示：类似由于入库重复导致的失败可以忽略，其他情况是不是可以忽略需要根据不同送死的具体业务来评估\nbash 1 2 3 4 5 6 7 8 9 10 11 12 mysql\u0026gt; show slave status\\G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event .... .... Slave_IO_Running: Yes Slave_SQL_Running: No Replicate_Do_DB: .... .... Last_SQL_Errno: 1007 Last_SQL_Error: Error \u0026#39;Can\u0026#39;t create database \u0026#39;zhangsan\u0026#39;; database exists\u0026#39; on query. Default database: \u0026#39;zhangsan\u0026#39;. Query: \u0026#39;create database zhangsan\u0026#39; 其他可能引起复制故障的问题：\nMySQL自身的原因及认为重复插入数据。\n不同的数据库版本会引起不同步，低版本到高版本可以，但是高版本不能往低版本同步。\nMySQL的运行错误或者程序BUG。\nbinlog记录模式，例如：row level模式就比默认的语句模式要好。\n让MySQL从库记录binlog日志方法 从库需要记录binlog的应用场景为：当前的从库还要作为其他从库的主库，例如：级联复制或双主互为主从场景的情况下。从库记录binlog日志的方法：\n在从库的 my.cnf 中加入如下参数，然后重启服务生效即可。\ntext 1 2 3 log-slave-updaes # 必须要有这个参数 log-bin=/data/3306/mysql-bin expire_logs_days=7 # binlog日志过期参数，过期自动删除 MySQL主从复制集群架构的数据备份策略 有了主从复制了，还需要做定时全量加增量备份么？答案是肯定的\n因为，如果主库有语句级误操作（例如：drop database test; ），从库也会这行 drop database test; ，这样MySQL主从库就都删除了该数据。\n把从库作为数据库备份服务器时，备份策略如下：\n高并发业务场景备份时，可以选择在一台数据库上备份（Slave5），把从库作为数据库备份服务器时需要在从库开启 binlog 功能，如图所示 步骤如下\n选择一个不对外提供服务器的从库，这样可以确保和主库更新最接近，专门做数据备份用。 开启从库binlog功能。 备份时可以选择只停止 SQL 线程，停止应用SQL语句到数据库，I/O线程保留工作状态，执行命令为 stop slave sql_thread; ，备份方式可以采取 mysqldump 逻辑备份或者直接物理备份，例如：cp tar（打包目录）工具，或 xtrabackup（第三方的物理备份软件）进行备份，逻辑备份和物理备份的选择，一般是根据总的本分数据了多少进行选择，数据量低于20G，建议选择 mysqldump 逻辑备份方法，安全稳定，最后把全备和 binlog 数据发总到备份服务器上留存\nMySQL主从复制延迟问题原因及解决方案 问题一：一个主库的从库太多，导致复制延迟\n建议从库数量3-5个为宜，要复制的节点数量过多，会导致复制延迟。\n问题二：从库硬件比主库差，导致复制延迟\n查看master和slave的系统配置，可能会因为机器配置的问题，包括磁盘IO、CPU内存等各方面因素曹成复制的延迟，一般发生在高并发大数据量写入场景。\n问题三：慢查询SQL语句过多\n假如一条SQL语句，执行时间是20秒，那么从执行完毕，到从库上能查到数据也至少是20秒，这样就延迟了20秒\nSQL语句的优化一般要作为常规工作不断的监控和优化，如果是单个SQL的写入时间长，可以修改后分多次写入，通过查看慢查询日志或 show full processlist 命令找出执行时间长的查询语句或者大的事务。\n问题四：主从复制的设计问题\n例如，主从复制单线程，如果主库写并发太大，来不及传送到从库就会导致延迟，更高版本的MySQL可以支持多线程复制(MySQL5.6 Mariadb10.0)，门户网站则会自己开发多线程同步功能。\n问题五：主从之间的网络延迟\n主从库的网卡，网线，链接的交换机等网络设备都可能成为复制的瓶颈，导致复制延迟。另外，跨公网主从复制很容易导致主从复制延迟。\n问题六：主库读写压力大，导致复制延迟\n主库的硬件要搞好一些，架构的前端要加buffer以及缓存层。\n通过read-only让主库只读访问 read-only参数选项可以让出服务器只允许来自从服务器线程或具有SUPER权限的数据库用户进行更新。可以确保从服务器不接受来自用户端的非法用户更新。\nread-only参数允许数据库更新的条件为：\n具有SUPER权限的用户可以更新，不收read-only参数影响。例如：管理员root（注：工作中用户连接的账号授权不要给all，这样可以防止用户写数据） 来自从服务器线程可以更新，不收read-only参数影响，例如，前文的rep用户，在生产环境中，可以再从库Slave中使用read-only参数，确保从库数据不被非法更新。 read-only参数配置方法如下 方法一：启动数据库时直接带\u0026ndash;read-only参数启动或重启使用\nbash 1 2 3 killall mysqld mysqladmin -uroot -p111 -S /data/3307/mysql.sock shutdown mysqld_safe --defaults-file=/data/3307/my.cnf --read-only \u0026amp; 方法二：在my.cnf中[mysqld]模块下加read-only参数，然后重启数据库，配置如下\ntext 1 2 [mysqld] read-only Web用户专业设置方案：MySQL主从复制读写分离集群 专业的运维人员提供给开发人员的读写分离账户设置方法如下：\n访问主库和从库使用一套用户密码，例如：用户为web，密码为111 即使访问IP不同，端口也尽量相同（3306）。例如：写库VIP为10.0.0.7，读库VIP10.0.0.8。 除了IP没办法修改之外，要尽量为开发人员提供方便，如果是数据库前段有DAL层（dbproxy），还可以只给开发人员一套用户、密码、IP、端口，这样就更专业了，剩下的都由鱼尾人员搞定。\n下面是授权web连接用户访问的方案：MySQL主从复制读写分离集群。\n方法1：从库和主库使用不同的用户，授权不同的权限\n主库上对web_m用户授权\n用户：web_m，密码：111 端口：3306 主库VIP：10.0.0.7\n权限：select insert update delete\n命令：grant select,insert,update,delete on web.* to web_m@10.0.0.% identified by '111'\n主库上对web_s用户授权\n用户：web_s，密码：111 端口：3306 主库VIP：10.0.0.8\n权限：select\n命令：grant select on web.* to web_m@10.0.0.% identified by '111'\n提示：此方法不够专业，但是可以满足开发需求\n方法2：主库和从库使用相同的用户，但授予不同的权限\n主库上对web用户授权\n用户：web密码：111 端口：3306 主库VIP：10.0.0.7 权限：select insert update delete 命令：grant select,insert,update,delete on web.* to web_m@10.0.0.% identified by '111' 从库上对web用户授权\n用户：web_m，密码：111 端口：3306 主库VIP：10.0.0.7 权限：select 命令：grant select on web.* to web_m@10.0.0.% identified by '111' 提示：由于主库和从库是同步复制的，所以从库上的web用户会自动和主库一致，即无法实现只读select权限\n要实现方法2中的授权方案，有两个方法\n在主库上创建完用户和权限，从库上revoke收回对应更新权限（insert,update,delete）。 bash 1 revoke insert,update,delete on `web`.* from web@10.0.0.%; 忽略授权库mysql同步，主库的配置参数如下： text 1 2 3 binlog-ignore-db = mysql replicate-ignore-db = mysql # 参数两旁必须有空格 方法3：在从库上设置read-only参数，让从库只读\n从库主库：主库和从库使用相同的用户，授予相同的权限（非ALL权限）\n用户：web密码：111 端口：3306 主库VIP：10.0.0.7 权限：select insert update delete 命令：grant select,insert,update,delete on web.* to web_m@10.0.0.% identified by '111' 由于主库设置了read-only，非super权限是无法写入的，因此，通过read-only参数就可以很好的控制用户非法将数据写入从库。\n生产工作场景的设置方案如下：\n忽略主库mysql同步 主库和从库使用相同的用户，但授权不同的权限 在从库上设置read-only参数，让从库只读。 ","permalink":"https://www.161616.top/ch6-mysql-replication/","summary":"Linux文件数据同步方案 在讲解MySQL主从复制之前，先回忆下，前面将结果的普通文件（磁盘上的文件）的同步方法。\n文件级别的异机同步方案\nscp/sftp/nc命令可以实现远程数据同步。 搭建ftp/http/svn/nfs服务器，然后在客户端上也可以把数据同步到服务器。 搭建samba文件共享服务，然后在客户端上也可以把数据同步到服务器。 利用rsync/csync2/union等均可以实现数据同步。 提示：union可实现双向同步，csync2可实现多机同步。\n​\t以上文件同步方式如果结合定时任务或innotify sersync等功能，可以实现定时以及实时的数据同步。\n扩展思想：文件级别复制也可以利用mysql,mongodb等软件作为容器实现。\n扩展思想：程序向两个服务器同时写数据，双写就是一个同步机制。\n​\t特点：简单、方便、效率和文件系统级别要差一些，但是被同步的节点可以提供访问。\n软件的自身同步机制（mysql、oracle、mongdb、ttserver、redis\u0026hellip;..），文件放到数据库，听不到从库，再把文件拿出来。 文件系统级别的异机同步方案\ndrbd基于文件系统同步，相当于网络RAID1，可以同步几乎任何业务数据。\nmysql数据的官方推荐drbd同步数据，所有单点服务例如：NFS，MFS(DRBD)，MySQL等度可以用drbd做复制，效率很高，缺点：备机服务不可用。\n数据库同步方案\n自身同步机制：mysql relication，（逻辑的SQL重写）物理复制方法drbd（丛库不提供读写）。 第三方drbd MySQL主从复制概述 MySQL的主从复制方案，和上述文件及文件系统级别同步是类似的，都是数据的传输。只不过MySQL无需借助第三方工具，而是其自带的同步复制功能，另外一点，MySQL的主从复制并不是磁盘上文件直接同步，而是逻辑的binlog日志绒布到本地在应用执行的过程\nMySQL主从复制是一个异步的复制过程（虽然一般情况下感觉是实时的），数据将从一个MySQL数据库（Master）复制到另一个数据库（Slave），在 mater 与 Slave之 间实现整个主从复制的过程是由三个线程参与完成的。其中有两个线程( SQL和IO )在Slave端，另外一个线程（I/O）在Master端。\n要实现MySQL的主从复制，首先必须打开 Master 端的 binlog 记录功能，否则就无法实现。因为整个复制过程实际上就是Slave从Master端获取Binlog日志，然后在Slave上以相同顺序逐自获取 binlog 日志中所记录的各种SQL操作。\n要打开MySQL的binlog记录功能，可能通过在MySQL的配置文件 my.cnf 中的 mysqld 模块( [mysqld] )标识后的参数部分增加 “log-bin” 参数选项来实现，具体信息如下：\nbash 1 2 [mysqld] log-bin = /data/3307/mysql-bin 提示：log-bin需放置在[mysqld]标识后，否则会导致配置复制不成功。\nMySQL数据可支持单向、双向、链式级联等不同场景的复制。在复制过程中，一台服务器充当主服务器（Master），而一个或多个其他的服务器充当从服务器（Slave）。\n复制可以使单向：M==\u0026gt;S，也可以是双向 M\u0026lt;==\u0026gt;M，当然也可以多M环装同步等。\n如果设置了链式级联复制，那么，从（slave）服务器本身除了充当从服务器外，也会同时充当其下面从服务器的主服务器。链式级联复制类似 A==\u0026gt;B==\u0026gt;C==\u0026gt;D 的复制形式。\n下面是MySQL各种同步架构的逻辑图。\n单向主从复制逻辑图，次架构只能在Master端进行数据写入。官方给出Slave最多9，工作中不要超过5\n双向主主同步逻辑图，次架构可以再Master1端或Master2端进行数据写入\n线性级联单向双主同步逻辑图，此架构只能在Master1端进行数据写入\n缺陷：1 ==\u0026gt;3 之间会存在延迟\n环装级联单向多主同步逻辑图，任意一个点都可以写入数据。","title":"ch06 - MySQL主从复制"},{"content":"设置MySQL管理员账号密码 在安装MySQL数据库后，MySQL管理员的账号root密码默认为空，极不安全\n启动修改丢失的MySQL单实例root密码方法\n停止MySQL\nbash 1 /etc/init.d/mysqld stop 使用 \u0026ndash;skip-grant-tables启动mysql，忽略授权登陆验证\nbash 1 2 3 4 5 6 7 8 9 10 11 # 单实例 /app/mysql/bin/mysqld_safe --skip-grant-tables --user=mysql # 多实例 /app/mysql/bin/mysqld_safe --defaults-file=/data/3306/my.cnf --user=mysql --skip-grant-tables \u0026amp; # 登录时空密码 $ mysql -S /data/3306/mysql.sock ... ... Welcome to the MySQL monitor. Commands end with ; or \\g. # 在启动时加 --skip-grant-tables参数，表示忽略授权 修改root密码为新密码\nbash 1 2 3 4 5 6 mysql\u0026gt; set password=password(\u0026#39;123\u0026#39;); ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement mysql\u0026gt; update mysql.user set password=password(\u0026#39;123\u0026#39;) where user=\u0026#39;root\u0026#39;; Query OK, 4 rows affected (0.00 sec) Rows matched: 4 Changed: 4 Warnings: 0 mysql\u0026gt; flush privileges; 重启服务再登陆\nbash 1 2 3 4 5 6 7 8 9 # 此时发现用原密码不能登陆mysql了 $ mysql -uroot -S /data/3306/mysql.sock ERROR 1045 (28000): Access denied for user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; (using password: NO) $ mysql -uroot -S /data/3306/mysql.sock -p111 ERROR 1045 (28000): Access denied for user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; (using password: YES) $ mysql -uroot -S /data/3306/mysql.sock -p123 Welcome to the MySQL monitor. Commands end with ; or \\g. 提示：启动时加 --skip-grant-tables 参数启动登陆修改完密码后一定要重启再对外提供服务，skip一定要放到后面\n清理无用的MySQL用户与库 清理无用的库\nbash 1 2 3 4 5 6 7 8 9 mysql\u0026gt; show databases; +---------------------+ | Database | +---------------------+ | information_schema | | mysql | # 这个是mysql系统信息 | performance_schema | | test | # 这个相当于linux的/tmp，不用保留，直接删掉 +---------------------+ 语法：dorp user \u0026lsquo;user\u0026rsquo;@\u0026rsquo;host/ip\u0026rsquo; \u0026lt;= 注意引号，可以使单或双引号\nbash 1 2 mysql\u0026gt; DROP USER \u0026#39;root\u0026#39;@\u0026#39;::1\u0026#39;; mysql\u0026gt; FLUSH PRIVILEGES; 注意：如果drop删除不了（一般为特殊字符或大写），可以用下面方式删除（以root用户，oldboy主机为例）：\nbash 1 2 DELETE FROM mysql.user WHERE user= \u0026#39;root\u0026#39; AND host=\u0026#39;oldboy\u0026#39;; flush privileges; 处理完用户必须执行 flush privileges\n创建MySQL用户及赋予用户权限 通过help查看grant命令帮助\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 mysql\u0026gt; help grant Name: \u0026#39;GRANT\u0026#39; Description: Syntax: GRANT priv_type [(column_list)] [, priv_type [(column_list)]] ... ON [object_type] priv_level TO user [auth_option] [, user [auth_option]] ... [REQUIRE {NONE | tls_option [[AND] tls_option] ...}] [WITH {GRANT OPTION | resource_option} ...] GRANT PROXY ON user TO user [, user] ... [WITH GRANT OPTION] .... .... CREATE USER \u0026#39;jeffrey\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;mypass\u0026#39;; GRANT ALL ON db1.* TO \u0026#39;jeffrey\u0026#39;@\u0026#39;localhost\u0026#39;; GRANT SELECT ON db2.invoice TO \u0026#39;jeffrey\u0026#39;@\u0026#39;localhost\u0026#39;; GRANT USAGE ON *.* TO \u0026#39;jeffrey\u0026#39;@\u0026#39;localhost\u0026#39; WITH MAX_QUERIES_PER_HOUR 90; 通过查看grant的命令帮助，可以很容易的找到创建用户并授权的例子。\n比较常见的创建用户的方法是，使用grant命令在创建用户的通同时进行授权。具体例子：\nbash 1 GRANT ALL ON db1.* TO \u0026#39;lc\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;111\u0026#39;; 上述grant命令帮助里还提供了一个先用create命令创建用户，然后再用grant授权的方法，即创建用户和授权分开进行\nbash 1 2 3 4 5 CREATE USER \u0026#39;jeffrey\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;mypass\u0026#39;;\t# 创建用户 useradd Jeffrey|passwd --stdin jeffrey GRANT ALL ON db1.* TO \u0026#39;jeffrey\u0026#39;@\u0026#39;localhost\u0026#39;; # 对用户授权 通过grant命令创建用户并授权\nbash 1 grant all privileges on dbname.* to username@\u0026#39;localhost\u0026#39; identified by \u0026#39;passwd\u0026#39; 列表说明如下\ngrant all privileges on dbname.* to username@localhost identified by \u0026lsquo;passwd\u0026rsquo; 授权命令 对应权限 目标：表和库 用户名和客户端主机 用户密码 说明：上述命令是授权localhost主机上通过用户username管理dbname数据库的所有权限，密码为passwd。其中username，dbname，passwd可根据业务的情况修改。\ncreate和grant配合法\n创建用户名username及密码passwd，授权主机localhost。\nbash 1 create user \u0026#39;username\u0026#39;@\u0026#39;localhost\u0026#39; identified by \u0026#39;passwd\u0026#39;; 然后授权localhost主机上通过用户名username管理dbname数据库的所有权限，无需密码。\nbash 1 grant all on dbname.* to \u0026#39;username\u0026#39;@\u0026#39;localhost\u0026#39;; 操作示例 案例1：创建oldboy用户，对zhangsan库具备所有权限，允许localhost主机登陆管理数据库，密码是oldboy123\n实现具体命令：\nbash 1 GRANT ALL PRIVILEGES ON oldboy.* TO zhangsan@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;123\u0026#39;; 演示：\nbash 1 2 3 4 5 # 查看当前数据库情况，然后执行对应命令授权如下： select user,host from mysql.user; # 查看zhangsan具体权限 show grants for zhangsan@\u0026#39;localhost\u0026#39;; 案例2：创建oldgirl用户，对test库具备所有权限，允许localhost主机登陆管理数据库的所有权限，无需密码\n查看当前数据库用户情况，然后执行命令创建用户\nbash 1 2 3 4 5 6 7 8 9 mysql\u0026gt; select user,host from mysql.user; +----------+--------------+ | user | host | +----------+--------------+ | root | 127.0.0.1 | | zhangsan\t| 172.168.1.% | | root | localhost | | zhangsan\t| localhost | +----------+--------------+ 创建用户，指定密码，提示：仅仅是创建用户并未授权\nbash 1 2 create user \u0026#39;oldgirl\u0026#39;@\u0026#39;localhost\u0026#39;; grant all on test.* to \u0026#39;oldgirl\u0026#39;@\u0026#39;localhost\u0026#39;; 查看授权后的MySQL用户列表情况\nbash 1 2 3 4 5 6 7 8 9 mysql\u0026gt; show grants for \u0026#39;oldgirl\u0026#39;@\u0026#39;localhost\u0026#39;; +-----------------------------------------------------------+ | Grants for oldgirl@localhost | +-----------------------------------------------------------+ | GRANT USAGE ON *.* TO \u0026#39;oldgirl\u0026#39;@\u0026#39;localhost\u0026#39;\t| | GRANT ALL PRIVILEGES ON `test`.* TO \u0026#39;oldgirl\u0026#39;@\u0026#39;localhost\u0026#39; | +-----------------------------------------------------------+ # 默认权限是usage，即连接的权限，因为此时还没有权限。 # 第二个表示对test库有所有权限 授权局域网内主机远程链接数据库 根据grant命令语法，知道test@\u0026rsquo;localhsot\u0026rsquo;位置为授权方位数据库的主机，localhost可以用域名，IP或IP段来代替，因此，要授权局域网内主机可以通过如下方法实现\n一条命令，“百分号” 匹配法\nbash 1 grant all on *.* to test@\u0026#39;10.0.0.%\u0026#39; identified by \u0026#39;111\u0026#39;; 一条命令，“子网掩码” 配置法\nbash 1 2 grant all on *.* to test@\u0026#39;10.0.0.0/255.255.255.0\u0026#39; identified by \u0026#39;111\u0026#39; # 子网掩码部分不要用24 两条命令实现\nbash 1 2 create user test@\u0026#39;10.0.0.%\u0026#39; identified by \u0026#39;111\u0026#39; grant all on *.* to test@\u0026#39;10.0.0.%\u0026#39; 最后记得上述每条grant命令都要刷新权限\nbash 1 flush privileges 提示：如果是web链接数据库的用户，尽量不要授权all，而是select,insert,update,delete\u0026hellip;.\n通过MySQL客户端链接异地数据库服务 本地 mysql -uroot -p111 连接相当于 mysql -uroot -p111 -hlocalhost\n要远程链接10.0.0.7的数据库，命令为 mysql -utest -p111 -h10.0.0.7，如果要能链接成功，还需要在10.0.0.7 的数据库服务器上通过如下命令授权：\nbash 1 grant all on *.* to test@\u0026#39;10.0.0.%\u0026#39;identified by \u0026#39;111\u0026#39; 用户可以授权的权限都有那些? 通过实验获得all privileges包括那些权限，\n先看看前面授权过的oldgirl的权限\nbash 1 2 3 4 5 6 7 8 mysql\u0026gt; show grants for oldgirl@\u0026#39;localhost\u0026#39;; +-----------------------------------------------------------+ | Grants for oldgirl@localhost | +-----------------------------------------------------------+ | GRANT USAGE ON *.* TO \u0026#39;oldgirl\u0026#39;@\u0026#39;localhost\u0026#39;\t| | GRANT ALL PRIVILEGES ON `test`.* TO \u0026#39;oldgirl\u0026#39;@\u0026#39;localhost\u0026#39; | +-----------------------------------------------------------+ # 此时查看，还是all privileges权限，但并未细分 取消 oldgirl 的只读权限（SELECT）看看结果\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 mysql\u0026gt; help revoke Name: \u0026#39;REVOKE\u0026#39; Description: Syntax: REVOKE priv_type [(column_list)] [, priv_type [(column_list)]] ... ON [object_type] priv_level FROM user [, user] ... REVOKE ALL PRIVILEGES, GRANT OPTION FROM user [, user] ... REVOKE PROXY ON user FROM user [, user] ... .... .... REVOKE INSERT ON *.* FROM \u0026#39;jeffrey\u0026#39;@\u0026#39;localhost\u0026#39;; .... .... # 通过help revoke可以看出删除一个权限的方法 使用revoke移除授权\nbash 1 2 3 4 5 6 7 mysql\u0026gt; revoke SELECT on test.* from oldgirl@\u0026#39;localhost\u0026#39;; Query OK, 0 rows affected (0.00 sec) mysql\u0026gt; show grants for oldgirl@\u0026#39;localhost\u0026#39;; | Grants for oldgirl@localhost ---------------| GRANT USAGE ON *.* TO \u0026#39;oldgirl\u0026#39;@\u0026#39;localhost\u0026#39;-------------------------- | GRANT INSERT, UPDATE, DELETE, CREATE, DROP, REFERENCES, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EVENT, TRIGGER ON `test`.* TO \u0026#39;oldgirl\u0026#39;@\u0026#39;localhost\u0026#39; | --------------------------------2 rows in set (0.01 sec)------------------------------ 此时我们再查看oldgirl用户权限，ALL PRIVILEGES权限已经被细分了，但是没有SELECT权限了。\n因此我们就可以得出结论：ALL PRIVILEGES包括\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 SELECT INSERT UPDATE DELETE CREATE DROP REFERENCES INDEX ALTER CREATE TEMPORARY TABLES LOCK TABLES EXECUTE CREATE VIEW SHOW VIEW CREATE ROUTINE ALTER ROUTINE EVENT TRIGGER 在授权时，可以授权用户最小的满足业务需求的权限，而不是一味的授权“ALL PRIVILEGES”\n生产环境如何授权用户权限 博客 CMS等产品的数据库授权：\n对于web链接用户授权尽量采用最小化原则，很多开源软件都是web界面安装，因此，在安装期间处理select, insert, update, delete4个权限外，还需要 create drop 等比较危险的权限。\n常规情况下授权 select, insert, update, delete4个权限即可，有的开源软件，如 discuz cms还需要create drop等比较危险的权限。\n生成数据库表后，要收回create、drop权限\n生产环境针对主库（写为主读为辅）用户授权\n普通的环境：\n本机：lnmp，lamp环境数据库授权\nbash 1 grant all privileges on `blog`.* to blog@\u0026#39;localhost\u0026#39; identified by \u0026#39;111\u0026#39;; 应用服务器和数据库服务器不在一个主机上的授权：\nbash 1 grant all privileges on `blog`.* to blog@\u0026#39;10.0.0.1\u0026#39; identified by \u0026#39;111\u0026#39;; 严格的授权：重视安全，忽略了方便\nbash 1 grant SELECT,INSERT,UPDATE,DELETE privileges on `blog`.* to blog@\u0026#39;10.0.0.1\u0026#39; identified by \u0026#39;111\u0026#39;; 生产环境从库（只读）用户的授权：\nbash 1 grant SELECT privileges on `blog`.* to blog@\u0026#39;10.0.0.1\u0026#39; identified by \u0026#39;111\u0026#39;; 说明：这里表示给10.0.0.0/24的用户blog管理blog数据库的所有表（ * 表示所有表）只读权限（select），密码为111。\n生产场景授权具体命令为 主库授权的命令\nbash 1 grant select,insert,update,delete on `blog`.* to blog@\u0026#39;10.0.0.%\u0026#39; identified by \u0026#39;111\u0026#39;; 从库授权用户命令\nbash 1 grant SELECT on `blog`.* to blog@\u0026#39;localhost\u0026#39; identified by \u0026#39;111\u0026#39; 当然从库除了做SELECT授权外，还可以加read-only等只读参数，严格控制web用户写从库。\n重要问题：就是主从库的MySQL库和表是同步的，无法针对同一个用户授权不同的权限。应为，主库授权后会自动同步到从库上，导致从库的授权只读失败。\n解决方法：\n取消mysql库的同步。\n授权主库权限后，从库执行收回增删改权限。\n不在授权上控制增删改，而是用read-only参数，控制普通用户更新从库，注意，read-only参数对超级用户无效。\n查看MySQL数据库中的用户和主机信息\n查询授权用户oldboy的具体的授权权限\nbash\\ 1 2 3 4 5 6 7 mysql\u0026gt; show grants for root@\u0026#39;localhost\u0026#39;; +--------------------------------------------------------------------------+ | Grants for root@localhost | +--------------------------------------------------------------------------+ | GRANT ALL PRIVILEGES ON *.* TO \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY PASSWORD \u0026#39;*23AE809DDACAF96AF0FD78ED04B6A265E05AA257\u0026#39; WITH GRANT OPTION | | GRANT PROXY ON \u0026#39;\u0026#39;@\u0026#39;\u0026#39; TO \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; WITH GRANT OPTION | +--------------------------------------------------------------------------+ ","permalink":"https://www.161616.top/ch2-mysql-security/","summary":"设置MySQL管理员账号密码 在安装MySQL数据库后，MySQL管理员的账号root密码默认为空，极不安全\n启动修改丢失的MySQL单实例root密码方法\n停止MySQL\nbash 1 /etc/init.d/mysqld stop 使用 \u0026ndash;skip-grant-tables启动mysql，忽略授权登陆验证\nbash 1 2 3 4 5 6 7 8 9 10 11 # 单实例 /app/mysql/bin/mysqld_safe --skip-grant-tables --user=mysql # 多实例 /app/mysql/bin/mysqld_safe --defaults-file=/data/3306/my.cnf --user=mysql --skip-grant-tables \u0026amp; # 登录时空密码 $ mysql -S /data/3306/mysql.sock ... ... Welcome to the MySQL monitor. Commands end with ; or \\g. # 在启动时加 --skip-grant-tables参数，表示忽略授权 修改root密码为新密码\nbash 1 2 3 4 5 6 mysql\u0026gt; set password=password(\u0026#39;123\u0026#39;); ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement mysql\u0026gt; update mysql.","title":"ch02 - MySQL安全相关配置"},{"content":"MySQL数据库字符集介绍 简单来说，字符集就是一套文字符号及其编码、比较规则的集合，第一个计算机字符集ASCII！\nMySQL数据库字符集包括字符集(character)和校对规则(collation)两个概念。其中，字符集是用来定义MySQL数据字符串的存储方式。而校对规则则是定义比较字符串的方式。\n上面命令查看已建立的test数据库语句中 CHARACTER SET latin1即为数据库字符集，而COLLATE latin1_swedish_ci为校对规则，更多内容 见mysql手册第10章。\n编译MySQL时，指定字符集了，这样以后建库的时候就直接create database test;\n二进制安装MySQL，并没有指定字符集，这时字符集默认latin1，此时，需要建立UTF8字符集的库，就需要指定UTF8字符集建库。\nsql 1 create database test1 default character set utf8 default collate=utf8_general_ci; MySQL常见字符集介绍 在互联网环境中，使用MySQL时常用的字符集有：\n常用字符集 一个汉字长度（字节） 说明 GBK 2 不是国际标准，对中文环境支持很好。 UTF8 3 中英文混合环境，建议使用此字符集，用的比较多的。 latin1 1 MySQL的默认字符集 utf8mb4 4 UTF8 Unicode，移动互联网 MySQL如何选择合适的字符集？ 如果处理各种各样的文字，发布到不同语言的国家地区，应选Unicode字符集，对MySQL来说就是utf-8（每个汉字三个字节），如果应用需处理英文，仅有少量汉字的utf-8更好。\n如果只需支持中文，并且数据两很大，性能要求也高，可选GBK（定长 每个汉字占双字节，英文也占双字节），如果需大量运算，比较排序等，定长字符集更快，性能\n处理移动互联网业务，可能需要使用utf8mb4字符集。\n如无特别需求，选择UTF8\n查看MySQL字符集 查看当前MySQL系统支持的字符集\nMySQL可支持多种字符集，同一台机器，库或表的不同字段都可以指定不同的字符集。\nsql 1 2 3 4 5 6 7 8 9 mysql\u0026gt; show character set; +----------+-------------------------+---------------------+--------+ | Charset | Description | Default collation | Maxlen | +----------+-------------------------+---------------------+--------+ | latin1 | cp1252 West European | latin1_swedish_ci | 1 | | gbk | GBK Simplified Chinese | gbk_chinese_ci | 2 | | utf8 | UTF-8 Unicode | utf8_general_ci | 3 | | utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | 4 | +----------+-------------------------+---------------------+--------+ 查看MySQL当前的字符集设置情况\nsql 1 2 3 4 5 6 7 8 9 10 11 12 13 mysql\u0026gt; show global variables like \u0026#39;%character_set%\u0026#39;; +---------------------------+-----------------------------------+ | Variable_name | Value | +---------------------------+-----------------------------------+ | character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | utf8 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | utf8 | | character_set_system | utf8 | | character_sets_dir | /app/mysql-5.5.54/share/charsets/ | +---------------------------+-----------------------------------+ 提示：默认情况下character_set_clientcharacter_set_connectioncharacter_set_results三者字符集和系统的字符集一致。即为\nbash 1 2 3 4 5 6 7 8 9 10 11 # CentOS 6 $ cat /etc/sysconfig/i18n LANG=\u0026#34;zh_CN.UTF-8\u0026#34; SYSFONT=\u0026#34;latarcyrheb-sun16\u0026#34; $ echo $LANG zh_CN.UTF-8 # CentOS 7 $ echo $LANG en_US.UTF-8 $ cat /etc/locale.conf LANG=\u0026#34;en_US.UTF-8\u0026#34; MySQL插入中文数据乱码深度剖析 MySQL数据库默认设置的字符集是什么？ 1.查看MySQL默认情况下设置的字符集\nsql 1 2 3 4 5 6 7 8 9 10 11 12 mysql\u0026gt; show global variables like \u0026#39;%character_set%\u0026#39;; +-----------------------------+---------+ | Variable_name | Value | +-----------------------------+---------+ | character_set_client | utf8 | 客户端字符集 | character_set_connection | utf8 | 客户端连接字符集 | character_set_database | utf8 | 数据库字符集，配置文件指定或建库建表指定 | character_set_filesystem | binary | 文件系统字符集 | character_set_results | utf8 | 返回结果字符集 | character_set_server | utf8 | 服务器字符集，配置文件指定或建库建表指定 | character_set_system | utf8 | 系统字符集 +-----------------------------+---------+ 执行set names gbk到底做了什么 无论Linux系统的字符集是gb2312还是utf8，默认情况插入的数据都是乱码\nsql 1 2 3 4 5 6 7 8 9 10 11 12 13 14 mysql\u0026gt; show global variables like \u0026#39;%character_set%\u0026#39;; +---------------------------+--------+ | Variable_name | Value | +---------------------------+--------+ | character_set_client | gbk | | character_set_connection | gbk | | character_set_database | utf8 | | character_set_filesystem | binary | | character_set_results | gbk | | character_set_server | utf8 | | character_set_system | utf8 | +---------------------------+--------+ | 5 | 2 | 3 | 0000-00-00 00:00:00 | 小中僠（国） | 大中僠（国） 执行完set对应的字符集操作，再次插入数据乱码就不乱了（注：原有乱码数据不可恢复）\nsql 1 2 3 4 5 6 7 8 9 mysql\u0026gt; select id,title,content from documents; +----+----------+--------------------------------------------------------+ | id | title | content | +----+----------+--------------------------------------------------------+ | 5 | 灏忎腑鍏\t| 澶т腑鍏 | | 6 | 小中共\t| | | 7 | 巨龙中国 | 大気汚染　超大国の苦闘 | | 8 | 巨龙中国 | 大気汚染　超大国の苦闘 ～ＰＭ２.５　沈黙を破る人々～ | \u0026lt;=字符集不对查询也乱码 +----+----------+--------------------------------------------------------+ set names 改变了如下字符串 bash 1 2 3 character_set_client character_set_connection character_set_results 提示：set names gbk 就是把上面3个桉树改成了 latin1 。也就是说 character_set_client character_set_connection character_set_results 三者的字符集和默认会和linux系统的字符集一致，但是当在mysql中执行 set names charset 操作后，这三者都会改变为设置的字符集，但是命令修改是临时生效的\nset names gbk也可以用下面三个命令替代\nsql 1 2 3 set character_set_client=gbk; set character_set_results=gbk; set character_set_connection=gbk; 此文下回看到要了解下：http://blog.sina.com.cn/s/blog_7c35df9b010122ir.html\nMySQL命令参数 --default-character-set=latin1 在做什么？ 先查看MySQL的字符集 sql 1 2 3 4 5 6 7 8 9 10 11 12 mysql -uroot -p111 -S /data/3306/mysql.sock -e \u0026#39;show variables like \u0026#34;character_set%\u0026#34;\u0026#39; +---------------------------+---------+ | Variable_name | Value | +---------------------------+---------+ | character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | utf8 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | utf8 | | character_set_system | utf8 | +---------------------------+---------+ 带参数\u0026ndash;default-character-set=latin1登录到MySQL中查看字符集 sql 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ mysql -uroot -p111 -S /data/3306/mysql.sock --default-character=latin1 mysql\u0026gt; show variables like \u0026#39;%character_set%\u0026#39;; +---------------------------+---------+ | Variable_name | Value | +---------------------------+---------+ | character_set_client | latin1 | | character_set_connection | latin1 | | character_set_database | utf8 | | character_set_filesystem | binary | | character_set_results | latin1 | | character_set_server | utf8 | | character_set_system | utf8 | +---------------------------+---------+ 提示：和 set names latin1 作用一样，MySQL命令后面加字符集也是把上面3个参数改成了 latin1 。即 character_set_client ，character_set_connection，character_set_results 三者字符集，但是这个登录命令修改字符集也是临时生效的。\n确保MySQL数据库插入数据不乱码解决方案 统一MySQL数据库客户及服务端字符集 通常MySQL数据库下面几个字符集（客户端和服务端）统一成一个字符集，才能确保插入的中文数据可以正确输出。即 show variables like 'character_set%'; 结果中的字符集设置尽量统一。当然，linux系统的字符集也要尽可能和数据库字符集统一。\nshow variables like \u0026lsquo;character_set%\u0026rsquo;;\n其中 character_set_client，character_set_connection，character_set_results 默认情况下采用Linux系统字符集设置，人工登录数据库执行 set names latin1; 以及MySQL指定字符集登录操作，都是改变了MySQL客户端的client connection results 三个参数的字符集为 latin，从而解决了插入中文乱码的问题，这个操作可以通过改变my.cnf配置文件客户端模块的参数来改变，并且永久生效。\n通过修改my.cnf实现修改MySQL客户端的字符集，配置方法如下\nbash 1 2 3 4 [client] default-character-set=latin1 # 提示无需重启服务，退出重新登录生效，此参数相当于，登录后执行 set names latin1; # 特别注意：多实例的MySQL客户端默认读/etc/my.cnf，所以指定客户端字符集就在/etc/my.cnf 更改MySQL服务端字符集参数 按如下要求更改my.cnf bash 1 2 3 [mysqld] default-character-set=latin1 # 5.1 character-set-server=latin1 # 5.5 强调：以上在[mysqld]下设置的参数会更改下面两个参数的字符集设置\nbash 1 2 character_set_database character_set_server 编译时指定服务端字符集 bash 1 2 3 -DDEFAULT_CHARSET=utf8 \\ -DDEFAULT_COLLATION=utf*_general_ci \\ -DEXTRA_CHARSETS=gbk,gb2312,utf8,ascii \\ 统一MySQL数据库客户服务端字符集总结 客户端字符集设置为 \u0026quot;set names utf8;\u0026quot;，这样可以确保插入后的中文，不会出现乱码，但是对执行 set names utf8; 前插入的中文无效，此命令临时生效。\n和设置客户端字符集 \u0026quot;set names utf8\u0026quot; 命令有相同作用的方法还有，MySQL命令指定utf8字符集参数登录，以及在my.cnf里更改参数实现。\n在MySQL的 my.cnf 配置文件里 [client] 模块下添加字符集配置，生效后，相当于命令行 \u0026quot;set names utf8;\u0026quot; 的效果，由于更改的是客户端、连接和返回结果3个字符集，因此无需重启服务就生效。\n在MySQL的 my.cnf 配置文件里 [mysqld] 模块下添加字符集配置，生效后，创建数据库和表默认都是这个设置的字符集MySQL5.5和5.1的服务端字符集参数有变化，具体为 character-set-server=utf8 参数适合5.5，default-character-set=utf8 参数适合5.1及以前版本。\n彻底解决MySQL数据库插入中文乱码方案 切记：字符集的不一致是数据库乱码的罪魁祸首。\n确保以下（客户端和服务端）字符集是一致的，当然，字符集的选择可以有多种。\n修改字客户端字符集\nbash 1 2 3 4 set names gbk; [mysql] /app/mysql/bin/mysqlsafe --default-character 建库建表的时候要指定和上述设置的字符集相同的字符集，以GBK字符集为例： bash 1 create database test default character set gbk collate gbk_chinese_ci; 在数据库中执行sql语句方法 尽量不在MySQL命令行直接插入数据。 可在MySQL中source执行sql文件。 sql文件用utf8没有签名。 开发程序的字符集 生产中如何更改MySQL数据库库表的字符集 数据库字符集修改步骤 对于已有的数据库想修改字符集不能直接通过 \u0026quot;alter database character set *\u0026quot; 或 \u0026quot;alter TableName character set *\u0026quot;，这两个命令都没有更新已有数据的字符集，而只是对新创建的表或者数据生效。\n已有数据的字符集调整，必须先将数据导出，经过修改字符集后重新导入后才可完成。\n步骤如下：\n1. 导出表结构\nsql 1 mysqldump -uroot -p --default-character-set=latin1 -d dbname\u0026gt;table.sql 2. 编辑表结构语句alltable.sql将所有latin1字符串改成utf8;\nsql 1 2 mysqldump -uroot -p111 -S /data/3306/mysql.sock --default-character-set=utf8 --compact -d test\u0026gt;table.sql sed -i \u0026#39;s#utf8#gbk#g\u0026#39; table.sql 3.确保数据不在更新，导出所有数据（不带表结构）\nbash 1 2 3 4 5 mysqldump -uroot -p111 \\ --S/data/3306/mysql.sock \\ --quick --no-create-info \\ --extended-insert \\ --default-character-set=utf8 参数说明\n--quick：用于转储大的表，强制mysqldump从服务器一次一行的检索数据而不是检索所有的行，并输出前cache到内存中。\n--no-create-info：不创建create table语句\n--extended-insert：使用包括几个values列表的多行insert语句，这样文件更小,IO也小，导入数据时会非常快。\n--default-character-set=latin1 ：按照原有字符集导出数据，这样导出的文件中，所有中文都是可见的，不会保存成乱码\n4. 修改my.cnf配置调整客户端及服务端字符集，重启生效\nbash 1 2 3 4 5 6 7 [client] default-character-set=latin1 # 提示无需重启服务，退出重新登录生效，此参数相当于，登录后执行 set names latin1; # 特别注意：多实例的MySQL客户端默认读/etc/my.cnf，所以指定客户端字符集就在/etc/my.cnf [mysqld] default-character-set=latin1 # 5.1 character-set-server=latin1 # 5.5 5. 通过utf8建库\nsql 1 create database test default character set utf8; 6.导入表结构（更改过字符集的表结构）\nsql 1 mysql -uroot -p111 -S /data/3306/mysql.sock dbname\u0026lt;table.sql 7.导入数据\nsql 1 mysql -uroot -p -S /data/3306/mysql.sock db\u0026lt;data.sql 更改字符集思想\n数据库不要更新，导出所有数据。 把导出的数据进行字符集更换（替换表和库）。 修改my.cnf，更改MySQL客户端服务端字符集，重启生效 导入更改过字符集的数据，包括表结构语句，提供服务。 SSH客户端，以及程序更改为对应字符集 校对集collate collate指的是 字符之间的比较关系！\n校对集，依赖于字符集！\n校对集，指的是，在某个字符集下，字符的排序关系应该是什么，称之为校对集！\na B c or B a c\n此时，使用 order by对结果排序，看结果：顺序为 a-B-c 忽略了大小写！\nsql 1 2 3 4 5 6 7 8 MariaDB [t_t]\u0026gt; select * from test order by name; +-----------+ |name | +-----------+ | a | | B | | c | +-----------+ 可以被 校对集改变：\n利用 show collation; 查看到所有的校对集！\nsql 1 2 3 4 5 6 7 8 9 mysql\u0026gt; show collation; +---------------------+-----------+------+----------+-----------+---------+ | Collation | Charset | Id | Default | Compiled | Sortlen | +---------------------+-----------+------+----------+-----------+---------+ | latin1_bin | latin1 | 47 | | Yes | 1 | | latin1_general_ci | latin1 | 48 | | Yes | 1 | | gbk_chinese_ci | gbk | 28 | Yes | Yes | 1 | | utf8_general_ci | utf8 | 33 | Yes | Yes | 1 | +---------------------+-----------+------+----------+-----------+---------+ ci大小写不敏感的\n一个字符集下可以存在多个校对集，且有一个是默认的。\n再创建一个 utt8_bin的校对集表，在排序：\nsql 1 2 3 4 5 6 7 8 9 10 11 create table t( name varchar(2) )engine innodb default charset=utf8 collate=utf8_bin; insert into t values (\u0026#39;a\u0026#39;),(\u0026#39;B\u0026#39;),(\u0026#39;c\u0026#39;); MariaDB [t_t]\u0026gt; select * from t order by name; +------+ | name | +------+ | B | | a | | c | +------+ 我们典型的选择：utf8_genreal_ci utf8_unicode_ci\n校验规则后缀说明： _bin 二进制编码层面直接比较\n_ci 忽略大小写（大小写不敏感）比较\n_cs 大小写敏感比较\n","permalink":"https://www.161616.top/ch5-mysql-charset/","summary":"MySQL数据库字符集介绍 简单来说，字符集就是一套文字符号及其编码、比较规则的集合，第一个计算机字符集ASCII！\nMySQL数据库字符集包括字符集(character)和校对规则(collation)两个概念。其中，字符集是用来定义MySQL数据字符串的存储方式。而校对规则则是定义比较字符串的方式。\n上面命令查看已建立的test数据库语句中 CHARACTER SET latin1即为数据库字符集，而COLLATE latin1_swedish_ci为校对规则，更多内容 见mysql手册第10章。\n编译MySQL时，指定字符集了，这样以后建库的时候就直接create database test;\n二进制安装MySQL，并没有指定字符集，这时字符集默认latin1，此时，需要建立UTF8字符集的库，就需要指定UTF8字符集建库。\nsql 1 create database test1 default character set utf8 default collate=utf8_general_ci; MySQL常见字符集介绍 在互联网环境中，使用MySQL时常用的字符集有：\n常用字符集 一个汉字长度（字节） 说明 GBK 2 不是国际标准，对中文环境支持很好。 UTF8 3 中英文混合环境，建议使用此字符集，用的比较多的。 latin1 1 MySQL的默认字符集 utf8mb4 4 UTF8 Unicode，移动互联网 MySQL如何选择合适的字符集？ 如果处理各种各样的文字，发布到不同语言的国家地区，应选Unicode字符集，对MySQL来说就是utf-8（每个汉字三个字节），如果应用需处理英文，仅有少量汉字的utf-8更好。\n如果只需支持中文，并且数据两很大，性能要求也高，可选GBK（定长 每个汉字占双字节，英文也占双字节），如果需大量运算，比较排序等，定长字符集更快，性能\n处理移动互联网业务，可能需要使用utf8mb4字符集。\n如无特别需求，选择UTF8\n查看MySQL字符集 查看当前MySQL系统支持的字符集\nMySQL可支持多种字符集，同一台机器，库或表的不同字段都可以指定不同的字符集。\nsql 1 2 3 4 5 6 7 8 9 mysql\u0026gt; show character set; +----------+-------------------------+---------------------+--------+ | Charset | Description | Default collation | Maxlen | +----------+-------------------------+---------------------+--------+ | latin1 | cp1252 West European | latin1_swedish_ci | 1 | | gbk | GBK Simplified Chinese | gbk_chinese_ci | 2 | | utf8 | UTF-8 Unicode | utf8_general_ci | 3 | | utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | 4 | +----------+-------------------------+---------------------+--------+ 查看MySQL当前的字符集设置情况","title":"ch05 - MySQL字符集相关配置"},{"content":"备份数据库的意义 运维工作到底是什么工作，到底是做什么？\n运维工作简单的概括就两件事：\n一是保护公司的数据；二是网站7*24小时提供服务。\n那么对数据丢失一部分和网站7*24小时提供服务那个更重要呢？\n都很重要，只是说相比哪个更为重要？这个具体要看业务个公司。例如：银行、金融行业，数据是最重要的，一条都不能丢，可能宕机停机影响就没那么大。百度搜索，腾讯qq聊天记录丢失了几万条数据，都不算啥。\n对于数据来讲，数据最核心的就是数据库数据。\n备份单个数据库练习多种参数的使用 MySQL数据库自带了一个很好用的备份命令，就是mysqldump，它的基本使用如下：\nsql 1 mysqldump -u UserName -p PassWord dbName \u0026gt; backName.sql 备份库 sql 1 mysqldump -S /data/3306/mysql.sock -uroot -p test\u0026gt;mysql.sql 检查备份结果\nsql 1 2 3 4 5 6 7 8 9 10 11 12 $ egrep -v \u0026#34;#|\\*|--|^$\u0026#34; ./mysql.sql DROP TABLE IF EXISTS `test1`; CREATE TABLE `test1` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `num1` varchar(20) NOT NULL, `num2` varchar(20) NOT NULL, `num3` varchar(20) NOT NULL, `num4` int(11) NOT NULL DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;test1\u0026#39;, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2000001 DEFAULT CHARSET=utf8; LOCK TABLES `test1` WRITE; INSERT INTO `test1` VALUES (1,\u0026#39;1455577\u0026#39;,\u0026#39;9779520\u0026#39;,\u0026#39;4530868\u0026#39;,0), 注：因为导出时的格式没有加字符集，一般恢复到数据库里会正常，只是系统外查看不正常而已。另外，insert是批量插入的方式，这样在恢复时效率很高。\n根据查看的结果，我们看到了已备份的表结构语句及插入的数据整合的sql语句，但是中文数据乱码了。\n设置字符集参数备份解决乱码问题 查看备份前数据库客户端及服务器端的字符集设置\nsql 1 2 3 4 5 6 7 8 9 10 11 12 13 mysql\u0026gt; show variables like \u0026#34;character%\u0026#34; +---------------------------+-----------------------------------+ | Variable_name | Value | +---------------------------+-----------------------------------+ | character_set_client | utf8 | | character_set_connection\t| utf8 | | character_set_database | utf8 | | character_set_filesystem\t| binary | | character_set_results | utf8 | | character_set_server | utf8 | | character_set_system | utf8 | | character_sets_dir | /app/mysql-5.5.54/share/charsets/ | +---------------------------+-----------------------------------+ 指定对应的字符集备份，这里为--default-character-set=utf8\ntext 1 mysqldump -uroot -p111 --default-character-set=utf8 t1 \u0026gt; t.sql -S /data/3306/mysql.sock 备份时加-B参数，增加创建库与选择库 sql 1 2 3 -- Current Database: `t1` CREATE DATABASE /*!32312 IF NOT EXISTS*/ `t1` /*!40100| USE `t1`; 优化配置文件大小减少输出注释（debug调试） 利用mysqldump的--compact参数优化下备份结果\nbash 1 2 $ mysqldump -uroot -p111 --default-character-set=utf8 --compact -B t1 \u0026gt; t_b.sql $ cat t_b.sql sql 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 CREATE DATABASE /*!32312 IF NOT EXISTS*/ `t1` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `t1`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `test1` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `num1` varchar(20) NOT NULL, `num2` varchar(20) NOT NULL, `num3` varchar(20) NOT NULL, `num4` int(11) NOT NULL DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;test1\u0026#39;, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2000031 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; INSERT INTO `test1` VALUES (2000001,\u0026#39;690938\u0026#39;,\u0026#39;794482\u0026#39;,\u0026#39;1899596\u0026#39;,0),(2000002,\u0026#39;7114536\u0026#39;,\u0026#39;9873892\u0026#39;,\u0026#39;8025852\u0026#39;,0),(2000003,\u0026#39;507584\u0026#39;,\u0026#39;8460367\u0026#39;,\u0026#39;779079\u0026#39;,0),(2000004,\u0026#39;8514298\u0026#39;,\u0026#39;234250\u0026#39;,\u0026#39;5628359\u0026#39;,0),(2000005,\u0026#39;7439042\u0026#39;,\u0026#39;310137\u0026#39;,\u0026#39;9233557\u0026#39;,0),(2000006,\u0026#39;5237373\u0026#39;,\u0026#39;8486196\u0026#39;,\u0026#39;6718861\u0026#39;,0),(2000007,\u0026#39;8135719\u0026#39;,\u0026#39;522012\u0026#39;,\u0026#39;8202901\u0026#39;,0),(2000008,\u0026#39;9448472\u0026#39;,\u0026#39;2633656\u0026#39;,\u0026#39;4822864\u0026#39;,0),(2000009,\u0026#39;6213355\u0026#39;,\u0026#39;6598180\u0026#39;,\u0026#39;4350835\u0026#39;,0),(2000010,\u0026#39;1959635\u0026#39;,\u0026#39;6745673\u0026#39;,\u0026#39;7849459\u0026#39;,0),(2000011,\u0026#39;9010275\u0026#39;,\u0026#39;1503001\u0026#39;,\u0026#39;484179\u0026#39;,0),(2000012,\u0026#39;7911894\u0026#39;,\u0026#39;8106930\u0026#39;,\u0026#39;6798971\u0026#39;,0),(2000013,\u0026#39;9674065\u0026#39;,\u0026#39;7973412\u0026#39;,\u0026#39;844865\u0026#39;,0),(2000014,\u0026#39;304088\u0026#39;,\u0026#39;8985848\u0026#39;,\u0026#39;4016974\u0026#39;,0),(2000015,\u0026#39;3127327\u0026#39;,\u0026#39;3585711\u0026#39;,\u0026#39;8546578\u0026#39;,0),(2000016,\u0026#39;1975754\u0026#39;,\u0026#39;4239037\u0026#39;,\u0026#39;5267926\u0026#39;,0),(2000017,\u0026#39;3622517\u0026#39;,\u0026#39;2308806\u0026#39;,\u0026#39;676480\u0026#39;,0),(2000018,\u0026#39;6455983\u0026#39;,\u0026#39;250474\u0026#39;,\u0026#39;1884422\u0026#39;,0),(2000019,\u0026#39;8670686\u0026#39;,\u0026#39;7700168\u0026#39;,\u0026#39;2488781\u0026#39;,0),(2000020,\u0026#39;9343400\u0026#39;,\u0026#39;9250657\u0026#39;,\u0026#39;8223086\u0026#39;,0),(2000021,\u0026#39;3363461\u0026#39;,\u0026#39;2148048\u0026#39;,\u0026#39;649856\u0026#39;,0),(2000022,\u0026#39;6805137\u0026#39;,\u0026#39;2076115\u0026#39;,\u0026#39;9965166\u0026#39;,0),(2000023,\u0026#39;3597485\u0026#39;,\u0026#39;8091927\u0026#39;,\u0026#39;9667180\u0026#39;,0),(2000024,\u0026#39;4060121\u0026#39;,\u0026#39;1299065\u0026#39;,\u0026#39;4314962\u0026#39;,0),(2000025,\u0026#39;7677614\u0026#39;,\u0026#39;5443186\u0026#39;,\u0026#39;4183087\u0026#39;,0),(2000026,\u0026#39;4585879\u0026#39;,\u0026#39;380131\u0026#39;,\u0026#39;8143022\u0026#39;,0),(2000027,\u0026#39;9574716\u0026#39;,\u0026#39;3444513\u0026#39;,\u0026#39;8498418\u0026#39;,0),(2000028,\u0026#39;2158552\u0026#39;,\u0026#39;5297508\u0026#39;,\u0026#39;11882\u0026#39;,0),(2000029,\u0026#39;4166888\u0026#39;,\u0026#39;798795\u0026#39;,\u0026#39;1493311\u0026#39;,0),(2000030,\u0026#39;5070170\u0026#39;,\u0026#39;870919\u0026#39;,\u0026#39;9144083\u0026#39;,0); \u0026ndash;compact参数说明：\n（测试时用的比较多）可以优化输出内容的大小，让容量更少，适合调试。\nsql 1 2 3 4 --compact\tGive less verbose output (useful for debugging).\tDisables structure comments and header/footer contructs. Enables options --skip-add-drop-table --no-set-names --skip-disable-keys --skip-add-locks 参数说明：该选项使得输出内容更简介，不包括默认选项中各种注释。有如下几个参数的功能。\n--skip-add-drop-table\n--no-set-names\n--skip-disable-keys\n--skip-add-locks\n压缩备份的数据 sql 1 2 3 4 5 6 --- 输出时用管道进行备份，压缩效率将近3倍 --- 这个保存为一个压缩文件 mysqldump -S /data/3306/mysql.sock -uroot -p111 -B t1|gzip \u0026gt;t1.sql.gz 1360 t1.sql.g 3365 t2.sql 小结：\n备份数据使用-B参数，会在备份数据中增加建库及use库的语句 备份数据使用-B参数，后面可以直接接多个库名。 共gzip对备份的数据亚索 debug时可以用\u0026ndash;compact减少输出，但不用于生产 指定字符集备份用\u0026ndash;default-character-set=utf8（一般不用） mysqldump的工作原理 利用mysqldump命令备份数据的过程，实际上就是把数据从mysql库里以逻辑的sql语句的形式直接输出或者生成备份的文件的过程。\n提示：使用mysqldump是把数据库的数据导出通过sql语句的形式存储，这种备份方式称之为逻辑备份，效率不是很高，一般50G以内的数据。\n其他备份方式：物理备份：cp tar（停库），xtrabackup物理热备份。 备份多个库及多个参数\nsql 1 2 mysqldump -uroot -p111 -S /data/3306/mysql.sock --compact \\ -B test t1|gzip\u0026gt;/data/test1.sql -B参数说明\nbash 1 2 3 4 5 6 7 8 9 -B，参数是关键，表示接多个库并且增加use db和create database db的信息 -B, --databases Dump several databases. Note the difference in usage; in this case no tables are given. All name arguments are regarded as database names. \u0026#39;USE db_name;\u0026#39; will be included in the output. # 用于导出多个数据库，注意这种情况系没有表。所有的名称参数都被认作为是数据库。 # 每个数据库名以空格隔开，将使用的数据库名称包含到输出里 # 当-B后的数据库列全时，同-A参数。 分库备份 分库备份实际上就是执行一个备份语句备份一个库，如果数据库里有多个库，就执行多条相同的备份单个库的备份语句就可以备份多个库了，注意每个库都可以用对应备份的库作为库名，结尾加.sql。备份多个库的命令如下：\nmysqldump -uroot -p111 -B oldboy;\nmysqldump -uroot -p111 -B test;\n\u0026hellip;.\n\u0026hellip;.\n分库备份法1：\nsql 1 2 3 4 5 6 7 8 9 10 11 12 13 14 mysql -uroot -p111 -S /data/3306/mysql.sock \\ -e \u0026#39;show databases;\u0026#39;|\\ egrep -v \u0026#34;Database|_schema|mysql\u0026#34;|\\ sed -r \u0026#39;s#^(.*)#mysqldump -uroot -p111 -S /data/3306/mysql.sock \\1#g\u0026#39; mysql -uroot -p111 -S /data/3306/mysql.sock bingbing mysql -uroot -p111 -S /data/3306/mysql.sock t1 mysql -uroot -p111 -S /data/3306/mysql.sock test -- 将结果交给bash mysql -uroot -p111 -S /data/3306/mysql.sock \\ -e \u0026#39;show databases;\u0026#39;|egrep -v \u0026#34;Database|_schema|mysql\u0026#34;|\\ sed -r \u0026#39;s#^(.*)#mysqldump -uroot -p111 -S /data/3306/mysql.sock -B \\1|gzip\u0026gt;\\1.sql.gz#g\u0026#39; \\ |bash 法2：\n见分库分表备份视频：http://edu.51cto.com/course/course_id-808.html\n分库备份的意义何在？\n有时一个企业的数据库里会有多个库，例如（www.bbs，blog），但是出问题的时候很可能是某一个库，如果在备份时把所有的库都备份成了一个数据文件的话，回复某一个库的数据是就比较麻烦了。\n备份单个表 语法：\nbash 1 2 mysqldump -uuserName -ppassWord dbName tableName \u0026gt;/data.sql mysqldump -uuserName -ppassWord dbName tableName1 tableName2.. \u0026gt;/data.sql 提示：不能加-B参数了，因为库后面就是表了。\n企业需求：一个库里有大表有小标，有时可能需要只回复某一个小表，上述的多表备份很难拆开，就想没有分库那样导致恢复某一个小表很麻烦。\n那么又如何进行分表备份呢？如下，和分库的思想一样，每执行一条语句备份一个表，生成不同的数据文件即可。如下：\nbash 1 2 3 mysql -uroot -p111 -e \u0026#39;use test; show tables;\u0026#39; \\ |egrep -v \u0026#39;Tables_in_test\u0026#39; \\ |sed -r \u0026#39;s#^(.*)#mysqldump -uroot -p111 --compact test \\1\u0026gt;\\1.sq$g\u0026#39;|bash 分表备份缺点：文件多，很碎\n备一个完整全备，在做一个分库分表备份\r脚本批量备份恢复多个SQL文件\r面试题：多个库或者多个表备份到一块了，如何恢复单个库或者表？\n解答：\n第三方测试库，导入到库里，然后把需要的备份出来，恢复到正式库里。 单表：grep表名 bak.sql\u0026gt;tab_name。 实现分库分表备份 备份数据表结构 利用mysqldump -d参数只备份表的结构，例：备份oldboy库的所有表的结构\nsql 1 mysqldump -uroot -p111 -d --compact -B test \u0026gt; t.sql 如果只导出数据则用-t\nsql 1 mysqldump -uroot -p111 -S /data/3306/mysql.sock -t --compact -B test \u0026gt; 1t.sql -T --tab=path：语句与数据分离，数据为文本。\nsql 1 mysqldump -uroot -p111 -S /data/3306/mysql.sock t1 test1 -T /data 注意只能对表进行分离，对数据库进行分离提示如下：\nbash 1 2 $ mysqldump -uroot -p111 -S /data/3306/mysql.sock -B t1 -T /data mysqldump: --databases or --all-databases can\u0026#39;t be used with --tab. 小结：\n-B备份多个库（并添加create和use语句） -d只备份库表结构 -t只备份数据（sql语句形式） -T分离表和数据成不同的文件，数据是文本，非SQL语句 刷新binlog参数 mysqldump用于定时对某一时刻的数据的全备份，例如：00点进行备份bak.sql.gz\n增量备份：当有数据写入到数据库时，还会同时把更新的SQL语句写入到对应的文件里，这个文件就叫做binlog。\n比如说晚上0点做备份， 10点宕机了，0-10点的数据就丢失了\n10点前丢失数据需要恢复的数据：\n00点时刻备份的bak.sql.gz数据还原到数据库，这个时候数据恢复到了00点 00-10点数据，就要从binlog里恢复。 binlog作用 记录数据库更新的sql语句，不记录show select等，只是记录对数据库记录变更的二进制文件。\n在mysql数据库当中，当你做一个全备之后到出问题的时刻，要想恢复，就是全备+全备之后的所有binlog。\n定界binlog 问题：怎么界定备份之后和binlog文件之间连接的很紧密不多也不少。\n通过文件的日志点。可以通过-F做一个区分。\n只要我们做了备份，然后就刷新binlog，将来恢复的就是130以下的。130以上的包里面就包含了 binlog日志切割：确定全备和增量的结界点-F刷新binlog日志，生成新日志文件，将来增量恢复从这个新日志文件开始。\nbinglog文件生效需要一个参数：log-bin log-bin=/data/3306/mysql-bin\nbash 1 2 3 4 5 6 7 8 9 10 11 12 $ ll # ←备份前，查看目录binlog文件 mysql-bin.000316 mysql-bin.000317 mysql-bin.index mysqldump -uroot -p111 -S /data/3306/mysql.sock --compact -B t1 \u0026gt; t1.sql -F $ ll #←在刷新之后可以看到binlog文件增加了 mysql-bin.000316 mysql-bin.000317 mysql-bin.000318 mysql-bin.index --master-data 在备份语句里添加 CHANGE MASTER 语句及 binlog 文件及位置点信息\n值1，为可执行的CHANGE MASTER语句\n值2，为注释的--CHANGE MASTER语句\n注：--master-data除了增量恢复确定临界点外，做主从复制时作用更大\nbash 1 2 3 4 5 # 不加--master-data语句 mysqldump -uroot -p111 --compact -B t1 \u0026gt; t1.sql cat t1.sql CREATE DATABASE /*!32312 IF NOT EXISTS*/ `t1` /*!40100 DEFAULT CHARACTER SET utf8 */; sql 1 2 3 4 5 6 7 -- 加上master-data语句后 mysqldump -uroot -p111 --compact -B t1 \u0026gt; t1.sql --master-data=1 cat t1.sql CHANGE MASTER TO MASTER_LOG_FILE=\u0026#39;mysql-bin.000318\u0026#39;, MASTER_LOG_POS=107; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `t1` /*!40100 DEFAULT CHARACTER SET utf8 */; bash 1 2 $ mysqldump -uroot -p111 --compact -B t1 \u0026gt; t1.sql --master-data=2 $ cat t1.sql sql 1 -- CHANGE MASTER TO MASTER_LOG_FILE=\u0026#39;mysql-bin.000318\u0026#39;, MASTER_LOG_POS=107; mysqldump关键参数说明 参数 说明 -B 指定多个库，增加建库语句和use语句。 \u0026ndash;compact 去掉注释，适合调试输出，生产不适用 -A 备份所有库 -F 刷新binlog日志，生成新文件，将来增量恢复从这个文件开始。 \u0026ndash;master-data 增加binlog日志文件名及对应的位置点（即CHANGE MASTER语句）。\u0026ndash;mater-data=1不注释 2注释 -x \u0026ndash;lock-all-tables -l \u0026ndash;lock-tables lock all tables for read -d 只备份数据，无表结构，SQL语句形式。 -t 只备份数据，无库表结构，SQL语句形式。 -T 库表和数据分离不同文件，数据是文本形式。 \u0026ndash;single-transaction 适合innodb事务数据库备份 -q --quick don\u0026rsquo;t bufferquery dump directly to stdout (Defaults to on; use \u0026ndash;skip-quick to disable) 说明：innodb表在备份时，通常启用选线\u0026ndash;single-transaction来保证备份的一致性，实际上它的工作原理是设定本次会话的隔离级别为REPEATABLE READ，确保本次会话（dump）时，不会看到其他会话已经提交了的数据。\n生产场景不同引擎mysqldump备份命令 myisam引擎企业生产备份，命令（适合所有引擎或混合引擎）： sh 1 mysqldump -uroot -p111 -A -B -F -R --master-data=2 -x --events|gzip \u0026gt; /data/all.sql.gz 提示：-F也可以不用，与\u0026ndash;，master-data有写重复\ninnodb引擎企业生产备份命令：推荐使用 sh 1 mysqldump -uroot -p111 -A -B -F -R --master-data=2 --events --single-transaction|gzip \u0026gt;/data/all.sql.gz \u0026ndash;master-data作用： 使用\u0026ndash;master-data=2进行备份文件会增加如下内容：适合普通备份增量恢复 sh 1 --CHANGE MASTER TO MASTER_LOG_FILE=\u0026#39;mysql-bin.00020\u0026#39;, MASTER_LOGZ_POS=1191 使用\u0026ndash;maste-data=1进行备份文件会增加如下内容：更适合主从复制 sh 1 CHANGE MASTER TO MASTER_LOG_FILE=\u0026#39;mysql-bin.00020\u0026#39;, MASTER_LOGZ_POS=1191 锁表的原理： innodb有acid的特性\ndump命令是看不到后面生成的数据，只能看到执行这个命令时所有数据之前的这个数据\n事务引擎不锁表备份原理：\u0026ndash;single-transaction 会话隔离\n额外补充：\nmysqldump逻辑备份说明 缺点：效率不是特别高\n优点：简单、方便、可靠、迁移。\n超过50G可选方案 恢复数据到数据库的时候，认为通过SQL语句将数据删除的时候，做主从复制的时候\n恢复数据库实战 数据库恢复事项\n数据恢复和字符集关联很大，如果字符集不正确会导致恢复的数据乱码。 mysql命令以及source命令恢复数据可的原理就是把文件的SQL语句，在数据库里重新执行过程。\n利用source命令恢复数据库。 进入mysql数据库控制台，mysql -uroot -p登陆后\nuse database;\n然后使用source xx.sql，后面参数为脚本文件（如这里用到的sql）\nsource 2.sql 这个文件是系统路径，默认是登陆mysql前的系统路径\n提示：\nsource数据恢复和字符集关联很大，如果字符集不正确会导致恢复的数据乱码。\rutf8数据库，那么恢复的文件格式需要为utf8无bom头。\r利用mysql命令恢复（标准）\nsh 1 2 mysql -uroot -p111 -e \u0026#39;use test;drop tables test1;show tables;\u0026#39; mysql -uroot -p111 test \u0026lt; /2.sql 假设开发人员让我们插入数据到数据库（可能是邮件发给我们的，内容可能是字符串或是文件）sql文件里没有use db这样的字符，在导入时就要指定数据库名了。\nsql 1 2 3 4 5 6 7 $ mysql -uroot -p111 \u0026lt; /data/3306/2.sql ERROR 1046 (3D000) at line 22: No database selected $ mysql -uroot -p111 -e\u0026#39;use test\u0026#39;; ERROR 1049 (42000) at line 1: Unknown database \u0026#39;test\u0026#39; $ mysql -uroot -p111 test\u0026lt;/data/3306/2.sql 如果在导出时指定-B参数，恢复时无需指定库恢复，为什么？\n因为-B参数带了use test;还会有create database test;，而恢复时指定库就类似与use test。\n如果mysqldump备份时指定了-B，则恢复可以用如下方法：\nsh 1 2 mysql -uroot -p111 \u0026lt; back.sql mysql -uroot -p111 DbName \u0026lt; back.sql$ 条件是dbname库必须存在 提示：此处DbName相当于use Dbname\n问题：分库分表备份的数据如何快速恢复呢？\n还是通过脚本读指定的库和表，调用mysql命令恢复\nsh 1 for name in `ls /back/*.sql|sed -r \u0026#39;s#.back.sq$#g\u0026#39;`;do mysql -uroot -p111 test\u0026lt;${name}.back.sql; done; 针对压缩的备份数据恢复\n方法1：\nsh 1 2 3 4 gzip -d /back/mysql_back.sql.gz mysql -uroot -p111 dbname \u0026lt; /back/mysql_back.sql gzip -cd mysql_back.sql.gz \u0026gt; mysql.sql$←不删除源备份文件 方法2：\nsh 1 2 gunzip \u0026lt; b.sql.gz \u0026gt; /opt/mysql.sql mysql -uroot -p111 \u0026lt; /opt/mysql.sql 或者\nsh 1 gunzip -c back.sql.gz|mysql -uroot -p111 test sh 1 2 3 4 mysql -uroot -p111 -e \\ \u0026#39;SELECT CONCAT(\u0026#34;drop table \u0026#34;,table_name,\u0026#34;;\u0026#34;) FROM information_schema.`TABLES` WHERE table_schema=\u0026#34;test\u0026#34;;\u0026#39; \\ |grep -Ev \u0026#39;CONCAT(\u0026#34;drop table \u0026#34;,table_name,\u0026#34;;\u0026#34;)\u0026#39;|sed -r \u0026#34;s#^(.*)#mysql -uroot -p111 -e \u0026#39;use test;\\1\u0026#39;#g\u0026#34; \\ |bash 分表分库备份脚本\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #!/bin/sh . /etc/init.d/functions # define variable BackDir=~/back User=root PassWD=111 Socket=/data/3306/mysql.sock # define comment Login=\u0026#34;mysql -u${User} -p${PassWD} -S ${Socket}\u0026#34; Dump=\u0026#34;mysqldump -u${User} -p${PassWD} -S${Socket} -x --master-data=2 --compact\u0026#34; DataBase=`$Login -e \u0026#39;show databases;\u0026#39;|egrep -v \u0026#39;*chema|mysql\u0026#39;|sed \u0026#39;1d\u0026#39;` [ ! -d $BackDir ] \u0026amp;\u0026amp; mkdir -p $BackDir for list in $DataBase do ST=`$Login -e \u0026#34;use $list;show tables;\u0026#34;|sed \u0026#39;1d\u0026#39;` [ ! -d $BackDir/$list ] \u0026amp;\u0026amp; mkdir -p $BackDir/$list for table in $ST do $Dump $list $table|gzip\u0026gt;$BackDir/$list/$table.`date +%F`.sql.gz [ $? -eq 0 ] \u0026amp;\u0026amp; action \u0026#34;$list \u0026gt; $table is ok\u0026#34; /bin/true || \u0026#34;$list \u0026gt; $table dump is fail\u0026#34; done done ","permalink":"https://www.161616.top/ch3-mysql-backup-and-restore/","summary":"备份数据库的意义 运维工作到底是什么工作，到底是做什么？\n运维工作简单的概括就两件事：\n一是保护公司的数据；二是网站7*24小时提供服务。\n那么对数据丢失一部分和网站7*24小时提供服务那个更重要呢？\n都很重要，只是说相比哪个更为重要？这个具体要看业务个公司。例如：银行、金融行业，数据是最重要的，一条都不能丢，可能宕机停机影响就没那么大。百度搜索，腾讯qq聊天记录丢失了几万条数据，都不算啥。\n对于数据来讲，数据最核心的就是数据库数据。\n备份单个数据库练习多种参数的使用 MySQL数据库自带了一个很好用的备份命令，就是mysqldump，它的基本使用如下：\nsql 1 mysqldump -u UserName -p PassWord dbName \u0026gt; backName.sql 备份库 sql 1 mysqldump -S /data/3306/mysql.sock -uroot -p test\u0026gt;mysql.sql 检查备份结果\nsql 1 2 3 4 5 6 7 8 9 10 11 12 $ egrep -v \u0026#34;#|\\*|--|^$\u0026#34; ./mysql.sql DROP TABLE IF EXISTS `test1`; CREATE TABLE `test1` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `num1` varchar(20) NOT NULL, `num2` varchar(20) NOT NULL, `num3` varchar(20) NOT NULL, `num4` int(11) NOT NULL DEFAULT \u0026#39;0\u0026#39; COMMENT \u0026#39;test1\u0026#39;, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2000001 DEFAULT CHARSET=utf8; LOCK TABLES `test1` WRITE; INSERT INTO `test1` VALUES (1,\u0026#39;1455577\u0026#39;,\u0026#39;9779520\u0026#39;,\u0026#39;4530868\u0026#39;,0), 注：因为导出时的格式没有加字符集，一般恢复到数据库里会正常，只是系统外查看不正常而已。另外，insert是批量插入的方式，这样在恢复时效率很高。","title":"ch03 - MySQL的备份与恢复"},{"content":"MySQL数据库简介 编程语言排名：http://www.tiobe.com/tiobe-index\n数据库排名：http://db-engines.com/en/ranking\nMySQL数据库分类与版本升级 MySQL数据库官网为http://www.mysql.com，其发布的MySQL版本采用双授权政策，和大多数开源产品的路线一样，分别为社区版和商业版，而这两个版本又各自分四个版本依次发布，这四个版本为Alpha版、Beta版、RC版和GA版（GA正式发布版）\nMySQL数据库商业版和社区版的区别 在前面的内容已经阐述过了，MySQL的版本发布采用双授权政策，即分为社区版和商业版，而这两个版本又各自分四个版本依次发布：Alpha版、Beta版、RC版和GA版（GA正式发布版）\nAlpha版 Alpha版一般只在开发的公司内部运行，不对外公开。主要死开发者自己对产品进行测试，检查产品是否存在缺陷、错误，验证产品功能与说明书、用户手册是否一致。MySQL是属于开放源代码的开源产品，因此需要世界各地开发者、爱好者和用户参与软件的开发测试和手册编写等工作。所以会对外公布此版本的源码和产品，方便任何人可以参与开发测试工作，甚至编写与修改用户手册。\nBeta版 Beta版一般是完成功能的开发和所有的测试工作时候的产品，不会存在较大的功能或性能BUG，并且邀请或提供给公户体验与测试，以便更全面地测试软件的不足之处或存在的问题。\nRC版 RC版属于生产环境发布之前的一个小版本或称候选版，是根据Beta测试结果，收集到的BUG或缺陷之处等收集到信息，进行修复和完善之后的新一版本\nGA版 GA版是软件产品正式发布的版本，也称生产版本的产品。一般情况下，企业生产环境都会选择GA版本的MySQL软件，用于真实的生产环境中。偶尔有个别的大型企业会追求新功能驱动而牺牲稳定性使用其他版本，但这个是个例。\nMySQL四中发布版本选择说明 MySQL AB官方网站会把五种数据库版本都提供下载，主要是MySQL数据库属于开发源代码的数据库产品，鼓励全球的技术爱好者参与研发、测试、文档编写和经验分享，甚至包过产品发展规划，对于Development版本、Alpha版本和Beta版本是绝对不允许使用在任何生产环境，毕竟这是一个GA版本之前，也即生产版本发布之前的一个小版本。另外，对MySQL数据库GA版本，也是需要慎重选择，开源社区产品毕竟不是经过严格的测试工序完成的产品，是全球开源技术人员的资源完成的，会存在比商业产品稳定性弱的缺陷。更严格的选择见后文。\nMySQL产品路线 MySQL产品路线变更历史背景 早起MySQL也是遵循版本号逐渐增加的方式发展的，格式例如：mysql-x.xx.xx.tar.gz，例如DBA都非常熟悉的生产场景版本：4.1.7、5.0.56等。\n近几年，为了提高MySQL产品的竞争优势、以及提高性能、降低开发维护成本等原因，同时，更方便企业用户更精准的选择适合的版本产品用于自己的企业生产环境中。 MySQL在发展到5.1系列版本之后，重新规划为3条产品线\n5.0.xx到5.1.xx产品线介绍 第一条产品线：5.0.xx及升级到5.1.xx的产品系列，这条产品线继续完善与改进其用户体验和性能，同时增加新功能，这条路线可以说是MySQL早起产品的延续系列，这一系列的产品发布情况及历史版本如下： MySQL 5.1是当前稳定（产品质量）发布系列。只针对漏洞修复重新发布；没有增加会影响稳定性的新功能。\nMySQL 5.1:Previous stable(production-quality) release MySQL 5.0是前一稳定（产品质量）发布系列。只针对严重漏洞修复和安全修复重新发布；没有增加会影响该系列的重要功能。 MySQL 5.0:Old stable release nearing the end of the product lifecycle MySQL 4.0和3.23是旧的稳定(产品质量)发布系列。该版本不再使用，新的发布只用来修复特别严重的漏洞(以前的安全问题)。 5.4.xx开始到5.7.xx产品线系列介绍 为了更好的整合MySQL AB公司社区和第三方公司开发的新存储引擎，以及吸收新的实现算法等，从而更好的支持SMP架构，提高性能而做了大量的代码重构。版本号为从5.4.xx开始，目前发展到了5.6.x 主流：互联网公司用mysql5.5，逐步过渡到5.6。\n6.0.xx-7.1.xx产品线系列介绍 第三条产品线：为了更好的推广MySQL Cluster版本，以及提高MySQL Cluster的性能和稳定性，以及功能改进和增加，以及改动mysql基础功能，使其对Cluster存储引擎提供更有效地支持与优化。版本号为6.0.xx开发，目前发展到7.1.xx\nMySQL数据库软件命名介绍 MySQL数据库软件的名字是由3个数字和一个后缀组成的版本号。例如，像 mysql-5.0.56.tar.gz 的版本号这样解释：\n第一个数字（5）为主版本号，描述了文件格式。所有版本5发行都有相同文件格式。 第二个数字（0）为发行级别，主版本号和发行级别组合到一起便构成了发行序列号。 第三个数字（56 为在此发行系列的版本号，随每个新分发版本递增。通常你需要已经选择的发行(release)的最新版本。 每次更新后，版本字符串的最后一个数字递增。如果相对于前一个版本增加了新功能或有微小的不兼容性，字符串的第二个数字递增。如果文件格式改变，第一个数字递增。 后缀显示发现的稳定性级别。通过一系列后缀显示如何改进稳定性。可能的后缀有：\nalpha 表明发行包含大量未被彻底测试的新代码。已知的缺陷应该在新闻小节被记录。请参见附录D：MySQL变更史。在大多数alpha版本中也有新的命令和扩展。alpha版本也可能有主要代码更改等开发。但我们在发布前一定对其进行测试。\nbeta 意味着该版本功能是完整的，并且所有的新代码被测试了，没有增加重要的新特征，应该没有已知的缺陷。当alpha版本至少一个月没有出现报导的致命漏洞，并且没有计划增加导致已经实施的功能不稳定的新功能时，版本则从alpha版变为beta版。在以后的beta版、发布版或产品发布中，所有API、外部可视结构和SQL命令列均不再更改。\nrc 是发布代表；是一个发行了一段时间的beta版本，看起来应该运行正常。只增加了很小的修复。(发布代表即以前所称的gamma 版) 如果没有后缀，这意味着该版本已经在很多地方运行一段时间了，而且没有非平台特定的缺陷报告。只增加了关键漏洞修复修复。这就是我们称为一个产品（稳定）或“通用”版本的东西。\nMySQL的命名机制于其它产品稍有不同。一般情况，我们可以很放心地使用已经投放市场两周而没有被相同发布系列的新版本所代替的版本。\nMySQL产品版本最终选择建议 稳定版：选择开源的社区版的稳定GA版本。 产品线：可以选择5.1或5.5，互联网公司主流5.5，其次是5.1和5.6 选择MySQL数据库GA版本发布后6个月以上的GA版本 选择前后几个月没有打的BUG修复的版本，二不是大量修复BUG的集中版本 最好向后较长时间没有更新发布的版本 考虑开发人员开发程序使用的版本是否兼容你选择的版本 作为内部开发测试数据库环境，跑大概3-6个月的时间 优先企业非核心业务采用新版本的数据库GA版本软件 向DBA高手请教，使用真正的高手们使用过得好用的GA版本产品 经过上述工序后，若是没有重要功能BUG或性能瓶颈，则可以开始开率作为任何业务数据服务的后端数据库软件。 安装MySQL 最正宗的产品线5.1及以前：常规的编译方式安装MySQL\n所谓常规方式编译安装MySQL就是延续早起MySQL的3部曲安装方式，即 ./configure; make; make install\n此种方式适合所有 MySQL 5.0.xx ~ 5.1.xx产品系列，是最常规的编译方式。\n采用cmake方式编译安装MySQL 由于MySQL 5.5.xx ~ 5.6.xx 产品系列特殊性，所以编译方式也和早期的产品安装方式不同，采用cmake或gmake方式编译安装。即 ./cmake;make;make install，生产场景具体命令及参数为\n采用二进制方式免编译安装MySQL 采用二进制方式免编译安装MySQL，这种方式和yum/rpm包安装方式类似，适合类MySQL产品系列，不需要负载的编译设置及编译时间等待，直接解压下载的软件包，初始化可完成mysql的安装启动。\n如何正确选择MySQL的安装方式 yum/rpm安装适合对数据库要求不太高的场合，例如并发不大，公司内部，企业内部的一些应用场景。二进制免安装比较简单方便，适合5.0-5.1和5.5-5.6系列，是很多专业DBA的选择，普通linux运维人员多采用编译的方式，5.0-5.1采用的常规方式，5.5-5.6采用cmake方式。\n建议，安装机器较少，推荐cmake方式，数量多了就用二进制免安装。\nMySQL下载 http://www.mysql.com\nenterprise：企业版 商业版 community：社区版 yum repository：yum仓库（centos redhat fedora） apt repository：apt-get仓库（debian Ubuntu） SUSE repository：suse仓库 window：windows版 企业场景MySQL安装方式 序号 安装方式 特点说明 1 yum/rpm包安装 简单、速度快，但是不能定制安装，入门新手常用这种方式 2 二进制安装 解压软件，简单配置后就能使用，不用安装，速度较快，专业DBA喜欢这种方式。软件名如：\nlinux-5.5.32-linux2.6-x86_64.tar.gz\nmysql-5.6.33-linux-glibc2.5-x86_64.tar 3 源码编译安装 可以定制安装，但是安装时间长，例如：字符集安装路径等，软件名：linux-5.5.32.tar.gz。\n针对mysql5.1；\nmysql5.5以上 ./cmake ; gmake;gmake instal ; 4 源码软件结合yum/rpm安装 把源码软件制作成符合要求的rpm，放到yum仓库里，然后通过yum来安装，结合上面1和3的优点，即安装快速，可任意定制参数，但是安装者需要具备更深能力。 默认路径 usr/local/mysql\n大型门户把源码根据企业的需求制作成rpm，搭建yum仓库，yum install xxx -y\n二进制包安装 创建MySQL用的账号 bash 1 useradd -s /sbin/nologin -M mysql 二进制方式安装mysql 解压软件包\nbash 1 tar -zxf mysql-5.5.32-linux2.6-x86_64.tar.gz 移动目录\nbash 1 mv mysql-5.5.32-linux2.6-x86_64 /app/mysql-5.5.32 创建软连接，生成去掉版本号的路径方便访问\nbash 1 ln -s /app/mysql-5.5.32/ /app/mysql 提示：操作到此部，相当于编译安装make install之后\n初始化MySQL bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $ /app/mysql/scripts/mysql_install_db \\ --basedir=/app/mysql/ \\ --datadir=/app/mysql/data \\ --user=mysql WARNING: The host \u0026#39;centos\u0026#39; could not be looked up with resolveip. This probably means that your libc libraries are not 100 % compatible with this binary MySQL version. The MySQL daemon, mysqld, should work normally with the exception that host name resolving will not work. This means that you should use IP addresses instead of hostnames when specifying MySQL privileges ! Installing MySQL system tables... OK Filling help tables... OK To start mysqld at boot time you have to copy support-files/mysql.server to the right place for your system ..... ..... You can start the MySQL daemon with: cd /app/mysql/ ; /app/mysql//bin/mysqld_safe \u0026amp; You can test the MySQL daemon with mysql-test-run.pl cd /app/mysql//mysql-test ; perl mysql-test-run.pl Please report any problems with the /app/mysql//scripts/mysqlbug script! 初始化MySQL配置文件 bash 1 2 3 4 5 6 7 $ ls /app/mysql/support-files/*.cnf my-huge.cnf my-innodb-heavy-4G.cnf my-large.cnf my-medium.cnf my-small.cnf # my-medium.cnf \u0026lt; my-small.cnf \u0026lt; my-small.cnf \u0026lt; my-huge.cnf \u0026lt; my-innodb-heavy-4G.cnf 虚拟机测试环境下选择my-small.cnf配置模板。如果是生产环境可以根据硬件配置选择高级的配置文件\nbash 1 cp /app/mysql/support-files/my-small.cnf /etc/my.cnf 配置启动MySql数据库 设置启动脚本 二进制默认安装路径是/usr/local/mysql，启动脚本里是/usr/local/mysql都需要替换\nbash 1 2 3 4 5 # 启动脚本 /app/mysql/bin/mysqld_safe # 替换命令 sed -i \u0026#39;s#/usr/local/mysql#/app/mysql#g\u0026#39; /app/mysql/bin/mysqld_safe 启动数据库\n传统方式 bash 1 2 3 4 5 6 7 8 9 10 11 12 cp mysql.server /etc/init.d/mysqld sed -i \u0026#39;s#/usr/local/mysql#/app/mysql#g\u0026#39; /etc/init.d/mysqld chmod +x /etc/init.d/mysqld killall mysqld $ /etc/init.d/mysqld start Starting MySQL.. SUCCESS! $ /etc/init.d/mysqld stop Shutting down MySQL. SUCCESS! $ /app/mysql/bin/mysqld_safe \u0026amp; #\u0026lt;==“\u0026amp;”作用是在后台执行MySQL服务 mysql启动错误\nbash 1 2 3 $ ./mysql start Starting MySQL... $ 170521 22:59:38 mysqld_safe error: log-error set to \u0026#39;/data/3306/logs/mysql_3306.err\u0026#39;, however file don\u0026#39;t exists. Create writable for user \u0026#39;mysql\u0026#39;. mysqlbug https://bugs.mysql.com/bug.php?id=84427\n检查MySQL数据库是否启动\nbash 1 2 3 $ lsof -i :3306 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME mysqld 38313 mysql 10u IPv4 73384 0t0 TCP *:mysql (LISTEN) 配置mysql命令全局使用 配置全局路径的意义\n如果不为MySQL的命令配置全局路径，就无法直接在命令行输入mysql这样的命令，只能用全局命令\n方法1：\nbash 1 2 3 4 vi /etc/profile PATH=\u0026#34;/app/mysql/bin:$PATH\u0026#34; source /etc/profile echo \u0026#39;PATH=\u0026#34;/app/mysql/bin:$PATH\u0026#34;\u0026#39; \u0026gt;\u0026gt;/etc/profile \u0026amp;\u0026amp; . /etc/profile 方法2：\nbash 1 2 3 4 5 6 # 尽量往前面拷，要不会和系统yum安装的mysql冲突 cp /app/mysql/bin/* /usr/local/sbin $ which mysql # 如果找到的是 /usr/bin是系统yum安装的，不要让我们安装的和yum安装的冲突 /app/mysql/bin/mysql 方法3：\nbash 1 ln -s /app/mysql/bin/* /usr/local/sbin/ 特别强调：必须把MySQL命令路径放在PATH路径中的前面，否则可能会导使用了mysql等命令和编译安装的不是一个，进而产生错误。\nyum安装MySQL命令访问编译安装的服务器而出来问题：http://oldboy.blog.51cto/25614110/011\nMySQL-5.6 二进制包安装 安装成功提示：与MySQL 5.5.x一样看到两个OK即安装完毕\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ /app/mysql/scripts/mysql_install_db \\ --basedir=/app/mysql/ \\ --datadir=/app/mysql/data \\ --user=mysql WARNING: The host \u0026#39;lamp_server\u0026#39; could not be looked up with /app/mysql//bin/resolveip. This probably means that your libc libraries are not 100 % compatible with this binary MySQL version. The MySQL daemon, mysqld, should work normally with the exception that host name resolving will not work. This means that you should use IP addresses instead of hostnames when specifying MySQL privileges ! Installing MySQL system tables...2016-09-28 17:45:23 0 [Warning] TIMESTAMP with implicit DEFAULT .... .... OK Filling help tables...2016-09-28 17:45:28 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details). .... 将启动脚本复制到/etc/ini.d下\nbash 1 2 3 cp support-files/mysql.server /etc/init.d/mysqld cp my.cnf /etc/my.cnf #←修改配置文件 vi /etc/my.cnf [mysqld] 中添加：\nbash 1 2 3 4 basedir = /usr/local/mysql datadir = /usr/local/mysql/data port = 3306 server_id = 1 将全局启动命令做个链接\nbash 1 2 ln -s /app/mysql/bin/mysql /usr/bin echo \u0026#34;PATH=/app/mysql/bin:$PATH\u0026#34; \u0026gt;\u0026gt;/etc/profile mysql5.6二进制后登陆\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ mysql -uroot -p Welcome to the MySQL monitor. Commands end with ; or \\g. Your MySQL connection id is 2 Server version: 5.6.33 MySQL Community Server (GPL) Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type \u0026#39;help;\u0026#39; or \u0026#39;\\h\u0026#39; for help. Type \u0026#39;\\c\u0026#39; to clear the current input statement. mysql\u0026gt; MySQL多实例 什么是MySQL多实例？ 简单的说，MySQL多实例就是一台服务器上同时开启多个不同的服务器端口（如3306、3307），同时运行多个MySQL服务器进程，这些服务进程通过不同socket监听不同的服务端口来提供服务。\n这些MySQL多实例共用一套MySQL安装程序，使用不同的my.cnf（也可相同）配置文件、启动程序（也可以相同）的数据文件。在提供服务时，多实例MySQL在逻辑上看来是各自独立的，他们根据配置文件的对应设定值，获得服务器相应数量的硬件资源。\nMySQL多实例就相当于房子的多个我是，每个实例可以看做一件我是，整个服务器就是一套房子，服务器的硬件资源（cpu,mem,disk）、软件资源（centos）可以看做房子的卫生间、厨房、客厅，是房子的公用资源。\nnginx apache Haproxy memcached redis等都可配置多实例。\nMySQL多实例的作用与问题 有效利用服务器资源 当单个服务器资源有剩余时，可以充分利用剩余的资源提供更多的服务，实现资源的逻辑隔离。\n节约服务器资源 当公司自己紧张，但是数据库又需要各自尽量独立地提供服务，而且需要主从复制等技术，多实例就再好不过了。\n当某个数据库实例并发很高或者有SQL慢查询时，整个实例会消耗大量的系统CPU、磁盘IO等资源，导致服务器上的其他数据库实例提供服务的质量一起下降。会存在资源互抢占问题。不同实例获取的资源是相对立的，无法像虚拟化一样完全隔离。\nMySQL多实例的生产应用场景 资金紧张型公司的选择 若公司资金紧张，公司业务访问量又不太大，但又希望不同业务的数据库服务各自尽量独立的提供服务而互相不收影响，同时，还需要主从复制等技术提供备份或读写分离服务，那么，多实例就再好不过了。比如：可以通过3台服务器部署9-15个实例，交叉做主从复制、数据备份及读写分离，这样就可达到9-15太服务器每个只装一个数据库才有的效果。这里要强调的是，所谓的尽量独立是相对的。\n并发不是特别大的业务 当公司业务访问量不太大的时候，服务器的资源基本都是浪费的，这时就很适合多实例的应用，如果对SQL语句的优化做的比较好，MySQL多实例会是一个很值得使用的技术，及时并发很大，合理分配好系统资源以及搭配好系统服务，也不会有太大问题\n门户网站应用MySQL多实例场景 门户网站通常都会使用多实例，因为配置硬件好的服务器，可节省IDC机柜空间，同时，跑多实例也会减少硬件资源跑不满的浪费。一般是丛库多实例，例如某部门中使用的IBM服务器为48核，96GB内存，一台服务器跑3-4个实例。(高配多实例，节省机柜空间，虚拟化也是这样)\nMySQL多实例常见的配置方案 单一配置文件、单一启动程序多实例部署方案 下面是MySQL官方文档提到的单一配置文件、单一启动程序多实例部署方案，不推荐此方案，这里仅作为知识点提及，不在涉及此方案说明。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [mysqld_multi] mysqld=/usr/bin/mysqld_safe mysqladmin=/usr/bin/mysqladmin user=mysql [mysql1] socket=/var/lib/mysql/mysql.sock port=3306 pid-file=/var/lib/mysql/mysql.pid datadir=/var/lib/mysql user=mysql [mysql1] socket=/var/lib/mysql/mysql.sock port=3306 pid-file=/var/lib/mysql/mysql.pid datadir=/var/lib/mysql user=mysql skip-name-resolve server-id=10 bash 1 mysql_multi --config-file=/data/mysql/my_multi.cnf start 1,2 #\u0026lt;==启动命令 对于该方案，缺点是耦合度太高，一个配置文件，不好管理。工作开发和运维的统一。原则：降低耦合度。\n多配置文件多启动程序部署方案 以下是已经部署好的MySQL-5.5双实例的目录信息及文件注释说明\nbash 1 2 3 4 5 6 7 8 data ├── 3306 │ ├── my.cnf #\u0026lt;==配置文件 │ ├── data #\u0026lt;==数据文件 │ └── mysql #\u0026lt;==多实例启动脚本 └── 3307 ├── my.cnf └── mysql 安装MySQL多实例 安装MySQL需要的依赖包和编译软件 安装MySQL之前需要安装MySQL依赖包\nbash 1 yum install -y libaio-devel ncurses-devel 安装编译MySQL需要的软件\n因MySQL5.5系列采用的cmake编译，需要先下载安装cmake\nbash 1 yum install cmake -y 采用编译方式安装MySQL bash 1 2 tar zxf mysql-5.5.52.tar.gz cd mysql-5.5.52 编译参数\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 cmake . -DCMAKE_INSTALL_PREFIX=/app/mysql-5.5.54 \\ -DMSQL_DATADIR=/app/mysql-5.5.54/data \\ -DMYSQL_UNIX_ADDR=/app/mysql-5.5.54/tmp/mysql.sock \\ -DDEFAULT_CHARSET=utf8 \\ -DDEFAULT_COLLATION=utf8_general_ci \\ -DEXTRA_CHARSETS=gbk,gb2312,utf8,ascii \\ -DENABLED_LOCAL_INFILE=ON \\ -DWITH_INNOBASE_STORAGE_ENGINE=1 \\ -DWITH_FEDERATED_STORAGE_ENGINE=1 \\ -DWITH_BLACKHOLE_STORAGE_ENGINE=1 \\ -DWITH_EXAMPLE_STORAGE_ENGINE=1 \\ -DWITHOUT_PARTITION_STORAGE_ENGINE=1 \\ -DWITH_FAST_MUTEXES=1 \\ -DWITH_ZLIB=bundled \\ -DENABLED_LOCAL_INFILE=1 \\ -DWITH_READLINE=1 \\ -DWITH_EMBEDDED_SERVER=1 \\ -DWITH_DEBUG=0 创建软连接\nbash 1 ln -s /app/mysql-5.5.54/ /app/mysql 创建MySQL多实例的数据文件目录 在企业中，通常以 /data 目录作为MySQL多实例总的根目录，然后规划不同的数字（即MySQL实例端口号）作为/data下面的二级目录，不同的二级目录对应的数字就作为MySQL实例的端口号，以区别不同的实例，数字对应的二级目录下包含MySQL的数据文件、配置文件及启动文件等。 创建数据目录\nbash 1 2 mkdir -p /data/{3306,3307}/data # 多个可以依次累加，生产环境中一般3-4个实例为最佳。 创建MySQL多实例的配置文件 MySQL数据库默认为用户提供了多个配置文件模板，用户可以根据服务器硬件配置的大小来选择。\nbash 1 2 3 4 5 6 $ ls /app/mysql/support-files/my*.cnf my-huge.cnf my-medium.cnf my-innodb-heavy-4G.cnf my-small.cnf my-large.cnf 上面是单实例的默认配置文件模板，如果配置多实例，和单实例会有不同。为了让MySQL多实例之间彼此独立，因此要为每一个实例建立一个my.cnf配置文件和一个启动文件mysql，让他们分别对应自己的数据文件目录data 创建配置文件及目录略，一般都是用配置好的配置文件与脚本\n配置权限 bash 1 2 3 4 $ find /data -type f -name \u0026#39;mysql\u0026#39; #\u0026lt;==启动脚本中存在mysql密码要注意权限 /data/3306/mysql /data/3307/mysql find /data -type f -name \u0026#39;mysql\u0026#39;|xargs chmod 700 MySQL相关命令加入全局路径的配置 bash 1 2 echo \u0026#34;PATH=/app/mysql/bin:$PATH\u0026#34; ln -s /app/mysql/bin/* /usr/sbin/ 初始化多实例数据库文件 上述步骤全部配置完毕后，就可以初始化数据库文件了，这个步骤其实也可以在编译安装MySQL之后就操作，只不过放到这里更合理，\n初始化MySQL数据库 初始化数据库会有很多提示，如果没有error级别的错误，有两个ok字样表示初始化成功，否则就解决初始化问题\nbash 1 2 3 4 5 6 7 8 9 10 11 cd /app/mysql/script/ ./mysql_install_db \\ --basedir=/app/mysql/ \\ --datedir=/data/3306/data \\ --user=mysql ./mysql_install_db \\ --basedir=/app/mysql/ \\ --datadir=/data/3306/data \\ --user=mysql \\ --defaults-file=/data/3306/my.cnf 初始化数据可的原理及结果说明 初始化数据库的实质就是创建基础的数据库系统的库文件，例如：生成MySQL库表等。 初始化数据可偶查看对应实例的数据目录，可以看到多了一些文件 启动MySQL多实例数据库\nbash 1 2 3 4 5 6 7 /data/3306/mysql start /data/3307/mysql start $ netstat -nltup tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 38980/mysqld tcp 0 0 0.0.0.0:3307 0.0.0.0:* LISTEN 22942/mysqld tcp 0 0 :::52113 :::* LISTEN 1123/sshd MySQL多实例启动故障排错说明 如果MySQL多实例有服务没有被启动，排查办法如下：\n如果发现没有显示MySQL对应实例的端口，请稍微等待几秒再检查，MySQL服务的启动比web服务慢一些\n如果还是不行，请查看MySQL服务对应实例的错误日志，错误日志路径在my.cnf配置的最下面定义。\n例如3306的错误日志为：\nbash 1 2 3 [mysqld_safe] log-error=/data/3306/mysql_3306.err pid-file=/data/3306/mysqld.pid 细看所有执行命令返回屏幕输出，不要忽略关键的输出内容。 辅助查看系统日志/var/log/message 如果是MySQL关联了其他服务。要同时查看相关服务的日志 配置及管理MySQL多实例数据库 配置MySQL多实例数据库开机自启动 服务的开机自启动很关键，MySQL多实例的启动也不例外。\n登陆MySQL测试\ntext 1 2 3 4 $ mysql -S /data/3306/mysql.sock #←socket用于区别不同的实例 Welcome to the MySQL monitor. Commands end with ; or \\g. ..... Type \u0026#39;help;\u0026#39; or \u0026#39;\\h\u0026#39; for help. Type \u0026#39;\\c\u0026#39; to clear the current input statement. MySQL多实例数据库的管理方法 MySQL安装完成后，默认root账户是没有密码的，登陆不同的实例需要指定不同实例的socket路径及文件，在my.cnf中指定的。注意：不同的socket虽然名字相同，但是路径是不同的，因此是不同的文件。\nbash 1 2 mysql -S /data/3306/mysql.sock mysql -S /data/3307/mysql.sock 重启多实例 若要重启多实例数据库也需要进行相应的配置。在重启数据库前，需要调整不同实例启动文件里对应的数据库密码：\nbash 1 2 3 4 sed -n \u0026#39;13p\u0026#39; mysql sed -i \u0026#39;s#mysql_pwd=\u0026#34;oldboy\u0026#34;#mysql_pwd=\u0026#34;111\u0026#34;#g\u0026#39; mysql ../3307/mysql sed -i \u0026#39;s#mysql_pwd=\u0026#34;oldboy\u0026#34;#mysql_pwd=\u0026#34;111\u0026#34;#g\u0026#39; mysql ../3307/mysql sed -n \u0026#39;13p\u0026#39; mysql 由于选择了mysqladmin shutdown的停止方式，所以停止数据库时，需要在启动文件里配置数据库的密码。上面由于密码不对，顾提示密码不对的错误\nbash 1 2 3 4 5 6 $ /data/3306/mysql stop Stoping MySQL... /app/mysql/bin/mysqladmin: connect to server at \u0026#39;localhost\u0026#39; failed error: \u0026#39;Access denied for user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; (using password: YES)\u0026#39; $ /data/3306/mysql stop -S /data/3306/mysql.sock Stoping MySQL... 提示：禁止使用pkill、kill -9 killall -9等命令强制杀死数据库，这回引起数据库无法启动等故障发生。\n多实例MySQL登陆问题 多实例本地登陆MySQL 多实例本地登陆一般是通过socket文件来指定具体登陆到那个实例的，此文件的具体位置是在mysql编译过程或my.cnf文件里指定的。在本地登陆数据库时，登陆程序会通过socket文件来判断登陆的是哪个数据库实例。\n例如：通过mysql -uroot -p111 -S /data/3307/mysql.sock可知，登陆的是3307实例。mysql.sock是MySQL服务器端与本地MySQL客户端进行通信的Unix套接字文件。\n远程连接登陆mysql 远程登陆MySQL多实例中的一个实例时，通过TCP端口(port)来指定所要登陆的MySQL实例，此端口的配置是在MySQL配置文件my.cnf指定的\n例如：在mysql -uroot -p111 -h192.16-P3307 -P为端口参数，后面接具体的实例端口，端口是一种 “逻辑连接位置” ，是客户端程序被分派到计算机上特殊服务程序的一种方式，强调提前在192.168.1.2上对该用户做授权。\nsql 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 DROP USER \u0026#39;monitor\u0026#39;@\u0026#39;%\u0026#39;; mysql\u0026gt; select user,host from mysql.user; +----------+------------+ | user | host | +----------+------------+ | root | 127. | | root | localhost | +----------+------------+ --MySQL的用户加主机名组成一个用户 mysql\u0026gt; select user(); +----------------+ | user() | +----------------+ | root@localhost | +----------------+ 常见错误 安装报错解决 Access denied for user 'root'@'localhost' (using password: NO) sql 1 ERROR 1045 (28000): Access denied for user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; (using password: NO) 问题原因：密码不对哦\nError: Can't create/write to file sql 1 Error: Can\u0026#39;t create/write to file \u0026#39;/tmp/#sql_4f4_0.MYD\u0026#39; (Errcode: 17) 问题原因： 权限问题\n解决方法：chmod -R 1777 /tmp\nERROR 2002 (HY000): Can't connect to local sql 1 2 ERROR 2002 (HY000): Can\u0026#39;t connect to local MySQL server through socket \u0026#39;/var/run/mysqld/mysqld.sock\u0026#39; (2) 问题原因：⑴ 服务没启动 ⑵ 配置文件中socket文件与编译时指定路径不一致\n解决方法：⑴ 启动服务 ⑵ 多实例启动指定socket文件\nerror while loading shared libraries: bash 1 2 3 4 5 6 $ /app/mysql/scripts/mysql_install_db \\ --basedir=/app/mysql/ \\ --datadir=/app/mysql/data \\ --user=mysql Installing MySQL system tables... /app/mysql//bin/mysqld:error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory 问题原因：缺少libaio包支持\n解决方法：安装后重新初始化即可\nbash 1 yum -install libaio* -y WARNING: The host 'xxx' bash 1 WARNING: The host \u0026#39;centos\u0026#39; could not be looked up with resolveip. 问题原因：需要修改主机名的解析，使其和uname -n一样\nERROR: 1004 Can’t create file bash 1 ERROR: 1004 Can’t create file \u0026#39;/tmp/#sql300e_1_0.frm\u0026#39; (error: 13) 解决：原因是/tmp权限有问题(不解决，后面可能无法登陆数据库)\n使用错误解决 ERROR 2006 在大批量导入数据库时，出现如下错误\nbash 1 2 ERROR 2006 (HY000): MySQL server has gone away No connection. Trying to reconnect... 排查思路：后来检查了没有导入成功的几篇文章，其大小都在8MB以上，会不会是单条记录太大了导致出现ERROR 2006 (HY000): MySQL server has gone away的呢？\n查看允许的最大值\n登陆MySQL后，使用如下命令查询：\nsql 1 2 3 4 5 6 mysql\u0026gt; show global variables like \u0026#39;max_allowed_packet\u0026#39;; +--------------------+---------+ | Variable_name | Value | +--------------------+---------+ | max_allowed_packet | 8388608 | +--------------------+---------+ 上限是刚好8MB，怪不得报错。\n即时生效方法\nsql 1 set global max_allowed_packet=1024*1024*16; 可在不重启MySQL的情况下立即生效，但是重启后就会恢复原样。\n永久生效方法\n编辑 /etc/my.cnf ，将\nsql 1 max_allowed_packet = 1M 修改为\nsql 1 max_allowed_packet = 16M 之后重新导入，就不会产生ERROR 2006 (HY000): MySQL server has gone away错误了。 Unknown/unsupported storage engine: Innodb Plugin \u0026lsquo;InnoDB\u0026rsquo; init function returned error.\nsql 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 mysql\u0026gt; show warnings; +---------+------+----------------------------------------+ | Level | Code | Message | +---------+------+----------------------------------------+ | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | | Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;Y \u0026#39; | +---------+------+----------------------------------------+ 64 rows in set (0.00 sec) ","permalink":"https://www.161616.top/ch1-mysql-deployment/","summary":"MySQL数据库简介 编程语言排名：http://www.tiobe.com/tiobe-index\n数据库排名：http://db-engines.com/en/ranking\nMySQL数据库分类与版本升级 MySQL数据库官网为http://www.mysql.com，其发布的MySQL版本采用双授权政策，和大多数开源产品的路线一样，分别为社区版和商业版，而这两个版本又各自分四个版本依次发布，这四个版本为Alpha版、Beta版、RC版和GA版（GA正式发布版）\nMySQL数据库商业版和社区版的区别 在前面的内容已经阐述过了，MySQL的版本发布采用双授权政策，即分为社区版和商业版，而这两个版本又各自分四个版本依次发布：Alpha版、Beta版、RC版和GA版（GA正式发布版）\nAlpha版 Alpha版一般只在开发的公司内部运行，不对外公开。主要死开发者自己对产品进行测试，检查产品是否存在缺陷、错误，验证产品功能与说明书、用户手册是否一致。MySQL是属于开放源代码的开源产品，因此需要世界各地开发者、爱好者和用户参与软件的开发测试和手册编写等工作。所以会对外公布此版本的源码和产品，方便任何人可以参与开发测试工作，甚至编写与修改用户手册。\nBeta版 Beta版一般是完成功能的开发和所有的测试工作时候的产品，不会存在较大的功能或性能BUG，并且邀请或提供给公户体验与测试，以便更全面地测试软件的不足之处或存在的问题。\nRC版 RC版属于生产环境发布之前的一个小版本或称候选版，是根据Beta测试结果，收集到的BUG或缺陷之处等收集到信息，进行修复和完善之后的新一版本\nGA版 GA版是软件产品正式发布的版本，也称生产版本的产品。一般情况下，企业生产环境都会选择GA版本的MySQL软件，用于真实的生产环境中。偶尔有个别的大型企业会追求新功能驱动而牺牲稳定性使用其他版本，但这个是个例。\nMySQL四中发布版本选择说明 MySQL AB官方网站会把五种数据库版本都提供下载，主要是MySQL数据库属于开发源代码的数据库产品，鼓励全球的技术爱好者参与研发、测试、文档编写和经验分享，甚至包过产品发展规划，对于Development版本、Alpha版本和Beta版本是绝对不允许使用在任何生产环境，毕竟这是一个GA版本之前，也即生产版本发布之前的一个小版本。另外，对MySQL数据库GA版本，也是需要慎重选择，开源社区产品毕竟不是经过严格的测试工序完成的产品，是全球开源技术人员的资源完成的，会存在比商业产品稳定性弱的缺陷。更严格的选择见后文。\nMySQL产品路线 MySQL产品路线变更历史背景 早起MySQL也是遵循版本号逐渐增加的方式发展的，格式例如：mysql-x.xx.xx.tar.gz，例如DBA都非常熟悉的生产场景版本：4.1.7、5.0.56等。\n近几年，为了提高MySQL产品的竞争优势、以及提高性能、降低开发维护成本等原因，同时，更方便企业用户更精准的选择适合的版本产品用于自己的企业生产环境中。 MySQL在发展到5.1系列版本之后，重新规划为3条产品线\n5.0.xx到5.1.xx产品线介绍 第一条产品线：5.0.xx及升级到5.1.xx的产品系列，这条产品线继续完善与改进其用户体验和性能，同时增加新功能，这条路线可以说是MySQL早起产品的延续系列，这一系列的产品发布情况及历史版本如下： MySQL 5.1是当前稳定（产品质量）发布系列。只针对漏洞修复重新发布；没有增加会影响稳定性的新功能。\nMySQL 5.1:Previous stable(production-quality) release MySQL 5.0是前一稳定（产品质量）发布系列。只针对严重漏洞修复和安全修复重新发布；没有增加会影响该系列的重要功能。 MySQL 5.0:Old stable release nearing the end of the product lifecycle MySQL 4.0和3.23是旧的稳定(产品质量)发布系列。该版本不再使用，新的发布只用来修复特别严重的漏洞(以前的安全问题)。 5.4.xx开始到5.7.xx产品线系列介绍 为了更好的整合MySQL AB公司社区和第三方公司开发的新存储引擎，以及吸收新的实现算法等，从而更好的支持SMP架构，提高性能而做了大量的代码重构。版本号为从5.4.xx开始，目前发展到了5.6.x 主流：互联网公司用mysql5.5，逐步过渡到5.6。\n6.0.xx-7.1.xx产品线系列介绍 第三条产品线：为了更好的推广MySQL Cluster版本，以及提高MySQL Cluster的性能和稳定性，以及功能改进和增加，以及改动mysql基础功能，使其对Cluster存储引擎提供更有效地支持与优化。版本号为6.0.xx开发，目前发展到7.1.xx\nMySQL数据库软件命名介绍 MySQL数据库软件的名字是由3个数字和一个后缀组成的版本号。例如，像 mysql-5.0.56.tar.gz 的版本号这样解释：\n第一个数字（5）为主版本号，描述了文件格式。所有版本5发行都有相同文件格式。 第二个数字（0）为发行级别，主版本号和发行级别组合到一起便构成了发行序列号。 第三个数字（56 为在此发行系列的版本号，随每个新分发版本递增。通常你需要已经选择的发行(release)的最新版本。 每次更新后，版本字符串的最后一个数字递增。如果相对于前一个版本增加了新功能或有微小的不兼容性，字符串的第二个数字递增。如果文件格式改变，第一个数字递增。 后缀显示发现的稳定性级别。通过一系列后缀显示如何改进稳定性。可能的后缀有：\nalpha 表明发行包含大量未被彻底测试的新代码。已知的缺陷应该在新闻小节被记录。请参见附录D：MySQL变更史。在大多数alpha版本中也有新的命令和扩展。alpha版本也可能有主要代码更改等开发。但我们在发布前一定对其进行测试。\nbeta 意味着该版本功能是完整的，并且所有的新代码被测试了，没有增加重要的新特征，应该没有已知的缺陷。当alpha版本至少一个月没有出现报导的致命漏洞，并且没有计划增加导致已经实施的功能不稳定的新功能时，版本则从alpha版变为beta版。在以后的beta版、发布版或产品发布中，所有API、外部可视结构和SQL命令列均不再更改。\nrc 是发布代表；是一个发行了一段时间的beta版本，看起来应该运行正常。只增加了很小的修复。(发布代表即以前所称的gamma 版) 如果没有后缀，这意味着该版本已经在很多地方运行一段时间了，而且没有非平台特定的缺陷报告。只增加了关键漏洞修复修复。这就是我们称为一个产品（稳定）或“通用”版本的东西。","title":"ch01 - Linux下安装Mysql"},{"content":"Overview 官方网站：https://www.samba.org/ftp/rsync/rsync.html\nrsync特性\nrsync类似于scp的功能\nrsync还可以在本地的不同分区和目录之间进行全量及增量的复制数据，类似cp又优于cp\nrsync可以实现文件的删除\n一个rsync相当于 scp cp rm，但是还优于他们每一个\n支持拷贝特殊文件如链接文件，设备等 可以有排除指定文件或目录的权限、时间、软硬链接、属主、组所有属性均不改变-p 可以有排除指定文件或目录的功能，相当于打包命令tar的排除功能 可以实现增量同步，即只同步发生变化的数据，因此数据传输效率很高tar。 可以使用rcp rsh ssh等方式来配合传输文件（rsync本身不对数据加密） 可以通过socket（进程方式）传输文件和数据 支持匿名的或认证（无须系统用户）的进程模式传输，可实现方便安全的进行数据备份及镜像 安装rsync linux上安装rsync Platform Command Debian/Ubuntu \u0026amp; Mint sudo apt-get install rsync Arch Linux pacman -S rsync Gentoo emerge sys-apps/rsync Fedora/CentOS/RHEL and Rocky Linux/AlmaLinux sudo yum install rsync openSUSE sudo zypper install rsync windows安装rsync 官网下载cwRsync的服务端和客户端软件，cwRsync官网为：www.itefix.net/cwrsync\nNotes：由于伟大的 people‘s leader president xi 网站已经无法中国地区访问（点击测试），伟大的俄罗斯因为俄乌战争，也不对俄罗斯访问了（俄乌战争开始后，西方大量学术网站禁止了俄罗斯地区的访问）\n所以目前只能下载到一些镜像站上4.x版本，截止到2022年11月的6.2.7相差很多，windows客户端版本可以通过chocolate 安装\nrsync使用说明 rsync命令语法\n选项 注释说明 rsync rsync同步命令 option 为同步时的选项参数 src 为源，及待拷贝的分区、文件或目录等 dest 为目标分区、文件或目录等 rsync参数说明\n参数选项 注释说明 -v \u0026ndash;verbose 详细模式输出，传输时的进度信息 -z \u0026ndash;compress 传输时进行压缩以提高传输效率，--compress-level=NUM 可按级别压缩 -a \u0026ndash;archive 归档模式，表示以递归方式传输文件，并保持所有文件属性，等于-rtopgDl r \u0026ndash;recursive 对子目录进行递归模式，即目录下的所有目录都同样传输 t \u0026ndash;times 保持文件时间信息 o \u0026ndash;owner 保持文件属主信息 p \u0026ndash;perms 保持文件权限 g \u0026ndash;group 保持文件属主信息 P \u0026ndash;progress 显示同步的过程及传输时的进度等信息 D \u0026ndash;devices 爆出设备文件信息 l \u0026ndash;links 保留软连接 -e \u0026ndash;rsh=COMMAND 使用的信道协议，制定替代rsh的shell程序，例如ssh -exclude=PATTERN 制定排除不需要传输的文件模式 \u0026ndash;bwlimit=RATE 限制socket I/O带宽 \u0026ndash;password-file=PATH 指定密码文件，可避免多次输入密码 \u0026ndash;delete 如果服务器端为空，而客户端有文件，加上这个参数就会删除客户端目录下所有文件，相当于rm \u0026ndash;exclude 排除单/多个文件 \u0026ndash;timeout=秒 超时参数 \u0026ndash;port 指定端口 -R \u0026ndash;relative 使用相对路径信息 -u \u0026ndash;update 仅仅进行更新，也就是跳过所有已经存在于DST，并且文件时间晚于要备份的文件。(不覆盖更新的文件) rsync配置文件说明 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 # 每一个程序的进程都要依赖一个用户，默认为nobody，给默认为rsync uid = rsync gid = rsync # 防止出现安全问题的，一般为局域网 use chroot = no # 有多少个客户端可以连接服务器 同时连接的客户端 max connections = 200 # 客户端连接多少时间超时（如连接了不穿数据，多少时间踢掉） timeout = 300 # linux中每个进程都对应一个进程号pid ，pid所在的文件就是pidfile，将来处理进程的时候不用再找了，直接杀pid文件就行 pid file = /var/run/rsyncd.pid # rsync 在传输数据时，双方都在穿可能出错，在一个用户改的时候，其他用户不能改 lock file = /var/run/rsync.lock # 日志，出错 log file = /var/log/rsyncd.log # 模块 相当于nfs共享的目录 可以理解为nfs的 [oldboy] path = /oldboy/ # 在传输过程中遇到错误自动忽略 ignore errors # 可读可写 read only = false # 允不允许你列表 false不允许 list = false # 允许的主机 hosts allow = 10.0.0.0/24 # 拒绝 rsync支持虚拟用户，不是系统用户，这 hosts deny = 0.0.0.0/32 # 传输时验证的用户 auth users = rsync_backup # 用户对应的密码文件，如果指定密码文件，就得来回输入密码，在内网中不需要重复输入密码，就将密码写入文件 secrets file = /etc/rsync.password rsync的工作模式 rsync提供了三种同步模式：\nRsync over SSH Rsync Daemon Local Local rsync如果不使用远程模式类似于cp命令，例如\nrsync /etc/hosts /opt/ == cp /etc/hosts/ /opt/ 注：目录结尾加 / 与不加 / 的区别，不加斜线表示目录以及目录里面的东西，加斜线表示目录里面的东西\nrsync作为damon模式运行 如果主机没有运行 SSH服务，可以使用 rsync --daemon 配置并作为守护进程运行。这种场景下 rsync 监听端口 873 以获取来自其他使用 rsync client 的同步，这种模式下数据是未加密的。\n配置daemon端 配置rsync daemon位置文件\nrsync damon模式配置文件默认在 /etc/rsyncd.conf 如果没有可以自行创建\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 lock file = /var/run/rsync.lock log file = /var/log/rsyncd.log pid file = /var/run/rsyncd.pid [documents] path = /home/juan/Documents comment = The documents folder of Juan uid = juan gid = juan read only = no list = yes auth users = rsyncclient secrets file = /etc/rsyncd.secrets hosts allow = 192.168.1.0/255.255.255.0 rsyncd配置文件分为两部分，全局参数与模块。\nlock file 是 rsync 用于处理最大连接数的文件 log file 是 rsync 同步时的日志；汇集路开始运行的时间, 其他客户端连接的时间与任何错误 pid file 是 rsync 记录了进程ID，可以使用这个文件来kill进程 在全局参数之后，[] 中的是模块部分，代表的是要同步的一个目录，每个模块都是要共享的文件夹\n[name] 分配给模块的名称，每个模块代表一个目录，不能包含斜杠或右方括号 path 同步的文件夹的路径 comment 当客户端获取所有可用模块的时，获取出的列表内模块名称旁边的注释 uid 当 rsync守护进程以root 身份运行时，可以指定以哪个用户拥有文件的权限 gid 同上 read only 决定rsync 客户端是否可以上传文件，默认所有模块为 true list 允许客户端请求可用模块列表，false 为从列表中隐藏模块。 auth users 允许访问该模块内容的用户列表，用户以逗号分隔。用户不需要存在于系统中，他们由密钥文件定义 secrets file 定义包含rsync同步时用户的用户名和密码的文件 hosts allow 允许连接到rsync daemon的网段，默认允许所有主机连接 创建rsync用户\nbash 1 $ useradd rsync -s /sbin/nologin -M 修改共享的目录的所属权限\nbash 1 $ chown -R rsync.rsync /data 创建密码文件\n该文件中，每行代表一个rsync用户，账号和密码用冒号分隔\nbash 1 2 3 $ echo \u0026#34;rsync_backup:111111\u0026#34;\u0026gt; /etc/rsync.password # 因密码可读，所以要降低权限 $ chmod 600 /etc/rsync.password 最后，更改此文件的权限，使其不能被其他用户读取或修改。如果此文件的权限设置不正确，rsync 会报错\nbash 1 sudo chmod 600 /etc/rsyncd.secrets 启动 rsync daemon\n直接使用 --daemon 启动即可\nbash 1 sudo rsync --daemon 使用rsync连接daemon一端 使用 rsync 命令连接 rsync daemon时，不可以像使用 SSH 时那样使用冒号，我们需要使用双冒号 ”::“，后跟模块名，以及同步的文件或文件夹，例如\nbash 1 rsync -rtv user@host::module/source/ destination/ 另一种方法是增加 rsync:// 协议，例如\nbash 1 rsync -rtv rsync://user@host/module/source/ destination/ 如果不想输入密码可以指定参数 --password-file= 提供密码文件，密码文件就是创建的secret file，例如\nbash 1 2 3 $ rsync -avz rsync_backup@192.168.59.121::data \\ /test \\ --password-file=/etc/rsync.password 使用rsync命令推与拉\nPull rsync [OPTION...] [USER@]HOST::SRC... [DEST]\nbash 1 rsync -avz rsync_backup@192.168.59.121::data /test/ --password-file=/etc/rsync.password Push rsync [OPTION...] SRC... [USER@]HOST::DEST || rsync [OPTION...] SRC... rsync://[USER@]HOST[:PORT]/DEST\nbash 1 rsync -avz /test/ rsync://rsync_backup@192.168.59.121/data/ --password-file=/etc/rsync.password Rsync Over SSH 除了daemon模式，也可以使用ssh模式进行传输，例如使用Over SSH命令\nPush rsync [OPTION]... -e ssh [SRC]... [USER@]HOST:DEST Pull rsync [OPTION]... -e ssh [USER@]HOST:SRC... [DEST] 较新版本的 rsync SSH 为默认，可以省略该-e ssh选项，但 over ssh 与 over daemon的区别还是 一个冒号与两个冒号\nrsync使用实例 Over SSH Pull rsync -avz -e 'ssh -p \u0026lt;port\u0026gt;' \u0026lt;user\u0026gt;@\u0026lt;host\u0026gt;:/\u0026lt;remote_dir\u0026gt; /\u0026lt;local_dir\u0026gt; Push rsync -avz -e 'ssh -p \u0026lt;port\u0026gt;' /\u0026lt;local_dir\u0026gt; \u0026lt;user\u0026gt;@\u0026lt;host\u0026gt;:/\u0026lt;remote_dir\u0026gt; Local to Remote Push rsync -avzh \u0026lt;local_dir\u0026gt; \u0026lt;system_user\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;remote_dir\u0026gt; Pull rsync -avzh \u0026lt;system_user\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;remote_dir\u0026gt; \u0026lt;local_dir\u0026gt; 显示进度条 rsync -avzhe ssh --progress \u0026lt;local_dir\u0026gt; \u0026lt;system_user\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;remote_dir\u0026gt; Include and exclude 排除和包含可以使用文件，也可以使用正则表达式，例如\n包含R开头文件： rsync -avze --include 'R*' \u0026lt;system_user\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;remote_dir\u0026gt; \u0026lt;local_dir\u0026gt; 排除所有文件： rsync -avze --exclude '*' \u0026lt;system_user\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;remote_dir\u0026gt; \u0026lt;local_dir\u0026gt; 仅上传R开头的文件：rsync -avze ssh --include 'R*' --exclude '*' \u0026lt;system_user\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;remote_dir\u0026gt; \u0026lt;local_dir\u0026gt; --exclude-from 是同步时排除文件中指定的文件 已存在文件的处理策略 如果想保证 \u0026lt;Src\u0026gt; 与 \u0026lt;DST\u0026gt; 保持一致可以使用 --delete 来删除 \u0026lt;Src\u0026gt; 现有文件或目录（只存在于目标目录不存在于源目标的文件）\nbash 1 rsync -avz --delete \u0026lt;system_user\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;remote_dir\u0026gt; \u0026lt;local_dir\u0026gt; 当在删除或更新目标目录已经存在的文件时，不想删除而想备份，可以指定参数 -b, --backup\n限制传输文件大小 可以使用 “ -–max-size ” 选项指定要传输或同步的最大文件大小。例如限制最大为200k\nbash 1 rsync -avzhe ssh --max-size=\u0026#39;200k\u0026#39; \u0026lt;local_dir\u0026gt; \u0026lt;system_user\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;remote_dir\u0026gt; 同步成功后自动删除源文件 删除发送方的文件：如果不想将备份的本地副本保留在服务器中，可以使用参数 “ \u0026ndash;remove-source-files ” 来自动删除 \u0026lt;src\u0026gt; 的文件吗，一般用于Push场景中。\nbash 1 rsync --remove-source-files -zvh \u0026lt;local_dir\u0026gt; \u0026lt;system_user\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;remote_dir\u0026gt; dry run 与kubectl一样，rsync也支持 --dry-run ，该选项不会对文件进行更改但会显示命令的输出，可以与 -v 参数配合，这样就可以看到哪些内容会被同步\nbash 1 rsync --dry-run --remove-source-files -zvh \u0026lt;local_dir\u0026gt; \u0026lt;system_user\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;remote_dir\u0026gt; 限制传输时带宽 传输时，可以使用选项 “ \u0026ndash;bwlimit ” 选项限制I/O带宽（默认单位KB），例如\nbash 1 rsync --bwlimit=100 -avzhe ssh \u0026lt;local_dir\u0026gt; \u0026lt;system_user\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;remote_dir\u0026gt; 忽略存在 如果目标目录中已经该文件，则忽略，例如\nbash 1 rsync --ignore-existing -zvh \u0026lt;local_dir\u0026gt; \u0026lt;system_user\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;remote_dir\u0026gt; 同步策略 默认rsync 只检查文件的大小和最后修改日期是否发生变化，如果发生变化，就重新传输；参数 -c，--checksum 可以改变rsync 的校验方式，会通过判断文件内容的校验和，决定是否重新传输。\n参数 --size-only 可以使rsync只同步大小有变化的文件，不考虑文件修改时间的差异。\ntroubleshooting text 1 rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1039) [sender=3.0.6] 问题原因：服务器端目录不存在。\n解决：在服务器端创建目录即可解决\n日志：2017/05/07 08:02:16 [2852] rsync: chdir /data1 failed:No such file or directory (2)\ntext 1 rsync: failed to set times on \u0026#34;.\u0026#34; (in data1): Operation not permitted (1) 问题原因：服务器端模块目录权限不对\n解决方法：将服务器模块目录权限更改为rsyncd.conf中的uid与gid\n日志：2017/05/07 08:07:49 [2862] rsync: mkstemp \u0026quot;.paichu.log.yOhm5e\u0026quot; (in data1) failed: Permission denied\ntext 1 2 rsync: failed to connect to 192.168.59.121: No route to host (113) rsync error: error in socket IO (code 10) at clientserver.c(124) [sender=3.0.6] 问题原因：防火墙处于开启状态\n解决方法：关闭防火墙 /etc/init.d/iptables stop\ntext 1 2 3 @ERROR: auth failed on module data1 rsync error: error starting client-server protocol (code 5) at main.c(1503) [sender=3.0.6] 错误原因：\n配置文件语法错误 密码与用户名错误 密码文件权限过大 解决方法：查看日志，查找具体问题\n日志文件：secrets file must not be other-acessible (see strict modes option)\ntext 1 2 rsync: failed to connect to 192.168.59.121: Connection refused (111) rsync error: error in socket IO (code 10) at clientserver.c(124) [receiver=3.0.6] 原因：rsync服务没开启\ntext 1 2 3 4 5 *** Skipping any contents from this failed directory *** sent 485 bytes received 14 bytes 332.67 bytes/sec total size is 45994 speedup is 92.17 rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1039) [sender=3.0.6] 原因：服务器同步目录权限不够或所属组\n解决 chown rsync:rsync\n原因：磁盘cache导致拷贝速度逐渐下降 [1]\nrsync拷贝数据过程中发现一个现象：开始拷贝的时候速度很快，每秒有40MB左右，但拷贝几十分钟之后就降到10MB左右了，两边机器都没有跑什么应用，网络用netcat测也没有问题\n然后我观察到的一个问题是两边的 free 命令都显示出内存占用很高，并且是buffered/cache一栏很高，因为这个缓存是可以手工释放的\nbash 1 2 sync; echo 3 \u0026gt; /proc/sys/vm/drop_caches ssh root@new-repos \u0026#39;sh -c \u0026#34;sync; echo 3 \u0026gt; /proc/sys/vm/drop_caches\u0026#34;\u0026#39; 补充说明\nlinux操作系统会将文件系统的内容缓存起来，以便后面用到时加速，但在数据迁移场景下，基本上没有“后面用到时”这个场景，这个缓存反而碍事（TODO: 为什么导致网络io下降） 对于本机内大量拷贝文件，有人提供了一个 nocache命令 Debian/Ubuntu 已经收录了这个工具。它的功能是临时禁用cache，用法是将要执行的命令用 nocache 包住，比如: nocache cp -a ~/ /mnt/backup/home-$(hostname)，但rsync会使用网络通讯，所以nocache rsync 对远端没有作用（ *Note however, that rsync uses sockets, so if you try a nocache rsync, only the local process will be intercepted.） 也有人在多年以前给rsync提交了一个补丁，增加了 --drop-cache 选项，但遗憾的是没被接纳，说是过于 linux-specific，开发人员的意见（见comment 3 ）是改用nocache: nocache rsync -aiv --rsync-path='nocache rsync' some-host:/src/ /dest/. P.S. nocache工具的主页最后在acknowledgements部分 说，其实它是衍生自rsync的这个补丁的。 Reference ​[1] rsync用于数据迁移/备份的几个细节\n","permalink":"https://www.161616.top/rsync/","summary":"Overview 官方网站：https://www.samba.org/ftp/rsync/rsync.html\nrsync特性\nrsync类似于scp的功能\nrsync还可以在本地的不同分区和目录之间进行全量及增量的复制数据，类似cp又优于cp\nrsync可以实现文件的删除\n一个rsync相当于 scp cp rm，但是还优于他们每一个\n支持拷贝特殊文件如链接文件，设备等 可以有排除指定文件或目录的权限、时间、软硬链接、属主、组所有属性均不改变-p 可以有排除指定文件或目录的功能，相当于打包命令tar的排除功能 可以实现增量同步，即只同步发生变化的数据，因此数据传输效率很高tar。 可以使用rcp rsh ssh等方式来配合传输文件（rsync本身不对数据加密） 可以通过socket（进程方式）传输文件和数据 支持匿名的或认证（无须系统用户）的进程模式传输，可实现方便安全的进行数据备份及镜像 安装rsync linux上安装rsync Platform Command Debian/Ubuntu \u0026amp; Mint sudo apt-get install rsync Arch Linux pacman -S rsync Gentoo emerge sys-apps/rsync Fedora/CentOS/RHEL and Rocky Linux/AlmaLinux sudo yum install rsync openSUSE sudo zypper install rsync windows安装rsync 官网下载cwRsync的服务端和客户端软件，cwRsync官网为：www.itefix.net/cwrsync\nNotes：由于伟大的 people‘s leader president xi 网站已经无法中国地区访问（点击测试），伟大的俄罗斯因为俄乌战争，也不对俄罗斯访问了（俄乌战争开始后，西方大量学术网站禁止了俄罗斯地区的访问）\n所以目前只能下载到一些镜像站上4.x版本，截止到2022年11月的6.2.7相差很多，windows客户端版本可以通过chocolate 安装\nrsync使用说明 rsync命令语法\n选项 注释说明 rsync rsync同步命令 option 为同步时的选项参数 src 为源，及待拷贝的分区、文件或目录等 dest 为目标分区、文件或目录等 rsync参数说明","title":"同步工具rsync使用指南"},{"content":"1 firewalld的配置文件 以xml格式为主\nbash 1 2 /etc/firewalld/ /usr/lib/firewalld/ 使用时的规则是这样的：当需要一个文件时firewalld会首先到第一个目录中查找，如果找到直接使用，否则会继续到第二个目录中查找。\n1.1 配置文件结构 firewalld的配置文件主要有两个文件和三个目录\n文件：firewalld.conf、lockdown-whitelist.xml\n目录：zone services icmptypes\n1.2 firewalld.conf firewalld的主配置文件，不过非常简单，只有五个配置项\ndefaultzone：默认使用的zone默认public\nminimalmark：标记最小值\ncleanUpOnExit：退出当前firewalld后是否清除防火墙规则，默认值为yes;\nzones\n保存zone配置文件\nservices\n保存service配置文件\nicmptypes\n保存和icmp类型相关的配置文件\n2 firewall操作 2.1 查看firewalld状态 bash 1 2 3 4 5 6 7 $ firewall-cmd --state not running $ systemctl start firewalld $ firewall-cmd --state running 2.2 不改变状态的条件下重新加载防火墙 bash 1 firewall-cmd --reload 获取支持的区域列表\nbash 1 firewall-cmd --get-zone 获得所有支持的服务\nbash 1 2 $ firewall-cmd --get-services RH-Satellite-6 amanda-client bacula bacula-client dhcp dhcpv6 dhcpv6-client dns ftp high-availability http https imaps ipp ipp-client ipsec kerberos kpasswd ldap ldaps libvirt libvirt-tls mdns mountd ms-wbt mysql nfs ntp openvpn pmcd pmproxypmwebapi pmwebapis pop3s postgresql proxy-dhcp radius redis rpc-bind samba samba-client smtp ssh telnet tftp tftp-clienttransmission-client vnc-server wbem-https 获取所有支持的ICMP类型\nbash 1 2 $ firewall-cmd --get-icmptypes destination-unreachable echo-reply echo-request parameter-problem redirect router-advertisement router-solicitation source-quench time-exceeded 3 firewall-cmd的基础操作 3.1 设置并获得默认区域 bash 1 2 3 4 $ firewall-cmd --set-default-zone=public success $ firewall-cmd --get-default-zone public 3.2 在不改变条件下重载防火墙 bash 1 2 $ firewall-cmd --reload success 获得所有支持区域\nbash 1 2 $ firewall-cmd --get-services RH-Satellite-6 amanda-client bacula bacula-client dhcp dhcpv6 dhcpv6-client dns ftp high-availability http https imaps ipp ipp-client ipsec kerberos kpasswd ldap ldaps libvirt libvirt-tls mdns mountd ms-wbt mysql nfs ntp openvpn pmcd pmproxypmwebapi pmwebapis pop3s postgresql proxy-dhcp radius redis rpc-bind samba samba-client smtp ssh telnet tftp tftp-clienttransmission-client vnc-server wbem-https 列出全部启用区域的信息\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ firewall-cmd --list-all-zones block interfaces: sources: services: ports: masquerade: no forward-ports: icmp-blocks: rich rules: dmz interfaces: sources: services: ssh ports: masquerade: no forward-ports: icmp-blocks: rich rules: ... ... 3.3 获得活动区域 bash 1 2 3 $ firewall-cmd --get-active-zones public interfaces: eth0 3.4 禁止掉ssh服务 bash 1 firewall-cmd --zone=public --remove-service=ssh 3.5 允许某个服务生效 bash 1 firewall-cmd --zone=public --add-service=ssh 永久添加 --permanent 3.6 允许某个服务在一个时间段内生效 bash 1 2 3 4 5 6 7 8 9 10 11 12 $ firewall-cmd --add-service=ssh --timeout=60 success $ firewall-cmd --list-all drop (default, active) interfaces: eth0 sources: services: ssh ports: masquerade: yes forward-ports: icmp-blocks: rich rules: bash 1 2 3 4 5 6 7 8 9 10 $ firewall-cmd --list-all drop (default, active) interfaces: eth0 sources: services: ports: masquerade: yes forward-ports: icmp-blocks: rich rules: 3.7 查询某个区域是否开启某项服务 bash 1 2 3 4 $ firewall-cmd --zone=public --query-service=ssh no $ firewall-cmd --zone=home --query-service=ssh yes 3.8 启动区域端口协议组合 此举将启用端口和协议的组合。端口可以是一个单独的端口 或者是一个端口范围 - 。协议可以是 tcp 或udp。\nbash 1 2 $ firewall-cmd --zone=public --add-port=80/tcp success bash 1 2 $ firewall-cmd --zone=public --add-port=10000-20000/tcp success bash 1 2 3 4 5 6 7 8 9 10 $ firewall-cmd --zone=public --list-all public interfaces: sources: services: dhcpv6-client ports: 80/tcp 10000-20000/tcp masquerade: yes forward-ports: icmp-blocks: rich rules: 3.9 禁用端口和协议组合 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 $ success $ firewall-cmd --zone=public --list-all public interfaces: sources: services: dhcpv6-client ports: 80/tcp masquerade: yes forward-ports: icmp-blocks: rich rules: 3.10 启动IP伪装功能 此举启用区域的伪装功能。私有网络的地址将被隐藏并映射到一个公有IP。这是地址转换的一种形式，常用于路由。由于内核的限制，伪装功能仅可用于IPv4。\nbash 1 2 $ firewall-cmd --add-masquerade --zone=home success 3.11 禁用ip伪装 bash 1 firewall-cmd -- -masquerade --zone=home 在区域中启用端口转发或映射\n语法：\nbash 1 firewall-cmd [--zone=] --add-forward-port=port=[-]:proto= { :toport=[-] | :toaddr=| :toport=[-]:toaddr=} bash 1 2 $ firewall-cmd --add-forward-port=port=9999:proto=tcp:toaddr=192.168.2.82:toport=80 success http://www.sa-log.com/282.html\nhttp://www.fedora.hk/linux/yumwei/show_15.html\nhttps://yq.aliyun.com/ziliao/94786\n启用区域的ICMP阻塞功能\n","permalink":"https://www.161616.top/firewalld/","summary":"1 firewalld的配置文件 以xml格式为主\nbash 1 2 /etc/firewalld/ /usr/lib/firewalld/ 使用时的规则是这样的：当需要一个文件时firewalld会首先到第一个目录中查找，如果找到直接使用，否则会继续到第二个目录中查找。\n1.1 配置文件结构 firewalld的配置文件主要有两个文件和三个目录\n文件：firewalld.conf、lockdown-whitelist.xml\n目录：zone services icmptypes\n1.2 firewalld.conf firewalld的主配置文件，不过非常简单，只有五个配置项\ndefaultzone：默认使用的zone默认public\nminimalmark：标记最小值\ncleanUpOnExit：退出当前firewalld后是否清除防火墙规则，默认值为yes;\nzones\n保存zone配置文件\nservices\n保存service配置文件\nicmptypes\n保存和icmp类型相关的配置文件\n2 firewall操作 2.1 查看firewalld状态 bash 1 2 3 4 5 6 7 $ firewall-cmd --state not running $ systemctl start firewalld $ firewall-cmd --state running 2.2 不改变状态的条件下重新加载防火墙 bash 1 firewall-cmd --reload 获取支持的区域列表\nbash 1 firewall-cmd --get-zone 获得所有支持的服务\nbash 1 2 $ firewall-cmd --get-services RH-Satellite-6 amanda-client bacula bacula-client dhcp dhcpv6 dhcpv6-client dns ftp high-availability http https imaps ipp ipp-client ipsec kerberos kpasswd ldap ldaps libvirt libvirt-tls mdns mountd ms-wbt mysql nfs ntp openvpn pmcd pmproxypmwebapi pmwebapis pop3s postgresql proxy-dhcp radius redis rpc-bind samba samba-client smtp ssh telnet tftp tftp-clienttransmission-client vnc-server wbem-https 获取所有支持的ICMP类型","title":"firewalld"},{"content":"Keepalived介绍 Keepalived软件起初是专为LVS负载均衡软件设计的，用来管理并监控LVS集群系统中各个服务节点的状态，后来又加入了可以实现高可用的VRRP功能。因此，Keepalived除了能够管理LVS软件外，还可以作为其他服务（例如：Nginx、Haproxy、MySQL等）的高可用解决方案软件。\nKeepalived软件主要是通过VRRP协议实现高可用功能的。VRRP是Virtual Router Redundancy Protocol（虚拟路由器冗余协议）的缩写，VRRP出现的目的就是为了解决静态路由单点故障问题的，它能够保证当个别节点宕机时，整个网络可以不间断地运行。所以，Keepalived一方面具有配置管理LVS的功能，同时还具有对LVS下面节点进行健康检查的功能，另一方面也可实现系统网络服务的高可用功能。\nKeepalived服务的三个重要功能 管理LVS负载均衡软件 早期的LVS软件，需要通过命令行或脚本实现管理，并且没有针对LVS节点的健康检查功能。为了解决LVS的这些使用不便的问题，Keepalived就诞生了，可以说，Keepalived软件起初是专为解决LVS的问题而诞生的。因此，Keepalived和LVS的感情很深，它们的关系如同夫妻一样，可以紧密地结合，愉快地工作。Keepalived可以通过读取自身的配置文件，实现通过更底层的接口直接管理LVS的配置以及控制服务的启动、停止等功能，这使得LVS的应用更加简单方便了。LVS和Keepalived的组合应用不是本章的内容范围。\n实现对LVS集群节点健康检查功能（healthcheck） 前文已讲过，Keepalived可以通过在自身的keepalived.conf文件里配置LVS的节点IP和相关参数实现对LVS的直接管理；除此之外，当LVS集群中的某一个甚至是几个节点服务器同时发生故障无法提供服务时，Keepalived服务会自动将失效的节点服务器从LVS的正常转发队列中清除出去，并将请求调度到别的正常节点服务器上，从而保证最终用户的访问不受影响；当故障的节点服务器被修复以后，Keepalived服务又会自动地把它们加入到正常转发队列中，对客户提供服务。\n作为系统网络服务的高可用功能（failover） Keepalived可以实现任意两台主机之间，例如Master和Backup主机之间的故障转移和自动切换，这个主机可以是普通的不能停机的业务服务器，也可以是LVS负载均衡、Nginx反向代理这样的服务器。\nKeepalived高可用功能实现的简单原理为，两台主机同时安装好Keepalived软件并启动服务，开始正常工作时，由角色为Master的主机获得所有资源并对用户提供服务，角色为Backup的主机作为Master主机的热备；当角色为Master的主机失效或出现故障时，角色为Backup的主机将自动接管Master主机的所有工作，包括接管VIP资源及相应资源服务；而当角色为Master的主机故障修复后，又会自动接管回它原来处理的工作，角色为Backup的主机则同时释放Master主机失效时它接管的工作，此时，两台主机将恢复到最初启动时各自的原始角色及工作状态。\n说明：Keepalived的高可用功能是本章的重点，后面除了讲解Keepalived高可用的功能外，还会讲解Keepalived配合Nginx反向代理负载均衡的高可用的实战案例。 Keepalived高可用故障切换转移原理 Keepalived高可用服务对之间的故障切换转移，是通过VRRP（Virtual Router Redundancy Protocol，虚拟路由器冗余协议）来实现的。\n在Keepalived服务正常工作时，主Master节点会不断地向备节点发送（多播的方式）心跳消息，用以告诉备Backup节点自己还活着，当主Master节点发生故障时，就无法发送心跳消息，备节点也就因此无法继续检测到来自主Master节点的心跳了，于是调用自身的接管程序，接管主Master节点的IP资源及服务。而当主Master节点恢复时，备Backup节点又会释放主节点故障时自身接管的IP资源及服务，恢复到原来的备用角色。\n什么是VRRP VRRP，全称Virtual Router Redundancy Protocol，中文名为虚拟路由冗余协议，VRRP的出现就是为了解决静态路由的单点故障问题，VRRP是通过一种竞选机制来将路由的任务交给某台VRRP路由器的。\nVRRP早期是用来解决交换机、路由器等设备单点故障的，下面是交换、路由的Master和Backup切换原理描述，同样适用于Keepalived的工作原理。\n在一组VRRP路由器集群中，有多台物理VRRP路由器，但是这多台物理的机器并不是同时工作的，而是由一台称为Master的机器负责路由工作，其他的机器都是Backup。Master角色并非一成不变的，VRRP会让每个VRRP路由参与竞选，最终获胜的就是Master。获胜的Master有一些特权，比如拥有虚拟路由器的IP地址等，拥有系统资源的Master负责转发发送给网关地址的包和响应ARP请求。\nVRRP通过竞选机制来实现虚拟路由器的功能，所有的协议报文都是通过IP多播（Multicast）包（默认的多播地址224.0.0.18）形式发送的。虚拟路由器由VRID（范围0-255）和一组IP地址组成，对外表现为一个周知的MAC地址：00-00-5E-00-01-{VRID}。所以，在一个虚拟路由器中，不管谁是Master，对外都是相同的MAC和IP（称之为VIP）。客户端主机并不需要因Master的改变而修改自己的路由配置。对它们来说，这种切换是透明的。\n在一组虚拟路由器中，只有作为Master的VRRP路由器会一直发送VRRP广播包（VRRP Advertisement messages），此时Backup不会抢占Master。当Master不可用时，Backup就收不到来自Master的广播包了，此时多台Backup中优先级最高的路由器会抢占为Master。这种抢占是非常快速的（可能只有1秒甚至更少），以保证服务的连续性。出于安全性考虑，VRRP数据包使用了加密协议进行了加密。\n如果你在面试时，要你解答Keepalived的工作原理，建议用自己的话回答如下内容，以下为对面试官的表述：\nKeepalived高可用对之间是通过VRRP通信的：\nVRRP，全称Virtual Router Redundancy Protocol，中文名为虚拟路由冗余协议，VRRP的出现是为了解决静态路由的单点故障。 VRRP是通过一种竞选协议机制来将路由任务交给某台VRRP路由器的。 VRRP用IP多播的方式（默认多播地址（224.0.0.18））实现高可用对之间通信。 工作时主节点发包，备节点接包，当备节点接收不到主节点发的数据包的时候，就启动接管程序接管主节点的资源。备节点可以有多个，通过优先级竞选，但一般Keepalived系统运维工作中都是一对。 VRRP使用了加密协议加密数据，但Keepalived官方目前还是推荐用明文的方式配置认证类型和密码。 Keepalived服务的工作原理 介绍完了VRRP，接下来我再介绍一下Keepalived服务的工作原理：\nKeepalived高可用对之间是通过VRRP进行通信的，VRRP是通过竞选机制来确定主备的，主的优先级高于备，因此，工作时主会优先获得所有的资源，备节点处于等待状态，当主挂了的时候，备节点就会接管主节点的资源，然后顶替主节点对外提供服务。 在Keepalived服务对之间，只有作为主的服务器会一直发送VRRP广播包，告诉备它还活着，此时备不会抢占主，当主不可用时，即备监听不到主发送的广播包时，就会启动相关服务接管资源，保证业务的连续性。接管速度最快可以小于1秒。\nKeepalived高可用服务搭建 安装Keepalived 通过官方地址获取Keepalived源码软件包编译安装\nsh 1 2 3 4 ./configure \\ --prefix=/app/keepalived-\\ --mandir=/usr/local/share/man make \u0026amp;\u0026amp; make install 复制命令到/usr/sbin下\nsh 1 ln -s /app/keepalived-1.3.5/sbin/keepalived /usr/sbin/ keepalived默认会读取/etc/keepalived/keepalived.conf配置文件\nsh 1 2 mkdir /etc/keepalived \u0026amp;\u0026amp; \\ cp /app/keepalived-1.3.5/etc/keepalived/keepalived.conf /etc/keepalived/ 复制sysconfig文件到/etc/sysconfig下\nsh 1 cp /app/keepalived-1.3.5/etc/sysconfig/keepalived /etc/sysconfig/ 复制启动脚本到/etc/init.d下\nsh 1 2 cp ./keepalived/etc/init.d/keepalived /etc/init.d/ chmod 700 /etc/init.d/keepalived 编译错误 sh 1 2 configure: error: libnfnetlink headers missing yum install -y libnfnetlink-devel 启动错误 在centos7中，执行上面的步骤安装完毕后，/etc/init.d/start启动keepalived服务会报如下错误.这是因为centos7 编译安装keepalived会自动生成/usr/lib/systemd/system/keepalived.service单元文件。在单元文件中的启动命令调用的脚本没有执行权限，并且我们复制的启动脚本是复制到/etc/init.d目录下导致的\nsh 1 2 3 4 5 6 7 8 9 10 11 12 $ systemctl status keepalived ● keepalived.service - LVS and VRRP High Availability Monitor Loaded: loaded (/usr/lib/systemd/system/keepalived.service; disabled; vendor preset: disabled) Active: failed (Result: exit-code) since 五 2017-04-07 21:22:25 CST; 5s ago Process: 24459 ExecStart=/app/keepalived-1.3.5/sbin/keepalived $KEEPALIVED_OPTIONS (code=exited, status=203/EXEC) 4月 07 21:22:25 mb7 systemd[1]: Starting LVS and VRRP High Availability Monitor... 4月 07 21:22:25 mb7 systemd[1]: keepalived.service: control process exited, code=exited status=203 4月 07 21:22:25 mb7 systemd[1]: Failed to start LVS and VRRP High Availability Monitor. 4月 07 21:22:25 mb7 systemd[1]: Unit keepalived.service entered failed state. 4月 07 21:22:25 mb7 systemd[1]: keepalived.service failed. Warning: keepalived.service changed on disk. Run \u0026#39;systemctl daemon-reload\u0026#39; to reload units. 解决方法：不使用/etc/init.d/keepalived脚本启动了，修改单元文件 删掉单元文件\nkeepalived配置文件说明 这里的具备高可用功能的keepalived.conf配置文件包含了两个重要区块.\n全局定义(Global Definitions)部分 这部分主要用来设置Keepalived的故障通知机制和Router ID标识。示例代码如下：\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ! Configuration File for keepalived global_defs { notification_email { #←定义服务故障报警的Email地址。作用是当服务发生切换或RS节点等有故障时，发报警邮件。 #←这几行是可选配置，notification_email指定在keepalived发生事件时，需要发送的Email地址，可以有多个，每行一个. example@mail.com } # 发送邮件的发送人，即发件人地址，可选. notification_email_from Alexandre.Cassen@firewall.loc # 发送邮件的smtp服务器，如果本机开启了sendmail或postfix，就可以使用上面默认配置实现邮件发送，可选. smtp_server 192.168.200.1 # 连接smtp的超时时间，可选. smtp_connect_timeout 30 # Keepalived服务器的路由标识（router_id）。在一个局域网内，router_id应该是唯一的. router_id LVS_01 } 注意：第4~11行所有和邮件报警相关的参数均可以不配，在实际工作中会将监控的任务交给更加擅长监控报警的Nagios或Zabbix软件。\nVRRP实例定义区块(VRRP instance(s)部分 sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 # 定义一个vrrp_instance实例,名字是VI_1,每个vrrp_instance实例可以认为是Keepalived服务的一个实例或者作为一个业务服务， # 在Keepalived服务配置中,这样的vrrp_instance实例可以有多个.注意,存在于主节点中的vrrp_instance实例在备节点中也要存在， # 这样才能实现故障切换接管. vrrp_instance VI_1 { # state MASTER表示当前实例VI_1的角色状态,当前角色为MASTER,这个状态只能有MASTER和BACKUP两种状态,并且需要大写这些字符。 # 其中MASTER为正式工作的状态,BACKUP为备用的状态.当MASTER所在的服务器故障或失效时， # BACKUP所在的服务器会接管故障的MASTER继续提供服务. state MASTER # interface为网络通信接口.为对外提供服务的网络接口,如eth0、eth1。当前主流的服务器都有2~4个网络接口。 interface eth0 # 虚拟路由ID标识,这个标识最好是一个数字,并且要在一个keepalived.conf配置中是唯一的。 # 但是MASTER和BACKUP配置中相同实例的virtual_router_id又必须是一致的,否则将出现脑裂问题. virtual_router_id 51 # 优先级,其后面的数值也是一个数字,数字越大,表示实例优先级越高.在同一个vrrp_instance实例里， # MASTER的优先级配置要高于BACKUP的.若MASTER的priority值为150,那么BACKUP的priority必须小于150， # 一般建议间隔50以上为佳,例如：设置BACKUP的priority为100或更小的数值。 priority 150 # 同步通知间隔.MASTER与BACKUP之间通信检查的时间间隔,单位为秒,默认为1。 advert_int 1 # 权限认证配置.包含认证类型（auth_type）和认证密码（auth_pass）； # 认证类型有PASS(Simple Passwd(suggested))、AH(IPSEC(not recommended))两种。 # 官方推荐使用的类型为PASS.验证密码为明文方式，最好长度不要超过8个字符，建议用4位的数字， # 同一vrrp实例的MASTER与BACKUP使用相同的密码才能正常通信。 authentication { auth_type PASS auth_pass 1111 } # 虚拟IP地址.可以配置多个IP地址,每个地址占一行,配置时最好明确指定子网掩码以及虚拟IP绑定的网络接口。 # 否则,子网掩码默认是32位,绑定的接口和前面的interface参数配置的一致。 # 注意,这里的虚拟IP就是在工作中需要和域名绑定的IP,即和配置的高可用服务监听的IP要保持一致. virtual_ipaddress { 192.168.2.120 } } Keepalived高可用服务单实例 当没有配置高可用服务时，如果服务器宕机了怎么解决呢？无非就是找一个新服务器，配好域名解析的那个原IP，然后搭好相应的网络服务罢了，只不过手工去实现这个过程会比较漫长，相比而言，自动化切换效率更高，效果更好，而且还可以有更多的功能，例如：发送ARP广播，触发执行相关脚本动作等。\n实际上也可以将高可用对的两台机器应用服务同时开启，但是只让有VIP一端的服务器提供服务，若主的服务器宕机，VIP会自动漂移到备用服务器上，此时用户的请求直接发送到备用服务器上，而无需临时启动对应服务（事先开启应用服务）。\n实战配置Keepalived主服务器lb01 master 首先，配置lb01 MASTER的keepalived.conf配置文件，操作步骤如下：\n删掉已有的所有默认配置：\nsh 1 vim /etc/keepalived/keepalived.conf sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ! Configuration File for keepalived global_defs { notification_email { example@mail.com } notification_email_from Alexandre.Cassen@firewall.loc smtp_server 192.168.200.1 smtp_connect_timeout 30 router_id LVS_01 #\u0026lt;==id为lb01，不同的keepalived.conf此ID要唯一 vrrp_skip_check_adv_addr vrrp_strict vrrp_garp_interval 0 vrrp_gna_interval 0 } vrrp_instance VI_1 { #←实例名字为VI_1,相同实例的备节点名字要和这个相同 state MASTER\t#←状态为MASTER,备节点状态需要为BACKUP interface eth0 #←通信接口为eth0，此参数备节点设置和主节点相同 virtual_router_id 51 #←实例ID为51，keepalived.conf里唯一 priority 150\t#←优先级为150，备节点的优先级必须比此数字低 advert_int 1\t#←通信检查间隔时间1秒 authentication {\t#←PASS认证类型，此参数备节点设置和主节点相同 auth_type PASS auth_pass 1111 } #←虚拟IP，即VIP为192.168.2.120,子网掩码为24位，绑定接口为eth0，别名为eth0:1，此参数备节点设置和主节点相同 virtual_ipaddress { 192.168.2.120 } } 配置完毕后，启动Keepalived服务，然后检查配置结果，查看是否有虚拟IP 192.168.2.120。\nsh 1 2 $ ip addr|grep 192.168.2.120 inet 192.168.2.120/24 scope global secondary eth0 配置Keepalived备服务器lb02 backup sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ! Configuration File for keepalived global_defs { notification_email { example@mail.com } notification_email_from Alexandre.Cassen@firewall.loc smtp_server 192.168.200.1 smtp_connect_timeout 30 router_id LVS_02 vrrp_skip_check_adv_addr vrrp_strict vrrp_garp_interval 0 vrrp_gna_interval 0 } vrrp_instance VI_1 { state BACKUP interface eth0 virtual_router_id 51 priority 100\tadvert_int 1\tauthentication {\tauth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.2.120 } } 配置完毕后重启服务，检查配置结果，查看是否有虚拟IP 192.168.2.120。\nsh 1 2 $ ip addr|grep 192.168.2.120 #←没有返回任何结果就对了,因为lb02为BACKUP,当主节点活着的时候,它不会接管VIP 192.168.2.120 出现无任何结果的现象，表示lb02的Keepalived服务单实例配置成功。如果有192.168.2.120的IP，则表示Keepalived工作不正常，说明高可用裂脑了，裂脑是两台服务器争抢同一资源导致的，同一个IP地址同一时刻应该只能出现一台服务器。\n出现上述两台服务器争抢同一IP资源问题，先考虑排查两个地方：\n主备两台服务器之间是否通信正常，如果不正常是否有iptables防火墙阻挡？ 主备两台服务器对应的keepalived.conf配置文件是否有错误？例如，是否同一实例的virtual_router_id配置不一致。 高可用主备服务器切换 停掉主服务器上的Keealived服务或关闭主服务器\nsh 1 2 3 4 5 6 $ ip addr|grep 192.168.2.120 inet 192.168.2.120/24 scope global secondary eth0 $ /etc/init.d/keepalived stop Stopping keepalived (via systemctl): [ 确定 ] $ ip addr|grep 192.168.2.120 #←查看VIP消失了 $ 此时查看BACKUP备服务器，看是否会有VIP 出现\nsh 1 2 $ ip addr|grep 192.168.2.120 inet 192.168.2.120/24 scope global secondary eth0 可以看到备节点lb02已经接管绑定了VIP，这期间备节点还会发送ARP广播，让所有的客户端更新本地的ARP表，以便客户端访问新接管VIP服务的节点。\n此时如果再启动主服务器的Keealived服务，主服务器就会接管回VIP 10.0.0.12，启动后可以观察下主备的IP漂移情况，备服务器是否释放了IP？主服务器是否又接管了IP？\nsh 1 2 3 4 5 $ /etc/init.d/keepalived start Starting keepalived (via systemctl): [ 确定 ] $ ip addr|grep 192.168.2.120 inet 192.168.2.120/24 scope global secondary eth0 $ ip addr|grep 192.168.2.120\t#←备节点上的VIP则被释放了 检查nginx + keepalived工作 当主节点工作是，web页面如下：\n此时模拟主节点宕机后查看nginx是否正常工作\n多实例 keepalive01.conf\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ! Configuration File for keepalived global_defs { notification_email { example@mail.com } notification_email_from Alexandre.Cassen@firewall.loc smtp_server 192.168.200.1 smtp_connect_timeout 30 router_id LVS_02 } vrrp_instance VI_1 { state BACKUP interface eth0 virtual_router_id 51 priority 140 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 10.0.0.10/24 } } vrrp_instance VI_2 { state BACKUP interface eth0 virtual_router_id 52 priority 141 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 10.0.0.11/24 } } keepalived04.conf\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ! Configuration File for keepalived global_defs { notification_email { example@mail.com } notification_email_from Alexandre.Cassen@firewall.loc smtp_server 192.168.200.1 smtp_connect_timeout 30 router_id LVS_01 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 150 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 10.0.0.10/24 } } vrrp_instance VI_2 { state MASTER interface eth0 virtual_router_id 52 priority 151 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 10.0.0.11/24 } } sh 1 2 3 4 5 6 7 8 9 10 11 $ ip addr|grep 10.0.0 inet 10.0.0.10/24 scope global eth0 $ ip addr|grep 10.0.0 inet 10.0.0.11/24 scope global eth0 $ /etc/init.d/keepalived stop Stopping keepalived (via systemctl): [ 确定 ] $ ip addr|grep 10.0.0 $ $ ip addr|grep 10.0.0 inet 10.0.0.10/24 scope global eth0 inet 10.0.0.11/24 scope global secondary eth0 Keepalived高可用服务器的 “裂脑” 问题 什么是裂脑 由于某些原因，导致两台高可用服务器对在指定时间内，无法检测到对方的心跳消息，各自取得资源及服务的所有权，而此时的两台高可用服务器对都还活着并在正常运行，这样就会导致同一个IP或服务在两端同时存在而发生冲突，最严重的是两台主机占用同一个VIP地址，当用户写入数据时可能会分别写入到两端，这可能会导致服务器两端的数据不一致或造成数据丢失，这种情况就被称为裂脑。\n导致裂脑发生的原因 一般来说，裂脑的发生，有以下几种原因：\n高可用服务器对之间心跳线链路发生故障，导致无法正常通信。 心跳线坏了（包括断了，老化）。 网卡及相关驱动坏了，IP配置及冲突问题（网卡直连）。 心跳线间连接的设备故障（网卡及交换机）。 仲裁的机器出问题（采用仲裁的方案）。 高可用服务器上开启了iptables防火墙阻挡了心跳消息传输。 高可用服务器上心跳网卡地址等信息配置不正确，导致发送心跳失败。 其他服务配置不当等原因，如心跳方式不同，心跳广播冲突、软件Bug等。 提示：Keepalived配置里同一VRRP实例如果virtual_router_id两端参数配置不一致，也会导致裂脑问题发生。\n解决裂脑的常见方案 在实际生产环境中，我们可以从以下几个方面来防止裂脑问题的发生：\n同时使用串行电缆和以太网电缆连接，同时用两条心跳线路，这样一条线路坏了，另一个还是好的，依然能传送心跳消息。\n当检测到裂脑时强行关闭一个心跳节点（这个功能需特殊设备支持，如Stonith、fence）。相当于备节点接收不到心跳消息，通过单独的线路发送关机命令关闭主节点的电源。\n做好对裂脑的监控报警（如邮件及手机短信等或值班），在问题发生时人为第一时间介入仲裁，降低损失。例如，百度的监控报警短信就有上行和下行的区别。报警信息发送到管理员手机上，管理员可以通过手机回复对应数字或简单的字符串操作返回给服务器，让服务器根据指令自动处理相应故障，这样解决故障的时间更短。\n当然，在实施高可用方案时，要根据业务实际需求确定是否能容忍这样的损失。对于一般的网站常规业务，这个损失是可容忍的。\n解决Keepalived裂脑的常见方案 作为互联网应用服务器的高可用，特别是前端Web负载均衡器的高可用，裂脑的问题对普通业务的影响是可以忍受的，如果是数据库或者存储的业务，一般出现裂脑问题就非常严重了。因此，可以通过增加冗余心跳线路来避免裂脑问题的发生，同时加强对系统的监控，以便裂脑发生时人为快速介入解决问题。\n如果开启防火墙，一定要让心跳消息通过，一般通过允许IP段的形式解决。 可以拉一条以太网网线或者串口线作为主被节点心跳线路的冗余。 开发监测程序通过监控软件（例如Nagios）监测裂脑。 下面是生产场景检测裂脑故障的一些思路： 简单判断的思想：只要备节点出现VIP就报警，这个报警有两种情况，一是主机宕机了备机接管了；二是主机没宕，裂脑了。不管属于哪个情况，都进行报警，然后由人工查看判断及解决。 比较严谨的判断：备节点出现对应VIP，并且主节点及对应服务（如果能远程连接主节点看是否有VIP就更好了）还活着，就说明发生裂脑了。 检测裂脑脚本 sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #!/bin/sh vip=192.168.2.120 ip=192.168.2.24 function check() { ping -c 2 -W 3 $ip \u0026amp;\u0026gt;/dev/null if [ $? -eq 0 -a `ip addr|grep $vip|wc -l` -eq 1 ] then echo \u0026#39;fail\u0026#39; else echo ok fi } while true do check done 解决服务监听的网卡上不存在IP地址问题 如果配置使用listen 10.0.0.12：80;的方式指定IP监听服务，而本地的网卡上没有10.0.0.12这个IP，Nginx就会报错：\nsh 1 2 root@lb01 ~]# /app/nginx/sbin/nginx nginx [emerg] bind(to 10.0.0.12 80 failed 99 Cannot assign requested address) 如果要实施双主即主备同时跑不同的业务，配置文件里指定了IP监听，备节点则会因为网卡实际不存在VIP也报错。\n出现上面问题的原因就是在物理网卡上没有与配置文件里监听的IP相对应的IP，解决办法是在/etc/sysctl.conf中加入如下内核参数配置：\nsh 1 net.ipv4.ip_nonlocal_bind = 1 也可通过如下命令快速追加：\nsh 1 echo \u0026#39;net.ipv4.ip_nonlocal_bind = 1\u0026#39; \u0026gt;\u0026gt; /etc/sysctl.conf 注：net.ipv4.ip_nonlocal_bind = 1 #此项表示启动nginx而忽略配置中监听的VIP是否存在，它同样适合Haproxy.\n最后执行sysctl-p使上述修改生效。\n解决高可用服务只针对物理服务器的问题 默认情况下Keepalived软件仅仅在对方机器宕机或Keepalived停掉的时候才会接管业务。但在实际工作中，有业务服务停止而Keepalived服务还在工作的情况，这就会导致用户访问的VIP无法找到对应的服务，那么，如何解决业务服务宕机可以将IP漂移到备节点使之接管提供服务？\n第一个方法：可以写守护进程脚本来处理。当Nginx业务有问题时，就停掉本地的Keepalived服务，实现IP漂移到对端继续提供服务。实际工作中部署及开发的示例脚本如下：\nsh 1 2 3 4 5 6 7 8 #!/bin/sh while true do if [ `ps -ef|grep nginx|grep -v grep|wc -l` -lt 2 ]; then /etc/init.d/keepalived stop \u0026amp;\u0026gt;/dev/null fi sleep 15 done 后台运行脚本后停止nginx服务，查看进程状态，发现脚本会自动执行命令停止keepalived服务.\nsh 1 2 3 4 5 6 $ ps -ef|grep keep root 3735 1 0 00:48 ? 00:00:00 keepalived -D root 3738 3735 0 00:48 ? 00:00:00 keepalived -D root 3782 3686 0 00:48 pts/2 00:00:00 /bin/sh /etc/init.d/keepalived stop root 3791 3782 0 00:48 pts/2 00:00:00 /bin/systemctl stop keepalived.service root 3797 1 0 00:48 ? 00:00:00 /bin/sh /etc/rc.d/init.d/keepalived stop 此时查看备节点状态，可以看出，备节点已经接管服务.\nsh 1 2 3 $ ip addr|grep 192.168.2.120 $ ip addr|grep 192.168.2.120 inet 192.168.2.120/24 scope global secondary eth0 配置指定文件接收Keepalived服务日志 默认情况下Keepalived服务日志会输出到系统日志/var/log/messages，和其他日志信息混合在一起，很不方便，可以将其调整成由独立的文件记录Keepalived服务日志。操作步骤如下：\n1. 编辑配置文件/etc/sysconfig/keepalived\nsh 1 2 KEEPALIVED_OPTIONS=\u0026#34;-D\u0026#34; KEEPALIVED_OPTIONS=\u0026#34;-D-d-S 0\u0026#34; 说明：可以查看/etc/sysconfig/keepalived里注释获得上述参数的说明\nsh 1 2 3 4 5 6 7 --vrrp -P #←只运行VRRP子系统. --check -C #←只运行健康检查子系统. --dont-release-vrrp -V #←不要在守护程序停止时删除VRRP VIP和VROUTE. --dont-release-ipvs -I #←不要在守护程序停止时删除IPVS拓扑. --dump-conf -d #←导出备份配置数据. --log-detail -D #←详细日志. --log-facility -S #←设置本地的syslog设备，编号0-7(default=LOG_DAEMON)0表示指定为local0设备 2. 修改rsyslog的配置文件/etc/rsyslog.conf\nsh 1 74 local0.*\t/var/log/keepalived.log 上述配置表示来自local0设备的所有日志信息都记录到/var/log/keepalived.log文件。\n然后在约第42行如下信息的第一列结尾加入“；local0.none”：\nsh 1 2 3 52 # Log anything (except mail) of level info or higher. 53 # Don\u0026#39;t log private authentication messages! 54 *.info;mail.none;authpriv.none;cron.none;local0.none /var/log/messages 上述配置表示来自local0设备的所有日志信息不再记录于/var/log/messages里。\n3. 配置完成后，重启rsyslog服务\nsh 1 systemctl restart rsyslog 测试Keepalived日志记录结果。在重启Keepalived服务后，就会把日志信息输出到rsyslog定义的/var/log/keepalived.log文件\nsh 1 2 3 4 $ head -3 /var/log/keepalived.log Apr 9 18:58:54 lb_02 Keepalived[32024]: Starting Keepalived v(03/19,2017), git commit v1.3.5-6-g6fa32f2 Apr 9 18:58:54 lb_02 Keepalived[32024]: Unable to resolve default script username \u0026#39;keepalived_script\u0026#39; - ignoring Apr 9 18:58:54 lb_02 Keepalived[32024]: Opening file \u0026#39;/etc/keepalived/keepalived.conf\u0026#39;. ","permalink":"https://www.161616.top/keepalived/","summary":"Keepalived介绍 Keepalived软件起初是专为LVS负载均衡软件设计的，用来管理并监控LVS集群系统中各个服务节点的状态，后来又加入了可以实现高可用的VRRP功能。因此，Keepalived除了能够管理LVS软件外，还可以作为其他服务（例如：Nginx、Haproxy、MySQL等）的高可用解决方案软件。\nKeepalived软件主要是通过VRRP协议实现高可用功能的。VRRP是Virtual Router Redundancy Protocol（虚拟路由器冗余协议）的缩写，VRRP出现的目的就是为了解决静态路由单点故障问题的，它能够保证当个别节点宕机时，整个网络可以不间断地运行。所以，Keepalived一方面具有配置管理LVS的功能，同时还具有对LVS下面节点进行健康检查的功能，另一方面也可实现系统网络服务的高可用功能。\nKeepalived服务的三个重要功能 管理LVS负载均衡软件 早期的LVS软件，需要通过命令行或脚本实现管理，并且没有针对LVS节点的健康检查功能。为了解决LVS的这些使用不便的问题，Keepalived就诞生了，可以说，Keepalived软件起初是专为解决LVS的问题而诞生的。因此，Keepalived和LVS的感情很深，它们的关系如同夫妻一样，可以紧密地结合，愉快地工作。Keepalived可以通过读取自身的配置文件，实现通过更底层的接口直接管理LVS的配置以及控制服务的启动、停止等功能，这使得LVS的应用更加简单方便了。LVS和Keepalived的组合应用不是本章的内容范围。\n实现对LVS集群节点健康检查功能（healthcheck） 前文已讲过，Keepalived可以通过在自身的keepalived.conf文件里配置LVS的节点IP和相关参数实现对LVS的直接管理；除此之外，当LVS集群中的某一个甚至是几个节点服务器同时发生故障无法提供服务时，Keepalived服务会自动将失效的节点服务器从LVS的正常转发队列中清除出去，并将请求调度到别的正常节点服务器上，从而保证最终用户的访问不受影响；当故障的节点服务器被修复以后，Keepalived服务又会自动地把它们加入到正常转发队列中，对客户提供服务。\n作为系统网络服务的高可用功能（failover） Keepalived可以实现任意两台主机之间，例如Master和Backup主机之间的故障转移和自动切换，这个主机可以是普通的不能停机的业务服务器，也可以是LVS负载均衡、Nginx反向代理这样的服务器。\nKeepalived高可用功能实现的简单原理为，两台主机同时安装好Keepalived软件并启动服务，开始正常工作时，由角色为Master的主机获得所有资源并对用户提供服务，角色为Backup的主机作为Master主机的热备；当角色为Master的主机失效或出现故障时，角色为Backup的主机将自动接管Master主机的所有工作，包括接管VIP资源及相应资源服务；而当角色为Master的主机故障修复后，又会自动接管回它原来处理的工作，角色为Backup的主机则同时释放Master主机失效时它接管的工作，此时，两台主机将恢复到最初启动时各自的原始角色及工作状态。\n说明：Keepalived的高可用功能是本章的重点，后面除了讲解Keepalived高可用的功能外，还会讲解Keepalived配合Nginx反向代理负载均衡的高可用的实战案例。 Keepalived高可用故障切换转移原理 Keepalived高可用服务对之间的故障切换转移，是通过VRRP（Virtual Router Redundancy Protocol，虚拟路由器冗余协议）来实现的。\n在Keepalived服务正常工作时，主Master节点会不断地向备节点发送（多播的方式）心跳消息，用以告诉备Backup节点自己还活着，当主Master节点发生故障时，就无法发送心跳消息，备节点也就因此无法继续检测到来自主Master节点的心跳了，于是调用自身的接管程序，接管主Master节点的IP资源及服务。而当主Master节点恢复时，备Backup节点又会释放主节点故障时自身接管的IP资源及服务，恢复到原来的备用角色。\n什么是VRRP VRRP，全称Virtual Router Redundancy Protocol，中文名为虚拟路由冗余协议，VRRP的出现就是为了解决静态路由的单点故障问题，VRRP是通过一种竞选机制来将路由的任务交给某台VRRP路由器的。\nVRRP早期是用来解决交换机、路由器等设备单点故障的，下面是交换、路由的Master和Backup切换原理描述，同样适用于Keepalived的工作原理。\n在一组VRRP路由器集群中，有多台物理VRRP路由器，但是这多台物理的机器并不是同时工作的，而是由一台称为Master的机器负责路由工作，其他的机器都是Backup。Master角色并非一成不变的，VRRP会让每个VRRP路由参与竞选，最终获胜的就是Master。获胜的Master有一些特权，比如拥有虚拟路由器的IP地址等，拥有系统资源的Master负责转发发送给网关地址的包和响应ARP请求。\nVRRP通过竞选机制来实现虚拟路由器的功能，所有的协议报文都是通过IP多播（Multicast）包（默认的多播地址224.0.0.18）形式发送的。虚拟路由器由VRID（范围0-255）和一组IP地址组成，对外表现为一个周知的MAC地址：00-00-5E-00-01-{VRID}。所以，在一个虚拟路由器中，不管谁是Master，对外都是相同的MAC和IP（称之为VIP）。客户端主机并不需要因Master的改变而修改自己的路由配置。对它们来说，这种切换是透明的。\n在一组虚拟路由器中，只有作为Master的VRRP路由器会一直发送VRRP广播包（VRRP Advertisement messages），此时Backup不会抢占Master。当Master不可用时，Backup就收不到来自Master的广播包了，此时多台Backup中优先级最高的路由器会抢占为Master。这种抢占是非常快速的（可能只有1秒甚至更少），以保证服务的连续性。出于安全性考虑，VRRP数据包使用了加密协议进行了加密。\n如果你在面试时，要你解答Keepalived的工作原理，建议用自己的话回答如下内容，以下为对面试官的表述：\nKeepalived高可用对之间是通过VRRP通信的：\nVRRP，全称Virtual Router Redundancy Protocol，中文名为虚拟路由冗余协议，VRRP的出现是为了解决静态路由的单点故障。 VRRP是通过一种竞选协议机制来将路由任务交给某台VRRP路由器的。 VRRP用IP多播的方式（默认多播地址（224.0.0.18））实现高可用对之间通信。 工作时主节点发包，备节点接包，当备节点接收不到主节点发的数据包的时候，就启动接管程序接管主节点的资源。备节点可以有多个，通过优先级竞选，但一般Keepalived系统运维工作中都是一对。 VRRP使用了加密协议加密数据，但Keepalived官方目前还是推荐用明文的方式配置认证类型和密码。 Keepalived服务的工作原理 介绍完了VRRP，接下来我再介绍一下Keepalived服务的工作原理：\nKeepalived高可用对之间是通过VRRP进行通信的，VRRP是通过竞选机制来确定主备的，主的优先级高于备，因此，工作时主会优先获得所有的资源，备节点处于等待状态，当主挂了的时候，备节点就会接管主节点的资源，然后顶替主节点对外提供服务。 在Keepalived服务对之间，只有作为主的服务器会一直发送VRRP广播包，告诉备它还活着，此时备不会抢占主，当主不可用时，即备监听不到主发送的广播包时，就会启动相关服务接管资源，保证业务的连续性。接管速度最快可以小于1秒。\nKeepalived高可用服务搭建 安装Keepalived 通过官方地址获取Keepalived源码软件包编译安装\nsh 1 2 3 4 ./configure \\ --prefix=/app/keepalived-\\ --mandir=/usr/local/share/man make \u0026amp;\u0026amp; make install 复制命令到/usr/sbin下\nsh 1 ln -s /app/keepalived-1.3.5/sbin/keepalived /usr/sbin/ keepalived默认会读取/etc/keepalived/keepalived.conf配置文件","title":"Keepalived 高可用集群应用实践"},{"content":"LVS概述 负载均衡(Load Balance)集群提供了一种廉价、有效、透明的方法，来扩展网络设备和服务器的负载、带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。\n搭建负载均衡服务的需求\n把单台计算机无法承受的大规模的并发访问或数据流量分担到多台节点设备上分别处理，减少用户等待响应的时间，提升用户体验. 单个重负载的运算分担到多台节点设备上做并发处理，每个节点设备处理结束后，将结果汇总，返回给用户，系统处理能力得到大幅度提高。 7*24小时服务保证，任意一个或多个有限后面节点设备宕机，要求不能影响业务。 在负载均衡集群中，所有计算机节点都应该提供相同的服务。集群负载均衡器所截获所有对该服务的入站请求。然后将这些请求尽可能的平均分配在所有集群节点上。\nLVS (Linux Virtual Server)介绍 LVS是Linux Virtual Server的简写，意即Linux虚拟服务器，是一个虚拟的服务器集群系统，可在UNIX、Linux平台下实现负载均衡集群功能。该项目在1998年5月由章文嵩博士组织成立，是中国国内最早出现的自由软件项目之一\nLVS项目介绍 http://www.linuxvirtualserver.org/zh/lvs1.html\nLVS集群的体系结构 http://www.linuxvirtualserver.org/zh/lvs2.html\nLVS集群中的IP负载均衡技术 http://www.linuxvirtualserver.org/zh/lvs3.html\nLVS集群的负载调度 http://www.linuxvirtualserver.org/zh/lvs4.html\nIPVS（LVS）发展史 早在2.2内核时，IPVS就已经以内核补丁的形式出现。\n从2.4.23版本开始，IPVS软件就是合并到Linux内核的常用版本的内核补丁的集合。\n从2.4.24以后IPVS已经成为Linux官方标准内核的一部分。\nIPVS软件工作层次图 从上图可以看出，LVS负载均衡调度技术是在Linux内核中实现的，因此，被称之为Linux虚拟服务器（Linux virtual Server）。我们使用该软件配置LVS时候，不能直接配置内核中的ipvs，而需要使用ipvs的管理工具ipsadm进行管理.\nLVS技术点小结：\n真正实现调度的工具是IPVS， 工作在Linux内核层面 LVS自导IPVS管理工具是ipvsadm keepalived实现管理IPVS及负载均衡器的高可用。 RedHat工具Piranha WEB管理实现调度的工具IPVS。 LVS体系结构与工作原理简单描述 LVS集群负载均衡器接受服务的所有入站客户端计算机请求，并根据调度算法决定那个集群几点应该处理回复请求。负载均衡器简称(LB)有时也被成为LVS Director简称Director\nLVS虚拟服务器的体系结构如下图所示，一组服务器通过告诉的局域网或者地理分布的广域网互相连接，在他们的前端有一个负载调度器（Load Balancer）。负载调度器能无缝地将网络请求调度到真实服务器上，从而使得服务器集群的结构对客户是透明的，客户访问集群系统提供的网络服务就像访问一台高性能、高可用的服务器一样。客户程序不收服务器集群的影响不需作任何修改。胸的伸缩性通过在服务集群中透明的加入和删除一各节点来达到，通过检测节点或服务进程故障和正确地重置系统达到高可用性。由于我们的负载调度技术是在Linux内核中实现的，我们称之为Linux虚拟服务器（Linux Virtual Server）。\nLVS基本工作过程图 **LVS基本工作过程图1：带颜色的小方块代表不同的客户端请求\nLVS基本工作过程图2：\n不同的客户端请求小方块经过负载均衡器，通过指定的分配策略被分发到后面的机器上\nLVS基本工作过程图3：\nLVS基本工作过程图4：\nLVS相关术语命名约定 名称 缩写 说明 虚拟IP地址(Virtual IP Address) VIP VIP为Direcort用于向客户端计算机提供IP地址.如www.baidu.com域名就要解析到VIP上提供服务 真实IP地址(Real Server IP Address) RIP 在集群下面节点上使用的IP地址，物理IP地址 Director的IP地址(Director IP Address) DIP Director用于连接内外网络的IP地址，物理网卡上的IP地址，是负载均衡器上的IP 客户端主机IP地址(Client IP Address) CIP 客户端用户计算机请求集群服务器的IP地址，该地址用作发送给集群的请求的源IP地址 LVS集群内部的节点称为真实服务器(Real Server)，也叫做集群节点。请求集群服务的计算机称为客户端计算机。\n与计算机通常在网上交换数据包的方式相同，客户端计算机、Director和真实服务器使用IP地址彼此进行通信。\nLVS集群的4种工作模式介绍与原理讲解 IP虚拟服务器软件IPVS\n在调度器的实现技术中，IP负载金恒技术是效率最高的。在已有的IP负载均衡技术中有通过网络地址转换(Network Address Translation)将一组服务器构成一个高性能的、高可用的虚拟服务器，我们称之为VS/NAT技术(Virtual Server via Network Address Translation)，大多数商业化的IP负载均衡调度器产品都是使用NAT方法，如Cisco的LocalDirector、F5、Netscaler的Big/IP和Alteon的ACEDirector。\n在分析VS/NAT的缺点和网络服务的非对称性的基础上，我们提出通过IP隧道实现虚拟服务器方法VS/TUN(Virtual Server via IP Tunneling ) 和通过直接路由实现虚拟服务器的方法VS/DR(Virtual Server via Direct Routing )，它们可以极大地提高系统的伸缩性。所以，IPVS软件实现了这三种IP负载均衡技术，淘宝开源的模式FULLNAT。\nNAT模式==\u0026gt;网络地址转换\u0026lt;==收费站模式 Virtual Server via Network Address Translation (VS/NAT)\n通过网络地址转换，调度器LB重写请求报文的目标地址，根据预设的调度算法，将请求分派给后端的真实服务器；真实服务器的响应报文处理之后，返回时必须要通过调度器，经过调度器时报文的源地址被重写，再返回给客户，完成整个负载调度过程。\n⚠ 提示：VS/NAT模式，很类似公路上的收费站，来去都要经过LB负载均衡器，通过修改目的地址，端口或源端口。\n原理描述\n客户端通过Virtual IP Address（虚拟服务的IP地址）访问网络服务时，请求的报文到达调度器LB时，调度器根据连接调度算法从一组真实服务器中选出一台服务器，将报文的目标地址VIP改写成选的服务器的地址RIP1，请求报文的目标端口改写成选定服务器的相应端口（RS）提供服务端口，最后将修改后的报文发送给选出服务器RS1。同时，调度器LB在连接的Hash表中记录这个连接，当这个连接的下一个报文到达时，从连接的Hash表中可以得到原选定服务器的地址和端口，进行同样的改写操作，并将报文传给原选定的服务器RS1。当来自真实服务器RS1的相应报文返回调度器时，调度器将返回报文的源地址和源端口改为VIP和相应端口，然后调度器再把报文发给请求用户。in DNAT out SNAT。\nNAT模式小结\nNAT技术将请求的报文（通过DNAT方式改写）和响应的报文（通过SNAT方式改写），通过调度器地址重写然后在转发给内部的服务器，报文返回时在改写成原来的用户请求的地址。\n只需要在调度器LB上配置WAN公网IP即可，调度器也要有私有LAN IP和内部RS节点通信。\n每台内部RS节点的网关地址，必须要配成调度器LB的私有LAN内物理网卡地址 (LD1P)，这样才能确保数据报文返回时仍然经过调度器LB。\n由于清求与响应的数据报文都经过调度器LB.因此，网站访问早大时调度器LB有较大瓶颈，一般要求最多10-20台节点。\nNAT模式支持对IP及端口的转换，即用户请求10.0.0.1:80,可以通过调度器转换到RS节点的10.0.0.2:8080 (DR和TUN模式不具备的）。-\n所有NAT内部RS节点只需配置私有LAN IP即可。\n由于数据包来回都需要经过调度器，因此，要开启内核转发net.ipv4.ip_forward=1,当然也包括iptables防火枪的forward功能（DR和TUX模式不需要）。\nDR模式-直连路由模式 Virtual Server via Direct Routing (VS/DR)\nVS/DR模式是通过改写请求报文的目标MAC地址，将请求发给真实服务器的，而真实服务器将相应后的处理结果直接返回给客户端用户。同VS/TUN技术一样，VS/DR技术可极大的提高集群系统的伸缩性。而且，这种DR模式没有IP隧道的开销，对集群中的真实服务器也没有必须支持IP隧道协议的要求，但是要求调度器LB与真实服务器RS都有一块物理网卡连在同一物理网段上，即必须在同一个局域网环境。\n在LVS-DR配置中，Director将所有入站请求转发给集群内部节点，但集群内部的节点直接将他们的回复发给客户端计算机（没有通过Director回来）如图所示：\n⚠ 特别提示：(VS/DR)模式是互联网使用的最多的一种模式。 VS/DR模式的工作流程如下图所示：它的连接调度和管理与VS/NAT和VS/TUN中的一样，它的报文转发方法和前两种又有不同，DR模式将报文直接路由给目标服务器，在VS/DR模式中，调度器根据各个真实服务器的负载情况，连接数多少等，动态地选择一台服务器，不修改目的IP地址和目的端口，也不封装IP报文，而是将请求的数据帧的MAC地址改为选出服务器的MAC地址，然后再将修改后的数据帧在与服务器组的局域网上发送。因为请求的数据帧的MAC地址是选出的真实服务器，所以真实服务器肯定可以收到这个改写了目标MAC地址的数据帧，从中可以获得该请求的IP报文。当真实服务器发现 报文的目标地址VIP是在本地的网络设备上，真实服务器处理这个报文，然后根据路由表 将响应报文直接返回给客户。\n在VS/DR中，根据缺省的TCP/IP协议栈处理，请求报文的目标地址为VIP，响应报文的源地址肯定也为VIP，所以响应报文不需要作任何修改，可以直接返回给客户，客户认为得到正常的服务，而不会知道是哪一台服务器处理的。\nVS/DR负载调度器跟VS/TUN—样只处于从客户到服务器的半连接中，按照半连接的TCP有限状态机进行状态迁移。\n原理图：IN更改目的MAC/OUT null\nDR模式小结：\n通过在调度器LB上修改数据包的目的MAC地址实现转发。注意，源IP地址仍然是 CIP，目的IP地址仍然是VIP。 请求的报文经过调度器，而RS响应处理后的报文无需经过调度器LB，因此，并发访问量大时使用效率很高（和NAT模式比）。 因DR模式是通过MAC地址的改写机制实现的转发，因此，所有RS节点和调度器LB 只能在一个局域网LAN中（小缺点）。 需要注意RS节点的VIP的绑定（lo:vip/32，lo1:vip/32)和ARP抑制问题。 强调下：RS节点的默认网关不需要是调度器LB的DIP而直接是IDC机房分配的上级路由器的IP (这是RS带有外网IP地址的情况），理论讲：只要RS可以出网即可，不是必须要配置外网IP。 由于DR模式的调度器仅进行了目的MAC地址的改写，因此，调度器LB无法改变请求的报文的目的端口（和NAT要区别）。 当前，调度器LB支持几乎所有的UNIX, LINUX系统，但目前不支持WINDOWS系统。真实服务器RS节点可以是WINDOWS系统。 总的来说DR模式效率很高，但是配置也较麻烦，因此，访问量不是特别大的公司可以用haproxy/nginx取代。这符合运维的原则：简单、易用、高效。日1000-2000W PV或并发请求小于1W以下都可以考虑用haproxy/nginx（LVS NAT）模式 直接对外的业务访问，例如web服务做RS节点，RS最好用公网IP地址。如果不直接对外的业务，例如：MySQL，存储系统RS节点，最好只用内部IP地址。 Virtual Server via IP Tunneling (VS/TUN) 采用NAT技术时，由于请求和响应的报文都必须经过调度器地址重写，当客户请求越来越多时，调度器的处理能力将成为瓶颈。为了解决这个问题，调度器把请求的报文通过IP隧道（相当于ipip或ipsec ）转发至真实服务器，而真实服务器将响应处理后直接返回给客户端用户，这样调度器就只处理请求的入站报文。由于一般网络服务应答数据比请求报文大很多，采用VS/TUN技术后，集群系统的最大吞吐量可以提高10倍。\n它的连接调度和管理与VS/NAT中的一样，只是它的报文转发方法不同。调度器根据各个服务器的负载情况，连接数多少，动态地选择一台服务器，将原请求的报文封装在另一个IP报文中，再将封装后的IP报文转发给选出的真实服务器；真实服务器收到报文后，先将收到的报文解封获得原来目标地址为VIP地址的报文，服务器发现VIP地址被配置在本地的IP隧道设备上(此处要人为配置)，所以就处理这个请求，然后根据路由表将响应报文直接返回给客户。\nTUN模式\n负载均衡器通过把请求的报文通过IP隧道(ipip隧道)的方式(请求的报文不经过原目的地址的改写(包括MAC)，而是直接封装成另外的IP报文)，转发至真实服务器，而真实服务器将响应处理后直接返回给客户端用户。 由于真实服务器将响应处理后的报文直接返回给客户端用户，因此。最好RS有一个外网IP地址，这样效率才会更高。理论上：只要能出网即可，无需外网IP地址。 由于调度器LB只处理入站请求的报文。因此，此集群系统的吞吐量可以提高10倍以上，但隧道模式也会带来一定的系统开销。TUN模式适合LAN/WAN. TUN模式的LAN环境转发不如DR模式效率高，而且还要考虑系统对IP隧道的支持问题。 所有的RS服务器都要绑定VIP，抑制ARP，配置复杂. LAN环境一般多采用DR模式，WAN环境可以用TUN模式，但是当前在，WAN环境下，请求转发更多的被haproxy/nginx/DNS调度等代理取代。因此，TUN模式在国内公司实际应用的已经很少。跨机房应用要么拉光纤成局域网，要么DNS调度，底层数据还得同步. 直接对外的访问业务，例如:web服务做RS节点，最好用公网IP地址。不直接对外的业务，例如:MySQL,存储系统RS节点，最好用内部IP地址。 FULLNAT模式 淘宝网最新开源的 背景\nLVS当前应用主要采用DR和NAT模式，但这2种模式要求RealServer和LVS在同 一个vlan中，导致部署成本过髙：TUNNEL模式虽然可以跨vlan，但RealServer上需要部署ipip隧道模块等，网络拓扑上需要连通外网，较复杂，不易运维。 为了解决上述问题，我们在LVS上添加了一种新的转发模式：FULLNAT，该模式和NAT模式的区别是：Packet IN时，除了做DNAT,还做SNAT(用户ip-\u0026gt;内网ip),从而实现LVS-RealServer之间可以跨vlan通讯，RealServer只需要连接到内网;\n目标\nFULLNAT将作为一种新工作镆式（同DR/NAT/TUNNEL),实现如下功能：\nPacket IN时，目标IP更换为realserver ip，源IP更换为内网local IP； Packet OUT时，目标IP更换为client IP 注：Local IP为一组内网ip地址;性能要求，和NAT比，正常转发性能下降\u0026lt;10%。 ARP协议 什么是ARP协议 ARP协议，全称“Address Resolution Protocol”，中文名称是地址解析协议，使用ARP协议可实现通过IP地址获得对应主机的的物理地址（MAC）。\n在TCP/IP的网络环境下，每个互联网的主机都会被分配一个32位的IP地址，这种互联网地址是在网际范围标识主机的一种逻辑地址。为了让报文在物理网路上传输，还补习要知道对方目的主机的物理地址才行。这样就存在把IP地址变换成物理地址的地址转换问题。\n在以太网环境，为了正确地向目的主机传送报文，必须把目的主机的32为IP地址转换成为目的主机48位以太网地址(MAC),这个就需要在互联层有一个服务或功能将IP地址转换为相应的物理地址(MAC)，这个服务就是ARP协议。\n所谓的地址解析\u0026quot;地址解析\u0026quot;，就是主机在发送帧之前将目标IP地址转换成目标MAC地址的过程。ARP协议的基本功能就是通过目标设备的IP地址，查询目标设备的MAC地址，以保证主机间互相通信的顺利进行.\nARP协议和DNS有相像之处。不同点是：DNS实在域名和IP之间解析，另外ARP协议不需要配置服务，而DNS要配置服务才行。\nARP缓存表 在每台安装有TCP/IP协议的电脑里都会有一个ARP缓存表（windows命令提示符里输入arp -a即可）， 表里的IP地址与MAC地址是一一对应的。\ntext 1 2 3 4 5 6 7 8 9 10 C:\\Users\\CM\u0026gt;arp -a 接口: 192.168.1.103 --- 0x3 Internet 地址 物理地址 类型 192.168.1.1 3c-46-d8-5d-53-87 动态 192.168.1.255 ff-ff-ff-ff-ff-ff 静态 224.0.0.22 01-00-5e-00-00-16 静态 224.0.0.251 01-00-5e-00-00-fb 静态 224.0.0.252 01-00-5e-00-00-fc 静态 239.11.20.1 01-00-5e-0b-14-01 静态 239.255.255.250 01-00-5e-7f-ff-fa 静态 arp常用命令\narp -a 查看所有记录\narp -d 清除\narp -s ip mac 绑定IP和MAC\nARP缓存是把双刃剑 主机有了arp缓存表，可以加快arp的解析速度，减少局域网内广播风暴。 正是有了arp缓存表，给恶意黑客带来了攻击服务器主机的风险，这个就是arp欺骗攻击 切换路由器，负载均衡器等设备时，可能会导致短时网络中断.\n为什么要使用ARP协议 OSI模型把网络工作分为7层，彼此不直接打交道，只通过接口(layer interface)。IP地址工作在第三层，MAC地址工作在第二层。当协议在发送数据包时，需要先封装第三层IP地址，第二层MAC地址的报头，但协议只知道目的的节点的IP地址，不知道目的节点的MAC地址，又不能跨第二、三层，所以得用ARP协议服务，来帮助获取到目的节点的MAC地址.\nosi7层模型协议 包封装解封装详解 http://www.tudou.com/programs/view/sP9JY_KranA\ntcp三次握手四次断开原理过程详解 http://www.tudou.com/programs/view/XjHCDedZQa8\nARP在生产环境产生的问题及解决办法 ARP病毒，ARP欺骗 高可用服务器对之间切换时要考虑ARP缓存问题 路由器等设备无缝迁移时要考虑ARP缓存的问题，例如：更换办公室的路由器.\nARP欺骗原理\nARP攻击就是通过伪造IP地址和MAC地址对实现ARP欺骗的，如果一台主机中了ARP病毒，那么它就能够在网络中产生大量的ARP通信量（它会以很快的频率进行广播），以至于使网络阻塞，攻击者只要持续不断的发出伪造ARP响应包就能更改局域网中目标主机ARP缓存中的IP-MAC条目，造成网络中断或中间人攻击。\nARP攻击主要是存在于局域网网络中，局域网中若有一个人感染ARP木马，则感染该ARP木马的系统将会试图通过“ARP欺骗”手段截获所在网络内其他计算机的通信故障。\n服务器切换ARP问题\n当网络中一台提供服务的机器宕机后，当在其他运行正常的机器添加宕机的机器的IP时，会因为客户端的ARP table cache的地址解析还是宕机的机器的MAC地址。从而导致，即使在其他运行正常的机器添加宕机的机器的IP，也会发生客户依然无法访问的情况。\n解决方法是：当宕机时，IP地址迁移到其他机器上时，需要通过arping命令来通知所有网络内机器清除其本地的ARP table cache，从而使得客户机访问时重新广播获取MAC地址。几乎所有的高可用软件都会考虑这个问题。\nARP广播而进行新的地址解析。\nlinux下具体命令：\nsh 1 2 arping -I eth0 -c 3 -s 10.0.0.162 10.0.0.253 arping -U -I eth0 10.0.0.162 LVS的调度算法 LVS的调度算法决定了如何在集群节点之间分布工作负荷。\n当Director调度器收到来自客户端计算机访问它的VIP上的集群服务的入站请求时，Director调度器必须决定哪个集群节点应该处理请求。Director调度器可用于做出该决定的调度方法分成两个基本类别:\n固定调度方法：rr wrr dh sh\n动态调度算法：wlc lc lblc lblcr SED NQ(后两种官方站点没提到，编译LVS, make过程可以看到召10种调度算法见如下表格：\n算法 说明 rr 轮循调度(Round-Robin)，它将请求依次分配不同的RS节点，也就是在RS节点中均摊请求。这种算法简单，但是只适合于RS节点处理性能相差不大的情况。 wrr 加权轮循调度(Weighted Round-Robin)，它将依据不同RS节点的权值分配任务。权值较高的RS将优先获得任务，并且分配到的连接数将比权值较低的RS节点更多。相同权值的RS得到相同数目的连接数。 dh 目的地址哈希调度(Destination Hashing)以目的地址为关键字查找一个静态hash表来获得需要的RS。 sh 源地址哈希调度(Source Hashing)以源地址为关键字查找一个静态hash表来获得需要的RS。 wlc 加权最小连接数调度(Weighted Least-Connection) 假设各台RS的权值依次为Wi(I=1..n)，当前的TCP连接数依次为Ti ( I= 1..n )，依次选取Ti/Wi为最小的RS作为 下一个分配的RS。 lc 最小连接数调度压(Least-Connection)，IPVS表存储了所有的活动的连接。把新的连接请求发送到当前连接数最小的RS。 lblc 基于地址的最小连接数调度(Locality-Based Least-Connection)，将来自同一目的地址的请求分配给同一台RS节点，如果这台服务器尚未满负荷，分配给连接数最小的RS，并以它为下一次分配的首先考虑。 lblcr 基于地址带重复最小连接数调度(Locality-Based Least-Connection with Replication)对于某一目的地址，对应有一个RS子集。对此地址请求，为它分配子集中连接数最小RS;如果子集中所有服务器均已满负荷，则从集群中选择一个连接数较小服务器，将它加入到此子集并分配连接;若一定时间内，未被做任何修改，则将子集中负载最大的节点从子集删除。 SED 最短的期望的延迟(Shortest Expected Delay Scheduling SED) (SED)\n基于wlc算法。这个必须举例来说了，\nABC三台机器分别权重123，连接数也分别是123。那么如果使用WLC算法的话一个新请求进入时它可能会分给ABC中的任意一个。使用sed算法后会进行这样一个运算。\nA(1+1)/1,\nB(1+2)/2,\nC(1+3)/3,\n根据运算结果，把连接交给C NQ 最少队列调度(Never Queue Scheduling NQ) (NQ)\n无需队列。如果有台realserver的连接数=0就直接分配过去，不需要在进行sed运算 LVS的调度算法的生产环境选型 (1) 一般的网络服务，如HTTP、Mail、MySQL等，常用的LVS调度算法为：\n基本轮叫调度rr算法 加权最小连接调度wlc 加权轮训调度wrr算法 (2) 基于局部性的最少链接LBLC和带复制的基于局部性最少链接LBLCR主要适用于Web Cache和Db Cache集群，但是我们很少这样用。\n(3) 源地址散列调度SH和目标地址散列调度DH可以结合使用在防火墙集群中，它们可以保证整个系统的唯一出入口。\n(4) 最短预期延时调度SED和不排队调度NQ主要是对处理时间相对比较长的网络服务。 实际使用中，这些算法的适用范围不限于这些。我们最好参考内核中的连接调度算法的实现原理，根据具体的业务需求合理的选型。\n安装LVS 下载地址：http://www.linuxvirtualserver.org/software/kernel-2.6/ipvsadm-1.26.tar.gz\n安装前准备\nsh 1 2 3 4 5 # ipvs为lvs调度器，工作在内核层，先查看是否安装 lsmod|grep ip_vs # 以uname -r结果为准工作中如果做安装虚拟化可能有多个内核，lvs是基于内核的 ln -s /usr/src/kernels/`uname -r`/ /usr/src/linux 安装依赖包\nsh 1 2 3 4 5 6 7 8 yum install libnl-devel popt-devel popt-static #\u0026lt;==centos7未发现问题 make \u0026amp;\u0026amp; make install /sbin/ipvsadm || modprobe ip_vs $ lsmod|grep ip_vs ip_vs 136798 0 nf_conntrack 105702 1 ip_vs libcrc32c 12644 2 xfs,ip_vs VS小结\n1、Centos5.X安装lvs，使用1.2.4版本。不要用1.2.6。\n2、Centos6.4安装lvs，使用1.2.6版本。并且需要先安装yum install libnl* popt*-y。\n3、安装lvs后，要执行ipvsadm把ip_vs模块加载到内核。\n手动配置LVS负载均衡服务 手工添加LVS转发 用户访问www.lb.com然后被DNS解析到vip 10.0.0.10，这个步骤是在DNS里配置的。 如果是自建DNS lb域的DNS记录设置如下\nsh 1 www IN A 10.0.0.10 如果未自建dns，需要在购买DNS域名商提供的DNS管理界面增加类似上面的DNS记录一条。这里的IP地址一定外网地址，才能正式使用，我们假设192.168.1.0/24段为外网段。修改结果类似下图(必须做真正环境才能做下面修改)\n配置LVS虚拟IP (VIP) sh 1 2 ifconfig eth0:0 10.0.0.10/24 up route add -host 10.0.0.10 dev eth0 #←添加主机路由，也可不加此行 因虚拟网卡网段和VIP不在一个网段，需要设置路由，windows设置路由方法如下：\nsh 1 2 route -p add 10.0.0.0/24 192.168.2.23 route print 到这里说明VIP\nsh 1 2 3 4 5 6 C:\\Users\\CM\u0026gt;ping 10.0.0.10 正在 Ping 10.0.0.10 具有 32 字节的数据: 来自 10.0.0.10 的回复: 字节=32 时间\u0026lt;1ms TTL=64 来自 10.0.0.10 的回复: 字节=32 时间\u0026lt;1ms TTL=64 来自 10.0.0.10 的回复: 字节=32 时间\u0026lt;1ms TTL=64 ⚠ 提示:到这里说明VIP地址己经配好，并可以使用了。\n手工执行配置添加LVS服务并增加两台RS sh 1 2 3 4 5 6 7 8 ipvsadm -C ipvsadm --set 30 5 60 ipvsadm -A -t 10.0.0.10:80 -s wrr ipvsadm -A -t 10.0.0.10:80 -s wrr -p 20 ipvsadm -a -t 10.0.0.10:80 -r 192.168.2.82:80 -g -w 1 ipvsadm -a -t 10.0.0.10:80 -r 192.168.2.21 -g -w 1 ipvsadm -D -t 10.0.0.10:80 -s wrr ipvsadm -d -t 10.0.0.10:80 -r 192.168.2.21 相关参数说明\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #\u0026lt;==清除内核虚拟服务器表中的所有记录 Either long or short options are allowed. --add-service -A add virtual service with options --edit-service -E edit virtual service with options --delete-service -D delete virtual service --clear -C clear the whole table --restore -R restore rules from stdin --save -S save rules to stdout --add-server -a add real server with options --edit-server -e edit real server with options --delete-server -d delete real server --list -L|-l list the table --set tcp tcpfin udp set connection timeout values --tcp-service -t service-address service-address is host[:port] --udp-service -u service-address service-address is host[:port] --scheduler -s scheduler one of rr|wrr|lc|wlc|lblc|lblcr|dh|sh|sed|nq, the default scheduler is wlc. --persistent -p [timeout] persistent service --netmask -M netmask persistent granularity mask --real-server -r server-address server-address is host (and port) --gatewaying -g gatewaying (direct routing) (default) --ipip -i ipip encapsulation (tunneling) --masquerading -m masquerading (NAT) --weight -w weight capacity of real server --mcast-interface interface multicast interface for connection sync --connection -c output of current IPVS connections --timeout output of timeout (tcp tcpfin udp) --stats output of statistics information 此时，在浏览器访问10.0.0.10结果是无法访问的：因为根据LVS原理，RS在接收到包后发现不是自己IP后就丢弃了。 IPVS(也叫LVS)的源码分析之persistent参数 - 沧海一粟 - CSDN博客\n手工在RS端绑定 sh 1 ifconfig lo:0 10.0.0.10/32 up #\u0026lt;==注意，子网掩码特殊 每个集群节点上的环回接口 (lo) 设备上被绑定VIP地址(其广播地址是其本身，子网掩码是255.255.255.255，采取可变长掩码方式把网段划分成只含一个主机地址的目的避免ip地址冲突)允许LVS-DR集群中的集群节点接受发向该VIP地址的数据包，这会有一个非常严重的问题发生，集群内部的真实服务器将尝试回复来自正在请求VIP客户端ARP广播，这样所有的真实服务器都将声称自己拥有该VIP地址，这时客户端将有可能接发送请求数据包到某台真实服务器上，从而破坏了DR集群的负载均衡策略。因此，必须要抑制所有真实服务器响应目标地址为VIP的ARP广播，而把客户端ARP广播的响应交给负载均衡调度器。\n抑制ARP响应方法如下\nsh 1 2 3 4 echo \u0026#34;1\u0026#34; \u0026gt;/proc/sys/net/ipv4/conf/lo/arp_ignore echo \u0026#34;2\u0026#34; \u0026gt;/proc/sys/net/ipv4/conf/lo/arp_announce echo \u0026#34;1\u0026#34; \u0026gt;/proc/sys/net/ipv4/conf/all/arp_ignore echo \u0026#34;2\u0026#34; \u0026gt;/proc/sys/net/ipv4/conf/all/arp_announce arp抑制技术参数说明 中文说明:，\narp_ignore- INTEGER\n定义对目标地址为本地IP的ARP询问不同的应答模式。\n0 (默认值)：回应任何网络接口上对任何本地IP地址的arp查询请求。\n1：只回答目标IP地址是来访问网络接口本地地址的ARP查询请求\n2：只回答目标IP地址是来访网络接口本地地址的ARP查询请求，且来访IP必须在该网络接口的子网段内。\n3：不回应该网络界面的arp请求，而只对设置的唯一和连接地址做出回应。，\n4-7：保留未使用。‘\n8：不回应所有(本地地址)的arp查询。\narp_announce一INTEGER\n对网络接口上，本地IP地址的发出的，ARP回应，作出相应级别的限制：确定不同程度的限制，宣布对来自本地源lP地址发出Ail，请求的接口\n0 (默认)在任意网络接口(eth0,eth1, lo)上的任何本地地址\n1：尽量避免不在该网络接口子网段的本地地址做出arp回应.当发起ARP请求的源IP地址是被设置应该经由路由达到此网络接口的时候很有用。此时会检查来访IP是否为所有接口上的子网段内ip之一。如果该来访IP不属于各个网络接口上的子网段内，那么将采用级别2的方式来进行处理。\n2：一对查询目标使用最适当的本地地址，在此模式下将忽略这个IP数据包的源地址并尝试能与该地址通信的本地地址，首要是选择所有的网络接口的子网中外出访问子网中包含该目标IP地址的本地地址。如果没有合适的地址被发现，将选择当前的发送网络接口或其他的有可能接受到该ARP回应的网络接来进发送。限制了使用本地VIP地址作为优先的网络接口。\n检查手工添加LVS转发成果 测试LVS服务的转发：\n首先在客户端浏览器访问RS http://192.168.1.6 及 RS http://192.168.1.7 确认是否RS端正常然后访问DR的VIP http://10.0.0.10， 如果经过多次测试能分别出现RS1, RS2的 不同结果内容就是表示配置成功。\n⚠ 提示:负载均衡的算法倾向于一个客户端IP定向到一个后端服务器，以保持会话连贯性，如果用两三台机器去测试也许就不一样。\n在访问的同时可以用命令查看状态信息 sh 1 2 3 4 5 6 7 $ ipvsadm -L -n --stats IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Conns InPkts OutPkts InBytes OutBytes -\u0026gt; RemoteAddress:Port TCP 10.0.0.10:80 88 674 0 88293 0 -\u0026gt; 192.168.2.21:80 44 355 0 47851 0 -\u0026gt; 192.168.2.82:80 44 319 0 40442 0 使用脚本配置lvs负载均衡服务 sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #!/bin/sh VIP=\u0026#39;10.0.0.10\u0026#39; . /etc/init.d/functions Port=80 RIP=( 192.168.2.21 192.168.2.82 ) clean_all(){ ipvsadm -C return $? } set_virtual_server(){ ipvsadm --set 30 5 60 ipvsadm -A -t $VIP:$Port -s wrr return $? } start(){ clean_all [ $? -eq 0 ] \u0026amp;\u0026amp; set_virtual_server || return 1 for n in ${RIP[@]} do ipvsadm -a -t $VIP:$Port -r $n:$Port -g -w 1 done } stop(){ clean_all } usage(){ echo \u0026#39;USAGE:\u0026#39;$0 \u0026#39;{start|stop|restart}\u0026#39; } case \u0026#34;$1\u0026#34; in start|START) start ;; stop|STOP) stop ;; restart|RESTART) stop start ;; *) usage esac sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #!/bin/sh . /etc/init.d/functions master_ip=192.168.2.23 VIP=\u0026#39;10.0.0.10\u0026#39; Port=80 RIP=( 192.168.2.21 192.168.2.82 ) set_virtual_server(){ ipvsadm --set 30 5 60 ipvsadm -A -t $VIP:$Port -s wrr return $? } check_master(){ /usr/bin/ping -c 2 $master_ip \u0026amp;\u0026gt;/dev/null return $? } clean_all(){ ipvsadm -C } start(){ clean_all \u0026amp;\u0026amp; /sbin/ifconfig eth0:0 $VIP/24 up [ $? -eq 0 ] \u0026amp;\u0026amp; set_virtual_server || return 10 for n in ${RIP[@]} do /sbin/ipvsadm -a -t $VIP:$Port -r $n:$Port -g -w 1 done } stop(){ clean_all /sbin/ifconfig eth0:0 $VIP/24 down } check_isnode(){ num=`ipvsadm -L -n|grep 192|wc -l` if [ $num -lt ${#RIP[@]} ];then return 0 fi return 11 } check_isup(){ num=`ifconfig eth0:0|grep $VIP|wc -l` if [ $num -eq 1 ];then return 0 fi return 12 } while true do check_master if [ $? -ne 0 ];then check_isnode if [ $? -eq 0 ];then start fi else check_isup if [ $? -eq 0 ];then stop fi fi sleep 3 done 常见的LVS负载均衡高可用解决方案 (1) 通过开发上面的脚本来解决，如果负载均衡器硬件坏了。几分钟或秒级别内在其它备机上完成新的部署，如果做的细的，还可以写脚本来做调度器之间的切换和接管功能。早起的方法，还是比较笨重的，目前已经不推荐使用。\n(2) heartbeart+lvs+ldirectord脚本配置方案，这个方案同学们自己可以去搜索，这个方案中heartbeat负责VIP的切换以及资源的启动停止，ldirectord负责RS节点的健康检查，用于比较复杂，不易控制，属于早期的解决方案，现在已经很少使用了。\nheartbeat and ldirectord方案资料:\nhttp://www.linuxvirtualserver.org/docs/ha/heartbeat_ldirectord.html\nhttp://www.linuxvirtualserver.org/docs/ha/ultramonkey.html\n(3) 通过Redhat提供的工具piranha来配置LVS Piranha是REDHAT提供的一个基于Web的LVS配置软件，可以省去手工配置LVS的繁琐工作，同时，也可单独提供。cluster功能，例如，可以通过Piranh。激活Director Server的后备主机，也就是配置Director Server的双机热备功能。\n(4) keepalived+LVS方案，当前最优方案，因为这个方案符合简单、易用、高效的运维原则。 The Keepalived Solution http://www.linuxvirtualserver.org/docs/ha/keepalived\n(5) 其他\nhttp://bbs.linuxtone.org/thread-1402-1-1.html\nLVS Documentation\nhttp://www.linuxvirtualserver.org/zh/index.html\nhttp://www.linuxvirtualserver.org/Documents.html#performance\nhttp://zh.linuxvirtualserver.org/node/2230\nLVS集群分发请求RS不均衡生产环境实战解决 生产环境中 ipvsadm -L -n 发现两台RS的负载不均衡，一台有很多请求，一台没有请求，并且没有请求的那台RS经测试服务正常，lo:VIP也有。但是就是没有请求。\nsh 1 2 3 TCP 172.168.1.50:3307 wrr persistent 10 一\u0026gt;172.168.1.51:3307 Route 1 0 0 一\u0026gt;172.168.1.52:3307 Route 1 8 12758 问题原因：\npersistent 10的原因，persistent会话保持，当clientA访问网站的时候，LVS把请求分发给了52,那么以后clientA再点击的其他操作其他请求，也会发送给52这台机器。\n解决办法:\n到keepalived中注释掉persistent 10然后/etc/init.d/keepalived reload，然后可以看到以后负载均衡两边都请求都均衡了。\n其它导致负载不均的原因可能有：\nLVS自身的会话保持参数设置((-p 300, persistent 300)。优化:大公司尽量用cookie替代session LVS调度算法设置，例如：rr, wrr, wld,lc算法。 后端RS节点的会话保持参数，例如:apache的keealive参数。 访问量较少的情况不均衡的现象更加明显。 用户发送的请求时间长短，和请求资源多少大小。 实现会话保持的方案：\nhttp://oldboy.blog.5lcto.com/2561410/1331316\nhttp://oldboy.blog.51cto.com/2561410/1323468\nLVS故障排错理论及实战讲讲解 排查的大的思路就是，要熟悉LVS的工作原理过程，然后根据原理过程来排查。\n调度器上LVS调度规则及IP的正确性。 RS节点上VIP绑定和arp抑制的检查。 生产处理思路：\n对绑定的vip做实时监控，出问题报警或者自动处理后报警。 把绑定的vip做成配置文件，例如:vi /etc/sysconfig/network-scripts/lo:0 ARP抑制的配置思路：\n如果是单个VIP，那么可以用stop传参设置0。 如果RS端有多个、VIP绑定，此时，即使是停止VIP绑定也一定不要置0。 sh 1 2 3 4 5 6 if [ ${#VIP[@]} ];then echo \u0026#34;0\u0026#34; \u0026gt;/proc/sys/net/ipv4/conf/lo/arp_ignore echo \u0026#34;0\u0026#34; \u0026gt;/proc/sys/net/ipv4/conf/lo/arp_announce echo \u0026#34;0\u0026#34; \u0026gt;/proc/sys/net/ipv4/conf/all/arp_ignore echo \u0026#34;0\u0026#34; \u0026gt;/proc/sys/net/ipv4/conf/all/arp_announce if RS节点上自身提供月够的检查(DR不能端口转换)\n辅助排除工具有tcpdump, ping等。\n负载均衡和反向代理集群的三角形排查理论。\nkeepalived+lvs负载均衡配置 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 virtual_server 10.0.0.10 80 { delay_loop 3 #\u0026lt;== 健康检查时间，单位是秒 lb_algo wrr #\u0026lt;== 负载调度的算法为wlc lb_kind DR #\u0026lt;==LVS实现负载的机制，NAT/DR/TUN/FULLNAT nat_mask 255.255.255.0 persistence_timeout 20 #\u0026lt;==会话保持 -p的功能 protocol TCP #\u0026lt;==lvs4层负载均衡 tcp udp # ipvsadm -A -t 10.0.0.10:80 -s wrr -p 20 real_server 192.168.1.5 80 { weight 1 #\u0026lt;==配置节点权值，数字越大权重越高 TCP_CHECK { #\u0026lt;==健康检查 connect_timeout 10 #\u0026lt;==超时时间 nb_get_retry 3 #\u0026lt;==延迟重试次数 delay_before_retry 3 #\u0026lt;==重试次数 connect_port 80 #\u0026lt;==检查端口 } } } # ipvsadm -a -t 10.0.0.10:80 -r 192.168.1.5 -g -w 1 查看负载结果\n当master宕机后，可看到近1分钟时间进行切换\nLVS负载均衡代码平滑上线发布思路\n发布代码：\n开发人员本地测试-一\u0026gt;办公室内部测试（开发个人，测试人员）一（配置管理员）\u0026ndash;\u0026gt;IDC机房测试环境（测试人员）\u0026ndash;\u0026gt;正是服务器\u0026ndash;\u0026gt;运维上线\u0026ndash;\u0026gt;100台\n一台一台下，测试完ok挂上去，此时，机器上代码不一致，用户体验就不同，此时需要下线一半，测试，测试完再下线另一半。\n下线方法：\n准备两套配置文件，一套配置文件含有前两台配置文件的配置，一套配置文件含有后两台配置文件的配置。最后用完整的配置文件替换。\n方法二：用ipvsadm来控制节点的增加和删除。keepalived不重启节点就不会改变\n生产场景测试步骤\n","permalink":"https://www.161616.top/lvs-and-keepalived/","summary":"LVS概述 负载均衡(Load Balance)集群提供了一种廉价、有效、透明的方法，来扩展网络设备和服务器的负载、带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。\n搭建负载均衡服务的需求\n把单台计算机无法承受的大规模的并发访问或数据流量分担到多台节点设备上分别处理，减少用户等待响应的时间，提升用户体验. 单个重负载的运算分担到多台节点设备上做并发处理，每个节点设备处理结束后，将结果汇总，返回给用户，系统处理能力得到大幅度提高。 7*24小时服务保证，任意一个或多个有限后面节点设备宕机，要求不能影响业务。 在负载均衡集群中，所有计算机节点都应该提供相同的服务。集群负载均衡器所截获所有对该服务的入站请求。然后将这些请求尽可能的平均分配在所有集群节点上。\nLVS (Linux Virtual Server)介绍 LVS是Linux Virtual Server的简写，意即Linux虚拟服务器，是一个虚拟的服务器集群系统，可在UNIX、Linux平台下实现负载均衡集群功能。该项目在1998年5月由章文嵩博士组织成立，是中国国内最早出现的自由软件项目之一\nLVS项目介绍 http://www.linuxvirtualserver.org/zh/lvs1.html\nLVS集群的体系结构 http://www.linuxvirtualserver.org/zh/lvs2.html\nLVS集群中的IP负载均衡技术 http://www.linuxvirtualserver.org/zh/lvs3.html\nLVS集群的负载调度 http://www.linuxvirtualserver.org/zh/lvs4.html\nIPVS（LVS）发展史 早在2.2内核时，IPVS就已经以内核补丁的形式出现。\n从2.4.23版本开始，IPVS软件就是合并到Linux内核的常用版本的内核补丁的集合。\n从2.4.24以后IPVS已经成为Linux官方标准内核的一部分。\nIPVS软件工作层次图 从上图可以看出，LVS负载均衡调度技术是在Linux内核中实现的，因此，被称之为Linux虚拟服务器（Linux virtual Server）。我们使用该软件配置LVS时候，不能直接配置内核中的ipvs，而需要使用ipvs的管理工具ipsadm进行管理.\nLVS技术点小结：\n真正实现调度的工具是IPVS， 工作在Linux内核层面 LVS自导IPVS管理工具是ipvsadm keepalived实现管理IPVS及负载均衡器的高可用。 RedHat工具Piranha WEB管理实现调度的工具IPVS。 LVS体系结构与工作原理简单描述 LVS集群负载均衡器接受服务的所有入站客户端计算机请求，并根据调度算法决定那个集群几点应该处理回复请求。负载均衡器简称(LB)有时也被成为LVS Director简称Director\nLVS虚拟服务器的体系结构如下图所示，一组服务器通过告诉的局域网或者地理分布的广域网互相连接，在他们的前端有一个负载调度器（Load Balancer）。负载调度器能无缝地将网络请求调度到真实服务器上，从而使得服务器集群的结构对客户是透明的，客户访问集群系统提供的网络服务就像访问一台高性能、高可用的服务器一样。客户程序不收服务器集群的影响不需作任何修改。胸的伸缩性通过在服务集群中透明的加入和删除一各节点来达到，通过检测节点或服务进程故障和正确地重置系统达到高可用性。由于我们的负载调度技术是在Linux内核中实现的，我们称之为Linux虚拟服务器（Linux Virtual Server）。\nLVS基本工作过程图 **LVS基本工作过程图1：带颜色的小方块代表不同的客户端请求\nLVS基本工作过程图2：\n不同的客户端请求小方块经过负载均衡器，通过指定的分配策略被分发到后面的机器上\nLVS基本工作过程图3：\nLVS基本工作过程图4：\nLVS相关术语命名约定 名称 缩写 说明 虚拟IP地址(Virtual IP Address) VIP VIP为Direcort用于向客户端计算机提供IP地址.如www.baidu.com域名就要解析到VIP上提供服务 真实IP地址(Real Server IP Address) RIP 在集群下面节点上使用的IP地址，物理IP地址 Director的IP地址(Director IP Address) DIP Director用于连接内外网络的IP地址，物理网卡上的IP地址，是负载均衡器上的IP 客户端主机IP地址(Client IP Address) CIP 客户端用户计算机请求集群服务器的IP地址，该地址用作发送给集群的请求的源IP地址 LVS集群内部的节点称为真实服务器(Real Server)，也叫做集群节点。请求集群服务的计算机称为客户端计算机。","title":"LVS \u0026 keepalived 集群架构"},{"content":"rpm 与 fpm 软件的安装方式 编译安装：优点是可以定制化安装目录、按需开启功能等，缺点是需要查找并实验出适合的编译参数，诸如MySQL之类的软件编译耗时过长 yum安装：优点是全自动化安装，不需要为依赖问题发愁，缺点是自主性太差，软件的功能、存放位置都已经固定好了，不易变更。 编译源码：根据自己的需求做成 RMP包 ==\u0026gt; 搭建yum仓库 ==\u0026gt; yum安装。结合前两者的优点，暂未发现什么缺点。可能的缺点就是RPM包的通用性差，一般人不会定制RPM包。 RPM概述 RPM全称是Red Hat Package Manager(RedHat包管理器)。几乎所有的Linux发型版本都使用这种形式的软件包管理安装、更新和卸载软件。\nrpm命令有5种基本功能（不包括创建软件包）：安装、卸载、升级、查询和验证。\n关于rpm命令的使用可以用rpm \u0026ndash;help来获得\nrpmbuild rpmbuild是reahat系的原声打包命令，这个命令的使用难点主要在于spec文件编写，一个类似于kickstart的ks.cfg文件。\n作为一个使用工具，种种繁琐，在没有替代品时还能存活。当有了其他简易工具时，他就到了完蛋的时候\nfpm fpm 是将一种类型的包转换成另一种类型\n支持的源类型包\n类型 说明 dir 将目录打包成所需要的类型，可以用于源码编译安装的软件包 rpm 对rpm进行转换 gem 对rubygem包进行转换 python 将python模块打包成相对应的类型 支持目标类型包 rpm 转换为rpm包 deb 转换为deb包 solaris 装环卫solaris包 puppet 转换为puppet模块 fpm安装 fpm是ruby写的，因此系统环境需要ruby，而且ruby版本号大于bshards运行的版本。\nyum安装ruby模块 bash 1 yum install ruby rubygems ruby-devel -y 查看ruby的版本\nbash 1 2 3 4 5 6 7 $ rpm -qa|grep ruby ruby-libs-1.8.7.374-4.el6_6.x86_64 rubygems-1.3.7-5.el6.noarch ruby-1.8.7.374-4.el6_6.x86_64 ruby-rdoc-1.8.7.374-4.el6_6.x86_64 ruby-devel-1.8.7.374-4.el6_6.x86_64 ruby-irb-1.8.7.374-4.el6_6.x86_64 替换gem源 ruby中国镜像：https://ruby-china.org/\nbash 1 gem source -a http://gems.ruby-china.org -r remove xx 删除一个gem源列表 -l list gem源列表 安装错误 此问题提示 ruby的版本至少要求为1.9.3，在我们yum安装的ruby为1.8.7\nbash 1 2 3 4 5 $ gem install fpm Building native extensions. This could take a while... Building native extensions. This could take a while... ERROR: Error installing fpm: ruby-xz requires Ruby version \u0026gt;= 1.9.3. 解决方法：\n升级ruby版本\n安装低版本fpm\nERROR: Could not find a valid gem 'fpm' (\u0026gt;= 0) in any repository\n原因：gem源失效，替换gem源即可\nbash 1 ERROR: Could not find a valid gem \u0026#39;fpm\u0026#39; (\u0026gt;= 0) in any repository 升级ruby 下载rvm bash 1 2 3 4 gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 curl -sSL https://get.rvm.io | bash -s stable # 如果上面的连接失败，可以尝试: curl -L https://raw.githubusercontent.com/wayneeseguin/rvm/master/binscripts/rvm-installer | bash -s stable rvm会下载安装到/usr/local/rvm下\n列出已知的ruby版本 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ /usr/local/rvm/bin/rvm list known # MRI Rubies [ruby-]1.8.6[-p420] [ruby-]1.8.7[-head]$ security released on head [ruby-]1.9.1[-p431] [ruby-]1.9.2[-p330] [ruby-]1.9.3[-p551] ... [ruby-]2.3[.3] [ruby-]2.4[.0] ruby-head ... # IronRuby ironruby[-1.1.3] ironruby-head 按照提示安装ruby1.9.3 bash 1 /usr/local/rvm/bin/rvm install ruby-1.9.3 将安装的ruby切换为默认 如果想设置为默认版本，这样一来以后新打开的控制台默认的 Ruby 就是这个版本\n错误：RVM is not a function\nbash 1 2 3 4 5 6 $ /usr/local/rvm/bin/rvm use 1.9.3 --default RVM is not a function, selecting rubies with \u0026#39;rvm use ...\u0026#39; will not work. You need to change your terminal emulator preferences to allow login shell. Sometimes it is required to use `/bin/bash --login` as the command. Please visit https://rvm.io/integration/gnome-terminal/ for an example. 原因：需要添加到系统变量\nbash 1 2 [[ -s \u0026#34;$HOME/.rvm/scripts/rvm\u0026#34; ]] \u0026amp;\u0026amp; . \u0026#34;$HOME/.rvm/scripts/rvm\u0026#34; source /etc/profile 安装低版本fpm 指定版本安装\nbash 1 gem install fpm -v 1.4 查看安装完的fpm版本号\nbash 1 2 $ fpm --version 1.4.0 卸载一个安装版本\nbash 1 rvm remove 1.9.2 https://www.ruby-lang.org/zh_cn/downloads/\n编译安装ruby\n参考资料：\nDownload RubyGems\nruby的升级过程\n如何快速正确的安装 Ruby, Rails 运行环境\nRVM 解决 Ruby 的版本问题 fpm使用 参数 说明 -s 指定源类型 -t 指定目标类型，即想要制作为什么包 -n 指定包的名字 -v 指定包的版本号 -C 指定打包的相对路径 -d 指定依赖于哪些包 -f 第二次包时目录下如果有同名安装包存在，则覆盖它 -p 输出的安装包的目录，不想放在当前目录下就需要指定 \u0026ndash;post-install 软件包安装完成之后所要运行的脚本；同\u0026ndash;offer-install \u0026ndash;pre-install 软件包安装完成之前所要运行的脚本；同\u0026ndash;before-install \u0026ndash;post-uninstall 软件包卸载完成之后所要运行的脚本；同\u0026ndash;offer-remove \u0026ndash;pre-uninstall 软件包卸载完成之前所要运行的脚本；同—before-remove yum安装是如何解决依赖问题的？ 在使用yum安装软件A时，yum会在下载完A的rpm包后，对该rpm包进行检查（rpm包会给出安装该rpm包所依赖的基础库和软件）。如果检查出A的安装还要依赖软件B，那么此时yum就会自动下载并安装B。B安装完毕后，就会继续安装A。如果是内网yum源的话，我们只需要把B放在内网yum源即可。如果检查出A的安装不需要其他软件的支持，那么yum会自动安装A。因此使用rpm -d添加依赖关系\n打包fpm bash 1 fpm -s dir -t rpm -n sphinx -v 5.5.54 -d \u0026#39;libaio-devel,ncurses-devel\u0026#39; --post-install /app/mysql.sh /app/mysql 相对路径问题\n使用相对路径打包，会直接保存为/目录。这样就不会是我们指定的目录了。\nbash 1 2 3 4 5 6 7 8 $ rpm -pql sphinx-1.0-1.x86_64.rpm /bin/indexer /bin/indextool /bin/searchd ... /var/data/test1stemmed.spp /var/data/test1stemmed.sps /var/log 制定依赖包\nbash 1 fpm -d \u0026#39;libaio-devel,ncurses-devel\u0026#39; 在安装时就会检测依赖包，如无此包就报错\nbash 1 2 3 4 $ rpm -ivh mysql-5.5.54-1.x86_64.rpm error: Failed dependencies: libaio-devel is needed by mysql-5.5.54-1.x86_64 ncurses-devel is needed by mysql-5.5.54-1.x86_64 安装完成后指定要执行的脚本\nbash 1 fpm --post-install \u0026#39;mysql.sh\u0026#39; 本地模拟yum安装自己打包的软件\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ yum localinstall mysql-5.5.54-1.x86_64.rpm 已加载插件：fastestmirror, security 设置本地安装进程 诊断 mysql-5.5.54-1.x86_64.rpm: mysql-5.5.54-1.x86_64 mysql-5.5.54-1.x86_64.rpm 将被安装 .... .... Non-fatal POSTIN scriptlet failure in rpm package mysql-5.5.54-1.x86_64 useradd: user \u0026#39;mysql\u0026#39; already exists # 安装后执行脚本中创建的用户，因已创建用户，故提示 warning: %post(mysql-5.5.54-1.x86_64) scriptlet failed, exit status 9 已安装: mysql.x86_64 0:5.5.54-1 作为依赖被安装: libaio-devel.x86_64 0:0.3.107-10.el6 ncurses-devel.x86_64 0:5.7-4.20090207.el6 作为依赖被升级: ncurses-base.x86_64 0:5.7-4.20090207.el6 ncurses-libs.x86_64 0:5.7-4.20090207.el6 初始化后启动MySQL并登陆\nbash 1 2 3 4 5 6 7 $ /etc/init.d/mysqld start Starting MySQL.. SUCCESS! $ bin/mysql Welcome to the MySQL monitor. Commands end with ; or \\g. Your MySQL connection id is 1 .. mysql\u0026gt; 打包错误 原因未知\n解决方法：可能是打包时会产生大量的缓存，导致空间不够，删除一些文件后问题再没出现过\nbash 1 2 $ fpm -s dir -t rpm -n mysqld -v 5.5.54 -f --post-install /app/m.in.sh --post-uninstall /app/un.m.sh /app/mariadb-5.5.54/ Process failed: rpmbuild failed (exit code 1). Full command was:[\u0026#34;rpmbuild\u0026#34;, \u0026#34;-bb\u0026#34;, \u0026#34;--define\u0026#34;, \u0026#34;buildroot /tmp/package-rpm-build-2570d4ce573d6b34f9153009975231fe511bee89c64018c2c7e219355d61/BUILD\u0026#34;, \u0026#34;--define\u0026#34;, \u0026#34;_topdir /tmp/package-rpm-build-2570d4ce573d6b34f9153009975231fe511bee89c64018c2c7e219355d61\u0026#34;, \u0026#34;--define\u0026#34;, \u0026#34;_sourcedir /tmp/package-rpm-build-2570d4ce573d6b34f9153009975231fe511bee89c64018c2c7e219355d61\u0026#34;, \u0026#34;--define\u0026#34;, \u0026#34;_rpmdir /tmp/package-rpm-build-2570d4ce573d6b34f9153009975231fe511bee89c64018c2c7e219355d61/RPMS\u0026#34;, \u0026#34;--define\u0026#34;, \u0026#34;_tmppath /tmp\u0026#34;, \u0026#34;/tmp/package-rpm-build-2570d4ce573d6b34f9153009975231fe511bee89c64018c2c7e219355d61/SPECS/mysqld.spec\u0026#34;] {:level=\u0026gt;:error} YUM 什么是yum yum主要用于自动安装、升级rpm软件包，他能自动查找并解决rpm包之间的依赖关系。要成功的使用yum工具安装更新软件或系统，就需要有一个包含各种rpm软件包的repository（软件仓库），这个软件仓库我们习惯称为yum源，网络上有大量的yum源，但由于收到网络环境的限制，导致软件安装耗时过长甚至失败。特别是当有大量服务器大量软件包需要安装时，缓慢的进度条令人难以忍受。因此我们在优化系统时，都会更换国内的源。\n相比较而言，本地yum源服务器最大优点是局域网的快速网络连接和稳定性。有了局域网中的yum源服务器，即使在internet连接中断的情况下，也不会影响其他yum客户端的软件安装和升级\n创建yum源 上传rpm包到此目录，此目录下面还可以包括文件夹\nbash 1 mkdir -p /tools/yum/centos6/x86_64 yum下载的文件缓存在\nbash 1 /var/cache/yum/x86_64/6/base/ 安装createrepo工具 bash 1 yum install -y createrepo 初始化repodata索引文件\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $ createrepo -pdo /tools/yum/centos6/x86_64/ /tools/yum/centos6/x86_64/ Spawning worker 0 with 4 pkgs Workers Finished Gathering worker results Saving Primary metadata Saving file lists metadata Saving other metadata Generating sqlite DBs Sqlite DBs complete $ cd /tools/yum/centos6/x86_64/ $ ls libaio-devel-0.3.107-10.el6.i686.rpm libaio-devel-0.3.107-10.el6.x86_64.rpm ncurses-devel-5.7-4.20090207.el6.i686.rpm ncurses-devel-5.7-4.20090207.el6.x86_64.rpm repodata $ ls repodata/. 1453d96f9216e7c230abfe7921a2fd1dd11541568934832306342c89ad04ff1d-other.sqlite.bz2 2475630f1247fc6ac2b822541d6ad71ed83d733792c7d52de580fdebd3abda2c-filelists.sqlite.bz2 9e22eb60f06501a3f4399dc3c2d3e3858567804e7ab352679fa650b93d279fb9-other.xml.gz c3ee9c8d8508c55c77f3f058f056bdd617587ee5a906f22bc9a512bc8b950ded-filelists.xml.gz d9d57f7733cb42831001e915ddd51bb126b67c26bca64bf6c7e7390eed9a54d9-primary.sqlite.bz2 e9616244b93c46350e33dc04d4cb442e49ae375b7400a262bd72e8ba90a1f4a8-primary.xml.gz repomd.xml 提供yum服务 可以用apache或nginx提供web服务，但用Python的http模块更简单，适用于内网环境。CentOS 6最小化安装后自带python。\n利用python的http模块提供服务 bash 1 2 python -m SimpleHTTPServer 80 \u0026amp;\u0026gt;/dev/null # 提供服务的目录是执行命令时的目录 利用nginx提供yum源服务 bash 1 2 3 4 5 6 location / { root /tools; index index.html index.htm; autoindex on; # 当找不到首页文件时，会展示目录结构，这个功能一般不要用除非有需求。 # 如没有此选项，会报403 } 添加新的rpm包 bash 1 2 # 只下载软件不安装 yumdownlocaler pcre-devel openssl-devel 每当该目录新增软件包需要更新源\nbash 1 createrepo --update /tools 本地客户端配置 text 1 2 3 4 5 6 /etc/yum.repos.d/test.repo [test] name=Server baseurl=http://192.168.2.110 enable=1 gpgchek=0 使用配置的自定义的源安装PHP\nbash 1 2 3 yum --enablerepo=test --disablerepo=base,extras,updates install php # --enbalerepo使用的源 # --disablerepo禁用源 yum命令 选项 说明 install 安装软件包 yum install httpd list 列出yum仓库内文件 yum list httpd，可搜索带名称的特定软件包 search 不急的软件报的确切名称，可以使用search函数，搜索与指定软件包的名称相匹配的所有可用软件包yum search httpd provides 查找某个特定文件属于哪个软件包。yum provides /app/apache/confi/http.conf grouplist 列出所有可用群组 groupinstall 安装群组软件包yum groupinstall develment-tools repolist 列出启用的软件库 repolist all 列出所有软件库，包括禁用的也列出 \u0026ndash;enablerepo 安装来自特定软件库的软件包 \u0026ndash;disablerepo 不安装来自指定软件库的软件包yum \u0026ndash;enablerepo=test \u0026ndash;disablerepo=base,extras\u0026hellip; install httpd chean all 清理yum缓存内容 history 查看yum历史记录 yum源 Yum源分为三大类：\nBase：就是你下载的光盘镜像里面的DVD1 Extra：就是你下载光盘镜像的DVD2 Epel：属于额外的，得到Epel官方获取 将光盘挂载到系统上，你会发现里面有个packages目录，里面全是rpm包\nbash 1 2 3 4 5 6 7 8 9 $ ls CentOS_BuildTag images ... Packages RPM-GPG-KEY-CentOS-Security-6 $ ll|wc -l 4186 配置yum源 找一个镜像站点，国内常用镜像站点\nhttp://mirrors.aliyun.com\nhttp://mirrors.163.com\n系统yum源路径，执行yum时，它只会读取yum.repo.d下这个目录下的所有以.repo结尾的文件。\nbash 1 2 3 4 5 6 7 $ ls /etc/yum.repos.d/ CentOS-Base.repo CentOS-fasttrack.repo CentOS-Vault.repo CentOS-Debuginfo.repo CentOS-Media.repo epel.repo repo文件的写入是有其特殊格式的，如下：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [aaa] name=aaa baseurl=http://192.168.2.110 enable=1 gpgchek=0 [bbb] name=bbb baseurl=http://192.168.2.110 enable=1 gpgchek=0 [ccc] name=ccc baseurl=http://192.168.2.110 enable=1 gpgchek=0 所谓的自己配置Yum仓库就是把网上那些程序包全下载下来，在本地(内网)提供Yum。除了epel提供的所有包外，还有镜像光盘DVD1,DVD2！\nyum服务配置文件 配置文件分为两部分main和repository\n配置本地yum源\n禁用默认的yum网络源，将yum网络源配置文件改名为CentOS-Base.repo.bak，否则会现在网络源中寻找适合的包，改名之后直接从本地源读取。也可以向上面一样自己写一个文件。\n全局配置文件 main部分定义了全局配置选项，整个yum配置文件应该只有一个main，/etc/yum.conf\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # /etc/yum.conf [main] # cachedir：yum缓存的目录，yum在此存储下载的rpm包和数据库，一般是/var/cache/yum/$basearch/$releasever。 cachedir=/var/cache/yum/$basearch/$releasever # 设置 keepcache=1，yum 在成功安装软件包之后保留缓存的头文件 (headers) 和软件包。默认值为 keepcache=0 不保存 keepcache=[1 or 0] # debuglevel：除错级别，0──10,默认是2 貌似只记录安装和删除记录 debuglevel=2 logfile=/var/log/yum.log # pkgpolicy： 包的策略。一共有两个选项，newest和last，这个作用是如果你设置了多个repository，而同一软件在不同的repository中同时存 在，yum应该安装哪一个，如果是newest，则yum会安装最新的那个版本。如果是last，则yum会将服务器id以字母表排序，并选择最后的那个 服务器上的软件安装。一般都是选newest。 pkgpolicy=newest # 指定一个软件包，yum会根据这个包判断你的发行版本，默认是RedHat-release，也可以是安装的任何针对自己发行版的rpm包 distroverpkg=CentOS-release # tolerent，也有1和0两个选项，表示yum是否容忍命令行发生与软件包有关的错误，比如你要安装1,2,3三个包，而其中3此前已经安装了，如果你设为1,则yum不会出现错误信息。默认是0。 tolerant=1 # exactarch，有两个选项1和0,代表是否只升级和你安装软件包cpu体系一致的包，如果设为1，则如你安装了一个i386的rpm，则yum不会用1686的包来升级。 exactarch=1 # retries，网络连接发生错误后的重试次数，如果设为0，则会无限重试。 retries=20 obsoletes=1 # gpgchkeck= 有1和0两个选择，分别代表是否是否进行gpg校验，如果没有这一项，默认是检查的。 gpgcheck=1 # 该选项用户指定 .repo 文件的绝对路径。.repo 文件包含软件仓库的信息 (作用与 /etc/yum.conf 文件中的 [repository] 片段相同)。 reposdir=[包含 .repo 文件的目录的绝对路径] # 默认是 /etc/yum.repos.d/ 低下的 xx.repo后缀文件 # exclude 排除某些软件在升级名单之外，可以用通配符，列表中各个项目要用空格隔开，这个对于安装了诸如美化包，中文补丁的朋友特别有用。 exclude=xxx 第二部分repoitory repoitory部分定义了每个源/服务器的具体配置，可以有一到多个，位于/etc/yum.repos.d/目录下的各文件中。这个字段其实也可以在yum.conf里面直接配置\nrepo文件的格式\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # serverid用于区别各个不同的repository，必须有一个独一无二的名称。 重复了前面覆盖后面--还是反过来呢？？？用enabled 测试是后面覆盖前面 [serverid] # name是对repository的描述，支持像$releasever $basearch这样的变量; name=Fedora Core $releasever - $basearch - Released Updates name=Some name for this server # baseurl是服务器设置中最重要的部分，只有设置正确，才能从上面获取软件。它的格式是： # 其中url支持的协议有 http:// ftp:// file://三种。baseurl后可以跟多个url，你可以自己改为速度比较快的镜像站，但baseurl只能有一个，也就是说不能像如下格式： baseurl=url://server1/path/to/repository/ baseurl=url://server2/path/to/repository/ baseurl=url://server3/path/to/repository/ 其中url指向的目录必须是这个repository header目录的上一级，它也支持$releasever $basearch这样的变量。 baseurl=url://path/to/repository/ baseurl=url://server1/path/to/repository/ url://server2/path/to/repository/ url://server3/path/to/repository/ # 这一行是指定一个镜像服务器的地址列表，通常是开启的，本例中加了注释符号禁用了，我们可以试试，将$releasever和$basearch替换成自己对应的版本和架构，例如10和i386，在浏览器中打开，我们就能看到一长串镜可用的镜像服务器地址列表。 mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-$releasever\u0026amp;arch=$basearch url之后可以加上多个选项，如gpgcheck、exclude、failovermethod等，比如： # 其中gpgcheck，exclude的含义和[main]部分相同，但只对此服务器起作用 gpgcheck=1 exclude=gaim # failovermethode 有两个选项roundrobin和priority，意思分别是有多个url可供选择时，yum选择的次序，roundrobin是随机选择，如果连接失 败则使用下一个，依次循环，priority则根据url的次序从第一个开始。如果不指明，默认是roundrobin。 failovermethod=priority # 当某个软件仓库被配置成 enabled=0 时，yum 在安装或升级软件包时不会将该仓库做为软件包提供源。使用这个选项，可以启用或禁用软件仓库。 # 通过 yum 的 --enablerepo=[repo_name] 和 --disablerepo=[repo_name] 选项，或者通过 PackageKit 的\u0026#34;添加/删除软件\u0026#34;工具，也能够方便地启用和禁用指定的软件仓库 enabled=[1 or 0] 变量\ntext 1 2 3 $releasever，发行版的版本，从[main]部分的distroverpkg获取，如果没有，则根据redhat-release包进行判断。 $arch，cpu体系，如i686,athlon等 $basearch，cpu的基本体系组，如i686和athlon同属i386，alpha和alphaev6同属alpha。 ","permalink":"https://www.161616.top/fpm/","summary":"rpm 与 fpm 软件的安装方式 编译安装：优点是可以定制化安装目录、按需开启功能等，缺点是需要查找并实验出适合的编译参数，诸如MySQL之类的软件编译耗时过长 yum安装：优点是全自动化安装，不需要为依赖问题发愁，缺点是自主性太差，软件的功能、存放位置都已经固定好了，不易变更。 编译源码：根据自己的需求做成 RMP包 ==\u0026gt; 搭建yum仓库 ==\u0026gt; yum安装。结合前两者的优点，暂未发现什么缺点。可能的缺点就是RPM包的通用性差，一般人不会定制RPM包。 RPM概述 RPM全称是Red Hat Package Manager(RedHat包管理器)。几乎所有的Linux发型版本都使用这种形式的软件包管理安装、更新和卸载软件。\nrpm命令有5种基本功能（不包括创建软件包）：安装、卸载、升级、查询和验证。\n关于rpm命令的使用可以用rpm \u0026ndash;help来获得\nrpmbuild rpmbuild是reahat系的原声打包命令，这个命令的使用难点主要在于spec文件编写，一个类似于kickstart的ks.cfg文件。\n作为一个使用工具，种种繁琐，在没有替代品时还能存活。当有了其他简易工具时，他就到了完蛋的时候\nfpm fpm 是将一种类型的包转换成另一种类型\n支持的源类型包\n类型 说明 dir 将目录打包成所需要的类型，可以用于源码编译安装的软件包 rpm 对rpm进行转换 gem 对rubygem包进行转换 python 将python模块打包成相对应的类型 支持目标类型包 rpm 转换为rpm包 deb 转换为deb包 solaris 装环卫solaris包 puppet 转换为puppet模块 fpm安装 fpm是ruby写的，因此系统环境需要ruby，而且ruby版本号大于bshards运行的版本。\nyum安装ruby模块 bash 1 yum install ruby rubygems ruby-devel -y 查看ruby的版本\nbash 1 2 3 4 5 6 7 $ rpm -qa|grep ruby ruby-libs-1.","title":"使用fpm制作rpm包与搭建本地yum源"},{"content":"heartbeat介绍 Heartbeat一款开源提供高可用(Highly-Available)服务的软件，通过heartbeat，可以将资源（IP及程序服务等资源）从一台已经故障的计算机快速转移到另一台正常运转的机器上继续提供服务，一般称之为高可用服务。在实际生产应用场景中，heartbeat的功能和另一个高可用开源软件keepalived有很多相同之处，但在生产中，对应实际的业务应用也是有区别的，例如:keepalived主要是控制IP的漂移，配置、应用简单，而heartbeat则不但可以控制IP漂移，更搜长对资源服务的控制，配置、应用比较复杂\nheartbeat工作原理 通过修改heartbeat软件的配置文件，可以指定哪一台Heartbeat服务器作为主服务器，则另一台将自动成为热备服务器.然后在热备服务器上配里Heartbeat守护程序来监听来自主服务器的心跳消息。如果热备服务器在指定时间内未监听到来自主服务器的心跳，就会启动故障转移程序，并取得主服务器上的相关资源服务的所有权，接替主服务器继续不间断的提供服务，从而达到资源及服务高可用性的目的。\n以上描述的是heartbeat主备的模式，heartbeat还支持主主棋式，即两台服务器互为主备，这时它们之间会相互发送报文来告诉对方自己当前的状态，如果在指定的时间内未受到对方发送的心跳报文，那么，一方就会认为对方失效或者宕机了，这时每个运行正常的主机就会启动自身的资源接管模块来接管运行在对方主机上的资源或者服务，继续为用户提供服务。一般情况下，可以较好的实现一台主机故障后，企业业务仍能够不间断的持续运行。\n注意：所谓的业务不间断，再故障转移期间也是需要切换时间的(例如:停止数据库及存储服务等)，heartbeat的主备高可用的切换时间一般是在5-20秒左右(服务器宕机的切换比人工切换要快)。\n另外，和keepalived高可用软件一样，heartbeat高可用是操作系统级别的，不是服务(软件)级别的，可以通过简单的脚本控制.实现软件级别的高可用。\n高可用服务器切换的常见条件场景：\n主服务器物理宕机(硬件损坏，操作系统故障)。 Heartbeat服务软件本身故障。 两台主备服务器之间心跳连接故障。 服务故障不会导致切换.可以通过服务宕机把heartbeat服务停掉。\n3 heartbeat心跳连接 经过前面的叙述，要部署heartbeat服务，至少需要两台主机来完成。那么，要实现高可用服务，这两台主机之间是如何做到互相通信和互相监侧的呢？\n下面是两台heartbeat主机之间通信的一些常用的可行方法：\n利用串行电缆，即所谓的串口线连接两台服务器(可选)。 一根以太网电缆两网卡直连(可选)。 以太网电缆，通过交换机等网络设备连接(次选)。 如何为高可用服务器端选择心跳通信方案？ 串口线信号不会和以太网网络交集，也不需要单独配置丐地址等信息，因此传输稳定不容易出现问题，使用串口线的缺点是两个服务器对之间的距离距离不能太远，串口线对应服务端的设备为/dev/ttys0。串口线形状如下图所示：\n使用以太网网线(无需特殊交叉线了)直连网卡的方式，配置也比较简单，只需对这两块直连网线的网卡配好独立的IP段地址能够互相通信即可，普通的网线就可以了（推荐）\n使用联网以太网网线和网卡作为心跳线是次选的方案，因为这个链路里增加了交换机设备这样的故障点，且这个线路不是专用心跳线路，容易受以太网其他数据传输的影响，导致心跳报文发送延迟或者无法送达问题。\n选择方案小结：\n和数据相关的业务，要求较高，可以串口和网线直连的方式并用。 Web业务，可以网线直连的方式或局域网通信方式也可。 Heartbeat软件未来发展说明 有关heartbeat分3个分支的说明\n自2.1.4版本后，Linux-HA将Heartbeat分包成三个不同的子项目，并且提供了一个cluster-glue的组件，专用于Local ResourceManager 的管理。即heartbeat + cluster-glue + resouce-agent 三部分。\nHeartbeat hearbeat本身是整个集群的基础（cluster messaging layer），负责维护集群各节点的信息以及它们之前通信。\nCluster Glue 相当于一个中间层，负责调度，可以将heartbeat和crm（pacemaker）联系起来，包括两个摸块:本地资漂管理(Local Resource Manager)LRM和STONITH。\nResource Agents 资源代理层，各种的资源的ocf脚本，这些脚本将被LRM调用从而实现各种资源启动、停止、监控等等。\nPacfrmaker资料\npacemaker介绍：http://baike.baidu.com/view/8635511.htm\n从头开始搭建其群在Fedora上面创建主/主和主/备集群 http://www.clusterlabs.org/doc/zh-CN/Pacemaker/1.1/html-single/Clusters_from_Scratch/index.html\n参考文档：http://www.2cto.com/os/201511/448872.html\n裂脑 什么是裂脑 由于某些原因，导致两台高可用服务器对之间在指定时间内，无法互相检侧到对方心跳而各自启动故障转移功能，取得了资源及服务的所有权，而此时的两台高可用服务器对都还活着并在正常运行，这样就会导致同一个IP或服务在两端同时启动而发生冲突的严重问题，最严重的是两台主机占用同一个VIP地址，当用户写入数据时可能会分别写入到两端，这样可能会导致服务器两端的数据不一致或造成数据丢失，这种情况就被称为裂脑，也有人称其为分区集群或大脑垂直分割，英文为split brain。\n导致裂脑发生的多种原因 一般来说，裂脑的发生，有以下几个原因。 高可用服务器对之间心跳线链路故障。导致无法正常通信。\n心跳线坏了(包括断了，老化)。 网卡及相关驱动坏了，IP配置及冲突问题(网卡直连)。 心跳线间连接的设备故障(网卡及交换机)。 仲裁的机器出问题(仲裁的方案)。 高可用服务器对上开启了如iptables防火墙阻挡了心跳的传输。 高可用服务器对上心跳网卡地址等信息配置不正确，导致发送心跳失败。 其它服务配置不当等原因，如心跳方式不同，心跳广播冲突、软件BUG等。 提示:另外的高可用软件keepalived配置里如果virtual router_id参数，两端配置不一致，也会导致裂脑问题发生。\n防止裂脑发生的8种秘籍 发生裂脑时，对业务的影响是极其严重的，有时甚至是致命的。如:两台高可用服务器对之间发生裂脑，导致互相争用同一IP资源，就如同我们在局域网内常见的IP地址冲突一样，两个机器就会有一个或者两个都不正常，影响用户正常访问服务器。如果是应用在数据库或者存储服务这种极重要的高可用上，那就可能会导致用户发布的数据间断的写在两台不同服务器上，最终数据恢复极困难或难以恢复(当然，有NAS等公共存储的硬件也许会好一些）。\n实际生产环境中，我们可以从以下几个方面来防止裂脑问题的发生。\n同时使用串行电缆和以太网电缆连接，同时用两条心跳线路，这样一条线路坏了，另一个还是好的，依然能传送心跳消息。(网卡设备和网线设备)。\n当检测到裂脑时强行关闭一个心跳节点。（这个功能需要特殊设备支持，如stonith、fence）相当于程序上北街店发现心跳线故障，发送关机命令到主节点。\n做好对裂脑的监控报警（如邮件及手机短信等，值班），在问题发生时人为第一时间介入仲裁，降低损失。百度的报警监控有上行和下行。和人工交互的过程。当然，在实施高可用方案时，要根据业务需求确定是否能容忍这样的损失。对于一般的网站常规业务，这个损失是可控的。\n启用磁盘锁.正在服务一方锁住共享磁盘，“裂脑”发生时让对方完全“抢不走”共享磁盘资源。但使用锁磁盘也会有一个不小的问题，如果占用共享盘的一方不主动\u0026quot;解锁\u0026quot;，另一方就永远得不到共享磁盘.现实中假如服务节点突然死机或崩演，就不可能执行解锁命令。后备节点也就接管不了共享资源和应用服务。于是有人在HA中设计了“智能”锁。即，正在服务的一方只在发现心跳线全部断开（察觉觉不到对端）时才启用磁盘锁。平时不上锁。此功能适合共享场景。\n报警报在服务器接管之前，给人员你处理留够时间。1分钟内报警了，但是服务器此时没有接管，而是5分钟接管。接管的时间较长，数据不会丢，导致用户无法写数据。\n报警后不自动服务器接管，而是由人为人员控制管理。\n增加仲裁机制，确定谁该获得资派。这又有几个参考的思路：\n加一个仲截机制。例如设且参考IP(如网关IP).当心跳线完全断开时，2个节点都各自Ping一下参考IP，不通则表明断点就出在本端。不仅心跳线、还有对外服务的本地网络链路断了，这样就主动放弃竞争.让能够Ping通参考IP的一端去接管服务。Ping不通参考IP的一方可以自我重启，以彻底释放有可能还占用着的那些共享资源(heanbeat也有此功能)。 通过第三方软件仲裁谁该获得资源，这个在阿里的集团有类似的软件应用。 小结:如何开发程序判断裂脑：\n简单判断，只要备节点出现VIP就是报普(a.主机宕机了，各机接管了。b.主机没宕，裂脑了)，不管哪个情况，人工查看。 严谨判断，备机出现VIP，并且主机及服务还活着，裂脑了（依赖报警）。 fence设备和仲裁机制 先说下我以前做项目的环境，基本都是RHEL/CENTOS (以下简称RHEL),用的是RHCS集群套件，这个集群套件其实只是很多个软件整合在一起，在其他linux发行版里也有，只是比较零散，在RHEL中RHCS集群套件被做成了一个group，可以通过yum group install来安装集群套件，当然一个个rpm包安装也没问题。这个集群套件里还包含了一个LB的软件，就是LVS. Heartbeat和RHCS其实是差不多的东西，都是靠心跳来检测健康状态的，所以下面说的内容在Heartbeat应该也是通用的。\n先说fence，fence只是HA集群环境下的术语，在硬件领域，fence设备其实就是一个只能电源管理设备(IPMI)，也叫做Intelligent PowerManagement Interface，如果你去和服务器代理商说fence，他们一定不知道是什么东西（原厂可能知道），你得和他们说是队们一定不知道是什么东西(原厂可能知道)，你得和他们说是智能电源管理设备或远程管理卡，他们就理解了，老师在视频里说这是一个特殊的插线板，这是fence设备的一种，叫做外部fence，还有一种叫内部fence.是插在服务器里的，不管是内部还是外部fence，这些设备都是带有以太网口的，用来在HA切换触发时通过网络重启提供资源服务的服务器。\n至于外部fence设备，我只用过APC(著名的UPS电源生产商）的PowerSwitch, 这是一个带以太网口的电源插座，征每个插口都对应一个ID号，用来在命令中指定对哪一个ID号上的电源进行切断或者重启，为什么我当时会接触到外部fence设备呢，是因为当时做了一个医院的挂号系统，用的是IBM的小机装的RHEL5.x，因为小机上没有支持的内部fence，只有使用外部fence来实现了，至于为什么在小机上装RHEL这种2B方案（AIX也有成熟的HA解决方案)，得牵涉到商务上的忽悠，给客户返点各种营销上的潜规则，我才得以在实战中接触到这些东西。\n在RHCS下有仲裁机制是一个叫做仲裁盘的东西，他是通过额外的存储来实现的，比如SAN，通过mkgdisk命令来制作的一个特殊块设备，这个设备做什么用呢?默认情况下双节点的HA架构，主从服务器的投票数都是1，双方使用的ping网关的方式来将自己的存活状态写入仲裁盘内，一旦节点心跳发生问题并且仲裁盘没有收到节点的存活信息，则启动fence设备来重启/关闭故障节点。这种方式可以有效的防止裂脑情况。缺点就是判断时间会不普通的HA时间长，这而要配合业务的需求，当时我们用RHCS+ORALCE+SAN存储来实现全国中信银行的帐务集中系统，数据必须保证完全一致，我们在实际测试中从故障到切换到备机接管能够提供服务的时间在2分钟以内。当然随着数据库增大，可能在启动Oracle数据库实例的时候会有所增加。以上就是我对fence设备和仲裁机制的个人补充，欢迎老师和大家拍砖。\nstonith 介绍 stonith是“shoot the other node in the head”的首字母简写，它是Heartbeat软件包的一个组件，它允许使用一个远程或“智能的”连接到健康服务器的电源设备自动重启失效服务器的电源，stonith设备可以关闭电源并响应软件命令，运行Heartbeat的服务器可以通过串口线或网线向stonith设备发送命令，它控制高可用服务器对中其他服务器的电力供应，换句话说，主服务器可以复位备用服务器的电源，备用服务器也可以复位主服务器的电源。\n注意:尽管理论上连接到远程或“智能的”循环电源系统的电力设备的数量是没有限制的，但大多数stonith实现只使用服务器，因为双服务器stonith配置是最简单的，最容易理解，它能够长时间运行且不系统的可靠性和高可用性。\nStonith事件触发工作步骤 当备用服务器听不到心跳时Stontih事件开始。 注意:这并不一定意味着主服务器没有发送心跳，心跳可能有多种原因而没有抵达备用服务器，这就是为什么建议至少需要两条物理路径传输心跳以避免出现假象的原因了。 2. 备用服务器发出一个Stonith复位命令到Stonith设备。\nStonith设备关闭主服务器的电力供应。 一经切断主服务器的电源，它就不能再访问集群资源，也不能再为客户端提供资源，保证客户端计算机不能访问主服务器上的资源，排除可能发生的头脑分裂状态。\n4. 然后备用服务器获得主服务器的资源，Heartbeat用start参数运行资源脚本，并执行arp欺骗广播以便客户端计算机发送它们的请求到它的网络接口上。 详情可参考Heartbeat高可用Stonith配置201503。\nHeartbeat消息类型 Heartbeat高可用软件在工作过程中，一般来说，有三种消息类型，具体为：心跳消息、集群转换消息、重传请求\n心跳消息 心跳消息为约150字节的数据包，可能为单播、广播或多播的方式，控制心跳频率及出现故障要等待多久进行故障转换。\n集群转换消息 ip-request和ip-request-respy\n当主服务器恢复在线状态时，通过ip-request 消息要求备机释放主服务器失败时备服务器取得的资源，然后备份服务器关闭释放主服务器失败时取得的资源及服务。\n备服务器释放主服务器失败时取得的资源及服务后，就会通过ip-request-resp消息通知主服务器它不在拥有该资源及服务，主服务器收到来自备节点的ip-request-resp消息通知后，启动失败时释放的资源及服务，并开始提供正常的访问服务。\n提示:以上心跳控制消息都使用UDP协议发送到/etc/ha.d/ha.cf文件指定的任意端口，或指定的多播地址，如果使用多播默认端口为694\n跳实现方式及查看心跳消息 参考：http://blog.chinaunix.net/uid-7921481-id-1617030.html\nHeartbeat IP地址接管和故障转移 Heartbeat是通过IP地址接管和ARP广播进行故陈转移的。\nARP广播:在主服务器故阵时，备用节点接管资源后.会立即强制更新所有客户端本地的ARP表(即清除客户端本地缓存的失败取务器的VIP地址和mac地址的解析记录)。确保客户端和新的主服务器对话。\nVIP/IP别名/辅助IP real IP 真实IP，又彼称为管理IP，一般是配置在物理网卡上的实际IP，这可以看作你本人的姓名，如:张三。在负载均衡及高可用环境中，管理IP是不对外提供用户访问服务的，而仅作为管理服务器用，如SSH可以通过这个管理IP连接服务器。\nVIP 虚拟IP即VIP，实际上救是heartbeat临时绑定在物理网卡上的别名IP (heartbeat3以上也采用了辅助IP)。如eth0:x，x为0-255的任惫数字.你可以在一块网卡上绑定多个别名.这个VIP可以看作是你上网的QQ网名、呢称、外号等。在实际生产环境中，需要把DNS配置中把网站域名地址解析到这个VIP地址。由这个VIP对用户提供服务。\n这样做的好处就是当提供服务的服务器宕机以后，在接管的服务器上直接会自动配置上同样的VIP提供服务。如果是使用管理IP的话，来回迁移就难以做到，而且，迁移走了。我们就只能去机房连接服务器了。VIP的实质就是确保两台服务器各有一个管理IP不动，就是随时可以连上机器，然后，增加绑定其他的VIP，这样就算VIP转移走了，也不至于服务器本身连不上，因为还有管理IP呢。\nLinux系统给网卡配置VIP的方法常见的有两种，即别名和辅助IP###\n别名IP（alias ip） ip alias 是由 Linux 系统的 ifconfig 命令来创建和维护的，别名IP就是在网卡设备上绑定的第二个及以上的IP，例如： 手工配置别名VIP的方法\nbash 1 2 3 ifconfig eth0:1 192.168.1.1 netmask 255.255.255.0 up ifconfig eth0:1 192.168.1.1/24 up #\u0026lt;==up 关键字可省略默认为up ifconfig eth0:1 192.168.1.1/24 down 让别名IP永久生效 写入到网卡配置文件可以让别名IP永久生效，名字可以为ifcfg-eth0:x，x为0-255的任意数字，IP等内容格式和ifcfg-eth0一致，或者将命令写入/etc/rc.local\n注意：别名IP在centos 7中被遗弃，用辅助IP替代。\n辅助IP（secondary ip address） 辅助IP则是由Linux系统的ip命令创建和维护的，ip addr add创建的辅助IP，不能通过ifconfig查看，但是通过ifconfig创建的别名IP却可以在ip addr show 命令查看。\nbash 1 2 ip addr add 192.168.3.3/24 dev eth0 ip addr del 192.168.3.3/24 dev eth0 heartbeat3 版本起，不在使用别名，而是使用辅助IP提供服务，而 keepalived 软件一直都是使用的辅助IP技术。\n部署heartbeat heartbeat服务主机资源规划 名称 接口 IP 用途 MATER eth0 192.168.2.22 外网管理IP，用于WAN数据转发。 eth1 10.0.0.1 内网管理IP，用于LAN数据转发。 eth2 10.1.0.1 用于服务器心跳连接（直连）。 VIP 172.168.1.1 用于提供应用程序A挂载服务。 BACKUP eth0 192.168.2.82 外网管理IP，用于WAN数据转发。 eth1 10.0.0.3 内网管理IP，用于LAN数据转发。 eth2 10.1.0.3 用于服务器心跳连接（直连）。 VIP 10.1.0.3 安装heartbeat http://blog.csdn.net/celeste7777/article/details/47808519 http://www.cnblogs.com/zhanjindong/p/3618055.html#anzhuang http://wangzhijian.blog.51cto.com/6427016/1708694?utm_source=tuicool\u0026utm_medium=referral\nbash 1 rpm -ivh https://mirrors.aliyun.com/epel/epel-release-latest-6.noarch.rpm 注：centos 7 epel源内没有heartbeat。\nyum安装heartbeat bash 1 yum install heartbeat -y 编译安装heartbeat heartbeat3.x版本把安装包分成了4个部分，分别是：Cluster Glue、Resource Agents、heartbeat和pacemaker，所以要分别安装。\n安装依赖\nbash 1 2 3 4 5 6 7 8 9 10 11 12 yum install gcc \\ gcc-c++ \\ autoconf \\ automake \\ libtool \\ glib2-devel \\ libxml2-devel \\ bzip2 bzip2-devel \\ e2fsprogs-devel \\ libxslt-devel \\ libtool-ltdl-devel \\ asciidoc -y 创建用户\nbash 1 useradd hab -s /sbin/nologin -M 安装Cluster Glue\nbash 1 2 3 4 5 6 7 ./autogen ./configure \\ --prefix=/app/heartbeat-3.0.6 \\ --with-daemon-user=hab \\ --with-daemon-group=hab \\ --enable-fatal-warnings=no LIBS=\u0026#39;/lib64/libuuid.so.1\u0026#39; #\u0026lt;==指定uuid库文件 编译Resource Agents\nbash 1 2 3 4 5 6 7 ./autogen ./configure \\ --prefix=/app/heartbeat-3.0.6 \\ --with-daemon-user=hab \\ --with-daemon-group=hab \\ --enable-fatal-warnings=no LIBS=\u0026#39;/lib64/libuuid.so.1\u0026#39; #\u0026lt;==指定uuid库文件 编译heartbeat\nbash 1 2 3 4 5 6 7 export CFLAGS=\u0026#34;$CFLAGS -I/app/heartbeat-3.0.6/include -L/app/heartbeat-3.0.6/lib\u0026#34; ./configure \\ --prefix=/app/heartbeat-3.0.6 \\ --with-daemon-user=hab \\ --with-daemon-group=hab \\ --enable-fatal-warnings=no LIBS=\u0026#39;/lib64/libuuid.so.1\u0026#39; heartbeat说明 sh 1 2 3 /etc/init.d/heartbeat #\u0026lt;== 启动脚本 /etc/ha.d #\u0026lt;==配置文件目录 /etc/ha.d/resource.d #\u0026lt;==控制资源的脚本，被HA调用。也可以放到/etc/init.d下 提示:把脚本放到上面两个路径其中任意一个下面，然后在heartbeat的haresourc配置文件中配置脚本名称就能调用到该脚本，进而控制资源和服务的启动和关闭。\nheartbeat配置文件 heartbeat的默认配置文件目录为/etc/ha.d。heartbeat常用的配置文件有三个，分别为ha.c，authkey，haresource，如果你细看，可以发现名字信息就如其实际功能。\n配置名称 作用 备注 ha.cf heartbeat参数配置文件 在这里配置heartbeat的一些基本参数 authkey heartbeat认证文件 高可用服务器对之间根据对端authkey，对对端进行认证。 haresource heartbeat资源配置文件 如配置启动IP资源及脚本程序，服务等，调用/etc/ha.d/resource.d下面配置 配置服务器心跳连接 eth2 10.0.0.1和eth2 10.1.0.3两块网卡之间是通过普通网线直连连接的，即不通过交换机，直接将两块网卡通过网线连在一起，用于做心跳检测或传输数据等。\n高可用服务器对上的Heartbeat软件会利用这条心跳找来性查对端的权器足否存活，进而决定是 否做故障漂移，资源切换，来保证业务的连续性。\n如条件允许，以上连接可同时使用，来加大保险系数防止裂肺问砚发生。在我的生产环境中，常使用前两者之一或者结合使用。本文的环节为一根以太网网线两网卡直连，也是近几年，在生产环境中选用的。选用原因:简单、容易部署、效果也不错。做一件事情有多种选择.往往到及后娜泛性价比方面的考虑。如:部署简单，维护方便，效果不是最好的，但也无不错的。这样就好了。并不是做什么都是最好的。实际工作中往往最好的是最不现实的。\n配置ha.cf文件 heartbeat配置文件模板路径在\nbash 1 /usr/share/doc/heartbeat-3.0.4/ ha.cf文件详细说明\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 debugfile /var/log/ha-debug #\u0026lt;== heartbeat的调试日志存放位置 logfile /var/log/ha-log #\u0026lt;== heartbeat的日志存放位置 logfacility local0 #\u0026lt;== 在syslog服务中配置通过local0设备接受日志 keepalive 2 #\u0026lt;== 指定心跳间隔时间为2秒（即每两秒中在eth2上发一次广播） deadtime 30 #\u0026lt;== 指定若备用节点在30秒内没有收到主节点的心跳信号，则立即接管主节点的服务资源 warntime 10 #\u0026lt;== 指定心跳延迟的时间为10秒。当10秒种内备份节点不能接收到主节点的心跳信号时，就会往日志中写入一个警告日志，但此时不会切换服务。 initdead 120 #\u0026lt;== 指定在HEARTBEAT首次运行后，需要等待120秒才启动主服务器的任何资源。该选项用于解决这种情况产生的时间间隔。取值至少为deadtime的两倍。单机启动时会遇到vip绑定很慢，为正常现象。该值设置的长的原因 bcast eth2 #\u0026lt;== 指明心跳使用以太网广播方式在eth2接口上进行广播。如使用两个实际网络来传送心跳，则 bcast eth1 eth2 mcast eth2 225.0.0.1 694 1 0 #\u0026lt;== 设置广播通信使用的端口，694为默认使用的端口号 auto_failback on #\u0026lt;== 用来定义当主节点恢复后，是否将服务自动切回. node data-1-1 #\u0026lt;== 主节点主机名，通过命令 uname -n查看 node data-1-3 #\u0026lt;== 备用节点主机名，可以通过命令 uname-n 查看 crm no\t#\u0026lt;== 是否开启cluster resource manager（集群资源管理）功能 还可以查/usr/share/doc/heanbeat-3.0.4/下的ha.cf.来了解更详细的参数信息\n配置authkey文件 软件提供的authkey默认文件并不是很复杂 http://wangzhijian.blog.51cto.com/6427016/1708694\nbash 1 2 3 4 Authentication file. Must be mode 600 #\u0026lt;==此处提到了authkey文件权限必须为600 Available methods: crc sha1, md5. Crc doesn\u0026#39;t need/want a key. #\u0026lt;==可以设置的认证方法 sha1 is believed to be the \u0026#34;best\u0026#34;, md5 next best. crc adds no security, except from packet corruption bash 1 2 3 4 5 6 7 $ echo 111|sha1sum 63bea2e3b0c7cd2d1f98bc5b7a9951eafcfead0f - cat \u0026gt;authkeys \u0026lt;\u0026lt;EOF auth 1 1 sha1 63bea2e3b0c7cd2d1f98bc5b7a9951eafcfead0f EOF 配置haresource文件 编辑配置heartbeat资源文件/etc/ha.d/haresources 生产环境的配置如下：\nbash 1 ha-b IPaddr::10.1.0.2/24/eth0 配置haresource说明 ha-b为主机名，表示初始状态会在ha-b绑定IP 10.0.0.17，IPaddr为heartbeat配置lP的默认脚本，其后的lP等都是脚本的参数。10.0.0.17/24/eth0为集群对外服务的VIP，初始启动在ha-b上，24为子网掩码，eth0为ip绑定的实际物理网卡，为heartbeat提供对外服务的通信接口。\ntext 1 ha-b drbddis:data Filesystem::/dev/drbd0::/data::ext3 rsdata IPaddr:10.0.0.1/24/eth0 设置drbb，drbddisk::data 挂载/data到/dev/drbdO Filesystem::/dev/drbd0::/data/ext3 NFS/MFS服务配置 rsdata (不要开机启动) 启动 VIP IPaddr::10.0.0.3/24/eth0 以上设置休验最好\n一旦heartbeat无法控制资源的启动，heartbeat会采取极端的措施.例如重启系统.来释放没法管理的资源。因此，被管理的资源必须不能开机启动。可以写脚本来张制控制资源的处理，来防止heartbeat重启。\n安装错误 bash 1 2 Oct 26 10:07:18 node1 heartbeat: [2063]: ERROR: Illegal directive [ucast] in /usr/local/heartbeat/etc/ha.d//ha.cf Oct 26 10:07:18 node1 heartbeat: [2063]: ERROR: Illegal directive [ping] in /usr/local/heartbeat/etc/ha.d//ha.cf 解决方法：建立plugin软链接:\nbash 1 ln -svf /app/heartbeat/lib64/heartbeat/plugins/* /app/heartbeat/lib/heartbeat/plugins/ heartbeat高可用实战 有关heartbeat调用资源的生产场景应用 在实际工作中有两种常见方法实现高可用问题：\nheartbeat可以仅控制vip资源的漂移，不负责服务资源的启动及停止，本节的httpd服务就可以这样做。适合web服务 heartbeat即控制vip资源的漂移，同时又控制服务资源启动及停止，本节的httpd服务例子既是ip和服务要切换都切换. ←适合数据服务(数据库和存储)只能一端写。 VIP正常，httpd服务宕了.这个时候不会做高可用切换.写个简单的脚本定时或守护进程判断httpd服务，如果有问题，则停止heartbeat，主动使其上的业务到另一台。\n两端服务能同时起，那最好不要交给heartbeat，对于某些服务，不能两端同时起，heartbeat可以控制服务启动。\nha高可用httpd案例结论 日志很重要。不管是heartbeat，所有服务的日志都很重要。有问题时多查看相关日志。 httpd的高可用还可以是两边都处于启动状态，即httpd不需要交给ha启动，而是默认状态就先启动运行。 这个httpd高可用性配置在生产环境中用的很少，但它确是生产环境需求的一个初级模型。 如:heanbeat+drbd+mysql实现数据库高可用性配置，heanbeat+active/active+nfs/mfs实现存储高可用性配置。\nheartbeat和keepalived应用场景区别 对于一般的web, db、负载均衡(nginx,haproxy )等等heartbeat和keepalived都可以实现。 lvs负载均衡最好和keepalived结合，虽然heartbeat也可以调用带有ipvsadm命令的脚本来启动和停止lvs负载均衡，但是heartbeat本身并没有对下面节点rs的健康检查功能，heartbeat的这个缺陷可以通过ldircetord插聆来弥补，所以，当你搜索heartbeat+lvs+ldirectord可以有lvs的别解决方案)。 需要要数据同步(配合drbd )的高可用业务最好用heartbeat，例如:mysgl双主多从，NFS/MFS 存储，他们的特点是需要数据同步，这样的业务最好用heartbeat.因为hearbeat自带了drbd的脚本，可以利用强大的drbd同步软件配合实现同步。如果你解决了数据同步可以不用drbd,例如:共享存储或者inotify+rsync (sersync+rsync)，那么就可以考虑keepalived。 运维人员对哪个更热悉就用哪个，其实，就是你要能控制维护你部署的服务。目前，总体网友们更倾向于使用leepalived软件的多一些。\nheartbeat服务生产环境下维护要点 修改配置文件要点：在我们每天的实战运维工作中，当有新项目上线或者VIP更改需求时，可能会进行添加修改服务VIP的操作，那么，下面我们就以heartbeat+haproxy/nginx高可用负载均衡为例给大家讲解下生产环境下的维护方法。\n所有配置放到SVN，更改后提交SVN，对比。推送到正式环境!\n常见的情况就是修改配置文件，我们知道配置文件有3个：ha.cf authkey haresourece。在修改配置前执行/etc/init.d/heartbeat stop或/app/heartbeat/share/heartbeat/hb_staudby(编译安装)，/usr/lib/heartbeat/hb_staudby(此命令最好)\n","permalink":"https://www.161616.top/heartbeat/","summary":"heartbeat介绍 Heartbeat一款开源提供高可用(Highly-Available)服务的软件，通过heartbeat，可以将资源（IP及程序服务等资源）从一台已经故障的计算机快速转移到另一台正常运转的机器上继续提供服务，一般称之为高可用服务。在实际生产应用场景中，heartbeat的功能和另一个高可用开源软件keepalived有很多相同之处，但在生产中，对应实际的业务应用也是有区别的，例如:keepalived主要是控制IP的漂移，配置、应用简单，而heartbeat则不但可以控制IP漂移，更搜长对资源服务的控制，配置、应用比较复杂\nheartbeat工作原理 通过修改heartbeat软件的配置文件，可以指定哪一台Heartbeat服务器作为主服务器，则另一台将自动成为热备服务器.然后在热备服务器上配里Heartbeat守护程序来监听来自主服务器的心跳消息。如果热备服务器在指定时间内未监听到来自主服务器的心跳，就会启动故障转移程序，并取得主服务器上的相关资源服务的所有权，接替主服务器继续不间断的提供服务，从而达到资源及服务高可用性的目的。\n以上描述的是heartbeat主备的模式，heartbeat还支持主主棋式，即两台服务器互为主备，这时它们之间会相互发送报文来告诉对方自己当前的状态，如果在指定的时间内未受到对方发送的心跳报文，那么，一方就会认为对方失效或者宕机了，这时每个运行正常的主机就会启动自身的资源接管模块来接管运行在对方主机上的资源或者服务，继续为用户提供服务。一般情况下，可以较好的实现一台主机故障后，企业业务仍能够不间断的持续运行。\n注意：所谓的业务不间断，再故障转移期间也是需要切换时间的(例如:停止数据库及存储服务等)，heartbeat的主备高可用的切换时间一般是在5-20秒左右(服务器宕机的切换比人工切换要快)。\n另外，和keepalived高可用软件一样，heartbeat高可用是操作系统级别的，不是服务(软件)级别的，可以通过简单的脚本控制.实现软件级别的高可用。\n高可用服务器切换的常见条件场景：\n主服务器物理宕机(硬件损坏，操作系统故障)。 Heartbeat服务软件本身故障。 两台主备服务器之间心跳连接故障。 服务故障不会导致切换.可以通过服务宕机把heartbeat服务停掉。\n3 heartbeat心跳连接 经过前面的叙述，要部署heartbeat服务，至少需要两台主机来完成。那么，要实现高可用服务，这两台主机之间是如何做到互相通信和互相监侧的呢？\n下面是两台heartbeat主机之间通信的一些常用的可行方法：\n利用串行电缆，即所谓的串口线连接两台服务器(可选)。 一根以太网电缆两网卡直连(可选)。 以太网电缆，通过交换机等网络设备连接(次选)。 如何为高可用服务器端选择心跳通信方案？ 串口线信号不会和以太网网络交集，也不需要单独配置丐地址等信息，因此传输稳定不容易出现问题，使用串口线的缺点是两个服务器对之间的距离距离不能太远，串口线对应服务端的设备为/dev/ttys0。串口线形状如下图所示：\n使用以太网网线(无需特殊交叉线了)直连网卡的方式，配置也比较简单，只需对这两块直连网线的网卡配好独立的IP段地址能够互相通信即可，普通的网线就可以了（推荐）\n使用联网以太网网线和网卡作为心跳线是次选的方案，因为这个链路里增加了交换机设备这样的故障点，且这个线路不是专用心跳线路，容易受以太网其他数据传输的影响，导致心跳报文发送延迟或者无法送达问题。\n选择方案小结：\n和数据相关的业务，要求较高，可以串口和网线直连的方式并用。 Web业务，可以网线直连的方式或局域网通信方式也可。 Heartbeat软件未来发展说明 有关heartbeat分3个分支的说明\n自2.1.4版本后，Linux-HA将Heartbeat分包成三个不同的子项目，并且提供了一个cluster-glue的组件，专用于Local ResourceManager 的管理。即heartbeat + cluster-glue + resouce-agent 三部分。\nHeartbeat hearbeat本身是整个集群的基础（cluster messaging layer），负责维护集群各节点的信息以及它们之前通信。\nCluster Glue 相当于一个中间层，负责调度，可以将heartbeat和crm（pacemaker）联系起来，包括两个摸块:本地资漂管理(Local Resource Manager)LRM和STONITH。\nResource Agents 资源代理层，各种的资源的ocf脚本，这些脚本将被LRM调用从而实现各种资源启动、停止、监控等等。\nPacfrmaker资料\npacemaker介绍：http://baike.baidu.com/view/8635511.htm\n从头开始搭建其群在Fedora上面创建主/主和主/备集群 http://www.clusterlabs.org/doc/zh-CN/Pacemaker/1.1/html-single/Clusters_from_Scratch/index.html\n参考文档：http://www.2cto.com/os/201511/448872.html\n裂脑 什么是裂脑 由于某些原因，导致两台高可用服务器对之间在指定时间内，无法互相检侧到对方心跳而各自启动故障转移功能，取得了资源及服务的所有权，而此时的两台高可用服务器对都还活着并在正常运行，这样就会导致同一个IP或服务在两端同时启动而发生冲突的严重问题，最严重的是两台主机占用同一个VIP地址，当用户写入数据时可能会分别写入到两端，这样可能会导致服务器两端的数据不一致或造成数据丢失，这种情况就被称为裂脑，也有人称其为分区集群或大脑垂直分割，英文为split brain。\n导致裂脑发生的多种原因 一般来说，裂脑的发生，有以下几个原因。 高可用服务器对之间心跳线链路故障。导致无法正常通信。\n心跳线坏了(包括断了，老化)。 网卡及相关驱动坏了，IP配置及冲突问题(网卡直连)。 心跳线间连接的设备故障(网卡及交换机)。 仲裁的机器出问题(仲裁的方案)。 高可用服务器对上开启了如iptables防火墙阻挡了心跳的传输。 高可用服务器对上心跳网卡地址等信息配置不正确，导致发送心跳失败。 其它服务配置不当等原因，如心跳方式不同，心跳广播冲突、软件BUG等。 提示:另外的高可用软件keepalived配置里如果virtual router_id参数，两端配置不一致，也会导致裂脑问题发生。\n防止裂脑发生的8种秘籍 发生裂脑时，对业务的影响是极其严重的，有时甚至是致命的。如:两台高可用服务器对之间发生裂脑，导致互相争用同一IP资源，就如同我们在局域网内常见的IP地址冲突一样，两个机器就会有一个或者两个都不正常，影响用户正常访问服务器。如果是应用在数据库或者存储服务这种极重要的高可用上，那就可能会导致用户发布的数据间断的写在两台不同服务器上，最终数据恢复极困难或难以恢复(当然，有NAS等公共存储的硬件也许会好一些）。","title":"heartbeat权威指南"},{"content":"为Redis客户端外部设置连接密码 因为redis速度相当快，所以在一台比较好的服务器下，一个外部的用户可在一秒钟进行上万次的密码尝试，这意味着你需要指定非常非常强大的密码来防止暴力破解。\n修改配置文件 bash 1 requirepass 123@1 重启服务后登录客户端提示没有验证\nbash 1 2 3 $ redis-cli 127.0.0.1:6379\u0026gt; keys * (error) NOAUTH Authentication required. 验证成功后，可以正常操作\nbash 1 2 3 4 5 127.0.0.1:6379\u0026gt; auth 123@1 OK 127.0.0.1:6379\u0026gt; keys * 1) \u0026#34;test-durable-1\u0026#34; 2) \u0026#34;test-durable\u0026#34; 命令行临时生效 在命令行设置后，redis在下次重启前，每次登录都需要验证密码\nbash 1 2 3 4 5 6 127.0.0.1:6379\u0026gt; CONFIG set requirepass 123@1 OK 127.0.0.1:6379\u0026gt; quit $ redis-cli 127.0.0.1:6379\u0026gt; keys * (error) NOAUTH Authentication required. 注意：配置Redis复制的时候如果主数据库设置了密码，需要在从数据库的配置文件中通过masterauth参数设置主数据库的密码，以使从数据库连接主数据库时自动使用AUTH命令认证。\n通过mysql命令行指定密码方式登录Redis客户端\nbash 1 2 3 4 $ redis-cli -a 123@1 127.0.0.1:6379\u0026gt; keys * 1) \u0026#34;test-durable-1\u0026#34; 2) \u0026#34;test-durable\u0026#34; 危险命令重命名 bash 1 2 3 rename-command flushall abc rename-command get eee rename-command FLUSHALL \u0026#34;\u0026#34; #←禁用FLUSHALL命令 bash 1 2 3 4 127.0.0.1:6379\u0026gt; get test-durable (error) ERR unknown command \u0026#39;get\u0026#39; 127.0.0.1:6379\u0026gt; eee test-durable \u0026#34;test1\u0026#34; 绑定只能本机连接 Redis的默认配置会接受来自任何地址发送来的请求，即在任何一个拥有公网IP的服务器上启动Redis服务器，都可以被外界直接访问到。要更改这一设置，在配置文件中修改bind参数，如只允许本机应用连接Redis.\nsh 1 bind 127.0.0.1 ","permalink":"https://www.161616.top/redis-security/","summary":"为Redis客户端外部设置连接密码 因为redis速度相当快，所以在一台比较好的服务器下，一个外部的用户可在一秒钟进行上万次的密码尝试，这意味着你需要指定非常非常强大的密码来防止暴力破解。\n修改配置文件 bash 1 requirepass 123@1 重启服务后登录客户端提示没有验证\nbash 1 2 3 $ redis-cli 127.0.0.1:6379\u0026gt; keys * (error) NOAUTH Authentication required. 验证成功后，可以正常操作\nbash 1 2 3 4 5 127.0.0.1:6379\u0026gt; auth 123@1 OK 127.0.0.1:6379\u0026gt; keys * 1) \u0026#34;test-durable-1\u0026#34; 2) \u0026#34;test-durable\u0026#34; 命令行临时生效 在命令行设置后，redis在下次重启前，每次登录都需要验证密码\nbash 1 2 3 4 5 6 127.0.0.1:6379\u0026gt; CONFIG set requirepass 123@1 OK 127.0.0.1:6379\u0026gt; quit $ redis-cli 127.0.0.1:6379\u0026gt; keys * (error) NOAUTH Authentication required. 注意：配置Redis复制的时候如果主数据库设置了密码，需要在从数据库的配置文件中通过masterauth参数设置主数据库的密码，以使从数据库连接主数据库时自动使用AUTH命令认证。\n通过mysql命令行指定密码方式登录Redis客户端\nbash 1 2 3 4 $ redis-cli -a 123@1 127.","title":"redis安全相关配置"},{"content":"Remote Dictonary Server(Redis)是一个基于key-value键值对的持久化数据库存储系统。redis和大名鼎鼎的Memcached缓存服务很像,但是redis支持的数据存储类型更丰富,包括string(字符串)、list(链表)、set(集合)和zset(有序集合)、Hash等。\n这些数据类型都支持push/pop,add/remove及取交集、并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上，redis支持各种不同方式的排序。与memcached缓存服务一样，为了保证效率数据都是缓存在内存中提供服务。和memcached不同的是，redis持久化缓存服务还会周期性的把更新的数据写入到磁盘以及把修改的操作记录追加到文件里记录下来，比memcached更有优势的是，redis还支持master-slave(主从)同步,这点很类似关系型数据库MySQL。\nRedis是一个开源的、使用C语言编写、３万多行代码、支持网络、可基于内存亦可久化的日志型、Key-Value数据库，并提供多种语言的API从2010年3月15日起，Redis开发工作由VMware主持。\nRedis的出现，再一定程度上弥补了memcached这类key-value内存缓存服务的不足,在部分场合可以对关系数据库起到很好的补充作用.redis提供了Python, Ruby, Erlang, PHP客户端，使用很方便。redis官方文档如下：http://www.redis.io/documentation\nRedis的优点 与memcached不用，redis可以持久化存储数据。 性能很高：Redis能支持超过10w/秒的读写频率。 丰富的数据类型：redis支持二进制的Strings, Lists, Hashes, Sets及sorted sets等数据类型操作。 原子：Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。 丰富的特性：Redis还支持publish/subscribe(发布/订阅)，通知，key过期等等特性。 redis支持异步主从复制。 Redis的应用场景 传统的MySQL+Memcached的网站架构遇到的问题：\nMySQL数据库实际上是适合进行海量数据存储的,加上通过Memcached将热点数据 存放到到内存cache里,达到加速数据访问的目的,绝大部分公司都曾经使用过这样的架构,但随着业务数据量的不断增加,和访问量的增长,很多问题就会暴漏出来：\n需要不断的对MySQL进行拆库拆表Memcached也需不断跟着扩容,扩容和维护工作占据大量开发运维时间。 Memcached与MySQL数据库数据一致性问题是个老大难。 Memcached数据命中率低或down机,会导致大量访问直接穿透到数据库,导致MySQL无法支撑访问。 跨机房cache同步一致性问题。 redis在微博中的应用 计数器：微博（评论、转发、阅读、赞等） 用户（粉丝、关注、收藏、双向关注等） redis在短信中的应用 发送短信后存入redis中60秒过期。\nredis的最佳应用场景 Redis最佳试用场景是全部数据in-memory。 Redis更多场景是作为Memcached的替代品来使用。 当需要除key/value之外的更多数据类型支持时,使用Redis更合适。 数据比较重要，对数据一致性有一定要求的业务。 当存储的数据不能被剔除时,使用Redis更合适。 更多 Redis作者谈Redis应用场景 http://blog.nosglfan.com/html/2235.html 使用redis bitmap进行活跃用户统计 http://blog.nosqlfun.com/html/3501.html 计数、cache服务、展示最近、最热、点击率最高、活跃度最高等等条件的top list、用户最近访问记录表、relation list/Message Queue、粉丝列表\nKey-Value Store更加注重对海量数据存取的性能、分布式、扩展性支持上，并不需要传统关系数据库的一些特征。例如：Schema事务、完整SQL查询支持等等，因此在布式环境下的性能相对于传统的关系数据库有较大的提升。\nredis的生产经验教训 要进行Master-slave主从同步配置，在出现服务故障时可以切换。 在master禁用数据据持久化只需在slave上配置数据持久化。 物理内存+虚拟内存不足，这个时候dump一直死着，时间久了机器挂掉。这个情就是灾难。 当Redis物理内存使用超过内存总容量的3/5时就会开始比较危险了，就开始做swap，内存碎片大！ 当达到最大内存时，会清空带有过期时间的如 redis与DB同步写的问题，先写DB，后写redis，因为写内存基本上没有问题。 业务场景 提高了DB的可扩展性,只需要将新加的数据放到新加的服务器上就可以了。 提高了DB的可用性,只影响到需要访问的shard服务器上的数据的用户。 提高了DB的可维护性,对系统的升级和配里可以按shard一个个来搞,对服务产生的影响小。 小的数据库存的查询压力小,查询更快,性能更好。 使用过程中的一些经验与教训,做个小结：\n要进行Master-slave配置,出现服务故障时可以支持切换。 在master侧禁用数据持久化,只需在slave上配置数据持久化。 物理内存+虚拟内存不足时,这个时候dump已知死着,时间久了机器挂掉。这个情况就是灾难。 当Redis物理内存使用超过内存总容量的3/5时就会开始比较危险了,就开始做swap,内存碎片大。 当达到最大内存时,会清空带有过期时间的key,即使key未到过期时间。 redis与DB同步写的问题,先写DB,后写redis,因为写内存基本上没有问题。 安装配置Redis 下载安装Redis redis官方网站：www.redis.io\nbash 1 2 make make PREFIX=/app/redis-3.2.8 install 命令执行完成后,会在/app/redis-3.2.8/bin/目录下生成5个可执行文件\nbash 1 2 3 4 5 6 7 /app/redis-3.2.8/ └── bin ├── redis-benchmark #← redis性能测试工具,测试redis在你的系统及你的配置下读写性能 ├── redis-check-aof #← 更新日志检查 ├── redis-check-rdb ├── redis-cli #← redis命令行操作工具。当然也可用telnet根据其纯文本协议来操作 └── redis-server #←redis服务器的daemon启动服务 配置并启动Redis服务 文件头部分 操作命令：\nbash 1 2 echo \u0026#39;PATH=\u0026#34;/app/redis/bin:$PATH\u0026#34;\u0026#39; \u0026gt;\u0026gt;/etc/profile . /etc/profile 查看命令帮助 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Usage: ./redis-server [/path/to/redis.conf] [options] ./redis-server - (read config from stdin) ./redis-server -v or --version ./redis-server -h or --help ./redis-server --test-memory \u0026lt;megabytes\u0026gt; Examples: ./redis-server (run the server with default conf) ./redis-server /etc/redis/6379.conf ./redis-server --port 7777 #\u0026lt;==指定端口 ./redis-server --port 7777 --slaveof 127.0.0.1 8888 ./redis-server /etc/myredis.conf --loglevel verbose #\u0026lt;==指定配置文件 Sentinel mode: ./redis-server /etc/sentinel.conf --sentinel 启动redis服务 创建配置文件,redis的配置文件在二进制安装包解压目录下的 redis.conf\nbash 1 2 mkdir /app/redis-3.2.8/conf cp /root/tools/redis-3.2.8/redis.conf /app/redis-3.2.8/conf/ 启动命令\nbash 1 redis-server /app/redis-3.2.8/conf/redis.conf \u0026amp; 启动错误 bash 1 WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. 原因：此参数确定了TCP连接中已完成队列(完成三次握手之后)的长度, 当然此值必须不大于Linux系统定义的/proc/sys/net/core/somaxconn值,默认是511,而Linux的默认参数值是128。当系统并发量大并且客户端速度缓慢的时候,可以将这二个参数一起参考设定。\n暂时解决：redis.conf中 tcp backlog=128\nbash 1 WARNING overcommit_memory is set to 0! Background save may fail under low memory condition.To fix this issue add \u0026#39;vm.overcommit_memory = 1\u0026#39; to /etc/sysctl.conf and then reboot or run the command \u0026#39;sysctl vm.overcommit_memory=1\u0026#39; for this to take effect. 原因：\novercommit_memory参数说明：\n设置内存分配策略（可选,根据服务器的实际情况进行设置）/proc/sys/vm/overcommit_memory可选值：0、1、2。\n0：当用户空间请求更多的内存时，内核尝试估算出剩余可用的内存。\n1：设这个参数值为1时，内核允许超量舒勇内存直到用完为止,主要用于科学计算。(最大限度的使用内存)\n2：当设这个参数值为2时，内核会使用一个决不过量使用内存的算法，即系统整个内存地址空间不能超过swap+50%的RAM值，50%参数的设定是在overcommit_ratio中设定。\n解决：sysctl vm.overcommit_memory=1*\nbash 1 2 3 4 5 6 7 WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command \u0026#39;echo never \u0026gt; /sys/kernel/mm/transparent_hugepage/enabled\u0026#39; as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled. 原因\n临时解决方法：\nbash 1 echo never \u0026gt; /sys/kernel/mm/transparent_hugepage/enabled 参考文档：http://blog.csdn.net/a491857321/article/details/52006376\n客户端测试redis服务 redis-cli客户端帮助 bash 1 redis-cli -help 使用redis-cli客户端连接redis 指定密码 ip 端口登陆\nbash 1 2 3 4 5 redis-cli -h 10.0.0.10 -p 3307 -a \u0026#39;111\u0026#39; Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]] -h \u0026lt;hostname\u0026gt; Server hostname (default: 127.0.0.1). -p \u0026lt;port\u0026gt; Server port (default: 6379). -s \u0026lt;socket\u0026gt; Server socket (overrides hostname and port). 在Linux命令行操作redis\nbash 1 2 3 4 $ redis-cli set zhangsan 10-10 OK $ redis-cli get zhangsan \u0026#34;10-10\u0026#34; 使用telnet连接redis bash 1 2 3 4 5 6 7 $ telnet 127.0.0.1 6379 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is \u0026#39;^]\u0026#39;. get name $2 45 通过NC连接redis bash 1 2 3 4 5 $ echo \u0026#34;set three-world 刘备\u0026#34;|nc 127.0.0.1 6379 +OK $ echo \u0026#34;get three-world\u0026#34;|nc 127.0.0.1 6379 $6 刘备 关闭redis服务 bash 1 redis-cli shutdown 通过客户端测试redis服务\nPHP安装Redis扩展 源码地址：\nhttps://github.com/phpredis/phpredis\nhttp://pecl.php.net/package/redis\n安装\nbash 1 2 /app/php/bin/phpize ./configure --with-php-config=/app/php/bin/php-config 修改php.ini设置\nbash 1 extension=redis.so 查看结果\nRedis配置文件注解 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # 是否在后台执行,yes：后台运行；no：不是后台运行（老版本默认） daemonize yes # 此参数确定了TCP连接中已完成队列(完成三次握手之后)的长度, 当然此值必须不大于Linux系统定义的/proc/sys/net/core/somaxconn值,默认是511,而Linux的默认参数值是128。当系统并发量大并且客户端速度缓慢的时候,可以将这二个参数一起参考设定。该内核参数默认值一般是128,对于负载很大的服务程序来说大大的不够。一般会将它修改为2048或者更大。在/etc/sysctl.conf中添加:net.core.somaxconn = 2048,然后在终端中执行sysctl -p。 tcp-backlog 511 # 指定 redis 只接收来自于该 IP 地址的请求,如果不进行设置,那么将处理所有请求 bind 127.0.0.1 # 此参数为设置客户端空闲超过timeout,服务端会断开连接,为0则服务端不会主动断开连接,不能小于0。 timeout 0 # 指定了服务端日志的级别。级别包括：debug（很多信息,方便开发、测试）,verbose（许多有用的信息,但是没有debug级别信息多）,notice（适当的日志级别,适合生产环境）,warn（只有非常重要的信息）\tloglevel notice # 指定了记录日志的文件。空字符串的话,日志会打印到标准输出设备。后台运行的redis标准输出是/dev/null。 logfile /var/log/redis/redis-server.log # 注释掉\u0026#34;save\u0026#34;这一行配置项就可以让保存数据库功能失效 # 设置sedis进行数据库镜像的频率。 # 900秒（15分钟）内至少1个key值改变（则进行数据库保存--持久化） # 300秒（5分钟）内至少10个key值改变（则进行数据库保存--持久化） # 60秒（1分钟）内至少10000个key值改变（则进行数据库保存--持久化） save 900 1 save 300 10 save 60 10000 更多配置详情：http://www.cnblogs.com/zhang-ke/p/5981108.html\n","permalink":"https://www.161616.top/redis-install/","summary":"Remote Dictonary Server(Redis)是一个基于key-value键值对的持久化数据库存储系统。redis和大名鼎鼎的Memcached缓存服务很像,但是redis支持的数据存储类型更丰富,包括string(字符串)、list(链表)、set(集合)和zset(有序集合)、Hash等。\n这些数据类型都支持push/pop,add/remove及取交集、并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上，redis支持各种不同方式的排序。与memcached缓存服务一样，为了保证效率数据都是缓存在内存中提供服务。和memcached不同的是，redis持久化缓存服务还会周期性的把更新的数据写入到磁盘以及把修改的操作记录追加到文件里记录下来，比memcached更有优势的是，redis还支持master-slave(主从)同步,这点很类似关系型数据库MySQL。\nRedis是一个开源的、使用C语言编写、３万多行代码、支持网络、可基于内存亦可久化的日志型、Key-Value数据库，并提供多种语言的API从2010年3月15日起，Redis开发工作由VMware主持。\nRedis的出现，再一定程度上弥补了memcached这类key-value内存缓存服务的不足,在部分场合可以对关系数据库起到很好的补充作用.redis提供了Python, Ruby, Erlang, PHP客户端，使用很方便。redis官方文档如下：http://www.redis.io/documentation\nRedis的优点 与memcached不用，redis可以持久化存储数据。 性能很高：Redis能支持超过10w/秒的读写频率。 丰富的数据类型：redis支持二进制的Strings, Lists, Hashes, Sets及sorted sets等数据类型操作。 原子：Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。 丰富的特性：Redis还支持publish/subscribe(发布/订阅)，通知，key过期等等特性。 redis支持异步主从复制。 Redis的应用场景 传统的MySQL+Memcached的网站架构遇到的问题：\nMySQL数据库实际上是适合进行海量数据存储的,加上通过Memcached将热点数据 存放到到内存cache里,达到加速数据访问的目的,绝大部分公司都曾经使用过这样的架构,但随着业务数据量的不断增加,和访问量的增长,很多问题就会暴漏出来：\n需要不断的对MySQL进行拆库拆表Memcached也需不断跟着扩容,扩容和维护工作占据大量开发运维时间。 Memcached与MySQL数据库数据一致性问题是个老大难。 Memcached数据命中率低或down机,会导致大量访问直接穿透到数据库,导致MySQL无法支撑访问。 跨机房cache同步一致性问题。 redis在微博中的应用 计数器：微博（评论、转发、阅读、赞等） 用户（粉丝、关注、收藏、双向关注等） redis在短信中的应用 发送短信后存入redis中60秒过期。\nredis的最佳应用场景 Redis最佳试用场景是全部数据in-memory。 Redis更多场景是作为Memcached的替代品来使用。 当需要除key/value之外的更多数据类型支持时,使用Redis更合适。 数据比较重要，对数据一致性有一定要求的业务。 当存储的数据不能被剔除时,使用Redis更合适。 更多 Redis作者谈Redis应用场景 http://blog.nosglfan.com/html/2235.html 使用redis bitmap进行活跃用户统计 http://blog.nosqlfun.com/html/3501.html 计数、cache服务、展示最近、最热、点击率最高、活跃度最高等等条件的top list、用户最近访问记录表、relation list/Message Queue、粉丝列表\nKey-Value Store更加注重对海量数据存取的性能、分布式、扩展性支持上，并不需要传统关系数据库的一些特征。例如：Schema事务、完整SQL查询支持等等，因此在布式环境下的性能相对于传统的关系数据库有较大的提升。\nredis的生产经验教训 要进行Master-slave主从同步配置，在出现服务故障时可以切换。 在master禁用数据据持久化只需在slave上配置数据持久化。 物理内存+虚拟内存不足，这个时候dump一直死着，时间久了机器挂掉。这个情就是灾难。 当Redis物理内存使用超过内存总容量的3/5时就会开始比较危险了，就开始做swap，内存碎片大！ 当达到最大内存时，会清空带有过期时间的如 redis与DB同步写的问题，先写DB，后写redis，因为写内存基本上没有问题。 业务场景 提高了DB的可扩展性,只需要将新加的数据放到新加的服务器上就可以了。 提高了DB的可用性,只影响到需要访问的shard服务器上的数据的用户。 提高了DB的可维护性,对系统的升级和配里可以按shard一个个来搞,对服务产生的影响小。 小的数据库存的查询压力小,查询更快,性能更好。 使用过程中的一些经验与教训,做个小结：\n要进行Master-slave配置,出现服务故障时可以支持切换。 在master侧禁用数据持久化,只需在slave上配置数据持久化。 物理内存+虚拟内存不足时,这个时候dump已知死着,时间久了机器挂掉。这个情况就是灾难。 当Redis物理内存使用超过内存总容量的3/5时就会开始比较危险了,就开始做swap,内存碎片大。 当达到最大内存时,会清空带有过期时间的key,即使key未到过期时间。 redis与DB同步写的问题,先写DB,后写redis,因为写内存基本上没有问题。 安装配置Redis 下载安装Redis redis官方网站：www.","title":"Redis安装"},{"content":"发布与订阅 Publish/Subscribe 发布订阅(pub/sub)是一种消息通信模式，主要的目的是解耦消息发布者和消息订阅者之间的藕合，这点和设计模式中的观察者模式比较相似。pub/sub不仅仅解决发布者和订阅者直接代码级别耦合也解决两者在物理部署上的耦合。Redis作为一个pub/sub的server,在订阅者和发布者之间起到了消息路由的功能。订阅者可以通过subscribe和psubscribe命令向Redis server订阅自己感兴趣的消息类型，Redis将消息类型称为通道(channel)。当发布者通过publish命令向Redis server发送特定类型的消息时。订阅该消息类型的全部client\n都会收到此消息。这里消息的传递是多对多的。一个client可以订阅多个channel,也可以向多个channel发送消息。\nRedis支持这样一种特性，你可以将数据推到某个信息管道中，然后其它人可以通过订阅这些管道来获取推送过来的信息。\n用一个客户端订阅频道 bash 1 2 3 4 5 6 psubscribe new #### 1.批量订阅 127.0.0.1:6379\u0026gt; publish news news-test (integer) 1 127.0.0.1:6379\u0026gt; publish video video-test (integer) 1 此时可以见到接受的信息\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 127.0.0.1:6379\u0026gt; psubscribe news video Reading messages... (press Ctrl-C to quit) 1) \u0026#34;psubscribe\u0026#34; 2) \u0026#34;news\u0026#34; 3) (integer) 1 1) \u0026#34;psubscribe\u0026#34; 2) \u0026#34;video\u0026#34; 3) (integer) 2 1) \u0026#34;pmessage\u0026#34; 2) \u0026#34;news\u0026#34; 3) \u0026#34;news\u0026#34; 4) \u0026#34;news-test\u0026#34; 1) \u0026#34;pmessage\u0026#34; 2) \u0026#34;video\u0026#34; 3) \u0026#34;video\u0026#34; 4) \u0026#34;video-test\u0026#34; 数据过期设置及机制 Redis key的过期机制 Redis对过期键采用了lazy expiration：在访间key的时候判定key是否过期，如果过期，则进行过期处理（过期的key没有被访间可能不会被删除）。其次，每秒对volatile keys进行抽样测试，如果有过期键，那么对所有过期key进行处理。\nexpire设置key的生命周期 使用ttl命令可以获得某个key的过期时间\nbash 1 2 3 4 127.0.0.1:6379\u0026gt; ttl test (integer) -1 #\u0026lt;== -1代表永久 127.0.0.1:6379\u0026gt; ttl test1 (integer) -2 #\u0026lt;==redis2.8后不存在的key返回-2 expire：设置一个key的生命周期，单位秒\nbash 1 2 3 4 127.0.0.1:6379\u0026gt; expire site 10240000 #\u0026lt;==设置key多长时间后过期,单位秒 (integer) 1 127.0.0.1:6379\u0026gt; ttl site (integer) 10239996 pexpire：以毫秒数设置key的生命周期\nbash 1 2 3 4 127.0.0.1:6379\u0026gt; pexpire a 10000 (integer) 1 127.0.0.1:6379\u0026gt; ttl a (integer) 8 pttl：以毫秒返回生命周期\nbash 1 2 127.0.0.1:6379\u0026gt; pttl a (integer) 5432 persist：将key设置为永久有效\nbash 1 2 3 4 5 6 127.0.0.1:6379\u0026gt; pexpire a 10000 (integer) 1 127.0.0.1:6379\u0026gt; persist a (integer) 1 127.0.0.1:6379\u0026gt; ttl a (integer) -1 设置指定时间过期 unix时间戳转换：http://tool.chinaz.com/Tools/unixtime.aspx\nlinux下获得unix时间戳\nbash 1 2 3 4 $ date +%s #\u0026lt;==获得当前时间的时间戳 1492845155 $ date +%s -d \u0026#39;2017-4-27 18:10\u0026#39; #\u0026lt;==获得指定时间的时间戳 1493287800 创建key时设置生命周期 bash 1 2 3 4 127.0.0.1:6379\u0026gt; set test-expire test-tmp ex 10 #\u0026lt;==ex/px 秒/毫秒 OK 127.0.0.1:6379\u0026gt; ttl test-expire (integer) 4 事务 redis对事务的支持目前还比较简单。redis只能保证一个client发起的事务中的命令可以连续的执行，而中间不会插入其他client的命令。 由于redis是单线程来处理所有client的请求的，所以做到这点是很容易的。一般情况下redis在接受到一个client发来的命令后会立即处理并返回处理结果，但是当一个client在一个连接中发出multi命令有，这个连接会进入一个事务上下文，该连接后续的命令并不是立即执行，而是先放到一个队列中。当从此连接受到exec命令后，redis会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到一起返回给client，然后此连接就结束事务上下文。\n用法 选项详解\n命令 描述 MULTI 于开启一个事务，它总是返回 OK DISCARD 清空事务队列， 并放弃执行事务。 EXEC 负责触发并执行事务中的所有命令 WATCH 为Redis 事务提供 check-and-set （CAS）行为；\n被WATCH的键会被监视，并会发觉这些键是否被改动过了。 如果有至少一个被监执行之前被修改了， 那么整个事务都会被取消。 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 127.0.0.1:6379\u0026gt; set num 1000 OK 127.0.0.1:6379\u0026gt; set num2 2000 OK 127.0.0.1:6379\u0026gt; multi #\u0026lt;==开启队列 OK 127.0.0.1:6379\u0026gt; decrby num2 1000 #\u0026lt;==将num2的值减少1000 QUEUED #\u0026lt;==当客户端处于事务状态时,所有传入的命令都会返回一个内容为QUEUED的状态回复 127.0.0.1:6379\u0026gt; get num2 \u0026#34;1000\u0026#34; #\u0026lt;==这时在当前查看num2的值，发现已经被更改了 127.0.0.1:6379\u0026gt; get num2 \u0026#34;2000\u0026#34; #\u0026lt;==此时在其他客户端查看num2的值 127.0.0.1:6379\u0026gt; exec #\u0026lt;==执行队列中的语句 1)(integer) 1000 127.0.0.1:6379\u0026gt; get num2 #\u0026lt;==这时在其他客户端查看num2 \u0026#34;1000\u0026#34; 注：即使事务中有某条/某些命令执行失败了， 事务队列中的其他命令仍然会继续执行，Redis 不会停止执行事务中的命令。\nbash 1 2 3 4 5 6 7 127.0.0.1:6379\u0026gt; decrby num1 5 QUEUED 127.0.0.1:6379\u0026gt; decrby num2 aaa QUEUED 127.0.0.1:6379\u0026gt; exec 1) (integer) -5 2) (error) ERR value is not an integer or out of range 放弃事务 执行 DISCARD 命令，事务会被放弃，事务队列会被清空，并且客户端会从事务状态中退出\nbash 1 2 3 4 5 6 7 8 9 127.0.0.1:6379\u0026gt; multi OK 127.0.0.1:6379\u0026gt; keys * QUEUED 127.0.0.1:6379\u0026gt; discard OK 127.0.0.1:6379\u0026gt; keys * 1) \u0026#34;num1\u0026#34; 2) \u0026#34;num2\u0026#34; 使用 check-and-set 操作实现乐观锁\n被 WATCH 的键会被监视，并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了， 那么整个事务都会被取消， EXEC 返回空多条批量回复（null multi-bulk reply）来表示事务已经失败。\nbash 1 2 3 4 5 6 7 8 9 10 127.0.0.1:6379\u0026gt; watch num1 OK 127.0.0.1:6379\u0026gt; multi OK 127.0.0.1:6379\u0026gt; incr num1 #\u0026lt;==在其它客户端修改num1 (integer) 101 127.0.0.1:6379\u0026gt; incr num1 QUEUED 127.0.0.1:6379\u0026gt; exec (nil) 参考资料 http://doc.redisfans.com/topic/transaction.html\n","permalink":"https://www.161616.top/redis-subscribe-and-transaction/","summary":"发布与订阅 Publish/Subscribe 发布订阅(pub/sub)是一种消息通信模式，主要的目的是解耦消息发布者和消息订阅者之间的藕合，这点和设计模式中的观察者模式比较相似。pub/sub不仅仅解决发布者和订阅者直接代码级别耦合也解决两者在物理部署上的耦合。Redis作为一个pub/sub的server,在订阅者和发布者之间起到了消息路由的功能。订阅者可以通过subscribe和psubscribe命令向Redis server订阅自己感兴趣的消息类型，Redis将消息类型称为通道(channel)。当发布者通过publish命令向Redis server发送特定类型的消息时。订阅该消息类型的全部client\n都会收到此消息。这里消息的传递是多对多的。一个client可以订阅多个channel,也可以向多个channel发送消息。\nRedis支持这样一种特性，你可以将数据推到某个信息管道中，然后其它人可以通过订阅这些管道来获取推送过来的信息。\n用一个客户端订阅频道 bash 1 2 3 4 5 6 psubscribe new #### 1.批量订阅 127.0.0.1:6379\u0026gt; publish news news-test (integer) 1 127.0.0.1:6379\u0026gt; publish video video-test (integer) 1 此时可以见到接受的信息\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 127.0.0.1:6379\u0026gt; psubscribe news video Reading messages... (press Ctrl-C to quit) 1) \u0026#34;psubscribe\u0026#34; 2) \u0026#34;news\u0026#34; 3) (integer) 1 1) \u0026#34;psubscribe\u0026#34; 2) \u0026#34;video\u0026#34; 3) (integer) 2 1) \u0026#34;pmessage\u0026#34; 2) \u0026#34;news\u0026#34; 3) \u0026#34;news\u0026#34; 4) \u0026#34;news-test\u0026#34; 1) \u0026#34;pmessage\u0026#34; 2) \u0026#34;video\u0026#34; 3) \u0026#34;video\u0026#34; 4) \u0026#34;video-test\u0026#34; 数据过期设置及机制 Redis key的过期机制 Redis对过期键采用了lazy expiration：在访间key的时候判定key是否过期，如果过期，则进行过期处理（过期的key没有被访间可能不会被删除）。其次，每秒对volatile keys进行抽样测试，如果有过期键，那么对所有过期key进行处理。","title":"redis事务与发布订阅"},{"content":"Redis的所有数据都存储在内存中，但是他也提供对这些数据的持久化。\nRedis是一个支持持久化的内存数据库，Redis需要经常将内存中的数据同步到磁盘来保证持久化。Redis支持四种持久化方式，一种是 Snapshotting(快照)也是默认方式 ，另一种是 Append-only file(aof)的方式 。\nRDB持久化方式 Snapshotting方式是将内存中数据以快照的方式写入到二进制文件中，默认的文件名为dump.rdb。可以通过配置设置自动做快照持久化。例如可以配置redis在n秒内如果超过m个key被修改就自动做快照。\n实现机制 Redis调用fork子进程。\n父进程继续处理client请求，子进程负责将内存内容写入到临时文件。由于os的实时复制机制(copy on write)父子进程会共享相同的物理页面，当父进程处理写请求时os会为父进程要修改的页面创建副本，而不是写共享的页面。所以子进程地址空间内的数据是fork时刻整个数据库的一个快照。\n当子进程将快照写入临时文件完毕后，用临时文件替换原来的快照文件，然后子进程退出。client也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主线程中保存快照的，由于redis是用一个主线程来处理所有client的请求，这种方式会阻塞所有clien:请求。所以不推荐使用。另一点需要注意的是，每次快照持久化都是完整写入到磁盘一次并不是增量的只同步变更数据。如果数据量大的话，而且写操作比较多，必然会引起大量的磁盘IO操作，可能会严重影响性能。\n缺点：\n快照方式是在一定间隔时间做一次的，所以如果redis意外down掉的话，就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话，可以采用aof持久化方式。\n相关配置 bash 1 2 3 4 5 6 7 8 save 900 1 #←900秒内至少有1个key被改变 save 300 10 #←300秒内至少有10个key被改变 save 60 10000 #←60秒内至少有10000个key被改变 stop-writes-on-bgsave-error yes #←后台存储错误后停止写。如：磁盘空间不足 rdbcompression yes #←使用LZF压缩rdb文件 rdbchecksum yes #←存储和加载rdb文件时校验 dbfilename dump.rdb #←存储rdb文件名 dir /app/redis/db/ #←rdb文件路径 持久化测试 bash 1 2 3 4 5 11512:M 22 Apr 01:04:47.028 * 5 changes in 60 seconds. Saving... 11512:M 22 Apr 01:04:47.029 * Backgroundsaving started by pid 11520 11520:C 22 Apr 01:04:47.032 * DB saved on disk 11520:C 22 Apr 01:04:47.033 * RDB: 0 MB of memory used by copy-on-write 11512:M 22 Apr 01:04:47.129 * Background saving terminated with success 生成dump.rdb文件\nbash 1 2 $ ll /app/redis/db dump.rdb 关闭服务删除dump.rdb文件重新启动redis服务\nbash 1 2 127.0.0.1:6379\u0026gt; keys * (empty list or set) 将dump.rdb.bk文件恢复成dump.rdb。再启动服务器。\nbash 1 2 3 127.0.0.1:6379\u0026gt; keys * 1) \u0026#34;test-durable\u0026#34; 2) \u0026#34;test-durable-1\u0026#34; 手工强制刷新 bash 1 2 3 4 127.0.0.1:6379\u0026gt; save OK 127.0.0.1:6379\u0026gt; bgsave Background saving started 此时服务器端消息\nbash 1 2 3 4 5 4330:M 30 Mar 02:42:14.496 * DB saved on disk $ 4330:M 30 Mar 02:42:28.700 * Background saving started by pid 18345 18345:C 30 Mar 02:42:28.718 * DB saved on disk 18345:C 30 Mar 02:42:28.718 * RDB: 0 MB of memory used by copy-on-write 4330:M 30 Mar 02:42:28.796 * Background saving terminated with success AOF存储介绍 Append-Only-File(追加式的操作日志记录)\n由于快照方式是在一定间隔时间做一次的，所以如果redis意外down掉的话，就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话，可以采用aof持久化方式。\naof比快照方式有更好的持久化性，是由于在使用aof持久化方式时,redis会将每一个收到的写命令都通过write函数追加到文件中(默认是appendonly.aof)。当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当然由于os会在内核中缓存write做的修改，所以可能不是立即写到磁盘上。这样aof方式的持久化也还是有可能会丢失部分修改.不过我们可以通过配置文件告诉redis我们想要通过fsync函数强制os写入到磁盘的时机。\n实现机制 以日志的形式来记录每个写操作，将Redis执行过的所有写指令记录下来(不记录读操作)，只许追加文件但不可以改写文件，redis启动之初会读取该文件重新构建数据。当 Redis 重启时，它会优先使用AOF文件来还原数据，因为AOF文件保存的数据集通常比RDB文件所保存的数据集更完整。你甚至可以关闭持久化功能，让数据只在服务器运行时存在。在redis中这种存储方式默认是关闭的，需要在redis.conf文件中开启。\nRedis AOF重写原理图\r相关配置 bash 1 2 3 4 5 6 7 appendonly no/yes #←是否打开 aof日志功能 appendfsync always #←每次收到写命令就立即强制写入磁盘，最慢,保证完全的持久化，不推荐 appendfsync everysec #←每秒钟强制写入磁盘一次，在性能和持久化方面折中，推荐 appendfsync no #←完全依赖os，性能最好,持久化没保证 no-appendfsync-on-rewrite yes: #←导出rdb快照的过程中,是否停止同步aof auto-aof-rewrite-percentage 100#←aof文件大小比起上次重写时的大小,增长率100%时,重写 auto-aof-rewrite-min-size 64mb #←aof文件,至少超过64M时,重写 测试 使用for循环批量更改一个key的值\nbash 1 for n in {1..8888};do redis-cli set name $n;done 此时redis服务端的信息\nbash 1 2 3 4 5 6 7 8 9 4330:M 30 Mar 02:15:23.581 * Starting automatic rewriting of AOF on 101% growth 4330:M 30 Mar 02:15:23.581 * Background append only file rewriting started by pid 11921 4330:M 30 Mar 02:15:24.598 * AOF rewrite child asks to stop sending diffs. 11921:C 30 Mar 02:15:24.598 * Parent agreed to stop sending diffs. Finalizing AOF... 11921:C 30 Mar 02:15:24.598 * Concatenating 0.02 MB of AOF diff received from parent. 11921:C 30 Mar 02:15:24.606 * SYNC append only file rewrite performed 11921:C 30 Mar 02:15:24.606 * AOF rewrite: 0 MB of memory used by copy-on-write 4330:M 30 Mar 02:15:24.685 * Background AOF rewrite terminated with success 4330:M 30 Mar 02:15:24.685 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB) 此时日志文件大小\nbash 1 2 3 4 5 6 $ ll -h total 132K -rw-r--r--. 1 root root 59K Mar 30 02:15 appendonly.aof -rw-r--r--. 1 root root 56 Mar 30 02:15 temp-rewriteaof-17659.aof $ ll -h -rw-r--r--. 1 root root 22K Mar 30 02:15 appendonly.aof 手动生成新的AOF文件 bash 1 bgrewriteaof 日志重写 aof的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用incr test命令100次，文保中必须保存全部的100条命令，其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件，Redis提供了bgrewriteaof命令.收到此命令Redis将使用与快照类似的方式将内存中的数据以命令的方式保存到临时文件中，最后替换原来的文件。\n自动bgrewriteaof参数设置\nsh 1 2 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64m 如何选择RDB和AOF ？ 一般来说,如果想达到足以媲美 PostgreSQL 的数据安全性，你应该同时使用两种持久化功能。如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失，那么你可以只使用 RDB 持久化。有很多用户都只使用 AOF 持久化，并不推荐这种方式： 因为定时生成 RDB 快照（snapshot）非常便于进行数据库备份， 并且RDB恢复数据集的速度也要比AOF恢复的速度要快， 除此之外， 使用RDB还可以避免之前提到的AOF程序的bug 。因为以上提到的种种原因， 未来Redis可能会将AOF和RDB整合成单个持久化模型。（这是一个长期计划）。\n","permalink":"https://www.161616.top/redis-persist/","summary":"Redis的所有数据都存储在内存中，但是他也提供对这些数据的持久化。\nRedis是一个支持持久化的内存数据库，Redis需要经常将内存中的数据同步到磁盘来保证持久化。Redis支持四种持久化方式，一种是 Snapshotting(快照)也是默认方式 ，另一种是 Append-only file(aof)的方式 。\nRDB持久化方式 Snapshotting方式是将内存中数据以快照的方式写入到二进制文件中，默认的文件名为dump.rdb。可以通过配置设置自动做快照持久化。例如可以配置redis在n秒内如果超过m个key被修改就自动做快照。\n实现机制 Redis调用fork子进程。\n父进程继续处理client请求，子进程负责将内存内容写入到临时文件。由于os的实时复制机制(copy on write)父子进程会共享相同的物理页面，当父进程处理写请求时os会为父进程要修改的页面创建副本，而不是写共享的页面。所以子进程地址空间内的数据是fork时刻整个数据库的一个快照。\n当子进程将快照写入临时文件完毕后，用临时文件替换原来的快照文件，然后子进程退出。client也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主线程中保存快照的，由于redis是用一个主线程来处理所有client的请求，这种方式会阻塞所有clien:请求。所以不推荐使用。另一点需要注意的是，每次快照持久化都是完整写入到磁盘一次并不是增量的只同步变更数据。如果数据量大的话，而且写操作比较多，必然会引起大量的磁盘IO操作，可能会严重影响性能。\n缺点：\n快照方式是在一定间隔时间做一次的，所以如果redis意外down掉的话，就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话，可以采用aof持久化方式。\n相关配置 bash 1 2 3 4 5 6 7 8 save 900 1 #←900秒内至少有1个key被改变 save 300 10 #←300秒内至少有10个key被改变 save 60 10000 #←60秒内至少有10000个key被改变 stop-writes-on-bgsave-error yes #←后台存储错误后停止写。如：磁盘空间不足 rdbcompression yes #←使用LZF压缩rdb文件 rdbchecksum yes #←存储和加载rdb文件时校验 dbfilename dump.rdb #←存储rdb文件名 dir /app/redis/db/ #←rdb文件路径 持久化测试 bash 1 2 3 4 5 11512:M 22 Apr 01:04:47.028 * 5 changes in 60 seconds.","title":"redis数据持久化"},{"content":"key/value介绍 Redis key值是二进制安全的，这意味着可以用任何二进制序列作为key值，从形如“0foo”的简单字符串到一个JPG文件的内容都可以。空字符串也是有效key值。\n关于key的几条规则：\n太长的键值，例如1024字节的键值，不仅因为消耗内存，而且在数据中查找这类键值的计算成本很高。\n太短的键值，如果你要用 u:1000:pwd来代替user:1000:password，这没有什么问题，但后者更易阅读，并且由此增加的空间消耗相对于key object和value object本身来说很小.当然，没人阻止您一定要用更短的键值节省一丁点空间。\n最好坚持一种模式;。例如：object-type:id:field就是个不错的注意，像这样user:1000:password。我喜欢对多单词的字段名中加上一个点，就像这样：comment.1234.renlv_to\nkey建议：object-type:id:field 长度10-20\nvalue建议：string不要超过2K set sortedset元素不要超过5000\nbash 1 2 3 4 $ redis-cli set user_list:user_id:5 zhangsan OK $ redis-cli get user_list:user_id:5 \u0026#34;zhangsan\u0026#34; 通用操作 找到全部给定模式的匹配到的key bash 1 2 3 4 5 6 7 127.0.0.1:6379\u0026gt; keys * #\u0026lt;==打印全部key 1) \u0026#34;name\u0026#34; 2) \u0026#34;site\u0026#34; 127.0.0.1:6379\u0026gt; keys na[ma]e #\u0026lt;==返回正则匹配到的key 1) \u0026#34;name\u0026#34; 127.0.0.1:6379\u0026gt; keys nam? 1) \u0026#34;name\u0026#34; randomkey返回随机key bash 1 2 3 4 127.0.0.1:6379\u0026gt; randomkey \u0026#34;name\u0026#34; 127.0.0.1:6379\u0026gt; randomkey \u0026#34;site\u0026#34; exists检查key是否存在 bash 1 2 3 4 127.0.0.1:6379\u0026gt; exists site (integer) 1 127.0.0.1:6379\u0026gt; exists sites (integer) 0 type 查看key类型 bash 1 2 3 4 127.0.0.1:6379\u0026gt; type set set 127.0.0.1:6379\u0026gt; type site string 修改key名称 bash 1 2 3 4 5 6 7 127.0.0.1:6379\u0026gt; rename site web OK 127.0.0.1:6379\u0026gt; keys * 1) \u0026#34;set\u0026#34; 2) \u0026#34;web\u0026#34; 3) \u0026#34;name\u0026#34; 4) \u0026#34;test\u0026#34; renamnx rename not exists,当要修改的新名称存在,会用改名后的覆盖存在的那个key\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 127.0.0.1:6379\u0026gt; set rename_test test OK 127.0.0.1:6379\u0026gt; set rename_test1 test1 OK 127.0.0.1:6379\u0026gt; set tmp tmp OK 127.0.0.1:6379\u0026gt; rename rename_test tmp OK 127.0.0.1:6379\u0026gt; keys * 1) \u0026#34;tmp\u0026#34; 2) \u0026#34;rename_test1\u0026#34; 127.0.0.1:6379\u0026gt; get tmp \u0026#34;test\u0026#34; #\u0026lt;==可以看到tmp把rename_test给覆盖了 将当前数据库的 key 移动到给定的数据库db当中 bash 1 2 3 4 5 6 7 8 9 10 11 12 127.0.0.1:6379\u0026gt; select 2 OK 127.0.0.1:6379[2]\u0026gt; keys * (empty list or set) 127.0.0.1:6379[2]\u0026gt; select 0 OK 127.0.0.1:6379\u0026gt; move web 2 (integer) 1 127.0.0.1:6379\u0026gt; select 2 OK 127.0.0.1:6379[2]\u0026gt; keys * 1)\u0026#34;web\u0026#34; dbsize返回当前选择的数据库的key数量 bash 1 2 127.0.0.1:6379\u0026gt; dbsize (integer) 1 shutdown 同步保存数据到磁盘，并结束Redis进程 语法：shutdown [NOSAVE|SAVE]\nbash 1 2 3 4 5 redis-cli shutdown save 12632:M 22 Apr 16:42:23.492 * Saving the final RDB snapshot before exiting. 12632:M 22 Apr 16:42:23.528 * DB saved on disk 12632:M 22 Apr 16:42:23.528 * Removing the pid file. 12632:M 22 Apr 16:42:23.529 # Redis is now ready to exit, bye bye... 异步操作Redis bgrewriteaof (异步重写aof日志文件) 作用减少aof文件大小 BGSAVE(异步保存数据到磁盘)\nString字符串类型 这是最简单的Redis类型。如果你只用这种类型,Redis就是一个可以持久化的memcached服务器(注:memcache的数据仅保存在内存中,服务器重启后,数据将丢失)。\n常规字符串操作 在Redis中，常用set设置一对key/value键值，然后用get来获取字符串的值。value值可以是任何类型的字符串(包括二进制数据)，例如你可以在一个键下保存一个jpg图片。但值的长度不能超过1GB。\nset：设置一个建的字符串值\n语法：set key value [EX seconds] [PX milliseconds] [NX|XX]\nbash 1 2 3 4 5 6 7 8 9 10 11 12 127.0.0.1:6379\u0026gt; set k1 test:key:123 OK 127.0.0.1:6379\u0026gt; ttl k1 (integer) -1 #\u0026lt;==set默认生命周期为永久有效 127.0.0.1:6379\u0026gt; set k1 test_key NX #\u0026lt;==设置一个key,如果不存在就创建。 (nil) #\u0026lt;==因k1存在故没有创建 127.0.0.1:6379\u0026gt; set k2 test_key NX OK\t#\u0026lt;==名为k2的key不存在成功创建 127.0.0.1:6379\u0026gt; set k2 test EX 100 #\u0026lt;==设置key的生命周期 px毫秒 ex秒 OK 127.0.0.1:6379\u0026gt; ttl k2 (integer) 97 mset：设置多个key value\nbash 1 2 3 4 5 6 127.0.0.1:6379\u0026gt; mset key zhangsan ex 100 key1 lisi OK 127.0.0.1:6379\u0026gt; keys * 1) \u0026#34;key1\u0026#34; 2) \u0026#34;ex\u0026#34; 3) \u0026#34;key\u0026#34; #\u0026lt;==通过结果可得知 mset不支持判断是否存在与设置生命周期 setrange：从指定偏移量开始重写字符串的一部分\nbash 1 2 3 4 5 6 127.0.0.1:6379\u0026gt; set key test:123 OK 127.0.0.1:6379\u0026gt; setrange key 5 abc (integer) 8 127.0.0.1:6379\u0026gt; get key \u0026#34;test:abc\u0026#34; #\u0026lt;==不存在的偏移量用\\x00填充 append：将值附加到key中\nbash 1 2 3 4 5 6 127.0.0.1:6379\u0026gt; set test a OK 127.0.0.1:6379\u0026gt; APPEND test bc (integer) 3 127.0.0.1:6379\u0026gt; get test \u0026#34;abc\u0026#34; getrange：Get a substring of the string stored at a key\nbash 1 2 3 4 5 6 7 8 127.0.0.1:6379\u0026gt; set k test:getrange OK 127.0.0.1:6379\u0026gt; getrange k 0 3 \u0026#34;test\u0026#34; 127.0.0.1:6379\u0026gt; getrange k 0 100 \u0026#34;test:getrange\u0026#34; #\u0026lt;==如果指定值大于字符串长度返回到字符串结尾处 127.0.0.1:6379\u0026gt; getrange k 100 1000 \u0026#34;\u0026#34; #\u0026lt;==如果开始位置大于字符串总长度,返回空字符串 String类型可以用来存储数字 虽然字符串是Redis的基本值类型,但你仍然能通过它完成一些有趣的操作。例如：原子递增。\nINCR命令将字符串值解析成整型,将其加1,最后将结果保存为新的字符串值,类似的命令有INCRBY, DECR and DECRBYC实际上他们在内部就是同一个命令,只是看上去有点儿不同。\nincr是原子操作意味着什么呢?就是说即使多个客户端对同一个key发出INCR命令,也决不会导致竟争的情况.例如如下情况永远不可能发生:「客户端1和客户端2同时读出“10\u0026quot;,他们俩都对其加到11,然后将新值设置为11。最终的值一定是12,read-increment-set操作完成时,其他客户端不会在同一时间执行任何命令。\nincr:将键的整数值递增1\nbash 1 2 3 4 127.0.0.1:6379\u0026gt; set num 1 OK 127.0.0.1:6379\u0026gt; incr num (integer) 2 decr:将键的整数值递减1\nbash 1 2 3 4 127.0.0.1:6379\u0026gt; decr num (integer) 0 127.0.0.1:6379\u0026gt; decr num (integer) -1 #\u0026lt;==与memcached不同的是,redis会被减少为负数 incrby:将键增加一个指定定量\nbash 1 2 127.0.0.1:6379\u0026gt; incrby num 10 (integer) 9 incrbyfloat: 将键增加一个指定定量的浮点值\nbash 1 2 3 4 127.0.0.1:6379\u0026gt; incrbyfloat num \u0026#34;9.1\u0026#34; #\u0026lt;==操作后值变为浮点型 127.0.0.1:6379\u0026gt; incrby num 11 (error) ERR value is not an integer or out of range #\u0026lt;==不可以在进行整型原子递增 为key设置新值并且返回原值 对字符串,另一个操作是GETSET命令,行如其名:他为key设置新值并且返回原值。这有什么用处呢?例如:你的系统每当有新用户访问时就用INCR命令操作一个Redis key。你希望每小时对这个信息收集一次。你就可以GETSET这个key并给其赋值0并读取原值。\ngetset：设置key的字符串值，并返回旧值\nbash 1 2 3 4 127.0.0.1:6379\u0026gt; getset k1 test:getset:demo \u0026#34;test\u0026#34; 127.0.0.1:6379\u0026gt; get k1 \u0026#34;test:getset:demo\u0026#34; setbit getbit bitcount binop 这是偏移位上的二进制值\nbash 1 2 3 4 5 6 (error) ERR bit offset is not an integer or out of range 127.0.0.1:6379\u0026gt; setbit s 4294967296 1 (error) ERR bit offset is not an integer or out of range 127.0.0.1:6379\u0026gt; setbit s 4294967295 1 #\u0026lt;==最大范围为232-1 512M key的最大值 (integer) 0 (1.46s) List类型 要说清楚列表数据类型,最好先讲一点儿理论背景,在信息技术界List这个词常常被使用不当.例如\u0026quot;Python Lists\u0026quot;就名不副实(名为Linked Lists),但他们实际上是数组(同样的数据类型在Ruby中叫数组)。\n列表就是有序元素的序列:10,20,1,2,3就是一个列表。但用数组实现的List和用Linked List实现的List,在属性方面大不相同。\nRedis lists基于Linked Lists实现。这意味着即使在一个list中有数百万个元素,在头部或尾部添加一个元素的操作,其时间复杂度也是常数级别的。用LPUSH命令在十个元素的list头部添加新元素,和在千万元素list头部添加新元素的速度相同。\n那么,坏消息是什么?在数组实现的list中利用索引访问元素的速度极快,而同样的操作在linked list实现的list上没有那么快。\nRedis Lists用linked list实现的原因是:对于数据库系统来说,至关重要的特性是:能非常快的在很大的列表上添加元素.另一个重要因素是,正如你将要看到的:Redis lists能在常数时何取得常数长度。\n创建list并插入数据 LPUSH命令可向list的左边(头部)添加一个新元素,而RPUSH命令可向list的右边(尾部)添加一个新元素。最后LRANGE命令可从list中取出一定范围的元素。\nlpush:向list头部插入数据，如果list不存在则自动创建\nbash 1 2 3 4 127.0.0.1:6379\u0026gt; lpush list lisi #\u0026lt;== 头部插入一个数据 (integer) 1 127.0.0.1:6379\u0026gt; lpush list zhangsan (integer) 2 llen:查看list列表元素个数\nbash 1 2 127.0.0.1:6379\u0026gt; llen list\t#\u0026lt;==获得列表元素个数 (integer) 2 lrange:查看列表所有元素\nbash 1 2 3 4 5 127.0.0.1:6379\u0026gt; lrange list 0 3 #\u0026lt;== 打印列表的1,3元素 1) \u0026#34;zhangsan\u0026#34; 2) \u0026#34;lisi\u0026#34; 3) \u0026#34;test1\u0026#34; 127.0.0.1:6379\u0026gt; lrange list 0 -1#\u0026lt;== 打印列表的所有元素 ⚠ 注意：RANGE带有两个索引,范围内第一个和最后一个元素。这两个索引都可以负来告知redis从尾部开始计数,因此-1表示最后一个元素,-2表示list中的倒数第二个元素,以此类推。 lindex:返回指定元素在列表中的下标\nbash 1 2 3 4 5 127.0.0.1:6379\u0026gt; rpush a 1 2 3 4 5 (integer) 5 127.0.0.1:6379\u0026gt; lindex a 1 \u0026#34;2\u0026#34; 127.0.0.1:6379\u0026gt; lindex a 1 linsert在指定元素前后插入数据\nbash 1 2 3 4 5 6 7 8 9 127.0.0.1:6379\u0026gt; rpush arr 1 2 3 (integer) 3 127.0.0.1:6379\u0026gt; linsert arr after 2 5 (integer) 4 127.0.0.1:6379\u0026gt; lrange arr 0 -1 1) \u0026#34;1\u0026#34; 2) \u0026#34;2\u0026#34; 3) \u0026#34;5\u0026#34; 4) \u0026#34;3\u0026#34; list应用场景 正如你可以从上面的例子中猜到的,list可被用来实现聊天系统。还可以作为不同进程间传递消息的队列。关键是,你可以每次都以原先添加的顺序访问数据。这不需要任何SQL ORDER BY操作,将会非常快,也会很容易扩展到百万级别元素的规模。\n例如在评级系统中,比如社会化新闻网站reddit.com,你可以把每个新提交的链接添加到一个list,用LRANGE可简单的对结果分页。\n在博客引擎实现中,你可为每篇日志设置一个list,在该list中推入进博客评论,等等。\n向Redis list压入ID而不是实际的数据,\n在上面的例子里,我们将“对象”(此例中是简单消息)直接压入Redis list,但通常不应这么做,由于对象可能被多次引4:例如在一个lis:中维护其时间顺序,在一个集合中保存它的类别,只要有必要,它还会出现在其他list中,等等。\n让我们回到reddit.com的例子,将用户提交的链接(新闻)添加到list中,有更可靠的方法如下所示\nbash 1 2 3 4 5 6 7 8 127.0.0.1:6379\u0026gt; incr next.news.id (integer) 1 127.0.0.1:6379\u0026gt; set new:1:title \u0026#34;redis is simple\u0026#34; OK 127.0.0.1:6379\u0026gt; set news:1:url \u0026#34;http://www.baidu.com\u0026#34; OK 127.0.0.1:6379\u0026gt; lpush submitted.news 1 (integer) 1 OK我们自增一个key,很容易得到一个独一无二的自增ID,然后通过此ID创建对象入为对象的每个字段设置一个key。最后将新对象的submitted.news lists。\nrpop 从尾部删除一个元素 rpush 向尾部插入一个数据 lpop 从头部删除一个元素\n删除list内元素 lrem:删除count的绝对值个value后结束 count \u0026gt; 0从头开始删,\u0026lt;0从尾部开始删\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 127.0.0.1:6379\u0026gt; rpush aa a a b c a a (integer) 6 127.0.0.1:6379\u0026gt; lrem aa 1 a (integer) 1 127.0.0.1:6379\u0026gt; LRANGE aa 0 -1 1) \u0026#34;a\u0026#34; 2) \u0026#34;b\u0026#34; 3) \u0026#34;c\u0026#34; 4) \u0026#34;a\u0026#34; 5) \u0026#34;a\u0026#34; 127.0.0.1:6379\u0026gt; lrem aa -2 a (integer) 2 127.0.0.1:6379\u0026gt; LRANGE aa 0 -1 1) \u0026#34;a\u0026#34; 2) \u0026#34;b\u0026#34; 3) \u0026#34;c\u0026#34; ltrim:剪切key对应的链接,切[start,stop]一段,并把该段重新赋给key\nbash 1 2 3 4 5 6 7 127.0.0.1:6379\u0026gt; rpush a 1 2 3 4 5 (integer) 5 127.0.0.1:6379\u0026gt; ltrim a 2 3 OK 127.0.0.1:6379\u0026gt; lrange a 0 -1 1) \u0026#34;3\u0026#34; 2) \u0026#34;4\u0026#34; rpoplpush:将arr尾部的元素弹出到tmp头部\n作用：接收返回值,并做业务处理。如果成功,rpop tmp清除任务；如不成功,下次从tmp表里取任务\nbash 1 2 3 4 5 6 7 8 127.0.0.1:6379\u0026gt; rpoplpush arr tmp \u0026#34;3\u0026#34; 127.0.0.1:6379\u0026gt; lrange arr 0 -1 1) \u0026#34;1\u0026#34; 2) \u0026#34;2\u0026#34; 3) \u0026#34;5\u0026#34; 127.0.0.1:6379\u0026gt; lrange tmp 0 -1 1) \u0026#34;3\u0026#34; set集合 Redis集合是未排序的集合,其元素是二进制安全的字符串。sadd命令可以向集合添加一个新元素。和sets相关的操作也有许多,比如检测某个元素是否存在,以及实现交集,并集,差集等等。\n创建并向集合插入新元素 sadd：向集合内添加一个或多个元素\nbash 1 2 127.0.0.1:6379\u0026gt; sadd set a b c d #\u0026lt;==向集合key中增加元素 (integer) 4 smembers：查看集合内所有元素\nbash 1 2 3 4 5 127.0.0.1:6379\u0026gt; smembers set #\u0026lt;==返回集合中的所有元素 1) \u0026#34;d\u0026#34; 2) \u0026#34;c\u0026#34; 3) \u0026#34;b\u0026#34; 4) \u0026#34;a\u0026#34; 删除集合中元素 sadd：从集合内删除一个或多个指定元素\nbash 1 2 3 4 5 6 127.0.0.1:6379\u0026gt; srem set a #\u0026lt;==删除集合中名为a的元素 (integer) 1 127.0.0.1:6379\u0026gt; smembers set 1) \u0026#34;d\u0026#34; 2) \u0026#34;c\u0026#34; 3) \u0026#34;b\u0026#34; 查看集合内元素 sismember：检查集合中是否存在某个元素\nbash 1 2 3 4 127.0.0.1:6379\u0026gt; sismember set a (integer) 0 127.0.0.1:6379\u0026gt; sismember set b (integer) 1 srandmember：返回元素中指定个数的随机元素\n语法 srandmember key [count]\nbash 1 2 3 127.0.0.1:6379\u0026gt; srandmember set 2 1) \u0026#34;d\u0026#34; 2) \u0026#34;b\u0026#34; scard返回元素中的个数\nbash 1 2 127.0.0.1:6379\u0026gt; scard set (integer) 3 移动集合内元素 smove：将source里的member移动到destination集合中\n语法：SMOVE source destination member\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 127.0.0.1:6379\u0026gt; keys * (empty list or set) 127.0.0.1:6379\u0026gt; sadd set a b c d e (integer) 5 127.0.0.1:6379\u0026gt; smove set tmp e (integer) 1 127.0.0.1:6379\u0026gt; keys * 1) \u0026#34;set\u0026#34; 2) \u0026#34;tmp\u0026#34; 127.0.0.1:6379\u0026gt; smembers tmp 1) \u0026#34;e\u0026#34; 127.0.0.1:6379\u0026gt; smembers set 1) \u0026#34;c\u0026#34; 2) \u0026#34;b\u0026#34; 3) \u0026#34;a\u0026#34; 4) \u0026#34;d\u0026#34; “b”是这个集合的成员,而“b”不是。集合特别适合表现对象之间的关系。例如用Redis集合可以很容易实现标签功能。\n下面是一个简单的方案:对每个想加标签的对象,用一个标签ID集合与之关联,并且对每个已有的标签,一组对象ID与之关联。\n例如假设我们的新闻ID=1000被加了三个tag 1,2,5就可以设置下面两个集合\nbash 1 2 3 4 5 6 7 8 9 10 127.0.0.1:6379\u0026gt; sadd news:1000:tags 1 (integer) 1 127.0.0.1:6379\u0026gt; sadd news:1000:tags 2 (integer) 1 127.0.0.1:6379\u0026gt; sadd news:1000:tags 5 (integer) 1 127.0.0.1:6379\u0026gt; smembers news:1000:tags 1) \u0026#34;1\u0026#34; 2) \u0026#34;2\u0026#34; 3) \u0026#34;5\u0026#34; 而有些看上去并不简单的操作仍然能使用相应的redis命令轻松实现。例如我们也许想获得一份同时拥有标签1,2,10和27的对象列表。这可以用SINTER命令来做,他可以在不同集合之间取出交集。\ntags:1:obj tags:2:obj tags:5:obj tags:27:obj\nbash 1 2 3 4 5 6 7 8 9 10 11 127.0.0.1:6379\u0026gt; smembers test1 1) \u0026#34;1\u0026#34; 2) \u0026#34;2\u0026#34; 3) \u0026#34;3\u0026#34; 127.0.0.1:6379\u0026gt; smembers test4 1) \u0026#34;1\u0026#34; 2) \u0026#34;2\u0026#34; 3) \u0026#34;9\u0026#34; 127.0.0.1:6379\u0026gt; sinter test1 test4 1) \u0026#34;1\u0026#34; 2) \u0026#34;2\u0026#34; 集合计算 创建一个集合\nbash 1 2 3 4 5 6 127.0.0.1:6379\u0026gt; sadd A 1 2 3 4 5 (integer) 5 127.0.0.1:6379\u0026gt; sadd B 1 3 4 5 (integer) 4 127.0.0.1:6379\u0026gt; sadd C 1 2 6 7 8 9 (integer) 6 sinter：取出指定集合内的交集\nbash 1 2 3 4 5 6 7 127.0.0.1:6379\u0026gt; sinter A B #\u0026lt;==取出A B两个集合中的交集 1) \u0026#34;1\u0026#34; 2) \u0026#34;3\u0026#34; 3) \u0026#34;4\u0026#34; 4) \u0026#34;5\u0026#34; 127.0.0.1:6379\u0026gt; sinter A B C 1)\u0026#34;1\u0026#34; sinterstore：将多个集合的交集放入另一个集合内\nbash 1 2 3 4 5 6 7 127.0.0.1:6379\u0026gt; sinterstore tmp A B #\u0026lt;== 将A B集合中的交集放置在tmp集合中 2)(integer) 4 3)127.0.0.1:6379\u0026gt; smembers tmp 4)1) \u0026#34;1\u0026#34; 5)2) \u0026#34;3\u0026#34; 6)3) \u0026#34;4\u0026#34; 7)4) \u0026#34;5\u0026#34; sunion获得多个集合的并集\nbash 1 2 3 4 5 6 127.0.0.1:6379\u0026gt; sunion A B 1) \u0026#34;1\u0026#34; 2) \u0026#34;2\u0026#34; 3) \u0026#34;3\u0026#34; 4) \u0026#34;4\u0026#34; 5) \u0026#34;5\u0026#34; sdiff获得多个集合的差\nbash 1 2 127.0.0.1:6379\u0026gt; sdiff A B 1) \u0026#34;2\u0026#34; 有序集合 集合是使用频率很高的数据类型,但是…对许多问题来说他们也有点儿太不讲顺序了；因此Redis1.2引入了有序集合。他和集合非常相似,也是二进制安全的字符串集合,但是这次带有关联的score,以及一个类似lrange的操作可以返回有序元素,此操作只能作用于有序集合,它就是,zrange命令。\n基本上有序集合从某种程度上说是SQL世界的索引在Redis中的等价物。例如在上面提到的reddit.com例子中,并祥有提到如何根据用户投票和时间因素将新闻组合生成首页。我们将看到有序集合如何解决这个问题,但最好先从更简单的事情开始,阐明这个高级数据类型是如何工作的.让我们添加几个黑客,并将他们的生日作为\u0026quot;score\u0026quot;。\n添加并查看有序集合 添加一个或多个成员到一个集合,如果这个成员存在,则更新其排序分数\n语法：ZADD key [NX|XX] [CH] [INCR] score member [score member \u0026hellip;]\n选项详解\n参数 解释 XX 仅更新已经存在的元素。不要添加元素。 NX 不要更新现有的元素。始终添加新元素。 CH 从添加的新元素的数量修改返回值,改变元素的总数（CH是更改的缩写）。已更改的元素是添加的新元素,已经存在的元素已更新。所以在命令行中指定的具有与过去相同的分数的元素不计算在内。注意：通常,ZADD的返回值仅计算添加的新元素的数量。 INCR 当指定此选项时,ZADD的行为就像ZINCRBY。在此模式下只能指定一个记分元素对。 创建一个集合\nbash 1 2 3 4 5 6 7 8 9 10 127.0.0.1:6379\u0026gt; zadd hacker 1953 zhangfei 1963 zhaoyun 1989 wangping 1992 zhugeliang 1945 liubei (integer) 5 127.0.0.1:6379\u0026gt; zadd hacker 100 zhugeliang (integer) 0 127.0.0.1:6379\u0026gt; zrange hacker 0 -1 1) \u0026#34;zhugeliang\u0026#34; 2) \u0026#34;liubei\u0026#34; 3) \u0026#34;zhangfei\u0026#34; 4) \u0026#34;zhaoyun\u0026#34; 5) \u0026#34;wangping\u0026#34; 查看某个元素在集合内的位置\nbash 1 2 127.0.0.1:6379\u0026gt; zscore code_lag py \u0026#34;4\u0026#34; 对有序集合采说,按生日排序返回这些数据易如反掌,因为他们已经是有序的。有序集合是通过一个dual-ported数据结构实现的,它包含一个精简的有序列表和一个hash table,因此添加一个元素的时间复杂度是O(log(N))。这还行,但当我们需要访问有序的元素时,Redis不必再做任何事情,它已经是有序的了。\n对集合的值排序 通过索引查看一个有序集合的范围\nbash 1 2 3 4 5 6 7 8 9 10 11 127.0.0.1:6379\u0026gt; zrange hacker 0 -1 withscores #\u0026lt;==打印score 1) \u0026#34;liubei\u0026#34; 2) \u0026#34;1945\u0026#34; 3) \u0026#34;zhangfei\u0026#34; 4) \u0026#34;1953\u0026#34; 5) \u0026#34;zhaoyun\u0026#34; 6) \u0026#34;1988\u0026#34; 7) \u0026#34;wangping\u0026#34; 8) \u0026#34;1989\u0026#34; 9) \u0026#34;zhugeliang\u0026#34; 10) \u0026#34;1992\u0026#34; 通过指定索引范围查看有序集合列表（倒序）\nbash 1 2 3 4 5 6 7 8 9 10 11 127.0.0.1:6379\u0026gt; zrevrange hacker 0 -1 withscores 1) \u0026#34;zhugeliang\u0026#34; 2) \u0026#34;1992\u0026#34; 3) \u0026#34;wangping\u0026#34; 4) \u0026#34;1989\u0026#34; 5) \u0026#34;zhaoyun\u0026#34; 6) \u0026#34;1988\u0026#34; 7) \u0026#34;zhangfei\u0026#34; 8) \u0026#34;1953\u0026#34; 9) \u0026#34;liubei\u0026#34; 10)\u0026#34;1945\u0026#34; 查看集合中的元素个数 返回有序集合中元素的个数\nbash 1 2 127.0.0.1:6379\u0026gt; zcard hacker (integer) 8 返回min,max包括极值\nbash 1 2 3 4 127.0.0.1:6379\u0026gt; zcount hacker 1950 2000 (integer) 7 127.0.0.1:6379\u0026gt; zcount hacker (1950 (2000 #\u0026lt;==不包括极值的用法 (integer) 4 查找集合区间的元素 zrangebysocre：返回有序集合key的分数min与max之间的所有元素（等于min或max）。这些元素被认为是从低到高的顺序排列。min和max可以是-inf(无穷)和+inf(正无穷)，可以不需要知道的有序集合最高或最低score来获得元素。\n获取1950年之前（两个极值也包含）出生的人\nbash 1 2 3 4 5 127.0.0.1:6379\u0026gt; zrangebyscore hacker -inf 1950 withscores 1) \u0026#34;liubei\u0026#34; 2) \u0026#34;1945\u0026#34; 3) \u0026#34;jiangwie\u0026#34; 4) \u0026#34;1950\u0026#34; 返回1950,1970区间内的值\nbash 1 2 3 4 5 127.0.0.1:6379\u0026gt; zrangebyscore hacker 1950 1970 withscores 1) \u0026#34;jiangwie\u0026#34; 2) \u0026#34;1950\u0026#34; 3) \u0026#34;zhangfei\u0026#34; 4) \u0026#34;1953\u0026#34; 使用)返回 1950,2000区间内的值\nbash 1 2 3 4 5 6 7 8 9 127.0.0.1:6379\u0026gt; zrangebyscore hacker (1950 (2000 withscores 1) \u0026#34;zhangfei\u0026#34; 2) \u0026#34;1953\u0026#34; 3) \u0026#34;zhaoyun\u0026#34; 4) \u0026#34;1988\u0026#34; 5) \u0026#34;wangping\u0026#34; 6) \u0026#34;1989\u0026#34; 7) \u0026#34;zhugeliang\u0026#34; 8) \u0026#34;1992\u0026#34; 使用limit返回指定区间内的值\nbash 1 2 3 4 5 6 7 127.0.0.1:6379\u0026gt; zrangebyscore hacker -inf +inf withscores limit 0 3 1) \u0026#34;liubei\u0026#34; 2) \u0026#34;1945\u0026#34; 3) \u0026#34;jiangwie\u0026#34; 4) \u0026#34;1950\u0026#34; 5) \u0026#34;zhangfei\u0026#34; 6) \u0026#34;1953\u0026#34; zremrangebyscore这个名字虽然不算好,但他却非常有用,还会返回已删除的元素数量。\n回到Reddit的例子，现在我们有个基于有序集合的像样方案来生成首页。用一个有序集合来包含最近几天的新闻(用zremrangebyscore不时的删除旧新闻).用一个后台任务从有序集合中获取所有元素,根据用户投票和新闻时间计算score,然后用新闻ID和scores关联生成reddit.home.page有序集合.要显示首页,我们只需闪电般的调用ZRANGE不时的从reddit.home.page有序集合中删除过旧的新闻也是为了让我们的系统总是工作在有限的新闻集合之上。 更新有序集合的scores.\n结束这篇指南之前还有最后一个小贴士.有序集合scores可以在任何时候更新。只要用ZADD对有序集合内的元素操作就会更新它的score(和位置),时间复杂度是O(log(N)),因此即使大量更新,有序集合也是合适的。其中N是排序集合中的元素数，M是返回元素的数量。如果M是常数（例如，总是用LIMIT来询问前10个元素)，可以考虑O(log=(N))。\n计算交并集 计算由numkeys指定个数的key的的交集，并将结果存储在其中destination\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 127.0.0.1:6379\u0026gt; zadd k1 1 a 2 b 3 c (integer) 3 #\u0026lt;==a b c相当于数组的key 1 2 3相当于数组的值 127.0.0.1:6379\u0026gt; zadd k2 10 a 20 b 30 c (integer) 3 127.0.0.1:6379\u0026gt; zinterstore tmp 1 k1 k2 (error) ERR syntax error #\u0026lt;==指定numkeys的数量和传参数量不一致会提示语法错误 127.0.0.1:6379\u0026gt; zinterstore tmp 2 k1 k2 (integer) 3 127.0.0.1:6379\u0026gt; zrange tmp 0 -1 withscores 1) \u0026#34;a\u0026#34; #\u0026lt;==可以看出默认的聚合方法是sum 2) \u0026#34;11\u0026#34; 3) \u0026#34;b\u0026#34; 4) \u0026#34;22\u0026#34; 5) \u0026#34;c\u0026#34; 6) \u0026#34;33\u0026#34; 127.0.0.1:6379\u0026gt; zinterstore tmp 2 k1 k2 weights 2 1 aggregate sum (integer) 3 #\u0026lt;==指定权重计算两个之间key之间的交集 127.0.0.1:6379\u0026gt; zrange tmp 0 -1 withscores 1) \u0026#34;a\u0026#34; 2) \u0026#34;12\u0026#34; #\u0026lt;==k1 a=1权重为2 就等于2x1+10=12 3) \u0026#34;b\u0026#34; 4) \u0026#34;24\u0026#34; 5) \u0026#34;c\u0026#34; 6) \u0026#34;36\u0026#34; hash hash能够存储一个key对多个属性的数据 如：user.username user.password\n设置key中的域与值 hset：将key中的field域设置为value，如果field域存在则覆盖原value，不存在则添加\nbash 1 2 3 4 5 6 127.0.0.1:6379\u0026gt; hset user1 name zhangsan (integer) 1 127.0.0.1:6379\u0026gt; hset user1 age 12 (integer) 1 127.0.0.1:6379\u0026gt; hset user1 gender male (integer) 1 hmset：给key设置多个field域与值\nbash 1 2 127.0.0.1:6379\u0026gt; hmset user2 name lisi age 11 gender female OK 获得key中的域和值 hget：返回key中一个域的值\nbash 1 2 127.0.0.1:6379\u0026gt; hget user1 name \u0026#34;zhangsan\u0026#34; hgetall：返回key中所有域和值\nbash 1 2 3 4 5 6 7 127.0.0.1:6379\u0026gt; hgetall user1 1) \u0026#34;name\u0026#34; 2) \u0026#34;zhangsan\u0026#34; 3) \u0026#34;age\u0026#34; 4) \u0026#34;12\u0026#34; 5) \u0026#34;gender\u0026#34; 6) \u0026#34;male\u0026#34; hmget：返回key中多个值\nbash 1 2 3 127.0.0.1:6379\u0026gt; hmget user1 name age 1) \u0026#34;zhangsan\u0026#34; 2) \u0026#34;12\u0026#34; hkeys：返回key中的所有域\nbash 1 2 3 4 127.0.0.1:6379\u0026gt; hkeys user1 1) \u0026#34;age\u0026#34; 2) \u0026#34;gender\u0026#34; 3) hvals：返回key中的所有域的值\nbash 1 2 3 127.0.0.1:6379\u0026gt; hvals user1 1) \u0026#34;12\u0026#34; 2) \u0026#34;male\u0026#34; 删除key hdel：删除key中一个或多个域\nbash 1 2 3 4 5 6 7 127.0.0.1:6379\u0026gt; hdel user1 name (integer) 1 127.0.0.1:6379\u0026gt; hgetall user1 1) \u0026#34;age\u0026#34; 2) \u0026#34;12\u0026#34; 3) \u0026#34;gender\u0026#34; 4) \u0026#34;male\u0026#34; 查看域信息 hlen：返回key中域的个数\nbash 1 2 127.0.0.1:6379\u0026gt; hlen user1 (integer) 2 hexists：查看key中是否存在某个域\nbash 1 2 3 4 127.0.0.1:6379\u0026gt; hexists user1 name (integer) 0 127.0.0.1:6379\u0026gt; hexists user1 age (integer) 1 hstrlen：返回key中域的值的长度\nbash 1 2 127.0.0.1:6379\u0026gt; HSTRLEN user2 gender (integer) 6 hash的原子操作 hincrby：对域中的值增长value个(单位整型)\n语法：hincrby key field increment\nbash 1 2 3 4 5 6 127.0.0.1:6379\u0026gt; hget user1 age \u0026#34;14\u0026#34; 127.0.0.1:6379\u0026gt; hincrby user1 age 2 (integer) 16 127.0.0.1:6379\u0026gt; hget user1 age \u0026#34;16\u0026#34; hincrbyfloat：对域中的值增长value(单位浮点型)\nbash 1 2 3 4 5 6 127.0.0.1:6379\u0026gt; hincrbyfloat user1 age 0.3 \u0026#34;16.3\u0026#34; 127.0.0.1:6379\u0026gt; hget user1 age \u0026#34;16.3\u0026#34; 127.0.0.1:6379\u0026gt; hincrbyfloat user1 age -2 #\u0026lt;==减少用负数即可 \u0026#34;14.3\u0026#34; bitMap 可实现用很小的内存实现高效的存储。\nHyperLogLog 超小内存唯一值计数。 12k来实现唯一值得计数\nGEO 基于地理信息位置定位\n","permalink":"https://www.161616.top/redis-datatype/","summary":"key/value介绍 Redis key值是二进制安全的，这意味着可以用任何二进制序列作为key值，从形如“0foo”的简单字符串到一个JPG文件的内容都可以。空字符串也是有效key值。\n关于key的几条规则：\n太长的键值，例如1024字节的键值，不仅因为消耗内存，而且在数据中查找这类键值的计算成本很高。\n太短的键值，如果你要用 u:1000:pwd来代替user:1000:password，这没有什么问题，但后者更易阅读，并且由此增加的空间消耗相对于key object和value object本身来说很小.当然，没人阻止您一定要用更短的键值节省一丁点空间。\n最好坚持一种模式;。例如：object-type:id:field就是个不错的注意，像这样user:1000:password。我喜欢对多单词的字段名中加上一个点，就像这样：comment.1234.renlv_to\nkey建议：object-type:id:field 长度10-20\nvalue建议：string不要超过2K set sortedset元素不要超过5000\nbash 1 2 3 4 $ redis-cli set user_list:user_id:5 zhangsan OK $ redis-cli get user_list:user_id:5 \u0026#34;zhangsan\u0026#34; 通用操作 找到全部给定模式的匹配到的key bash 1 2 3 4 5 6 7 127.0.0.1:6379\u0026gt; keys * #\u0026lt;==打印全部key 1) \u0026#34;name\u0026#34; 2) \u0026#34;site\u0026#34; 127.0.0.1:6379\u0026gt; keys na[ma]e #\u0026lt;==返回正则匹配到的key 1) \u0026#34;name\u0026#34; 127.0.0.1:6379\u0026gt; keys nam? 1) \u0026#34;name\u0026#34; randomkey返回随机key bash 1 2 3 4 127.0.0.1:6379\u0026gt; randomkey \u0026#34;name\u0026#34; 127.0.0.1:6379\u0026gt; randomkey \u0026#34;site\u0026#34; exists检查key是否存在 bash 1 2 3 4 127.","title":"redis数据类型"},{"content":"Replication的工作原理 设置一个Slave，无论是第一次还是重连到Master，它都会发出一个sync命令。当Master收到sync命令之后，会做两件事：\nMaster执行BGSAVE，即在后台保存数据到磁盘（rdb快照文件）。 Master同时将新收到的写入和修改数据集的命令存入缓冲区（非查询类）。 当Master在后台把数据保存到快照文件完成之后，把这个快照传送给Slave，而Slave则把内存清空后，加载该文件到内存中。而Master也会把此前收集到缓冲区中的命令，通过Reids命令协议形式转发给Slave，Slave执行这些命令，实现和Master的同步。Master/Slave此后会不断通过异步方式进行命令的同步。\n注：在redis2.8之前，主从之间一旦发生重连都会引发全量同步操作。但在2.8之后版本，也可能是部分同步操作。\n部分复制 2.8后，当主从之间的连接断开之后，他们之间可以采用持续复制处理方式代替采用全量同步。Master端为复制流维护一个内存缓冲区（in-memory backlog），记录最近发送的复制流命令；同时，Master和Slave之间都维护一个复制偏移量(replication offset)和当前Master服务器ID（Master run id）。当网络断开，Slave尝试重连时：\n如果MasterID相同（即仍是断网前的Master服务器），并且从断开时到当前时刻的历史命令依然在Master的内存缓冲区中存在，则Master会将缺失的这段时间的所有命令发送给Slave执行，然后复制工作就可以继续执行了\n否则，依然需要全量复制操作。\nRedis 2.8 的这个部分重同步特性会用到一个新增的PSYNC内部命令， 而 Redis 2.8以前的旧版本只有SYNC命令，不过，只要从服务器是Redis 2.8或以上的版本，它就会根据主服务器的版本来决定到底是使用 PSYNC还是SYNC。 如果主服务器是 Redis 2.8 或以上版本，那么从服务器使用 PSYNC 命令来进行同步。 如果主服务器是 Redis 2.8 之前的版本，那么从服务器使用 SYNC 命令来进行同步。\nredis主从同步特点 一个Master可以有多个Slave。 Redis使用异步复制。从2.8开始，Slave会周期性（每秒一次）发起一个ack确认复制流（replication stream）被处理进度； 不仅主服务器可以有从服务器， 从服务器也可以有自己的从服务器， 多个从服务器之间可以构成一个图状结构； 复制在Master端是非阻塞模式的，这意味着即便是多个Slave执行首次同步时，Master依然可以提供查询服务； 复制在Slave端也是非阻塞模式的：如果你在redis.conf做了设置，Slave在执行首次同步的时候仍可以使用旧数据集提供查询；你也可以配置为当Master与Slave失去联系时，让Slave返回客户端一个错误提示； 当Slave要删掉旧的数据集，并重新加载新版数据时，Slave会阻塞连接请求（一般发生在与Master断开重连后的恢复阶段）； 复制功能可以单纯地用于数据冗余（data redundancy），也可以通过让多个从服务器处理只读命令请求来提升扩展性（scalability）： 比如说， 繁重的 SORT 命令可以交给附属节点去运行。 可以通过修改Master端的redis.config来避免在Master端执行持久化操作（Save），由Slave端来执行持久化。 redis replication配置文件详解 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 slaveof [masterip] [masterport] #←该redis为slave ip和port是master的ip和port masterauth \u0026lt;master-password\u0026gt; #←如果master设置了安全密码，此处为master的安全密码 slave-serve-stale-data yes#←当slave丢失master或同步正在进行时，如果发生对slave的服务请求： slave-serve-stale-data no #←slave返回client错误:\u0026#34;SYNC with master in progress\u0026#34; slave-serve-stale-data yes #←slave依然正常提供服务 slave-read-only yes #←设置slave不可以写数据，只能用于同步 repl-ping-slave-period 10 #←发送ping到master的时间间隔 repl-timeout 60 #←IO超时时间 repl-backlog-size 1mb #←backlog的大小，当从库连接不到主库时，backlog的队列能放多少 repl-backlog-ttl 3600 #←backlog的生命周期 min-slaves-max-lag 10 #←延迟小于min-slaves-max-lag秒的slave才认为是健康的slave # 当master不可用,Sentinel会根据slave的优先级选举一个master。 # 最低的优先级的slave,当选master.而配置成0,永远不会被选举 slave-priority 100 配置Replication 当前生效：在命令行输入以下命令\nbash 1 redis-cli slaveof 127.0.0.1 6379 永久生效：修改配置文件\u0026quot;slaveof\u0026quot;选项\nbash 1 2 slaveof [masterip] [masterport] slaveof 127.0.0.1 6379 这样就可以保证Redis_6380服务程序在每次启动后都会主动建立与Redis_6379的Replication连接了。\n查看从库的同步情况\nbash 1 2 3 4 5 127.0.0.1:6380\u0026gt; MONITOR #←监听服务器实时收到的所有请求 OK 1492710733.401532 [0 127.0.0.1:6379] \u0026#34;PING\u0026#34; #← 从库会ping主库 1492710743.510808 [0 127.0.0.1:6379] \u0026#34;PING\u0026#34; 1492711152.901232 [0 127.0.0.1:6379] \u0026#34;sadd\u0026#34; \u0026#34;web_site\u0026#34; \u0026#34;www.baidu.com\u0026#34; \u0026#34;www.google.com\u0026#34; \u0026#34;www.qq.com\u0026#34; 获取有关服务器的信息和统计信息\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 127.0.0.1:6380\u0026gt; info Replication Replication role:slave master_host:127.0.0.1 master_port:6379 master_link_status:up master_last_io_seconds_ago:9 master_sync_in_progress:0 slave_repl_offset:8999 slave_priority:100 slave_read_only:1 connected_slaves:0 master_repl_offset:0 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0 当前主从同步存在的问题 由于master和slave服务器不是Redis自动选举产生，需要人工参与，因此主从倒换无法自动完成。这样就存在一个问题，什么时候以及由谁来触发倒换。redis2.8的master和slave服务器是Redis自动选举产生了。\nredis高可用 http://www.cnblogs.com/Xrinehart/p/3501372.html\n","permalink":"https://www.161616.top/redis-replication/","summary":"Replication的工作原理 设置一个Slave，无论是第一次还是重连到Master，它都会发出一个sync命令。当Master收到sync命令之后，会做两件事：\nMaster执行BGSAVE，即在后台保存数据到磁盘（rdb快照文件）。 Master同时将新收到的写入和修改数据集的命令存入缓冲区（非查询类）。 当Master在后台把数据保存到快照文件完成之后，把这个快照传送给Slave，而Slave则把内存清空后，加载该文件到内存中。而Master也会把此前收集到缓冲区中的命令，通过Reids命令协议形式转发给Slave，Slave执行这些命令，实现和Master的同步。Master/Slave此后会不断通过异步方式进行命令的同步。\n注：在redis2.8之前，主从之间一旦发生重连都会引发全量同步操作。但在2.8之后版本，也可能是部分同步操作。\n部分复制 2.8后，当主从之间的连接断开之后，他们之间可以采用持续复制处理方式代替采用全量同步。Master端为复制流维护一个内存缓冲区（in-memory backlog），记录最近发送的复制流命令；同时，Master和Slave之间都维护一个复制偏移量(replication offset)和当前Master服务器ID（Master run id）。当网络断开，Slave尝试重连时：\n如果MasterID相同（即仍是断网前的Master服务器），并且从断开时到当前时刻的历史命令依然在Master的内存缓冲区中存在，则Master会将缺失的这段时间的所有命令发送给Slave执行，然后复制工作就可以继续执行了\n否则，依然需要全量复制操作。\nRedis 2.8 的这个部分重同步特性会用到一个新增的PSYNC内部命令， 而 Redis 2.8以前的旧版本只有SYNC命令，不过，只要从服务器是Redis 2.8或以上的版本，它就会根据主服务器的版本来决定到底是使用 PSYNC还是SYNC。 如果主服务器是 Redis 2.8 或以上版本，那么从服务器使用 PSYNC 命令来进行同步。 如果主服务器是 Redis 2.8 之前的版本，那么从服务器使用 SYNC 命令来进行同步。\nredis主从同步特点 一个Master可以有多个Slave。 Redis使用异步复制。从2.8开始，Slave会周期性（每秒一次）发起一个ack确认复制流（replication stream）被处理进度； 不仅主服务器可以有从服务器， 从服务器也可以有自己的从服务器， 多个从服务器之间可以构成一个图状结构； 复制在Master端是非阻塞模式的，这意味着即便是多个Slave执行首次同步时，Master依然可以提供查询服务； 复制在Slave端也是非阻塞模式的：如果你在redis.conf做了设置，Slave在执行首次同步的时候仍可以使用旧数据集提供查询；你也可以配置为当Master与Slave失去联系时，让Slave返回客户端一个错误提示； 当Slave要删掉旧的数据集，并重新加载新版数据时，Slave会阻塞连接请求（一般发生在与Master断开重连后的恢复阶段）； 复制功能可以单纯地用于数据冗余（data redundancy），也可以通过让多个从服务器处理只读命令请求来提升扩展性（scalability）： 比如说， 繁重的 SORT 命令可以交给附属节点去运行。 可以通过修改Master端的redis.config来避免在Master端执行持久化操作（Save），由Slave端来执行持久化。 redis replication配置文件详解 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 slaveof [masterip] [masterport] #←该redis为slave ip和port是master的ip和port masterauth \u0026lt;master-password\u0026gt; #←如果master设置了安全密码，此处为master的安全密码 slave-serve-stale-data yes#←当slave丢失master或同步正在进行时，如果发生对slave的服务请求： slave-serve-stale-data no #←slave返回client错误:\u0026#34;SYNC with master in progress\u0026#34; slave-serve-stale-data yes #←slave依然正常提供服务 slave-read-only yes #←设置slave不可以写数据，只能用于同步 repl-ping-slave-period 10 #←发送ping到master的时间间隔 repl-timeout 60 #←IO超时时间 repl-backlog-size 1mb #←backlog的大小，当从库连接不到主库时，backlog的队列能放多少 repl-backlog-ttl 3600 #←backlog的生命周期 min-slaves-max-lag 10 #←延迟小于min-slaves-max-lag秒的slave才认为是健康的slave # 当master不可用,Sentinel会根据slave的优先级选举一个master。 # 最低的优先级的slave,当选master.","title":"redis主从复制工作原理"},{"content":"1 防火墙实战 关闭两项功能：\nselinux（生产中也是关闭的），ids入侵检测，MD5指纹将。系统所有核心文件全部做指纹识别，将指纹留下，将来出问题，一看就知道那个文件被改过。 iptables（生产中看情况，内网关闭，外网打开），大并发的情况，不能开iptables，影响性能。 使用防火墙就不如不使用防火墙，不使用防火墙的前提是不给外网ip，工作中要少给外网服务器ip，这样防火墙使用率较低，防火墙使用也很消耗资源 安全优化：\n尽可能不给服务器配置外网IP。可以通过代理转发或者通过防火墙映射。 并发不是特别大情况再外网IP的环境，要开启iptables防火墙 http://edu.51cto.com/course/course_id-772.html\n学好iptables基础：\nOSI7层模型以及不同层对应那些协议？ TCP/IP三次握手，四次断开的过程，TCP HEADER。 常用的服务端口要了如指掌。 1.1 iptables防火墙简介 Netfilter/iptables(以下简称iptables)是unix/linux自带的一款优秀且开放源代码的完全自由的基于包过滤的防火墙工具，它的功能十分强大，使用非常灵活，可以对流入和流出的服务器数据包进行很精细的控制。特别是他可以在一台非常低的硬件配置下跑的非常好（赛扬500MHZ 64M内存的情况部署网关防火墙）提供400人的上网服务四号==不逊色企业级专业路由器防火墙==。iptables+zebra+squid\niptables是linux2.4及2.6内核中集成的服务。其功能与安全性比其老一辈ipwadin ipchains强大的多（长江水后浪推前浪），iptables主要工作在OSI七层的二、三、四层，如果重新编译内核，iptables也可以支持7层控制（squid代理+iptables）。\n1.2 iptables名词和术语 容器：包含或者说属于关系\n什么是容器？\n​\t在iptables里，就是用老描述这种包含或者说属于的关系\n什么是Netfilter/iptables?\n​\tNetfilter是表（tables）的容器\n什么是表（tables）？\n​\t表是链的容器，所有的链（chains）都属于其对应的表。\n什么是链（chains）？\n​\t链（chains）是规则的容器\n什么是规则（policy）\n​\tiptables一系列过滤信息的规范和具体方法条款\niptables抽象和实际比喻对比表\r| Netfilter | tables | chains | policy |\r| --------- | ---------- | ------------ | -------------------- |\r| 一栋楼 | 楼里的房子 | 房子里的柜子 | 柜子里衣服的摆放规则 |\r1.3 iptables工作流程 iptables是采用数据包过滤机制工作的，所以他会对请求的数据包包头数据进行分析，并根据我们预先设定的规则进行匹配来决定是否可以进入主机。\n数据包的流向是从左向右的!\niptables工作小结：\ntext 1 2 3 4 防火墙是一层层过滤的。实际是按照配置规则的顺序从上到下，从前到后进行过滤的。 如果匹配上规则，即明确表明是阻止还是通过，此时数据包就不在向下匹配新规则了。 如果所有规则中没有明确表明是阻止还是通过这个数据包，也就是没有匹配上规则，向下进行匹配，知道匹配默认规则得到明确的阻止还是通过。 防火墙的默认规则是对应链的所有的规则执行完才会执行的。 提示：\niptables防火墙规则的执行顺序默认从前到后（从上到下）依次执行，遇到匹配的规则就不在继续向下检查，只有遇到不匹配的规则才会继续向下进行匹配。\n重点：匹配上了拒绝规则也是匹配，这点要多注意。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 iptables -A INPUT -p tcp --dport 3306 -j DROP iptables -A INPUT -p tcp --dport 3306 -j ACCEPT $ iptables -nL Chain INPUT (policy ACCEPT) DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 Chain FORWARD (policy ACCEPT) target prot opt source destination REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited Chain OUTPUT (policy ACCEPT) target prot opt source destination 此时 ``telnet10.0.0.148 3306是不通的，原因就是telnet请求已匹配上了拒绝规则iptables -A INPUT -p tcp \u0026ndash;dport 3306 -j DROP,因此，不会在找下面的规则匹配了。如果希望telnet 10.0.0.148 3306 连通，可以吧ACCEPT规则中的-A改为-I，即 iptables -I INPUT -p tcp \u0026ndash;dport 3306 -j ACCEPT` 把允许规则放于INPUT链第一行生效\n==默认规则是所有的规则执行完才会执行的==。\n1.4 iptables表（tables）和链（chains） 默认情况下，iptables根据功能和表的定义划分包含三个表，filter nat mangle，其每个表有包含不同的操作链（chains）。\n下面的表格展示了表和链的对应关系\n表\r链（chains）\rINPUT\rFORWARD\rOUTPUT\rPREROUTING\rPOSTROUTING\rFilter\rNAT\rMangle\r注：绿色表示有， 灰色表示无\r**提示：所有链名要大写**\r表名\r作用\rfilter\r强调主要和主机自身有关，真正负责主机防火墙功能的（过滤流入流出主机的数据包）。filter表是iptables默认使用的表。这个表定义了三个链（chains）。企业工作场景：主机防火墙。\rnat\r负责网络地址转换，即来源与目的IP地址和port的转换。应用：和主机本身无关。一般用于局域网共享上网或者特殊的端口转换服务相关。\nNAT功能一般企业工作场景\n1. 用于做企业路由（zebra）或网关（iptables），共享上网（POSTROUTING）\n2. 做内部外部IP地址一对一映射（dmz），硬件防火墙映射IP到内部服务器，ftp服务，（PREROUTING）\n3. web，单个端口的映射，直接映射80端口（PREROUTING）。这个表示定义了三个链（chains），nat功能就相当于网络的acl控制。和网络交换机acl类似。\rMangle\r主要负责修改数据包中特殊的路由标记，如TTL，TOS，NARK等。这个表定义了5个链\r链名\r作用\rINPUT\r负责过滤所有目标地址是本机地址的数据包。通俗的讲，就是过滤进入主机的数据包\rFORWAED\r负责转发流经主机的数据包。起转发的作用，和NAT关系很大，后面会详细讲LVS NAT模式。\rPREROUTING\r在数据包到达防火墙时进行路由判断之前执行的规则，作用是改变数据包的目的地址、目的端口等。（通俗比喻，就是收信时，根据规则重写收件人的地址，这看上去很不地道啊！）\r例如：把公网IP：124.42.60.113映射到居于玩分10.0.0.19服务器上。如果是web服务，可以把80端转为局域网的服务器上9000端口。\rPOSTROUTING\r在数据包离开防火墙时进行路由判断之后执行的规则，作用改变数据包的源地址、源端口等。（通俗比喻，就是寄信时，写好发件人的地址，要让人家回信时能后有地址可回。）例如：我们在现在的笔记本和虚拟机都是10.0.0.0/24，就是楚王的时候被我们企业路由器把源地址改成了公网地址了。生产应用：局域网共享上网。\r由于这个表与特殊标记相关，一般情况下，我们用不到这个mangle表，这里就不做详细介绍了。给初学者的建议：新手学习时最好抓住一个主线向前学，能够跑通路就好，不一定要面面俱到，不然很容易陷进去，而苦恼，甚至失去学习的兴趣\n1.5 iptables表和链的流程图 下面这张图清晰的描绘了netfilter对包的处理流程\n为了更好的学习将上图简化为如下\n图1-1 iptables简化流程图\r强调：上图可以看作地铁1 2号线来\r1号线：主要是NAT功能，\n​\t企业案例：\n​\t1. 局域网上网共享（路由和网关），使用NAT的POSTROUTING链\n​\t2. 外部IP和端口映射为内部IP和端口（DMZ功能），使用NAT的PREROUTING链。\n2号线：主要是FILTER功能，即防火墙功能 FILTER INPUT FORWARD\n​\t企业案例：\n​\t主要应用就是主机服务器防火墙，使用FILTER的INPUT链\n","permalink":"https://www.161616.top/ch1-iptables-introduction/","summary":"1 防火墙实战 关闭两项功能：\nselinux（生产中也是关闭的），ids入侵检测，MD5指纹将。系统所有核心文件全部做指纹识别，将指纹留下，将来出问题，一看就知道那个文件被改过。 iptables（生产中看情况，内网关闭，外网打开），大并发的情况，不能开iptables，影响性能。 使用防火墙就不如不使用防火墙，不使用防火墙的前提是不给外网ip，工作中要少给外网服务器ip，这样防火墙使用率较低，防火墙使用也很消耗资源 安全优化：\n尽可能不给服务器配置外网IP。可以通过代理转发或者通过防火墙映射。 并发不是特别大情况再外网IP的环境，要开启iptables防火墙 http://edu.51cto.com/course/course_id-772.html\n学好iptables基础：\nOSI7层模型以及不同层对应那些协议？ TCP/IP三次握手，四次断开的过程，TCP HEADER。 常用的服务端口要了如指掌。 1.1 iptables防火墙简介 Netfilter/iptables(以下简称iptables)是unix/linux自带的一款优秀且开放源代码的完全自由的基于包过滤的防火墙工具，它的功能十分强大，使用非常灵活，可以对流入和流出的服务器数据包进行很精细的控制。特别是他可以在一台非常低的硬件配置下跑的非常好（赛扬500MHZ 64M内存的情况部署网关防火墙）提供400人的上网服务四号==不逊色企业级专业路由器防火墙==。iptables+zebra+squid\niptables是linux2.4及2.6内核中集成的服务。其功能与安全性比其老一辈ipwadin ipchains强大的多（长江水后浪推前浪），iptables主要工作在OSI七层的二、三、四层，如果重新编译内核，iptables也可以支持7层控制（squid代理+iptables）。\n1.2 iptables名词和术语 容器：包含或者说属于关系\n什么是容器？\n​\t在iptables里，就是用老描述这种包含或者说属于的关系\n什么是Netfilter/iptables?\n​\tNetfilter是表（tables）的容器\n什么是表（tables）？\n​\t表是链的容器，所有的链（chains）都属于其对应的表。\n什么是链（chains）？\n​\t链（chains）是规则的容器\n什么是规则（policy）\n​\tiptables一系列过滤信息的规范和具体方法条款\niptables抽象和实际比喻对比表\r| Netfilter | tables | chains | policy |\r| --------- | ---------- | ------------ | -------------------- |\r| 一栋楼 | 楼里的房子 | 房子里的柜子 | 柜子里衣服的摆放规则 |\r1.3 iptables工作流程 iptables是采用数据包过滤机制工作的，所以他会对请求的数据包包头数据进行分析，并根据我们预先设定的规则进行匹配来决定是否可以进入主机。","title":"ch1 iptables介绍"},{"content":"2 iptables命令帮助信息 有问题查帮助，下面是很全的帮助信息（必须拿下它）\nbash 1 $ iptables -h bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ iptables -nL # INPUT链 ACCEPT默认允许决策 Chain INPUT (policy ACCEPT) target prot opt source destination ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt:22 REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited Chain FORWARD (policy ACCEPT) target prot opt source destination REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited Chain OUTPUT (policy ACCEPT) target prot opt source destination 2.1 启动和查看iptables状态 bash 1 2 /etc/init.d/iptables start systemctl start iptables 实例演示1：\nbash 1 2 3 4 5 6 7 8 9 10 11 $ /etc/init.d/iptables status 表格：filter Chain INPUT (policy ACCEPT) num target prot opt source destination 1 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED 2ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 num target prot opt source destination $ iptables -nL Chain INPUT (policy ACCEPT) target prot opt source destination ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED 提示：如果遇到下面的无法启动IPTABLES的情况\nbash 1 2 3 4 $ /etc/init.d/iptables start $ /etc/init.d/iptables start $ /etc/init.d/iptables status Firewall is stopped 解决：setup -\u0026gt; Firewall Configuration -\u0026gt; enable\nIptables默认加载的内核模块\nbash 1 2 3 $ lsmod|egrep \u0026#34;nat|filter\u0026#34; iptable_filter 2793 1 ip_tables 17831 1 iptable_filter 加载如下模块到linux内核\nbash 1 2 3 4 5 6 7 modprobe ip_tables \\ modprobe iptable_filter \\ modprobe iptable_nat \\ modprobe ip_conntrack \\ modprobe ip_conntrack_ftp \\ modprobe ip_nat_ftp \\ modprobe ipt_state 2.2 iptables参数 参数选项 注释说明 -n num 数字 -L 列表 -F 清除所有规则，不会处理默认的规则 -X 删除用户自定义的链 -Z 链的计数器清零 -t 指定表 -A 添加协议 -p 协议（all tcp udp Icmp）默认为all \u0026ndash;dport 目的端口 \u0026ndash;sport 源端口 -j 处理的行为（ACCEPT接受 DROP丢弃 REJECT拒绝） -D 删除规则 -A 添加规则到指定链的结尾 -I 添加规则到指定链的开头 -s 指定源地址 \u0026ndash;line-numbers 显示序号 -i \u0026lt;网络接口\u0026gt; 指定数据包进入本机的网络接口。 -o \u0026lt;网络接口\u0026gt; 指定数据包要离开本机所使用的网络接口。 2.3 清除默认规则 iptables -F 清除所有规则，不会处理默认的规则 iptables -X 删除用户自定义的链 iptables -Z 链的计数器清零 实例演示2：\nbash 1 2 3 iptables -F == iptables --flush iptables -X == iptables --delete-chain iptables -Z 禁止规则\n1.禁止ssh端口\n(1) 找出当前机器SSH端口\nbash 1 2 $ netstat -lntup|grep ssh tcp 0 0 192.168.1.5:52113 0.0.0.0:* LISTEN 1053/sshd (2) 禁止掉当前SSH端口，这里是52113\nbash 1 iptables -t [table] -[AD] chain rule-specification [options] 具体命令\nbash 1 2 iptables -A INPUT -p tcp --dport 52113 -j DROP iptables -tfilter -A INPUT -p tcp --dport 52113 -j DROP\t注：\ntxt 1 2 3 1. iptables默认用的就是filter表，因此，以上两条命令等价。 2. 其中INPUT DROP要大写 3. --jump -j target target for rule（may load target extension）基本处理行为：ACCEPT（接受）、DROP（丢弃）、REJECT（拒绝）。比较：DROP好于REJECT（不要给reject，拒绝会给对方信息，透漏信息了） 命令行执行的规则，仅仅在内存里临时生效。\nbash 1 2 3 $ iptables -A INPUT -p tcp --dport 52113 -j DROP $ ÐÅºÅµÆ³¬Ê±Ê 打台球：如果对方告诉你不去，REJECT（拒绝），如果对方没反应，DROP（丢弃）。\n(3) 恢复刚才断掉的SSH连接\ntxt 1 2 3 4 5 1. 去机房重启系统或者登陆服务器删除刚才的禁止规则。 2. 让机房人员重启服务器或让机房人员拿用户密码登陆进去。 3. 通过服务器的远程管理卡管理（推荐）。 4. 先写一个定时任务，每5分钟就停止防火墙。 5. 测试环境测试号，写成脚本，批量执行。 我们恢复的办法，登陆虚拟终端页面删除掉刚才的规则。当然也可执行iptables -F， iptables stop等。 练习：禁止用户访问80端口或3306端口：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 iptables -t filter -A INPUT -p tcp --dport 80 -j DROP $ telnet 192.168.1.5 80 Trying 192.168.1.5... Connected to 192.168.1.5. Escape character is \u0026#39;^]\u0026#39;. ^CConnection closed by foreign host. $ telnet 192.168.1.5 80 Trying 192.168.1.5... $ iptables -nL Chain INPUT (policy ACCEPT) target prot opt source destination DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 使用-I和-A的顺序，防火墙的过滤根据规则顺序的。\n-A 是添加规则到指定链的结尾，最后一条。\n-I 是添加规则到指定链的结尾，第一条。\nbash 1 2 3 $ iptables -nL Chain INPUT (policy ACCEPT) target prot opt source destination bash 1 2 3 4 5 $ iptables -A INPUT -p tcp -s 192.168.1.1 --dport 80 -j DROP $ iptables -nL Chain INPUT (policy ACCEPT) target prot opt source destination DROP tcp -- 192.168.1.1 0.0.0.0/0 tcp dpt:80 查看规则序号：\nbash 1 2 3 4 5 6 7 iptables -L -n --line-numbers $ iptables -L -n --line-numbers Chain INPUT (policy ACCEPT) num target prot opt source destination 1 DROP tcp -- 192.168.1.1 0.0.0.0/0 tcp dpt:80 2 DROP tcp -- 192.168.1.2 0.0.0.0/0 tcp dpt:80 Chain FORWARD (policy ACCEPT) 指定位置插入规则：插入到第二行\nbash 1 2 3 4 5 6 7 8 $ iptables -I INPUT 2 -p tcp -s 192.168.1.3 --dport 80 -j DROP $ iptables -L -n --line-numbers Chain INPUT (policy ACCEPT) num target prot opt source destination 1 DROP tcp -- 192.168.1.1 0.0.0.0/0 tcp dpt:80 2 DROP tcp -- 192.168.1.3 0.0.0.0/0 tcp dpt:80 3 DROP tcp -- 192.168.1.2 0.0.0.0/0 tcp dpt:80 Chain FORWARD (policy ACCEPT) 通过序号删除规则，删除上述第2条规则\nbash 1 2 3 4 5 6 7 $ iptables -D INPUT 2 == delete from iptables where id=2 $ iptables -L -n --line-numbers Chain INPUT (policy ACCEPT) num target prot opt source destination 1 DROP tcp -- 192.168.1.1 0.0.0.0/0 tcp dpt:80 2 DROP tcp -- 192.168.1.2 0.0.0.0/0 tcp dpt:80 Chain FORWARD (policy ACCEPT) 小结：总结删除规则的方法：\ntext 1 2 3 4 1. iptables -D INPUT -P tcp --dport 8080 -J DROP 2. iptables -F 删除所有规则 3. /etc/init.d/iptables restart （用iptables命令行配置的命令都是临时生效） 4. iptables -D INPUT 序列号 基于客户端源地址网段控制，禁止10.0.0.0网段连入\nbash 1 2 iptables -t filter -A INPUT -i eth0 -s 10.0.0.0/24 -J DROP iptables -A INPUT -i eth0 -s 10.0.0.0/24 -J DROP 注：iptables默认用的就是filter表，因此以上两条命令等价。 执行以上命令可以发现，我这里已经无法远程连接了。 登陆虚拟机，删除刚才禁止的来源地址为10网段的命令。 iptables -D INPUT -i eth0 -s 10.0.0.0/24 -J DROP (完整策略规则删除) iptables -D INPUT 1（根据策略在链中的序号删，每条链都是各自从1编号）。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ iptables -nL --line-numbers Chain INPUT (policy ACCEPT) num target prot opt source destination 1 DROP tcp -- 192.168.1.5 0.0.0.0/0 tcp dpt:80 2 DROP tcp -- 192.168.1.4 0.0.0.0/0 tcp dpt:80 3 DROP tcp -- 192.168.1.2 0.0.0.0/0 tcp dpt:80 Chain FORWARD (policy ACCEPT) n $ iptables -D INPUT 1 $ iptables -nL --line-numbers Chain INPUT (policy ACCEPT) num target prot opt source destination 1 DROP tcp -- 192.168.1.4 0.0.0.0/0 tcp dpt:80 2 DROP tcp -- 192.168.1.2 0.0.0.0/0 tcp dpt:80 Chain FORWARD (policy ACCEPT) 还可以通过“！”来取反\nbash 1 2 # 只有 192.168.1.1才可访问80端口 ！放在选项的前面而不是参数的前面 iptables -I INPUT -p tcp ! -s 192.168.1.1 --dport 80 -j DROP 测试配置拒绝规则也是匹配：下面的测试有两个要点：非的作用，匹配拒绝也是匹配。\ncentos5版本\nbash 1 iptables -I INPUT -p tcp -s ! 192.168.1.1 --dport 80 -j DROP centos6.4高版本：\nbash 1 2 3 $ iptables -t filter -A INPUT -i eth0 -s ! 10.0.0.115 -j DROP Using intrapositioned negation (`--option ! this`) is deprecated in favor of extrapositioned (`! --option this`). # 解决方案： iptables -t filter -A INPUT -i eth0 -s ! 10.0.0.115 -j DROP 测试非”！“\n1.源地址不是10.0.0.101单个IP的禁止链接\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 iptables -t filter -I INPUT -i eth0 ! -s 10.0.0.101 -j DROP iptables -A INPUT -p all -i eth0 ! -s 10.0.0.106 -j DROP # p(udp tcp icmp all) # 不让主机ping通 iptables -t filter -I INPUT -p icmp --icmp-type 8 -i eth0 -s ! 192.168.2.83 -j DROP # ssh 断开链接 $ iptables -t filter -I INPUT -i eth0 ! -s 192.168.2.83 -j DROP $ ÐÅºÅµÆ³¬Ê±Ê # ping 不通 C:\\Users\\Company\u0026gt;ping 192.168.2.83 正在 Ping 192.168.2.83 具有 32 字节的数据: 请求超时。 请求超时。 请求超时。 192.168.2.83 的 Ping 统计信息: 数据包: 已发送 = 4，已接收 = 0，丢失 = 4 (100% 丢失)， 2.原地址不是192.168.2.0/24的网段禁止连接\nbash 1 2 iptables -t filter -I INPUT -i eth0 -s ! 192.168.2.0/24 -j DROP == iptables -t filter -I INPUT -i eth0 -s 192.168.2.0/24 -j ACCECT # 工作场景 第一节讲了linux优化，更改root和和ssh端口\nbash 1 iptables -A INPUT -p tcp --dport 52113 ! -s 192.168.2.0/24 -J DROP 在默认规则为允许的情况下，上述可以封堵ssh访问。\n企业工作中解决这个问题：\ntext 1 2 3 1. vpn服务（拨号拨到VPN上，然后以VPN的内网地址区访问内部的机器地址）。 2. 前端对外提供服务器的机器SSH端口都做禁止外部IP访问限制，可以开启后端或者不对外提供服务的机器，保留SSH服务（更改root和SSH端口）。然后，我们平时就先连接此机器没在去连其他机器。 3. 流量特别大的外网机器不要开防火墙，会影响性能，购买硬件防火墙。 封掉3306端口\nbash 1 iptables -A INPUT -p tcp --dorp 3306 -j DROP 匹配指定的协议\nbash 1 iptables -A INPUT -P tcp # 如果不指定-p，默认就是all 匹配指定协议外的所有协议\nbash 1 2 iptables -A INPUT -p ! tcp iptables -I INPUT ! -p tcp -s 192.168.2.83 -j DROP 匹配网段\nbash 1 2 iptables -A INPUT -s 10.0.0.0/24 iptables -A INPUT ! -s 10.0.0.0/24 匹配单一端口\nbash 1 2 iptables -A INPUT -p tcp ! --sport 22 iptables -A INPUT -p tcp ! --dport 22 -s 10.0.0.20 -j DROP 匹配端口范围\nbash 1 2 3 4 iptables -A INPUT -p tcp --sport 22:80 iptables -I INPUT -p tcp --dport 21,22,23,24 # 错误语法 iptables -I INPUT -p tcp -m multiport --dport 18:80 -j DROP iptables -I INPUT -p tcp --dport 21:23 -j DROP # 最佳 实例1：测试匹配端口范围\nbash 1 2 3 4 5 6 7 8 iptables -F $ iptables -t filter -A INPUT -p tcp --dport 20:100 -j DROP C:\\Users\\Company\u0026gt;telnet 192.168.2.83 80 正在连接192.168.2.83...无法打开到主机的连接。 在端口 80: 连接失败 $ iptables -nL Chain INPUT (policy ACCEPT) target prot opt source destination DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpts:20:100 测试结果\nssh52113端口终端直接断掉 telnet连接80不通 bash 1 2 3 $ iptables -t filter -A INPUT -p tcp --dport 50000:60000 -j DROP $ ÐÅºÅµÆ³¬Ê±Ê 实例2:列举端口\nbash 1 2 3 4 5 $ iptables -t filter -A INPUT -p tcp -m multiport --dport 80,90,100 -j DROP $ iptables -nL Chain INPUT (policy ACCEPT) target prot opt source destination DROP tcp -- 0.0.0.0/0 0.0.0.0/0 multiport dports 80,90,100 测试结果：telnet连接80不通\nbash 1 2 $ telnet 192.168.1.5 80 正在连接192.168.1.5...无法打开到主机的连接。 在端口 80: 连接失败 匹配ICMC类型\niptables -A INPUT -p icmp --icmp-type 8\n例：iptables -A INPUT -p icmp --icmp-type 8 -j DROP\nbash 1 2 3 iptables -A INPUT -p icmp -m icmp --icmp-type any -j ACCEPT iptables -A FORWARD -s 192.168.1.0/24 -p icmp -m icmp --icmp-type any -j ACCEPT # 在工作中默认是拒绝状态，用什么开什么，只有内网允许ping 匹配指定的网络接口\nbash 1 2 iptables -A INPUT -i eth0 iptables -A FORWARD -o eth0 记忆方法：\nin-interface -i input name\nin-interface -o output name\n匹配网络状态\n-m state \u0026ndash;state\nNEW：已经或将启动洗呢连接\nESTABLISHED：已经建立的连接\nRELATED：正在启动的新连接\nINVALID：非法或无法识别的\nFTP服务是特殊的，需要配状态连接。\n允许关联的状态包通过（web服务不要使用ftp）\nbash 1 2 iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 比喻：看电影出去WC或者接个电话，回来也得允许进去。\n限制指定时间包的允许通过数量及并发数 -m limit --limit n/{second/minute/hour/day}\n指定时间内的请求速率“n”为速率，后面为时间分别为：秒时分\n--limit-burst [n]: 在同一时间内允许通过的请求“n”为数字，不指定默认为5\n","permalink":"https://www.161616.top/ch2-iptables-command/","summary":"2 iptables命令帮助信息 有问题查帮助，下面是很全的帮助信息（必须拿下它）\nbash 1 $ iptables -h bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ iptables -nL # INPUT链 ACCEPT默认允许决策 Chain INPUT (policy ACCEPT) target prot opt source destination ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt:22 REJECT all -- 0.0.0.0/0 0.","title":"ch2 iptables命令帮助信息"},{"content":"生产环境配置主机防火墙有两种模式：\n逛公园及看电影两种模式：\n逛公园：默认随便出进，对非法的分子进行拒绝。企业应用：企业配置上网网关路由。\n看电影：默认没有票进不去。花钱买票才能看电影。企业应用：服务器主机防火墙。\n很显然：第二种更严格，更安全。\n逛公园及看电影两种模式本事就是防火墙的默认规则是允许还是拒绝。\n1.清理当前所有规则和计数器\nbash 1 2 3 iptables -F iptables -Z iptables -X 2.配置允许SSH登陆端口进入\nbash 1 2 iptables -A INPUT -p tcp --dport 52113 -j ACCEPT iptables -A INPUT -p tcp -s 192.168.1.0/30 -j ACCEPT 提示：此步骤是为了防止执行下面的步骤，把自己关在外面，除非你在本地处理，这部可以不做。\n3.设置允许本机lo通讯规则\nbash 1 2 iptables -A INPUT -i lo -j ACCEPT iptables -A OUTPUT -o lo -j ACCEPT # output加不加都行，在工作环境上是加的 4.设置默认的防火墙禁止和允许规则\nbash 1 2 3 iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT 一般情况下OUTPUT我们不要drop，像电影院一样，电影已经看完了，中间不想看就回家了，你不可能说不行不能走，所以一般出去没人管，进来才收票，OUTPUT一般不设置，但是不设置也有风险，企业流量暴涨，由于服务器中病毒外发流量。\n查看结果\nbash 1 2 3 4 5 6 7 8 9 10 11 12 $ iptables -nL Chain INPUT (policy DROP) target prot opt source destination ACCEPT tcp -- 192.168.1.0/27 0.0.0.0/0 tcp dpt:52113 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 Chain FORWARD (policy DROP) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 5.开启网段信任\n允许IDC LAN/WAN和办公网ip的访问，及对外合作机构访问\n办公室固定IP段，IDC机房内网网段，其他机房内网网段，IDC机房外网网段\n例：\ntext 1 iptables -A INPUT -p all -s 192.168.1.0/27 -j ACCEPT 安全提示：要细化到掩码最小，租用阿里云攻击同网段案例\n6.允许icmp类型协议通过\nbash 1 iptables -A INPUT -p icmp -m icmp --icmp-type any -j ACCEPT 注： 如果不想开，就不执行此行命令。如果对内网开，对外不开就用下面方式。\nbash 1 iptables -A INPUT -p icmp -s 10.0.0.0/24 -m icmp --icmp-type any -j ACCEPT 8.允许关联的状态包通过（web服务不要使用FTP）\n通过其他服务器扫描我们配置的防火墙：\nbash 1 2 3 4 5 6 7 8 9 10 11 $ nmap 192.168.1.5 -p 1-65535 # 时间很长 Starting Nmap 5.51 ( http://nmap.org ) at 2016-10-31 01:24 CST Nmap scan report for 192.168.1.5 Host is up (0.00024s latency). Not shown: 65533 filtered ports PORT STATE SERVICE 80/tcp open http 52113/tcp open unknown MAC Address: 00:0C:29:BE:2D:75 (VMware) Nmap done: 1 IP address (1 host up) scanned in 117.86 seconds 在命令行操作的每一条命令都是在内存里 ，没有写入磁盘里，重启服务就丢了\n在上面的命令行配置中所有的命令结果仅仅存在放于内存中，重启服务就会丢失。因此，我们有必要保存成配置文件。 法一：\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ /etc/init.d/iptables save iptables：将防火墙规则保存到 /etc/sysconfig/iptables：[确定] $ cat /etc/sysconfig/iptables # Generated by iptables-save v1.4.7 on Tue Nov 15 02:50:43 2016 *filter :INPUT DROP [133073:5855518] :FORWARD DROP [0:0] :OUTPUT ACCEPT [202:14432] -A INPUT -s 192.168.1.0/27 -p tcp -m tcp --dport 52113 -j ACCEPT -A INPUT -p icmp -m icmp --icmp-type any -j ACCEPT -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT -A OUTPUT -o lo -j ACCEPT COMMIT # Completed on Tue Nov 15 02:50:43 2016 法二：\nshell 1 $ iptables-save \u0026gt;/etc/sysconfig/iptables 提示：/etc/sysconfig/iptables为iptables的默认配置文件路径\n提示：第一次保存可以覆盖，以后保存只能追加\n","permalink":"https://www.161616.top/ch3-iptables-configuration/","summary":"生产环境配置主机防火墙有两种模式：\n逛公园及看电影两种模式：\n逛公园：默认随便出进，对非法的分子进行拒绝。企业应用：企业配置上网网关路由。\n看电影：默认没有票进不去。花钱买票才能看电影。企业应用：服务器主机防火墙。\n很显然：第二种更严格，更安全。\n逛公园及看电影两种模式本事就是防火墙的默认规则是允许还是拒绝。\n1.清理当前所有规则和计数器\nbash 1 2 3 iptables -F iptables -Z iptables -X 2.配置允许SSH登陆端口进入\nbash 1 2 iptables -A INPUT -p tcp --dport 52113 -j ACCEPT iptables -A INPUT -p tcp -s 192.168.1.0/30 -j ACCEPT 提示：此步骤是为了防止执行下面的步骤，把自己关在外面，除非你在本地处理，这部可以不做。\n3.设置允许本机lo通讯规则\nbash 1 2 iptables -A INPUT -i lo -j ACCEPT iptables -A OUTPUT -o lo -j ACCEPT # output加不加都行，在工作环境上是加的 4.设置默认的防火墙禁止和允许规则\nbash 1 2 3 iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT 一般情况下OUTPUT我们不要drop，像电影院一样，电影已经看完了，中间不想看就回家了，你不可能说不行不能走，所以一般出去没人管，进来才收票，OUTPUT一般不设置，但是不设置也有风险，企业流量暴涨，由于服务器中病毒外发流量。","title":"ch3 iptables配置防火墙"},{"content":"生产中，一般第一次添加规则命令行或者脚本加入然后一次性保存成文件，然后可以改配置文件管理：\nbash 1 2 3 4 5 6 7 8 9 $ cat /etc/sysconfig/iptables # Generated by iptables-save v1.4.7 on Wed Nov 23 09:18:12 2016 *filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [115:13341] -A INPUT -p tcp -m tcp --dport 52113 -j ACCEPT COMMIT # Completed on Wed Nov 23 09:18:12 2016 生产维护：\n⑴ 确定规则\nsh 1 2 3 4 5 vim /etc/sysconfig/iptables # 加入想要的规则：例如： -A INPUT -p tcp -m tcp --dport 873 -j ACCEPT /etc/init.d/iptables reload # 或者修改配置的同时命令行再执行，也是永久生效 ⑵ 命令行试错，没问题了，然后放配置文件。这时不需要重启了。\n封IP，第一行封。10.0.0.115 这个机器攻击我们服务器或者在BBS里发垃圾帖子。\n手工封IP：\nsh 1 2 iptables -I INPUT -s 10.0.0.115 -j DROP # 范围大，外部攻击者。 iptables -I INPUT -p tcp -s 10.0.0.106 --dport 80 -j DROP # 细，范围小 内部 自动封IP：分析web或应用日志或者网络连接状态封掉垃圾IP\n详见：shell笔记\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #!/bin/sh IPT=/sbin/iptables # remove any existing rules $IPT -F $IPT -X $IPT -Z #setting default firewall policy $IP #setting forloopback interface $IPT -A INPUT -i lo -j ACCEPT $IPT -A OUTPUT -o lo -j ACCEPT # source address spoofing and other bad addresses $IPT -A INPUT -i eth0 -s 192.168.2.0/24 -j DROP $IPT -A INPUT -i eth0 -s 0.0.0.0/8 -j DROP #prevent all stealth scans and tcp state flags $IPT -A INPUT -p tcp --tcp-flags ALL ALL -j DROP #All of the bits are cleared $IPT -A INPUT -p tcp --tcp-flags ALL NONE -j DROP $IPT -A INPUT -p tcp --tcp-flags ALL,FIN,URG,PSH -j DROP #SYN and RST are both set $IPT -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP #SYN and FIN are both set $IPT -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP # FIN is the only bit set ,whitout the expected accompanying ACK $IPT -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j DROP # PSH is the only bit set ,whitout the expected accompanying ACK $IPT -A INPUT -p tcp --tcp-flags ACK,PSH PSH -j DROP # URG is the only bit set ,whitout the expected accompanying ACK $IPT -A INPUT -p tcp --tcp-flags ACK,URG URG -j DROP #setting access rules #one,ip access rules,allow, all the ips of $IPT -A INPUT -s 10.0.10.0/24 -p tcp --dport 5666 -j ACCEPT $IPT -A INPUT -s 10.0.0.0/24 -p tcp --dport 5666 -j ACCEPT #db $IPT -A INPUT -s 10.0.0.0/24 -p tcp --dport 3306 -j ACCEPT $IPT -A INPUT -s 10.0.0.0/24 -p tcp --dport 3307 -j ACCEPT #ssh difference from other servers here $IPT -A INPUT -s 10.0.0.0/24 -p tcp --dport 52113 -j ACCEPT $IPT -A INPUT -s 10.0.0.0/24 -p tcp --dport 22 -j ACCEPT #http $IPT -A INPUT -p tcp --dport 80 -j ACCEPT #snmp $IPT -A INPUT -s 10.0.0.0/24 -p UDP --dport 161 -j ACCEPT #rsync $IPT -A INPUT -s 10.0.0.0/24 -p tcp --dport 873 -j ACCEPT $IPT -A INPUT -s 10.0.10.0/24 -p tcp --dport 873 -j ACCEPT #icmp $IPT -A INPUT -p icmp -m icmp --icmp-type any -j ACCEPT #others RELATED $IPT -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT $IPT -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT #setting default firewall rules $IPT -P OUTPUT ACCEPT $IPT --policy FORWARD DROP $IPT --policy INPUT DROP 技巧：具备外网IP的服务器上不对外的服务最好要做源地址限制。\n对外提供服务，不能做源地址限制，例如：80端口。\n问题：企业硬件防火墙和IPTABLES防火墙是否要同时用。\n解决：可以同时用。\n举例：\n企业硬件防火墙一般放在网关位置，相当于大厦的保安。\n但是，楼道里的每个屋子还是需要有人或者锁门的（iptables）\n问题：IDC机房部署了硬件防火墙，我们的服务器可以不开防火墙吗？\n​\t解答：绝对不可以！大厦有保安，你的办公室就不锁门么？\n","permalink":"https://www.161616.top/ch4-iptables-p/","summary":"生产中，一般第一次添加规则命令行或者脚本加入然后一次性保存成文件，然后可以改配置文件管理：\nbash 1 2 3 4 5 6 7 8 9 $ cat /etc/sysconfig/iptables # Generated by iptables-save v1.4.7 on Wed Nov 23 09:18:12 2016 *filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [115:13341] -A INPUT -p tcp -m tcp --dport 52113 -j ACCEPT COMMIT # Completed on Wed Nov 23 09:18:12 2016 生产维护：\n⑴ 确定规则\nsh 1 2 3 4 5 vim /etc/sysconfig/iptables # 加入想要的规则：例如： -A INPUT -p tcp -m tcp --dport 873 -j ACCEPT /etc/init.","title":"ch4 生产环境如何维护iptables"},{"content":"1 办公室路由网关架构图 对应实际企业办公上网场景逻辑图\n2.实验环境配置需求前期准备 2.1 服务器网关B需要准备如下条件 物理条件是具备上网卡，建议eth0外网地址（这里是192.168.1.5,gw 192.168.1.2），ech1内网地址（这里是172.168.1.10，内网卡不配GW。 确保服务器网关B要可以上网（B上网才能代理别的机器上网）。可以通过ping baidu.com或外网IP测试。 内核文件/etc/sysctl.conf里开启转发功能。在服务器网关B192.168.1.5机器上开启路由转发功能。编辑/etc/sysctl.conf修改内容为net.ipv4.ip_forward = 1，然后执行sysctl -p使修改生效 iptables的filter表的FORWARD链允许转发 不要filter防火墙功能，共享上网，因此最好暂停防火墙测试/etc/init.d/tables stop 2.2 加载iptables内核模块 配置网关需要iptables的nat表，PREROUTING，POSTROUTING。\n(1)载入iptables内核模块，执行并放入rc.local\nbash 1 2 3 4 5 6 7 modprobe ip_tables \\ modprobe iptable_filter \\ modprobe iptable_nat \\ modprobe ip_conntrack \\ modprobe ip_conntrack_ftp \\ modprobe ip_nat_ftp \\ modprobe ipt_state bash 1 2 3 $ lsmod|egrep ^ip iptable_nat 6051 0 iptable_filter 2793 0 2.3 局域网的机器： 局域网的机器有一块网卡即可，确保局域网的机器C，默认网关这只了网关服务器B的eth1内网卡IP（172.168l.1.10）。把主机C的gateway设置为B的内网卡192的网卡ip即172.168l.1.10。 检查手段： 分别ping网关服务器B的内外网卡IP，都应该是通的就对了. 出公网检查除了PING网站域名外，也要ping下外网ip，排除DNS故障。不通 ping 10.0.0.254网关也是不通的。 如上，请准备两台虚拟机B和C，其中B要有双网卡。B的内网卡的网段和C的网段一样。\n网关B：假设192.168.1.0/24为外部IP，172.168.1.0/24为内部IP\neth0:192.168.1.5 IPADDR=192.168.1.5\ngw:192.168.1.2\tGATEWAY=192.168.1.2\neth1 eth1:172.168.1.10\ngw：不配\n内部服务器C：\neth0：172.168.1.11 IPADDR=172.168.1.11\ngw：172.168.1.10（网关B的内网卡IP）\tGATEWAY=172.168.1.10\n准备结果：\nB网关服务器配置\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ ifconfig eth0 Link encap:Ethernet HWaddr 00:0C:29:31:E5:AF inet addr:192.168.1.4 Bcast:192.168.1.255 Mask:255.255.255.0 ...... eth1 Link encap:Ethernet HWaddr 00:0C:29:31:E5:B9 inet addr:172.168.1.10 Bcast:172.168.1.255 Mask:255.255.255.0 # 路由 $ route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 172.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1 169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0 169.254.0.0 0.0.0.0 255.255.0.0 U 1003 0 0 eth1 0.0.0.0 192.168.1.2 0.0.0.0 UG 0 0 0 eth0 C为内网PC或者服务器\nbash 1 2 3 4 5 6 7 8 9 10 $ ifconfig eth0 Link encap:Ethernet HWaddr 00:0C:29:BE:2D:75 inet addr:172.168.1.11 Bcast:172.168.1.255 Mask:255.255.255.0 .... $ route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 172.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0 0.0.0.0 172.168.1.10 0.0.0.0 UG 0 0 0 eth0 3 先做一些测试记录 1.登陆C主机172.168.1.11看是否能访问外部页面（配好DNS）。如ping www.baidu.com。正确结果：当前情况不通 lamp为10 lnmp为11\nbash 1 2 3 4 $ ping www.baidu.com ^C $ ping 61.105.221.1 PING 61.105.221.1 (61.105.221.1) 56(84) bytes of data. 2.在笔记本上分别测试telnet 172.168.1.10 22看是否能连通、结果:当前情况通。\nbash 1 2 3 4 5 6 7 8 $ telnet 172.168.1.10 80 Trying 172.168.1.10... Connected to 172.168.1.10. Escape character is \u0026#39;^]\u0026#39;.\t$ telnet 172.168.1.10 80 Trying 172.168.1.10... Connected to 172.168.1.10. Escape character is \u0026#39;^]\u0026#39; 在笔记本上分别测试ping 172.168.1.11看是否能连通。结果当前情况通。 测试登陆172.168.1.10看是否能访问外部页面。如ping www.baidu.com结果当前情况通。 在笔记本上分别测试telnet C主机 172.168.1.11 22 结果当前情况不通。 在笔记本上分别测试ping 172.168.1.11看是否能连通。结果当前情况不通。 4 根据逻辑图实现如下要求 4.1 局域网共享上网项目案例 1.实现c可经过b，通过A上因特网。 解答：提示2:注意主机防火墙功能的影响，可以尝试在GW上先/etc/init.d/iptables stop后在加命令\n2.实际处理的局域网共享上网NAT命令 局域网共享的两条命令方法：\n方法1：适合于有固定ip外网地址的：\nbash 1 iptables -t nat -A POSTROUTING -s 172.168.1.0/24 -o eth0 -j SNAT --to-source 192.168.1.4 -s 172.168.1.0/24办公室或IDC、内网网段。 -o eth0 为网关的外网卡接口。 -j SNAT \u0026ndash;to-source 192.168.1.4是网关外网卡IP地址。 方法2：适合变化外网地址（ADSL）\nbash 1 iptables -t nat -A POSTROUTING -s 172.168.1.0/24 -j MASQUERADE 测试结果\nbash 1 2 3 4 5 6 7 8 9 10 11 $ ping www.baidu.com PING www.a.shifen.com (58.217.200.112) 56(84) bytes of data. 64 bytes from 58.217.200.112: icmp_seq=1 ttl=127 time=32.1 ms $ iptables -t nat -nL Chain PREROUTING (policy ACCEPT) target prot opt source destination Chain POSTROUTING (policy ACCEPT) target prot opt source destination SNAT all -- 172.168.1.0/24 0.0.0.0/0 to:192.168.1.4 为什么要用POSTROUTING？\n企业共享上网：\n1.办公网共享上网（网关要有外网IP，否则用路由（zebra））\n2.IDC内网机器上网\n企业上网到底需要不需要linux网关？\n解答：\n如果企业里有企业级路由器的情况下，可以不需要上网网关。使用网关只是解决路由器无法解决的需求（例如：上网行为，IP及端口的映射，网关杀毒）。 IDC机房，大厦有固定IP的宽带，直接用网关解决上网及控制问题。 4.2 把外部IP地址及端口映射到内部服务器地址及端口（和贡献上网环境一样） 在10段主机可以通过访问192.168.1.4:80，即可访问到192.168.1.8:9000 提供的web服务。也可SSH（192.168.1.4:222 \u0026ndash;\u0026gt; 192.168.1.8:52113）\u0026lt;== PREROUTING\nC配置WEB服务器\n解答：\n⑴ 在172.168.1.10开启http服务监听9000端口，然后在网关服务器B可以访问\n⑵ 具体转换命令：\nbash 1 2 iptables -t nat -A PREROUTING -d 192.168.1.5 -p tcp --dport 9000 -j DNAT --to-destination 172.168.1.11:80 # DNAT：目的地址转换，将将本地内部的地址映射到互联网地址 测试结果\n清空NAT表的规则。\nbash 1 iptables -t nat -F 这个时候访问83的9000端口是不能访问的\nbash 1 iptables -t nat -A PREROUTING -d 192.168.2.83 -p tcp --dport 9000 -j DNAT --to-destination 172.168.1.11:80 这里看到访问192.168.1.5的9000端口就会映射到内网172.168.1.11的80上。\nssh转发实验 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 网关A IP 192.168.2.83 内网IP 172.168.1.10 $ ifconfig eth0 Link encap:Ethernet HWaddr 00:0C:29:AF:21:4F inet addr:192.168.2.83 Bcast:192.168.2.255 Mask:255.255.255.0 eth1 Link encap:Ethernet HWaddr 00:50:56:20:37:C2 inet addr:172.168.1.10 Bcast:172.168.1.255 Mask:255.255.255.0 # 在网关上设置转发 iptables -t nat -I PREROUTING -p tcp -m tcp --dport 9020 -j DNAT --to-destination 172.168.1.11:52113 # 用外网访问网关外网IP $ ssh -p 9020 192.168.2.83 ..... root@192.168.2.83\u0026#39;s password: Last login: Mon Dec 12 22:24:58 2016 from 192.168.2.84 $ ifconfig inet addr:172.168.1.11 Bcast:172.168.1.255 Mask:255.255.255.0 强调：有个网友说网关服务需要开启80服务，但不需要对外服务？\n测试结果：网关开启httpd:80后。\n此时，来自80端口的请求转发依然会转发到后端的服务器。但是iptables nat规则删除后，此时就到达了http服务的80端口所以显示的是默认页面。\n企业应用场景：\n把访问外网IP及读研口的请求映射到内网某个服务器及端口（企业内部）。 硬件防火墙，把访问LVS/nginx外网VIP及80端口的请求映射到IDC负载均衡器内部IP及端口上（IDC机房的操作） iptables企业常用案例：\nlnux主机防火墙（表FILTER INPUT链） 局域网机器共享上网（表：NAT POSTROUTING链） bash 1 iptables -t nat -A POSTROUTING -s 172.168.1.0/24 -o eth0 -j SNAT --to-source 192.168.1.5 外部地址和端口，映射为内部地址和端口（表：NAT PREROUTING） bash 1 iptables -t nat -A PREROUTING -d 192.168.1.5 -p tcp --dport 80 -j DNAT --to-destination 172.168.1.11:9000 4.3 实现192段外网IP和172段内网IP一对一映射 网关IP：eth0:192.168.1.5 ech1:172.168.1.10\n首先在路由网关上绑定接口外网ip，可以是别名的方式。\nbash 1 2 3 4 5 6 # 访问外网IP就映射到0.8 -A PREROUTING -d 124.42.34.112 -j DNAT --to-destination 10.0.0.8 # 出网时候改回去 -A POSTROUTING -s 10.0.0.8 -j SNAT --to-destination 124.42.34.112 # 当局域网使用外网IP访问这台机器，会出现问题，只要是局域网访问这个地址，冲定向到网关，防止可能环路 -A POSTROUTING -s 10.0.0.0/255.255.240.0 -d 124.42.34.112 -j SNAT --to-source 10.0.0.254 4.4 实现192段机器和10段机器互相访问 http://v.youku.com/v_show/id_XNTAyMjAwMzI0.html\n","permalink":"https://www.161616.top/ch5-iptables-nat/","summary":"1 办公室路由网关架构图 对应实际企业办公上网场景逻辑图\n2.实验环境配置需求前期准备 2.1 服务器网关B需要准备如下条件 物理条件是具备上网卡，建议eth0外网地址（这里是192.168.1.5,gw 192.168.1.2），ech1内网地址（这里是172.168.1.10，内网卡不配GW。 确保服务器网关B要可以上网（B上网才能代理别的机器上网）。可以通过ping baidu.com或外网IP测试。 内核文件/etc/sysctl.conf里开启转发功能。在服务器网关B192.168.1.5机器上开启路由转发功能。编辑/etc/sysctl.conf修改内容为net.ipv4.ip_forward = 1，然后执行sysctl -p使修改生效 iptables的filter表的FORWARD链允许转发 不要filter防火墙功能，共享上网，因此最好暂停防火墙测试/etc/init.d/tables stop 2.2 加载iptables内核模块 配置网关需要iptables的nat表，PREROUTING，POSTROUTING。\n(1)载入iptables内核模块，执行并放入rc.local\nbash 1 2 3 4 5 6 7 modprobe ip_tables \\ modprobe iptable_filter \\ modprobe iptable_nat \\ modprobe ip_conntrack \\ modprobe ip_conntrack_ftp \\ modprobe ip_nat_ftp \\ modprobe ipt_state bash 1 2 3 $ lsmod|egrep ^ip iptable_nat 6051 0 iptable_filter 2793 0 2.3 局域网的机器： 局域网的机器有一块网卡即可，确保局域网的机器C，默认网关这只了网关服务器B的eth1内网卡IP（172.168l.1.10）。把主机C的gateway设置为B的内网卡192的网卡ip即172.168l.1.10。 检查手段： 分别ping网关服务器B的内外网卡IP，都应该是通的就对了. 出公网检查除了PING网站域名外，也要ping下外网ip，排除DNS故障。不通 ping 10.0.0.254网关也是不通的。 如上，请准备两台虚拟机B和C，其中B要有双网卡。B的内网卡的网段和C的网段一样。","title":"ch5 配置网关及服务器地址映射"},{"content":" 1、局域网共享上网（适合做企业内部局域网上网网关，以及IDC机房内网的上网网关 nat POSTROUTING）\n2、服务器防火墙功能（适合IDC机房具有外网IP服务器，主要是filter INPUT的控制）\n3、把外部IP及端口映射到局域网内部（可以一对一IP映射，也可针对某一个端口映射。）\n也可能是IDC把网站的外网VIP级网站端口映射到负载均衡器上（硬件防火墙）（NAT PREROUTING）\n4、办公路由器+网关功能（zebra路由+iptables过滤及NAT+squid正向透明代理80+ntop/iftop/iptaf流量查看+tc/cbq流量控制限速）。\n5、邮件的网关。\n问题2：的生产环境应用：用于没有外网地址的内网服务器，映射为公网IP后对外提供服务，也包括端口的映射\n问题3：IP一对一映射 用于没有外网地址的内网服务器，映射为公网IP后对外提供服务，例如：ftp服务要一对一IP映射。\n共享上网封IP的方法：\nbash 1 2 /sbin/iptables -I FROWAED -s 10.0.0.26 -j　DROP /sbin/iptables ${deal} FROWARD -m mac --mac -source ${strIpMac} -j DROP 映射多个外网IP上网 bash 1 2 iptables -t nat -A POSTROUTING -s 10.0.1.0/255.255.240.0 -o eth0 -j SNAT --to-source 124.42.60.11-124.42.60.16 iptables -t nat -A POSTROUTING -s 172.168.1.0/255.255.255.0 -o eth0 -j SNAT --to=source 124.42.60.60-124.42.60.63 问题：公司内网主机多的时候，访问网站容易被封。\n","permalink":"https://www.161616.top/ch6-iptables-application/","summary":"1、局域网共享上网（适合做企业内部局域网上网网关，以及IDC机房内网的上网网关 nat POSTROUTING）\n2、服务器防火墙功能（适合IDC机房具有外网IP服务器，主要是filter INPUT的控制）\n3、把外部IP及端口映射到局域网内部（可以一对一IP映射，也可针对某一个端口映射。）\n也可能是IDC把网站的外网VIP级网站端口映射到负载均衡器上（硬件防火墙）（NAT PREROUTING）\n4、办公路由器+网关功能（zebra路由+iptables过滤及NAT+squid正向透明代理80+ntop/iftop/iptaf流量查看+tc/cbq流量控制限速）。\n5、邮件的网关。\n问题2：的生产环境应用：用于没有外网地址的内网服务器，映射为公网IP后对外提供服务，也包括端口的映射\n问题3：IP一对一映射 用于没有外网地址的内网服务器，映射为公网IP后对外提供服务，例如：ftp服务要一对一IP映射。\n共享上网封IP的方法：\nbash 1 2 /sbin/iptables -I FROWAED -s 10.0.0.26 -j　DROP /sbin/iptables ${deal} FROWARD -m mac --mac -source ${strIpMac} -j DROP 映射多个外网IP上网 bash 1 2 iptables -t nat -A POSTROUTING -s 10.0.1.0/255.255.240.0 -o eth0 -j SNAT --to-source 124.42.60.11-124.42.60.16 iptables -t nat -A POSTROUTING -s 172.168.1.0/255.255.255.0 -o eth0 -j SNAT --to=source 124.42.60.60-124.42.60.63 问题：公司内网主机多的时候，访问网站容易被封。","title":"ch6 iptables生产应用场景"},{"content":"调整内核参数文件/etc/sysctl.conf，以下是我们生产环境的某个服务器的配置：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # 表示如果套接字由本端要求关闭，这个檀树决定了他保持在FIN-WAIT-2状态的时间。 net.ipv4.tcp_fin_timeout = 2 # 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接，默认为0，表示关闭。 net.ipv4.tcp_tw_reuse = 1 # 表示开启TCP连接中TIME-WAIT socket的快速收回，默认为0，表示关闭 net.ipv4.tcp_tw_recycle = 1 提示：以上两个参数为了防止生产环境下 time_wait过多设置的。 ############################################################ # 表示开启SYN Cookie。当出现SYN等带队列溢出时，启动cookie来处理，可防范少量SYN攻击，默认为0表示关闭 net.ipv4.tcp_syncookies = 1 # 表示当keepalive起用的时候，TCP发送keepalive消息的频度。缺省是两小时，改为20分钟 单位秒 net.ipv4.tcp_keepalive_time = 1200 # 表示对用向外连接的端口范围。缺省情况下很小。 net.ipv4.ip_local_port_range = 4000 65000 # 表示SYN队列的长度，默认为1024，加大队列长度为8192，可容纳更过等待连接的网络连接数。 net.ipv4.tcp_max_syn_backlog = 16384 # 表示系统同时保持TIME_WAIT套接字的最大数量，如果超过这个数字，TIME_WAIT套接字将立刻被清楚并打印警告信息。默认为180000，对于Apache Nginx等服务器来说可以调低一点，如：改为5000-30000，不同业务的服务器也可以给大一点，比如LVS，squid 以上几行的参数可以很好的减少TIME_WAIT套接字数量，但对于squid效果却不大。 net.ipv4.tcp_max_tw_buckets = 36000 net.ipv4.route.gc_timeout = 100 net.ipv4.tcp_syn_retries = 1 net.ipv4.tcp_synack_retries = 1 # 以下参数是对iptables防火墙的优化，防火墙不会开提示，可以忽略不理。 net.nf_conntrack_max = 25000000 net.netfilter.nf_conntrack_max = 25000000 net.netfilter.nf_conntrack_tcp_timeout_established = 180 net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120 net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60 net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120 dmesg里面显示 ip_contrack:table full，``dropping packet.` 的错误提示，如何解决?\n这有两个可能，一个是打开的端口太少至不够用，修改ip_conntrack文件为1024 65535。\n还有一个原因是nat链接真的达到65535了。此时就把NAT映射表保持时间设置短一些。\n强调：如果并发比较大，或者日PV多的情况下，开启防火墙要注意，很可能导致网站访问缓慢。\n大并发（并发1万，PV日3000万）要么购买硬件防火墙，要么不开iptables防火墙。\n","permalink":"https://www.161616.top/ch7-iptables-kernel-parameter/","summary":"调整内核参数文件/etc/sysctl.conf，以下是我们生产环境的某个服务器的配置：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # 表示如果套接字由本端要求关闭，这个檀树决定了他保持在FIN-WAIT-2状态的时间。 net.ipv4.tcp_fin_timeout = 2 # 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接，默认为0，表示关闭。 net.ipv4.tcp_tw_reuse = 1 # 表示开启TCP连接中TIME-WAIT socket的快速收回，默认为0，表示关闭 net.ipv4.tcp_tw_recycle = 1 提示：以上两个参数为了防止生产环境下 time_wait过多设置的。 ############################################################ # 表示开启SYN Cookie。当出现SYN等带队列溢出时，启动cookie来处理，可防范少量SYN攻击，默认为0表示关闭 net.ipv4.tcp_syncookies = 1 # 表示当keepalive起用的时候，TCP发送keepalive消息的频度。缺省是两小时，改为20分钟 单位秒 net.ipv4.tcp_keepalive_time = 1200 # 表示对用向外连接的端口范围。缺省情况下很小。 net.ipv4.ip_local_port_range = 4000 65000 # 表示SYN队列的长度，默认为1024，加大队列长度为8192，可容纳更过等待连接的网络连接数。 net.","title":"ch7 关于iptables的内核参数"},{"content":"httpd下载地址：Historical releases\n安装httpd bash 1 2 3 4 5 6 7 $ ls ABOUT_APACHE buildconf\temacs-style INSTALL\tLICENSE\tos\tsrclib acinclude.m4 CHANGES\thttpd.dep InstallBin.dsp Makefile.in README support Apache.dsw config.layout httpd.dsp LAYOUT Makefile.win README.platforms test build configure httpd.mak libhttpd.dep modules README-win32.txt VERSIONING BuildAll.dsp configure.in httpd.spec libhttpd.dsp NOTICE ROADMAP BuildBin.dsp docs include libhttpd.mak NWGNUmakefile server httpd 编译参数 参数选项 注释说明 ./configure 配置源代码树 –prefix=/usr/local/apache2 体系无关文件的顶级安装目录PREFIX，也就Apache的安装目录。 –enable-module=so [-enable-deflate] 打开so模块，so模块是用来提DSO支持的apache核心模块 –enable-deflate=shared [-enable-expires] 支持网页压缩 –enable-expires=shared [-enable-rewrite] 支持缓存过期控制 –enable-rewrite=shared 支持URL重写 –enable-cache 支持缓存 –enable-file-cache 支持文件缓存 –enable-mem-cache 支持记忆缓存 –enable-disk-cache 支持磁盘缓存 –enable-static-support 支持静态连接(默认为动态连接) –enable-static-htpasswd 使用静态连接编译htpasswd–管理用于基本认证的用户文件 –enable-static-htdigest 使用静态连接编译htdigest–管理用于摘要认证的用户文件 –enable-static-rotatelogs 使用静态连接编译rotatelogs–滚动Apache日志的管道日志程序 –enable-static-logresolve 使用静态连接编译logresolve–解析Apache日志中的IP地址为主机名 –enable-static-htdbm 使用静态连接编译htdbm–操作DBM密码数据库 –enable-static-ab 使用静态连接编译ab–Apache服务器性能测试工具 –enable-static-checkgid 使用静态连接编译checkgid –disable-cgid 禁止用一个外部CGI守护进程执行CGI脚本 –disable-cgi 禁止编译CGI版本的PHP –disable-userdir 禁止用户从自己的主目录中提供页面 –with-mpm=worker 让apache以worker方式运行 –enable-authn-dbm=shared 对动态数据库进行操作。Rewrite时需要。 以下是分门别类的更多参数注解，与上面的会有重复 用于apr的configure脚本的选项： 可选特性 \u0026ndash;enable-experimental-libtool 启用试验性质的自定义libtool \u0026ndash;disable-libtool-lock 取消锁定(可能导致并行编译崩溃) \u0026ndash;enable-debug 启用调试编译，仅供开发人员使用。 \u0026ndash;enable-maintainer-mode 打开调试和编译时警告，仅供开发人员使用。 \u0026ndash;enable-profile 打开编译profiling(GCC) \u0026ndash;enable-pool-debug[=yes|no|verbose|verbose-alloc|lifetime|owner|all] 打开pools调试 \u0026ndash;enable-malloc-debug 打开BeOS平台上的malloc_debug \u0026ndash;disable-lfs 在32-bit平台上禁用大文件支持(large file support) \u0026ndash;enable-nonportable-atomics 若只打算在486以上的CPU上运行Apache，那么使用该选项可以启用更加高效的基于互斥执行 的原子操作。 \u0026ndash;enable-threads 启用线程支持在线程型的MPM上必须打开它 \u0026ndash;disable-threads 禁用线程支持，如果不使用线程化的MPM，可以关闭它以减少系统开销。 \u0026ndash;disable-dso 禁用DSO支持 \u0026ndash;enable-other-child 启用可靠子进程支持 \u0026ndash;disable-ipv6 禁用IPv6支持 **可选的额外程序包** \u0026ndash;with-gnu-ld 指定C编译器使用GNU ld \u0026ndash;with-pic 只使PIC/non-PIC对象[默认为两者都使用] \u0026ndash;with-tags[=TAGS] 包含额外的配置 \u0026ndash;with-installbuilddir=DIR 指定APR编译文件的存放位置(默认值为：’${datadir}/build’) \u0026ndash;without-libtool 禁止使用libtool连接库文件 \u0026ndash;with-efence[=DIR] 指定Electric Fence的安装目录 \u0026ndash;with-sendfile 强制使用sendfile(译者注：Linux2.4/2.6内核都支持) \u0026ndash;with-egd[=DIR] 使用EDG兼容的socket \u0026ndash;with-devrandom[=DEV] 指定随机设备[默认为：/dev/random] 用于apr-util的configure脚本的选项 ： 可选的额外程序包 \u0026ndash;with-apr=PATH 指定APR的安装目录(–prefix选项值或apr-config的路径) \u0026ndash;with-ldap-include=PATH ldap包含文件目录(带结尾斜线) \u0026ndash;with-ldap-lib=PATH ldap库文件路径 \u0026ndash;with-ldap=library 使用的ldap库 \u0026ndash;with-dbm=DBM 选择使用的DBM类型DBM={sdbm,gdbm,ndbm,db,db1,db185,db2,db3,db4,db41,db42,db43,db44} \u0026ndash;with-gdbm=PATH 指定GDBM的位置 \u0026ndash;with-ndbm=PATH 指定NDBM的位置 \u0026ndash;with-berkeley-db=PATH 指定Berkeley DB的位置 \u0026ndash;with-pgsql=PATH 指定PostgreSQL的位置 \u0026ndash;with-mysql=PATH 参看INSTALL.MySQL文件的内容 \u0026ndash;with-sqlite3=PATH 指定sqlite3的位置 \u0026ndash;with-sqlite2=PATH 指定sqlite2的位置 \u0026ndash;with-expat=PATH 指定Expat的位置或builtin \u0026ndash;with-iconv=PATH iconv的安装目录 关于 2.2 编译参数\nbash 1 2 3 4 5 6 7 8 9 ./configure \\ --prefix=/app/apache2.4.28 \\ --enable-deflate \\ --enable-expires \\ --enable-headers \\ --enable-modules=most \\ --enable-so \\ --with-mpm=worker \\ --enable-rewrite 关于 2.4 编译参数\nBASH 1 2 3 4 5 6 7 8 9 10 11 ./configure \\ --prefix=/app/apache-2.4.25.1 \\ --enable-so \\ --enable-deflate=shared \\ --enable-ssl=shared \\ --enable-expires=shared \\ --enable-headers=shared \\ --enable-rewrite=shared \\ --enable-static-support \\ --with-included-apr \\ --with-mpm=worker 开始安装httpd 先安装依赖包\nbash 1 2 3 4 5 6 yum -y install \\ gcc \\ gcc-c++ \\ openssl-devel \\ zlib-devel \\ pcre-devel Troubleshooting configure: error: Bundled APR requested but not found bash 1 configure: error: Bundled APR requested but not found at ./srclib/. Download and unpack the corresponding apr and apr-util packages to ./srclib/. 编译时需要下载 apr 和 apr-util 解压到 httpd/srclib/ 目录里 http://apr.apache.org/download\nerror: \u0026lsquo;PCRE_DUPNAMES\u0026rsquo; undeclared (first use in this function) 网上搜了下说是yum安装的pcre的版本太老了，不支持PCRE_DUPNAMES 和 PCRE_JAVASCRIPT_COMPAT 这样的PCRE特性。好吧，我去下个最新版的pcre来编译安装。\n解决方法：编译安装pcre后，我们重新对httpd-2.4.9执行编译,这下就不会继续报错.\nbash 1 2 3 4 wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.35.tar.gz tar zxf pcre-8.35.tar.gz \u0026amp;\u0026amp; cd pcre-8.35 ./configure make \u0026amp;\u0026amp; make install cannot restore segment prot after reloc: Permission denied 错误如下：\nbash 1 httpd: Syntax error on line 149 of /usr/local/apache/conf/httpd.conf: Cannot load modules/libphp5.so into server: /usr/local/apache/modules/libphp5.so: cannot restore segment prot after reloc: Permission denied 解决方法：很明显的selinux的权限问题\nbash 1 2 3 chown -R apache:apache /usr/local/apache setenforce 0 chcon -c -v -R -u system_u -r object_r -t textrel_shlib_t /usr/local/apache/modules/libphp5.so configure: WARNING: OpenSSL version is too old 错误现象：CentOS 6.x\nbash 1 2 3 4 checking for OpenSSL version \u0026gt;= 0.9.8a... FAILED configure: WARNING: OpenSSL version is too old no checking whether to enable mod_ssl... configure: error: mod_ssl has been requested but can not be built due to prerequisite failures 解决方法：\nbash 1 yum install openssl openssl-devel zlib location\u0026hellip; not found 错误现象：\nbash 1 2 checking for zlib location... not found checking whether to enable mod_deflate... configure: error: mod_deflate has been requested but can not be built due to prerequisite failures 解决方法\nbash 1 yum install zlib zlib-devel -y no acceptable C compiler found in $PATH 错误现象：\nbash 1 configure: error: no acceptable C compiler found in $PATH 解决方法：gcc工具没安装，或开发工具包没安装 [CentOS 6.x]\nbash 1 2 yum groupinstall \u0026#34;Development tools\u0026#34; -y yum install gcc -y httpd: Could not reliably determine the server\u0026rsquo;s fully qualified domain name, 错误现象：\nbash 1 2 3 $ ./bin/apachectl graceful httpd: apr_sockaddr_info_get() failed for web-lamp01 httpd: Could not reliably determine the server\u0026#39;s fully qualified domain name, using 127.0.0.1 for ServerName 错误原因：找不到完整的 fqdn www.etiantian.com 为一个完整的 fqdn 使用 127.0.0.1 替代\n解决方法：在 httpd.conf 如果没有注册的DNS域名，用IP地址替换它\n启动参数 apachectl参数\n选项参数 注释说明 configtest 检查设置文件中的语法是否正确。 fullstatus 显示服务器完整的状态信息。 graceful 重新启动Apache服务器，但不会中断原有的连接。 help 显示帮助信息。 restart 重新启动Apache服务器。 start 启动Apache服务器。 status 显示服务器摘要的状态信息。 stop 停止Apache服务器。 httpd参数\n参数选项 注释说明 -c \u0026lt;httpd指令\u0026gt; 在读取配置文件前，先执行选项中的指令。 -C \u0026lt;httpd指令\u0026gt; 在读取配置文件后，再执行选项中的指令。 -d \u0026lt;服务器根目录\u0026gt; 指定服务器的根目录。 -D \u0026lt;设定文件参数\u0026gt; 指定要传入配置文件的参数。 -f \u0026lt;设定文件\u0026gt; 指定配置文件。 -h 显示帮助。 -l 显示服务器编译时所包含的模块。 -L 显示httpd指令的说明。 -S 显示配置文件中的设定。 -t 测试配置文件的语法是否正确。 -v 显示版本信息。 -V 显示版本信息以及建立环境。 -X 以单一程序的方式来启动服务器。 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ /app/apache/bin/apachectl start $ lsof -i:80 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME httpd 5871 root 4u IPv6 69297 0t0 TCP *:http (LISTEN) httpd 5873 daemon 4u IPv6 69297 0t0 TCP *:http (LISTEN) httpd 5874 daemon 4u IPv6 69297 0t0 TCP *:http (LISTEN) httpd 5875 daemon 4u IPv6 69297 0t0 TCP *:http (LISTEN) $ ps -ef|grep httpd root 5871 1 0 03:12 ? 00:00:00 /app/apache2.2.27/bin/httpd -k start daemon 5872 5871 0 03:12 ? 00:00:00 /app/apache2.2.27/bin/httpd -k start daemon 5873 5871 0 03:12 ? 00:00:00 /app/apache2.2.27/bin/httpd -k start daemon 5874 5871 0 03:12 ? 00:00:00 /app/apache2.2.27/bin/httpd -k start daemon 5875 5871 0 03:12 ? 00:00:00 /app/apache2.2.27/bin/httpd -k start root 5960 2056 0 03:12 pts/0 00:00:00 grep --color=auto httpd httpd目录结构 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 $ tree -L 1 . ├── bin #这是apache命令目录，如apache启动命令apachectl ├── ab #压力测试工具 同类软件还有jmeter loadrunner webench等 ├── apachectl #apache启动命令，需重点掌握，apachectl是一个脚本 ├── apxs # apxs是一个为Apache HTTP服务器编译和安装扩展模块的工具，在进行DOS当时编译模块时会用到 ├── htcacheclean #这是清理磁盘缓冲区的命令，需要在编译时指定相关参数才可以使用，一般很少用 ├── htpasswd # 建立和更新基本认证文件，如：配置nagios等监控服务时会用到 ├── httpd #httpd为apache的控制命令程序，apachectl执行时会调用httpd └── rotatelogs #apache自带的日志轮询命令，其他未使用过的略过未提级，以便大家学习主要的命令 ├── build ├── cgi-bin ├── conf # apache所有配置文件的目录 ├── extra # 额外的apache配置目录，这个目录里的文件我们会经常访问修改，如：httpd-vhosts.conf默认就在此目录 ├── httpd.conf #apache的主配置文件，这个文件我们会经常访问修改，其中的每一行的参数作用都应该闹明白 ├── magic ├── mime.types └── original ├── error ├── htdocs # 默认的apache的站点目录 ├── icons ├── include ├── lib ├── logs # 这个时apache的日志默认路径，包括错误日志及访问日志 ├── access_log #这是apache的默认访问日志 ├── cgisock.5871 ├── error_log # apache的错误日志 └── httpd.pid # httpd的pid文件，httpd进程启动后，会把所有的进程号ID写到此文件 ├── man ├── manual └── modules # apache模块的目录，如memcache httpd主配置文件说明 bash 1 grep -Ev \u0026#34;#|^$\u0026#34; httpd.conf \u0026gt;httpd.conf.ori bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 # 服务的根目录 软件安装到哪 ServerRoot \u0026#34;/app/apache2.2.27\u0026#34; # web服务监听的端口 监听的ip默认为本机的所有ip地址 listen可以为多个，也可以指定ip # 没有listen端口是开启不了的 Listen 80 Listen 8000 Listen 192.168.59.1:90 # 如不加默认为所有 \u0026lt;IfModule !mpm_netware_module\u0026gt; \u0026lt;IfModule !mpm_winnt_module\u0026gt; User daemon # 编译安装软件默认用户是daemon 用户 Group daemon #组 \u0026lt;/IfModule\u0026gt; \u0026lt;/IfModule\u0026gt; #管理员的邮箱 当前网站出问题了，会在页面显示 ServerAdmin you@example.com # 默认的站点目录 DocumentRoot \u0026#34;/app/apache2.2.27/htdocs\u0026#34; # 权限控制 表示根目录拒绝其他人访问 \u0026lt;Directory /\u0026gt; Options FollowSymLinks #带符号连接 AllowOverride None #禁止相关的功能 如.htaccess Order deny,allow #不让任何人访问这个目录 Deny from all \u0026lt;/Directory\u0026gt; # 如果新增加一个站点目录必须增加这6行，否则网站打不开 \u0026lt;Directory \u0026#34;/app/apache2.2.27/htdocs\u0026#34;\u0026gt; Options Indexes FollowSymLinks #Indexs 没有index展示所有目录 AllowOverride None Order allow,deny Allow from all \u0026lt;/Directory\u0026gt; # 指定访问的首页 如果有多个用空格分开 \u0026lt;IfModule dir_module\u0026gt; DirectoryIndex index.html index.php \u0026lt;/IfModule\u0026gt; \u0026lt;FilesMatch \u0026#34;^\\.ht\u0026#34;\u0026gt; Order allow,deny Deny from all Satisfy All \u0026lt;/FilesMatch\u0026gt; #错误日志 ErrorLog \u0026#34;logs/error_log\u0026#34; #日志级别 警告 LogLevel warn #访问日志的类型 \u0026lt;IfModule log_config_module\u0026gt; LogFormat \u0026#34;%h %l %u %t \\\u0026#34;%r\\\u0026#34; %\u0026gt;s %b \\\u0026#34;%{Referer}i\\\u0026#34; \\\u0026#34;%{User-Agent}i\\\u0026#34;\u0026#34; combined LogFormat \u0026#34;%h %l %u %t \\\u0026#34;%r\\\u0026#34; %\u0026gt;s %b\u0026#34; common \u0026lt;IfModule logio_module\u0026gt; LogFormat \u0026#34;%h %l %u %t \\\u0026#34;%r\\\u0026#34; %\u0026gt;s %b \\\u0026#34;%{Referer}i\\\u0026#34; \\\u0026#34;%{User-Agent}i\\\u0026#34; %I %O\u0026#34; combinedio \u0026lt;/IfModule\u0026gt; CustomLog \u0026#34;logs/access_log\u0026#34; common \u0026lt;/IfModule\u0026gt; # cgi的配置 现在已经过时了，工作中应该删掉 \u0026lt;IfModule alias_module\u0026gt; ScriptAlias /cgi-bin/ \u0026#34;/app/apache2.2.27/cgi-bin/\u0026#34; \u0026lt;/IfModule\u0026gt; \u0026lt;IfModule cgid_module\u0026gt; \u0026lt;/IfModule\u0026gt; \u0026lt;Directory \u0026#34;/app/apache2.2.27/cgi-bin\u0026#34;\u0026gt; AllowOverride None Options None Order allow,deny Allow from all \u0026lt;/Directory\u0026gt; # 缺省的类型 DefaultType text/plain #添加的类型，对什么类型做什么控制 \u0026lt;IfModule mime_module\u0026gt; TypesConfig conf/mime.types AddType application/x-compress .Z AddType application/x-gzip .gz .tgz \u0026lt;/IfModule\u0026gt; \u0026lt;IfModule ssl_module\u0026gt; SSLRandomSeed startup builtin SSLRandomSeed connect builtin \u0026lt;/IfModule\u0026gt; httpd的扩展配置文件 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 $ ll 总用量 56 -rw-r--r--. 1 root root 2843 4月 8 03:03 httpd-autoindex.conf -rw-r--r--. 1 root root 1713 4月 8 03:03 httpd-dav.conf -rw-r--r--. 1 root root 2344 4月 8 03:03 httpd-default.conf -rw-r--r--. 1 root root 1103 4月 8 03:03 httpd-info.conf -rw-r--r--. 1 root root 5078 4月 8 03:03 httpd-languages.conf -rw-r--r--. 1 root root 933 4月 8 03:03 httpd-manual.conf -rw-r--r--. 1 root root 3789 4月 8 03:03 httpd-mpm.conf -rw-r--r--. 1 root root 2183 4月 8 03:03 httpd-multilang-errordoc.conf -rw-r--r--. 1 root root 11378 4月 8 03:03 httpd-ssl.conf -rw-r--r--. 1 root root 817 4月 8 03:03 httpd-userdir.conf -rw-r--r--. 1 root root 1491 4月 8 03:03 httpd-vhosts.conf mpm\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 --with-mpm=worker \\ \u0026lt;IfModule mpm_prefork_module\u0026gt; StartServers 5 MinSpareServers 5 MaxSpareServers 10 MaxClients 150 MaxRequestsPerChild 0 \u0026lt;/IfModule\u0026gt; # 如果指定了worker就是woker，否则是prefork \u0026lt;IfModule mpm_worker_module\u0026gt; StartServers 2 MaxClients 150 MinSpareThreads 25 MaxSpareThreads 75 ThreadsPerChild 25 MaxRequestsPerChild 0 \u0026lt;/IfModule\u0026gt; defalut配置文件\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ grep -Ev \u0026#34;#|^$\u0026#34; httpd-default.conf # 连接超时 Timeout 300 #保持连接的状态 KeepAlive On #最大接受多少个永久连接 MaxKeepAliveRequests 100 #在同一个连接上，等待下一个请求的时间 KeepAliveTimeout 5 UseCanonicalName Off #将伪静态的语法写在这个里面 AccessFileName .htaccess #隐藏apache版本号，不同意被攻击 ServerTokens Full ServerSignature On HostnameLookups Off 虚拟主机 所谓基于 ”x“ 的虚拟主机，就是靠 ”x“ 来区分不同的站点。支持各种混合，N多个虚拟主机\n基于域名的虚拟主机★★★★★ 建立三个站点\nwww.etiantian.com\t\u0026mdash;\u0026mdash; /var/html/www blog.etiantian.com \u0026mdash;\u0026mdash; /var/html/blog bbs.etiantian.com \u0026mdash;\u0026mdash; /var/html/bbs 步骤1：在 httpd.conf 中打开 Include conf/extra/httpd-vhosts.conf\n步骤2：在 extra 中的 vhost 中添加三个虚拟主机\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 \u0026lt;VirtualHost *:80\u0026gt; ServerAdmin test.com@gmail.com DocumentRoot \u0026#34;/var/html/www\u0026#34; ServerName www.etiantian.com ErrorLog \u0026#34;logs/www_error_log\u0026#34; CustomLog \u0026#34;logs/www_access_log\u0026#34; common \u0026lt;/VirtualHost\u0026gt; \u0026lt;VirtualHost *:80\u0026gt; ServerAdmin test.com@gmail.com DocumentRoot \u0026#34;/var/html/blog\u0026#34; ServerName blog.etiantian.com ErrorLog \u0026#34;logs/blog_error_log\u0026#34; CustomLog \u0026#34;logs/blog_access_log\u0026#34; common \u0026lt;/VirtualHost\u0026gt; \u0026lt;VirtualHost *:80\u0026gt; ServerAdmin test.com@gmail.com DocumentRoot \u0026#34;/var/html/bbs\u0026#34; ServerName bbs.etiantian.com ErrorLog \u0026#34;logs/bbs_error_log\u0026#34; CustomLog \u0026#34;logs/bbs_access_log\u0026#34; common \u0026lt;/VirtualHost\u0026gt; 步骤3：在主配置文件，或在 \u0026lt;VirtualHost\u0026gt; 里配置权限\ntext 1 2 3 4 5 6 \u0026lt;Directory \u0026#34;/var/html\u0026#34;\u0026gt; Options FollowSymLinks AllowOverride None Order allow,deny Allow from all \u0026lt;/Directory\u0026gt; 如果配置为 Options Indexes FollowSymLinks 则显示为下图所示\n如果配置为 Options -Indexes FollowSymLinks 则显示为下图所示\n基于端口的虚拟主机 一般来讲是内部网站\n优点：安全一些，别人找不到 步骤1：在 httpd.conf 中增加监听端口\ntext 1 2 3 Listen 80 Listen 8000 Listen 9000 步骤1：在 vhost 文件中增加\ntext 1 2 3 4 NameVirtualHost *:80 NameVirtualHost *:8000 \u0026lt;VirtualHost *:8000\u0026gt; 基于IP的虚拟主机 步骤1：配置别名ip\nbash 1 ifconfig eth0:0 192.168.59.140/24 步骤2：1.修改 vhost 文件虚拟主机配置\ntext 1 2 3 4 5 6 7 \u0026lt;VirtualHost 192.168.59.140:80\u0026gt; ServerAdmin test.com@gmail.com DocumentRoot \u0026#34;/var/html/blog\u0026#34; ServerName 192.168.59.140 ErrorLog \u0026#34;logs/blog_error_log\u0026#34; CustomLog \u0026#34;logs/blog_access_log\u0026#34; common \u0026lt;/VirtualHost\u0026gt; 混合虚拟主机 text 1 2 3 4 5 6 7 \u0026lt;VirtualHost 192.168.59.140:900\u0026gt; ServerAdmin test.com@gmail.com DocumentRoot \u0026#34;/var/html/blog\u0026#34; ServerName 192.168.59.140 ErrorLog \u0026#34;logs/blog_error_log\u0026#34; CustomLog \u0026#34;logs/blog_access_log\u0026#34; common \u0026lt;/VirtualHost\u0026gt; 日志格式 通用日志格式Common Log Format 组合日志格式Combinded Log Format text 1 2 LogFormat \u0026#34;%h %l %u %t \\\u0026#34;%r\\\u0026#34; %\u0026gt;s %b \\\u0026#34;%{Referer}i\\\u0026#34; \\\u0026#34;%{User-Agent}i\\\u0026#34;\u0026#34; combined LogFormat \u0026#34;%h %l %u %t \\\u0026#34;%r\\\u0026#34; %\u0026gt;s %b\u0026#34; common 如何使用combined或common呢？\n答：在虚拟主机配置中更改格式\ntext 1 2 3 4 5 6 7 \u0026lt;VirtualHost *:80\u0026gt; ServerAdmin test.com@gmail.com DocumentRoot \u0026#34;/var/html/www\u0026#34; ServerName www.etiantian.com ErrorLog \u0026#34;logs/www_error_log\u0026#34; CustomLog \u0026#34;logs/www_access_log\u0026#34; common/combind \u0026lt;/VirtualHost\u0026gt; 日志轮询 rotatelog cronolog ☆☆☆ 安装 cronolog\nbash 1 2 3 4 5 6 ./configure make make install $ which cronolog /usr/local/sbin/cronolog 如果用cronolog轮询的的话最好用全路径\nbash 1 2 3 4 CustomLog \u0026#34;|/usr/local/sbin/cronolog /app/logs/access_bbs_%Y%m%d.log\u0026#34; combined $ mkdir /app/logs -p $ ../bin/apachectl graceful 日志轮询除了可以使用年月日还可以使用周\nbash 1 CustomLog \u0026#34;|/usr/local/sbin/cronolog /app/logs/access_bbs_%w.log\u0026#34; combined 时间格式串：\n时间格式串 %H 24小时制小时(00..23) %I 12小时制小时(01..12) %p 本地AM/PM指示符 %M 分钟(00..59) %S 秒(00..61) %X 本地时间(e.g.:“15:12:47″) %Z 时区(e.g.GMT)，如果不能检测出时区，值为空 日期格式串 %a 本地简短星期名(e.g.: Sun..Sat) %A 本地完整星期名(e.g.: Sunday .. Saturday) %b 本地简短月名(e.g.: Jan .. Dec) %B 本地完整月名(e.g.: January .. December) %c 本地日期与时间(e.g.: “Sun Dec 15 14:12:47 GMT 1996″) %d 一月中的第几日(01 .. 31) %j 一年中的第几天 (001 .. 366) %m 月名的数字表示 (01 .. 12) %U 一年中以星期日为每周第一天计算的星期数(00..53, 第一周包括新年的第一个星期日) %W 一年中以星期一为每周第一天计算的星期数(00..53, 第一周包括新年的第一个星期一) %w 星期名的数字表示 (0 .. 6, 0为星期日) %x 本地日期 (e.g. 今天在北京是: “15/12/96″) %y 不带世纪的年(00 .. 99) %Y 带世纪的年(1970 .. 2038) 错误写法：\n提示：cronolog轮询日志的正确写法，被轮询的日志路径要写全路径\n按天轮询（生产环境常见用法，推荐使用）\nbash 1 2 3 4 # 错误写法 CustomLog \u0026#34;|usr/local/sbin/cronolog logs/access_www_%w.log\u0026#34; combined # 正确写法 CustomLog \u0026#34;|usr/local/sbin/cronolog /app/logs/access_www_%Y%m%d.log\u0026#34; combined 提示：这是大多数网站的常规配置方法（按天记录日志，日志不会自动覆盖）\n按小时轮询（生产环境较常见用法）：\nbash 1 2 3 4 # 按小时轮询（生产环境较常见用法） CustomLog \u0026#34;|usr/local/sbin/cronolog /app/logs/access_www_%Y%m%d%H.log\u0026#34; combined # 按天轮询（生产环境较常见用法） CustomLog \u0026#34;|usr/local/sbin/cronolog /app/logs/access_www_%Y%m%d.log\u0026#34; combined 完整配置如下\nbash 1 2 3 4 5 6 7 \u0026lt;VirtualHost *:80\u0026gt; ServerAdmin test.com@gmail.com DocumentRoot \u0026#34;/var/html/blog\u0026#34; ServerName blog.etiantian.com ErrorLog \u0026#34;logs/blog_error_log\u0026#34; CustomLog \u0026#34;|/usr/local/sbin/cronolog /app/logs/access_blog_%w.log\u0026#34; combined \u0026lt;/VirtualHost\u0026gt; 配置定时任务\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 # 创建脚本目录 mkdir /server/script/web/ -p # 创建脚本文件 touch /server/script/web/apache_log.sh # 编辑脚本内容 cd /app/apache/logs mv www_access_log /app/logs/www_access_$(date +%F -d \u0026#39;-1day\u0026#39;).log www_access_log \u0026gt;www_access_log /app/apache/bin/apachectl graceful # 添加定时任务 00 00 * * * /bin/sh /server/script/web/apache_log.sh \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 # 测试脚本是否可执行成功 # 如果脚本没有执行权限 $ /server/script/web/apache_log.sh -bash: /server/script/web/apache_log.sh: 权限不够 $ /bin/sh /server/script/web/apache_log.sh # 查看执行结果 $ ll 总用量 0 # 将时间设置为0时 $ date -s \u0026#39;2017-04-20 23:59:00\u0026#39; # 这时候时23:59查看当天日志是有内容的，并且/app/logs/下面没有文件 $ cat /app/apache/logs/www_access_log 192.168.59.1 - - [20/Apr/2017:23:59:19 +0800] \u0026#34;GET / HTTP/1.1\u0026#34; 304 - \u0026#34;-\u0026#34; \u0026#34;Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:45.0) Gecko/20100101 Firefox/45.0\u0026#34; # 0时这个时候发现目录生成新日志文件了 $ ll 总用量 4 -rw-r--r--. 1 root root 153 4月 20 23:59 www_access_2017-04-21.log 不记录日志 正如下列配置所示\ntext 1 2 3 \u0026lt;FilesMatch \u0026#39;\\. (css|js|gif|jpg|ico|swf|png)\u0026#39;\u0026gt; SetEnv IMAG 1 \u0026lt;/FilesMatch\u0026gt; 实际日志配置\ntext 1 CustomLog \u0026#34;|/usr/local/sbin/cronolog /app/logs/access_bbs_%w.log\u0026#34; combined evn=!IMAG 查询日志\ntext 1 awk \u0026#39;{print $1}\u0026#39; www-access_2014-09-18log |sort|uniq -c|sort -rn -k1|head -10 隐藏httpd版本号 步骤1：编译前隐藏版本信息\nbash 1 cat /home/tools/httpd-2.2.27/include/ap_release.h 找到 #define AP_SERVER_BASEPRODUCT \u0026quot;Apache\u0026quot;\ntext 1 2 3 4 5 6 7 8 9 10 11 12 #define AP_SERVER_BASEPRODUCT \u0026#34;Apache\u0026#34; # 修改为 #define AP_SERVER_BASEPRODUCT \u0026#34;Microsoft-IIS/5.0\u0026#34; #define AP_SERVER_BASEVENDOR \u0026#34;Apache Software Foundation\u0026#34; #define AP_SERVER_BASEPROJECT \u0026#34;Apache HTTP Server\u0026#34; #define AP_SERVER_BASEPRODUCT \u0026#34;Apache\u0026#34; #define AP_SERVER_MAJORVERSION_NUMBER 2 #define AP_SERVER_MINORVERSION_NUMBER 2 #define AP_SERVER_PATCHLEVEL_NUMBER 27 #define AP_SERVER_DEVBUILD_BOOLEAN 0 找到 cat /home/tools/httpd-2.2.27/os/unix/os.h，修改 #define PLATFORM \u0026quot;Unix\u0026quot; 修改为 #define PLATFORM \u0026quot;Win32\u0026quot; **步骤3：最后修改 /etc/httpd/conf/httpd.conf **\ntext 1 2 ServerTokens Prod # Prod 同 ProductOnly ServerSignature Off 配置前的响应头\nbash 1 2 3 4 $ curl 127.0.0.1 -I HTTP/1.1 200 OK Date: Thu, 20 Apr 2017 16:18:24 GMT Server: Apache/2.2.27 (Unix) DAV/2 配置如下参数\nbash 1 2 3 vi /app/apache/conf/extra/httpd-default.conf ServerTokens Prod ServerSignature Off 配置后的响应头\nbash 1 2 3 4 $ curl 127.0.0.1 -I HTTP/1.1 200 OK Date: Thu, 20 Apr 2017 16:22:19 GMT Server: Apache 附：\nServerSignature 三个选项，分别是 On | Off | EMail ServerTokens 的取值如下，其分别隐藏信息依次增加，推荐： ServerTokens ProductOnly 未经修改的请求头如下\nbash 1 2 3 4 5 6 7 8 9 10 $ curl –head 127.0.0.1 HTTP/1.1 200 OK Date: Thu, 22 Jan 2015 15:39:00 GMT Server: Apache/2.2.26 (CentOS) X-Powered-By: PHP/5.5.9 Vary: Cookie,Accept-Encoding,User-Agent X-Pingback: http://blog.mimvp.com/xmlrpc.php Cache-Control: max-age=600 Expires: Thu, 22 Jan 2015 15:49:00 GMT Content-Type: text/html; charset=UTF-8 上面头信息中，会显示服务器类型和版本(Apache/2.2.26)，以及操作系统(CentOS)\n修改 ServerTokens\n修改 ServerTokens OS 为 ServerTokens productonly\n再次返回头信息如下：\nbash 1 2 3 4 5 6 7 8 9 10 $ curl –head 127.0.0.1 HTTP/1.1 200 OK Date: Thu, 22 Jan 2015 15:40:53 GMT Server: Apache X-Powered-By: PHP/5.5.9 Vary: Cookie,Accept-Encoding,User-Agent X-Pingback: http://blog.mimvp.com/xmlrpc.php Cache-Control: max-age=600 Expires: Thu, 22 Jan 2015 15:50:53 GMT Content-Type: text/html; charset=UTF-8 隐藏php版本号 php.ini\nexpose_php On 改成 expose_php Off\nHTTP fastcgi模式启动 编译PHP时候不要使用aspx\n注释掉以下，fpm安装方式。不用管，例如下面配置\ntext 1 2 3 4 LoadModule php5_module modules/libphp5.so \u0026lt;FilesMatch \\.php$\u0026gt; SetHandler application/x-httpd-php \u0026lt;/FilesMatch\u0026gt; 然后去掉 mod_proxy.so 和 mod_proxy_fcgi.so 之前的注解，确保他们被apache加载。\n如果php-fpm使用的是TCP socket，那么在httpd.conf末尾加上：\ntext 1 2 3 \u0026lt;FilesMatch \\.php$\u0026gt; 正则匹配文件，如果文件名为.php结尾 SetHandler \u0026#34;proxy:fcgi://127.0.0.1:9000\u0026#34; \u0026lt;/FilesMatch\u0026gt; httpd的工作模式 worker/perfor模式 在linux中，我们可以使用 http-l 查看安装的模块是 prefork 模式还是 worker 模式\nbash 1 2 3 $ /app/apache/bin/apachectl -l|sed -n \u0026#39;/worker\\|prefork/p\u0026#39; prefork.c\t#\u0026lt;== 默认为prefork（预派生）模式5个进程 482 Include conf/extra/httpd-mpm.conf #\u0026lt;==在httpd.conf中打开mpm配置文件. prefork模式 prefork使用的是多个子进程，而每个子进程只有一个线程，每个进程在某个确定的时间只能维持一个连接.\n工作原理 控制进程最初简历若干个子进程，为了不在请求到来时再生成子进程，所以要根据需求不断的创建新的子进程，最大可以达到每秒32个知道满足需求为止.\n安装方法：在编译的过程中，加入参数 --with-mpm=prefork 如果不加也可以，因为默认的话，会采用 prefork模式。\n优点：效率高，稳定，安全。对于线程调试困难的平台来说，调试更加容易些. 缺点：和work模式比消耗资源多. prefork配置参数说明 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # prefork MPM # StartServers: number of server processes to start # MinSpareServers: minimum number of server processes which are kept spare # MaxSpareServers: maximum number of server processes which are kept spare # MaxRequestWorkers: maximum number of server processes allowed to start # MaxConnectionsPerChild: maximum number of connections a server process serves # before terminating \u0026lt;IfModule mpm_prefork_module\u0026gt; StartServers 1 #\u0026lt;==最初建立的子进程 MinSpareServers 1\t#\u0026lt;==最小空闲进程数，如果空闲进程小于设定值，apache会自动建立进程，如果服务器并发及负载大的话，可以考虑加大。 MaxSpareServers 2\t#\u0026lt;==最大空闲进程，如果空闲的进程大于设定值，apache会自动kill掉多余的进程，如果服务器负载大的话，可以考虑加大 MaxRequestWorkers 20\t#\u0026lt;==设定apache可以同时处理的请求，是对apache性能影响最大的参数，就是apache可以同时处理的请求数，就是说，如果有150个用户在访问，那么第151个用户就要等之前的访问结束后才能访问 MaxConnectionsPerChild 0\t#\u0026lt;==每个子进程可处理的请求数。每个子进程在处理了[max requests perchild] 个请求后将自动销毁。0意味着无限，即子进程永不销毁，虽然缺省值为0,可以使每个子进程处理更多的请求，但如果设成非0值也有两点重要好处1.可防止意外的内存泄露2.在服务器负载下降的时候会自动减少子进程数 \u0026lt;/IfModule\u0026gt; worker模式 worker模式是 Apache2.X 新引进来的模式，是线程与进程的结合，在worker模式下会有多个子进程，每个进程又会有多个线程。每个线程在某个确定的时间只能维持一个连接。\n工作原理\n由主控制进程生成若干个子进程，而每个子进程中又包含固定的线程数，各个线程独立处理请求，同样为了不在请求到来时再生成线程，再配置文件中设置了最小和最大的空闲线程数及所有子进程中的线程总数，如果现有子进程中的线程总数不能满足并发及负载，控制进程将派生新的子进程。\n在配置编译的过程中，加入参数 --with-mpm-worker，如果不加的话系统会采用默认prefork模式。\n优点：内存占用比prefork模式低，适合高并发高流量HTTP服务。 缺点：假如一个线程崩溃，整个进程就会连同其任何线程一起 “死掉”。由于线程贡献内存空间，所以一个程序在运行时必须被系统识别为“每个线程都是安全的”服务稳定性不如prefork模式。 worker 模式配置参数说明 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # worker MPM # StartServers: initial number of server processes to start # MinSpareThreads: minimum number of worker threads which are kept spare # MaxSpareThreads: maximum number of worker threads which are kept spare # ThreadsPerChild: constant number of worker threads in each server process # MaxRequestWorkers: maximum number of worker threads # MaxConnectionsPerChild: maximum number of connections a server process serves # before terminating \u0026lt;IfModule mpm_worker_module\u0026gt; StartServers 3\t#\u0026lt;==最初建立的子进程 MinSpareThreads 75\t#\u0026lt;==最小空闲线程数，如果空闲的线程小于设定值，apache会自动建立线程，如果服务器负载大的话，可以考虑加大此参数值。 MaxClients #\u0026lt;==所有子进程中的线程总数。如果现有子进程中的线程总数不能满足负载，控制进程将派生新的子进程。 MaxSpareThreads 250 #\u0026lt;==最大空闲线程数，如果空闲的线程大于设定值，apache会自动kill掉多余的线程，如果服务器负载过大的话，可以考虑加大此参数值。 ThreadsPerChild 25\t#\u0026lt;==每个进程包含固定的线程数，此参数再worker模式中，是影响最大的参数 MaxRequestWorkers 400\tMaxConnectionsPerChild 0Threads \u0026lt;/IfModule\u0026gt; worker模式下所能同时处理的请求总数是由子进程总数乘Threadsperchild值决定的，应该大于等于maxcliens。如果负载很大，现有的子进程数不能满足时，控制进程会派生新的子进程。\n默认最大的子进程总数是16，如需加大时，也需要显式声明serverlimit的值（最大值是20000）\n特别说明\n如果显式生命了serverLimit，那么它乘 ThreadPreChild 的值必须大于等于MaxClients,而且MaxClients必须是ThreadPerChild的整数倍，否则Apache将会自动调节Apache将会自动调节到一个相应的值（可能是个非常期望值）\n通过数学表达\nMaxClient \u0026lt;= 总的进程（ServerLimit）* 线程数（ThreadsPerChild）、\nMaxClient % ThreadsPerChild = 0\n注意：worker MPM也有不完善的地方，如果一个线程崩溃，整个进程就会连同其所有线程一起 “死掉”\n配置httpd对站点文件压缩 mod_deflate模块介绍 Apache的 mod_deflate 模块提供了 Deflate 输出过滤器，允许httpd服务器将输出内容在发送到客户端之前根据具体的策略进行压缩，以节约网络带宽，同时提升用户访问体验。\nmod_deflate 模块安装方法\n检查是否安装了模块 mod_deflate\nbash 1 2 3 4 5 6 $ /app/apache/bin/apachectl -l Compiled in modules: core.c mod_so.c http_core.c prefork.c 常规方法安装 --enable-deflate 提供对内容的亚索传输编码支持，一般html js css等内容站点，使用此参数功能会大大提高传输速度，提升访问者访问体验。在生产环境中，这是 httpd 调优的一个重要选项之一。\nmod_deflate DSO安装方法 以DSO动态模块加载mod_deflate配置的全部命令为\nbash 1 2 cd /root/tools/httpd-2.4.18/modules/filters\t#\u0026lt;==切换到apache软件目录mod_deflate程序下 /app/apache/bin/apxs -c -i -a mod_deflate.c #\u0026lt;==以dso的方式编译入到apache中 出现如下错误\nbash 1 httpd: Syntax error on line 103 of /app/apache2.4/conf/httpd.conf: Cannot load modules/mod_deflate.so into server: /app/apache2.4/modules/mod_deflate.so: undefined symbol: inflateEnd 解决方法：\n出现这个错误其实时因为 mod_deflate 模块没有找到 zlib 库。解决办法就是找到 apr-config 文件中的LDFLAGS=\u0026quot;\u0026quot;，把他改成 LDFLAGS=\u0026quot;-lz\u0026quot; ，然后在运行 /usr/local/apache2/bin/apxs -i -c -a mod_deflate.c 一般就可以了。\n方法1：在apr的主配置文件 apr-1-config（老版本可能是apr-conf）里面将 LDFLAGS=\u0026quot;\u0026quot; 修改为 LDFLAGS=\u0026quot;-lz\u0026quot;，然后用 apxs 从新编译 mod_deflate.c 后，httpd 服务就正常了，并且也可以正常压缩文件了。\n方法2：需要在 LoadModule deflate_module modules/mod_deflate.so 的前面加载 zlib.so64 操作系统就在 LoadModule deflate_module modules/mod_deflate.so 这行的上一行添加 LoadFile /usr/lib64/libz.so即可。\n上述参数选项说明\n参数 说明 -c 需要执行编译操作。他首先会编译C源码程序(.c)files为对应的目标代码文件(.o)，然后连接这些目标代码和files中其余的目标代码文件(.o和.a)，以生成动态共享对象dsofile。如果没有指定-o选项，则此输出文件名由files中的第一个文件名推测得到，也就是默认为mod_name.so -i 需要执行安装操作，以安装一个或多个共享对象到服务器的modules目录中。 -a 增加一个LoadMoudule行到httpd.conf文件中，以激活此模块 mod_deflate测试 mod_deflate模块可以在主配置文件中配置，也可以在虚拟主机中配置\n在主http.conf配置中配置如下：\ntext 1 2 3 4 5 6 \u0026lt;ifmodule mod_deflate.c\u0026gt; # \u0026lt;==开始标签标记 判断模块是否开启 DeflateCompressionLevel 6 # \u0026lt;==压缩级别 1-9 SetOutputFilter DEFLATE\t# \u0026lt;==启用deflate模块对本站点的所有输出进行GZIP压缩 AddOutputFilterByType Deflate text/html test/pain text/xml text/css AddOutputFilterByType Deflate application/javascript \u0026lt;/ifmodule\u0026gt; # \u0026lt;==判断结束标记 mod_expires 缓存功能 mod_expires允许通过apache配置文件控制 HTTP “Expires:” 和 “Cache-Control:” 头内容，这个模块控制服务器应答时的Expires头内容和Cache-Control头的max-age指令。有效期可以设置为相对于源文件的最后修改时刻或者客户端的访问时刻。\n这些HTTP头向客户端表明了内容的有效性和持久性。如果客户端本地有缓存，则内容就可以从缓存（除非过期）而不是从服务器读取。人后客户端会检查缓存中的副本，看看是否过期或者失效，以决定是否重新从服务器获得内容更新。\n常规方法安装 \u0026ndash;enable-deflate\n提供对内容的亚索传输编码支持，一般html js css等内容站点，使用此参数功能会大大提高传输速度，提升访问者访问体验。在生产环境中，这是Apache调优的一个重要选项之一。\n以DSO动态模块加载mod_deflate配置的全部命令为：\nbash 1 2 cd /root/tools/httpd-2.4.18/modules/metadata#\u0026lt;==切换到apache软件目录mod_expires程序下 /app/apache/bin/apxs -c -i -a mod_expires.c #\u0026lt;==以dso的方式编译入到apache中 mod_expires在httpd中应用 在主配置文件中配置，所有的虚拟主机都生效，在对应的虚拟主机上配置，只有对应的虚拟主机生效\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 \u0026lt;ifmodule expires_module\u0026gt; ExpiresActive on ExpiresDefault \u0026#34;access plus 12 months\u0026#34; ExpiresByType test/html \u0026#34;access plus 1 years\u0026#34; ExpiresByType test/css \u0026#34;access plus 1 years\u0026#34; ExpiresByType test/xml \u0026#34;access plus 1 years\u0026#34; ExpiresByType test/php \u0026#34;access plus 1 years\u0026#34; ExpiresByType image/gif \u0026#34;access plus 30 days\u0026#34; #ExpiresByType image/jpeg \u0026#34;access plus 30 days\u0026#34; #ExpiresByType image/jpg \u0026#34;access plus 30 days\u0026#34; ExpiresByType image/jpeg A7200000 ExpiresByType image/png \u0026#34;access plus 30 days\u0026#34; ExpiresByType application/javascript \u0026#34;access plus 30 days\u0026#34; ExpiresByType application/x-javascript \u0026#34;access plus 30 days\u0026#34; ExpiresByType image/png \u0026#34;access plus 30 days\u0026#34; \u0026lt;/ifmodule\u0026gt; 配置httpd防盗链功能 httpd Web服务实现防盗链 利用referer和rewrite实现Apache防盗链调用，在主配置文件 httpd.conf 或者在虚拟主机 httpd-vhosts.conf 中配置如下\ntext 1 2 3 4 5 6 RewriteEngine ON\tRewriteCond %{HTTP_REFERER} !^http://www.test.com/.*$ [NC] #\u0026lt;==此处，如果加http://就必须加^,否则直接写servername即可 如下 RewriteCond %{HTTP_REFERER} !http://www.test.com [NC] RewriteCond %{HTTP_REFERER} !^http://www.test.com$ [NC] RewriteCond %{SCRIPT_FILENAME} !nolink.png [NC]#\u0026lt;==为防止无限302，文件名如果为nolink.png即不重写 RewriteRule .*\\.(gif|jpg|swf|png)$ img/nolink.png [R,NC,L] #\u0026lt;==重写时，必须加R 访问查看结果\n用原本的域名访问，可直接访问\nhttpd常用Rewrite标志 flag 说明 Chain|C 如果当前规则被匹配，则继续处理其后继规则，如果当前规则不被匹配， 则其后继规则将被跳过。 forbidden|F 强制禁止当前URL的响应，并向客户端发送一个403的HTTP响应代码。 gone|G 强制废弃当前URL,向客户端发送一个410的HTTP响应代码，来表明此URL是已被废弃的。 handler|H=Content-handler 为需要处理的目标文件强制指定一个内容处理器。 last|L 停止标志，当mod_rewrite模块处理到此标志时会停止重写搡作，并不再应用其他重写规则。此标志通常用于跳出规则处理。 next|N 循环重写规则，从第一个规则开始再次执行重写规则，但此时处理的URL己经不是原始的URL.它相当于Perl语言中的next命令。 nocase|NC 忽略Pattern中的大小写。 Noescape|NE 使用此标记可以让URL中允许使用一些特殊字符，例如，‘T’、‘%’、‘;’等，如果不使用此标记则是将这些特殊字符转换成等值的16进制编码如‘%25’、‘%24’、‘%3B’等。 nosubreq|NS 不对内部子请求进行处理，使用此标记如果是内部子请求，则跳过当前规则。通常使用CGI脚本的子请求会出现一些问题，因此可以使用些标记来 禁止子请求。 proxy|P 强制重写的URL在内部由代理服务器模块来处理，并中断重写过程。使用这个标记要注意的是需要保证代理模块已经被加载，同时替换的字串是一个能被代理模块处理的有效的URL,否则代理模块将会返回一个错误的信息。 Passthiough|PT 将此URL强制交给下一个处理器来进行处理，而在转交之前， mod_rewrite模块会将内部request_rec结构中的URL字段设置为filename字段的值，这使得RewriteRule指令的输出能够被（从URL转换到文件名的）Alias、 ScriptAlias、Redirect等指令进行后续处理。 Qsappend|QSA 你可以通过此标记在现有的替换字符串中增加一个査询字符串， 注意：是增加而不是替换。 将重写的URL作为一个重定向处理，在使用这个标记时，必须确保该替换字段是一个有效的URL。否则，它会指向一个无效的位置，并且此 标记本身只是对URL加上http://thishost[:thisport]/前缀。如果没有在此标记后面指 定HTTP响应代码则会使用302 (临时性移动）这个响应代码来进行处理。用户可指定的响应代码范围为300〜400,或是使用符号化的名称，例如，temp、seeother 等。 redirect|R[=code] 将重写的URL作为一个重定向处理，在使用这个标记时，必须确保该替换字段是一个有效的URL。否则，它会指向一个无效的位置，并且此 标记本身只是对URL加上 http://thishost[:thisport]/ 前缀。如果没有在此标记后面指 定HTTP响应代码则会使用302 (临时性移动）这个响应代码来进行处理。用户可指定的响应代码范围为300〜400,或是使用符号化的名称，例如，temp、seeother 等。 skip|S=num 与chain|C标记不同，使用此标记将强制跳过后继的规则，用户可以通过此标记来模拟if-then-else结构，最后一个规则是then从句，而被跳过的skip=N 个规则是else从句。 type|T=MIME-type 强制目标文件的MIME类型为MIME-type所指定的类型。 禁止资源目录解析PHP程序 方案1：提示下载不解析\ntext 1 2 3 4 5 6 \u0026lt;Directory \u0026#34;/app/apache-2.4.18/htdocs/php\u0026#34;\u0026gt; AllowOverride None Options None Require all granted php_admin_flag engine off #\u0026lt;==禁止解析php文件，此参数必须apex方式安装才存在。反之报错 \u0026lt;/Directory\u0026gt; php_admin_flag engine off 这个语句就是禁止解析 php 的控制语句，但只这样配置还不够，因为这样配置后用户依然可以访问 php 文件，只不过不解析了，但可以下载，用户下载 php 文件也是不合适的，所以有必要再禁止一下。\n禁止目录索引浏览功能 默认配置当网站没有首页文件时，httpd会把整个目录结构展示给网站用户，这是非常大的隐患，必须要屏蔽掉。\n在主配置文件 httpd.conf 或者虚拟主机的配置文件 httpd-vhost.conf 文件配置如下内容即可\ntext 1 2 3 4 5 6 \u0026lt;Directory \u0026#34;/app/apache-2.4.18/htdocs\u0026#34;\u0026gt; Options FollowSymLinks AllowOverride None Require all granted Allow from all \u0026lt;/Directory\u0026gt; 或\ntext 1 2 3 4 5 6 \u0026lt;Directory \u0026#34;/app/apache-2.4.18/htdocs\u0026#34;\u0026gt; Options -Indexes FollowSymLinks AllowOverride None Require all granted Allow from all \u0026lt;/Directory\u0026gt; 更改之后出现403权限问题，说明禁止了目录索引功能\n关闭无用的CGI功能配置 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 304 \u0026lt;IfModule alias_module\u0026gt; 305 # 306 # Redirect: Allows you to tell clients about documents that used to 307 # exist in your server\u0026#39;s namespace, but do not anymore. The client 308 # will make a new request for the document at its new location. 309 # Example: 310 # Redirect permanent /foo http://www.example.com/bar 311 312 # 313 # Alias: Maps web paths into filesystem paths and is used to 314 # access content that does not live under the DocumentRoot. 315 # Example: 316 # Alias /webpath /full/filesystem/path 317 # 318 # If you include a trailing / on /webpath then the server will 319 # require it to be present in the URL. You will also likely 320 # need to provide a \u0026lt;Directory\u0026gt; section to allow access to 321 # the filesystem path. 322 323 # 324 # ScriptAlias: This controls which directories contain server scripts. 325 # ScriptAliases are essentially the same as Aliases, except that 326 # documents in the target directory are treated as applications and 327 # run by the server when requested rather than as documents sent to the 328 # client. The same rules about trailing \u0026#34;/\u0026#34; apply to ScriptAlias 329 # directives as to Alias. 330 # 331 ScriptAlias /cgi-bin/ \u0026#34;/app/apache-2.4.18/cgi-bin/\u0026#34; 332 333 \u0026lt;/IfModule\u0026gt; \u0026lt;Directory \u0026#34;/app/apache-2.4.18/htdocs/bbs\u0026#34;\u0026gt; Options FollowSymLinks AllowOverride None Order allow,deny Allow form all \u0026lt;/Directory\u0026gt; 禁止httpd用户重载功能 text 1 2 3 4 5 6 \u0026lt;Directory \u0026#34;/app/apache-2.4.18/htdocs\u0026#34;\u0026gt; Options -Indexes FollowSymLinks AllowOverride None\t#\u0026lt;==禁止用户重载 Require all granted Allow from all \u0026lt;/Directory\u0026gt; 禁止用户重载会加快服务器响应速度，因为它不在为每个请求寻找每个目录访问控制文件(.htaccess)。也杜绝了开发人员变相修改配置的安全隐患\n避免使用.htaccess文件 在Apache中，AllowOvCTride 指令来设置是否启用 .htaccess 文件功能。但是对于一些管理员来说，更简单更精细化的控制目录可以为他们节省很多的时间，于是 .htaccess 文件提供了一个这样的功能，你可以使用默认AllowOverride指令是使用None参数来禁止使用.htaccess文件功能。\n可使用参数：\ntext 1 2 3 4 5 6 * All：使用所有能在.htaccess文件中使用的指令. * AuthConfig：使用鉴权指令，例如，AuthName、AuthType 等. * FileInfo：使用控制文件类型的指令，例如，ErrorDocument、SetOutputFilter等. * Indexes:使用目录索引指令. * Options：使用控制目录功能指令. * Limit：使用主机访问控制指令. 注意：\n.htaccess 文件会导致服务器性能的急速（如果服务器上有很多目录，且每层目录下都有.haccess文件）下降，在使用了AllowOverride指令允许使用 .htaccess 文件后，无论是否使用 .htaccess 文件，httpd都会在每个目录下查找 .htaccess 文件。其次，当每个请求链接到来时，httpd 会查找链接所请求目录下的 .htaccess 文件，并且再査找它的上级目录中的 .htaccess 文件以使 .htaccess 文件内的设置都能生效。这些査找会让httpd 性能降低很多。另外还有安全方面的问题，.htaccess 文件可以修改和覆盖服务器的指令，因此会产生一些未被限制的修改，而这些修改将会导致一些安全问题的出现。\n如何对httpd配置优化？ 配置软件软件轮训Apache访问日志 优化访问日志记录的信息 配置HTTP错误页面优雅显示 配置 httpd 对站点文件压缩 Mod_Expires缓存功能 更改 httpd 默认用户 调整 httpd 的工作模式 屏蔽 httpd 对外显示的版本等敏感信息 屏蔽 httpd 对外显示的版本等敏感信息 最小化 httpd 目录及文件权限设置 最小化 httpd 日志目录权限 加大 httpd 并发连接数 配置 httpd 防盗链功能 禁止 httpd 目录索引浏览功能 禁止 httpd 用户重载功能 避免使用.htaccess文件 关闭无用的CGI功能配置 禁止资源目录解析PHP程序 使用TMPFS文件系统替代频繁访问的目录 尽可能减少HTTP请求 使用CDN左网站加速 httpd 程序架构优化 httpd 的安全模块 正确途径取得源代码，勤打 httpd 补丁 系统内核参数优化 配置Mod_Pagespeed优化Web性能 防止用户请求跳出Web站点目录 ","permalink":"https://www.161616.top/apache-httpd/","summary":"httpd下载地址：Historical releases\n安装httpd bash 1 2 3 4 5 6 7 $ ls ABOUT_APACHE buildconf\temacs-style INSTALL\tLICENSE\tos\tsrclib acinclude.m4 CHANGES\thttpd.dep InstallBin.dsp Makefile.in README support Apache.dsw config.layout httpd.dsp LAYOUT Makefile.win README.platforms test build configure httpd.mak libhttpd.dep modules README-win32.txt VERSIONING BuildAll.dsp configure.in httpd.spec libhttpd.dsp NOTICE ROADMAP BuildBin.dsp docs include libhttpd.mak NWGNUmakefile server httpd 编译参数 参数选项 注释说明 ./configure 配置源代码树 –prefix=/usr/local/apache2 体系无关文件的顶级安装目录PREFIX，也就Apache的安装目录。 –enable-module=so [-enable-deflate] 打开so模块，so模块是用来提DSO支持的apache核心模块 –enable-deflate=shared [-enable-expires] 支持网页压缩 –enable-expires=shared [-enable-rewrite] 支持缓存过期控制 –enable-rewrite=shared 支持URL重写 –enable-cache 支持缓存 –enable-file-cache 支持文件缓存 –enable-mem-cache 支持记忆缓存 –enable-disk-cache 支持磁盘缓存 –enable-static-support 支持静态连接(默认为动态连接) –enable-static-htpasswd 使用静态连接编译htpasswd–管理用于基本认证的用户文件 –enable-static-htdigest 使用静态连接编译htdigest–管理用于摘要认证的用户文件 –enable-static-rotatelogs 使用静态连接编译rotatelogs–滚动Apache日志的管道日志程序 –enable-static-logresolve 使用静态连接编译logresolve–解析Apache日志中的IP地址为主机名 –enable-static-htdbm 使用静态连接编译htdbm–操作DBM密码数据库 –enable-static-ab 使用静态连接编译ab–Apache服务器性能测试工具 –enable-static-checkgid 使用静态连接编译checkgid –disable-cgid 禁止用一个外部CGI守护进程执行CGI脚本 –disable-cgi 禁止编译CGI版本的PHP –disable-userdir 禁止用户从自己的主目录中提供页面 –with-mpm=worker 让apache以worker方式运行 –enable-authn-dbm=shared 对动态数据库进行操作。Rewrite时需要。 以下是分门别类的更多参数注解，与上面的会有重复 用于apr的configure脚本的选项： 可选特性 \u0026ndash;enable-experimental-libtool 启用试验性质的自定义libtool \u0026ndash;disable-libtool-lock 取消锁定(可能导致并行编译崩溃) \u0026ndash;enable-debug 启用调试编译，仅供开发人员使用。 \u0026ndash;enable-maintainer-mode 打开调试和编译时警告，仅供开发人员使用。 \u0026ndash;enable-profile 打开编译profiling(GCC) \u0026ndash;enable-pool-debug[=yes|no|verbose|verbose-alloc|lifetime|owner|all] 打开pools调试 \u0026ndash;enable-malloc-debug 打开BeOS平台上的malloc_debug \u0026ndash;disable-lfs 在32-bit平台上禁用大文件支持(large file support) \u0026ndash;enable-nonportable-atomics 若只打算在486以上的CPU上运行Apache，那么使用该选项可以启用更加高效的基于互斥执行 的原子操作。 \u0026ndash;enable-threads 启用线程支持在线程型的MPM上必须打开它 \u0026ndash;disable-threads 禁用线程支持，如果不使用线程化的MPM，可以关闭它以减少系统开销。 \u0026ndash;disable-dso 禁用DSO支持 \u0026ndash;enable-other-child 启用可靠子进程支持 \u0026ndash;disable-ipv6 禁用IPv6支持 **可选的额外程序包** \u0026ndash;with-gnu-ld 指定C编译器使用GNU ld \u0026ndash;with-pic 只使PIC/non-PIC对象[默认为两者都使用] \u0026ndash;with-tags[=TAGS] 包含额外的配置 \u0026ndash;with-installbuilddir=DIR 指定APR编译文件的存放位置(默认值为：’${datadir}/build’) \u0026ndash;without-libtool 禁止使用libtool连接库文件 \u0026ndash;with-efence[=DIR] 指定Electric Fence的安装目录 \u0026ndash;with-sendfile 强制使用sendfile(译者注：Linux2.","title":"Apache httpd配置集锦"},{"content":"PHP引擎缓存优化加速 eaccelerator zend opcode xcache 使用tmpfs作为缓存加速缓存的文件目录 bash 1 2 mount -t tmpfs tmpfs /dev/shm -o size=256m mount -t tmpfs /dev/shm/ /tmp/eaccelerator/ 利用好tmpfs\n1.上传目录缩略图临时处理目录/tmp.\n2.其他加速器临时目录/tmp/eaccelerator/\nphp.ini参数优化 无论是 apache 还是 nginx，php.ini都是适合的。而 php-fpm.conf 适合nginx。而php-fpm.conf更适合 nginx+fcgi 的配置。首选选择产品环境的 php.ini\n开发场景：development 生产环境：production 打开php的安全模式 php的安全模式是个非常重要的php内嵌的安全机制，能够控制一些php中的函数执行，比如system() ,同时把很多文件操作的函数进行了权限控制。php5.4后弃用\n该参数配置如下：\ntext 1 2 3 336 ; Safe Mode 337 ; http://php.net/safe-mode 338 safe_mode = Off 用户和安全组 当safe_mode打开时，safe_mode_gid被关闭，那么php脚本能够对文件进行访问，而且相同组的用户也能够对文件进行访问。建议设置为safe_mode_gid=off;\n如果不进行设置，可能我们无法对我们服务器网站目录下的文件进行操作了，比如我们需要对文件进行操作的时候。php5.3默认为 safe_mode_gid=off; （新版弃用）\n关闭危险函数 如果打开了安全模式，那么函数禁止是可以不需要的，但是我们为了安全还是考虑进去。比如，我们觉得不希望执行包括 system() 等在那的能够执行命令的php函数，或者能够查看php信息的 phpinfo() 等函数，那么我们就可以禁止他们，方法如下：\ntext 1 disable_functions = system,passthru,exec,shell_exec,popen,phpinfo 如果你要禁止任何文件和目录的操作，那么可以关闭很多文件操作。\ntext 1 disable_functions = chdir,chroot,getcwd,opendir,readdir,scandir,fopen,unlink,delete,copy,mkdir,rmdir,rename,file,file_get_contents,fputs,fwrite,chgrp,chmod,chown 以上只列出部分不叫常用的文件处理函数，你也可以把上面执行命令函数和这个函数结合，就能够地址大部分的phpshell了。该参数默认为 disable_functions=.\n关闭PHP版本信息在http头中的泄露 为了防止黑客获取服务器中php版本信息，可以关闭该信息泄露在http头中，该参数默认如下：\ntext 1 2 3 4 5 # 在http头中加上其签名，不会有安全上直接威胁，但使客户端知道PHP版本 expose_php = On # 建议设置为Off expose_php = Off 建议设置为Off，这样当黑客抓取头信息时，无法看到PHP的信息。\n关闭注册全局变量 在PHP中提交的变量，包括使用POST或者GET提交的变量，都将自动注册为全局变量，能够直接访问，这是对服务器非常不安全的，所以我们不能让它注册为全局变量，就把注册全局变量选项关闭（5.4弃用）\ntext 1 register_globals = Off 打开magic_quotes_gpc来防止SQL注入 SQL注入是非常危险的问题，轻则网站后台被入侵，重则整个服务器沦陷，php.ini 有一个设置，5.4已移除\n错误信息控制 一般php在没有连接到数据库或者其他情况下会有提示错误，一般错误信息会包含php脚本当前距离信息或者查询的SQL语句等信息，这类信息提供给黑客后，是不安全的，所以一般服务器建议禁止错误提示\ntext 1 2 3 4 5 display_errors = Off ; 是否将错误信息作为输出的一部分显示给终端用户。应用调试时，可以打开，方便查看错误. ; 在最终发布的web站点上，强烈建议你关掉这个特性，并使用错误日志代替（参看下面）. ; 在最终发布的web站点打开这个特性可能暴露一些安全信息. ; 例如你的web服务器上的文件路径、数据库规划或别的信息. 设置为\ntext 1 display = Off # (php 5.3+默认即为Off) 如果你确实要显示错误信息，一定要设置显示错误级别，比如只显示警告以上的信息，当然最好是关闭错误提示\ntext 1 error_reporting = E_WARNING \u0026amp; E_ERROR 错误日志 建议在关闭display_errors后能够把错误信息记录下来，便于查找服务器运行的原因：\ntext 1 log_errors = On 同时也要设置错误日志存放的目录，建议和Web日志存放在一起.\ntext 1 2 # 文件必须允许web用户和组具有写权限 error_log = /app/logs/php_errors.log 部分资源限制参数优化 设置每个脚本运行的最长时间 当无法上传较大的文件或者后台备份数据经常超时，此时需要调整如下设置：\ntext 1 2 3 4 5 6 max_execution_time = 30 ; 每个脚本允许最大执行时间（秒），0表示没有限制. ; 这个参数有助于组织劣质脚本无休止占用服务器资源. ; 该指令仅影响脚本本身的运行时间，任何其它华为在脚本运行之外的时间. ; 如用system()/sleep()函数的使用、数据库查询、文件上传等，都不包括在内. ; 在安全模式下，你不能用ini_set()在运行时改变这个位置. 每个脚本使用的最大内存 text 1 2 3 4 5 6 memory_limit = 128M ; 一个脚本能够申请到的最大内存字节数（可以使用K和M作为单位）. ; 这有助于防止劣质脚本消耗完服务器上所有内存. ; 要能够使用该指令碧玺在编译时使用“--enable-memory-limit”配置选项. ; 如果要取消内存限制，则必须将其设为-1. ; 设置了该指令后，memory_get_usage()函数变为可用. 每个脚本等待输入数据最长时间 text 1 2 3 max_input_time = -1 ; 每个脚本解析输入数据（POST,GET,upload）的最大允许时间（秒） ; -1 表示不限制 http://php.net/max-input-time\n设置为\ntext 1 max_input_time = 60 上载文件的最大许可大小 当上传较大文件时，需要调整如下参数：\ntext 1 2 ; 上载文件的最大许可大小 upload_max_filesize = 2M 详情：http://php.net/upload-max-filesize\n部分安全参数优化 禁止打开远程地址 记得最近出的 php include 的那个漏洞吗？就是在一个php程序中include变量，那么入侵者就可以利用这个控制服务器在本地执行远程的一个php程序，例如phpshell，所以我们关闭这个。\ntext 1 2 ; Whether to allow the treatment of URLs (like http:// or ftp://) as files. allow_url_fopen = On 详情：http://php.net/allow-url-fopen\n测试远程allow_url_include www.remote.com/1.php：\nphp 1 2 3 \u0026lt;?php $arr = array(1,2,3,4,5); ?\u0026gt; www.httpd.com/1.php：\nphp 1 2 3 4 \u0026lt;?php include(\u0026#39;http://www.remote.com:81/1.php\u0026#39;); var_dump($arr); ?\u0026gt; 运行结果\n测试远程allow_url_fopen 本地读取远端文件的脚本文件内容如下：\nphp 1 2 3 4 5 6 7 8 9 10 \u0026lt;?php $file = fopen(\u0026#39;http://www.remote.com:81/1.php\u0026#39;, \u0026#39;r\u0026#39;);\t// 读取远程文件 $i=1; //初始化行号 while( !feof($file) ){ $row = fgets($file);\t//读取一行 echo $i . \u0026#39;--\u0026#39; . $row;\t//输出 $i++; } fclose($file); ?\u0026gt; 执行脚本结果如下：\nbash 1 2 3 4 $ /app/php/bin/php 2.php 1--\u0026lt;?php 2--$arr = array(1,2,3,4,5); 3--?\u0026gt; 当在禁止后，再重新运行以上脚本文件会报如下错误\nbash 1 2 3 Warning: include() [[function.include](http://www.httpd.com/function.include)]: http:// wrapper is disabled in the server configuration by allow_url_fopen=0 Warning: fopen() [[function.fopen](http://www.httpd.com/function.fopen)]: http:// wrapper is disabled in the server configuration by allow_url_fopen=0 调整php session信息存放类型和位置 text 1 2 3 4 5 6 ; 存储和检索会话关联的数据的处理器名字。默认为文件“file” ; 如果想要使用自定义的处理器（如基于数据库的处理器），可用“user” ; 设为\u0026#34;memcache\u0026#34;则可以使用memcache作为会话处理器（需要指定\u0026#34;--enable-memcache-session\u0026#34;编译选项）。 session.save_handler = files ; 传递给处理器的参数。对于files处理器，此值是创建会出数据文件的路径。 session.save_path = \u0026#34;/tmp\u0026#34; web集群session贡献存储设置，默认php.ini中session的类型和配置路径：\ntext 1 session.save_handler = \u0026#34;tcp://192.168.2.8:11211\u0026#34; PHP-FPM参数优化 如果你的高负载网站使用PHP-FPM管理FastCGI，也许下面这些技巧对你有用\n尽量少安装PHP模块，最简单是最好（快）的\n把你的PHP FastCGI子进程数调到100或以上，在4G内存的服务器上200就可以（建议压力测试来得出自己服务器合理的值）\nsocket连接FastCGI，/dev/shm是内存文件系统，socket放在内存中肯定会快些\nLinux下增加文件打开数，命令如下：\ntext 1 2 ; 增加 PHP-FPM 打开文件描述符的限制，此参数和php-fpm进程数有关 rlimit_files = 51200 使用php代码加速器，例如 eAccelerator, XCache.在Linux平台上可以把 cache_dir 指向 /dev/shm\nphp-fpm.conf重要优化参数详解：\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 pm = dynamic ; pm参数指定了进程管理方式，有两种可供选择：static或dynamic，从字面意思不难理解，为静态或动态方式。如果是静态方式，那么在php-fpm启动的时候就创建了指定数目的进程，在运行过程中不会再有变化(并不是真的就永远不变)；而动态的则在运行过程中动态调整，当然并不是无限制的创建新进程，受pm.max_spare_servers参数影响；动态适合小内存机器，灵活分配进程，省内存。静态适用于大内存机器，动态创建回收进程对服务器资源也是一种消耗 pm.max_children = 24 ; static模式下创建的子进程数或dynamic模式下同一时刻允许最大的php-fpm子进程数量 pm.start_servers = 16 ; 动态方式下的起始php-fpm进程数量 pm.min_spare_servers = 12 ; 动态方式下服务器空闲时最小php-fpm进程数量 pm.max_spare_servers = 24 ; 动态方式下服务器空闲时最大php-fpm进程数量 log_level = error ; 错误级别. 可用级别为: alert（必须立即处理）, error（错误情况）, warning（警告情况）, notice ;（一般重要信息）, debug（调试信息）. 默认: notice. error_log = /app/run/log/php-fpm.log ; 错误日志，默认在安装目录中的var/log/php-fpm.log pid = /app/run/php/php-fpm #\u0026lt;==一般规划好目录与mysql nginx等在同目录下，方便管理 ; pid设置，默认在安装目录中的var/run/php-fpm.pid，建议开启 emergency_restart_threshold = 0 emergency_restart_interval = 0 ; 表示在emergency_restart_interval所设值内出现SIGSEGV或者SIGBUS错误的php-cgi进程数如 ; 果超过 emergency_restart_threshold个，php-fpm就会优雅重启。这两个选项一般保持默认值。 process_control_timeout = 0 ; 设置子进程接受主进程复用信号的超时时间. 可用单位: s(秒), m(分), h(小时), 或者 d(天) 默认单位: s(秒). 默认值: 0. daemonize = yes ; 后台执行fpm,默认值为yes，如果为了调试可以改为no。在FPM中，可以使用不同的设置来运行多个进程池。 这些设置可以针对每个进程池单独设置. listen = 127.0.0.1:9000 ; fpm监听端口，即nginx中php处理的地址，一般默认值即可。可用格式为: \u0026#39;ip:port\u0026#39;, \u0026#39;port\u0026#39;, \u0026#39;/path/to/unix/socket\u0026#39;. 每个进程池都需要设置. listen.backlog = -1 ; backlog数，-1表示无限制，由操作系统决定，此行注释掉就行。backlog含义参考http://www.3gyou.cc/?p=41 listen.allowed_clients = 127.0.0.1 ; 允许访问FastCGI进程的IP，设置any为不限制IP，如果要设置其他主机的nginx也能访问这台FPM进程，listen处要设置成本地可被访问的IP。默认值是any。每个地址是用逗号分隔. 如果没有设置或者为空，则允许任何服务器请求连接 listen.owner = www listen.group = www listen.mode = 0666 ; unix socket设置选项，如果使用tcp方式访问，这里注释即可。 user = www group = www ; 启动进程的帐户和组 pm.max_requests = 1000 ; 设置每个子进程重生之前服务的请求数. 对于可能存在内存泄漏的第三方模块来说是非常有用的. 如果设置为 \u0026#39;0\u0026#39; 则一直接受请求. 等同于 PHP_FCGI_MAX_REQUESTS 环境变量. 默认值: 0. pm.status_path = /status ; FPM状态页面的网址. 如果没有设置, 则无法访问状态页面. 默认值: none. munin监控会使用到 ping.path = /ping ; FPM监控页面的ping网址. 如果没有设置, 则无法访问ping页面. 该页面用于外部检测FPM是否存活并且可以响应请求. 请注意必须以斜线开头 (/)。 ping.response = pong ; 用于定义ping请求的返回相应. 返回为 HTTP 200 的 text/plain 格式文本. 默认值: pong. request_terminate_timeout = 0 ; 设置单个请求的超时中止时间. 该选项可能会对php.ini设置中的\u0026#39;max_execution_time\u0026#39;因为某些特殊原因没有中止运行的脚本有用. 设置为 \u0026#39;0\u0026#39; 表示 \u0026#39;Off\u0026#39;.当经常出现502错误时可以尝试更改此选项。 request_slowlog_timeout = 10s ; 当一个请求该设置的超时时间后，就会将对应的PHP调用堆栈信息完整写入到慢日志中. 设置为 \u0026#39;0\u0026#39; 表示 \u0026#39;Off\u0026#39; slowlog = log/$pool.log.slow ; 慢请求的记录日志,配合request_slowlog_timeout使用 rlimit_files = 1024 ; 设置文件打开描述符的rlimit限制. 默认值: 系统定义值默认可打开句柄是1024，可使用 ulimit -n查看，ulimit -n 2048修改。 一般 php-fpm 进程占用20~30m左右的内存就按 30m 算。如果单独跑 php-fpm，动态方式起始值可设置物理内存 $\\frac{Mem}{30M}$，由于大家一般Nginx, MySQL都在一台机器上，于是预留一半给它们，即php-fpm进程数为 $\\frac{mem}{2\\times30M}$。\nReference https://jingyan.baidu.com/article/fdbd4277c4dacbb89f3f4855.html\nhttp://www.cnblogs.com/argb/p/3604340.html\n","permalink":"https://www.161616.top/php-ini/","summary":"PHP引擎缓存优化加速 eaccelerator zend opcode xcache 使用tmpfs作为缓存加速缓存的文件目录 bash 1 2 mount -t tmpfs tmpfs /dev/shm -o size=256m mount -t tmpfs /dev/shm/ /tmp/eaccelerator/ 利用好tmpfs\n1.上传目录缩略图临时处理目录/tmp.\n2.其他加速器临时目录/tmp/eaccelerator/\nphp.ini参数优化 无论是 apache 还是 nginx，php.ini都是适合的。而 php-fpm.conf 适合nginx。而php-fpm.conf更适合 nginx+fcgi 的配置。首选选择产品环境的 php.ini\n开发场景：development 生产环境：production 打开php的安全模式 php的安全模式是个非常重要的php内嵌的安全机制，能够控制一些php中的函数执行，比如system() ,同时把很多文件操作的函数进行了权限控制。php5.4后弃用\n该参数配置如下：\ntext 1 2 3 336 ; Safe Mode 337 ; http://php.net/safe-mode 338 safe_mode = Off 用户和安全组 当safe_mode打开时，safe_mode_gid被关闭，那么php脚本能够对文件进行访问，而且相同组的用户也能够对文件进行访问。建议设置为safe_mode_gid=off;\n如果不进行设置，可能我们无法对我们服务器网站目录下的文件进行操作了，比如我们需要对文件进行操作的时候。php5.3默认为 safe_mode_gid=off; （新版弃用）\n关闭危险函数 如果打开了安全模式，那么函数禁止是可以不需要的，但是我们为了安全还是考虑进去。比如，我们觉得不希望执行包括 system() 等在那的能够执行命令的php函数，或者能够查看php信息的 phpinfo() 等函数，那么我们就可以禁止他们，方法如下：\ntext 1 disable_functions = system,passthru,exec,shell_exec,popen,phpinfo 如果你要禁止任何文件和目录的操作，那么可以关闭很多文件操作。","title":"php.ini优化"},{"content":"编译错误 错误：同时指定了fpm与aspxs2方式错误 bash 1 2 You\u0026#39;ve configured multiple SAPIs to be build.You can build only one SAPI module and CLI binary at the same time 原因：导致的原因是我的配置参数中同时使用了\u0026ndash;enable-fpm 与\u0026ndash;with-apxs2，因此编译的时候出错了，去掉其中的任意一个参数编译成功。\n系统缺少libtool bash 1 make ***[libphp5.la] Error 1 解决方法：在编译PHP版本时，产生错误 make ***[libphp5.la] Error 1\n错误原因：系统缺少libtool\n解决办法：yum install libtool-ltdl-devel\nmake过程错误 make: *** [sapi/cli/php] Error 1 原因：在 「./configure 」 沒抓好一些环境变数值。错误发生点在建立「-o sapi/cli/php」是出错，没給到要 link 的 iconv 库参数。\n报错提示：\nbash 1 2 3 4 5 6 7 8 9 libiconv.so.2: cannot open shared object file: No such file or directory mak /root/tools/php-7.1.3/ext/iconv/iconv.c:2591: undefined reference to `libiconv_open\u0026#39; ext/xmlrpc/libxmlrpc/.libs/encodings.o: In function `convert\u0026#39;: /root/tools/php-7.1.3/ext/xmlrpc/libxmlrpc/encodings.c:74: undefined reference to `libiconv_open\u0026#39; /root/tools/php-7.1.3/ext/xmlrpc/libxmlrpc/encodings.c:82: undefined reference to `libiconv\u0026#39; /root/tools/php-7.1.3/ext/xmlrpc/libxmlrpc/encodings.c:102: undefined reference to `libiconv_close\u0026#39; collect2: ld returned 1 exit status make: *** [sapi/cli/php] Error 1 解决方法1：编辑Makefile 我的php7.1.3在88行的地方:在最后加上 -liconv，或者编译时，编译参数指定 iconv 安装目录不会报此错误。\nbash 1 2 3 4 113 EXTRA_LIBS = -lcrypt -lz -lexslt -lcrypt -lrt -lmcrypt -lpng -lz -ljpeg -lcurl -lz -lrt -lm -ldl -lnsl -lrt -lxml2 -l z -lm -lgssapi_krb5 -lkrb5 -lk5crypto -lcom_err -lssl -lcrypto -lcurl -lxml2 -lz -lm -lfreetype -lmysqlclient -lm -lr t -ldl -lxml2 -lz -lm -lxml2 -lz -lm -lcrypt -lxml2 -lz -lm -lxml2 -lz -lm -lxml2 -lz -lm -lxml2 -lz -lm -lxslt -lxml 2 -lz -lm -lssl -lcrypto -lcrypt 「-liconv」 解决方法2：自己打包替换系统内的iconv包\nmake: *** [ext/phar/phar.php] Error 127 bash 1 2 /root/dev/php-5.3.6/sapi/cli/php: error while loading shared libraries: libmysqlclient.so.18: cannot open shared object file: No such file or directory make: *** [ext/phar/phar.php] Error 127 解决：网上找到的解决办法是\nbash 1 ln -s /usr/local/mysql/lib/libmysqlclient.so.18 /usr/lib/ 照做后仍然报错，原因是该方法适用于32位系统，64位系统应使用下面的这行\nbash 1 ln -s /usr/local/mysql/lib/libmysqlclient.so.18 /usr/lib64/ 另外：在编译的时候，不写mysql的路径，而使用mysqlnd代替，也可解决该问题的出现。\n参考：\nbash 1 2 echo \u0026#34;/app/mysql/lib/libmysqlclient.so.18\u0026#34; \u0026gt;\u0026gt;/etc/ld.so.conf ldconfig configure: error: Don\u0026rsquo;t know how to define struct flock on this system, set \u0026ndash;enable-opcache=no 原因:目前不明\nbash 1 2 3 4 5 6 7 8 checking for sysvipc shared memory support... no checking for mmap() using MAP_ANON shared memory support... no checking for mmap() using /dev/zero shared memory support... no checking for mmap() using shm_open() shared memory support... no checking for mmap() using regular file shared memory support... no checking \u0026#34;whether flock struct is linux ordered\u0026#34;... \u0026#34;no\u0026#34; checking \u0026#34;whether flock struct is BSD ordered\u0026#34;... \u0026#34;no\u0026#34; configure: error: Don\u0026#39;t know how to define struct flock on this system, set --enable-opcache=no 解决方法：执行如下后，重新编译即可\nbash 1 export LD_LIBRARY_PATH=/app/mysql/lib 参考资料：http://www.jianshu.com/p/0d6d188c2ddc\nphp5.5 mysql5.6 bash 1 Don\u0026#39;t know how to define struct flock on this system, set --enable-opcache=no 解决方法：\nbash 1 2 3 4 5 ln -s /app/mysql/lib/libmysqlclient.so /usr/lib ln -s /app/mysql/lib/libmysqlclient.so.18.1.0 /usr/lib vim /etc/ld.so.conf /usr/lib ldconfig -v 在虚拟机中编译PHP问题 错误 make: *** [ext/fileinfo/libmagic/apprentice.lo] Error 1\n原因：这是由于内存小于1G所导致。\n解决办法：在./configure加上选项。\nbash 1 --disable-fileinfo Disable # \u0026lt;==fileinfo support 禁用 fileinfo configure: error: Cannot find libmysqlclient under /app/mysql. 经查，问题是64位系统中 libmysqlclient 默认安装到了 /usr/lib64/mysql/ 目录下，而 /usr/lib 目录下没有相应文件，但是php编译时，要去 /usr/lib目录下查找\n解决：ln -s /app/mysql/lib /app/mysql/lib64\nmake install错误 bash 1 2 3 /home/tools/php-5.3.27/sapi/cli/php: error while loading shared libraries: libmysqlclient.so.18: cannot open shared object file: No such file or directory make[1]: *** [install-pear-installer] 错误 127 make: *** [install-pear] 错误 2 原因：mysql5.5的的lib路径跟之前的不一样 解决：\nbash 1 2 echo \u0026#34;/app/mysql/lib\u0026#34; \u0026gt;\u0026gt; /etc/ld.so.conf ldconfig make install正确安装 PHP5.3\nbash 1 2 3 /home/tools/php-5.3.27/build/shtool install -c ext/phar/phar.phar /app/php-5.3.27/bin ln -s -f /app/php-5.3.27/bin/phar.phar /app/php-5.3.27/bin/phar Installing PDO headers: /app/php-5.3.27/include/php/ext/pdo/ PHP5.5\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Thank you for using PHP. config.status: creating php5.spec config.status: creating main/build-defs.h config.status: creating scripts/phpize config.status: creating scripts/man1/phpize.1 config.status: creating scripts/php-config config.status: creating scripts/man1/php-config.1 config.status: creating sapi/cli/php.1 config.status: creating sapi/fpm/php-fpm.conf config.status: creating sapi/fpm/init.d.php-fpm config.status: creating sapi/fpm/php-fpm.service config.status: creating sapi/fpm/php-fpm.8 config.status: creating sapi/fpm/status.html config.status: creating sapi/cgi/php-cgi.1 config.status: creating ext/phar/phar.1 config.status: creating ext/phar/phar.phar.1 config.status: creating main/php_config.h config.status: executing default commands ","permalink":"https://www.161616.top/install-troubleshooting/","summary":"编译错误 错误：同时指定了fpm与aspxs2方式错误 bash 1 2 You\u0026#39;ve configured multiple SAPIs to be build.You can build only one SAPI module and CLI binary at the same time 原因：导致的原因是我的配置参数中同时使用了\u0026ndash;enable-fpm 与\u0026ndash;with-apxs2，因此编译的时候出错了，去掉其中的任意一个参数编译成功。\n系统缺少libtool bash 1 make ***[libphp5.la] Error 1 解决方法：在编译PHP版本时，产生错误 make ***[libphp5.la] Error 1\n错误原因：系统缺少libtool\n解决办法：yum install libtool-ltdl-devel\nmake过程错误 make: *** [sapi/cli/php] Error 1 原因：在 「./configure 」 沒抓好一些环境变数值。错误发生点在建立「-o sapi/cli/php」是出错，没給到要 link 的 iconv 库参数。\n报错提示：\nbash 1 2 3 4 5 6 7 8 9 libiconv.so.2: cannot open shared object file: No such file or directory mak /root/tools/php-7.","title":"PHP安装错误记录"},{"content":"1 Memcached介绍及常见同类软件对比 1.1 Memcached是什么？ Memcached是一个开源的、支持高性能、高并发的分布式缓存系统，由C语言编写，总共2000多行代码。从软件名称上看，前3个字符的单词Mem就是内存的意思，接下来的后面5个字符的单词Cache就是缓存的意思，最后一个字符d是daemon的意思，代表是服务端守护进程模式服务。\nMemcached服务分为服务端和客户端两部分，其中，服务端软件的名字形如 Memcached-1.4.24.tat.gz，客户端软件的名字形如 Memcache-2.25.tar.gz\nMemcached软件诞生于2003年，最初由LiveJournal的BradFitzpatrick开发完成。Memcached是整个项目的名称，而Memcached是服务器端的主程序名，因其协议简单，使用部署方便、且支持高并发而被互联网企业广泛使用，知道现在仍然被广泛应用。官方网址：http://memcached.org\n1.2 Memcached的作用 传统场景，多数Web应用都将数据保存到关系型数据库中（例如MySQL），Web服务器从中读取数据并在浏览器中显示。但随着数据量的增大、访问的集中，关系型数据库的负担就会加重、响应缓慢、导致网站打开延迟等问题，影响用户体验。\n这时就需要Memcached软件出马了。使用Memcached的主要目的是，通过在自身内存中缓存关系型数据库的查询结果，减少数据库自身被访问的次数，以提高动态web应用的速度、提高网站架构的并发能力和可扩展性。\nMemcached服务的运行原理是通过在实现规划好的系统内存空间中临时缓存数据库的各类数据，以达到减少前端业务服务对数据库的直接高并发访问，从而达到提升大规模网站急群众动态服务的并发访问能力。\n生产场景的Memcached服务一般被用来保存网站中经常被读取的对象或数据，就像我们的客户端浏览器也会把经常访问的网页缓存起来一样，通过内存缓存来存取对象或数据要比磁盘存取快很多，因为磁盘是机械的，因此，在当今的IT企业中，Memcached的应用范围很广泛\n1.3 互联网常见内存服务软件 下表为互联网企业场景常见内存缓存服务软件相关对比信息：\n软件 类型 主要作用 缓存的数据 Memcached 纯内存型 常用于缓存网站后端的各类数据，例如数据库中的数据 主要缓存用户重复请求的动态内容，blog的博文BBS的帖子等内容用户的Session会话信息 Redis/Mongodb/memcachedb 可持久化存储，即使用内存也会使用磁盘存储 1. 缓存后端数据库的查询数据\n2.作为关系数据库的重要补充 1.作为缓存：主要缓存用户重复请求的动态内容：例如BLOG的博文、BBS的帖子等内容。2.作为数据库的有效补充：例如：好友关注、粉丝统计、业务统计等功能可以用持久化存储。 Squid/Nginx 内存或内存加磁盘缓存 主要用于缓存web前端的服务内容 主要用于静态数据缓存，例如：图片，附件（压缩包），js,css,html等，此部分功能大多数企业会选择专业的CDN公司如：蓝讯、网宿。 2 Memcached常见用途工作流程 Memcached是一种内存缓存软件，在工作中经常用来缓存数据库的查询数据，数据被缓存在事先预分配的Memcached管理的内存中，可以通过API或命令的方式存取内存中缓存的这些数据，Memcached服务内存中缓存的数据就像一张巨大的HASH表，每条数据都是以key-value对的形式存在。\n2.1 网站读取Memcached数据时的工作流程 Memcached用来缓存查询到的数据库中的数据，逻辑上，当程序访问后端数据库获取数据时会先优先访问Memcached缓存，如果缓存中有数据就直接返回给客户端用户，如果没有数据（没有命中）程序再去读取后端的数据库的数据，读取到需要的数据后，把数据返回给客户端，同时还会把读取到的数据库缓存到Memcached内存中，这样客户端用户再请求相同数据就会直接读取Memcached缓存的数据，这样就大大减轻了后端数据库的压力，并提高了整个网站的响应速断，提升了用户体验。\n图2-1展示了Memcached缓存系统和后端数据库系统的协作流程\r上图，使用Memcached缓存查询数据来减少数据库压力的具体工作流程如下：\nweb程序首先检查客户端请求的数据是否在Memcached缓存中存在，如果存在，直接把请求的数据返回给客户端，此时不在请求后端数据库。\n如果请求的数据在Memcached缓存中不存在，则程序会请求数据库服务，把数据库中取到的数据返回给客户端，此时不再请求后端数据库。\n2.2 网站更新Memcached数据时工作流程 当程序更新或者删除数据时，会首先处理后端数据库中的数据。 程序处理后端数据库中的数据的同时，也会通知Memcached中的对应旧数据失效，从而保证Memcached中缓存的数据始终和数据库中的户数一直，这个数据一致性非常重要，也是大型网站分布式缓存集群的最头痛的问题所在。 如果是在高并发读写场合，除了要程序通知Memcached过期的缓存失效外，还可能会通过相关机制，例如在数据库上部署相关程序（例如：在数据库中设置触发器使用UDFs），实现当数据库有更新就会把数据更新到Memcached服务中，使得客户端在访问新数据前，预先把更新过的数据库数据复制到Memcached中缓存起来，这样可以减少第一次查询数据库带来的访问压力，提升Memcached中缓存的命中率，甚至sina门户还会把持久化存储redis做成MySQL数据库的从库，实现真正的主从复制。 Memcached网站作为缓存应用更新数据流程图见下图1-2\rMemcached服务作为缓存应用通过相关软件更新数据见图2-2\r3 Memcached在企业中的应用场景 3.1 作为数据库查询数据缓存 3.1.1 完整数据缓存 例如电商的商品分类功能不会经常变动，就可以实现放到Memcached里，然后再对外提供数据访问。这个过程被称之为“数据预热”。\n此时秩序读取缓存无需读取数据库就能读到Memcached缓存里的所有商品分类数据了，所以数据库的访问压力就会大大降低了。\n为什么商品分类数据可以实现放在缓存里呢？\n因为，商品分类几乎都是由内部人员管理的，如果需要更新数据，更新数据库后，就可以把数据同时更新到Memcached里。\n如果把商品分类数据做成静态化文件，然后通过在前段WEB缓存或者使用CDN加速效果更好。\n3.1.2 热点数据缓存 热点数据缓存一般是用于由用户更新的商品，例如淘宝的卖家，当卖家新增商品后，网站程序就会把商品写入后端数据库，同时把这部分数据，放入Memcached内存中，下一次访问这个商品的请求就直接从Memcached内存中取走了。这种方法用来缓存网站热点的数据，即利用Memcached缓存经常被访问的数据。\n特别提示：这个过程可以通过程序实现，也可以在数据库上安装软件进行设置，直接由数据库把内容更新到Memcached中，相当于Memcached是MySQL的丛库一样。\n淘宝、京东、小米等电商双11秒杀抢购场景：\n如果碰到电商双11秒杀高并发的业务场景，必须要实现预热各种缓存，包括前端的web缓存和后端的数据缓存。\n先把数据放入内存预热，然后在逐步动态更新。先读取缓存，如果缓存里没有对应的数据，再去读取数据库，然后把读到的数据放入缓存。如果数据库里的数据更细，需要同时触发缓存更细，防止给用户过期的数据，当然对于百万级别并发还有很多其它的工作要做。\n⚠ 提示：这个过程可以通过程序实现，也可以在数据库上安装相关软件进行设置，直接由数据库把内容更新到Memcached中，就相当于Memcached是MySQL的从库一样\n如果碰到双十一、秒杀高并发的业务场景，必须要事先预热各种缓存，包括前段的Web缓存和后端的数据缓存。\n也就是说事先把数据放入内存预热，然后逐步动态更新。此时，会先读取缓存，如果缓存里没有对应的数据，再去读取数据库，然后把读到的数据放入缓存。如果数据库里的数据更新，需要同时触发缓存更新，防止给用户过期的数据，当然对于百万级别并发还有很多其他的工作要做。\n绝大多数的网站动态数据都是保存在数据库当中的，每次频繁地存取数据库，会导致数据库性能急剧下降，无法同时服务更多的用户（比如MySQL特别频繁的表锁就会存在此问题），那么，就可以让Memcached来分担数据库的压力。增加Memcached服务的好处除了可以分担数据库的压力以外，还包括无须改动整个网站架构，只需简单修改下程序逻辑，让程序先读取Memcached缓存查询数据即可。更新数据时也要更新Memcached缓存。\n【分布式应用1】\nMemcached支持分布式，我们在应用服务程序上改造，就可以更好的支持。例如：可以根据key适当进行有规律的封装，比如以用户位置的网站来说，每个用户都有UID。那么可以按照固定的ID来进行提取和存取，比如1开头的用户保存在第一台Memcached服务器上，以2开头的用户的数据保存在第二天Memcached服务器上，存取数据都先按照UID来进行的转换和存取。\n【分布式应用2】\n在应用服务器上通过程序及URL_HASH，抑制性哈希算法区访问Memcache服务，所有Memcached服务器的地址池可以简单的配在程序的配置文件里。\n【分布式应用3】\n门户网站如百度，会通过一个中间件代理负责请求后端的Cache服务。\n【分布式应用4】\n可以用常见的LVS haproxy做Cache的负载均衡，和普通应用服务相比，这里的重点是轮训算法，一般会选择url_hash，及consistent hash算法。\n算法重要性图解\n3.2 作为集群节点的session会话存储 即把客户端用户请求多个前端应用服务集群产生的session会话信息，统一存储到一个Memcached缓存中。由于session会话数据是存储在内存中的，所以速度很快。\n图3-2为Memcached服务在企业集群架构中常见的工作位置。\n3.3 Memcached服务在企业集群架构中的位置 下图为Memcached服务在企业集群架构中常见的工作位置。\n3.4 缓存雪崩效应 一般是由于某个节点生效，导致其他节点的缓存命中率下降，缓存中缺失的数据去数据可查询。短时间内造成数据库服务器崩溃。或，由于缓存周期性的输小，如：6小时失效一次，那么每6小时，将有一个请求\u0026quot;峰值\u0026quot;，严重情况下会导致数据库宕机。\n4 Memcached的特点与工作机制 4.1 Memcache的特征 Memcached作为高并发、高性能的缓存服务，具有如下特征：\n4.1.1 协议简单 Memcached的协议实现简单，采用基于文本行的协议，能通过telnet/nc等命令直接操作Memcached服务读取数据。\n4.1.2 支持epoll/kqueue异步I/O模型，使用libevent作为事件处理通知机制。 简单的说libevent是一套利用C开发的程序库，他将BSD系统的kqueue，Linux系统的epoll等事件处理功能封装成一个接口，确保即使服务器端的连接数量增加也能发挥很好的性能。Memcached就是利用这个libevent库进行异步事件处理。\n4.1.3 key/value键值数据类型 被缓存的数据以key/value键形式存在的，例如：\ntext 1 2 zhangsan=\u0026gt;23 key=zhangsan value=23 通过zhangsan key可以获取到23 4.1.4 全内存缓存，效率高 Memcached管理内存的方式非常搞笑，即全部的数据都存放于Memcached服务实现分配好的内存中，无持久化存储的设计，和系统的物理内存一样，当重启系统或Memcached服务时，Memcached内存中的数据即会丢失。\n如果希望重启后，数据依然能保存，那么就可以采用redis这样的持久性内存缓存系统的缓存数据。也可以在存放数据时，对存储的数据设置过期时间，这样过期后数据就自动被清除，Memcached服务本身不会监控数据过期，而是在访问的时候查看key的时间戳判断是否过期。\n4.1.5 可支持分布式集群 Memcached没有像MySQL那样的主从复制方式，分布式Memcached集群的不同服务器之间是互不通讯的，每一个节点都独立存取数据，并且数据内容也不一样。通过对Web应用端的程序设计或者通过支持hash算法的负载均衡软件，可以让Memcached支持大规模海量分布式缓存集群应用。\n4.2 Memcached工作原理与机制 4.2.1 Memcached工作原理 Memcached是一套类似C/S模式的架构软件，在服务器端启动Memcached服务守护进程，可以指定监听本地的IP地址、端口号、并发访问连接数，以及分配了多少内存来处理客户端请求。\n4.2.2 Socket时间处理机制 Memcached软件是由C语言来实现的，全部代码仅有2000多行，采用的是异步epoll/kqueue非阻塞I/O网络模型，其实现方式是基于异步的libevent时间单进程、单线程模式。使用libevent作为事件通知机制，应用程序端通过指定服务器的IP地址及端口，就可以连接Memcached服务进程通讯。\n4.2.3 数据存储机制 需要被缓存的数据以key/value键值对的形式保存在服务器端预分配的内存区中，每个被缓存的数据都有唯一的标识key，操作Memcached中的数据就是通过这个唯一标识的key进行的。缓存到Memcached中的数据仅放置在Memcached服务预分配的内存中，而非存储在Memcached服务器所在的磁盘上，因此存取速度非常快。\n由于Memcached服务自身没有对缓存的数据进行持久化存储的设计，因此，在服务端的Memcached服务进程重启之后，存储在内存中的这些数据都会丢失。且当内存中缓存的数据容量达到启动时设定的内存值时，也会自动使用LRU算法删除过期的数据。\n开发Memcached的初衷仅是通过内存缓存提示访问效率，并没有过多考虑数据的永久存储问题。因此，如果使用Memcached作为缓存数据服务，要考虑数据丢失后带来的问题，例如：是否可以重新生成数据，还有，在高并发场合下缓存宕机或重启会不会导致大量请求直接到数据库，导致数据库无法承受，最终导致网站架构雪崩等。\n4.2.4 内存管理机制 Memcached采用了如下机制： 采用slab内存分配机制。 采用LRU对象清除机制。 采用hash机制快速检索item。 4.2.5 多线程处理机制 多线程处理时采用的是pthread（POSIX）线程模式。 若要激活多线程，可在编译时指定：./configure --enable-threads 。 锁机制不够完善。 负载过重时，可以开启多线程（-t线程数为CPU核数）。 4.3 Memcached预热理念及集群节点的正确重启方法 4.3.1 Memcached预热理念 当需要大面积重启Memcached时，首先要在前端控制网站入口的访问流量，然后重启Memcached集群并进行数据预热，所有数据都预热完毕之后，在逐步开放前端网站入口的流量。\n为了满足Memcached服务可以持久化存储的需求，在较早时期，新浪网基于Memcached服务开发了一款NoSQL软件，名字为MemcacheDB，实现了在缓存的基础上增加了之久存储的特性，不过目前逐步被更优秀的redis mongodb取代了。\n4.3.2 如何正确开启网站集群服务器 如果由于机房断电或者搬迁服务器集群到新机房，那么启动集群服务器时，一定要从网站集群的后端**==依次往前端开启==**，特别是开启Memcached缓存服务器时要提前预热。\n5 Memcached内存管理 5.1 Memcached内存管理机制深入剖析 5.1.1 Malloc内存管理机制 在讲解Memcached内存管理机制前，先来了解malloc。\nmalloc的全称是memory allocation，中文名称动态内存分配，当无法知道内存具体位置的时候，想要绑定真正的内存空间，就需要用到动态分配内存。\n早期的Memcached内存管理是通过malloc分配的内存实现的，使用完后通过free来回收内存。这种方式容易产生内存碎片并降低操作系统对内存的管理效率。因此，也会加重操作系统内存管理器的负担，最坏的情况下，会导致操作系统比Memcached进程本身还慢，为了解决上述问题，Slab Allocator内存分配机制就诞生了。\n5.1.2 Slab内存管理机制 现在的Memcached是利用Slab Allocation机制来分配和管理内存的，过程如下。\n提前将大内存分配大小为1MB的若干个slab，然后针对每个slab在进行小对象填充，这个小对象成为chunk，避免大量重复的初始化和清理，减轻了内存管理器的负担。 Slab Allocation内存分配的原理是按照预先规定的大小，将分配给Memcached服务的内存预先分割成特定长度的内存块(chunk)分成组(chunks slab class)，这些内存块不会释放，可以重复利用。 新增数据对象存储时。因Memcached服务器中保存这slab内空闲chunk的列表，他会根据该列表选择chunk，然后将数据缓存于其中。当有数据存入时，Memcached根据接收到的数据大小，选择最适合数据大小的slab分配一个能存下这个数据的最小内存块(chunk)。例如：有100字节的一个数据，就会分配存入下面112字节的内存块中，会有这样12字节被浪费，这部分空间就不能被使用了，这也是SlabAllocator机制的一个缺点。\nSlab Allocator还可以重复使用已分配的内存，即分配道德内存不书房，而是重复利用。\nSlab Allocation的主要术语\nSlab Allocation主要术语 注解说明 slab class 内存区类别（48bytes-1MB） slab 动态创建的实际内存区，即分配给Slab的内存空间，默认是1MB。分配给Slab之后根据slab的大小切分成chunk。slab默认大小为1048576byte(1MB)，大于1MB的数据会忽略。 slab classid slab class的ID chunk 数据区块，固定大小，chunk初始大小，1.4版本中是48bytes item 实际存储在chunk中的数据项。 Slab内存管理机制特点\n提前分配大量内存Slab 1Mb，再进行小对象填充chunk。 避免大量重复的初始化和清理，减轻内存管理器的负担。 避免频繁malloc/free内存分配导致的碎片。 下面对Mc内存管理机制进行一个小结\nmc的早期内存管理机制为malloc（动态分配内存）。 malloc（动态内存分配）产生内存碎片，导致操作系统性能急剧下降。 Slab内存分配机制可以解决内存碎片的问题 Memcached服务的内存预先分割成特定长度的内存块，成为chunk，用于缓存数据的内存空间或内存块，相当于磁盘的block，只不过磁盘的每一个block都是相等的，而chunk只有在同一个Slab Class内才是相等的。 Slab Class指特定大小（1MB）的包含多个chunk的集合或组，一个Memcached包含多个Slab Class，每个Slab Class包含多个相同大小的chunk。 Slab机制也有缺点，例如，Chunk的空间会有浪费等。 5.2 Memcached Slab Allocator内存管理机制的缺点 chunk存储item浪费空间\nSlab Allocator解决了当初的内存碎片问题，但新的机制也给Memcached带来了新的问题。这个问题就是，由于分配的是特定长度的内存，因此无法有效利用分配的内存。例如：将100字节的数据缓存到128字节的chunk中，剩余的28字节就浪费了\n避免浪费内存的方法是，预先计算出应用存入的数据大小，或把同一业务类型的数据存入一个Memcached服务器中，确保存入的数据大小相对均匀，这样就可以较少内存的浪费。\n还有一种方法是，在启动时，指定-f参数，能在某种程度上控制内存组之间的大小差异。在应用中使用Memcached时，通常可以不重新设置这个参数，即默认值1.25进行部署即可。如果想优化Memcached对内存的使用，可以考虑重新计算数据的预期品均长度，调整这个参数来获得合适的设置值。\n5.3 Memcached的检测过期与删除机制 5.3.1 Memcached懒惰检测对象的过期机制 首先要知道，Memcached不会主动检测item对象是否过期，而是在进行get操作时检查item对象是否过期自己是否应该删除！\n因为不会主动检测item对象是否过期，自然也就不会释放已分配给对象的内存空间了，除非为添加的数据设定过期时间或内存缓存满了，在数据过期后，客户端不能通过key取他的值，起存储空前被重新利用。\nMemcached使用的这种策略为懒惰检测对象过期策略，即自己不监控存入的key/value对是否过期，而是在获取key值时查看记录时间戳(set key flag exptime bytes)，从而检查key/value对空间是否过期。这种策略不会在过期检测上浪费CPU资源\n5.3.2 Memcached惰性删除对象机制 当删除item对象时，一般不会释放内存空间，而是做删除标记，将指针放入slot回收插槽，下次分配的时候直接使用。\nMemcached在分配空间时，会优先使用已经过期的key/value对空间；若分配的内存空间占满，Memcached就会使用LRU算法来分配空间，删除最近最少使用的key/value对，从而将其空间分配给新的key/value对。在某些情况下（完整缓存），如果不想使用LRU算法，那么可以通过“-M”参数来启动Memcached，这样Memcached在内存耗尽时，会返回一本报错信息，如下：\nsh 1 -M return error on memory exhausted (rather than removing items) Memcache删除机制小结：\n不主动检测item对象是否过期，而是在get时才会检查item对象是否过期以及是否应该删除。\n当删除item对象时，一般不释放内存空间，而是做删除标记，将指针放入slot回收插槽，下次分配的时候直接使用。\n当内存空间满的时候，将会根据LRU算法把最近最少使用的item对象删除。\n数据存入可以设定过期时间，但是数据过期后不会被立即删除，而是在get时检查item对象是否过期以及是否应该删除。\n如果不希望系统使用LRU算法清楚数据，可以使用-M参数。\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 stats STAT curr_items 3 STAT total_items 10\t#←删除前的item对象 STAT evictions 0 END flush_all OK stats STAT pid 1532 ... STAT curr_items 3\t#←由于memcached删除机制的原理，数据并未释放而是做了标记 STAT total_items 10\t#←在下次访问过 STAT evictions 0 END flush_all OK STAT curr_items 2 STAT total_items 6 STAT evictions 0 END get a END stats ... STAT bytes 67 STAT curr_items 1 STAT total_items 6 STAT evictions 0 END # 数据过期与上述基本相同 set key 0 10 2 23 STORED stats .. STAT curr_items 1 STAT total_items 2 STAT evictions 0 END get ket END stats ... STAT curr_items 1 STAT total_items 2 STAT evictions 0 END get key END stats ... STAT curr_items 0 STAT total_items 2 STAT evictions 0 END memcached-slab内存管理\n6 Memcache服务安装 6.1 Memcached 安装 Memcached的安装比较简单，支持Memcached的平台常见的有Linux、FreeBSD、Solaris、windows。这里以CentOS 7为例进行讲解。\n6.1.1 安装libevent及连接Memcached工具nc 系统安装环境如下\nsh 1 2 3 4 5 6 $ cat /etc/redhat-release CentOS Linux release 7.1.1503 (Core) $ uname -r 3.10.0-229.el7.x86_64 $ uname -m x86_64 安装Memcached前，需要先安装libevent，此处用yum安装libevent。\nsh 1 2 yum install libevent libevent-devel nc -y # centos7 nc变更为nmap-ncat 6.1.2 编译安装Memcached sh 1 2 ./configure --prefix=/app/memcached make \u0026amp;\u0026amp; make install yum安装的Memcached版本略低，但是不影响使用。\nsh 1 2 3 4 5 6 7 8 9 10 11 # CentOS 6 $ yum list nc memcached Loaded plugins: fastestmirror, security Loading mirror speeds from cached hostfile Available Packages memcached.x86_64 1.4.4-3.el6_8.1 updates nc.x86_64 1.84-24.el6 base # CentOS 7 $ yum list memcached|grep memcached memcached.x86_64 1.4.15-10.el7_3.1 updates 6.2 安装Memcached客户端 LAMP PHP环境准备\n这里以PHP虚拟机程序为例，首先要在LAMP环境下能出来phpinfo信息页面，只有这样才能继续操作。具体如图：\n6.2.1 PHP扩展插件Memcache与Memcached PHP的Memcached扩展分为两个版本：\nmemcache 是 pecl 扩展库版本，原生支持php，出现于2004年。\nmemcached 是 libmemcached 版本，出现较后，是新一代，因此也更加完善，推荐使用。\n在安装memcache扩展的时候并不要求安装其他依赖，但是在安装memcached的时候会要求你安装libmemcached，问题来了，libmemcached是memcache的C客户端，它具有的优点是低内存，线程安全等特点。比如新浪微博之前就全面将php的memcache替换成php的memcached，在高并发下，稳定性果断提高。差别比较大的一点是，memcached 支持 Binary Protocol，而 memcache 不支持，意味着 memcached 会有更高的性能。不过，还需要注意的是，memcached 目前还不支持长连接。\n参考网址：http://blog.wpjam.com/m/memcache-vs-memcached/\n6.2.2 PHP Memcache扩展安装 php的Memcache的扩展插件下载地址为：http://pecl.php.net/package/memcache\nPHP的Memcache客户端扩展插件安装命令如下：\nbash 1 2 3 /app/php/bin/phpize ./configure --enable-memcache --with-php-config=/app/php/bin/php-config make \u0026amp;\u0026amp; make install 配置Memcache客户端，使其生效\n修改PHP的配置文件php.ini，加入Memcache客户端的配置\nsh 1 extension=/app/php-5.5/lib/php/extensions/no-debug-non-zts-20121212/memcache.so 重启php fpm服务使php的配置修改生效\nsh 1 /app/php/sbin/php-fpm -t 打开浏览器访问phpinfo页面，出现下图表示Memcache客户端安装成功。\n6.2.2 部署memcached PHP Memcached 扩展基于 libmemcached 开发的，使用 libmemcached 库提供的 API 与 Memcached 服务进行交互。顾安装php memcached扩展需要先安装libmemcached\nlibmemcached下载地址:https://launchpad.net/libmemcached\n安装libmemcached依赖包\nsh 1 yum install cyrus-sasl-devel -y 遇到如下错误：\nsh 1 2 configure: error: no, sasl.h is not available. Run configure with --disable-memcached-sasl to disable this check 解决：需先安装后在编译libmemcached\nsh 1 yum install cyrus-sasl-devel -y 编译libmemcached：\nsh 1 2 3 ./configure \\ --with-memcached=/usr/local/memcached \\ --prefix=/usr/local/libmemcached 安装PHP Memcached组件\n下载和解压这步，我们要区分是PHP7还是之前的版本：\n下载网址：http://pecl.php.net/package，这里写名3.0版本之后，支持PHP版本为7.0或以上\n编译参数\nsh 1 2 3 4 5 /app/php/bin/phpize ./configure \\ --enable-memcached \\ --with-php-config=/app/php-5.5/bin/php-config \\ --with-libmemcached-dir=/app/libmem-1.0.18/ 配置Memcache客户端，使其生效\n修改PHP的配置文件php.ini，加入Memcache客户端的配置\ntext 1 extension=/app/php-5.5/lib/php/extensions/no-debug-non-zts-20121212/memcached.so 打开浏览器访问phpinfo页面，出现下图表示Memcached组件安装成功。\n测试Memcache扩展与Memcached扩展\n测试Memcache扩展是否成功 php 1 2 3 4 $m = new Memcache(); $m-\u0026gt;connect(\u0026#39;127.0.0.1\u0026#39;,11211) or die(\u0026#39;Could not connect\u0026#39;); $m-\u0026gt;set(\u0026#39;key2321\u0026#39;,\u0026#39;zhangsan\u0026#39;); echo $m-\u0026gt;get(\u0026#39;key2321\u0026#39;); 测试Memcached扩展是否成功 php 1 2 3 4 $m = new Memcached(); $m-\u0026gt;addServer(\u0026#39;127.0.0.1\u0026#39;,11211,40); $m-\u0026gt;set(\u0026#39;ke1\u0026#39;,\u0026#39;zhangsan2\u0026#39;); echo $m-\u0026gt;get(\u0026#39;ke1\u0026#39;); 用telnet查询\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 $ telnet 127.0.0.1 11211 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is \u0026#39;^]\u0026#39;. get key2321 VALUE key2321 0 8 zhangsan END get ke1 VALUE ke1 0 9 zhangsan2 END 参考网址：http://www.bcty365.com/content-103-3516-1.html\n7 Memcached服务的基本管理 7.1 启动Memcached 启动Memcached的命令如下：\nsh 1 $ /home/memcached/bin/memcached -m 16m -p 11211 -d -u root -c 8192 查看启动状态\nsh 1 2 3 4 5 6 7 8 9 10 11 12 $ lsof -i:11211 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME memcached 16729 root 26u IPv4 139948 0t0 TCP *:memcache (LISTEN) memcached 16729 root 27u IPv6 139949 0t0 TCP *:memcache (LISTEN) memcached 16729 root 28u IPv4 139952 0t0 UDP *:memcache memcached 16729 root 29u IPv4 139952 0t0 UDP *:memcache memcached 16729 root 30u IPv4 139952 0t0 UDP *:memcache memcached 16729 root 31u IPv4 139952 0t0 UDP *:memcache memcached 16729 root 32u IPv6 139953 0t0 UDP *:memcache memcached 16729 root 33u IPv6 139953 0t0 UDP *:memcache memcached 16729 root 34u IPv6 139953 0t0 UDP *:memcache memcached 16729 root 35u IPv6 139953 0t0 UDP *:memcache 配置ld.so.conf路径防止启动Memcached时报错\nsh 1 2 echo \u0026#39;/usr/local/lib\u0026#39; \u0026gt;\u0026gt;/etc/ld.so.conf ldconfig 启动多个实例\nsh 1 memcached -m 16m -p 11212 -d -uroot -c 8192 查看结构\nsh 1 2 3 4 5 6 7 8 9 10 11 $ netstat -lntup Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:11211 0.0.0.0:* LISTEN 16729/memcached tcp 0 0 0.0.0.0:11212 0.0.0.0:* LISTEN 16741/memcached tcp6 0 0 :::11211 :::* LISTEN 16729/memcached tcp6 0 0 :::11212 :::* LISTEN 16741/memcached udp 0 0 0.0.0.0:11211 0.0.0.0:* 16729/memcached udp 0 0 0.0.0.0:11212 0.0.0.0:* 16741/memcached udp6 0 0 :::11211 :::* 16729/memcached udp6 0 0 :::11212 :::* 16741/memcached 7.2 Memcached启动相关参数说明 表7-1为Memcached启动命令相关参数说明\n命令参数 说明 进程与连接参数 -d 以守护进程（daemon）方式运行服务 -u 指定运行Memcached的用户，如果当前用户为root，需要使用此参数指定用户 -l 指定Memcached进程监听的服务器IP地址，可以不设置此参数。 -p 指定Memcached服务监听TCP端口号。默认为11211 -P 设置Memcached的PID文件（$$），保存PID到指定文件 内存相关设置 -m 指定Memcached服务可以缓存数据的最大内存，默认为64M -M Memcached服务内存不够时禁止LRU，如果内存满了会报错 -n 为key+value+flags分配的最小内存空间，默认为48节 -f chunk size增长因子，默认为1.25 -L 启用大内存页，可以降低内存浪费，改进性能。 并发连接设置 -c 最大的并发连接数，默认是1024 -t 线程数，默认4,。由于Memcached采用的是NIO，所以太多线程作用不大 -R 每个event的最大请求，默认是20 -C 禁用CAS（可以禁止版本计数，减少开销） 调试参数 -v 打印较少的errors/warings -vv 打印非常多调试信息和错误输出到控制台，也打印客户端命令及相应 -vvv 打印极多的调试信息和错误输出，也打印内部状态转变 更多参数 memcached -h\n7.3 向Memcached中写入数据。并检查 7.3.1 Memcached中的数据形式及与MySQL相关语句对比 向Memcached中添加数据时，注意添加的数据一般为键值对的形式，例如：key1-value1 key2-value2\n这里把Memcached添加、查询、删除等的命令和MySQL数据库做了一个基本类比。见表7-2\nMySQL数据库管理 Memcached管理 MySQL的insert语句 Memcached的set命令 MySQL的select语句 Memcached的get命令 MySQL的delete语句 Memcached的delete命令 管理MySQL和Memcached的常见命令类比\n7.3.2 向Memcached中写入数据实践 通过printf配合nc想Memcached中写入数据\nbash 1 2 $ printf \u0026#34;set key1 0 0 6\\r\\nzhang\\r\\n\u0026#34;|nc 127.0.0.1 11211 $ 如果set命令的字节是6就要6个字符（字节）。否则插入数据就不会成功\nbash 1 2 $ printf \u0026#34;set key1 0 0 6\\r\\nzhangs\\r\\n\u0026#34;|nc 127.0.0.1 11211 STORED 通过printf配合nc从Memcached中读取数据，命令如下：\nsh 1 2 3 4 $ printf \u0026#34;get key1\\r\\n\u0026#34;|nc 127.0.0.1 11211 VALUE key1 0 6 zhangs # 这就是读到的key1对应额值 END 通过printf配合nc从Memcached中删除数据\nsh 1 2 3 4 $ printf \u0026#34;delete key1\\r\\n\u0026#34;|nc 127.0.0.1 11211 DELETED #←出现DELETED表示成功删除key1及对应的数据 $ printf \u0026#34;get key1\\r\\n\u0026#34;|nc 127.0.0.1 11211 END ⚠ 提示：推荐使用上述方法测试操作Memcached 通过telnet命令写入数据时，具体步骤如下。\n通过telnet向Memcached写入数据\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $ telnet 127.0.0.1 11211 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is \u0026#39;^]\u0026#39;. add id 0 0 5\t12345\t#← 写入数据 STORED add id 0 0 3\t123 NOT_STORED\t#← 若key存在则报如下错误 set name 0 0 6 #← 写入数据，如果key不存在则创建key，如果存在则更改key的value值 张三\t#← 中文，每个字占3个bytes STORED get name VALUE name 0 6 张三 END get id VALUE id 0 5 12345 END set id 0 0 1\t#← 写入数据，如果key不存在则创建key，如果存在则更改key的value值 2 STORED get id VALUE id 0 1 2 END incr/decr\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 set key 0 0 1 9 STORED get key VALUE key 0 1 9 END incr key 1 10\tget key VALUE key 0 2\t#← 对整型增加后对应的bytes也增加 10\tEND get key VALUE key 0 1 1 END decr key 1 0 decr key 1 0 ⚠ 提示：telnet连接后如果输入字符错了，可以通过Ctrl+Backspace删除 7.3.3 操作Memcached相关命令的语法 以下为操作Memcached的相关命令基础语法\nsh 1 2 3 4 set\tkey1\t0 0\t6 [command name] [key] [flags] [exptime] [bytes] [datablock]\\r\\n [status]\\r\\n 表7-3 Memcached相关命令详细说明\n命令 说明 command name set：无论如何都进行写入数据，会覆盖老数据\nadd：只有对应数据不存在时才添加数据\nrepalce：只有数据存在时进行替换数据\ndelete [second] 加秒数之后，被删除的key N秒内不能再用，作用：让网站的页面也代谢完毕\nappend往后追加：prepend[key]datablock[status]?\nprepend往前追加：prepend[key]datablock[status]\ncas按版本号更换\nincr key num 增加一个值的大小。\ndecr key num 减少一个值的大小。\nincr decr操作将值进行32位无符号计算 0-232-1范围内\n应用场景，限时秒杀中库存量，先给抢中者分发订单号，数据低谷期将数据写入数据库 key 普通字符串，要求小于250个字符，不包含空格和控制字符 flags 客户端用来标识数据格式和数值，如json、xml、压缩、数组等 exptime 存活时间s，0为永久有效：编译时默认为30天。小于30天，60x60x24x30为秒数，大于30天为unix timestamp 如：团购网站，某团到中午12:00失效。 bytes byte字节数，不包含\\r\\n，根据长度截取存/取的字符串，可以是0，即存空串 datablock 文本行，以\\r\\n结尾，当然可以包含\\r或\\n status STORED/NOT_STORED/EXISTS/NOT_FOUNDERROR/CLIENT_ERROR/SERVER_ERROR服务器端会关闭连接以修复。 事例1：向memcached中插入数据\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 add id 0 0 5 12345 STORED set name 0 0 6 张三 STORED get name VALUE name 0 6 张三 END get id VALUE id 0 5 12345 END set id 0 0 1 2 STORED get id VALUE id 0 1 2 END 7.3.4 关闭Memcached 单实例关闭Memcached的方法如下：\nsh 1 killall memcached 或 pkill 若启动了多个实例Memcached，使用killall或pkill方式就会同时关闭这些实例！因此最好在启动时增加-P参数指定固定的pid文件，这样便于管理不同的实例。实例如下\nsh 1 2 memcached -m 16m -p 11211 -d -u root -c 8192 -P /var/run/memcached/11211.pid memcached -m 16m -p 11211 -d -u root -c 8192 -P /var/run/memcached/11212.pid 此时，即可以通过kill命令关闭Memcached\nsh 1 kill `cat /var/run/memcached/11211.pid` 关闭Memcached的方法小结如下：\nsh 1 2 3 4 ps -ef|grep memcached|grep -v grep|awk \u0026#39;{print $2}\u0026#39;|xargs kill kill `cat /var/run/memcached/11211.pid` pkill memcached killall memcached 7.3.5 企业工作场景中如何配置Memcached 在企业实际工作中，一般是开发人员提出需求，说要不熟一个Memcached数据缓存。运维人员在接到这个不确定的需求后，需要和开发人员深入沟通，进而确定要将内存指定为多大，或者和开发人员商量如何根据具体业务来指定内存缓存的大小。此外，还要确定业务的重要性，进而决定是否采取负载均衡、分布式缓存集群等架构，最后确定使用多大的并发连接数等。\n对于运维人员，部署Memcached一般就是安装Memcached服务端，把服务启动起来，最好监控，配好开机自启动，基本上就OK了，客户端的PHP程序环境一般在安装LNMP环境时都会提前安装Memcached客户端插件，Java程序环境下，开发人员会用第三方的JAR包直接连接Memcached服务。\n","permalink":"https://www.161616.top/memcached/","summary":"1 Memcached介绍及常见同类软件对比 1.1 Memcached是什么？ Memcached是一个开源的、支持高性能、高并发的分布式缓存系统，由C语言编写，总共2000多行代码。从软件名称上看，前3个字符的单词Mem就是内存的意思，接下来的后面5个字符的单词Cache就是缓存的意思，最后一个字符d是daemon的意思，代表是服务端守护进程模式服务。\nMemcached服务分为服务端和客户端两部分，其中，服务端软件的名字形如 Memcached-1.4.24.tat.gz，客户端软件的名字形如 Memcache-2.25.tar.gz\nMemcached软件诞生于2003年，最初由LiveJournal的BradFitzpatrick开发完成。Memcached是整个项目的名称，而Memcached是服务器端的主程序名，因其协议简单，使用部署方便、且支持高并发而被互联网企业广泛使用，知道现在仍然被广泛应用。官方网址：http://memcached.org\n1.2 Memcached的作用 传统场景，多数Web应用都将数据保存到关系型数据库中（例如MySQL），Web服务器从中读取数据并在浏览器中显示。但随着数据量的增大、访问的集中，关系型数据库的负担就会加重、响应缓慢、导致网站打开延迟等问题，影响用户体验。\n这时就需要Memcached软件出马了。使用Memcached的主要目的是，通过在自身内存中缓存关系型数据库的查询结果，减少数据库自身被访问的次数，以提高动态web应用的速度、提高网站架构的并发能力和可扩展性。\nMemcached服务的运行原理是通过在实现规划好的系统内存空间中临时缓存数据库的各类数据，以达到减少前端业务服务对数据库的直接高并发访问，从而达到提升大规模网站急群众动态服务的并发访问能力。\n生产场景的Memcached服务一般被用来保存网站中经常被读取的对象或数据，就像我们的客户端浏览器也会把经常访问的网页缓存起来一样，通过内存缓存来存取对象或数据要比磁盘存取快很多，因为磁盘是机械的，因此，在当今的IT企业中，Memcached的应用范围很广泛\n1.3 互联网常见内存服务软件 下表为互联网企业场景常见内存缓存服务软件相关对比信息：\n软件 类型 主要作用 缓存的数据 Memcached 纯内存型 常用于缓存网站后端的各类数据，例如数据库中的数据 主要缓存用户重复请求的动态内容，blog的博文BBS的帖子等内容用户的Session会话信息 Redis/Mongodb/memcachedb 可持久化存储，即使用内存也会使用磁盘存储 1. 缓存后端数据库的查询数据\n2.作为关系数据库的重要补充 1.作为缓存：主要缓存用户重复请求的动态内容：例如BLOG的博文、BBS的帖子等内容。2.作为数据库的有效补充：例如：好友关注、粉丝统计、业务统计等功能可以用持久化存储。 Squid/Nginx 内存或内存加磁盘缓存 主要用于缓存web前端的服务内容 主要用于静态数据缓存，例如：图片，附件（压缩包），js,css,html等，此部分功能大多数企业会选择专业的CDN公司如：蓝讯、网宿。 2 Memcached常见用途工作流程 Memcached是一种内存缓存软件，在工作中经常用来缓存数据库的查询数据，数据被缓存在事先预分配的Memcached管理的内存中，可以通过API或命令的方式存取内存中缓存的这些数据，Memcached服务内存中缓存的数据就像一张巨大的HASH表，每条数据都是以key-value对的形式存在。\n2.1 网站读取Memcached数据时的工作流程 Memcached用来缓存查询到的数据库中的数据，逻辑上，当程序访问后端数据库获取数据时会先优先访问Memcached缓存，如果缓存中有数据就直接返回给客户端用户，如果没有数据（没有命中）程序再去读取后端的数据库的数据，读取到需要的数据后，把数据返回给客户端，同时还会把读取到的数据库缓存到Memcached内存中，这样客户端用户再请求相同数据就会直接读取Memcached缓存的数据，这样就大大减轻了后端数据库的压力，并提高了整个网站的响应速断，提升了用户体验。\n图2-1展示了Memcached缓存系统和后端数据库系统的协作流程\r上图，使用Memcached缓存查询数据来减少数据库压力的具体工作流程如下：\nweb程序首先检查客户端请求的数据是否在Memcached缓存中存在，如果存在，直接把请求的数据返回给客户端，此时不在请求后端数据库。\n如果请求的数据在Memcached缓存中不存在，则程序会请求数据库服务，把数据库中取到的数据返回给客户端，此时不再请求后端数据库。\n2.2 网站更新Memcached数据时工作流程 当程序更新或者删除数据时，会首先处理后端数据库中的数据。 程序处理后端数据库中的数据的同时，也会通知Memcached中的对应旧数据失效，从而保证Memcached中缓存的数据始终和数据库中的户数一直，这个数据一致性非常重要，也是大型网站分布式缓存集群的最头痛的问题所在。 如果是在高并发读写场合，除了要程序通知Memcached过期的缓存失效外，还可能会通过相关机制，例如在数据库上部署相关程序（例如：在数据库中设置触发器使用UDFs），实现当数据库有更新就会把数据更新到Memcached服务中，使得客户端在访问新数据前，预先把更新过的数据库数据复制到Memcached中缓存起来，这样可以减少第一次查询数据库带来的访问压力，提升Memcached中缓存的命中率，甚至sina门户还会把持久化存储redis做成MySQL数据库的从库，实现真正的主从复制。 Memcached网站作为缓存应用更新数据流程图见下图1-2\rMemcached服务作为缓存应用通过相关软件更新数据见图2-2\r3 Memcached在企业中的应用场景 3.1 作为数据库查询数据缓存 3.1.1 完整数据缓存 例如电商的商品分类功能不会经常变动，就可以实现放到Memcached里，然后再对外提供数据访问。这个过程被称之为“数据预热”。\n此时秩序读取缓存无需读取数据库就能读到Memcached缓存里的所有商品分类数据了，所以数据库的访问压力就会大大降低了。\n为什么商品分类数据可以实现放在缓存里呢？\n因为，商品分类几乎都是由内部人员管理的，如果需要更新数据，更新数据库后，就可以把数据同时更新到Memcached里。\n如果把商品分类数据做成静态化文件，然后通过在前段WEB缓存或者使用CDN加速效果更好。\n3.1.2 热点数据缓存 热点数据缓存一般是用于由用户更新的商品，例如淘宝的卖家，当卖家新增商品后，网站程序就会把商品写入后端数据库，同时把这部分数据，放入Memcached内存中，下一次访问这个商品的请求就直接从Memcached内存中取走了。这种方法用来缓存网站热点的数据，即利用Memcached缓存经常被访问的数据。\n特别提示：这个过程可以通过程序实现，也可以在数据库上安装软件进行设置，直接由数据库把内容更新到Memcached中，相当于Memcached是MySQL的丛库一样。\n淘宝、京东、小米等电商双11秒杀抢购场景：\n如果碰到电商双11秒杀高并发的业务场景，必须要实现预热各种缓存，包括前端的web缓存和后端的数据缓存。","title":"memcached从入门到精通"},{"content":"配置防火墙，开启FTP服务器需要的端口 CentOS 7.0默认使用的是firewall作为防火墙，这里改为iptables防火墙。\n关闭firewall： bash 1 2 systemctl stop firewalld.service #停止firewall systemctl disable firewalld.service #禁止firewall开机启动 安装iptables防火墙 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 yum install iptables-services # 安装 vi /etc/sysconfig/iptables # 编辑防火墙配置文件 # Firewall configuration written by system-config-firewall # Manual customization of this file is not recommended. *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 -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 21 -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 10060:10090 -j ACCEPT -A INPUT -j REJECT --reject-with icmp-host-prohibited -A FORWARD -j REJECT --reject-with icmp-host-prohibited COMMIT :wq! #保存退出 systemctl restart iptables.service #最后重启防火墙使配置生效 systemctl enable iptables.service #设置防火墙开机启动 说明：\n21端口是ftp服务端口；10060到10090是Vsftpd被动模式需要的端口，可自定义一段大于1024的tcp端口。\n关闭SELINUX bash 1 2 3 4 5 6 vi /etc/selinux/config #SELINUX=enforcing # 注释掉 #SELINUXTYPE=targeted # 注释掉 SELINUX=disabled # 增加 :wq! #保存退出 setenforce 0 # 使配置立即生效 安装vsftpd bash 1 2 3 4 5 6 7 # 安装vsftpd yum install -y vsftpd # 安装vsftpd虚拟用户配置依赖包 yum install -y psmisc net-tools systemd-devel libdb-devel perl-DBI systemctl start vsftpd.service # 启动 systemctl enable vsftpd.service # 设置vsftpd开机启动 配置vsftp服务器 备份默认配置文件\nbash 1 cp /etc/vsftpd/vsftpd.conf /etc/vsftpd/vsftpd.conf-bak 执行以下命令进行设置\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 sed -i \u0026#34;s/anonymous_enable=YES/anonymous_enable=NO/g\u0026#34; \u0026#39;/etc/vsftpd/vsftpd.conf\u0026#39; sed -i \u0026#34;s/#anon_upload_enable=YES/anon_upload_enable=NO/g\u0026#34; \u0026#39;/etc/vsftpd/vsftpd.conf\u0026#39; sed -i \u0026#34;s/#anon_mkdir_write_enable=YES/anon_mkdir_write_enable=YES/g\u0026#34; \u0026#39;/etc/vsftpd/vsftpd.conf\u0026#39; sed -i \u0026#34;s/#chown_uploads=YES/chown_uploads=NO/g\u0026#34; \u0026#39;/etc/vsftpd/vsftpd.conf\u0026#39; sed -i \u0026#34;s/#async_abor_enable=YES/async_abor_enable=YES/g\u0026#34; \u0026#39;/etc/vsftpd/vsftpd.conf\u0026#39; sed -i \u0026#34;s/#ascii_upload_enable=YES/ascii_upload_enable=YES/g\u0026#34; \u0026#39;/etc/vsftpd/vsftpd.conf\u0026#39; sed -i \u0026#34;s/#ascii_download_enable=YES/ascii_download_enable=YES/g\u0026#34; \u0026#39;/etc/vsftpd/vsftpd.conf\u0026#39; sed -i \u0026#34;s/#ftpd_banner=Welcome to blah FTP service./ftpd_banner=Welcome to FTP service./g\u0026#34; \u0026#39;/etc/vsftpd/vsftpd.conf\u0026#39; echo -e \u0026#34;use_localtime=YES\\nlisten_port=21\\nchroot_local_user=YES\\nidle_session_timeout=300 \\ndata_connection_timeout=1\\nguest_enable=YES\\nguest_username=vsftpd \\nuser_config_dir=/etc/vsftpd/vconf\\nvirtual_use_local_privs=YES \\npasv_min_port=10060\\npasv_max_port=10090 \\naccept_timeout=5\\nconnect_timeout=1\u0026#34; \u0026gt;\u0026gt; /etc/vsftpd/vsftpd.conf 这是配置好的配置文件\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 anonymous_enable=NO local_enable=YES write_enable=YES local_umask=022 anon_upload_enable=NO anon_mkdir_write_enable=YES dirmessage_enable=YES xferlog_enable=YES connect_from_port_20=YES chown_uploads=NO xferlog_std_format=YES async_abor_enable=YES ascii_upload_enable=YES ascii_download_enable=YES ftpd_banner=Welcome to FTP service. listen=NO listen_ipv6=YES pam_service_name=vsftpd userlist_enable=YES tcp_wrappers=YES use_localtime=YES listen_port=21 chroot_local_user=YES idle_session_timeout=300 data_connection_timeout=1 guest_enable=YES guest_username=vsftpd user_config_dir=/etc/vsftpd/vconf virtual_use_local_privs=NO pasv_min_port=10060 pasv_max_port=10090 accept_timeout=5 connect_timeout=1 建立虚拟用户名单文件 bash 1 2 3 4 5 6 7 8 touch /etc/vsftpd/virtualUsers # 编辑虚拟用户名单文件：（第一行账号，第二行密码，注意：不能使用root做用户名，系统保留） vi /etc/vsftpd/virtualUsers public 123456 admin 111111 :wq! #保存退出 生成虚拟用户数据文件 bash 1 2 db_load -T -t hash -f /etc/vsftpd/virtusers /etc/vsftpd/virtualUsers.db chmod 600 /etc/vsftpd/virtualUsers.db #设定PAM验证文件，并指定对虚拟用户数据库文件进行读取 在/etc/pam.d/vsftpd的文件头部加入以下信息 bash 1 2 3 4 5 # 修改前先备份 cp /etc/pam.d/vsftpd /etc/pam.d/vsftpdbak vi /etc/pam.d/vsftpd auth sufficient /lib64/security/pam_userdb.so db=/etc/vsftpd/virtualUsers account sufficient /lib64/security/pam_userdb.so db=/etc/vsftpd/virtualUsers 注：\n在后面加入无效 如果系统为32位，上面改为lib，否则配置失败\n新建系统用户vsftpd 用户目录为/home/ftp 用户登录终端设为/bin/false(即使之不能登录系统)\nbash 1 2 3 useradd vsftpd -d /home/ftp -s /bin/false chown vsftpd:vsftpd /home/ftp -R chown www:www /home/www-R # 如果虚拟用户的宿主用户为www，需要这样设置。 建立虚拟用户个人Vsftp的配置文件 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 创建虚拟用户用户的权限配置文件 mkdir /etc/vsftpd/vconf # 进入该目录 cd /etc/vsftpd/vconf # 创建public与admin两个用户的配置文件 touch public admin # 编辑用户admin配置文件，其他的跟这个配置文件类似 vi admin local_root=/home/ftp/ write_enable=YES anon_world_readable_only=NO anon_upload_enable=YES anon_mkdir_write_enable=YES anon_other_write_enable=YES allow_writeable_chroot=YES 最后重启vsftpd服务器 text 1 systemctl restart vsftpd.service 备注 guest_username=vsftpd #指定虚拟用户的宿主用户（就是我们前面新建的用户） guest_username=www #如果ftp目录是指向网站根目录，用来上传网站程序，可以指定虚拟用户的宿主用户为nginx运行账户www，可以避免很多权限设置问题。 vsftpd配置文件详解 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 allow_anon_ssl NO # 只有ss1_enable激活了才可以启用此项。如果设置为YES，匿名用户将容许使用安全的SSL连接服务器。 anon_mkdir_write_enable NO # 如果设为YES，匿名用户将容许在指定的环境下创建新目录。如果此项要生效，那么配置write_enable必须被激活，并且匿名用户必须在其父目录有写权限。 anon_other_write_enable NO # 如果设置为YES，匿名用户将被授予较大的写权限，例如删除和改名。一般不建议这么做，除非想完全授权。也可以和cmds_allowed配合来实现控制，这样可以达到文件续传功能。 anon_upload_enable NO # 如果设为YES，匿名用户就容许在指定的环境下上传文件。如果此项要生效，那么配置write_enable必须激活。并且匿名用户必须在相关目录有写权限。 anon_world_readable_only YES # 启用的时候，匿名用户只容许下载完全可读的文件，这也就容许了ftp用户拥有对文件的所有权，尤其是在上传的情况下。 anonymous_enable YES # 控制是否容许匿名用户登录。如果容许，那么“ftp”和“anonymous”都将被视为“anonymous\u0026#34;而容许登录。 ascii_download_enable NO # 启用时，用户下载时将以ASCII模式传送文件。 ascii_upload_enable NO # 启用时，用户上传时将以ASCII模式传送文件。 async_abor_enable NO # 启用时，一个特殊的FTP命令\u0026#34;async ABOR”将容许使用。只有不正常的FTP客户端要使用这一点。而且，这个功能又难于操作，所以，默认是把它关闭了。但是，有些客户端在取消一个传送的时候会被挂死(注：估计是客户端无响应了)，那你只有启用这个功能才能避免这种情况。 background NO # 启用时，并且VSFTPD是“listen”模式启动的(注：就是standalone模式)，VSFTPD将把监听进程置于后台。但访问VSFTPD时，控制台将立即被返回到SHELL。 check_shell YES # 注意：这个选项只对非PAM结构的VSFTPD才有效。如果关闭，VSFTPD将不检查/etc/shells以判定本地登录的用户是否有一个可用的SHELL。 chmod_enable YES # 启用时，将容许使用SITE CHMOD命令。注意，这只能用于本地用户。匿名用户绝不能使用SITE CHMOD。 chown_uploads NO # 如果启用，所以匿名用户上传的文件的所有者将变成在chown_username里指定的用户。这对管理FTP很有用，也许也对安全有益。 chroot_list_enable NO # 如果激活，你要提供一个用户列表，表内的用户将在登录后被放在其home目录，锁定在虚根下(注：进入FTP后，PWD一下，可以看到当前目录是\u0026#34;/\u0026#34;, 这就是虚根。是FTP的根目录，并非FTP服务器系统的根目录)。如果chroot_local_user设为YES后，其含义会发生一点变化。在这种情况下，这个列表内的用户将不被锁定在虚根下。默认情况下，这个列表文件是/etc/vsftpd.chroot_list, 但你也可以通过修改chroot_list_file来改变默认值。 chroot_local_user NO # 如果设为YES，本地用户登录后将被(默认地)锁定在虚根下，并被放在他的home目录下。 警告：这个配置项有安全的意味，特别是如果用户有上传权限或者可使用SHELL的话。在你确定的前提下，再启用它。 注意，这种安全暗示并非只存在于VSFTPD，其实是广泛用于所有的希望把用户锁定在虚根下的FTP软件。 connect_from_port_20 NO # 这用来控制服务器是否使用20端口号来做数据传输。为安全起见，有些客户坚持启用。相反，关闭这一项可以让VSFTPD更加大众化。 deny_email_enable NO # 如果激活，你要提供一个关于匿名用户的密码E-MAIL表(注：我们都知道，匿名用户是用邮件地址做密码的)以阻止以这些密码登录的匿名用户。默认情况下，这个列表文件是/etc/vsftpd.banner_emails，但你也可以通过设置banned_email_file来改变默认值。 dirlist_enable YES # 如果设置为NO，所有的列表命令(注：如ls)都将被返回“permission denied”提示。 dirmessage_enable NO # 如果启用，FTP服务器的用户在首次进入一个新目录的时候将显示一段信息。默认情况下，会在这个目录中查找.message文件，但你也可以通过更改message_file来改变默认值。 download_enable YES # 如果设为NO，下载请求将返回“permission denied”。 dual_log_enable NO # 如果启用，两个LOG文件会各自产生，默认的是/var/log/xferlog和/var/log/vsftpd.log。前一个是wu-ftpd格式的LOG，能被通用工具分析。后一个是VSFTPD的专用LOG格式。 force_dot_files NO # 如果激活，即使客户端没有使用“a”标记，(FTP里)以.开始的文件和目录都会显示在目录资源列表里。但是把\u0026#34;.\u0026#34;和\u0026#34;..\u0026#34;不会显示。(注：即LINUX下的当前目录和上级目录不会以‘.’或‘..’方式显示)。 force_local_data_ssl YES # 只有在ssl_enable激活后才能启用。如果启用，所有的非匿名用户将被强迫使用安全的SSL登录以在数据线路上收发数据。 force_local_logins_ssl YES # 只有在ssl_enable激活后才能启用。如果启用，所有的非匿名用户将被强迫使用安全的SSL登录以发送密码。 guest_enable NO # 如果启用，所有的非匿名用户登录时将被视为”游客“，其名字将被映射为guest_username里所指定的名字。 hide_ids NO # 如果启用，目录资源列表里所有用户和组的信息将显示为\u0026#34;ftp\u0026#34;. listen NO # 如果启用，VSFTPD将以独立模式(standalone)运行，也就是说可以不依赖于inetd或者类似的东东启动。直接运行VSFTPD的可执行文件一次，然后VSFTPD就自己去监听和处理连接请求了。 listen_ipv6 NO # 类似于listen参数的功能，但有一点不同，启用后VSFTPD会去监听IPV6套接字而不是IPV4的。这个设置和listen的设置互相排斥。 local_enable NO # 用来控制是否容许本地用户登录。如果启用，/etc/passwd里面的正常用户的账号将被用来登录。 log_ftp_protocol NO # 启用后，如果xferlog_std_format没有被激活，所有的FTP请求和反馈信息将被纪录。这常用于调试(debugging)。 ls_recurse_enable NO # 如果启用，\u0026#34;ls -R\u0026#34;将被容许使用。这是为了避免一点点安全风险。因为在一个大的站点内，在目录顶层使用这个命令将消耗大量资源。 no_anon_password NO # 如果启用，VSFTPD将不会向匿名用户询问密码。匿名用户将直接登录。 no_log_lock NO # 启用时，VSFTPD在写入LOG文件时将不会把文件锁住。这一项一般不启用。它对一些工作区操作系统问题，如Solaris / Veritas文件系统共存时有用。因为那在试图锁定LOG文件时，有时候看上去象被挂死(无响应)了。(注：这我也不是很理解。所以翻译未必近乎原意。原文如下：It exists to workaround operating system bugs such as the Solaris / Veritas filesystem combination which has been observed to sometimes exhibit hangs trying to lock log files.) one_process_model NO # 如果你的LINUX核心是2.4的，那么也许能使用一种不同的安全模式，即一个连接只用一个进程。只是一个小花招，但能提高FTP的性能。请确定需要后再启用它，而且也请确定你的站点是否会有大量的人同时访问。 passwd_chroot_enable (注：这段自己看，无语...) if enabled, along with .BR chroot_local_user , then a chroot() jail location may be specified on a per-user basis. Each user\u0026#39;s jail is derived from their home directory string in /etc/passwd. The occurrence of /./ in the home directory string denotes that the jail is at that particular location in the path. 默认值：NO pasv_enable YES # 如果你不想使用被动方式获得数据连接，请设为NO。 pasv_promiscuous NO # 如果你想关闭被动模式安全检查(这个安全检查能确保数据连接源于同一个IP地址)的话，设为YES。确定后再启用它(注：原话是：只有你清楚你在做什么时才启用它!)合理的用法是：在一些安全隧道配置环境下，或者更好地支持FXP时(才启用它)。 port_enable YES # 如果你想关闭以端口方式获得数据连接时，请关闭它。 port_promiscuous NO # 如果你想关闭端口安全检查(这个检查可以确保对外的(outgoing)数据线路只通向客户端)时，请关闭它。确认后再做! run_as_launching_user NO # 如果你想让一个用户能启动VSFTPD的时候，可以设为YES。当ROOT用户不能去启动VSFTPD的时候会很有用(注：应该不是说ROOT用户没有权限启动VSFTPD，而是因为别的，例如安全限制，而不能以ROOT身份直接启动VSFTPD)。强烈警告!!别启用这一项，除非你完全清楚你在做什么(:无语....)!!!随意地启动这一项会导致非常严重的安全问题，特别是VSFTPD没有或者不能使用虚根技术来限制文件访问的时候(甚至VSFTPD是被ROOT启动的)。有一个愚蠢的替代方案是启用deny_file，将其设置为{/*,*..*}等，但其可靠性却不能和虚根相比，也靠不住。 如果启用这一项，其他配置项的限制也会生效。例如，非匿名登录请求，上传文件的所有权的转换，用于连接的20端口和低于1024的监听端口将不会工作。其他一些配置项也可能被影响。 secure_email_list_enable NO # 如果你想只接受以指定E-MAIL地址登录的匿名用户的话，启用它。这一般用来在不必要用虚拟用户的情况下，以较低的安全限制去访问较低安全级别的资源。如果启用它，匿名用户除非用在email_password_file里指定的E-MAIL做为密码，否则不能登录。这个文件的格式是一个密码一行，而且没有额外的空格(注：whitespace,译为空格，不知道是否正确)。 默认的文件名是：/etc/vsftpd.email_passwords. session_support NO # 这将配置是否让VSFTPD去尝试管理登录会话。如果VSFTPD管理会话，它会尝试并更新utmp和wtmp。它也会打开一个pam会话(pam_session)，直到LOGOUT才会关闭它，如果使用PAM进行认证的话。如果你不需要会话纪录，或者想VSFTPD运行更少的进程，或者让它更大众化，你可以关闭它。注：utmp和wtmp只在有PAM的环境下才支持。 setproctitle_enable NO # 如果启用，VSFTPD将在系统进程列表中显示会话状态信息。换句话说，进程名字将变成VSFTPD会话当前正在执行的动作(等待，下载等等)。为了安全目的，你可以关闭这一项。 ssl_enable NO # 如果启用，vsftpd将启用openSSL，通过SSL支持安全连接。这个设置用来控制连接(包括登录)和数据线路。同时，你的客户端也要支持SSL才行。注意：小心启用此项.VSFTPD不保证OpenSSL库的安全性。启用此项，你必须确信你安装的OpenSSL库是安全的。 ssl_sslv2 NO # 要激活ssl_enable才能启用它。如果启用，将容许SSL V2协议的连接。TLS V1连接将是首选。 ssl_sslv3 NO # 要激活ssl_enable才能启用它。如果启用，将容许SSL V3协议的连接。TLS V1连接将是首选。 ssl_tlsv1 YES # 要激活ssl_enable才能启用它。如果启用，将容许TLS V1协议的连接。TLS V1连接将是首选。 syslog_enable NO # 如果启用，系统log将取代vsftpd的log输出到/var/log/vsftpd.log.FTPD的了log工具将不工作。 tcp_wrappers NO # 如果启用，vsftpd将被tcp_wrappers所支持。进入的(incoming)连接将被tcp_wrappers访问控制所反馈。如果tcp_wrappers设置了VSFTPD_LOAD_CONF环境变量，那么vsftpd将尝试调用这个变量所指定的配置。 ext_userdb_names NO # 默认情况下，在文件列表中，数字ID将被显示在用户和组的区域。你可以编辑这个参数以使其使用数字ID变成文字。为了保证FTP性能，默认情况下，此项被关闭。 tilde_user_enable NO # 如果启用，vsftpd将试图解析类似于~chris/pics的路径名(一个\u0026#34;~\u0026#34;(tilde)后面跟着个用户名)。注意，vsftpd有时会一直解析路径名\u0026#34;~\u0026#34;和\u0026#34;~/\u0026#34;(在这里，～被解析成内部登录目录)。～用户路径(～user paths)只有在当前虚根下找到/etc/passwd文件时才被解析。 use_localtime NO # 如果启用，vsftpd在显示目录资源列表的时候，在显示你的本地时间。而默认的是显示GMT(格林尼治时间)。通过MDTM FTP命令来显示时间的话也会被这个设置所影响。 use_sendfile YES # 一个内部设定，用来测试在你的平台上使用sendfile()系统呼叫的相关好处(benefit). userlist_deny YES # 这个设置在userlist_enable被激活后能被验证。如果你设置为NO，那么只有在userlist_file里明确列出的用户才能登录。如果是被拒绝登录，那么在被询问密码前，用户就将被系统拒绝。 userlist_enable NO # 如果启用，vsftpd将在userlist_file里读取用户列表。如果用户试图以文件里的用户名登录，那么在被询问用户密码前，他们就将被系统拒绝。这将防止明文密码被传送。参见userlist_deny。 virtual_use_local_privs NO # 如果启用，虚拟用户将拥有和本地用户一样的权限。默认情况下，虚拟用户就拥有和匿名用户一样的权限，而后者往往有更多的限制(特别是写权限)。 write_enable NO # 这决定是否容许一些FTP命令去更改文件系统。这些命令是STOR, DELE, RNFR, RNTO, MKD, RMD, APPE 和 SITE。 xferlog_enable NO # 如果启用，一个log文件将详细纪录上传和下载的信息。默认情况下，这个文件是/var/log/vsftpd.log，但你也可以通过更改vsftpd_log_file来指定其默认位置。 xferlog_std_format NO # 如果启用，log文件将以标准的xferlog格式写入(wu-ftpd使用的格式)，以便于你用现有的统计分析工具进行分析。但默认的格式具有更好的可读性。默认情况下，log文件是在/var/log/xferlog。但是，你可以通过修改xferlog_file来指定新路径。 数字选项 # 以下是数字配置项。这些项必须设置为非负的整数。为了方便umask设置，容许输入八进制数，那样的话，数字必须以0开始。 accept_timeout 60 # 超时，以秒为单位，设定远程用户以被动方式建立连接时最大尝试建立连接的时间。 anon_max_rate 0　(无限制) # 对于匿名用户，设定容许的最大传送速率，单位：字节/秒。 anon_umask 077 # 为匿名用户创建的文件设定权限。注意：如果你想输入8进制的值，那么其中的0不同于10进制的0。 connect_timeout 60 # 超时。单位：秒。是设定远程用户必须回应PORT类型数据连接的最大时间。 data_connection_timeout 300 # 超时，单位：秒。设定数据传输延迟的最大时间。时间一到，远程用户将被断开连接。 file_open_mode 0666 # 对于上传的文件设定权限。如果你想被上传的文件可被执行，umask要改成0777。 ftp_data_port 20 # 设定PORT模式下的连接端口(只要connect_from_port_20被激活)。 idle_session_timeout 300 # 超时。单位：秒。设置远程客户端在两次输入FTP命令间的最大时间。时间一到，远程客户将被断开连接。 listen_port 21 # 如果vsftpd处于独立运行模式，这个端口设置将监听的FTP连接请求。 local_max_rate　0(无限制) #　为本地认证用户设定最大传输速度，单位：字节/秒。 local_umask 077 # 设置本地用户创建的文件的权限。注意：如果你想输入8进制的值，那么其中的0不同于10进制的0。 max_clients 0(无限制) # 如果vsftpd运行在独立运行模式，这里设置了容许连接的最大客户端数。再后来的用户端将得到一个错误信息。 max_per_ip 0(无限制) # 如果vsftpd运行在独立运行模式，这里设置了容许一个IP地址的最大接入客户端。如果超过了最大限制，将得到一个错误信息。 pasv_max_port 0(使用任何端口) # 指定为被动模式数据连接分配的最大端口。可用来指定一个较小的范围以配合防火墙。 pasv_min_port 0(使用任何端口) # 指定为被动模式数据连接分配的最小端口。可用来指定一个较小的范围以配合防火墙。 trans_chunk_size 0(让vsftpd自行选择) # 你一般不需要改这个设置。但也可以尝试改为如8192去减小带宽限制的影响。 以下是STRING 配置项 anon_root 无 # 设置一个目录，在匿名用户登录后，vsftpd会尝试进到这个目录下。如果失败则略过。 banned_email_file /etc/vsftpd.banned_emails # deny_email_enable启动后，匿名用户如果使用这个文件里指定的E-MAIL密码登录将被拒绝。 banner_file 无 # 设置一个文本，在用户登录后显示文本内容。如果你设置了ftpd_banner，ftpd_banner将无效。 chown_username ROOT # 改变匿名用户上传的文件的所有者。需设定chown_uploads。 chroot_list_file /etc/vsftpd.chroot_list # 这个项提供了一个本地用户列表，表内的用户登录后将被放在虚根下，并锁定在home目录。这需要chroot_list_enable项被启用。如果chroot_local_user项被启用，这个列表就变成一个不将列表里的用户锁定在虚根下的用户列表了。 cmds_allowed 无 # 以逗号分隔的方式指定可用的FTP命令(post　login. USER, PASS and QUIT 是始终可用的命令)。 其他命令将被屏蔽。这是一个强有力的locking down一个FTP服务器的手段。例如：cmds_allowed=PASV,RETR,QUIT(只允许检索文件) cmds_allowed=ABOR,APPE,CWD,CDUP,FEAT,LIST,MKD,MDTM,PASS,PASV,PWD,QUIT,RETR,REST, STOR,STRU,TYPE,USER(支持上传和下载的断点续传等命令)。 详细参考：http://www.nsftools.com/tips/RawFTP.htm deny_file 无 # 这可以设置一个文件名或者目录名式样以阻止在任何情况下访问它们。并不是隐藏它们，而是拒绝任何试图对它们进行的操作(下载，改变目录层，和其他有影响的操作)。这个设置很简单，而且不会用于严格的访问控制-文件系统权限将优先生效。然而，这个设置对确定的虚拟用户设置很有用。 # 特别是如果一个文件能多个用户名访问的话(可能是通过软连接或者硬连接)，那就要拒绝所有的访问名。 # 建议你为使用文件系统权限设置一些重要的安全策略以获取更高的安全性。如deny_file={*.mp3,*.mov,.private} dsa_cert_file 无(有一个RSA证书就够了) # 这个设置为SSL加密连接指定了DSA证书的位置。 email_password_file /etc/vsftpd.email_passwords # 在设置了secure_email_list_enable后，这个设置可以用来提供一个备用文件。 ftp_username ftp # 这是用来控制匿名FTP的用户名。这个用户的home目录是匿名FTP区域的根。 ftpd_banner 无(默认的界面会被显示) # 当一个连接首次接入时将现实一个欢迎界面。 guest_username ftp # 参见相关设置guest_enable。这个设置设定了游客进入后，其将会被映射的名字。 hide_file 无 # 设置了一个文件名或者目录名列表，这个列表内的资源会被隐藏，不管是否有隐藏属性。但如果用户知道了它的存在，将能够对它进行完全的访问。hide_file里的资源和符合hide_file指定的规则表达式的资源将被隐藏。vsftpd的规则表达式很简单，例如hide_file={*.mp3,.hidden,hide*,h?} listen_address 无 # 如果vsftpd运行在独立模式下，本地接口的默认监听地址将被这个设置代替。需要提供一个数字化的地址。 listen_address6 无 # 如果vsftpd运行在独立模式下，要为IPV6指定一个监听地址(如果listen_ipv6被启用的话)。需要提供一个IPV6格式的地址。 local_root无 # 设置一个本地(非匿名)用户登录后，vsftpd试图让他进入到的一个目录。如果失败，则略过。 message_file .message # 当进入一个新目录的时候，会查找这个文件并显示文件里的内容给远程用户。dirmessage_enable需启用。 nopriv_user nobody # 这是vsftpd做为完全无特权的用户的名字。这是一个专门的用户，比nobody更甚。用户nobody往往用来在一些机器上做一些重要的事情。 pam_service_name ftp # 设定vsftpd将要用到的PAM服务的名字。 pasv_address 无(地址将取自进来(incoming)的连接的套接字) # 当使用PASV命令时，vsftpd会用这个地址进行反馈。需要提供一个数字化的IP地址。 rsa_cert_file /usr/share/ssl/certs/vsftpd.pem # 这个设置指定了SSL加密连接需要的RSA证书的位置。 secure_chroot_dir /usr/share/empty # 这个设置指定了一个空目录，这个目录不容许ftp　user写入。在vsftpd不希望文件系统被访问时，目录为安全的虚根所使用。 ssl_ciphers DES-CBC3-SHA # 这个设置将选择vsftpd为加密的SSL连接所用的SSL密码。详细信息参见ciphers。 user_config_dir 无 # 这个强大的设置容许覆盖一些在手册页中指定的配置项(基于单个用户的)。用法很简单，最好结合范例。如果你把user_config_dir改为/etc/vsftpd_user_conf，那么以chris登录，vsftpd将调用配置文件/etc/vsftpd_user_conf/chris。 user_sub_token 无 # 这个设置将依据一个模板为每个虚拟用户创建home目录。例如，如果真实用户的home目录通过guest_username为/home/virtual/$USER 指定，并且user_sub_token设置为 $USER ，那么虚拟用户fred登录后将锁定在/home/virtual/fred下。 userlist_file /etc/vsftpd.user_list # 当userlist_enable被激活，系统将去这里调用文件。 vsftpd_log_file /var/log/vsftpd.log # 只有xferlog_enable被设置，而xferlog_std_format没有被设置时，此项才生效。这是被生成的vsftpd格式的log文件的名字。 # dual_log_enable和这个设置不能同时启用。如果你启用了syslog_enable，那么这个文件不会生成，而只产生一个系统log. xferlog_file /var/log/xferlog # 这个设置是设定生成wu-ftpd格式的log的文件名。只有启用了xferlog_enable和xferlog_std_format后才能生效。但不能和dual_log_enable同时启用。 数字代码 数字代码 意义 110 重新启动标记应答。 120 服务在多久时间内ready。 125 数据链路埠开启，准备传送。 150 文件状态正常，开启数据连接端口。 200 命令执行成功。 202 命令执行失败。 211 系统状态或是系统求助响应。 212 目录的状态。 213 文件的状态。 214 求助的讯息。 215 名称系统类型。 220 新的联机服务ready。 221 服务的控制连接埠关闭，可以注销。 225 数据连结开启，但无传输动作。 226 关闭数据连接端口，请求的文件操作成功。 227 进入passive 230 使用者登入。 250 请求的文件操作完成。 257 显示目前的路径名称。 331 用户名称正确，需要密码。 332 登入时需要账号信息。 350 请求的操作需要进一部的命令。 421 无法提供服务，关闭控制连结。 425 无法开启数据链路。 426 关闭联机，终止传输。 450 请求的操作未执行。 451 命令终止：有本地的错误。 452 未执行命令：磁盘空间不足。 500 格式错误，无法识别命令。 501 参数语法错误。 502 命令执行失败。 503 命令顺序错误。 504 命令所接的参数不正确。 530 未登入。 532 储存文件需要账户登入。 550 未执行请求的操作。 551 请求的命令终止，类型未知。 552 请求的文件终止，储存位溢出。 553 未执行请求的的命令，名称不正确。 ","permalink":"https://www.161616.top/vsftp-network-filesystem/","summary":"配置防火墙，开启FTP服务器需要的端口 CentOS 7.0默认使用的是firewall作为防火墙，这里改为iptables防火墙。\n关闭firewall： bash 1 2 systemctl stop firewalld.service #停止firewall systemctl disable firewalld.service #禁止firewall开机启动 安装iptables防火墙 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 yum install iptables-services # 安装 vi /etc/sysconfig/iptables # 编辑防火墙配置文件 # Firewall configuration written by system-config-firewall # Manual customization of this file is not recommended. *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 -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 21 -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 10060:10090 -j ACCEPT -A INPUT -j REJECT --reject-with icmp-host-prohibited -A FORWARD -j REJECT --reject-with icmp-host-prohibited COMMIT :wq!","title":"网络共享 - centos7安装vsftpd"},{"content":"Memcache应用场景 基本场景 比如有 N 台 cache 服务器（后面简称 cache），那么如何将一个对象 object 映射到 N 个 cache 上呢，你很可能会采用类似下面的通用方法计算 object 的 hash 值，然后均匀的映射到到N个cache; hash(object)%N\n如下图：\n这时，一切都运行正常，再考虑如下的两种情况：\n一个 cache服务器m down掉了（在实际应用中必须要考虑这种情况），这样所有映射到cache m的对象都会失效，怎么办，需要把cache m从cache 中移除，这时候 cache 是 $N-1$ 台，映射公式变成了 hash(object)%(N-1) 。此时数据 $3%3-1=3%2=1$ 此时，3应该在S3上，但是由于S3down机导致到S1去取，这时会未命中。如下图\n由于访问加重，需要添加 cache ，这时候 cache 是 $N+1$ 台，映射公式变成了 hash(object)%(N+1) 。1和2意味着突然之间几乎所有的 cache 都失效了。对于服务器而言，这是一场灾难，洪水般的访问都会直接冲向后台服务器。$\\frac{N-1} { N\\times (N-1)}$\n即：\n有N台服务器，变为 $N-1$ 台，即每 $N \\times (N-1)$个数中，求余相同的只有 N-1 个。命中率为：$\\frac{1}{3}$\n再来考虑第三个问题，由于硬件能力越来越强，你可能想让后面添加的节点多做点活，显然上面的 hash 算法也做不到。\n有什么方法可以改变这个状况呢，这就是 consistent hashing\u0026hellip;\n但现在一致性hash算法在分布式系统中也得到了广泛应用，研究过memcached缓存数据库的人都知道，memcached服务器端本身不提供分布式cache的一致性，而是由客户端来提供，具体在计算一致性hash时采用如下步骤：\n首先求出memcached服务器（节点）的哈希值，并将其配置到 0～232 的圆（continuum）上。\n然后采用同样的方法求出存储数据的键的哈希值，并映射到相同的圆上。\n然后从数据映射到的位置开始顺时针查找，将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器，就会保存到第一台memcached服务器上。\n从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化而影响缓存的命中率，但Consistent Hashing中，只有在圆（continuum）上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响，如下图所示：\n接下来使用如下算法定位数据访问到相应服务器：将数据key使用相同的函数Hash计算出哈希值，并确定此数据在环上的位置，从此位置沿环顺时针“行走”，第一台遇到的服务器就是其应该定位到的服务器。\nconsistent hash原理 基本概念 一致性哈希算法（Consistent Hashing）最早在论文《Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web》中被提出。简单来说，一致性哈希将整个哈希值空间组织成一个虚拟的圆环，如假设某哈希函数H的值空间为 0-232-1（即哈希值是一个32位无符号整形），整个哈希空间环如下：\n整个空间按顺时针方向组织。0和232-1在零点中方向重合。\n下一步将各个服务器使用Hash进行一个哈希，具体可以选择服务器的ip或主机名作为关键字进行哈希，这样每台机器就能确定其在哈希环上的位置，这里假设将上文中四台服务器使用ip地址哈希后在环空间的位置如下：\n接下来使用如下算法定位数据访问到相应服务器：将数据key使用相同的函数Hash计算出哈希值，并确定此数据在环上的位置，从此位置沿环顺时针“行走”，第一台遇到的服务器就是其应该定位到的服务器。\n例如我们有Object A、Object B、Object C、Object D四个数据对象，经过哈希计算后，在环空间上的位置如下：\n根据一致性哈希算法，数据A会被定为到Node A上，B被定为到Node B上，C被定为到Node C上，D被定为到Node D上。\n下面分析一致性哈希算法的容错性和可扩展性。现假设Node C不幸宕机，可以看到此时对象A、B、D不会受到影响，只有C对象被重定位到Node D。一般的，在一致性哈希算法中，如果一台服务器不可用，则受影响的数据仅仅是此服务器到其环空间中前一台服务器（即沿着逆时针方向行走遇到的第一台服务器）之间数据，其它不会受到影响。如下图所示：\n下面考虑另外一种情况，如果在系统中增加一台服务器Node X，如下图所示：\n此时对象Object A、B、D不受影响，只有对象C需要重定位到新的Node X 。一般的，在一致性哈希算法中，如果增加一台服务器，则受影响的数据仅仅是新服务器到其环空间中前一台服务器（即沿着逆时针方向行走遇到的第一台服务器）之间数据，其它数据也不会受到影响。\n综上所述，一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据，具有较好的容错性和可扩展性。\n另外，一致性哈希算法在服务节点太少时，容易因为节点分部不均匀而造成数据倾斜问题。例如系统中只有两台服务器，其环分布如下：\n此时必然造成大量数据集中到Node A上，而只有极少量会定位到Node B上。为了解决这种数据倾斜问题，一致性哈希算法引入了虚拟节点机制，即对每一个服务节点计算多个哈希，每个计算结果位置都放置一个此服务节点，称为虚拟节点。具体做法可以在服务器ip或主机名的后面增加编号来实现。例如上面的情况，可以为每台服务器计算三个虚拟节点，于是可以分别计算 Node A#1 Node A#2 Node A#3 Node B#1 Node B#2 Node B#3 的哈希值，于是形成六个虚拟节点：\n同时数据定位算法不变，只是多了一步虚拟节点到实际节点的映射，例如定位到Node A#1 Node A#2 Node A#3 三个虚拟节点的数据均定位到Node A上。这样就解决了服务节点少时数据倾斜的问题。在实际应用中，通常将虚拟节点数设置为**==32==**甚至更大，因此即使很少的服务节点也能做到相对均匀的数据分布。\n参考: http://www.cnblogs.com/haippy/archive/2011/12/10/2282943.html\n一致性hash（consistent hash）在PHP中使用 php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 \u0026lt;?php class ConsistentHash { public $nodes = array(); public function __construct(){ } public function generateHash($str){ return sprintf(\u0026#39;%u\u0026#39;,crc32($str)); } public function findNode(){ } public function lookup($key){ $tmp = $this-\u0026gt;generateHash($key); $node = current($this-\u0026gt;nodes); foreach($this-\u0026gt;nodes as $key=\u0026gt;$val){ if( $tmp \u0026lt;= $key ){ $node = $val; break; } } return $node; } public function getNode(){ var_dump($this-\u0026gt;nodes); } public function addNode($node){ $this-\u0026gt;nodes[$this-\u0026gt;generateHash($node)] = $node; ksort($this-\u0026gt;nodes); }\t} $hash = new ConsistentHash; $hash-\u0026gt;addNode(\u0026#39;192.168.2.80:11211\u0026#39;); $hash-\u0026gt;addNode(\u0026#39;192.168.2.80:11212\u0026#39;); $hash-\u0026gt;addNode(\u0026#39;192.168.2.80:11213\u0026#39;); echo \u0026#39;\u0026lt;hr\u0026gt;\u0026lt;br\u0026gt;\u0026#39;; $hash-\u0026gt;getNode(); echo \u0026#39;\u0026lt;br\u0026gt;\u0026#39;; echo $hash-\u0026gt;generateHash(\u0026#39;zhangsan\u0026#39;),\u0026#39;\u0026lt;br\u0026gt;\u0026#39;; echo $hash-\u0026gt;lookup(\u0026#39;zhangsan\u0026#39;),\u0026#39;\u0026lt;br\u0026gt;\u0026#39;; ?\u0026gt; 执行结果\n这时可以看出，数据倾斜问题。\n创建虚拟节点，解决数据倾斜问题\nphp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 \u0026lt;?php class ConsistentHash{ public $nodes = array(); protected $num = 0; protected $priNode = array(); public function __construct($nodeNum){ $this-\u0026gt;num = $nodeNum; } public function generateHash($str){ return sprintf(\u0026#39;%u\u0026#39;,crc32($str)); } public function selectNode($key){ $tmp = $this-\u0026gt;generateHash($key); $node = current($this-\u0026gt;nodes); # 选择最小的节点作为默认值 foreach($this-\u0026gt;priNode as $key=\u0026gt;$val){ if( $tmp \u0026lt;= $key ){ $node = $val; break; } } return $node; } public function getNode(){ var_dump($this-\u0026gt;nodes); var_dump($this-\u0026gt;priNode); } public function addNode($node){\tfor($n=0;$n\u0026lt;$this-\u0026gt;num;$n++){ $this-\u0026gt;priNode[$this-\u0026gt;generateHash($node.\u0026#39;_\u0026#39;.$n)] = $node; } $this-\u0026gt;nodes[$this-\u0026gt;generateHash($node)] = $node; ksort($this-\u0026gt;priNode); }\t} $hash = new ConsistentHash(32); $hash-\u0026gt;addNode(\u0026#39;192.168.2.80:11211\u0026#39;); $hash-\u0026gt;addNode(\u0026#39;192.168.2.80:11212\u0026#39;); $hash-\u0026gt;addNode(\u0026#39;192.168.2.80:11213\u0026#39;); $hash-\u0026gt;getNode(); echo \u0026#39;\u0026lt;br\u0026gt;\u0026#39;; echo $hash-\u0026gt;generateHash(\u0026#39;zhangsan\u0026#39;),\u0026#39;\u0026lt;br\u0026gt;\u0026#39;; echo $hash-\u0026gt;generateHash(\u0026#39;lisi\u0026#39;),\u0026#39;\u0026lt;br\u0026gt;\u0026#39;; echo $hash-\u0026gt;lookup(\u0026#39;zhangsan\u0026#39;),\u0026#39;\u0026lt;br\u0026gt;\u0026#39;; echo $hash-\u0026gt;lookup(\u0026#39;lisi\u0026#39;),\u0026#39;\u0026lt;br\u0026gt;\u0026#39;; echo $hash-\u0026gt;generateHash(\u0026#39;wangwu\u0026#39;),\u0026#39;\u0026lt;br\u0026gt;\u0026#39;; echo $hash-\u0026gt;lookup(\u0026#39;wangwu\u0026#39;),\u0026#39;\u0026lt;br\u0026gt;\u0026#39;; ?\u0026gt; 此时自动分配的节点为\n可看出6E-8E存在11211上，大于。35.7E-35.9E存在11212上\n一致性hash与取模命中率的对比实验 dring.rar\n实验目的 测试Memcached缓存服务器有N台变为N-台时，取模和consistent hasing算法的命中率\n实验原理 相同的硬件环境、操作系统、数据缓存环境，5个memcached节点，用两种分布式算法建立缓存，缓存命中率稳定后，减少1个节点，观察命中率的变化，知道命中率在次稳定。\n前端软件架构 sh 1 2 3 4 5 6 config.php #←配置memcached节点信息 hash.php #←分布式算法 init.php #←初始化数据 exec.php #←减少节点后请求数据 stat.php\t#←统计平均命中率 index.html #←生成动态图表 取模算法的实验 当5台缓存服务器全部正常的情况下，此时的命中率统计图如下：\n这是查看5台缓存服务器的查询与命中次数如下：\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ for n in {1..5};do printf \u0026#34;stats\\r\\n\u0026#34;|nc 127.0.0.1 1121$n|egrep \u0026#39;get_hits|cmd_get\u0026#39;; done STAT cmd_get 0 STAT get_hits 0 STAT cmd_get 0 STAT get_hits 0 STAT cmd_get 0 STAT get_hits 0 STAT cmd_get 0 STAT get_hits 0 STAT cmd_get 0 STAT get_hits 0 $ for n in {1..5};do printf \u0026#34;stats\\r\\n\u0026#34;|nc 127.0.0.1 1121$n|grep item; done STAT curr_items 2044 STAT total_items 2044 STAT curr_items 1983 STAT total_items 1983 STAT curr_items 1993 STAT total_items 1993 STAT curr_items 2001 STAT total_items 2001 STAT curr_items 1979 STAT total_items 1979 这时断掉一台缓存服务器，此时的命中率从100%瞬间降至8%。\n运行一段时间后，可见命中率保持20%左右，在预热完毕后，逐步上升。\n此时查看5台缓存服务器的查询次数与命中次数，发现已经很均匀了。\nsh 1 2 3 4 5 6 7 8 9 10 11 $ for n in {1..5};do printf \u0026#34;stats\\r\\n\u0026#34;|nc 127.0.0.1 1121$n|egrep \u0026#39;get_hits|cmd_get\u0026#39;; done STAT cmd_get 0 STAT get_hits 0 STAT cmd_get 8481 STAT get_hits 6465 STAT cmd_get 8482 STAT get_hits 6476 STAT cmd_get 8482 STAT get_hits 6504 STAT cmd_get 8483 STAT get_hits 6478 经过较长时间后，可以看到命中率已经很平稳了\n一致性hash算法命中率实验 模拟出正常情况下，5台缓存服务器的命中率\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $ for n in {1..5};do printf \u0026#34;stats\\r\\n\u0026#34;|nc 127.0.0.1 1121$n|egrep \u0026#39;get_hits|cmd_get\u0026#39;; done STAT cmd_get 0 STAT get_hits 0 STAT cmd_get 0 STAT get_hits 0 STAT cmd_get 0 STAT get_hits 0 STAT cmd_get 0 STAT get_hits 0 STAT cmd_get 0 STAT get_hits 0 $ for n in {1..5};do printf \u0026#34;stats\\r\\n\u0026#34;|nc 127.0.0.1 1121$n|grep item; done STAT curr_items 999 STAT total_items 999 STAT curr_items 1005 STAT total_items 1005 STAT curr_items 6005 STAT total_items 6005 STAT curr_items 998 STAT total_items 998 STAT curr_items 993 STAT total_items 993 此时断开1台服务器，可以见到命中率下降到73%就稳定了。\n观察一段时间后命中率逐步上升到95%\n在实战中会存在的问题 缓存雪崩的现象\n一般是由于某个节点生效，导致其他节点的缓存命中率下降，缓存中缺失的数据去数据可查询。短时间内造成数据库服务器崩溃。或，由于缓存周期性的输小，如：6小时失效一次，那么每6小时，将有一个请求““峰值”，严重情况下会导致数据库宕机。\n建议解决方案：\n将缓存的生命周期设置为随机的时间短（如4-10）小时，这样缓存不同时失效，把工作分担到各个时间点上。 可在夜间缓慢建立一部分缓存 可建立多个缓存交叉使用，做好镜像，将多个缓存失效时间错开。 ","permalink":"https://www.161616.top/consistent-hash/","summary":"Memcache应用场景 基本场景 比如有 N 台 cache 服务器（后面简称 cache），那么如何将一个对象 object 映射到 N 个 cache 上呢，你很可能会采用类似下面的通用方法计算 object 的 hash 值，然后均匀的映射到到N个cache; hash(object)%N\n如下图：\n这时，一切都运行正常，再考虑如下的两种情况：\n一个 cache服务器m down掉了（在实际应用中必须要考虑这种情况），这样所有映射到cache m的对象都会失效，怎么办，需要把cache m从cache 中移除，这时候 cache 是 $N-1$ 台，映射公式变成了 hash(object)%(N-1) 。此时数据 $3%3-1=3%2=1$ 此时，3应该在S3上，但是由于S3down机导致到S1去取，这时会未命中。如下图\n由于访问加重，需要添加 cache ，这时候 cache 是 $N+1$ 台，映射公式变成了 hash(object)%(N+1) 。1和2意味着突然之间几乎所有的 cache 都失效了。对于服务器而言，这是一场灾难，洪水般的访问都会直接冲向后台服务器。$\\frac{N-1} { N\\times (N-1)}$\n即：\n有N台服务器，变为 $N-1$ 台，即每 $N \\times (N-1)$个数中，求余相同的只有 N-1 个。命中率为：$\\frac{1}{3}$\n再来考虑第三个问题，由于硬件能力越来越强，你可能想让后面添加的节点多做点活，显然上面的 hash 算法也做不到。\n有什么方法可以改变这个状况呢，这就是 consistent hashing\u0026hellip;\n但现在一致性hash算法在分布式系统中也得到了广泛应用，研究过memcached缓存数据库的人都知道，memcached服务器端本身不提供分布式cache的一致性，而是由客户端来提供，具体在计算一致性hash时采用如下步骤：\n首先求出memcached服务器（节点）的哈希值，并将其配置到 0～232 的圆（continuum）上。\n然后采用同样的方法求出存储数据的键的哈希值，并映射到相同的圆上。\n然后从数据映射到的位置开始顺时针查找，将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器，就会保存到第一台memcached服务器上。","title":"一致性hash在memcache中的应用"},{"content":"安装samba服务 text 1 yum install samba -y 配置samba服务 text 1 2 cp /etc/samba/smb.cnf{,.`date +%F`} #\u0026lt;== 修改前备份 vim /etc/samba/smb.cnf smb配置文件 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #=================== Global Settings[全局选项] ============================== [global] workgroup = WORKGROUP #\u0026lt;==设定Samba Server所要加入的工作组或域 server string = Samba Server Version %v #\u0026lt;==设定注释，宏%v表示显示Samba的版本号 netbios name = zhi #\u0026lt;==设置Samba Server的NetBIOS名称 map to guest = bad user #\u0026lt;==开启匿名访问 # ----------------- Logging Options [日志选项]----------------------------- #设置日志文件存储位置及名称，宏%m(主机名),表示对每台访问Samba Server的机器都单独记录一个日志文件 log file = /var/log/samba/log.%m max log size = 50 #\u0026lt;==设置Samba Server日志文件的最大容量，单位为KB，0代表不限制 # ---------------- Standalone Server Options[独立运行进程] --------------------- # 共享级别 share被弃用 security = share passdb backend = tdbsam #\u0026lt;==建立安全账户管理数据库 # =================== share settings[共享参数] =================== [web] comment = Public Stuff #\u0026lt;==定义说明信息 path = /data/web #\u0026lt;==共享目录路径 public = yes #\u0026lt;==匿名访问 writable = yes #\u0026lt;==可写 read only =yes #\u0026lt;==read和writable后面的替换前面的属性 printable = no write list = +staff #\u0026lt;==允许写入该共享的用户 @是组 browseable=yes #\u0026lt;==用来指定该共享是否可以浏览 share参数被去除\ntext 1 WARNING: Ignoring invalid value \u0026#39;share\u0026#39; for parameter \u0026#39;security\u0026#39; 配置中问题 window无法打开smb共享 windows中电脑必须在控制面板中开启次功能\n关闭后在局域网内找不到任何电脑，并不能访问smb共享\n权限无法访问解决方法 解决方法：（该方法在/etc/samba/smb.conf中有提到）\ntext 1 2 3 # Set SELinux labels only on files and directories you have created. Use the # chcon command to temporarily change a label: # chcon -t samba_share_t /path/to/directory 所以执行以上命令：即可解决问题。\nbash 1 #chcon -t samba_share_t /path/to/directory ","permalink":"https://www.161616.top/samba-network-filesystem/","summary":"安装samba服务 text 1 yum install samba -y 配置samba服务 text 1 2 cp /etc/samba/smb.cnf{,.`date +%F`} #\u0026lt;== 修改前备份 vim /etc/samba/smb.cnf smb配置文件 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #=================== Global Settings[全局选项] ============================== [global] workgroup = WORKGROUP #\u0026lt;==设定Samba Server所要加入的工作组或域 server string = Samba Server Version %v #\u0026lt;==设定注释，宏%v表示显示Samba的版本号 netbios name = zhi #\u0026lt;==设置Samba Server的NetBIOS名称 map to guest = bad user #\u0026lt;==开启匿名访问 # ----------------- Logging Options [日志选项]----------------------------- #设置日志文件存储位置及名称，宏%m(主机名),表示对每台访问Samba Server的机器都单独记录一个日志文件 log file = /var/log/samba/log.","title":"网络共享 - centos7安装samba"},{"content":" 工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 理解ldap - OpenLDAP客户端命令行使用 sed 语法\nbash 1 sed \u0026#39;/过滤的内容/处理的命令\u0026#39; 文件 参数 注释说明 n 取消sed默认的输出 i 替换文件内容 r 如果有特殊字符不用转义（正则） g 全局替换 d 删除 p print打印 # 为分隔符可以用其他符号替换（最好用$ @ /）替换内容中如果有分隔符，需要将分隔符替换为别的分隔符，如果不换可将内容转义 s 为search g为globla全局替换，不加的话只替换一列\n打印\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ sed -n \u0026#39;2p\u0026#39; 3.txt 1 $ sed -n \u0026#39;1,20p\u0026#39; 3.txt 0 1 2 3 4 5 6 8 9 10 11 12 13 14 15 16 17 18 19 问：已知一个文件内容为 aaa bbb ccc lisi 请打印出不包含lisi的内容\n文件原内容\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 $ cat 1.txt -bash: ech: command not found -bash: ech: command not found -bash: ech: command not found dasda aaa bbb ccc ddd eee fff ggg 替换功能：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ sed -i \u0026#39;s#aaa#cylon#g\u0026#39; 1.txt $ cat 1.txt -bash: ech: command not found -bash: ech: command not found -bash: ech: command not found dasda cylon bbb ccc ddd eee fff ggg 默认不加参数会将文件原内容打印再将符合的内容打印\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ sed \u0026#39;/aaa/p\u0026#39; 1.txt -bash: ech: command not found -bash: ech: command not found -bash: ech: command not found dasda aaa aaa bbb ccc ddd eee fff ggg 在指定文件中指定行插入数据\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ cat test.txt 1 2 3 4 5 # $为行尾 a\\为行后追加 i\\为行前追加 c\\为替换 不加$为行首 $ sed -i \u0026#34;3a zhangsan\u0026#34; test.txt $ cat test.txt 1 2 3 zhangsan 4 -n取消默认的输出\nbash 1 2 $ sed -n \u0026#39;/aaa/p\u0026#39; 1.txt aaa 将符合的内容删除后输出，并不操作文件\nbash 1 2 3 4 5 6 7 8 9 10 11 12 $ sed \u0026#39;/aaa/d\u0026#39; 1.txt -bash: ech: command not found -bash: ech: command not found -bash: ech: command not found dasda bbb ccc ddd eee fff ggg 删除文件中一部分内容\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 删除首行 sed \u0026#39;1d\u0026#39; nginx.conf # 删除1-102行 sed \u0026#39;1,102d\u0026#39; nginx.conf # 正则表达式 # 删除每行中 on sed \u0026#39;/on/d\u0026#39; nginx.conf # 删除偶数行删除偶数行 sed \u0026#39;0~2d\u0026#39; nginx.conf # 删除奇数行 sed \u0026#39;1~2d\u0026#39; nginx.conf wc 参数 说明 c 统计字节数 l 统计行数 m 统计字符数，不能与c一起用 w 统计字数，一个字被定义为由空白、跳格、或换行字符分割的字符串 L 打印最长行的字符数量 \u0026ndash;help 帮助信息 \u0026ndash;version 版本信息 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 $ cat -n a.html 1 000 2 111 3 222 4 333 5 444 6 555 7 666 8 777 9 888 10 999 11 aaa 12 bbb 13 ccc 14 ddd 15 eee 16 $ wc -c a.html 61 a.html $ wc -l a.html 16 a.html $ wc -w a.html 15 a.html $ wc -L a.html 3 a.html sort ★★★★ 将文件进行排序，并将排序结果标准输出。sort命令既可以从特定的文件，也可以从stdin中获取输入\n参数选项 注释说明 -b 忽略每行前面开始出的空格字符； -c 检查文件是否已经按照顺序排序； -d 排序时，处理英文字母、数字及空格字符外，忽略其他的字符； -f 排序时，将小写字母视为大写字母； -n 依照数值的大小排序 -r 以相反的顺序来排序 实例：sort将 文件/文本 的每一行作为一个单位，相互比较，比较原则是从首字符向后，依次按ASCII码值进行比较，最后将他们按升序输出。\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ cat sort.txt aaa:10:1.1 ccc:30:3.3 ddd:40:4.4 bbb:20:2.2 eee:50:5.5 eee:50:5.5 $ sort sort.txt aaa:10:1.1 bbb:20:2.2 ccc:30:3.3 ddd:40:4.4 eee:50:5.5 eee:50:5.5 忽略相同行使用-u选项或者uniq：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ cat sort.txt aaa:10:1.1 ccc:30:3.3 ddd:40:4.4 bbb:20:2.2 eee:50:5.5 eee:50:5.5 $ sort -u sort.txt aaa:10:1.1 bbb:20:2.2 ccc:30:3.3 ddd:40:4.4 eee:50:5.5 # $ uniq sort.txt aaa:10:1.1 ccc:30:3.3 ddd:40:4.4 bbb:20:2.2 eee:50:5.5 sort的-n、-r、-k、-t选项的使用：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 $ cat sort.txt AAA:BB:CC aaa:30:1.6 ccc:50:3.3 ddd:20:4.2 bbb:10:2.5 eee:40:5.4 eee:60:5.1 # 将BB列按照数字从小到大顺序排列 $ sort -nk 2 -t: sort.txt AAA:BB:CC bbb:10:2.5 ddd:20:4.2 aaa:30:1.6 eee:40:5.4 ccc:50:3.3 eee:60:5.1 # 将CC列数字从大到小顺序排列 $ sort -nrk 3 -t: sort.txt eee:40:5.4 eee:60:5.1 ddd:20:4.2 ccc:50:3.3 bbb:10:2.5 aaa:30:1.6 AAA:BB:CC # -n是按照数字大小排序，-r是以相反顺序，-k是指定需要排序的栏位，-t指定栏位分隔符为冒号 uniq 用于报告或忽略文件中的重复行，一般与sort命令结合使用\n参数选项 注释说明 -c 在每行前面显示改行重复的次数 -d 仅打印重复出现的行 -u 仅打印不重复的行 实例：删除重复行\nbash 1 2 3 4 5 6 7 8 9 10 11 12 $ uniq a.txt a b c d e f g h i g k 在文件中找出重复的行：\nbash 1 sort file.txt | uniq -d 查找重复次数\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $ uniq -c a.txt 5 a 1 b 2 c 2 d 1 e 1 f 1 g 3 h 2 i 1 g 1 k $ uniq -d a.txt a c d h i $ uniq -u a.txt b e f g g k cut 用来显示行中的指定部分，删除文件中指定字段。cut经常用来显示文件的内容\n参数选项 注释说明 -b 以字节为单位进行分割。这些字节位置将忽略多字节字符边界，除非也指定了 -n 标志。 -c 以字符为单位进行分割 -f 与-d一起使用，取第几列 -d 指定分隔符 实例\nbash 1 2 3 4 5 $ cat 1.log i am a protester myqq is 1112222 $ cut -d \u0026#34; \u0026#34; -f4,7 1.log protester 1112222 以字节取，我们想去who命令的第三个字节 bash 1 2 3 4 5 6 7 8 $ who root pts/0 2010-02-02 04:09 (192.168.88.1) root pts/1 2010-02-02 08:34 (192.168.88.1) lc pts/2 2010-02-02 08:44 (192.168.88.1) $ who|cut -b 3 o o 取第1、2、3和第23个字节 bash 1 2 3 4 $ who|cut -b 1-3,23 roo2 roo2 lc 2 如果取中文的话，-c 与 -b就有差异了，-c取的是字节，而-b取得是8位2进制来计算输出的是乱码或空 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ cat a.txt 星期一 星期二 星期三 星期四 星期五 星期六 星期日 $ cut -b 3 a.txt � � $ cut a.txt -c 3 一 二 三 四 五 六 日 grep Global search Regular Expression(RE) and Print out the line，全面搜索正则表达式并把行打印出来；是一种强大的文本搜索工具，它能使用正则表达式搜索文本，并把匹配的行打印出来。\n过滤，将想要的和不想要的去除\n参数 说明 ==-E== 同egrep同时过滤多个字符串，使grep可以使用正则表达式 -v 翻转查找，查找除了匹配到结果之外的信息 -B Num 除了显示匹配的一行之外，并显示该行之前的num行 -A Num 除了显示匹配的一行之外，并显示改行之后的num行 -C Num 除了显示匹配的一行之外，并显示改行之前后各num行 -o 输出匹配字符，而不是默认的整行输出 -i 不区分大小写 -n 讲匹配出的结果在文件所在的行号打印 -c 打印匹配到的行数 -H 在匹配到符合行之前打印文件名 \u0026ndash;color=auto 给匹配倒的字符串加颜色（不是整行。关键字高亮显示） 实例\n显示/etc/services 下3306和1521 端口信息 bash 1 2 3 4 5 $ grep -E \u0026#34;3306|1521\u0026#34; /etc/services mysql 3306/tcp # MySQL mysql 3306/udp # MySQL ncube-lm 1521/tcp # nCube License Manager ncube-lm 1521/udp # nCube License Manager 过滤出文件内指定字符串 bash 1 2 3 4 5 6 7 $ cat text.txt zhangsan lisi oldbl $ grep \u0026#34;lisi\u0026#34; text.txt lisi 排除指定字符 bash 1 2 3 4 5 6 $ grep -v \u0026#34;lisi\u0026#34; text.txt zhangsan oldbl $ grep -n \u0026#34;555\u0026#34; a.html 6:555 一个文件有100行，只看20~30行 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # 方法1 $ grep 30 -B 10 test.txt 20 21 22 23 24 25 26 27 28 29 30 # 方法2 $ head -30 3.txt|tail -11 20 21 22 23 24 25 26 27 28 29 30 列出文件名 bash 1 2 3 $ grep -H root /etc/passwd /etc/passwd:root:x:0:0:root:/root:/bin/bash /etc/passwd:operator:x:11:0:operator:/root:/sbin/nologin 日志查询中常用命令 打印一段时间的日志\nbash 1 sed -n \u0026#39;/2019-12-28 11:26/,/2019-12-28 12:13/p\u0026#39; nohup.out 输出日志文件中的某个日期中的ERROR的行\nbash 1 sed -n \u0026#39;/^2016-06-21.*ERROR/p\u0026#39; nohup.out 统计http相应状态码\nbash 1 cat looklinix.com_access.log | cut -d \u0026#39;\u0026#34;\u0026#39; -f3 | cut -d \u0026#39; \u0026#39; -f2 | sort | uniq -c | sort 使用awk\nbash 1 awk \u0026#39;{print $9}\u0026#39; looklinix.com_access.log | sort | uniq -c | sort 列出404的接口\nbash 1 awk \u0026#39;($9 ~ /404/)\u0026#39; looklinix.com_access.log | awk \u0026#39;{print $7}\u0026#39; | sort | uniq -c | sort -r 检查404请求来自哪里\nbash 1 awk -F \\\u0026#34; \u0026#39;($2 ~ \u0026#34;/survey/report/na\u0026#34;){print $1}\u0026#39; looklinix.com_access.log | awk \u0026#39;{print $1}\u0026#39; | sort | uniq -c | sort –r 查询x 分钟内访问最多的前 10 个IP\nday hour minutes\nbash 1 awk -vDate=`date -d\u0026#39;now-30 minutes\u0026#39; +[%d/%b/%Y:%H:%M:%S` \u0026#39;$4 \u0026gt; Date {print $1}\u0026#39; /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -n 10 查询请求URL数量排行\nbash 1 awk \u0026#39;{print $7}\u0026#39; /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -n 50 统计所有的IP请求量\nbash 1 awk \u0026#39;{print $1}\u0026#39; access.log | sort -n | uniq | wc -l 统计某一时间段的IP请求量\nbash 1 grep \u0026#34;07/Apr/2017:0[4-5]\u0026#34; access.log | awk \u0026#39;{print $1}\u0026#39; | sort | uniq -c| sort -nr | wc -l 统计IP请求数量大于一个值的排行\nbash 1 awk \u0026#39;{print $1}\u0026#39; access.log | sort -n |uniq -c |awk \u0026#39;{if($1 \u0026gt;100) print $0}\u0026#39;|sort -rn 列出请求时间超过3s的接口\nbash 1 cat access.log|awk \u0026#39;($NF \u0026gt; 3){print $7}\u0026#39;|sort -n|uniq -c|sort -nr|head -20 获取每分钟的请求数量并输出成csv文件\nbash 1 cat access.log | awk \u0026#39;{print substr($4,14,5)}\u0026#39; | uniq -c | awk \u0026#39;{print $2\u0026#34;,\u0026#34;$1}\u0026#39; \u0026gt; access.csv 查看搜索引擎爬虫\nbash 1 2 3 4 5 6 7 8 # 百度爬虫 降序 cat access.log | grep \u0026#34;Baiduspider\u0026#34; | awk \u0026#39;{print $7}\u0026#39; | sort | uniq -c | sort -r # 谷歌爬虫降序 cat access.log | grep \u0026#34;Googlebot\u0026#34; | awk \u0026#39;{print $7}\u0026#39; | sort | uniq -c | sort -r # 谷歌爬虫404的次数 grep \u0026#39;Googlebot\u0026#39; access.log |grep \u0026#39;404\u0026#39; | wc -l ","permalink":"https://www.161616.top/awesome-linux-log-command/","summary":"工具命令集合 长期总结 - Linux日志查询命令 长期总结 - Linux网络命令合集 长期总结 - Linux性能分析命令 awk常用案例 bash shell常用示例 探索kubectl - 巧用jsonpath提取有用数据 探索kubectl - kubectl诊断命令集合 理解ldap - OpenLDAP客户端命令行使用 sed 语法\nbash 1 sed \u0026#39;/过滤的内容/处理的命令\u0026#39; 文件 参数 注释说明 n 取消sed默认的输出 i 替换文件内容 r 如果有特殊字符不用转义（正则） g 全局替换 d 删除 p print打印 # 为分隔符可以用其他符号替换（最好用$ @ /）替换内容中如果有分隔符，需要将分隔符替换为别的分隔符，如果不换可将内容转义 s 为search g为globla全局替换，不加的话只替换一列\n打印\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ sed -n \u0026#39;2p\u0026#39; 3.","title":"长期总结 - Linux日志查询命令"},{"content":"什么是 SSH ？ SSH全称(SecureSHell)是一种网络协议，顾名思义就是非常安全的shell，主要用于计算机间加密传输。早期，互联网通信都是基于明文通信，一旦被截获，内容就暴露无遗。1995年，芬兰学者Tatu Ylonen设计了SSH协议，将登录信息全部加密，成为互联网安全的一个基本解决方案，迅速在全世界获得推广，目前已经成为Linux系统的标准配置。\nSSH服务是由OpenSSH服务端软件OpenSSH和客户端（常见的由SSH，SecureCRT，Xshell，putty）组成，默认使用22端口提供服务，有两个不兼容的ssh协议版本，分别为1.x和2.x。\nSSH协议目前有SSH1和SSH2两个主流版本，SSH2协议兼容SSH1，强烈建议使用SSH2版本。目前实现SSH1和SSH2协议的主要软件有OpenSSH 和SSH Communications Security Corporation　公司的SSH Communications 软件。前者是OpenBSD组织开发的一款免费的SSH软件，后者是商业软件，因此在linux、FreeBSD、OpenBSD 、NetBSD等免费类UNIX系统种，通常都使用OpenSSH作为SSH协议的实现软件。\nsh 1 2 3 $ rpm -qa openssh openssl openssl-1.0.1e-30.el6.x86_64 openssh-5.3p1-104.el6.x86_64 SSH1.x\n每台ssh服务器主机都可以使用rsa加密方式来产生一个1024bit的RSAKey，这个RSA的加密方式就是用来产生公钥与私钥的算法之一，SSH1.x的整个联机加密步骤如下： 当SSH服务启动时，会产生一个768-bit的临时公钥（sshd_config配置文件中ServerKeyBits 768）存放在server中\nsh 1 2 3 # centos5为768 centos6为1024 $ grep ServerKey /etc/ssh/sshd_config #ServerKeyBits 1024 当客户端联机请求传送过来时，服务器就会将这个768-bit的公钥传给客户端，此时客户端会将此公钥与先前存储的公钥进行对比，看是否一致。判断标准是服务器端联机用户目录下~/.ssh/know_hosts文件的内容（linux客户端）\nsh 1 2 3 $ ssh -p22 lamp@192.168.65.62 $ ll .ssh/known_hosts -rw-r--r--. 1 root root 395 Jun 16 13:15 .ssh/known_hosts windows SecureCRT图示 在客户端接收到这个768-bit的Server Key后，客户端本地也会产生一个256bit的私钥（private key或host key），并且以加密的方式（具体的加密算法由客户端在服务器提供的所有可用算法中选择，默认为3DES算法），将Server key与host\nSSHD_CONFIG配置文件详解 参数选项 注释说明 Port Num 指定sshd服务器侦听端口num。将num改成非标准端口可以提高安全性。默认端口为22。 Protocol 1|2|2,1 指定 sshd 支持的SSH协议的版本号。'1'和'2'表示仅仅支持SSH-1和SSH-2协议。\u0026quot;2,1\u0026quot;表示同时支持SSH-1和SSH-2协议。 PubkeyAuthentication 是否允许公钥认证。仅可以用于SSH-2。默认值为\u0026quot;yes\u0026quot;。 ListenAddress 0.0.0.0 监听的IP，默认监听所有地址（可指定多个监听的地址），可使用以下格式：\nListenAddress\nhost | IPv4_addr | IPv6_addr\nListenAddress host | IPv4_addr:port\nListenAddress [host|IPv6_addr]:port\n举例说明：如果您有两个 IP，分别是 192.168.0.100 及 192.168.2.20 ，那么只想要开放 192.168.0.100 时，就可以写如同下面的样式：ListenAddress 192.168.0.100只监听来自 192.168.0.100 这个 IP 的SSH联机。 HostKey /etc/ssh/ssh_host_key SSH version 1 使用的私钥 HostKey /etc/ssh/ssh_host_rsa_key SSH version 2 使用的 RSA 私钥 HostKey /etc/ssh/ssh_host_dsa_key SSH version 2 使用的 DSA 私钥 KeyRegenerationInterval 1h 由前面联机的说明可以知道， version 1 会使用 server 的 Public Key ，那么如果这个 Public Key 被偷的话，岂不完蛋？所以需要每隔一段时间来重新建立一次！这里的时间为秒！ ServerKeyBits 768 定义服务器密匙的位数，centos5 768，centos6 1024 SyslogFacility 指定 sshd(8) 将日志消息通过哪个日志子系统(facility)发送。有效值是：DAEMON, USER, AUTH(默认), LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7 LogLevel 指定 sshd(8) 的日志等级(详细程度)。可用值如下：QUIET, FATAL, ERROR, INFO(默认), VERBOSE, DEBUG, DEBUG1, DEBUG2, DEBUG3\nDEBUG 与 DEBUG1 等价；DEBUG2 和 DEBUG3 则分别指定了更详细、更罗嗦的日志输出。\n注：比 DEBUG 更详细的日志可能会泄漏用户的敏感信息，因此反对使用。 LoginGraceTime 2m 当使用者连上 SSH server 之后，会出现输入密码的画面，在该画面中，在多久时间内没有成功连上 SSH server ，就断线！时间为默认为秒，可加时间标志m,h PermitRootLogin 是否允许 root 登录。可用值如下：\n\u0026ldquo;yes\u0026rdquo;(默认) 表示允许。\u0026ldquo;no\u0026quot;表示禁止。\n\u0026ldquo;without-password\u0026quot;表示禁止使用密码认证登录。\n\u0026ldquo;forced-commands-only\u0026quot;表示只有在指定了 command 选项的情况下才允许使用公钥认证登录。同时其它认证方法全部被禁止。这个值常用于做远程备份之类的事情。 StrictModes 指定是否要求 sshd 在接受连接请求前对用户主目录和相关的配置文件进行宿主和权限检查。强烈建议使用默认值\u0026quot;yes\u0026quot;来预防可能出现的低级错误。 MaxAuthTries 指定每个连接最大允许的认证次数。默认值是 6 。如果失败认证的次数超过这个数值的一半，连接将被强制断开，且会生成额外的失败日志消息。 PubkeyAuthentication 是否允许公钥认证。仅可以用于SSH-2。默认值为\u0026quot;yes\u0026quot;。 AuthorizedKeysFile 存放该用户可以用来登录的 RSA/DSA 公钥。该指令中可以使用下列根据连接时的实际情况进行展开的符号：\n%% 表示\u0026rsquo;%\u0026rsquo;\n%h 表示用户的主目录\n%u 表示该用户的用户名。\n经过扩展之后的值必须要么是绝对路径，要么是相对于用户主目录的相对路径。默认值是\u0026quot;.ssh/authorized_keys\u0026quot;。 IgnoreRhosts 是否在 RhostsRSAAuthentication 或 HostbasedAuthentication 过程中忽略 .rhosts 和 .shosts 文件。不过 /etc/hosts.equiv 和 /etc/shosts.equiv 仍将被使用。推荐设为默认值\u0026quot;yes\u0026quot; IgnoreUserKnownHosts 是否在 RhostsRSAAuthentication 或 HostbasedAuthentication 过程中忽略用户的 ~/.ssh/known_hosts 文件。\n默认值是\u0026quot;no\u0026quot;。为了提高安全性，可以设为\u0026quot;yes\u0026quot;。 PasswordAuthentication 是否允许使用基于密码的认证。默认为\u0026quot;yes\u0026quot;。 PermitEmptyPasswords 是否允许密码为空的用户远程登录。默认为\u0026quot;no\u0026quot;。 UseDNS 指定 sshd是否应该对远程主机名进行反向解析，以检查此主机名是否与其IP地址真实对应。默认值为\u0026quot;yes\u0026quot;。。 PidFile 指定在哪个文件中存放SSH守护进程的进程号，默认为/var/run/sshd.pid。文件。 MaxStartups 最大允许保持多少个未认证的连接。默认值是 10 。到达限制后，将不再接受新连接，除非先前的连接认证成功或超出 LoginGraceTime 的限制。 SSH服务的认证类型 从ssh客户端来看，SSH服务主要提供两种级别的安全验证，具体级别如下：\n基于口令的安全验证 基于口令的安全验证是大家一直用的，只要知道服务器的SSH连接账号和口令（对应的服务器IP与开放的SSH端口，默认22），就可以通过ssh客户端登陆到这台远程主机。此时联机过程中所有传输的数据都是加密的。\n口令验证测试\nsh 1 2 3 4 5 6 7 8 9 $ ssh -p52113 root@192.168.252.61 The authenticity of host \u0026#39;[192.168.252.61]:52113 ([192.168.252.61]:52113)\u0026#39; can\u0026#39;t be established. RSA key fingerprint is 38:68:34:4e:09:c0:75:18:be:72:17:20:2c:95:0a:e6. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added \u0026#39;[192.168.252.61]:52113\u0026#39; (RSA) to the list of known hosts. root@192.168.252.61\u0026#39;s password: Last login: Thu May 11 21:37:33 2017 from 192.168.252.1 $ 基于秘钥的安全验证 基于秘钥的安全验证方式是指，需要依靠秘钥。也就是必须实现建立一对秘钥对，然后吧公用秘钥（public key）放在需要访问的目标服务器上，另外，还需要把私钥（private key）放到SSH客户端或对应的客户端服务器上。\n此时，如果想要连接到这个带有公用秘钥的SSH服务器，客户端SSH软件或客户端服务器就会向SSH服务器发出请求，请求用联机的用户秘钥进行安全验证。SSH服务器收到请求后，会先在该SSH服务器上连接的用户的家目录下寻找事先放上去的对应用户的公共密钥，然后把它和连接的SSH客户端发送过来的公用密钥进行比较。如果两个密钥一致，SSH服务器就用公用密钥加密“质询”（challenge）并把他发送给SSH客户端。\nSSH客户端收到“质询”之后就可以用自己的私钥解密，再把发送给SSH服务器。使用这种方式，需要使用联机用户的密钥文件。与第一种基于口令验证的方式相比，第二种方式不需要在网络上传送口令密码，所以安全性更高了，这时我们也要注意保护我们的密钥文件。特别是私钥文件，一旦被黑客获取，危险就大了。\n基于密钥的安全认证也有windows客户端与linux客户端的区别\nWINDOWS下使用秘钥方式登陆linux 方法一：在linux服务器上生成密钥对\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 $ ssh-keygen -t dsa Generating public/private dsa key pair. # 输入文件中要保存的key Enter file in which to save the key (/root/.ssh/id_dsa): zhangsan # 输入一个要解密的密码，空为无密码 Enter passphrase (empty for no passphrase): # 再次输入密码，空为无密码 Enter same passphrase again: # 身份证明以保存至zhangsan这个文件 Your identification has been saved in zhangsan. # 公钥以保存为zhangsan.pub这个文件 Your public key has been saved in zhangsan.pub. # lamp主机的root用户的key的指纹为那些 The key fingerprint is: 10:d2:e8:03:30:4b:c9:8b:76:97:d0:6e:09:52:f7:06 root@lamp The key\u0026#39;s randomart image is: +--[ DSA 1024]----+ |+oo.oE. | |.*.ooo+. | |o oo+ +o | |.o .o*.. | |. . o. S | | | | | | | | | +-----------------+ $ ls known_hosts zhangsan zhangsan.pub # 在服务器上将公钥重命名,名字错误将验证不成功 $ mv id_dsa.pub authorized_keys # 查看是否重命名成功 $ ll -rw-r--r--. 1 root root 599 Jun 17 03:23 authorized_keys -rw-------. 1 root root 668 Jun 17 03:23 id_dsa # 修改公钥权限 $ chmod 600 authorized_keys # 查看公钥权限是否修改成功 $ ll -rw-------. 1 root root 599 Jun 17 03:23 authorized_keys -rw-------. 1 root root 668 Jun 17 03:23 id_dsa 使用SecureCRT将私钥转换为openSSH格式的私钥\n选择之前下载的私钥的路径\n如果没输入密码则无这一步\n测试过程\n方法二：使用SecureCRT创建秘钥对\n选择当前要生成的秘钥窗口，然后点击“工具”中的创建公钥选项，如下图所示： 秘钥生成向导选择下一步，如下图所示： 秘钥类型选择DSA或RSA，然后点击下一步，如下图所示： 生成秘钥长度，保持默认即可，如下图所示： 秘钥生成过程，如下图所示： 秘钥生成后选择下一步，如下图所示： 秘钥生成后，点击下一步，选择秘钥的私钥保存的路径和，然后点击完成。如下图所示： 点击完成后，弹出是否使用此秘钥为全局公钥，以个人习惯选择是或否，这里选择否，如下图所示： 将SecureCRT生成的公钥上传到linux服务器上 sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # 创建公钥存放目录 $ mkdir .ssh # 修改.ssh目录的权限 $ chmod 700 .ssh/ # 查看ssh2兼容格式转换成openssh的格式结果 $ ssh-keygen -i -f Identity.pub ssh-dss AAAAB3NzaC1kc3MAAACBAIJLJsIZJ0G/RKBnWQ04uRfqnv3Vkm8iusrI3Bm784lP64kn/IVZnC/2wcs6xjwjuGFt8GgNVKty+ s5/jf4uMBse7Ju3alIv42iOmS5+qeztb3Yio1r0rEjLcEdFZVEW3dbVsYX3ufwvBa9GhPum4q3eYwY7TziKR9ub2UJZTqVzAAAAFQDSfk HVhjW7J80YBBI2PdqVcRfZ3wAAAIBwmHcLm+BVCcMmpKfYzl+W1/79Cd7vSbeFMW+linn82Li/RcVnWF47hxeKwOwJ/O2UJ879cW1xPUG nJUNfEvHJs93rn3zthlbwLCmyH8Ugp2pF38DgyydbU6Xs/6lyiUc14WlzvL3QqO1H+QBX/18ZgXsZjoxakd86pz329r9wTgAAAIAVakBN 8DtSRZqPLeaXDhevg3texOTlmDsmhmqBcAOmH6VnzP1VB0E8DhTB8/MWJChSJalslyEzaa1PhBNwZEn3SmG6vWe+CXSuEtHBXbVBQHsDb xois6L+oqkL86PA7JdzTDSfiZKoGOks5f37Qb+CREnhyYcXJIjPZTE/0aIsdA== # 将ssh2兼容格式转换成openssl的格式结果 $ ssh-keygen -i -f Identity.pub \u0026gt;\u0026gt;.ssh/authorized_keys # 修改公钥的权限 $ chmod 600 .ssh/authorized_keys # 查看权限是否修改成功 $ ll .ssh/ -rw-------. 1 root root 589 Jun 17 04:37 authorized_keys 测试结果 新建快速链接，在鉴权中选择公钥，如下图所示： 如果设置了通行短语，就会出现以下框\n裸奔的后果！一次ssh被篡改的入侵事件\n扫描端口实例：牤牛阵解决ssh安全问题\nsh 1 2 3 4 5 6 7 8 9 10 11 $ nmap 192.168.65.62 -p1-65535 Starting Nmap 5.51 ( http://nmap.org ) at 2016-06-17 05:11 CST Nmap scan report for 192.168.65.62 Host is up (0.0000040s latency). Not shown: 65532 closed ports PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 3306/tcp open mysql Nmap done: 1 IP address (1 host up) scanned in 0.79 seconds ","permalink":"https://www.161616.top/ssh-service/","summary":"什么是 SSH ？ SSH全称(SecureSHell)是一种网络协议，顾名思义就是非常安全的shell，主要用于计算机间加密传输。早期，互联网通信都是基于明文通信，一旦被截获，内容就暴露无遗。1995年，芬兰学者Tatu Ylonen设计了SSH协议，将登录信息全部加密，成为互联网安全的一个基本解决方案，迅速在全世界获得推广，目前已经成为Linux系统的标准配置。\nSSH服务是由OpenSSH服务端软件OpenSSH和客户端（常见的由SSH，SecureCRT，Xshell，putty）组成，默认使用22端口提供服务，有两个不兼容的ssh协议版本，分别为1.x和2.x。\nSSH协议目前有SSH1和SSH2两个主流版本，SSH2协议兼容SSH1，强烈建议使用SSH2版本。目前实现SSH1和SSH2协议的主要软件有OpenSSH 和SSH Communications Security Corporation　公司的SSH Communications 软件。前者是OpenBSD组织开发的一款免费的SSH软件，后者是商业软件，因此在linux、FreeBSD、OpenBSD 、NetBSD等免费类UNIX系统种，通常都使用OpenSSH作为SSH协议的实现软件。\nsh 1 2 3 $ rpm -qa openssh openssl openssl-1.0.1e-30.el6.x86_64 openssh-5.3p1-104.el6.x86_64 SSH1.x\n每台ssh服务器主机都可以使用rsa加密方式来产生一个1024bit的RSAKey，这个RSA的加密方式就是用来产生公钥与私钥的算法之一，SSH1.x的整个联机加密步骤如下： 当SSH服务启动时，会产生一个768-bit的临时公钥（sshd_config配置文件中ServerKeyBits 768）存放在server中\nsh 1 2 3 # centos5为768 centos6为1024 $ grep ServerKey /etc/ssh/sshd_config #ServerKeyBits 1024 当客户端联机请求传送过来时，服务器就会将这个768-bit的公钥传给客户端，此时客户端会将此公钥与先前存储的公钥进行对比，看是否一致。判断标准是服务器端联机用户目录下~/.ssh/know_hosts文件的内容（linux客户端）\nsh 1 2 3 $ ssh -p22 lamp@192.168.65.62 $ ll .ssh/known_hosts -rw-r--r--. 1 root root 395 Jun 16 13:15 .ssh/known_hosts windows SecureCRT图示 在客户端接收到这个768-bit的Server Key后，客户端本地也会产生一个256bit的私钥（private key或host key），并且以加密的方式（具体的加密算法由客户端在服务器提供的所有可用算法中选择，默认为3DES算法），将Server key与host","title":"SSH服务详解"},{"content":"SSH客户端附带的远程拷贝scp命令 scp是加密 的远程拷贝，可以把数据从一台机器推送到另一台机器，也可以从其他服务器把数据拉回到本地执行命令的服务器，但是，每次都是全量拷贝（rsync增量拷贝），因此效率不高。 scp的基本语法使用 secure copy （remote file copy ）\n参数选项 注释说明 -p 拷贝前后保持文件或目录属性 -P （大写）\t接端口，默认22端口时可省略 -r 拷贝目录 -l 限制速度 推：scp -Pport 源 目标(user@host_ip):/path\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ touch {a..f}.txt $ ls a.txt b.txt c.txt d.txt e.txt f.txt $ scp -P22 a.txt root@192.168.65.62:/ root@192.168.65.62\u0026#39;s password: a.txt 100% 0 0.0KB/s 00:00 $ ls / app a.txt 拉：scp -Pport 源(user@host_ip):/path 目标\nsh 1 2 3 4 5 6 7 8 9 10 11 $ ll / total 98 app a.txt $ ls $ scp -P22 root@192.168.65.62:/a.txt ./ -rw-r--r--. 1 root root 0 Jun 16 18:20 a.txt root@192.168.65.62\u0026#39;s password: a.txt 100% 0 0.0KB/s 00:00 $ ls a.txt 拷贝目录\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ touch {a..g}.txt $ ls a.txt b.txt c.txt d.txt e.txt f.txt g.txt $ scp -P22 -r /test1 root@192.168.65.62:/ root@192.168.65.62\u0026#39;s password: c.txt 100% 0 0.0KB/s 00:00 b.txt 100% 0 0.0KB/s 00:00 g.txt 100% 0 0.0KB/s 00:00 e.txt 100% 0 0.0KB/s 00:00 a.txt 100% 0 0.0KB/s 00:00 d.txt 100% 0 0.0KB/s 00:00 f.txt 100% 0 0.0KB/s 00:00 $ l $ ll / drwxr-xr-x. 2 root root 4096 Jun 17 06:52 test1 SSH服务附带sftp功能服务 安全的FTP的功能，即通过ssh加密数据后进行传输\nLinux FTP客户端连接sftp服务器方法 linux客户端连接 sftp user@ip 如果端口为52113则登陆命令如下：\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 $ sftp -oPort=22 root@192.168.65.62 Connecting to 192.168.65.62... root@192.168.65.62\u0026#39;s password: # sftp默认的目录为用户家目录 sftp\u0026gt; ls -l Identity.pub anaconda-ks.cfg install.log install.log.syslog # 上传，默认上传到对方家目录 sftp\u0026gt; put /test/1 Uploading /test/1 to /root/1 /test/1 100% 0 0.0KB/s 00:00 $ ll -rw-r--r--. 1 root root 0 Jun 17 08:43 1 sftp\u0026gt; ls -l 1 Identity.pub anaconda-ks.cfg install.log install.log.syslog # sftp不支持tab键 sftp\u0026gt; cat 1 Invalid command. # 下载，默认下载到用户家目录 sftp\u0026gt; get install.log Fetching /root/install.log to install.log /root/install.log 100% 21KB 21.2KB/s 00:00 # sftp并不锁定目录，可以自行选择文件上传 # 把/etc/passwd从客户端本地传到sftp服务端指定目录/根下 sftp\u0026gt; put /etc/passwd / Uploading /etc/passwd to /passwd /etc/passwd 100% 1202 1.2KB/s 00:00 # lnmp长传自sftp服务器上的passwd文件 $ ll / -rw-r--r--. 1 root root 1202 Jun 17 08:51 passwd # sftp不能下载/上传目录 sftp\u0026gt; get asdc Fetching /root/asdc to asdc Cannot download non-regular file: /root/asdc 小结：\nsftp -oPort=22 root@1.1.1.1 上传put加客户端本地路径，也可以指定路径上传，put /etc/hosts(客户端目录) /tmp（服务器目录） 下载直接get服务端的内容，下载到本地的当前目录 windows客户端连接sftp方法 这时是windows连接的lnmp而和lamp无关，相当于rz sz上传下载的功能\nsh 1 2 3 4 5 6 7 8 # 上传，路径要加双引号 sftp\u0026gt; put \u0026#34;D:\\vmware-0.log\u0026#34; Uploading vmware-0.log to /root/vmware-0.log 100% 193KB 193KB/s 00:00:00 D:/vmware-0.log: 197820 bytes transferred in 0 seconds (193 KB/s) sftp\u0026gt; ls anaconda-ks.cfg install.log install.log.syslog vmware-0.log 下载\nsh 1 2 3 4 5 6 7 8 9 10 sftp\u0026gt; get install.log Downloading install.log from /root/install.log 100% 21KB 21KB/s 00:00:00 /root/install.log: 21682 bytes transferred in 0 seconds (21 KB/s) sftp\u0026gt; get install.log \u0026#34;D:\\\u0026#34; Downloading install.log from /root/install.log get: D:/: 系统找不到指定的文件。 100% 21KB 21KB/s 00:00:00 /root/install.log: 21682 bytes transferred in 0 seconds (21 KB/s) 下载的目录是CRT设置的目录，注意设置后需要重新启动软件\n","permalink":"https://www.161616.top/scp/","summary":"SSH客户端附带的远程拷贝scp命令 scp是加密 的远程拷贝，可以把数据从一台机器推送到另一台机器，也可以从其他服务器把数据拉回到本地执行命令的服务器，但是，每次都是全量拷贝（rsync增量拷贝），因此效率不高。 scp的基本语法使用 secure copy （remote file copy ）\n参数选项 注释说明 -p 拷贝前后保持文件或目录属性 -P （大写）\t接端口，默认22端口时可省略 -r 拷贝目录 -l 限制速度 推：scp -Pport 源 目标(user@host_ip):/path\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ touch {a..f}.txt $ ls a.txt b.txt c.txt d.txt e.txt f.txt $ scp -P22 a.txt root@192.168.65.62:/ root@192.168.65.62\u0026#39;s password: a.txt 100% 0 0.0KB/s 00:00 $ ls / app a.txt 拉：scp -Pport 源(user@host_ip):/path 目标","title":"使用SSH协议来传输文件"},{"content":"什么是Expect Expent是基于tcl的相对简单的一个免费的脚本编程工具语言，用来实现自动和交互任务程序进行通信，无需人的手工干预。比如SSH、FTP等，这些程序正常情况都需要手工与它们进行交互，而使用Expect就可以模拟人手工交互的过程，实现自动的和远程的程序交互，从而达到自动化运维的目的。\nExpect程序工作流程 Expect的工作流程可以理解为，spawn启动进程 ==\u0026gt; expect期待关键字 ==\u0026gt; send向进程发送字符 ==\u0026gt; 退出结束。\n安装Expect软件 首先，配置好yum安装源，并且确保机器可以上网，然后执行yum install expect -y即可安装Expect软件。\n安装完后查看结果：\nsh 1 2 $ rpm -qa|grep expect expect-5.44.1.15-5.el6_4.x86_64 先看一个Expect小实例 首先准备3台虚拟机：\nsh 1 2 3 4 192.168.252.60 client 192.168.252.62 client 192.168.252.63 client 192.168.252.64 server 再执行下面例子前，我们先手工执行如下命令\nsh 1 ssh -p52113 root@192.168.252.64 ifconfig 执行结果：\nsh 1 2 $ ssh -p52113 root@192.168.252.64 ifconfig root@192.168.252.64\u0026#39;s password: Expect 例子脚本内容：\nsh 1 2 3 4 5 6 7 #!/usr/bin/expect spawn ssh -p52113 root@192.168.252.62 /sbin/ifconfig eth0 set timeout 60 expect \u0026#34;*password:\u0026#34; send \u0026#34;111111\\n\u0026#34; expect eof exit 执行结果：问题：\nsh 1 2 3 4 5 6 7 8 9 10 11 $ ./a.exp spawn ssh -p52113 root@192.168.252.62 /sbin/ifconfig eth0 root@192.168.252.62\u0026#39;s password: eth0 Link encap:Ethernet HWaddr 00:0C:29:C3:48:CB inet addr:192.168.252.62 Bcast:192.168.252.255 Mask:255.255.255.0 inet6 addr: fe80::20c:29ff:fec3:48cb/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:3027 errors:0 dropped:0 overruns:0 frame:0 TX packets:1632 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:268416 (262.1 KiB) TX bytes:175473 (171.3 KiB) 问题：\nsh 1 2 3 4 5 $ ./a.exp ./a.exp: line 8: spawn: command not found couldn\u0026#39;t read file \u0026#34;*password:\u0026#34;: no such file or directory ./a.exp: line 11: send: command not found couldn\u0026#39;t read file \u0026#34;eof\u0026#34;: no such file or directory 原因：\n含有expect的脚本不能用bash执行，bash无法解析。添加可执行权限后，直接./your_script即可 这个代码是expect的代码，不由bash解释。 spawn可以看作expect脚本的内部函数。要第一行改成 #!/usr/bin/expect Expect语法 Expect中的命令是最重要的部分了，命令的使用语法如下：\nsh 1 命令 [选项] 参数 spawn spawn命令是Expect的初始命令，它用于启动一个进程，之后所有expect操作都在这个进程中进行，如果没有spawn语句，整个expect就无法执行了，spawn使用方法如下：\nsh 1 spawn ssh root@192.168.252.62 在spawn命令后面直接加上要启动的进程、命令等信息，除此之外，spawn还支持其他选项如：\n-open\t启动文件进程，具体说明省略。 -ignore\t忽略某些信号，具体说明省略。 expect 使用方法：\nsh 1 expect 表达式 动作 表达式 动作 expect命令用于等候一个相匹配内容的输出，一旦匹配上就执行expect后面的动作或命令。这个命令接受几个特有的参数，用的最多的就是-re，表示使用正则表达式的方式匹配，使用起来就像这样：\nsh 1 2 spawn ssh root@192.168.252.64 expect \u0026#34;password:\u0026#34; {send \u0026#34;111111\\r\u0026#34;} 从上面的例子可以看出，expect是依附与spawn命令的，当执行ssh命令后，expect就匹配命令执行后的关键字：“password：”，如果匹配到关键字就会执行后面包含在“{}”中的send或exp_send动作，匹配的动作可以放在二行，这样就不需要使用“{}”了，就像下面这样，实际完成的功能与上面是一样的：\nsh 1 2 3 spawn ssh root@192.168.252.64 expect \u0026#34;password:\u0026#34; send \u0026#34;111111\\r\u0026#34; expect命令还有一种用法，他可以在一个expect匹配中多次匹配关键字，并给出处理动作，只需要将关键字放在一个大括号中就可以了，当然还要有exp_continue。\nsh 1 2 3 4 5 spawn ssh root@192.168.252.64 expect { \u0026#34;yes/no\u0026#34;\t{ exp_send \u0026#34;yes\\r\u0026#34;; exp_continue } \u0026#34;*password:\u0026#34;\t{ exp_send \u0026#34;111111\\r\u0026#34; } } exp_send和send 在上面的介绍中，我们已经看到exp_send命令的使用，exp_send命令是expect中的动作命令，它还有一个完成同样工作的同胞：send，exp_send命令可以发送一些特殊符号，我们看到了\\r（回车），还有一些其他的比如：\\n、\\t等等，这些都与TCL中的特殊符号相同。\nsh 1 2 3 4 5 spawn ssh root@192.168.252.64 expect { \u0026#34;yes/no\u0026#34;\t{ exp_send \u0026#34;yes\\r\u0026#34;; exp_continue } \u0026#34;*password:\u0026#34;\t{ exp_send \u0026#34;111111\\r\u0026#34; } } send命令有几个可用参数：\n-i 制定spawn_id，这个参数用来向不同spawn_id的进程发送命令，是进行多程序控制的关键参数。 -s s代表slowly，也就是控制发送的速度，这个参数使用的时候要与expect中的变量send_slow相关联。 exp_continue 这个命令一般用在动作中，它被使用的条件比较苛刻，看看下面的例子：\nsh 1 2 3 4 5 6 7 8 9 10 11 #!/usr/bin/expect spawn ssh root@192.168.252.64 /sbin/ifconfig eth0 set timeout 60 expect { -timeout 1 \u0026#34;yes/no\u0026#34;\t{ exp_send \u0026#34;yes\\r\u0026#34;; exp_continue } \u0026#34;*password:\u0026#34;\t{ exp_send \u0026#34;111111\\r\u0026#34; } timeout { puts \u0026#34;expect was timeout by oldboy.\u0026#34;; return} } expect eof exit 在这个例子中，可以发现exp_continue命令的使用方法，首先它要处于一个expect命令中，然后它属于一种动作命令，完成的工作就是从头开始遍历，也就是说如果没有这个命令，匹配第一个关键字以后就会继续匹配第二个关键字，但有了这个命令后，匹配第一个关键字以后，第二次匹配仍然从第一个关键字开始。\nsend_user send_user命令用来把后面的参数输出到标准输出中去，默认的send、exp_send命令都是将参数输出到程序中去的，用起来就像这样：\nsh 1 send_user \u0026#34;please input password:\u0026#34; 这个语句就可以在标准输出中打印Please input password：字符了。\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #!/usr/bin/expect if { $argc != 2 } { send_user \u0026#34;usage: expect scp-expect.exp file host dir\\n\u0026#34; exit } #define var set file [lindex $argv 0] set host [lindex $argv 1] set password \u0026#34;111111\u0026#34; #spawn scp /etc/hosts root@10.0.0.142:/etc/hosts spawn ssh-copy-id -i $file \u0026#34;-p 52113 root@$host\u0026#34; expect { \u0026#34;yes/no\u0026#34; {send \u0026#34;yes\\r\u0026#34;;exp_continue} \u0026#34;*password\u0026#34; {send \u0026#34;$password\\r\u0026#34;} } expect eof exit -onexit { send_user \u0026#34;Oldboy say good bye to you!\\n\u0026#34; } #script usage #expect oldboy-6.exp file host dir #example #./oldboy-6.exp /etc/hosts 10.0.0.179 /etc/hosts exit exit命令功能很简单，就是直接退出脚本，但是你可以利用这个命令对脚本做一些扫尾工作，比如下面这样：\nsh 1 2 3 4 exit -onexit { exec rm $tmpfile send_user \u0026#34;Good bye\\n\u0026#34; } sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #!/usr/bin/expect if { $argc != 2 } { send_user \u0026#34;usage: expect scp-expect.exp file host dir\\n\u0026#34; exit } #define var set file [lindex $argv 0] set host [lindex $argv 1] set password \u0026#34;111111\u0026#34; #spawn scp /etc/hosts root@10.0.0.142:/etc/hosts spawn ssh-copy-id -i $file \u0026#34;-p 52113 root@$host\u0026#34; expect { \u0026#34;yes/no\u0026#34; {send \u0026#34;yes\\r\u0026#34;;exp_continue} \u0026#34;*password\u0026#34; {send \u0026#34;$password\\r\u0026#34;} } expect eof exit -onexit { send_user \u0026#34;Oldboy say good bye to you!\\n\u0026#34; } #script usage #expect oldboy-6.exp file host dir #example #./oldboy-6.exp /etc/hosts 10.0.0.179 /etc/hosts Except变量 expect中有很多有用的变量，它们使用方法与TCL语言中的变量相同，比如： set 变量名 变量名 # 设置变量的方法 set $变量名 # 读取变量的方法\nsh 1 2 3 4 5 #define var set file [lindex $argv 0] set file [lindex $argv 1] set file [lindex $argv 2] set password \u0026#34;123456\u0026#34; Expect关键字 expect中的特殊关键字用于匹配过程，代表某些特殊含义或状态，一般用于expect族命令中不能在外面单独使用，也可以理解为事件，使用上类似于expect eof {}\neof eof (end-of-file)关键字用于匹配 结束符，比如文件的结束符、FTP传输停止等情况，在这个关键字后跟上动作来做进一步的控制，特别是FTP交互操作方面，它的动作很大。用一个例子来看看：\nsh 1 2 3 4 5 6 spawn ftp anonymous@10.11.105.110 expect { \u0026#34;password:\u0026#34; {exp_send \u0026#34;who I’m I\u0026#34;} eof {ftp connect close} } interact { } timeout timeout是expect中的一个重要变量，它是一个全局性的时间控制开关，你可以通过为这个变量赋值来规定整个expect操作时间，注意这个变量是服务于expect全局的，它不会纠缠于某一条命令，即使命令没有任何错误，到时见仍然会激活这个变量，但这个时间到达以后除了激活一个开关之外不会做其他的事情，如何处理是脚本编写人员的事情，看看它的实际使用方法：\nsh 1 2 3 4 set timeout 60 spawn ssh root@192.168.2.1 expect \u0026#34;password:\u0026#34; {send \u0026#34;word\\r\u0026#34;} expect timeout { puts \u0026#34;Expect was timeout\u0026#34;; return } 上面的处理中，首先将timeout变量设置为60秒，当出现问题的时候程序可能会停止下来，只要到60秒。就会激活下面的timeout动作，这里我们打印一个信息并且停止了脚步的运行\u0026ndash;你可以做更多其他的事情，看自己的意思。\n在另一种expect格式中，我们还有一种设置timeout变量的方法，看看下面的例子：\nsh 1 2 3 4 5 6 7 8 9 spawn ssh root@192.168.1.1 expect { -timeout 60 -re \u0026#34;password:\u0026#34; {exp_send \u0026#34;word\\r\u0026#34;} -re \u0026#34;TopsecOS#\u0026#34; { } timeout { puts \u0026#34;Expect was timeout\u0026#34;; return } -re \u0026#34;TopsecOS#\u0026#34; { } timeout { puts \u0026#34;Expecr was timeout\u0026#34;; return } } 生产场景Expect实战 sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 $ cat fenfa.exp #!/usr/bin/expect if { $argc != 3 } { send_user \u0026#34;usage: expect scp-expect.exp file host dir\\n\u0026#34; exit } #define var set file [lindex $argv 0] set host [lindex $argv 1] set dir [lindex $argv 2] set password \u0026#34;111111\u0026#34; spawn scp -P 52113 -p $file root@$host:$dir #spawn ssh-copy-id -i $file \u0026#34;-p 52113 root@$host\u0026#34; expect { \u0026#34;yes/no\u0026#34; {send \u0026#34;yes\\r\u0026#34;;exp_continue} \u0026#34;*password\u0026#34; {send \u0026#34;$password\\r\u0026#34;} } expect eof exit -onexit { send_user \u0026#34;Oldboy say good bye to you!\\n\u0026#34; } #script usage #expect oldboy-6.exp file host dir #example #./oldboy-6.exp /etc/hosts 10.0.0.179 /etc/hosts 实战1：使用expect实现批量分发/etc/hosts文件 sh 1 2 3 4 5 6 7 8 9 10 11 12 $ cat hosts.sh #!/bin/sh . /etc/init.d/functions for ip in 192.168.252.60 192.168.252.64 192.168.252.62 do expect fenfa.exp /etc/hosts/ $ip ~/ \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 if [ $? -eq 0 ];then action \u0026#34;$ip\u0026#34; /bin/true else action \u0026#34;$ip\u0026#34; /bin/false fi done 如果ip很多写循环列表\nsh 1 for ip in `cat ip_list` 测试结果：\nsh 1 2 3 4 $ sh hosts.sh 192.168.252.60 [确定] 192.168.252.64 [确定] 192.168.252.62 [确定] 实战2：使用expect批量分发SSH密钥文件 首先准备4台机器：\nIP hostname- 192.168.252.60 lamp_client 192.168.252.62 rsync_client 192.168.252.63 nfs_server 192.168.252.64 mysql_client 1 在server端创建公钥与私钥\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ ssh-keygen -t dsa Generating public/private dsa key pair. Enter file in which to save the key (/root/.ssh/id_dsa): Created directory \u0026#39;/root/.ssh\u0026#39;. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /root/.ssh/id_dsa. Your public key has been saved in /root/.ssh/id_dsa.pub. The key fingerprint is: 85:c5:f0:88:31:ed:a6:e6:9f:54:61:1e:68:ee:b2:c7 root@server-nfs The key\u0026#39;s randomart image is: +--[ DSA 1024]----+ | o..o. | | +.*. | | ..= * | | oo+ o | | oS o | | o. . | | o..o | | .+E. | | .oo | +-----------------+ 2 查看密钥\nsh 1 2 3 $ ll ~/.ssh/ id_dsa id_dsa.pub 3 写批量拷贝脚本\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #!/usr/bin/expect if { $argc != 2 } { send_user \u0026#34;usage: expect scp-expect.exp file host dir\\n\u0026#34; exit } #define var set file [lindex $argv 0] set host [lindex $argv 1] set password \u0026#34;111111\u0026#34; #spawn scp /etc/hosts root@10.0.0.142:/etc/hosts spawn ssh-copy-id -i $file \u0026#34;-p 52113 root@$host\u0026#34; expect { \u0026#34;yes/no\u0026#34; {send \u0026#34;yes\\r\u0026#34;;exp_continue} \u0026#34;*password\u0026#34; {send \u0026#34;$password\\r\u0026#34;} } expect eof exit -onexit { send_user \u0026#34;Oldboy say good bye to you!\\n\u0026#34; } #script usage #expect oldboy-6.exp file host dir #example #./oldboy-6.exp /etc/hosts 10.0.0.179 /etc/hosts sh 1 2 3 4 5 6 7 8 9 10 11 12 13 $ ll ~/.ssh/ authorized_keys $ ll ~/.ssh/ id_dsa id_dsa.pub known_hosts $ ll ~/.ssh/ authorized_keys $ ll ~/.ssh/ authorized_keys sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ ssh -p52113 root@192.168.252.60 ifconfig eth0 Link encap:Ethernet HWaddr 00:0C:29:A7:DB:43 inet addr:192.168.252.60 Bcast:192.168.252.255 Mask:255.255.255.0 inet6 addr: fe80::20c:29ff:fea7:db43/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:1187 errors:0 dropped:0 overruns:0 frame:0 TX packets:792 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:105294 (102.8 KiB) TX bytes:73135 (71.4 KiB) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:40 errors:0 dropped:0 overruns:0 frame:0 TX packets:40 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:3520 (3.4 KiB) TX bytes:3520 (3.4 KiB) 大功告成。\n特别提示：如果是禁止了ROOT远程连接，那么就使用普通用户加sudo的方式结合在expect大量分发。有的同学们问，既然做密钥认证了，为什么还要expect分发呢。在做认证期间，第一次的密钥分发，如果机器数量多，比如1000台，就可以expect分发，比ssh-copy-id方式或http的方式都会好一点。\n当然从例一可以看出来，即使不用密钥认证，expect依然可以实现分发数据及文件及批量管理部署服务。\n","permalink":"https://www.161616.top/expect/","summary":"什么是Expect Expent是基于tcl的相对简单的一个免费的脚本编程工具语言，用来实现自动和交互任务程序进行通信，无需人的手工干预。比如SSH、FTP等，这些程序正常情况都需要手工与它们进行交互，而使用Expect就可以模拟人手工交互的过程，实现自动的和远程的程序交互，从而达到自动化运维的目的。\nExpect程序工作流程 Expect的工作流程可以理解为，spawn启动进程 ==\u0026gt; expect期待关键字 ==\u0026gt; send向进程发送字符 ==\u0026gt; 退出结束。\n安装Expect软件 首先，配置好yum安装源，并且确保机器可以上网，然后执行yum install expect -y即可安装Expect软件。\n安装完后查看结果：\nsh 1 2 $ rpm -qa|grep expect expect-5.44.1.15-5.el6_4.x86_64 先看一个Expect小实例 首先准备3台虚拟机：\nsh 1 2 3 4 192.168.252.60 client 192.168.252.62 client 192.168.252.63 client 192.168.252.64 server 再执行下面例子前，我们先手工执行如下命令\nsh 1 ssh -p52113 root@192.168.252.64 ifconfig 执行结果：\nsh 1 2 $ ssh -p52113 root@192.168.252.64 ifconfig root@192.168.252.64\u0026#39;s password: Expect 例子脚本内容：\nsh 1 2 3 4 5 6 7 #!/usr/bin/expect spawn ssh -p52113 root@192.","title":"expect使用案例"},{"content":"下载PHP 台湾镜像站：http://ftp.ntu.edu.tw/php/distributions/ 搜狐镜像站：http://mirrors.sohu.com/php/ 阿里镜像：http://mirrors.aliyun.com/ 官网：http://php.net/downloads.php 检查PHP所需的lib库 bash 1 2 3 4 5 6 7 8 9 10 11 rpm -qa \\ zlib-devel \\ libxml2-devel \\ libjpeg-devel \\ libjpeg-turbo-devel \\ libiconv-devel \\ freetype-devel \\ libpng-devel \\ gd-devel \\ libcurl-devel \\ libxslt-devel 提示：libjpeg-turbo-devel是早期libjpeg-devel的新名字，libcurl-devel是早期curl的新名字。\n每个lib一般都会存在对应的以“-devel”命名的包，安装lib对应的-devel包后，对应的lib包就会自动安装好，例如安装gd-devel时就会安装gd。\n这些lib库不是必须安装的，但是目前的企业环境下一般都需要安装。否则，PHP程序运行时会出现问题，例如验证码无法显示等。\n执行下面命令安装相关的lib软件包：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 yum install -y \\ zlib-devel \\ libxml2-devel \\ libjpeg-devel \\ libjpeg-turbo-devel \\ freetype-devel \\ libpng-devel \\ gd-devel \\ curl-devel \\ libxslt-devel \\ bzip2-devel \\ gmp-devel \\ readline-devel 提示：从安装上看，仅有libiconv-devel这个包没有安装，因为默认的yum源没有此包。可以一个一个地yum安装或通过源文件手工编译安装（这样效率慢）\n安装libiconv-devel libiconv下载地址：http://ftp.gnu.org/pub/gnu/libiconv/\n可以将libiconv制作成rpm包，批量安装时，可放至本地yum源内\nbash 1 2 ./configure --prefix=/usr/local/libiconv make \u0026amp; make install 安装epel源 可以安装redhat官方yum源里没有的软件，epel源和官方源不冲突\n阿里镜像 http://mirrors.aliyun.com/\nbash 1 wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-6.repo Centos7\nbash 1 wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo 安装libmcrypt-devel 这是一个使用动态加载的模块化的libmcrypt。libmcrypt对于在程序运行时添加/移除算法是有用的。limbcrypt-nm目前不再被官方支持，其软件地址为http://mcrypt.hellug.gr/lib/，编译PHP的过程中，libmcrypt库不是必须要安装的包。\nbash 1 2 3 4 yum install libmcrypt-devel -y # 安装成功后 rpm -qa libmcrypt-devel libmcrypt-devel-5.8-9.el6.x86_64 安装mhash加密扩展库 mhash是基于离散数学原理不可逆向的PHP加密方式扩展库，其在默认情况下不会开启。mhash可以用于创建校验数值、消息摘要、消息认证码，以及无需原文的关键信息保存（如密码）等。它为PHP提供了多种散列算法，如MD5、SHA1、GOST等。可以通过MHASH_hashname()查看其支持的算法有哪些。\nbash 1 yum install mhash -y 安装mcrypt PHP程序员在编写代码程序时，除了要保证代码的高性能之外，还有一点是非常重要的，那就是程序的安全性保障。PHP除了自带的几种加密函数外，还有功能更全面的PHP加密扩展库mcrypt和mhash。 其中，mcrypt扩展库可以实现加密解密功能，就是既能将明文加密，也可以将密文还原。 可以说，mcrypt是PHP里面重要的加密支持扩展库，该库在默认情况下不开启。\nbash 1 yum install mcrypt -y bash 1 yum install mcrypt mhash libmcrypt-devel -y 问：如果在不能联网的状态下怎么配置PHP环境？ 答：在yum时，可以在yum配置文件中设置安装后不删除包 vi /etc/yum.conf\n编译PHP 编译PHP参数详解 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 ./configure --prefix=/app/php-7. # \u0026lt;==指定PHP的安装目录 --with-curl # \u0026lt;==打开PHP curl扩展 --with-curlwrappers # \u0026lt;==curl工具打开url流的测试版，不建议开启 --with-freetype-dir # \u0026lt;== 打开freetype字体库支持 --with-gd # \u0026lt;==打开PHP GD库依赖 --with-png-dir # \u0026lt;== --with-jpeg-dir # \u0026lt;== --enable-gd-native-ttf # \u0026lt;==打开PHP GD库ttf --with-gettext # \u0026lt;==打开PHP gettext库，多国语言时需要 --with-iconv-dir=/usr/local/lib # \u0026lt;==打开PHP iconv字符集转换格式时需要 --with-kerberos # \u0026lt;==PHP的一种加密方式 DES --with-libdir=lib64 # \u0026lt;==默认找/usr/lib下。64位需指定路径 --with-libxml-dir # \u0026lt;==打开libxml2库的支持 --enable-xml --enable-safe-mode # \u0026lt;==打开安全模式 # 需要指定mysql的安装路径,安装PHP需要的MySQL相关内容。 # 当然如果没有MySQL软件包，也可以不单独安装， # 这样的情况可使用--with-mysql=mysqlnd替代--with-mysql=/app/mysql # 因为PHP软件里面已经自带连接MySQL的客户端工具。PHP7遗弃 --with-mysql=/app/mysql/ --with-mysqli=/app/mysql/bin/mysql_config # \u0026lt;==使用PHP mysqli扩展 --with-pdo-mysql # \u0026lt;==使用pdo mysql扩展 --with-pdo-sqlite # \u0026lt;==使用pdo sqlite扩展 --with-openssl # \u0026lt;==https需要 --with-pcre-regex # \u0026lt;==使用pcre正则\t--with-pear # \u0026lt;==安装pear，一般没啥用\t--with-xmlrpc # \u0026lt;==打开xml-rpc的c语言 --enable-libxml --disable-rpath # \u0026lt;==关闭额外的运行库文件 --with-xsl # \u0026lt;==打开XSLT文件支持,扩展libXML2库,需要libxslt软件 --with-zlib # \u0026lt;==打开zlib库的支持,用于http压缩传输 --enable-zip # \u0026lt;==打开对zip的支持 --enable-bcmath # \u0026lt;==打开图片大小调整,用zabbix监控时会用到该模块 --enable-inline-optimization # \u0026lt;==优化线程 --enable-mbregex # \u0026lt;== --enable-mbstring # \u0026lt;==支持mbstring --with-mcrypt # \u0026lt;==编码函数库 --with-mhash # \u0026lt;==mhash算法的扩展 --enable-opcache # \u0026lt;==php自带的加速模块php5.5 --enable-pcntl # \u0026lt;==freeTDS需要用到,可能是链接mssql --enable-shmop # \u0026lt;== --enable-soap # \u0026lt;==soap模块的扩展 --enable-sockets # \u0026lt;==打开sockets支持 --enable-sysvsem # \u0026lt;==使用sysv信号机制,则打开此选项 --enable-short-tags # \u0026lt;==开启短标签 --with-fpm-user=www --with-fpm-group=www --enable-fpm # \u0026lt;==表示激活PHP-FPM方式服务,即FactCGI方式运行PHP服务 --with-apxs2=/app/apache/bin/apxs # \u0026lt;==使httpd支持PHP --enable-json --with-bz2 --enable-filter --enable-session 公共编译参数 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 ./configure \\ --prefix=/app/php-5.5.38 \\ --with-curl \\ --with-freetype-dir \\ --with-gd \\ --with-png-dir \\ --with-jpeg-dir \\ --enable-gd-native-ttf \\ --with-gettext \\ --with-iconv-dir \\ --with-kerberos \\ --with-libxml-dir \\ --enable-xml \\ --enable-safe-mode \\ --with-mysql=/app/mysql/ \\ --with-mysqli=/app/mysql/bin/mysql_config \\ --with-pdo-mysql \\ --with-pdo-sqlite \\ --with-openssl \\ --with-pcre-regex \\ --with-pear \\ --with-xmlrpc \\ --enable-libxml \\ --disable-rpath \\ --with-xsl \\ --with-zlib \\ --enable-zip \\ --enable-bcmath \\ --enable-inline-optimization \\ --enable-mbregex \\ --enable-mbstring \\ --with-mcrypt \\ --with-mhash \\ --enable-opcache \\ --enable-pcntl \\ --enable-shmop \\ --enable-soap \\ --enable-sockets \\ --enable-sysvsem \\ --enable-short-tags \\ --enable-json \\ --with-bz2 \\ --enable-filter \\ --enable-session \\ --with-fpm-user=www \\ --with-fpm-group=www \\ --enable-fpm \\ --with-apxs2=/app/apache/bin/apxs bash 1 configure: error: Cannot find libmysqlclient under /app/mysql/. 解决：\nbash 1 2 ln -s /app/mysql/lib /app/mysql/lib64 ln -s /app/mysql/lib/libmysqlclient.so.15./app/mysql/lib64/libmysqlclient_r.so nginx 5.3.27 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ./configure \\ --prefix=/app/php-5.3.27 \\ --with-mysql=/app/mysql \\ --with-iconv-dir=/usr/local/libiconv \\ --with-freetype-dir \\ --with-jpeg-dir \\ --with-png-dir \\ --with-zlib-dir \\ --with-libxml-dir \\ --enable-xml \\ --disable-rpath \\ --enable-safe-mode \\ --enable-bcmath \\ --enable-shmop \\ --enable-sysvsem \\ --enable-inline-optimization \\ --with-curl \\ --enable-mbregex \\ --enable-fpm \\ --enable-mbstring \\ --with-mcrypt \\ --with-gd \\ --enable-gd-native-ttf \\ --with-openssl \\ --with-mhash \\ --enable-pcntl \\ --enable-sockets \\ --with-xmlrpc \\ --enable-zip \\ --enable-soap \\ --enable-short-tags \\ --enable-zend-multibyte \\ --enable-static \\ --with-xsl \\ --with-fpm-user=nginx \\ --with-fpm-group=nginx \\ --enable-ftp 注：上述每行结尾的换行符反斜线（\\）之后不能再有任何字符包括空格\napache 5.3 bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 ./configure \\ --prefix=/app/php-5.3.27 \\ --with-mysql=/app/mysql \\ --with-iconv-dir=/usr/local/libiconv \\ --with-apxs2=/app/apache/bin/apxs \\ --with-freetype-dir \\ --with-jpeg-dir \\ --with-png-dir \\ --with-zlib-dir \\ --with-libxml-dir \\ --enable-xml \\ --disable-rpath \\ --enable-safe-mode \\ --enable-bcmath \\ --enable-shmop \\ --enable-sysvsem \\ --enable-inline-optimization \\ --with-curl \\ --with-curlwrappers \\ --enable-mbregex \\ --enable-mbstring \\ --with-mcrypt \\ --with-gd \\ --enable-gd-native-ttf \\ --with-openssl \\ --with-mhash \\ --enable-pcntl \\ --enable-sockets \\ --with-xmlrpc \\ --enable-zip \\ --enable-soap \\ --enable-short-tags \\ --enable-zend-multibyte \\ --enable-static \\ --with-xsl \\ --enable-ftp 配置php.ini bash 1 2 /home/tools/php-5.3.27/php.ini-production /home/tools/php-5.3.27/php.ini-development bash 1 cp /home/tools/php-5.3.27/php.ini-production /app/php/lib/php.ini 开发环境更多的是开启日志、调试信息，而生产环境都是关闭状态。\n配置PHP（FastCGI）的配置文件php-fpm.conf PHP5位置：/app/php/etc/ PHP7位置：/app/php/etc/和 php-fpm.d bash 1 2 cp php-fpm.conf.default php-fpm.conf # \u0026lt;==PHP5只需要改它即可 php-fpm.d/www.conf.default # \u0026lt;==PHP7还需要改这个文件 3 配置Nginx支持PHP程序 nginx 1 2 3 4 5 6 7 8 # 这里如果配置不好，很容易出现404错误， # 此处也可以吧两个localtion里的root html/blog合成一个 location ~.*\\.(php|php5)?$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi.conf; index index.html index.htm; } # \u0026lt;==可将所有location里的root提出到外面。 原因：local中没有路径，要么加路径，要么提出最外面\r4 配置apache支持PHP 默认生成\nbash 1 2 3 4 5 6 $ grep libphp5 /app/apache/conf/httpd.conf LoadModule php5_module modules/libphp5.so $ ll /app/apache/modules/ 总用量 29620 -rw-r--r--. 1 root root 94月 8 03:02 httpd.exp -rwxr-xr-x. 1 root root 303164月 21 01:46 libphp5.so 修改311行\nbash 1 2 AddType application/x-httpd-php .php .phtml AddType application/x-httpd-php-source .phps 更改daemon，更改用户是为了安全考虑\n打不开解决方法：\nbash 1 2 3 4 $ lsof -i:80 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME httpd 5 root 4u IPv6 69 TCP *:http (LISTEN) httpd 60apache 4u IPv6 69 TCP *:http (LISTEN) bash 1 $ /app/apache/bin/apachectl restart ","permalink":"https://www.161616.top/php-installtation/","summary":"下载PHP 台湾镜像站：http://ftp.ntu.edu.tw/php/distributions/ 搜狐镜像站：http://mirrors.sohu.com/php/ 阿里镜像：http://mirrors.aliyun.com/ 官网：http://php.net/downloads.php 检查PHP所需的lib库 bash 1 2 3 4 5 6 7 8 9 10 11 rpm -qa \\ zlib-devel \\ libxml2-devel \\ libjpeg-devel \\ libjpeg-turbo-devel \\ libiconv-devel \\ freetype-devel \\ libpng-devel \\ gd-devel \\ libcurl-devel \\ libxslt-devel 提示：libjpeg-turbo-devel是早期libjpeg-devel的新名字，libcurl-devel是早期curl的新名字。\n每个lib一般都会存在对应的以“-devel”命名的包，安装lib对应的-devel包后，对应的lib包就会自动安装好，例如安装gd-devel时就会安装gd。\n这些lib库不是必须安装的，但是目前的企业环境下一般都需要安装。否则，PHP程序运行时会出现问题，例如验证码无法显示等。\n执行下面命令安装相关的lib软件包：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 yum install -y \\ zlib-devel \\ libxml2-devel \\ libjpeg-devel \\ libjpeg-turbo-devel \\ freetype-devel \\ libpng-devel \\ gd-devel \\ curl-devel \\ libxslt-devel \\ bzip2-devel \\ gmp-devel \\ readline-devel 提示：从安装上看，仅有libiconv-devel这个包没有安装，因为默认的yum源没有此包。可以一个一个地yum安装或通过源文件手工编译安装（这样效率慢）","title":"编译安装PHP"},{"content":"PHP缓存加速器介绍 操作码介绍及缓存原理 当客户端请求一个PHP程序时，服务器的PHP引擎会解析该PHP程序，并将其编译为特定的操作码（Operate Code，简称opcode）文件，该文件是执行PHP代码后的一种二进制表示形式。默认情况下，这个编译好的操作码文件由PHP引擎执行后丢弃。而操作码缓存（Opcode Cache）的原理就是将编译后的操作码保存下来，并放到共享内存里，以便在下一次调用该PHP页面时重用它，避免了相同代码的重复编译，节省了PHP引擎重复编译的时间，降低了服务器负载，同时减少了CPU和内存开销。\nPHP缓存加速软件介绍 为了提高PHP引擎的高并发访问及执行速度，产生了一系列PHP缓存加速软件。这些软件设计的目的就是缓存前文提到的PHP引擎解析过的操作码文件，以便在指定时间内有相同的PHP程序请求访问时，不再需要重复解析编译，而是直接调用缓存中的PHP操作码文件，这样就提高了动态Web服务的处理速度，从而提升了用户访问企业网站的整体体验。\nLAMP环境PHP缓存加速器的原理 下面简单介绍Apache环境的PHP缓存加速器原理。\n在LAMP环境中，Apache服务是使用libphp5.so响应处理PHP程序请求的，整个流程大概如下：\nApache接收客户的PHP程序请求，并根据规则过滤之。\nApache将PHP程序请求传递给PHP处理模块libphp5.so。\nPHP引擎定位磁盘上的PHP文件，并将其加载到内存中解析。\nPHP处理模块libphp5.so将PHP源代码编译成为opcode。\nPHP处理模块libphp5.so执行opcode，然后把opcode缓存起来。\nApache接收客户端新的PHP程序请求，PHP引擎直接读取缓存执行opcode文件，并将结果返回。在这一次任务中，就无第4步的编译解 析了，从而提升了PHP编译解析效率。\nPHP缓存加速器解决的是上述第5步的问题，默认情况下PHP会将opcode内容执行后丢弃，这里却通过PHP缓存加速软件，将opcode内容缓存了下来，目的是当有重复请求时，不需要再重复编译解析PHP程序代码，因为在高并发高访问量的网站上，大量的重复编译会消耗很多的系统资源和时间，而这也就会成为瓶颈，既影响了处理速度，又加重了服务器的负载，为了解决此问题，PHP缓存加速器就这样诞生了。\n图4-1是LAMP环境下PHP请求及操作码缓存过程的原理示意图\rLNMP环境PHP缓存加速器的原理详解 在LNMP环境中，PHP引擎不再使用libphp5.so模块了，而是启动了独立的FCGI即php-fpm进程，由它监听来自Nginx的PHP程序请求，并交给PHP引擎解析处理，整个执行流程大概如下：\nPHP缓存加速器软件种类及选择建议 PHP缓存加速器软件常见的种类有XCache、eAccelerator、APC（Alternative PHP Cache），ZendOpcache等，那么，在企业环境我们要如何选择PHP缓存加速器软件呢？\n事实上，任选其一即可，没必要都安装上，都安装也可能会发生冲突。总的建议就是根据企业的业务需求及选择前的压力测试结果，或者根据个人的经验偏好选择。不过，老男孩建议首选XCache，其次是eAccelerator，如果想尝新，可以选择ZendOpcache。\n首选XCache的原因如下：\n经过测试，XCache效率更高、速度更快。 XCache软件开发社区更活跃，最新版2014年底发布。 支持更高版本的PHP，例如PHP 5.5、PHP 5.6。 次选eAccelerator的原因如下：\n安装及配置参数更简单，加速效果也不错。 文档资料较多，但官方对软件的更新很慢，社区不活跃。 仅适合PHP版本5.4以下的程序。 选择ZendOpcache的原因如下：\n是PHP官方研发的新一代缓存加速软件，以后的发展潜力可能会很好，PHP 5.5以前的版本可以通过ZendOpcache软件以插件扩展的方式安装，从PHP 5.5版本开始已经整合到PHP软件里了，编译时只需指定一个参数即可，例如：\u0026ndash;enable-opcache。 ZendOpcache可能是未来的缓存加速首选，现在的稳定性还有待检验，小规模环境下PHP 5以前的版本可以通过插件式安装使用，PHP 5以上的版本可以直接指定参数编译使用。若可以忍受ZendOpcache的各种未知问题的话，也可以尝试使用。 安装PHP缓存加速器扩展 安装PHP eAccelerator缓存加速模块 eAccelerator缓存加速插件说明\neAccelerator是一个免费的、开放源代码的PHP加速、优化及缓存的扩展插件软件，它可以缓存PHP程序编译后的中间代码文件（opcode）、session数据等，降低PHP程序在编译解析时对服务器的性能开销。eAccelerator还可以加快PHP程序的执行速度，降低服务器负载压力，使PHP程序代码执行效率提高1~10倍。\neAccelerator会把编译好的PHP程序存放在共享内存里，然后每次从内存里调用执行，可以设定把一些不适合放在内存里缓存的编译结果存储到磁盘上，默认情况下，磁盘和内存缓存都会被eAccelerator使用。\neAccelerator的最新版为0.9.6.1，支持的PHP最新版本为PHP 5.3及以前5系列的版本。 早期的0.9.5版本支持PHP 4和PHP 5.2以前的版本。\neAccelerator下载地址为：https://github.com/eaccelerator/eaccelerator/downloads。\neAccelerator插件安装过程\n具体的安装命令集如下：\nbash 1 2 /app/php/bin/phpize ./configure --enable-eaccelerator=shared --with-php-config=/app/php/bin/php-config 安装PHP XCache缓存加速模块 XCache缓存加速插件说明\nXCache是一个开源的、又快又稳定的PHP opcode缓存器/优化器，其项目leader曾经是Lighttpd（和Nginx类似的高速Web服务软件）的开发成员之一。XCache把PHP程序编译后的数据（opcode）缓存到共享内存里，避免相同的程序重复编译。用户请求相同的PHP程序时，可以直接使用缓存中已编译好的数据，从而提高PHP的访问速度，通常可以提升2~5倍，并大幅降低服务器负载开销。\n很多公司使用XCache，它已经能在大流量/高负载的生产环境中稳定运行，与同类型的opcode缓存器相比在各个方面都更胜一筹，例如：社区活跃、快速开发、能够快速跟进PHP的版本更新等。 当前稳定版本为3.1.x（全面支持PHP 5.1~5.5）和3.2.x（2014年底发布，全面支持PHP 5.1~5.6）。\nXCache软件详情请参考：\nhttp://xcache.lighttpd.net\nhttp://xcache.lighttpd.net/wiki/Introduction。\nXCache插件的安装过程\nbash 1 2 3 /app/php/bin/phpize ./configure --enable-xcache --with-php-config=/application/php/bin/php-config ./configure --enable-xcache=shared --with-php-config=/app/php/bin/php-config PHP官方加速插件ZendOpcache ZendOpcache插件说明\n从PHP 5.5开始，官方已经集成了新一代的缓存加速插件，其名字为ZendOpcache，功能和前三者相似但又有少许不同，据官方说，ZendOpcache缓存速度更快。\n这几个PHP加速插件的主要原理基本相同，就是把PHP执行后的数据缓存到内存中从而避免重复的编译过程，使其能够直接使用缓存中已编译的代码，从而提高速度，降低服务器负载。它们的效率是显而易见的，\t一些大型的CMS，每次打开一个页面要调用数十个PHP文件，执行数万行代码，效率可想而知，安装上述加速器后，打开页面的速度明显加快。\nPHP 5.5以上版本，支持ZendOpcache很简单，只需在编译PHP 5.5时加上\u0026ndash;enable-opcache就行了。其实，在PHP5.5版本以前，ZendOpcache也有独立的软件，并且也支持低版本的PHP 5.2.*、PHP5.3.*、PHP 5.4.*。\n官方下载地址为：http://pecl.php.net/package/ZendOpcache。\nZendOpcache插件安装过程\n在PHP源码包ext目录下有一些扩展库，可直接安装，也可去官网下载扩展后安装\nbash 1 2 /application/php/bin/phpize ./configure --enable-opcache --with-php-config=/application/php/bin/php-config 最后需要在php.ini里开启opcache模块才可使用\nbash 1 2 3 4 5 6 7 8 9 [Zend Opcache] zend_extension = /app/php5.6.26/lib/php/extensions/no-debug-non-zts-20131226/opcache.so opcache.memory_consumption=64 opcache.interned_strings_buffer=8 opcache.max_accelerated_files=4000 opcache.force_restart_timeout=180 opcache.revalidate_freq=60 opcache.fast_shutdown=1 opcache.enable_cli=1 安装数据库缓存及其他PHP扩展插件 安装PHP Memcached扩展插件 图4-3是Memcached缓存架构逻辑图。\rPHP的Memcached扩展插件下载地址为：http://pecl.php.net/package/memcache。\n安装PDO_MYSQL扩展模块 PDO_MYSQL扩展插件说明\nPDO扩展为PHP访问数据库定义了一个轻量级一致性的接口，它提供了一个数据访问抽象层，这样，无论使用的是什么数据库，都可以通过一致的函数执行查询并获取数据。\nPDO_MYSQL扩展插件下载地址为：http://pecl.php.net/get/PDO_MYSQL-1.0.2.tgz。\n技巧：php源码包有或使用谷歌搜索关键字“PDO_MYSQL-1.0.2.tgz download”。\nPDO_MYSQL扩展插件的安装过程\nPDO_MYSQL的安装有两种方法，一种是插件方式安装，另一种是编译PHP时加入PDO_MYSQL支持，直接指定PHP的对应PDO_MYSQL编译参数安装，例如：--with-pdo-mysql=mysqlnd，同时PHP的环境也可以不装MySQL软件，直接指定如下参数--with-mysql=mysqlnd，即可让PHP支持连接MySQL数据库。\n安装其他的PHP扩展插件模块 安装ImageMagick图像软件 ImageMagick是一套功能强大、稳定而且免费的工具集和开发包，可以用来读、写和处理超过89种基本格式的图片文件，包括流行的tiff、jpeg、gif、png、pdf，以及PhotoCD等。利用ImageMagick，可以根据Web应用程序的需要动态生成图片，还可以对一个（或一组）图片进行改变大小、旋转、锐化、减色或增加特效等操作，并将操作的结果以相同格式或其他格式保存。对图片的操作，即可以通过命令行进行，也可以用C/C++、Perl、Java、PHP、Python或Ruby编程来完成。同时ImageMagick提供了一个高质量的2D工具包，部分支持SVG。现在，ImageMagic的主要精力集中在加强性能、减少bug，以及提供稳定的API和ABI上。\nImageMagick的常见功能如下：\n将图片从一个格式转换成另一个格式，包括直接转换成图标。 可以改变图片尺寸，旋转、锐化（sharpen）、减色，设置图片特效。 对图片设置各种尺寸缩略图。 将图片设置为可以适应于Web背景的透明图片。 将一组图片做成gif动画，直接convert。 将几张图片做成一张组合图片。 在一个图片上写字或画图形，带文字阴影和边框渲染。 给图片加边框或框架。 取得一些图片的特性信息。 它几乎包括了gimp可以实现的所有常规插件功能，甚至包括各种曲线参数的渲染功能。ImageMagick的下载地址为：\nhttp://pecl.php.net/package/imagick\nhttps://www.imagemagick.org/download/\nImageMagick安装报错及解决方法。 以下错误是书上写的。我在两天centos6 与centos7共计4台测试并未发现任何错误\n问题1：make步骤出错。 示例如下：\nbash 1 2 3 4 5 6 7 cd PerlMagick \u0026amp;\u0026amp; /usr/bin/perl Makefile.PL Can\u0026#39;t locate ExtUtils/MakeMaker.pm in @INC(@INC contains:/usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at Makefile.PL line 24. BEGIN failed--compilation aborted at Makefile.PL line 24. make[1]:*** [PerlMagick/Makefile] Error 2 make[1]:Leaving directory `/home/oldboy/tools/ImageMagick-6.5.1-2\u0026#39; make:*** [all] Error 2 可以看到，上述内容中有Makefile.PL、/usr/lib64/perl5/vendor_perl和Perl语言的字样，因此可以试着使用 yum install perl-devel -y 命令安装相关包，看看是否可以解决问题。\n安装imagick PHP扩展插件 imagick插件工作需要ImageMagick软件的支持，所以，必须要先安装ImageMagick，否则会报错。\nimagick插件是一个可以供PHP调用ImageMagick功能的扩展模块。使用这个扩展可以使PHP具备和ImageMagick相同的功能。\n安装了ImageMagick图像程序后，再安装PHP的扩展imagick插件，才能使用ImageMagick提供的api进行图片的创建与修改、压缩等操作，因为它们都集成在imagick这个PHP扩展中。\nbash 1 2 3 ./configure \\ --with-php-config=/app/php/bin/php-config \\ --with-imagick=/app/imagemag/\t#←如果编译安装的ImageMagick需要指定安装路径 配置PHP加速与缓存相关的扩展插件模块 配置xcache/PDO_MYSQL/imagick模块生效 修改PHP的配置文件php.ini\n修改php.ini的配置文件过程如下：\n查找extension_dir=\u0026quot;./\u0026quot;参数，修改为extension_dir=\u0026quot;/app/php5.3.27/lib/php/extensions/no-debug-non-zts-20090626/\u0026quot;，这个extension_dir对应的路径就是前文编译的模块所在的路径。\n⚠ 提示：默认的PHP配置文件路径为 /app/php/lib/php.ini ，可以通过在编译PHP时添加参数指定php.ini的配置路径 --with-config-file-path=/application/php5.3.27/lib/etc ，如果不指定编译路径，默认为 /application/php/lib/ 。\n在vim命令模式下按Shift+G键跳到文件结尾，增加如下几行，然后保存：\nbash 1 2 3 4 extension = memcache.so extension = pdo_mysql.so .. extension = imagick.so 配置eAccelerator插件生效并优化参数 1 配置eAccelerator缓存目录\n配置命令1：配置eAccelerator缓存目录：\nbash 1 2 mkdir -p /tmp/eaccelerator #←此目录可以用tmpfs内存文件系统或SSD固态硬盘来存储。 chown -R nginx.nginx /tmp/eaccelerator #←chown后的用户是nginx的用户 配置命令2：配置eAccelerator参数，命令如下：\nbash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [eaccelerator] extension=eaccelerator.so eaccelerator.shm_size=\u0026#34;64\u0026#34; eaccelerator.cache_dir=\u0026#34;/tmp/eaccelerator\u0026#34; eaccelerator.enable=\u0026#34;1\u0026#34; eaccelerator.optimizer=\u0026#34;1\u0026#34; eaccelerator.check_mtime=\u0026#34;1\u0026#34; eaccelerator.debug=\u0026#34;0\u0026#34; eaccelerator.filter=\u0026#34;\u0026#34; eaccelerator.shm_max=\u0026#34;0\u0026#34; eaccelerator.shm_ttl=\u0026#34;3600\u0026#34; eaccelerator.shm_prune_period=\u0026#34;3600\u0026#34; eaccelerator.shm_only=\u0026#34;0\u0026#34; eaccelerator.compress=\u0026#34;1\u0026#34; eaccelerator.compress_level=\u0026#34;9\u0026#34; 2 eAccelerator配置参数的详细说明。\neaccellerator 解释说明 [eaccelerator] 开始eAccelerator加速模块配置 extension 加载eaccelerator加速模块，路径相对于extension_dir的配置 eaccelerator.shm_size 存储缓存数据的共享内存大小，如果为0，则最大值看内核配置/proc/sys/kernel/shmmax eaccelerator.cache_dir=\u0026quot;/tmp/eaccelerator\u0026quot; 磁盘缓存存储路径，缓存内容为precompiled code、session、data、content和user entries。默认路径为\u0026quot;tmp/eaccelerator\u0026quot; eaccelerator.enable=\u0026ldquo;1\u0026rdquo; 加速PHP代码执行速度，1为默认值，表示激活；0为不激活。用于缓存前的代码加速 eaccelerator.check_mtime=\u0026ldquo;1\u0026rdquo; 检查缓存修改时间，决定代码是否需要重新编译，1为激活，是默认值 eaccelerator.debug=\u0026ldquo;0\u0026rdquo; 缓存加速调试，0为关闭，1为打开，打开后可以看到缓存命中信息 eaccelerator.filter=\u0026quot;\u0026quot; 设定对象是否缓存规则，空表示不设定 eaccelerator.shm_max=\u0026ldquo;0\u0026rdquo; 可以被放置的缓存最大值，0是不限制 eaccelerator.shm_ttl=\u0026ldquo;3600\u0026rdquo; 缓存文件的生存期 eaccelerator.shm_prune_period=3600\u0026quot; 当共享内存空间不够时，从共享内存中移除老数据的时间周期 eaccelerator.shm_only=\u0026ldquo;0\u0026rdquo; 是否允许缓存数据到磁盘，0为允许，但是对于session data and content caching无影响 eaccelerator.compress=\u0026ldquo;1\u0026rdquo; 是否开启压缩，1为开启 eaccelerator.compress_leve=\u0026ldquo;9\u0026rdquo; 压缩级别，9为最高 根据内容指定是否缓存到共享内存或磁盘的其他参数：\nbash 1 2 3 4 5 6 7 8 eaccelerator.keys=\u0026#34;shm_and_disk\u0026#34; #←控制keys缓存位置 eaccelerator.sessions=\u0026#34;shm_and_disk\u0026#34; #←控制sessions缓存位置 eaccelerator.content=\u0026#34;shm_and_disk\u0026#34; #←控制内容缓存位置上述3个参数可选的值为： shm_and_disk：cache data in shared memory and on disk(default value) shm:cache data in shared memory or on disk if shared memory is full or data size greater then \u0026#34;eaccelerator.shm_max\u0026#34; shm_only:cache data in shared memory disk_only:cache data on disk none:don\u0026#39;t cache data 更多信息请参考 https://github.com/eaccelerator/eaccelerator/wiki/Settings。\n3 访问PHP页面测试检查eAccelerator加速情况\nbash 1 2 3 4 5 6 $ find /tmp/eaccelerator/ -type f /tmp/eaccelerator/48/0/1/eaccelerator-01aa58b81ae5ff3ed966dbbac55535a8 /tmp/eaccelerator/48/0/2/eaccelerator-02399225c2489318da660dc2213a940e ... /tmp/eaccelerator/48/0/3/eaccelerator-03621af70cbe37e82c125a39bdb8c0b9 /tmp/eaccelerator/48/0/3/eaccelerator-03ad092ef38eaae48de869a58a893a16 4 使用tmpfs优化eAccelerator缓存目录\ntmpfs是一种基于内存的文件系统，通常使用tmpfs作为数据临时存储，比本地磁盘存储快很多，此方法适用于临时使用的各类缓存场景。例如：上传图片时很多软件默认在/tmp下临时缓存切图、存放session数据，则可以让/tmp使用tmpfs文件系统来加快访问效率。\nbash 1 mount -t tmpfs -o size=16m tmpfs /tmp/eaccelerator 配置XCache插件加速 XCache配置文件参数\n参数 说明 [xcache-common] extension=xcacheso 加载xcache.so,路径相对于extension_dir的配置。自3.0版本开始不再支持使用zend_extension加载XCache的方式 [xcache.admin]xcache.admin.enable_auth=On 激活管理员认证 xcache.admin.user=\u0026ldquo;mOo\u0026rdquo;xcache.admin.pass=\u0026ldquo;md5encrypted password\u0026rdquo; 指定XCache管理员用户名和密码.密码根据http://xcache. lighttpd.net/demo/cacher/mkpassword php 地址产生，留空表示禁止管理页面 [xcache] 开始XCache缓存参数配置段.下面所有的初始值即为默认值，除非明确说明 xcache.shm_scheme=\u0026ldquo;mmap\u0026rdquo; 设置XCache如何从系统分配共享内存 xcache.size = 60M 0为禁止缓存，非0则启动缓存。需要注意系统所允许的mmap最大值 xcachc.count = 1 指定将Cache切分成多少块，官方方推荐设置为服务器CPU的数量grep -c processor /proc/cpuinfo 1 xcache.slols=8K hash槽个数的参考值.缓冲超过此数值时也没有任何问题（you can always store count(items) \u0026gt; slots) xcache.gc_interval = 0 回收器扫描过期的对象回收内存空间的间隔.0为不扫描，其他值的单位是秒 xcache.var_size = 4M\nxcache.var_count = 1 xcache.var_slots = 8K\nxcache.var_ttl = 0 xcache.var_gc_interval =300 这几个值和上面的几个类似，只不过用于变量缓存，而不是 opcode缓存 ;N/A for /dev/zero xcache.readonly_proteciion = off 如果启用了该参数.将略微降低性能，但会提高一定的安全系数。 这个选项对于 xcache.mmap_path = /dev/zero 无效 xcache.mmap_path=\u0026quot;/dev/zero\u0026quot; 对于nix, xcache.mmap_path是一个文件路径而非目录。如果要启 用该参数，请使用\u0026quot;/tmp/xcache\u0026quot;这样的路径，而不是\u0026quot;/dev/\u0026quot;。如 果开启了 xcache.readonly_protection参数，不同进程组的PHP将不会共享同一个/tmp/xcache路径 xcache.coredump_directory=\u0026quot;\u0026quot; 当XCache crash后，适否把数据保存到指定路径 xcachc.disable_on_crash =off 当XCache发生crash时，自动动关闭XCache缓存 更多参数请参考官方文档：http://xcache.lighttpd.net/wiki/XcacheIni\n编辑xcache.ini，修改XCache的配置参数\nbash 1 2 3 4 5 xcache.size=256M xcache.count=2 xcache.ttl=86# 24小时 xcache.gc_interval=3600 xcache.var_size=64M 将修改后的xcache.ini合并到php.ini结尾。\nbash 1 cat /home/oldboy/tools/xcache-3.2.0/xcache.ini \u0026gt;\u0026gt;php.ini 检查XCache加速情况配置\nbash 1 2 3 4 5 6 7 8 Warning: Module \u0026#39;XCache\u0026#39; already loaded in Unknown on line 0 # 出现这样的提示，应该是加载xcache原因 ，取消后提示消失 /app/php/bin/php -v PHP 5.5.20 (cli) (built: Oct 16 2016 21:28:19) Copyright (c) 1997-2014 The PHP Group Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies with XCache v3.2.0, Copyright (c) 2005-2014, by mOo with XCache Cacher v3.2.0, Copyright (c) 2005-2014, by mOo 复制XCache软件下面的缓存加速管理PHP程序到站点目录下\nbash 1 2 cp -r ~/tools/xcache-3.2.0/htdocs /var/html/www/xadmin chown -R www.www /var/html/www/xadmin 编辑php.ini文件，xcache.admin模块配置如下\nbash 1 2 3 4 [xcache.admin] xcache.admin.enable_auth = On xcache.admin.user = \u0026#34;admin\u0026#34; xcache.admin.pass = \u0026#34;111\u0026#34; MD5加密可用如下方法生成\nbash 1 2 3 4 $ echo 111|md5sum 1181c1834012245d785120e3505ed169 - #←这里生成的并不是md5生成的111，因为echo默认有换行 $ echo -n 111|md5sum 698d51a19d8a121ce581499d7b701668 - 配置ZendOpcache插件加速 1 配置ZendOpcache参数\n在php.ini里添加如下配置：\nbash 1 2 3 4 5 6 7 8 9 10 11 [opcache] extension=opcache.so #←这种方法不能用了 zend_extension=/application/php5.3.27/lib/php/extensions/no-debug-non-zts- 20090626/opcache.so; extension=opcache.so opcache.memory_consumption=32 opcache.interned_strings_buffer=8 opcache.max_accelerated_files=1000 opcache.revalidate_freq=60 opcache.fast_shutdown=1 opcache.enable_cli=1 2 ZendOpcache配置参数说明\n下表Opcache的部分重要参数进行了说明。\nOPcache参数解释说明 opcache.memoiy_consumption=128 OPcache共享内存空间大小，用于存放precompiled PHP code，默认为64，单位为Mbytes opcache.interned_strings_buffer=8 默认依为4,interned strings内存的数量,单位是M opcache.max_accelerated_files=4000 默认值为2000, OPcache散列表的key的最大数量 opcache.revalidate_freq=60 默认值为2,检查文件时间戳的频率，用于共享内存分配的变化 opcache.fast_shutdown=l 默认值为0,如果激活,一个快速的关闭队列将被用来加速代码 opcache.enable_cli=l 默认值为0,激活PHP CLI的OPcache, 于测试和调试 更多的OPcache参数可以查看安装目录下的README 说明：ZendOpcache是PHP官方的新一代的缓存加速软件，PHP5.5以前可以通过ZendOpcache软件以插件扩展的方式安装，从 ==PHP5.5== 版本开始已经整合到PHP软件里，编译时只需指定一个参数即可，例如： --enable-opcache。\nZendOpcache可能是未来的首选，现在的稳定性还有待检验。在小规模环境下，PHP 5以上的版本可以使用。如果可以忍受其未知的问题也可以使用。\n生产环境PHP扩展插件的安装建议 1 PHP的安装插件表格列表\nPHP EXT module 说明 eaccelerator0.9.5.2 适合PHP5.3以前的版本，PHP缓存加速 eaccelerator-0.9.6 适合PHP5.3版本，PHP缓存加速 ImageMagick 常用图像处理程序，属功能应用 imagick 需要先装图像处理程序，属功能应用 memcache memcache客户端数据库缓存优化应用 memcached 基于libmemcache客户端，性能较memcache更好，高并发首选 PDO_MYSQL PHP数据库访问插件，属功能应用 xcache 支持PHP5.1-5.6，PHP缓存加速 zend opcache zend官方PHP缓存加速插件 2 生产环境插件的安装建议\n对于功能性插件，如果业务产品不需要使用，可以暂时不考虑安装，例如\nPDO_MYSQL\\memcache\\imagick 等。如果不清楚是否需要，最好还是装上，有备无患。\n对于性能优化插件，eAccelerator、XCache、ZendOpcache、APC可以安装任一种，具体情况看实际业务需求，在选择时最好能搭建相关环境进行压力测试，然后根据实际测试结果来选择，用数据说话很重要。\n3 PHP加速插件的测试对比\neAccelerator不支持5.3以上版本\nxcache支持7.0以下版本。\nPHP缓存加速压力测试练习 分别安装ZendOpcache、eacc、XCache缓存加速插件，通过压测软件对比三者的缓存效率。 测试方法：\n不装任何加速插件和分别安装某一个缓存加速软件。 可用压力测试软件webbench、loadruner。 用压力测试方法，通过数据看看到底哪个加速器好 ","permalink":"https://www.161616.top/php-install-cache-accelerator/","summary":"PHP缓存加速器介绍 操作码介绍及缓存原理 当客户端请求一个PHP程序时，服务器的PHP引擎会解析该PHP程序，并将其编译为特定的操作码（Operate Code，简称opcode）文件，该文件是执行PHP代码后的一种二进制表示形式。默认情况下，这个编译好的操作码文件由PHP引擎执行后丢弃。而操作码缓存（Opcode Cache）的原理就是将编译后的操作码保存下来，并放到共享内存里，以便在下一次调用该PHP页面时重用它，避免了相同代码的重复编译，节省了PHP引擎重复编译的时间，降低了服务器负载，同时减少了CPU和内存开销。\nPHP缓存加速软件介绍 为了提高PHP引擎的高并发访问及执行速度，产生了一系列PHP缓存加速软件。这些软件设计的目的就是缓存前文提到的PHP引擎解析过的操作码文件，以便在指定时间内有相同的PHP程序请求访问时，不再需要重复解析编译，而是直接调用缓存中的PHP操作码文件，这样就提高了动态Web服务的处理速度，从而提升了用户访问企业网站的整体体验。\nLAMP环境PHP缓存加速器的原理 下面简单介绍Apache环境的PHP缓存加速器原理。\n在LAMP环境中，Apache服务是使用libphp5.so响应处理PHP程序请求的，整个流程大概如下：\nApache接收客户的PHP程序请求，并根据规则过滤之。\nApache将PHP程序请求传递给PHP处理模块libphp5.so。\nPHP引擎定位磁盘上的PHP文件，并将其加载到内存中解析。\nPHP处理模块libphp5.so将PHP源代码编译成为opcode。\nPHP处理模块libphp5.so执行opcode，然后把opcode缓存起来。\nApache接收客户端新的PHP程序请求，PHP引擎直接读取缓存执行opcode文件，并将结果返回。在这一次任务中，就无第4步的编译解 析了，从而提升了PHP编译解析效率。\nPHP缓存加速器解决的是上述第5步的问题，默认情况下PHP会将opcode内容执行后丢弃，这里却通过PHP缓存加速软件，将opcode内容缓存了下来，目的是当有重复请求时，不需要再重复编译解析PHP程序代码，因为在高并发高访问量的网站上，大量的重复编译会消耗很多的系统资源和时间，而这也就会成为瓶颈，既影响了处理速度，又加重了服务器的负载，为了解决此问题，PHP缓存加速器就这样诞生了。\n图4-1是LAMP环境下PHP请求及操作码缓存过程的原理示意图\rLNMP环境PHP缓存加速器的原理详解 在LNMP环境中，PHP引擎不再使用libphp5.so模块了，而是启动了独立的FCGI即php-fpm进程，由它监听来自Nginx的PHP程序请求，并交给PHP引擎解析处理，整个执行流程大概如下：\nPHP缓存加速器软件种类及选择建议 PHP缓存加速器软件常见的种类有XCache、eAccelerator、APC（Alternative PHP Cache），ZendOpcache等，那么，在企业环境我们要如何选择PHP缓存加速器软件呢？\n事实上，任选其一即可，没必要都安装上，都安装也可能会发生冲突。总的建议就是根据企业的业务需求及选择前的压力测试结果，或者根据个人的经验偏好选择。不过，老男孩建议首选XCache，其次是eAccelerator，如果想尝新，可以选择ZendOpcache。\n首选XCache的原因如下：\n经过测试，XCache效率更高、速度更快。 XCache软件开发社区更活跃，最新版2014年底发布。 支持更高版本的PHP，例如PHP 5.5、PHP 5.6。 次选eAccelerator的原因如下：\n安装及配置参数更简单，加速效果也不错。 文档资料较多，但官方对软件的更新很慢，社区不活跃。 仅适合PHP版本5.4以下的程序。 选择ZendOpcache的原因如下：\n是PHP官方研发的新一代缓存加速软件，以后的发展潜力可能会很好，PHP 5.5以前的版本可以通过ZendOpcache软件以插件扩展的方式安装，从PHP 5.5版本开始已经整合到PHP软件里了，编译时只需指定一个参数即可，例如：\u0026ndash;enable-opcache。 ZendOpcache可能是未来的缓存加速首选，现在的稳定性还有待检验，小规模环境下PHP 5以前的版本可以通过插件式安装使用，PHP 5以上的版本可以直接指定参数编译使用。若可以忍受ZendOpcache的各种未知问题的话，也可以尝试使用。 安装PHP缓存加速器扩展 安装PHP eAccelerator缓存加速模块 eAccelerator缓存加速插件说明\neAccelerator是一个免费的、开放源代码的PHP加速、优化及缓存的扩展插件软件，它可以缓存PHP程序编译后的中间代码文件（opcode）、session数据等，降低PHP程序在编译解析时对服务器的性能开销。eAccelerator还可以加快PHP程序的执行速度，降低服务器负载压力，使PHP程序代码执行效率提高1~10倍。\neAccelerator会把编译好的PHP程序存放在共享内存里，然后每次从内存里调用执行，可以设定把一些不适合放在内存里缓存的编译结果存储到磁盘上，默认情况下，磁盘和内存缓存都会被eAccelerator使用。\neAccelerator的最新版为0.9.6.1，支持的PHP最新版本为PHP 5.3及以前5系列的版本。 早期的0.9.5版本支持PHP 4和PHP 5.2以前的版本。\neAccelerator下载地址为：https://github.com/eaccelerator/eaccelerator/downloads。\neAccelerator插件安装过程\n具体的安装命令集如下：\nbash 1 2 /app/php/bin/phpize ./configure --enable-eaccelerator=shared --with-php-config=/app/php/bin/php-config 安装PHP XCache缓存加速模块 XCache缓存加速插件说明\nXCache是一个开源的、又快又稳定的PHP opcode缓存器/优化器，其项目leader曾经是Lighttpd（和Nginx类似的高速Web服务软件）的开发成员之一。XCache把PHP程序编译后的数据（opcode）缓存到共享内存里，避免相同的程序重复编译。用户请求相同的PHP程序时，可以直接使用缓存中已编译好的数据，从而提高PHP的访问速度，通常可以提升2~5倍，并大幅降低服务器负载开销。\n很多公司使用XCache，它已经能在大流量/高负载的生产环境中稳定运行，与同类型的opcode缓存器相比在各个方面都更胜一筹，例如：社区活跃、快速开发、能够快速跟进PHP的版本更新等。 当前稳定版本为3.","title":"配置PHP插件"},{"content":"默认生成\nbash 1 2 3 4 5 6 7 $ grep libphp5 /app/apache/conf/httpd.conf LoadModule php5_module modules/libphp5.so $ ll /app/apache/modules/ 总用量 29620 -rw-r--r--. 1 root root 9115 4月 8 03:02 httpd.exp -rwxr-xr-x. 1 root root 30316906 4月 21 01:46 libphp5.so 修改httpd配置文件311行\ntext 1 2 AddType application/x-httpd-php .php .phtml AddType application/x-httpd-php-source .phps 更改daemon，更改用户是为了安全考虑\n常用的httpd支持php的编译参数\ntext 1 2 3 4 ./configure --enable-xcache=shared --with-php-config=/app/php/bin/php-config ./configure --enable-eaccelerator=shared --with-php-config=/app/php/bin/php-config ./configure --with-php-config=/app/php/bin/php-config --with-pdo-mysql=/app/mysql ./configure --with-php-config=/app/php/bin/php-config --with-imagick=/app/imagemag/ sysconfdir 指定php配置文件路径\n","permalink":"https://www.161616.top/apache-php/","summary":"默认生成\nbash 1 2 3 4 5 6 7 $ grep libphp5 /app/apache/conf/httpd.conf LoadModule php5_module modules/libphp5.so $ ll /app/apache/modules/ 总用量 29620 -rw-r--r--. 1 root root 9115 4月 8 03:02 httpd.exp -rwxr-xr-x. 1 root root 30316906 4月 21 01:46 libphp5.so 修改httpd配置文件311行\ntext 1 2 AddType application/x-httpd-php .php .phtml AddType application/x-httpd-php-source .phps 更改daemon，更改用户是为了安全考虑\n常用的httpd支持php的编译参数\ntext 1 2 3 4 ./configure --enable-xcache=shared --with-php-config=/app/php/bin/php-config ./configure --enable-eaccelerator=shared --with-php-config=/app/php/bin/php-config ./configure --with-php-config=/app/php/bin/php-config --with-pdo-mysql=/app/mysql ./configure --with-php-config=/app/php/bin/php-config --with-imagick=/app/imagemag/ sysconfdir 指定php配置文件路径","title":"配置apache httpd支持php"},{"content":"关于Linux服务管理 Linux系统从启动到提供服务的过程是这样，先是机器加电，然后通过MBR或者UEFI加载GRUB，再启动内核，内核启动服务，然后开始对外服务。 SysV init UpStart systemd主要是解决服务引导管理的问题。\nCentOS 5：SysV init CentOS 6：Upstart CentOS 7：Systemd http://www.linuxidc.com/Linux/2015-04/115937.htm 1.1 SysV init的优缺点 SysV init是最早的解决方案，依靠划分不同的运行级别，启动不同的服务集，服务依靠脚本控制，并且是顺序执行的。\nSysV init方案的优点：\n1.原理简单，易于理解； 2.依靠shell脚本控制，编写服务脚本门槛比较低。 缺点是：\n1.服务顺序启动，启动过程比较慢。 2.不能做到根据需要来启动服务，比如通常希望插入U盘的时候，再启动USB控制的服务，这样可以更好的节省系统资源。 1.2 UpStart的改进 为了解决系统服务的即插即用，UpStart应运而生，在CentOS6系统中，SysV init和UpStart是并存的，UpStart主要解决了服务的即插即用。服务顺序启动慢的问题，UpStart的解决办法是把相关的服务分组，组内的服务是顺序启动，组之间是并行启动。\n1.3 systemd的诞生 SysV init服务启动慢，在以前并不是一个问题，尤其是Linux系统以前主要是在服务器系统上，常年也难得重启一次。有的服务器光硬件检测都需要5分钟以上，相对来说系统启动已经很快了。\n但是随着移动互联网的到来，SysV init服务启动慢的问题显得越来越突出，许多移动设备都是基于Linux内核，比如安卓。移动设备启动比较频繁，每次启动都要等待服务顺序启动，显然难以接受，systemd就是为了解决这个问题诞生的。\nsystemd的设计思路是：\n尽可能的快速启动服务。 尽可能的减少系统资源占用。 1.4 为什么systemd能做到启动很快 systemd使用并行的方法启动服务，不像SysV init是顺序执行的，所以大大节省了系统启动时间。\n使用并行启动，最大的难点是要解决服务之间的依赖性，systemd的解决办法是使用类似缓冲池的办法。比如对TCP有依赖的服务，在启动的时候会检查依赖服务的TCP端口，systemd会把对TCP端口的请求先缓存起来，当依赖的服务器启动之后，在将请求传递给服务，使两个服务通讯。同样的进程间通讯的D-BUS也是这样的原理，目录挂载则是先让服务以为目录被挂载了，到真正访问目录的时候，才去真正操作。\nsystemd的特性 systemd解决了那些问题？\n按需启动服务，减少系统资源消耗； 尽可能并行启动进程，减少系统启动等待时间； 提供一个一致的配置环境，不光是服务配置； 提供服务状态快照，可以恢复特定点的服务状态。 CentOS 7的systemd特性 3.1 套接字服务保持激活功能 在系统启动的时候，systemd为所有支持套接字激活功能的服务创建监听端口，当服务启动后，就将套接字传给这些服务。这种方式不仅可以允许服务在启动的时候平行启动，也可以保证在服务重启期间，试图连接服务的请求，不会丢失。对服务端口的请求被保留，并且存放到队列中。\n3.2 进程间通讯保持激活功能 当有客户端应用第一次通过D-Bus方式请求进程间通讯时，systemd会立即启动对应的服务。systemd依据D-Bus的配置文件使用进程间通讯保持激活功能。\n3.3 设备保持激活功能 当特定的硬件插入时，systemd启动对应的硬件服务支持。systemd依据硬件服务单元配置文件保持硬件随时被激活。\n3.4 文件路径保持激活功能 当特定的文件或者路径状态发生改变的时候，systemd会激活对应的服务。systemd依据路径服务单元配置文件保证服务被激活。\n3.5 系统状态快照 systemd可以临时保存当前所有的单元配置文件，或者从前一个快照中恢复单元配置文件。为了保存当前系统服务状态，systemd可以动态的生成单元文件快照。\n3.6 挂载和自动挂载点管理 systemd监控和管理挂载和自动挂载点，并根据挂载点的单元配置文件进行挂载。\n3.7 闪电并行启动 因为使用套接字保持激活功能，systemd可以并行的启动所以套接字监听服务，大大减少系统启动时间。\n3.8 单元逻辑模拟检查 当激活或者关闭一个单元，systemd会计算依赖行，产生一个临时的模拟检查，并且校验一直性。如果不一致，systemd会尝试自动修正，并且移除报错的不重要的任务。\n3.9 和SysV init向后兼容 systemd完全支持SysV init Linux标准的基础核心规范脚本，这样的脚本易于升级到systemd服务单元。\n核心概念:unit 4.1 什么是单元 在RHEL7之前，服务管理是分布式的被SysV init或UpStart通过 /etc/rc.d/init.d 下的脚本管理。这些脚本是经典的Bash脚本，允许管理员控制服务的状态。在RHEL7中，这些脚本被服务单元文件替换。\n在systemd中，服务、挂载等资源统一被称为单元，所以systemd中有许多单元类型，服务单元文件的扩展名是.service，同脚本的功能相似。例如有查看、启动、停止、重启、启用或者禁止服务的参数。\n配置文件进行标识和配置：文件中主要包含了系统服务、监听socket、保存的系统快照及其他与init相关的信息。\nsystemd单元文件放置位置：\nsh 1 2 3 /usr/lib/systemd/system/systemd\t# 默认单元文件安装目录 /run/systemd/system\t# 单元运行时创建，这个目录优先于安装目录 /etc/systemd/system\t# 系统管理员创建和管理的单元目录，优先级最高。 4.2 Unit类型 类型 详解- Service unit 文件扩展名为service，用于定义系统服务。 Target unit 文件扩展名为.target，用于模拟实现“运行级别” Device unit 文件扩展名为.device，用于定义内核识别的设备。 Mount unit 文件扩展名为.mount，定义文件系统挂载点 Socket unit 文件扩展名为.socket，用于表示进程间通信用的socket文件 Snapshot unit 文件扩展名为.sanpshot，管理系统快照 Swap unit 文件扩展名为.swap，用于表示swap设备 Automount unit 文件扩展名为.automount，文件系统的自动挂载点 Path unit 文件扩展名为.path，用于定义文件系统中的一个文件或目录 .service 与服务对应的后缀名为service的unit、文件，无需执行权限，仅仅为systemd的配置文件。当systemd探测到有进程访问时，按需激活这个服务的机制，任何依赖与这个服务的其他服务想启动的话，\n服务的并行启动：\n.device 在某个硬件设备被激活或变得可用时，从而激活服务\n.path：某个文件路径变得可用或里面有文件时（文件发生变动）激活某个服务\n系统快照：\nsystemd能将所有unit当前状态保存到临时文件中（临时保存到一个持久设备上）。启动时，可从保存的快照开始继续向后运行。 必要时能自动载入。\n向后兼容：\nsysV init脚本。（能够兼容 start、stop restart status至少这4个服务脚本）以前启动服务的脚本放到centos7里直接可以用。\nCentOS 7的systemd向后兼容 systemd被设计成尽可能向后兼容SysV init和Upstart，下面是一些特别要注意的和之前主要版本的RHEL不再兼容的部分。\n5.1 systemd对运行级别支持有限 为了保存兼容，systemd提供有限target单元，“模拟”一些运行级别，也可以被早期的分布式的运行级别命令支持。不是所有的target都可以被映射到运行级别，在这种情况下，使用runlevel命令有可能会返回一个为N的不知道的运行级别，所以推荐尽量避免在RHEL7中使用runlevel命令。\n5.2 systemd不支持像init脚本那样的个性化命令。 除了一些标准命令参数例如：start、stop、status，SysV init脚本可以根据需要支持想要的任何参数，通过参数提供附加的功能，因为SysV init的服务器脚本实际上就是shell脚本，命令参数实际上就是shell子函数。\n举个例子，RHEL6的iptables服务脚本可以执行panic命令行参数，这个参数可以让系统立即进入紧急模式，丢弃所有的进入和发出的数据包。但是类似这样的命令行参数在systemd中是不支持的，systemd只支持在配置文件中指定命令行参数。\n5.3 systemd不支持和没有从systemd启动的服务通讯。 当systemd启动服务的时候，他保存进程的主ID以便于追踪，systemctl工具使用进程PID查询和管理服务。相反的，如果用户从命令行启动特定的服务，systemctl命令是没有办法判断这个服务的状态是启动还是运行的。\n5.4 systemd可以只停止运行的服务 在RHEL6及之前的版本，当关闭系统的程序启动之后，RHEL6的系统会执行/etc/rc0.d/下所有服务脚本的关闭操作，不管服务是处于运行或者根本没有运行的状态。而systemd可以做到只关闭在运行的服务，这样可以大大节省关机的时间。\n5.5 不能从标准输出设备读到系统服务信息。 systemd启动服务的时候，将标准输出信息定向到/dev/null，以免打扰用户。\n5.6 systemd不继承任何上下文环境。 systemd不继承任何上下文环境，如用户或者会话的HOME或者PATH的环境变量。每个服务得到的是干净的上下文环境。\n5.7 SysV init脚本依赖性 当systemd启动SysV init脚本，systemd在运行的时候，从LinuxStandardBase(LSB)Linux标准库头文件读取服务的依赖信息并继承。\n5.8 超时机制 为了防止系统被卡住，所有的服务有5分钟的超时机制。\nsystemd服务管理 使用systemcl命令可以控制服务，service命令和chkconfig命令依然可以使用，但是主要是出于兼容的原因，应该尽量避免使用service命令和chkconfig命令。\n使用systemctl命令的时候，服务名字的扩展名可以写全，例如：\nsh 1 systemctl stop httpd.service 也可以忽略，例如：\nsh 1 systemctl stop httpd 6.1 常用命令 6.2 服务管理 说明 命令 启动服务 service name start ==\tsystemctl start name.service 停止服务 service name stop\t== systemctl stop name.service 重启服务 service name restart\t== systemctl restart name.service 查看服务状态 service name status == systemctl status name.service 条件式重启 service name condrestart == systemctl try-restart name.service 重载或重启服务 systemctl reload-or-restart name.service 重载或条件式重启 systemctl reload-or-try-restart name.service 禁止设定为开机自启动 systemctl mask name.service 取消设定为开机自启动 systemctl unmask name.service 查看服务当前激活与否的状态 systemctl is-active name.service 允许服务开机启动 systemctl enable name.service 禁止服务开机启动 systemclt disable name.service 级别切换 systemctl list-units \u0026ndash;type target 获取默认运行级别 systemctl get-default 修改默认级别 systemctl set-default name.service 切换至紧急救援模式 systemctl rescue 切换至emergency模式 systemctl emergency 关机 systemctl halt systemctl poweroff 重启 systemctl reboot 挂起 systemctl suspend 快照 systemctl hibernate 快照并挂起 systemctl hybrid-sleep 6.3 查看服务详细信息 查看所有已激活的服务。默认只列出处于激活状态的服务，如果希望看到所有的服务，使用\u0026ndash;all或-a参数：\nsh 1 systemctl list-units --type service 查看所有的服务\nsh 1 systemctl list-units --type service --all 检查服务开机启动状态\nsh 1 2 systemctl status name.service systemctl is-enabled name.service 列出所有服务并且检查是否开机启动\nsh 1 systemctl list-unit-files --type service 选项重新加载所以单元文件并重新创建依赖书，在需要立即应用单元文件改变的时候使用。另外，也可以使用init q的命令达到同样的目的。还有，如果修改的是一个正在运行服务的单元文件，服务需要被重启下：\nsh 1 2 systemct lrestart name.service systemctl daemon-reload 查看服务依赖关系\nsh 1 systemctl list-dependencies systemd target 在RHEL7之前的版本，使用运行级别代表特定的操作模式。运行级别被定义为七个级别，用数字0到6表示，每个级别可以启动特定的一些服务。RHEL7使用target替换运行基本。\nsystemd target使用target单元文件描述，target单位文件扩展名是.target，target单元文件的唯一目标是将其他systemd单元文件通过一连串的依赖关系组织在一起。举个例子，graphical.target单元，用于启动一个图形会话，systemd会启动像GNOME显示管理(gdm.service)、帐号服务（axxounts-daemon）这样的服务，并且会激活multi-user.target单元。相似的multi-user.target单元，会启动必不可少NetworkManager.service、dbus.service服务，并激活basic.target单元。\nRHEL7预定义了一些target和之前的运行级别或多或少有些不同。为了兼容，systemd也提供一些target映射为SysV init的运行级别，具体的对应信息如下：\n代码 命令 说明 0 runlevel0.target,poweroff.targe 关闭系统。 1 runlevel1.target,rescue.target 进入救援模式。 2 runlevel2.target,multi-user.target 进入非图形界面的多用户方式。 3 runlevel3.target,multi-user.target 进入非图形界面的多用户方式。 4 runlevel4.target,multi-user.target 进入非图形界面的多用户方式。 5 runlevel5.target,graphical.target 进入图形界面的多用户方式。 6 runlevel6.target,reboot.target 重启系统。 注：对于systemd来说 234没有区别\n7.1 target管理 使用如下命令查看目前可用的target：\nsh 1 systemctl list-units --type target 改变当前的运行基本使用如下命令：\nsh 1 2 3 4 systemctl isolate name.target systemctl isolate rescue.target $ runlevel 1 3 7.2 修改默认的运行级别 使用systemctl get-default命令得到默认的运行级别：\nsh 1 2 $ systemctl get-default multi-user.target 使用systemctl set-default name.target修改默认的运行级别：\nsh 1 2 3 4 systemctl set-default graphical.target # 可以看到。默认级别就是操作如下两步骤 rm \u0026#39;/etc/systemd/system/default.target\u0026#39; ln-s\u0026#39;/usr/lib/systemd/system/graphical.target\u0026#39;\u0026#39;/etc/systemd/system/default.target\u0026#39; 使用 Target 的时候，systemctl list-dependencies命令和systemctl isolate命令也很有用。 查看 multi-user.target 包含的所有服务\nsh 1 systemctl list-dependencies multi-user.target 一般来说，常用的 Target 有两个：一个是multi-user.target，表示多用户命令行状态；另一个是graphical.target，表示图形用户状态，它依赖于multi-user.target。官方文档有一张非常清晰的 Target 依赖关系图。\n7.3 Target 的配置文件 Target 也有自己的配置文件。\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 [Unit] Description=Multi-User System Documentation=man:systemd.special(7) Requires=basic.target\t# Requires字段：要求basic.target一起运行。 # 冲突字段。如果rescue.service或rescue.target正在运行，multi-user.target就不能运行，反之亦然。 Conflicts=rescue.service rescue.target # 表示multi-user.target在basic.target 、 rescue.service、 rescue.target之后启动，如果它们有启动的话。 After=basic.target rescue.service rescue.target # 允许使用systemctl isolate命令切换到multi-user.target。 AllowIsolate=yes 注意，Target 配置文件里面没有启动命令。\n7.4 救援模式和紧急模式 使用进入救援模式，如果连救援模式都进入不了，可以进入紧急模式：\nsh 1 2 systemctl rescue # 救援模式（进入救援模式，如果连救援模式都进入不了，可以进入紧急模式） systtmctl emergency # 紧急模式 紧急模式进入最小的系统环境，以便于修复系统。紧急模式根目录以只读方式挂载，不激活网络，只启动很少的服务，进入紧急模式需要root密码。\n关闭、暂停、休眠系统 RHEL7中，使用systemctl替换一些列的电源管理命令，原有的命令依旧可以使用，但是建议尽量不用使用。systemctl和这些命令的对应关系为：\n说明 SysV/Upstart system 停止系统（关机） halt systemctl hatl 关闭系统，关闭系统电源 poweroff systemctl poweroff 重启系统 reboot systemctl reboot 暂停系统 pm-suspend systemctl suspend 休眠系统 pm-hibernate systemct lhibernate 暂停并休眠系统 pm-suspend-hybrid systemctl hybrid-sleep 通过systemd管理远程系统 不光是可以管理本地系统，systemd还可以控制远程系统，管理远程系统主要是通过SSH协议，只有确认可以连接远程系统的SSH，在systemctl命令后面添加-H或者\u0026ndash;host参数，加上远程系统的ip或者主机名就可以。\n创建和修改systemd单元文件 10.1 单元文件概述 一个服务怎么启动，完全由它的配置文件决定。下面就来看，配置文件有些什么内容。\n配置文件主要放在/usr/lib/systemd/system目录，也可能在/etc/systemd/system目录。找到配置文件以后，使用文本编辑器打开即可。\n下面以sshd.service文件为例，它的作用是启动一个 SSH 服务器，供其他用户以 SSH 方式登录。\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [Unit] Description=OpenSSH server daemon Documentation=man:sshd(8) man:sshd_config(5) After=network.target sshd-keygen.service Wants=sshd-keygen.service [Service] EnvironmentFile=/etc/sysconfig/sshd ExecStart=/usr/sbin/sshd -D $OPTIONS ExecReload=/bin/kill -HUP $MAINPID Type=simple KillMode=process Restart=on-failure RestartSec=42s [Install] WantedBy=multi-user.target sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ systemctl status rsyncd rsyncd.service - fast remote file copy program daemon Loaded: loaded (/usr/lib/systemd/system/rsyncd.service; disabled) # loaded 服务已经被加载，显示单元文件绝对路径，标志单元文件可用。 # disabled表示开机不允许启动Status服务的附件信息。 Active: active (running) since 四 2017-01-26 21:59:41 CST; 5min ago # active表示当前状态 从什么时间被激活 Main PID: 1360 (rsync) # main pid 与进程名字一致的PID，主进程PID。进程可能有多个进程可能有一组 CGroup: /system.slice/rsyncd.service └─1360 /usr/bin/rsync --daemon --no-detach # cgroup表示资源组，启动命令是/usr/bin/rsync --daemon --no-detach 1月 26 21:59:41 lnmp systemd[1]: Starting fast remote file copy program daemon... 1月 26 21:59:41 lnmp systemd[1]: Started fast remote file copy program daemon. $ systemctl status rsyncd rsyncd.service - fast remote file copy program daemon Loaded: loaded (/usr/lib/systemd/system/rsyncd.service; disabled) Active: inactive (dead)/ 上面的输出结果含义如下:\u0026quot;\u0026quot;\nLoaded行：配置文件的位置，是否设为开机启动 Active行：表示正在运行 Main PID行：主进程ID Status行：由应用本身（这里是 httpd ）提供的软件当前状态 Cgroup块：应用的所有子进程 日志块：应用的日志 10.2 理解单元文件结构 10.3 单元文件概述 可以看到，配置文件分成几个区块，每个区块包含若干条键值对。 典型的单元文件包含三节：\n[Unit]：包含不依赖单元类型的一般选项，这些选型提供单元描述，知道单元行为，配置单元和其他单元的依赖性。\n[unittype]：如果单元有特定的类型指令，在unittype节这些指令被组织在一起。举个例子，服务单元文件包含[Service]节，里面有经常使用的服务配置。\n[Install]：包含systemctl enable或者disable的命令安装信息。\n10.3.1 [Unit]节选项 字段 说明 Description 字段给出当前服务的简单描述 Documentation 给出文档位置。 启动顺序 注意:After和Before字段只涉及启动顺序，不涉及依赖关系 After 表示sshd.service应该在network.target sshd-keygen.service之后启动。 Before 定义sshd.service应该在哪些服务之前启动。 举例来说，某 Web 应用需要 postgresql 数据库储存数据。在配置文件中，它只定义要在 postgresql 之后启动，而没有定义依赖 postgresql 。上线后，由于某种原因，postgresql 需要重新启动，在停止服务期间，该 Web 应用就会无法建立数据库连接。 设置依赖关系，需要使用Wants字段和Requires字段。 Wants 表示sshd.service与sshd-keygen.service之间存在 \u0026ldquo;弱依赖\u0026rdquo; 关系，即如果\u0026quot;sshd-keygen.service\u0026quot;启动失败或停止运行，不影响sshd.service继续执行。 Requires 表示 \u0026ldquo;强依赖\u0026rdquo; 关系，即如果该服务启动失败或异常退出，那么sshd.service也必须退出。 注意，Wants字段与Requires字段只涉及依赖关系，与启动顺序无关，默认情况下是同时启动的。 10.3.2 [Service] 区块：启动行为 Service区块定义如何启动当前服务。\n启动命令\n许多软件都有自己的环境参数文件，该文件可以用EnvironmentFile字段读取。\nEnvironmentFile字段：指定当前服务的环境参数文件。该文件内部的key=value键值对，可以用$key的形式，在当前配置文件中获取。\r上面的例子中，sshd 的环境参数文件是/etc/sysconfig/sshd。\n配置文件里面最重要的字段是ExecStart。\nExecStart：定义启动进程时执行的命令。\r上面的例子中，启动sshd，执行的命令是/usr/sbin/sshd -D $OPTIONS，其中的变量$OPTIONS就来自EnvironmentFile字段指定的环境参数文件。\n与之作用相似的，还有如下这些字段：\n选项 说明 ExecStart 指定启动单元的命令或者脚本，ExecStartPre和ExecStartPost节指定在ExecStart之前或者之后用户自定义执行的脚本。Type=oneshot允许指定多个希望顺序执行的用户自定义命令。 ExecStop 指定单元停止时执行的命令或者脚本。 ExecReload 指定单元重新加载是执行的命令或者脚本。 ExecStartPre 启动服务之前执行的命令 ExecStartPost 启动服务之后执行的命令 ExecStopPost 停止服务之后执行的命令 Restart 这个选项如果被允许，服务重启的时候进程会退出，会通过systemctl命令执行清除并重启的操作。 RemainAfterExit 如果设置这个选择为真，服务会被认为是在激活状态，即使所以的进程已经退出，默认的值为假，这个选项只有在Type=oneshot时需要被配置。 请看下面的例子。\nsh 1 2 3 4 5 6 [Service] ExecStart=/bin/echo execstart1 ExecStart= ExecStart=/bin/echo execstart2 ExecStartPost=/bin/echo 1 ExecStartPost=/bin/echo 2 上面这个配置文件，第二行ExecStart设为空值，等于取消了第一行的设置，运行结果如下。\nsh 1 2 3 4 5 6 7 8 9 10 $ systemctl start test $ systemctl status test test.service - test daemon Loaded: loaded (/usr/lib/systemd/system/test.service; disabled) Active: inactive (dead) 1月 30 07:11:16 lnmp systemd[1]: Starting test daemon... 1月 30 07:11:16 lnmp systemd[1]: Started test daemon. 1月 30 07:11:16 lnmp echo[1822]: test1_start 1月 30 07:11:16 lnmp echo[1824]: test1_stop 所有的启动设置之前，都可以加上一个连词号（-），表示\u0026ldquo;抑制错误\u0026rdquo;，即发生错误的时候，不影响其他命令的执行。比如，EnvironmentFile=-/etc/sysconfig/sshd（注意等号后面的那个连词号），就表示即使/etc/sysconfig/sshd 文件不存在，也不会抛出错误。\n启动类型\nType字段定义启动类型。它可以设置的值如下。\n选项值 说明 simple 默认值，ExecStart字段启动的进程为主进程 forking 进程作为服务主进程的一个子进程启动，父进程在完全启动之后退出。 oneshot 类似于simple，但只执行一次，进程在启动单元之后随之退出。 dbus 类似于simple，但随着单元启动后只有主进程得到D-BUS名字。 notify 类似于simple，启动结束后会发出通知信号，然后 Systemd 再启动其他服务 idle 类似于simple，但是要等到其他任务都执行完，才会启动该服务。一种使用场合是为让该服务的输出，不与其他服务的输出相混合 下面是一个oneshot的例子，笔记本电脑启动时，要把触摸板关掉，配置文件可以这样写。\nsh 1 2 3 4 5 6 7 8 9 [Unit] Description=Switch-off Touchpad [Service] Type=oneshot ExecStart=/usr/bin/touchpad-off [Install] WantedBy=multi-user.target 上面的配置文件，启动类型设为oneshot，就表明这个服务只要运行一次就够了，不需要长期运行。\n如果关闭以后，将来某个时候还想打开，配置文件修改如下。\nsh 1 2 3 4 5 6 7 8 9 10 11 [Unit] Description=Switch-off Touchpad [Service] Type=oneshot ExecStart=/usr/bin/touchpad-off start ExecStop=/usr/bin/touchpad-off stop RemainAfterExit=yes [Install] WantedBy=multi-user.target 上面配置文件中，RemainAfterExit字段设为yes，表示进程退出以后，服务仍然保持执行。这样的话，一旦使用systemctl stop命令停止服务，ExecStop指定的命令就会执行，从而重新开启触摸板。\n重启行为\nService区块有一些字段，定义了重启行为。\nsh 1 KillMode字段：定义 Systemd 如何停止 sshd 服务。 上面这个例子中，将KillMode设为process，表示只停止主进程，不停止任何sshd 子进程，即子进程打开的 SSH session 仍然保持连接。这个设置不太常见，但对sshd很重要，否则你停止服务的时候，会连自己打开的SSH session一起杀掉。\nKillMode字段可以设置的值如下：\n选项值 说明 control-group （默认值）\t当前控制组里面的所有子进程，都会被杀掉 process 只杀主进程 mixed 主进程将收到 SIGTERM 信号，子进程收到 SIGKILL 信号 none 没有进程会被杀掉，只是执行服务的 stop 命令。 Restart字段。\nRestart字段：定义了 sshd 退出后，Systemd 的重启方式。\n上面的例子中，Restart设为on-failure，表示任何意外的失败，就将重启sshd。如果 sshd 正常停止（比如执行systemctl stop命令），它就不会重启。\nRestart字段可以设置的值如下。\n选项值 说明 no 默认值；退出后不会重启 on-success 只有正常退出时（退出状态码为0），才会重启 on-failure 非正常退出时（退出状态码非0），包括被信号终止和超时，才会重启 on-abnormal 只有被信号终止和超时，才会重启 on-abort 只有在收到没有捕捉到的信号终止时，才会重启 on-watchdog 超时退出，才会重启 always 不管是什么退出原因，总是重启 对于守护进程，推荐设为on-failure。对于那些允许发生错误退出的服务，可以设为on-abnormal。\nRestartSec字段。\nRestartSec字段：表示 Systemd 重启服务之前，需要等待的秒数。上面的例子设为等待42秒。\n[Install] 区块\r\u0026nbsp;说明：Install区块，定义如何安装这个配置文件，即怎样做到开机启动。\rWantedBy字段：表示该服务所在的 Target。\rTarget的含义是服务组，表示一组服务。WantedBy=multi-user.target指的是，sshd 所在的 Target 是multi-user.target。\n这个设置非常重要，因为执行systemctl enable sshd.service命令时，sshd.service的一个符号链接，就会放在/etc/systemd/system目录下面的multi-user.target.wants子目录之中。\nSystemd 有默认的启动 Target。\nsh 1 2 $ systemctl get-default multi-user.target 上面的结果表示，默认的启动 Target 是multi-user.target。在这个组里的所有服务，都将开机启动。这就是为什么systemctl enable命令能设置开机启动的原因。\n修改配置文件后重启\n修改配置文件以后，需要重新加载配置文件，然后重新启动相关服务。\nsh 1 2 # 重新加载配置文件 systemctl daemon-reload 10.4 一个postfix服务的例子： 单元文件位于/usr/lib/systemd/system/postifix.service，内容如下：\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [Unit] Description=PostfixMailTransportAgent After=syslog.targetnetwork.target Conflicts=sendmail.serviceexim.service [Service] Type=forking PIDFile=/var/spool/postfix/pid/master.pid EnvironmentFile=-/etc/sysconfig/network ExecStartPre=-/usr/libexec/postfix/aliasesdb ExecStartPre=-/usr/libexec/postfix/chroot-update ExecStart=/usr/sbin/postfixstart ExecReload=/usr/sbin/postfixreload ExecStop=/usr/sbin/postfixstop [Install] WantedBy=multi-user.target 10.5 创建自定义的单元文件 以下几种场景需要自定义单元文件：\n希望自己创建守护进程； 为现有的服务创建第二个实例； 引入SysV init脚本。 另外一方面，有时候需要修改已有的单元文件。下面介绍创建单元文件的步骤：\n准备自定义服务的执行文件。 可执行文件可以是脚本，也可以是软件提供者的的程序，如果需要，为自定义服务的主进程准备一个PID文件，一保证PID保持不变。另外还可能需要的配置环境变量的脚本，确保所以脚本都有可执行属性并且不需要交互。\n2.在/etc/systemd/system/目录创建单元文件，并且保证只能被root用户编辑\nsh 1 2 touch /etc/systemd/system/mariadb.service chmod 644 /etc/systemd/system/mariadb.service 注：文件不需要执行权限。\n打开name.service文件，添加服务配置，各种变量如何配置视所添加的服务类型而定，下面是一个依赖网络服务的配置例子： sh 1 2 3 4 5 6 7 8 9 10 11 12 13 [Unit] Description=mariadb multi demo 3306 After=network.target [Service] ExecStart=/data/3306/mysql start ExecReload=/data/3306/mysql restart ExecStop=/data/3306/mysql stop Type=forking PIDFile=/data/3306/mysqld.pid [Install] WantedBy=multi-user.target 4.通知systemd有个新服务添加：\nsh 1 2 systemctl daemon-reload systemctl start name.service 10.5 创建第二个sshd服务的例子\r1.拷贝sshd_config文件\nsh 1 2 cp /etc/ssh/sshd{,-second}_config # {,second} 等于 和second ，类似与 {a,c}的用法 2.编辑sshd-second_config文件，添加22220的端口，和PID文件：\ntext 1 2 Port 22220 PidFile /var/run/sshd-second.pid 如果还需要修改其他参数，请阅读帮助。\n3.拷贝单元文件：\nsh 1 cp /usr/lib/systemd/system/sshd{,-second}.service 4.编辑单元文件sshd-second.service\nsh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [Unit] Description=OpenSSH server second instance daemon After=syslog.target network.targe tauditd.service sshd.service [Service] EnvironmentFile=/etc/sysconfig/sshd ExecStart=/usr/sbin/sshd -D -f /etc/ssh/sshd-second_config $OPTIONS ExecReload=/bin/kill -HUP $MAINPID KillMode=process Restart=on-failure RestartSec=42s [Install] WantedBy=multi-user.target 5.如果使用SELinux，添加tcp端口，负责第二sshd服务的端口就会被拒绝绑定：\nsh 1 semanage port -a -tssh_port_t -p tcp22220 6.设置开机启动并测试：\nsh 1 2 systemctl enable sshd-second.service ssh -p 22220 user@server 10.6 修改已经存在的单元文件 systemd unit配置文件默认保存在/usr/lib/systemd/system/目录，不建议直接修改这个目录下的文件，自定义的文件在/etc/systemd/system/目录下，如果有扩展的需求，可以使用以下方案：\n创建一个目录/etc/systemd/system/unit.d/，这个是最推荐的一种方式，可以参考初始的单元文件，通过附件配置文件来扩展默认的配置，对默认单元文件的升级会被自动升级和应用。\n从/usr/lib/systemd/system/拷贝一份原始配置文件到/etc/systemd/system/，然后修改。复制的版本会覆盖原始配置，这种方式不能增加附件的配置包，用于不需要附加功能的场景。\n如果需要恢复到默认的配置文件，只需要删除/etc/systemd/system/下的配置文件就可以了，不需要重启机器。\nReference [CentOS7/RHEL7 systemd详解](CentOS7/RHEL7 systemd详解)\nsystemd.index 中文手册\nSystemd 入门教程：实战篇\n","permalink":"https://www.161616.top/systemd/","summary":"关于Linux服务管理 Linux系统从启动到提供服务的过程是这样，先是机器加电，然后通过MBR或者UEFI加载GRUB，再启动内核，内核启动服务，然后开始对外服务。 SysV init UpStart systemd主要是解决服务引导管理的问题。\nCentOS 5：SysV init CentOS 6：Upstart CentOS 7：Systemd http://www.linuxidc.com/Linux/2015-04/115937.htm 1.1 SysV init的优缺点 SysV init是最早的解决方案，依靠划分不同的运行级别，启动不同的服务集，服务依靠脚本控制，并且是顺序执行的。\nSysV init方案的优点：\n1.原理简单，易于理解； 2.依靠shell脚本控制，编写服务脚本门槛比较低。 缺点是：\n1.服务顺序启动，启动过程比较慢。 2.不能做到根据需要来启动服务，比如通常希望插入U盘的时候，再启动USB控制的服务，这样可以更好的节省系统资源。 1.2 UpStart的改进 为了解决系统服务的即插即用，UpStart应运而生，在CentOS6系统中，SysV init和UpStart是并存的，UpStart主要解决了服务的即插即用。服务顺序启动慢的问题，UpStart的解决办法是把相关的服务分组，组内的服务是顺序启动，组之间是并行启动。\n1.3 systemd的诞生 SysV init服务启动慢，在以前并不是一个问题，尤其是Linux系统以前主要是在服务器系统上，常年也难得重启一次。有的服务器光硬件检测都需要5分钟以上，相对来说系统启动已经很快了。\n但是随着移动互联网的到来，SysV init服务启动慢的问题显得越来越突出，许多移动设备都是基于Linux内核，比如安卓。移动设备启动比较频繁，每次启动都要等待服务顺序启动，显然难以接受，systemd就是为了解决这个问题诞生的。\nsystemd的设计思路是：\n尽可能的快速启动服务。 尽可能的减少系统资源占用。 1.4 为什么systemd能做到启动很快 systemd使用并行的方法启动服务，不像SysV init是顺序执行的，所以大大节省了系统启动时间。\n使用并行启动，最大的难点是要解决服务之间的依赖性，systemd的解决办法是使用类似缓冲池的办法。比如对TCP有依赖的服务，在启动的时候会检查依赖服务的TCP端口，systemd会把对TCP端口的请求先缓存起来，当依赖的服务器启动之后，在将请求传递给服务，使两个服务通讯。同样的进程间通讯的D-BUS也是这样的原理，目录挂载则是先让服务以为目录被挂载了，到真正访问目录的时候，才去真正操作。\nsystemd的特性 systemd解决了那些问题？\n按需启动服务，减少系统资源消耗； 尽可能并行启动进程，减少系统启动等待时间； 提供一个一致的配置环境，不光是服务配置； 提供服务状态快照，可以恢复特定点的服务状态。 CentOS 7的systemd特性 3.1 套接字服务保持激活功能 在系统启动的时候，systemd为所有支持套接字激活功能的服务创建监听端口，当服务启动后，就将套接字传给这些服务。这种方式不仅可以允许服务在启动的时候平行启动，也可以保证在服务重启期间，试图连接服务的请求，不会丢失。对服务端口的请求被保留，并且存放到队列中。\n3.2 进程间通讯保持激活功能 当有客户端应用第一次通过D-Bus方式请求进程间通讯时，systemd会立即启动对应的服务。systemd依据D-Bus的配置文件使用进程间通讯保持激活功能。\n3.3 设备保持激活功能 当特定的硬件插入时，systemd启动对应的硬件服务支持。systemd依据硬件服务单元配置文件保持硬件随时被激活。\n3.4 文件路径保持激活功能 当特定的文件或者路径状态发生改变的时候，systemd会激活对应的服务。systemd依据路径服务单元配置文件保证服务被激活。\n3.5 系统状态快照 systemd可以临时保存当前所有的单元配置文件，或者从前一个快照中恢复单元配置文件。为了保存当前系统服务状态，systemd可以动态的生成单元文件快照。\n3.6 挂载和自动挂载点管理 systemd监控和管理挂载和自动挂载点，并根据挂载点的单元配置文件进行挂载。","title":"Linux服务管理 - systemd"},{"content":"rsyslog介绍 syslog守护进程，内部有两个进程，syslogd主要负责用户空间的用户进程记录日志；klog负责内核所发生的各种时间记录日志。两者合并后形成syslog。\nrsyslog是syslog下一代升级产品，依然有syslogd klogd提供服务。\nrsyslog可以开通远程机制监听在某个套接字上，其他任何主机所产生的日志信息由本机的rsyslog收集起来，收集完后不负责记录，而是建立一个tcp或udp连接发送给专门的日志服务器，由专门的日志服务器负责记录。默认情况下是明文的。\nrsyslog特性 多线程。 支持UDP,TCP协议，基于ssl tls加密完成远程日志传输。支持RELP协议 实现将日志存储到MySQL PGSQL等关系型数据库中。 强大的过滤器，可实现过滤日志信息中任何部分，支持自定义输出格式。 日志格式 事件产生的事件 主机 进程pid 事件\nbash 1 2 3 Jun 6 23:36:58 Lamp-02 NET[1838]: /etc/sysconfig/network-scripts/ifup-post: updated /etc/resolv.conf Jun 6 23:46:15 Lamp-02 yum[1963]: Updated: mysql-libs-5.1.73-8.el6_8.x86_64 Jun 6 23:46:16 Lamp-02 yum[1963]: Installed: mysql-5.1.73-8.el6_8.x86_64 有些日志记录二进制日志 /var/log/wtmp /var/log/btmp\nlast：/var/log/wtmp 当前系统中成功登陆的日志\nlastb：/var/log/btmp 当前系统中失败的登陆尝试\nlastlog：显示当前系统每一个用户最近一次登陆时间\n日志等级 日志级别：事件的关键性程度\nlev\t|说明\nlev 说明 none 不记录 debug 调试信息 info 正常信息，仅是一些基本信息说明 notice 比info还需要注意的一些信息内容 warning,warn 警告信息，可能有些问题，但是还不至于影响到某个服务运作的信息 err,error 一些重大的错误信息 crit 临界状态，比error还要严重的错误信息，橙色警报 alert 红色警报，已经很有问题的等级，比crit还要严重 emerg,panic 疼痛等级，意指系统已经要宕机的状态！很严重的错误信息 设施类型 facility：把某一类具有相同特性的由各个应用程序所产生的日志数据流归类到用一个数据收集管道中，这个收集管道称之为facility。\n类型 说明 auth(authpriv) 与认证有关的机制，例如login ssh su等需要账号密码 cron 例行性工作调度cron/at等生成信息日志的地方 daemon 与各个daemon有关的信息。 kern 内核产生信息地方 lpr 打印相关信息 mail 邮件收发有关的信息 news 新闻组服务器有关信息 syslog 自身产生日志 user 用户 security 与安全相关信息 local0-local7 用户自定义8个设置 通配机制 通配符 说明 . 代表【比后面还要高的等级（含该等级）都被记录下来】 例如：mail.info代表只要是mail信息，而且该信息等级高于info（含info）时，就会被记录下来。 .= 代表所需要的等级就是后面接的等级而已，其他的不要。例如：main.=info代表的只要是mail信息，而且该信息等于info级别，就会被记录下来。 .! 代表不等于（取反），亦是除了该等级外的其他等级都记录。 * 所有；例如：*.info代表所有设施的info级别 none 不记录 日志的输出位置 位置 说明 文件 /var/log/messages 打印机或其他设备 /dev/lp0这个打印机装置 使用者名称 显示给用户，*代表目前在线所有的人 远程主机 @10.0.0.2 远程日志服务器，@代表udp协议，@@代表tcp协议 管道 |command bash 1 2 3 4 5 6 7 8 9 10 11 12 $ rpm -ql rsyslog /etc/logrotate.d/syslog /etc/pki/rsyslog /etc/rsyslog.conf \u0026lt;==配置文件 /etc/rsyslog.d /etc/sysconfig/rsyslog /usr/bin/rsyslog-recover-qi.pl /usr/lib/systemd/system/rsyslog.service \u0026lt;==单元文件 /usr/lib64/rsyslog /usr/lib64/rsyslog/imdiag.so \u0026lt;==收集日志接受日志流输入时的输入过滤工具 /usr/lib64/rsyslog/omjournal.so \u0026lt;== /var/lib/rsyslog rsyslog配置文件详解 rsyslog配置文件分为3个模块， 每段的配置必须严格写在#### xxx ####位置内\nbash 1 2 3 4 5 6 $ grep \u0026#39;##\u0026#39; /etc/rsyslog.conf #### MODULES #### #\u0026lt;==加载的模块 #### GLOBAL DIRECTIVES #### #\u0026lt;==定义日志格式默认模板 #### RULES #### #\u0026lt;==转发规则 # ### begin forwarding rule ### # ### end of the forwarding rule ### 常用参数 bash 1 2 $ModLoad imklog #\u0026lt;==加载模块 mail.* -/var/log/maillog #\u0026lt;==将mail所有类型的日志异步写入maillog文件中 rsyslog案例 设置ssh日志为其他设施 修改ssh配置文件 bash 1 2 #SyslogFacility AUTHPRIV SyslogFacility local1 修改rsyslog配置文件 将ssh日志写入到ssh.log中\nbash 1 local1.* -/var/log/ssh.log 使用ssh登陆查看日志生成结果\nbash 1 2 $ cat /var/log/ssh.log Jun 7 00:15:17 Lamp-02 sshd[2966]: Accepted password for root from 192.168.2.1 port 57670 ssh2 将日志记录到远程服务器 在客户端配置\nbash 1 *.info;mail.none;authpriv.none;cron.none;local0.none; @192.168.2.82 服务端开启配置\nbash 1 2 $ModLoad imudp $UDPServerRun 514 查看服务端的日志记录情况\nbash 1 2 3 4 5 $ cat /var/log/messages Jun 2 05:47:59 lnmp yum[128171]: Updated: httpd-tools-2.4.6-45.el7.centos.4.x86_64 Jun 2 05:48:03 lnmp yum[128171]: Updated: httpd-2.4.6-45.el7.centos.4.x86_64 Jun 2 05:48:03 lnmp systemd: Reloading. Jun 2 05:48:04 lnmp systemd: Configuration file /usr/lib/systemd/system/auditd.service is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway. 基于MySQL存储日志 将日志存储到数据库中需要加载相对应的模块\nbash 1 2 3 4 5 6 7 8 rsyslog.x86_64 5.8.10-10.el6_6 base rsyslog-gnutls.x86_64 5.8.10-10.el6_6 base rsyslog-gssapi.x86_64 5.8.10-10.el6_6 base rsyslog-mysql.x86_64 5.8.10-10.el6_6 base rsyslog-pgsql.x86_64 5.8.10-10.el6_6 base rsyslog-relp.x86_64 5.8.10-10.el6_6 base rsyslog-snmp.x86_64 5.8.10-10.el6_6 base ... 安装rsyslog程序包 bash 1 2 3 4 $ rpm -ql rsyslog-mysql /lib64/rsyslog/ommysql.so /usr/share/doc/rsyslog-mysql-5.8.10 /usr/share/doc/rsyslog-mysql-5.8.10/createDB.sql 配置服务器端参数 bash 1 2 $Modload ommysql *.info;mail.none;authpriv.none;cron.none :ommysql:localhost,Syslog,syslog,111 导入数据库并授权 sql 1 2 mysql \u0026lt; /usr/share/doc/rsyslog-mysql-5.8.10/createDB.sql grant all privileges on syslog.* to syslog@\u0026#39;localhost\u0026#39; identified by \u0026#39;111\u0026#39;; 查看结果\nsql 1 2 3 4 5 6 7 mysql\u0026gt; select count(*) from systemevents; +----------+ | count(*) | +----------+ | 3 | +----------+ 1 row in set (0.00 sec) 安装loganalyzer 下载 loganalyzer\nbash 1 2 3 sh configure.sh sh secure.sh chmod 666 config.php ","permalink":"https://www.161616.top/syslog/","summary":"rsyslog介绍 syslog守护进程，内部有两个进程，syslogd主要负责用户空间的用户进程记录日志；klog负责内核所发生的各种时间记录日志。两者合并后形成syslog。\nrsyslog是syslog下一代升级产品，依然有syslogd klogd提供服务。\nrsyslog可以开通远程机制监听在某个套接字上，其他任何主机所产生的日志信息由本机的rsyslog收集起来，收集完后不负责记录，而是建立一个tcp或udp连接发送给专门的日志服务器，由专门的日志服务器负责记录。默认情况下是明文的。\nrsyslog特性 多线程。 支持UDP,TCP协议，基于ssl tls加密完成远程日志传输。支持RELP协议 实现将日志存储到MySQL PGSQL等关系型数据库中。 强大的过滤器，可实现过滤日志信息中任何部分，支持自定义输出格式。 日志格式 事件产生的事件 主机 进程pid 事件\nbash 1 2 3 Jun 6 23:36:58 Lamp-02 NET[1838]: /etc/sysconfig/network-scripts/ifup-post: updated /etc/resolv.conf Jun 6 23:46:15 Lamp-02 yum[1963]: Updated: mysql-libs-5.1.73-8.el6_8.x86_64 Jun 6 23:46:16 Lamp-02 yum[1963]: Installed: mysql-5.1.73-8.el6_8.x86_64 有些日志记录二进制日志 /var/log/wtmp /var/log/btmp\nlast：/var/log/wtmp 当前系统中成功登陆的日志\nlastb：/var/log/btmp 当前系统中失败的登陆尝试\nlastlog：显示当前系统每一个用户最近一次登陆时间\n日志等级 日志级别：事件的关键性程度\nlev\t|说明\nlev 说明 none 不记录 debug 调试信息 info 正常信息，仅是一些基本信息说明 notice 比info还需要注意的一些信息内容 warning,warn 警告信息，可能有些问题，但是还不至于影响到某个服务运作的信息 err,error 一些重大的错误信息 crit 临界状态，比error还要严重的错误信息，橙色警报 alert 红色警报，已经很有问题的等级，比crit还要严重 emerg,panic 疼痛等级，意指系统已经要宕机的状态！很严重的错误信息 设施类型 facility：把某一类具有相同特性的由各个应用程序所产生的日志数据流归类到用一个数据收集管道中，这个收集管道称之为facility。","title":"Linux日志管理 - syslog"},{"content":"NFS介绍 什么是NFS NFS是 Network File System 网络文件系统。它的主要功能是通过网络（一般是局域网）让不同的主机系统之间可以共享文件或目录。NFS客户端（一般为应用服务器，例如Web）可以通过挂载（mount）的方式将NFS服务器端共享的数据目录挂载到NFS客户端本地系统中（就是某一个挂载点下）。从客户端本地看，NFS服务器端共享的目录就好像是客户端自己的磁盘分区或者目录一样，而实际上却是远端的NFS服务器的目录。\n一般情况下，Windows网络共享服务或samba服务用于办公局域网共享，而互联网中小型网站集群架构后端常用NFS进行数据共享，如果是大型网站，那么有可能还会用到更复杂的分布式文件系统，例如：Moosefs（mfs）、GlusterFS、FastDFS。\nNFS的历史介绍 第一个网络文件系统被称为File Access Listener，由Digital Equipment Corporation（DEC）在1976年开发。\nNFS是第一个构建于IP协议之上的现代网络文件系统。在20世纪80年代，它首先作为实验的文件系统，由Sun Microsystems在内部完成开发。NFS协议归属于Request for Comments（RFC）标准，并且随后演化为NFSv2。作为一个标准，由于NFS与其他客户端和服务器的互操作能力很好而发展快速。\n之后，标准继续演化，成为NFSv3，在RFC1813中有定义。这一新的协议比以前的版本具有更好的可扩展性，支持大文件（超过2GB），异步写入，并且将TCP作为传输协议，为文件系统在更广泛的网络中使用铺平了道路。在2000年，RFC 3010（由RFC 3530修订）将NFS带入企业级应用。此时，Sun引入了具有较高安全性、带有状态协议的NFSv4（NFS之前的版本都是无状态的）。今天，NFS版本的4.1（由RFC 5661定义）增加了对跨越分布式服务器并行访问的支持（称为PNFS extension）。\nNFS系统已经历了近30年的发展。它代表了一个非常稳定的（及可移植）网络文件系统，具备可扩展、高性能等特性，并达到了企业级应用质量标准。由于网络速度的增加和延迟的降低，NFS系统一直是通过网络提供文件系统服务的有竞争力的选择，特别是在中小型互联网企业中，应用十分广泛。\nNFS在企业中的应用场景 在企业集群架构的工作场景中，NFS网络文件系统一般被用来存储共享视频、图片、附件等静态资源文件，通常网站用户上传的文件都会放到NFS共享里，例如：BBS产品的图片、附件、头像（注意网站BBS程序不要放在NFS共享里），然后前端所有的节点访问这些静态资源时都可读取NFS存储上的资源。NFS是当前互联网系统架构中最常用的数据存储服务之一，前面说过，中小型网站公司应用频率更高，大公司或门户除了使用NFS外，还可能会使用更为复杂的分布式文件系统，比如Moosefs（mfs）、GlusterFS、FastDFS等。\n企业生产集群为什么需要共享存储角色 例如：A用户传图片到Web1服务器，然后让B用户访问这张图片，结果B用户访问的请求分发到了Web2，因为Web2上没有这张图片，这就导致它无法看到A用户上传的图片，如果此时有一个共享存储，A用户上传图片的请求无论是分发到Web1还是Web2上，最终都会存储到共享存储上，而在B用户访问图片时，无论请求分发到Web1还是Web2上，最终也都会去共享存储上找，这样就可以访问到需要的资源了。这个共享存储的位置可以通过开源软件和商业硬件实现，互联网中小型集群架构会用普通PC服务器配置NFS网络文件系统实现。\n当集群中没有NFS共享存储时，用户访问图片的情况如图所示。\n如果集群中有NFS共享存储，用户访问图片的情况如图所示。\n中小型互联网企业一般不会买硬件存储，因为太贵，大公司如果业务发展很快的话，可能会临时买硬件存储顶一下网站的压力，当网站并发继续加大时，硬件存储的扩展相对就会很费劲，且价格成几何级数增加。例如：淘宝网就曾替换掉了很多硬件设备，比如，用LVS+Haproxy替换了netscaler负载均衡设备，用FastDFS、TFS配合PC服务器替换了netapp、emc等商业存储设备，去IOE正在成为互联网公司的主流。\nNFS系统原理介绍 NFS系统挂载结构图解与介绍 在NFS服务器端设置好一个共享目录/video后，其他有权限访问NFS服务器端的客户端都可以将这个共享目录/video挂载到客户端本地的某个挂载点（其实就是一个目录，这个挂载点目录可以自己随意指定），不同客户端的挂载点可以不相同。\n客户端正确挂载完毕后，就可以通过NFS客户端的挂载点所在的/v/video或/video目录查看到NFS服务器端/video共享出来的目录下的所有数据。在客户端上查看时，NFS服务器端的/video目录就相当于客户端本地的磁盘分区或目录，几乎感觉不到使用上的区别，根据NFS服务器端授予的NFS共享权限以及共享目录的本地系统权限，只要在指定的NFS客户端操作挂载/v/video或/video的目录，就可以将数据轻松地存取到NFS服务器端上的/video目录中了。\n什么是RPC 因为NFS支持的功能相当多，而不同的功能都会使用不同的程序来启动，每启动一个功能就会启用一些端口来传输数据，因此，NFS的功能所对应的端口无法固定，它会随机取用一些未被使用的端口来作为传输之用，其中CentOS 5.x的随机端口都小于1024，而CentOS 6.x的随机端口都是较大的。\n因为端口不固定，这样一来就会造成NFS客户端与NFS服务器端的通信障碍，因为NFS客户端必须要知道NFS服务器端的数据传输端口才能进行通信，才能交互数据。\n要解决上面的困扰，就需要通过远程过程调用RPC（Remote Proce-dure Call）服务来帮忙了，NFS的RPC服务最主要的功能就是记录每个NFS功能所对应的端口号，并且在NFS客户端请求时将该端口和功能对应的信息传递给请求数据的NFS客户端，从而确保客户端可以连接到正确的NFS端口上去，达到实现数据传输交互数据目的。这个RPC服务类似NFS服务器端和NFS客户端之间的一个中介，流程如图10-7所示。\n拿房屋中介打个比喻吧：假设我们要找房子，这里的我们就相当于NFS客户端，中介介绍房子，就相当于RPC服务，房子所有者房东就相当于NFS服务，租房的人找房子，就要找中介，中介要预先存有房子主人的信息，才能将房源信息告诉租房的人。\n那么RPC服务如何知道每个NFS的端口呢？\n当NFS服务器端启动服务时会随机取用若干端口，并主动向RPC服务注册取用的相关端口及功能信息，如此一来，RPC服务就知道NFS每个端口对应的NFS功能了，然后RPC服务使用固定的111端口来监听NFS客户端提交的请求，并将正确的NFS端口信息回复给请求的NFS客户端，这样一来，NFS客户端就可以与NFS服务器端进行数据传输了。\n在启动NFS Server之前，首先要启动RPC服务（CentOS 5.8下为portmap服务，CentOS 6.6下为rpcbind服务，下同），否则NFS Server就无法向RPC服务注册了。另外，如果RPC服务重新启动，原来已经注册好的NFS端口数据就会丢失，因此，此时RPC服务管理的NFS程序也需要重新启动以重新向RPC注册。要特别注意的是，一般修改NFS配置文件后，是不需要重启NFS的，直接在命令行执行/etc/init.d/nfs reload或exportfs-rv即可使修改的/etc/exports生效。\nNFS的工作流程原理 当访问程序通过NFS客户端向NFS服务器端存取文件时，其请求数据流程大致如下：\n1）首先用户访问网站程序，由程序在NFS客户端上发出存取NFS文件的请求，这时NFS客户端（即执行程序的服务器）的RPC服务（rpcbind服务）就会通过网络向NFS服务器端的RPC服务（rpcbind服务）的111端口发出NFS文件存取功能的询问请求。 2）NFS服务器端的RPC服务（rpcbind服务）找到对应的已注册的NFS端口后，通知NFS客户端的RPC服务（rpcbind服务）。 3）此时NFS客户端获取到正确的端口，并与NFS daemon联机存取数据。 4）NFS客户端把数据存取成功后，返回给前端访问程序，告知用户存取结果，作为网站用户，就完成了一次存取操作。 因为NFS的各项功能都需要向RPC服务（rpcbind服务）注册，所以只有RPC服务才能获取到NFS服务的各项功能对应的端口号（port number）、PID、NFS在主机所监听的IP等信息，而NFS客户端也只能通过向RPC服务询问才能找到正确的端口。也就是说，NFS需要有RPC服务的协助才能成功对外提供服务。从上面的描述，我们不难推断，无论是NFS客户端还是NFS服务器端，当要使用NFS时，都需要首先启动RPC服务，NFS服务必须在RPC服务启动之后启动，客户端无需启动NFS服务，但需要启动RPC服务。\n注意： NFS的RPC服务，在CentOS 5.X下名称为portmap，在CentOS 6.X下名称为rpcbind。\n安装NFS服务 搭建NFS环境准备 克隆虚拟机 克隆的虚拟机存在的网络问题 原因分析：\n使用VM的克隆功能，会为新产生的虚拟机配置一个与原始虚拟机网卡MAC地址不同的网卡。对于CentOS这样的Linux系统，会把运行时的网卡MAC地址记入/etc/udev/rules.d/70-persistent-net.rules文件中。这样克隆好的新系统里也保存了这个记录。\n当新系统启动时，由于vm已经为其配置了不同的MAC地址，因此系统会在启动扫描硬件时把这个新的MAC地址的网卡当做是eth1，并且增加记入上述文件中。而此时配置文件里的/etc/sysconfig/network-scripts/ifcfg-eth0里记录的还是原来的MAC地址，而这个MAC地址在新系统里是不存在的，所以无法启动。\n解决方法1\n删除里面的uuid，因为这个是唯一的值 更改HWaddr为eth1的值 重启系统后\n解决方法2 1.编辑eth0的配置文件：vi /etc/sysconfig/network-scripts/ifcfg-eth0 ,删除HWADDR地址那一行及UUID的行\nonfig $ cat /etc/sysconfig/network-scripts/ifcfg-eth0\rDEVICE=eth0\rIPV6INIT=no\rUSERCTL=no\rHWADDR=00:0c:29:08:28:9f\rUUID=cee39dbb-6a10-4425-9daf-768b6e79a9c9 2.清空 /etc/udev/rules.d/70-persistent-net.rules\ntext 1 \u0026gt;/etc/udev/rules.d/70-persistent-net.rules 3.重启系统\n注： 两种方法更改后重启网卡是不行的，必须重启系统\n安装NFS 要部署NFS服务，需要安装下面的软件包：\nnfs-utils：NFS服务的主程序，包括rpc.nfsd、rpc.mountd这两个daemons和相关文档说明，以及执行命令文件等。 rpcbind：CentOS 6.X下面RPC的主程序。NFS可以视为一个RPC程序，在启动任何一个RPC程序之前，需要做好端口和功能的对应映射工作，这个映射工作就是由rpcbind服务来完成的。因此，在提供NFS服务之前必须先启动rpcbind服务才行。 NFS安装的三种方式 检查软件是否安装\ntext 1 rpm -qa nfs-utils rpcbind yum安装\ntext 1 yum install nfs-utils rpcbind -y 通过系统光盘里的rpm包安装，命令 nfs-utils-1.2.3-64.el6.x86_64\ntext 1 2 yum grouplistgrep -i nfs yum groupinstall \u0026#34;NFS file server\u0026#34; -y 启动NFS相关服务 启动rpcbind服务\n因为NFS及其辅助程序都是基于RPC（Remote Procedure Call）协议的（使用的端口为111），所以首先要确保系统中运行了rpcbind服务。\ntext 1 2 3 /etc/init.d/rpcbind start /etc/init.d/rpcbind status\t#\u0026lt;==检查rpcbind服务状态 rpcinfo -p localhost #\u0026lt;==rpcbind服务未启动检查rpcinfo信息的报错 https://yd.baidu.com/view/38b81d255fbfc77da369b13c?cn=24-105,24-592\u0026pn=15 text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ ps -ef|egrep \u0026#34;rpc|nfs\u0026#34; rpcuser 901 1 0 06:43 ? 00:00:00 rpc.statd \u0026lt;==检查文件一致性 rpc 1071 1 0 07:24 ? 00:00:00 rpcbind root 1101 2 0 07:24 ? 00:00:00 [rpciod/0] root 1110 1 0 07:24 ? 00:00:00 rpc.rquotad root 1115 1 0 07:24 ? 00:00:00 rpc.mountd root 1122 2 0 07:24 ? 00:00:00 [nfsd4] root 1123 2 0 07:24 ? 00:00:00 [nfsd4_callbacks] root 1124 2 0 07:24 ? 00:00:00 [nfsd] root 1125 2 0 07:24 ? 00:00:00 [nfsd] root 1126 2 0 07:24 ? 00:00:00 [nfsd] root 1127 2 0 07:24 ? 00:00:00 [nfsd] root 1128 2 0 07:24 ? 00:00:00 [nfsd] root 1129 2 0 07:24 ? 00:00:00 [nfsd] root 1130 2 0 07:24 ? 00:00:00 [nfsd] root 1131 2 0 07:24 ? 00:00:00 [nfsd] root 1158 1 0 07:24 ? 00:00:00 rpc.idmapd 问：如何让rpcbind比nfs先启动？\n一般都是将启动命令放入rc.local中\n配置NFS nfs配置文件\n提示：NFS默认配置文件/etc/exports其实是存在的，但是没有内容，需要用户自行配置\ntext 1 2 3 4 $ cat /etc/exports # NFS共享的目录 共享给谁(权限) NFS客户端地址1（参数1,参数2） NFS客户端地址2（参数1,参数2） /data 192.168.59.*(rw,sync) /data 10.1.1.1(rw,sync) NFS地址配置的详细说明\r客户端地址 具体地址 说明 授权单一客户端访问NFS 10.0.0.30 一般情况下生产环境中此配置不多 授权整个网段可访问NFS 10.0.0.0/24 其中24位255.255.255.0，指定网段为生产环境中最常见的配置。配置简单，维护方便 授权整个网段可访问NFS 10.0.0.0/24 其中24位255.255.255.0，指定网段为生产环境中最常见的配置。配置简单，维护方便 授权整个网段可访问NFS 10.0.0.* 指定网段的另外写法（不推荐使用） 授权某个域名客户端访问 nfs.bodboy.com 生产环境中一般情况下不常用。（域名可在hosts文件中解析） 授权整个域名客户端访问 *.oldboy.com 生产环境中一般情况下不常用 /etc/exports 文件格式配置实例说明\r常用格式说明 要共享的目录 客户端IP地址或IP段（参数1，参数2\u0026hellip;） 配置例一 /data 10.0.0.0/24(rw,sync) #\u0026lt;\u0026ndash; 允许客户端读写，并且数据同步写到服务器端磁盘里，注意，24和 ( 之间不能有空格 配置例二 /data 10.0.0.0/24(rw,sync,all_squash,anonuid=2000,anongid=2000) #\u0026lt;\u0026ndash;允许客户端读写，并且数据同步写到服务器端的磁盘里，并指定客户端的用户UID和GID。早期生产环境的一种配置，适合多客户端共享一个NFS服务单目录，如果所有服务器的nfsnobody账户UID都是65534，则本例没什么必要了。早期CentOS5.5的系统默认情况下nfsbobody的UID不一定是65534，此时如果这些服务器共享一个NFS目录，就会出现访问权限问题 配置例三 /home/oldbody 10.0.0.0/24(ro) #\u0026lt;\u0026ndash; 只读并共享\n用途：例如在生产环境中，开发人员有查看盛传服务器日志的需求，但有不希望给开发生产服务器的权限，那么就可以给开发提供某个测试服务器NFS客户端上查看某个生产服务器日志目录（NFS共享）的权限，当然这不是唯一的放法，例如可以把程序记录的日志发送到测试服务器供开发查看或者通过收集日志等其他方式展现 NFS常用配置参数权限\r参数选项 说明 ro 只读访问 rw 读写访问 sync 请求或写入数据时，数据同步写入到NFS服务器的硬盘后才返回。\n优点，数据安全不会丢，缺点，性能比不启用该参数要差 async 写入数据会先写到内存缓存区，直到硬盘有空挡才会再写入磁盘，这样可以提升写入效率！风险若为服务器宕机或不正常关机，会损失缓冲区中未写入磁盘的数据（解决方法：服务器主板点出或加UPS不间断电源） all_squash 不管访问NFS服务器共享目录的用户身份如歌，它的权限都将被压缩成匿名用户，同时它的UID和GID都会变成nfsnobody账号身份。在早期多个NFS客户端同时读写NFS服务器数据时，这个参数很有用\n在生产中配置NFS的重要技巧：\n1)确保所有客户端服务器对NFS共享目录具备想用的用户访问权限\na.all_squash把所有客户端都压缩成固定的匿名用户（UID相同）\nb.就是anonuid，anongid指定的UID和GID用户 2)所有的客户端和服务器端都需要有一个相同的UID和GID的用户，即nfsnobody（UID必须相同） no_all_squash 访问NFS服务器共享目录的用户如果是root的话，它对该共享目录具有root权限。这个配置原是为无盘客户端准备的。用户应该避免使用 root_squash 如果访问NFS服务器共享目录是root，则它的权限将被压缩成匿名用户，同时它的UID和GID通常会变成nfsnobody账号身份 anonuid=xxx 参数以anon*开头即指anonymous匿名用户，这个用户的UID设置值通常为nfsnobody的UID值，当然也可以自行设置这个UID值。但是，UID必须存在于/etc/passwd中。在多NFS客户端时，如多台webserver共享一个NFS目录，通过这个参数可以使得不同的NFS客户端写入的数据对所有NFS客户端保持永阳的用户权限，即为配置的匿名UID对应用户权限，这个参数很有用，一般默认即可 anonuid 同uid 配置服务器端 text 1 $ /etc/init.d/nfs reload = exportfs -rv（平滑生效） reload = exportfs -rv（平滑生效）位置\n检查挂载信息\ntext 1 2 3 $ showmount -e 127.0.0.1 Export list for 127.0.0.1: /data 192.168.59.* 执行挂载命令\ntext 1 $ mount -t nfs 192.168.59.133:/data /mnt 查看挂载结果\ntext 1 2 3 4 5 6 $ df -h Filesystem Size Used Avail Use% Mounted on /dev/sda3 9.1G 1.5G 7.2G 17% / tmpfs 242M 0 242M 0% /dev/shm /dev/sda1 190M 27M 153M 16% /boot 192.168.59.133:/data 9.1G 1.5G 7.2G 17% /mnt 配置客户端 开启rpcbind\ntext 1 2 $ /etc/init.d/rpcbind start 正在启动 rpcbind：[确定] 将rpcbind服务设置为开机自启动\ntext 1 2 3 4 5 6 7 8 9 $ vi /etc/rc.local #!/bin/sh # # This script will be executed *after* all the other init scripts. # You can put your own initialization stuff in here if you don\u0026#39;t # want to do the full Sys V style init stuff. touch /var/lock/subsys/local /etc/init.d/rpcbind start 查看挂载信息\ntext 1 2 3 $ showmount -e 192.168.59.133 Export list for 192.168.59.133: /data 192.168.59.* 设置挂载\ntext 1 2 3 4 5 6 7 $ mount -t nfs 192.168.59.133:/data /mnt $ df -h Filesystem Size Used Avail Use% Mounted on /dev/sda3 9.1G 1.4G 7.2G 17% / tmpfs 242M 0 242M 0% /dev/shm /dev/sda1 190M 27M 153M 16% /boot 192.168.59.133:/data 9.1G 1.5G 7.2G 17% /mnt 切换到挂载的目录\ntext 1 $ cd /mnt 查看nfs服务器文件列表\ntext 1 2 $ ls a.log 解决不能写的问题\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ vi /var/lib/nfs/etab /data 192.168.59.*(rw,sync,wdelay,hide,nocrossmnt,secure,root_squash,no_all_squash,no_subtree_check,secure_locks,acl,anonuid=65534,anongid=65534,sec=sys,rw,root_squash,no_all_squash) $ grep 65534 /etc/passwd nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin # 将目录所属用户设置为nfsnobody $ chown -R nfsnobody /data # 查看设置结果 $ ls -l / drwxr-xr-x. 2 nfsnobody root 4096 4月 8 21:54 data # 在客户端创建一个文件 $ touch b.txt # 在服务器端查看 $ ls b.txt 重启后挂载失效\n在 /etc/rc.loacl 中挂载，不要在fstab中，因为linux启动过程，fstab先启动。网络是后启动的，网络磁盘是挂不上的\n挂载服务器断\ntext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 mount -t nfs 192.168.59.133:/data /mnt $ cat /proc/mounts rootfs / rootfs rw 0 0 proc /proc proc rw,relatime 0 0 sysfs /sys sysfs rw,seclabel,relatime 0 0 devtmpfs /dev devtmpfs rw,seclabel,relatime,size=235908k,nr_inodes=58977,mode=755 0 0 devpts /dev/pts devpts rw,seclabel,relatime,gid=5,mode=620,ptmxmode=000 0 0 tmpfs /dev/shm tmpfs rw,seclabel,relatime 0 0 /dev/sda3 / ext4 rw,seclabel,relatime,barrier=1,data=ordered 0 0 none /selinux selinuxfs rw,relatime 0 0 devtmpfs /dev devtmpfs rw,seclabel,relatime,size=235908k,nr_inodes=58977,mode=755 0 0 /proc/bus/usb /proc/bus/usb usbfs rw,relatime 0 0 /dev/sda1 /boot ext4 rw,seclabel,relatime,barrier=1,data=ordered 0 0 none /proc/sys/fs/binfmt_misc binfmt_misc rw,relatime 0 0 sunrpc /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0 nfsd /proc/fs/nfsd nfsd rw,relatime 0 0 192.168.59.133:/data/ /mnt nfs4 rw,relatime,vers=4,rsize=4096,wsize=1024,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=192.168.59.133,minorversion=0,local_lock=none,addr=192.168.59.133 0 0 $ cd /mnt $ umount /mnt umount.nfs: /mnt: device is busy umount.nfs: /mnt: device is busy $ umount -lf /mnt $ df -h Filesystem Size Used Avail Use% Mounted on /dev/sda3 9.1G 1.5G 7.2G 17% / tmpfs 242M 0 242M 0% /dev/shm /dev/sda1 190M 27M 153M 16% /boot NFS挂载参数说明\r参数 参数功能 默认参数 fg/bg 当在客户端执行挂载时，可选择时前台（fg）还是后台（bg）执行。若在前台执行，则mount会持续尝试挂载，直到成功或挂载时间超时为止，若在后台执行，则mount会在后台持续多次进行mount，而不会影响到前台的其他程序操作。如果网络联机不稳定，或是服务器常常需要开关机，建议使用bg比较妥当 fg soft/hard 当NFS客户端以soft挂载服务器时，若网络或服务器出现问题，造成客户端与服务器之间无法传输资料，客户端就会一直尝试，直到timeout（超时时间）后显示错误才停止。若使用soft mount的话，可能会在timeout出现时造成资料丢失，故一般不建议使用。若用hard模式挂载硬盘时，刚与soft相反，此时客户端会一直尝试连线倒服务器，若服务器有回应就继续刚才的操作，若没有回应NFS客户端会一直尝试，此时无法umount或kill，所以常常会配合intr使用 hard intr 当使用hard挂载的资源timeout后，若有指定intr参数，可以在timeout后把它中断掉，这避免出问题时系统整个被NFS锁死，建议使用intr 无 rsize/wsize 读出（rsize）与写入（wsize）的区块大小（block size），这个设置值可以影响客服端与服务器传输数据的传冲存储量，一般来说，如果在局域网内，别且客户端与服务器端都具有足够的内存，这个值可以设置大一点，比如65535（bytes），提升缓冲区块将提升NFS文件系统的传输能力。但设置的值也不要太大，最好以网络能够传输的最大值为限 CentOS5X：默认值\nrsize=1024\nwsize=1024\nCentOS6：默认值\nrsize=131072\nwsize=131072 proto 使用UDP协定来传输资料，在LAN中会有比较好的性能。若要跨越Internet的话，使用proto=tcp多传输的数据会有比较好的纠错能力 proto=tcp 通过man nfs查看上述参数信息。如果追求极致，可以用如下参数挂载\ntext 1 mount -t nfs -o fg,hard,intr,rsize=1111,wsize=1111 10.0.0.7:/data /mnt 如果考虑简单、易用为原则，直接选择默认值\ntext 1 mount -t nfs 10.0.0.7:/data /mnt mount -o 参数对应的选项\r参数 参数意义 系统默认值 suid/nosuid 当挂载的文件系统上有任何SUID的程序时，只要使用nosuid就能够取消设置SUID功能 suid rw/ro 可以指定文件系统是只读或可写 rw dev/nodev 是否可以保留装置文件的特殊功能？一般来说只有/dev才会有特殊的装置，因此可选择nodev dev exec/noexec 是否具有执行文件权限？如果想要挂载的文件时普通的资源区（例如：图片、附件），那个可以选择noexec exec user/nouser 是否允许用户拥有文件的挂载与卸载功能？如果要保护文件系统，最好不要为用户提供挂载与卸载功能 nouer auto/noauto 这个auto是指“mount -a”时会不会被挂载的项目，如果不需要这个分区随时被挂载，可以设置为noauto\tauto default 这个是fstab里面的默认值，包括rw、suid、dev、exec、auto、nouser、async默认情况下大部分都是默认值 noatime/atime atime:在每一次数据访问时，会永不更新访问文件的inode时间戳，在高并发情况下，建议通过加上noatime来取消这个默认项，已到提升I/O性能，优化I/O的目的\nnoatime：访问文件不更新文件的inode时间戳，高并发环境，推荐显示应用该选项，可提高磁盘I/O性能 atime sync/async 该参数与async相反。有I/O操作时，会同步处理I/O即吧数据同步写入硬盘。次参数会牺牲一点I/O性能，但是，换来的时断电后数据的安全性。 sync：涉及文件I/O的操作都是异步处理，即不会写到磁盘，此参数会提高性能，但会降低数据安全。一般情况，生产环境不推荐使用。除非对性能要求很高，对数据可靠性不要求场合 问：在企业生产环境中，NFS客户端挂载有没有必要加某些参数，如：noexec、nosuid、bg、soft、rsize、wsize等参数，有书上说建议加rsize、wszie这两个参数\n这个问题属于mount挂载优化内容（有些参数也适合其他文件系统），一般来说要适当加挂载参数，但是，最好先做测试，用数据来说话，才能更好的确定到底是挂载还是不挂载。\n有关安全挂载参数选项 在工作环境，一般来说，NFS服务器共享的只是普通静态数据（图片、附件、视频）不需要执行suid、exec等权限。挂载的这个文件系统时能作为数据存取只用，无法执行程序，对于客户端来讲增加了安全性，例如：很多木马篡改文件都是由上传入口上传的程序倒存储目录然后执行的。 因此在挂载的时候用下面的命令很有必要\ntext 1 mount -t nfs -o noexec,nosuid,nodev,rw 10.0.0.7:/data /mnt 注：通过mount -o指定挂载参数与/etc/fstab里指定挂载参数的效果是一样的。\n挂载性能优化参数\n禁止更新目录及文件时间戳挂载\ntext 1 mount -t nfs -o noatime,nodiratime 10.0.0.7:/data /mnt 安全加优化的挂载方法如下\ntext 1 mount -t nfs -o nosuid,noexec,nodev,noatime,nodiratime,intr,rsize=131072,wsize=131072 10.0.0.7:/data /mnt NFS优缺点\n优点：\n简单易上手，容易掌握 数据可见 部署快速，维护简单，且可控，满足需求就是最好的 可靠，从软件层面上来看，数据可靠性高，经久耐用。数据实在文件系统之上的。 服务非常稳定 缺点：\n存在单点故障，服务器宕机，所有的客户端都不能访问共享目录 在大数据高并发的场合，NFS效率、性能有限（2000W/日以下的PV网站不是瓶颈。除非网站架构设计太差） 安全性一般等 ","permalink":"https://www.161616.top/nfs-network-filesystem/","summary":"NFS介绍 什么是NFS NFS是 Network File System 网络文件系统。它的主要功能是通过网络（一般是局域网）让不同的主机系统之间可以共享文件或目录。NFS客户端（一般为应用服务器，例如Web）可以通过挂载（mount）的方式将NFS服务器端共享的数据目录挂载到NFS客户端本地系统中（就是某一个挂载点下）。从客户端本地看，NFS服务器端共享的目录就好像是客户端自己的磁盘分区或者目录一样，而实际上却是远端的NFS服务器的目录。\n一般情况下，Windows网络共享服务或samba服务用于办公局域网共享，而互联网中小型网站集群架构后端常用NFS进行数据共享，如果是大型网站，那么有可能还会用到更复杂的分布式文件系统，例如：Moosefs（mfs）、GlusterFS、FastDFS。\nNFS的历史介绍 第一个网络文件系统被称为File Access Listener，由Digital Equipment Corporation（DEC）在1976年开发。\nNFS是第一个构建于IP协议之上的现代网络文件系统。在20世纪80年代，它首先作为实验的文件系统，由Sun Microsystems在内部完成开发。NFS协议归属于Request for Comments（RFC）标准，并且随后演化为NFSv2。作为一个标准，由于NFS与其他客户端和服务器的互操作能力很好而发展快速。\n之后，标准继续演化，成为NFSv3，在RFC1813中有定义。这一新的协议比以前的版本具有更好的可扩展性，支持大文件（超过2GB），异步写入，并且将TCP作为传输协议，为文件系统在更广泛的网络中使用铺平了道路。在2000年，RFC 3010（由RFC 3530修订）将NFS带入企业级应用。此时，Sun引入了具有较高安全性、带有状态协议的NFSv4（NFS之前的版本都是无状态的）。今天，NFS版本的4.1（由RFC 5661定义）增加了对跨越分布式服务器并行访问的支持（称为PNFS extension）。\nNFS系统已经历了近30年的发展。它代表了一个非常稳定的（及可移植）网络文件系统，具备可扩展、高性能等特性，并达到了企业级应用质量标准。由于网络速度的增加和延迟的降低，NFS系统一直是通过网络提供文件系统服务的有竞争力的选择，特别是在中小型互联网企业中，应用十分广泛。\nNFS在企业中的应用场景 在企业集群架构的工作场景中，NFS网络文件系统一般被用来存储共享视频、图片、附件等静态资源文件，通常网站用户上传的文件都会放到NFS共享里，例如：BBS产品的图片、附件、头像（注意网站BBS程序不要放在NFS共享里），然后前端所有的节点访问这些静态资源时都可读取NFS存储上的资源。NFS是当前互联网系统架构中最常用的数据存储服务之一，前面说过，中小型网站公司应用频率更高，大公司或门户除了使用NFS外，还可能会使用更为复杂的分布式文件系统，比如Moosefs（mfs）、GlusterFS、FastDFS等。\n企业生产集群为什么需要共享存储角色 例如：A用户传图片到Web1服务器，然后让B用户访问这张图片，结果B用户访问的请求分发到了Web2，因为Web2上没有这张图片，这就导致它无法看到A用户上传的图片，如果此时有一个共享存储，A用户上传图片的请求无论是分发到Web1还是Web2上，最终都会存储到共享存储上，而在B用户访问图片时，无论请求分发到Web1还是Web2上，最终也都会去共享存储上找，这样就可以访问到需要的资源了。这个共享存储的位置可以通过开源软件和商业硬件实现，互联网中小型集群架构会用普通PC服务器配置NFS网络文件系统实现。\n当集群中没有NFS共享存储时，用户访问图片的情况如图所示。\n如果集群中有NFS共享存储，用户访问图片的情况如图所示。\n中小型互联网企业一般不会买硬件存储，因为太贵，大公司如果业务发展很快的话，可能会临时买硬件存储顶一下网站的压力，当网站并发继续加大时，硬件存储的扩展相对就会很费劲，且价格成几何级数增加。例如：淘宝网就曾替换掉了很多硬件设备，比如，用LVS+Haproxy替换了netscaler负载均衡设备，用FastDFS、TFS配合PC服务器替换了netapp、emc等商业存储设备，去IOE正在成为互联网公司的主流。\nNFS系统原理介绍 NFS系统挂载结构图解与介绍 在NFS服务器端设置好一个共享目录/video后，其他有权限访问NFS服务器端的客户端都可以将这个共享目录/video挂载到客户端本地的某个挂载点（其实就是一个目录，这个挂载点目录可以自己随意指定），不同客户端的挂载点可以不相同。\n客户端正确挂载完毕后，就可以通过NFS客户端的挂载点所在的/v/video或/video目录查看到NFS服务器端/video共享出来的目录下的所有数据。在客户端上查看时，NFS服务器端的/video目录就相当于客户端本地的磁盘分区或目录，几乎感觉不到使用上的区别，根据NFS服务器端授予的NFS共享权限以及共享目录的本地系统权限，只要在指定的NFS客户端操作挂载/v/video或/video的目录，就可以将数据轻松地存取到NFS服务器端上的/video目录中了。\n什么是RPC 因为NFS支持的功能相当多，而不同的功能都会使用不同的程序来启动，每启动一个功能就会启用一些端口来传输数据，因此，NFS的功能所对应的端口无法固定，它会随机取用一些未被使用的端口来作为传输之用，其中CentOS 5.x的随机端口都小于1024，而CentOS 6.x的随机端口都是较大的。\n因为端口不固定，这样一来就会造成NFS客户端与NFS服务器端的通信障碍，因为NFS客户端必须要知道NFS服务器端的数据传输端口才能进行通信，才能交互数据。\n要解决上面的困扰，就需要通过远程过程调用RPC（Remote Proce-dure Call）服务来帮忙了，NFS的RPC服务最主要的功能就是记录每个NFS功能所对应的端口号，并且在NFS客户端请求时将该端口和功能对应的信息传递给请求数据的NFS客户端，从而确保客户端可以连接到正确的NFS端口上去，达到实现数据传输交互数据目的。这个RPC服务类似NFS服务器端和NFS客户端之间的一个中介，流程如图10-7所示。\n拿房屋中介打个比喻吧：假设我们要找房子，这里的我们就相当于NFS客户端，中介介绍房子，就相当于RPC服务，房子所有者房东就相当于NFS服务，租房的人找房子，就要找中介，中介要预先存有房子主人的信息，才能将房源信息告诉租房的人。\n那么RPC服务如何知道每个NFS的端口呢？\n当NFS服务器端启动服务时会随机取用若干端口，并主动向RPC服务注册取用的相关端口及功能信息，如此一来，RPC服务就知道NFS每个端口对应的NFS功能了，然后RPC服务使用固定的111端口来监听NFS客户端提交的请求，并将正确的NFS端口信息回复给请求的NFS客户端，这样一来，NFS客户端就可以与NFS服务器端进行数据传输了。\n在启动NFS Server之前，首先要启动RPC服务（CentOS 5.8下为portmap服务，CentOS 6.6下为rpcbind服务，下同），否则NFS Server就无法向RPC服务注册了。另外，如果RPC服务重新启动，原来已经注册好的NFS端口数据就会丢失，因此，此时RPC服务管理的NFS程序也需要重新启动以重新向RPC注册。要特别注意的是，一般修改NFS配置文件后，是不需要重启NFS的，直接在命令行执行/etc/init.d/nfs reload或exportfs-rv即可使修改的/etc/exports生效。\nNFS的工作流程原理 当访问程序通过NFS客户端向NFS服务器端存取文件时，其请求数据流程大致如下：\n1）首先用户访问网站程序，由程序在NFS客户端上发出存取NFS文件的请求，这时NFS客户端（即执行程序的服务器）的RPC服务（rpcbind服务）就会通过网络向NFS服务器端的RPC服务（rpcbind服务）的111端口发出NFS文件存取功能的询问请求。 2）NFS服务器端的RPC服务（rpcbind服务）找到对应的已注册的NFS端口后，通知NFS客户端的RPC服务（rpcbind服务）。 3）此时NFS客户端获取到正确的端口，并与NFS daemon联机存取数据。 4）NFS客户端把数据存取成功后，返回给前端访问程序，告知用户存取结果，作为网站用户，就完成了一次存取操作。 因为NFS的各项功能都需要向RPC服务（rpcbind服务）注册，所以只有RPC服务才能获取到NFS服务的各项功能对应的端口号（port number）、PID、NFS在主机所监听的IP等信息，而NFS客户端也只能通过向RPC服务询问才能找到正确的端口。也就是说，NFS需要有RPC服务的协助才能成功对外提供服务。从上面的描述，我们不难推断，无论是NFS客户端还是NFS服务器端，当要使用NFS时，都需要首先启动RPC服务，NFS服务必须在RPC服务启动之后启动，客户端无需启动NFS服务，但需要启动RPC服务。\n注意： NFS的RPC服务，在CentOS 5.X下名称为portmap，在CentOS 6.X下名称为rpcbind。\n安装NFS服务 搭建NFS环境准备 克隆虚拟机 克隆的虚拟机存在的网络问题 原因分析：","title":"网络文件系统 - NFS"},{"content":"","permalink":"https://www.161616.top/","summary":"","title":""}]