<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>knktc的杂乱空间</title>
  
  <subtitle>python,cloud,linux...</subtitle>
  <link href="https://knktc.com/atom.xml" rel="self"/>
  
  <link href="https://knktc.com/"/>
  <updated>2026-10-12T01:21:06.414Z</updated>
  <id>https://knktc.com/</id>
  
  <author>
    <name>knktc</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>当年没给 Kubernetes 节点留的 swap 监控位，现在该补上了</title>
    <link href="https://knktc.com/2026/10/12/kubernetes-swap-monitoring-in-ai-era/"/>
    <id>https://knktc.com/2026/10/12/kubernetes-swap-monitoring-in-ai-era/</id>
    <published>2026-10-12T04:00:00.000Z</published>
    <updated>2026-10-12T01:21:06.414Z</updated>
    
    <content type="html"><![CDATA[<p>以前我们做一个基于 Kubernetes 的管理界面时，没有给节点的 swap 预留监控位。</p><p>当时的想法很简单：Kubernetes 节点上的 swap 都要关掉，既然不会用，监控页面也就不用给它留位置了。</p><p>最近读到 Kubernetes 官方的一篇文章：<a href="https://kubernetes.io/zh-cn/blog/2026/10/05/scaling-kubernetes-workloads-with-node-swap/">《使用节点交换内存扩展 Kubernetes 工作负载》</a>，又想起了这个取舍。</p><p>文章聊的是用本地 SSD 做 swap，让同一台节点容纳更多浏览器和 Python 沙箱。这些容器会占着不少内存，却经常在等用户的下一条指令。</p><p>读到这里，我的第一反应是：看来当年没留的那个位置，现在还真得补上。</p><span id="more"></span><h2 id="当年为什么觉得用不到-swap"><a href="#当年为什么觉得用不到-swap" class="headerlink" title="当年为什么觉得用不到 swap"></a>当年为什么觉得用不到 swap</h2><p>这个想法有它的背景。Linux 节点启用了 swap 时，kubelet 默认会拒绝启动，关掉 swap 长期以来也是搭建 Kubernetes 集群时的常见步骤。</p><p>而且 swap 确实会影响性能。进程要访问的数据如果被换到了磁盘上，就得先读回内存。内存紧张时，这种等待可能反复发生，对延迟敏感的服务尤其麻烦。</p><p>所以当时做界面，没有特别考虑 swap，我觉得也很正常。只是时间一长，“节点上要关 swap”就很容易被记成“Kubernetes 不需要 swap”。后来再考虑节点监控时，也就顺手把它略过去了。</p><p>其实 Kubernetes 对节点 swap 的支持已经推进了好几年：v1.22 时进入 Alpha，到 v1.34 已经 GA。AI 沙箱的需求，让这项能力又受到更多关注。</p><h2 id="容器在等，内存还占着"><a href="#容器在等，内存还占着" class="headerlink" title="容器在等，内存还占着"></a>容器在等，内存还占着</h2><p>说起 AI 基础设施，首先想到的往往是 GPU。但智能体干活的时候，还可能需要启动浏览器、跑 Python，或者在隔离沙箱里执行外部代码。这些环境也会带来不少 CPU 和内存开销。</p><p>这类容器有个特点：忙的时候需要不少内存，忙完之后又不能马上退出，因为还要保留会话和执行状态，等下一次交互。</p><p>一个两个还好，会话多了，等待中的容器也会占掉很大一部分内存。节点的 CPU 可能还没用满，RAM 已经不够了。</p><p>但进程保留着这些数据，不代表它一直在访问这些数据。等待期间，有些内存页可能很久都不会被碰到。</p><p>swap 在这里就有用了。Linux 可以把暂时不用的内存页换出去，把 RAM 腾给其他 Pod。等原来的会话再次访问这些数据时，再读回来。应用还是照常访问内存，换页由操作系统处理。</p><p>当然，读回来需要时间。能不能接受这点等待，就得看具体业务了。</p><h2 id="能多放些-Pod，但也有代价"><a href="#能多放些-Pod，但也有代价" class="headerlink" title="能多放些 Pod，但也有代价"></a>能多放些 Pod，但也有代价</h2><p>原文用本地 SSD 做 swap，测试了编译任务、浏览器和 Python 沙箱。比较显眼的一个结果是，gVisor Python 沙箱从 80 个会话增加到了 240 个，容量变成了原来的三倍。</p><p>不过文章里也有一个值得一起看的结果：编译任务的 RAM 限额压到 300 MB 时表现不错，再压到 200 MB，执行时间就增加了超过 40%。作者的解释是，这时连活跃工作集都被迫进入了 swap。</p><p>这几组数据是文章作者的测试结果。我还没有在自己的环境里做同样的验证，三倍这个数字也不能直接拿来规划别的业务。</p><p>现在的条件确实比以前好了。cgroup v2 能分别核算和限制内存与 swap，本地 NVMe SSD 也比机械磁盘快得多。但经常要用的数据如果不断被换出去、再读回来，性能照样会受影响。</p><p>所以我觉得，先看清楚负载的特点更重要：平时有多少会话在等，多少内存暂时用不到，以及这些会话同时忙起来的时候会怎么样。原文里的收益，很大程度上就来自那些可以暂时换出的数据。</p><h2 id="监控页面该补些什么"><a href="#监控页面该补些什么" class="headerlink" title="监控页面该补些什么"></a>监控页面该补些什么</h2><p>回到我们当年做的管理界面，如果现在要补 swap 监控，我会先放上容量、用量和趋势，但不会只放一个使用率就结束。</p><p>毕竟，既然允许工作负载使用 swap，看到它有用量就不一定是坏事。还得知道哪些 Pod 在用、换页是否频繁，以及业务有没有变慢。</p><table><thead><tr><th>信息</th><th>想看清楚什么</th></tr></thead><tbody><tr><td>节点 swap 状态、容量与使用量</td><td>节点是否配置了 swap，还有多少空间？</td></tr><tr><td>Pod 使用 swap 的策略与实际用量</td><td>哪些工作负载可以使用，哪些正在使用？</td></tr><tr><td>swap 换入、换出速率</td><td>数据是否正在频繁往返于 RAM 和存储？</td></tr><tr><td>内存压力、存储延迟与业务延迟</td><td>换页是否已经影响节点或应用的响应？</td></tr></tbody></table><p>比如一台节点的 swap 使用量一直不低，但里面大多是暂时不用的数据，换入换出也很少；另一台节点用量没那么高，却一直在换页，业务响应也慢了。只看百分比，很容易忽略后面这种情况。</p><p>Kubernetes 已经提供了节点、Pod 和容器级别的 swap 指标。kubelet 的 <code>/metrics/resource</code> 端点可以提供下面四项数据，单位均为字节，表示当前用量或上限：</p><table><thead><tr><th>指标</th><th>层级</th><th>含义</th></tr></thead><tbody><tr><td><code>node_swap_usage_bytes</code></td><td>节点</td><td>节点当前的 swap 使用量</td></tr><tr><td><code>pod_swap_usage_bytes</code></td><td>Pod</td><td>Pod 当前的 swap 使用量</td></tr><tr><td><code>container_swap_usage_bytes</code></td><td>容器</td><td>容器当前的 swap 使用量</td></tr><tr><td><code>container_swap_limit_bytes</code></td><td>容器</td><td>容器允许使用的 swap 上限</td></tr></tbody></table><p>Pod 指标带有 <code>namespace</code> 和 <code>pod</code> 标签，容器指标还带有 <code>container</code>。有了这些数据，就可以从节点总览一路看到具体容器，知道它用了多少 swap、上限是多少。</p><p>有一点接入时要留意：<strong>NodeSwap 功能已经 GA，但上面这四个监控指标目前仍标为 ALPHA。</strong> 它们还没有稳定指标那样的兼容性保证。接入前要确认自己的集群和采集链路能拿到这些数据，升级时也需要留意指标是否有变化。</p><p>这四项指标都是用量或限额。换入、换出速率还需要从节点采集器等数据来源补充，再和延迟放到一起看。</p><p>页面上最好也把“没配 swap”“不允许 Pod 用”和“没采到数据”分开显示。这几种情况都写成 0，看着整齐，排查问题时却帮不上忙。</p><h2 id="还有些地方不能混在一起"><a href="#还有些地方不能混在一起" class="headerlink" title="还有些地方不能混在一起"></a>还有些地方不能混在一起</h2><p>补监控时，配置也得一起看。<code>failSwapOn: false</code> 只是允许 kubelet 在启用了 swap 的节点上运行。要让符合条件的容器使用 swap，还需要配置 <code>LimitedSwap</code>。</p><p>另外，调度器目前不会把 swap 当成额外的可调度内存。所以页面上也别把 RAM 和 swap 加起来，直接显示成“节点可用内存”。两者的性能和调度含义都不一样。具体配置可以看官方的 <a href="https://kubernetes.io/docs/concepts/cluster-administration/swap-memory-management/">swap 管理文档</a>。</p><p>这次读文章，技术本身之外，让我印象比较深的还是那个没留的监控位。当时觉得不会用到，也就没再往下想。</p><p>现在有了新的负载，也有了相应的支持，节点监控页面自然得跟着改。真要补起来，我最想先看到的就是：哪个 Pod 在用 swap，以及它开始频繁换页时，业务到底慢了多少。</p>]]></content>
    
    
    <summary type="html">从一次 Kubernetes 管理界面的设计取舍，聊聊 AI 沙箱为何让 swap 重新受到关注，以及节点监控应该补上什么。</summary>
    
    
    
    
    <category term="linux" scheme="https://knktc.com/tags/linux/"/>
    
    <category term="kubernetes" scheme="https://knktc.com/tags/kubernetes/"/>
    
    <category term="swap" scheme="https://knktc.com/tags/swap/"/>
    
    <category term="ai" scheme="https://knktc.com/tags/ai/"/>
    
    <category term="监控" scheme="https://knktc.com/tags/%E7%9B%91%E6%8E%A7/"/>
    
  </entry>
  
  <entry>
    <title>我用 Codex 写了一个 3DS 镜像转换工具：CiaForge</title>
    <link href="https://knktc.com/2026/09/02/ciaforge-introduction/"/>
    <id>https://knktc.com/2026/09/02/ciaforge-introduction/</id>
    <published>2026-09-02T02:30:00.000Z</published>
    <updated>2026-09-02T00:49:55.309Z</updated>
    
    <content type="html"><![CDATA[<p>最近我用 Codex 搓了一个小桌面工具，叫 <code>CiaForge</code>。</p><p>事情的起点其实很简单：我从 Vimm’s Lair 下载 3DS 游戏时，拿到的通常是 <code>.cci</code> 或 <code>.3ds</code> 格式的镜像；而在 3DS 上安装时，更常用的是 <code>.cia</code>。现有的 Python 项目 <a href="https://github.com/ihaveamac/3dsconv">3dsconv</a> 能完成这件事，但它是命令行工具。</p><p>正好我也想认真看看 Tauri 能做到什么程度，于是就有了 CiaForge：用 Tauri 做跨平台桌面界面，用 Rust 重写 3dsconv 的基础转换逻辑，把原本需要在终端里完成的流程做成一个可以拖拽使用的小工具。</p><blockquote><p>CiaForge 不包含游戏内容、密钥或固件资料；请只转换和安装自己有权使用的文件。</p></blockquote><p align="center">  <img src="ciaforge-icon.png" alt="CiaForge logo" width="180"></p><span id="more"></span><h2 id="从命令行到一个顺手的窗口"><a href="#从命令行到一个顺手的窗口" class="headerlink" title="从命令行到一个顺手的窗口"></a>从命令行到一个顺手的窗口</h2><p><code>3dsconv</code> 已经把最核心的事情解决得很好：把符合条件的 3DS 镜像转换成 CIA。它也正是 CiaForge 的灵感和参考来源。</p><p>但我想要的体验更偏向日常使用：打开应用，把文件拖进去，选好输出位置，然后能直观看到每个文件的进度和结果。遇到失败时，也应该直接知道是哪一个文件、为什么失败，而不是从终端输出里翻。</p><p>所以 CiaForge 的目标并不是重新发明一个万能转换器，而是把一个很具体的使用场景收拾得更轻一点：</p><ul><li>原生文件选择器，也支持把一个或多个 <code>.cci</code> / <code>.3ds</code> 文件拖进窗口；</li><li>转换队列会显示文件名、大小、状态和单文件进度；</li><li>可选择输出到源文件旁边，或统一输出到指定文件夹；</li><li>自动避开重名，不会直接覆盖已有的 <code>.cia</code>；</li><li>转换成功后可以直接在 Finder 里定位结果文件。</li></ul><p>下面是空列表时的导入界面，基本就是“拖进来，或者点一下选择文件”。</p><p><img src="ciaforge-empty-state.png" alt="CiaForge 空列表与拖拽导入界面"></p><h2 id="用-Tauri-做壳，用-Rust-写转换逻辑"><a href="#用-Tauri-做壳，用-Rust-写转换逻辑" class="headerlink" title="用 Tauri 做壳，用 Rust 写转换逻辑"></a>用 Tauri 做壳，用 Rust 写转换逻辑</h2><p>这次我没有把 Python 命令行工具直接打包进应用里，而是让 Rust 负责转换引擎，Tauri 负责把原生能力和前端交互接起来。</p><p>这样拆下来，前端处理队列、设置和交互；Rust 侧读取镜像结构、校验 CCI / NCCH 内容、补齐 CIA 所需的元数据并写入内容记录和 SHA-256 完整性数据。Tauri 则把文件选择、拖放和“在文件管理器中显示”这类桌面能力连到一起。</p><p>转换开始后，队列会逐个执行，避免同一批任务被重复启动。每个文件的状态都能单独看，不会因为其中一个失败就让整批任务变得不清不楚。</p><p><img src="ciaforge-conversion-queue.png" alt="CiaForge 转换队列"></p><h2 id="为什么选-Tauri"><a href="#为什么选-Tauri" class="headerlink" title="为什么选 Tauri"></a>为什么选 Tauri</h2><p>以前想做一个跨平台、又带 UI 的小工具，脑子里第一个冒出来的通常就是 Electron。它生态成熟、上手也直接，但对 CiaForge 这种功能很聚焦的应用来说，打包体积确实有点重。</p><p>Tauri 这次给我的感觉很好：前端照样可以写出熟悉的界面，底层又能用 Rust 做原生逻辑，最终体积也很克制。CiaForge 这个小工具打包完成后还不到 10MB；对于一个只是想把镜像转换过程变得顺手一点的桌面应用来说，这个大小就很舒服。</p><h2 id="当前能处理什么，不能处理什么"><a href="#当前能处理什么，不能处理什么" class="headerlink" title="当前能处理什么，不能处理什么"></a>当前能处理什么，不能处理什么</h2><p>这里有一个很重要的边界：CiaForge 当前支持的是 <code>.cci</code> 和 <code>.3ds</code> 镜像中<strong>未加密的 NCCH 内容</strong>。</p><p>对于原始 NCCH 加密或 zero-key 加密的输入，它会明确识别并报错，不会悄悄产出一个不完整的 CIA。后面如果继续做，我也会更倾向于先把已有路径打磨稳，再看是否值得扩展更多格式和加密场景。</p><p>对我来说，这个限制并不只是“功能还没写完”。桌面工具最重要的一点，是不能让人误以为已经转换成功，最后才在设备上发现文件不能用。明确告诉用户支持范围，反而比勉强给一个看起来完成了的结果更靠谱。</p><h2 id="这次用-Codex-的感觉"><a href="#这次用-Codex-的感觉" class="headerlink" title="这次用 Codex 的感觉"></a>这次用 Codex 的感觉</h2><p>这次用 Codex 做下来，后端的转换流程反而挺顺利：把 3dsconv 的基础逻辑用 Rust 重写出来、跑通实际转换，推进得比预想快。</p><p>真正花时间的是最后的界面逻辑和样式调整。比如文件队列怎么展示、转换时哪些按钮该禁用、错误信息怎么让人一眼看明白，以及窗口尺寸变化后布局怎样保持顺手。这些看起来都是小细节，但要把它们调到自己愿意长期使用，还是来回改了不少轮。</p><h2 id="获取与参与测试"><a href="#获取与参与测试" class="headerlink" title="获取与参与测试"></a>获取与参与测试</h2><p>项目源码和各平台安装包都放在 GitHub：</p><ul><li>GitHub: <a href="https://github.com/knktc/CiaForge">https://github.com/knktc/CiaForge</a></li><li>Releases: <a href="https://github.com/knktc/CiaForge/releases">https://github.com/knktc/CiaForge/releases</a></li></ul><p>目前 CI 会分别构建 macOS Apple Silicon、macOS Intel 和 Windows 的安装包。macOS 版本暂时是 ad-hoc 签名、未公证的构建，因此第一次从网络下载后，Gatekeeper 可能会拦截或提示应用已损坏。</p><h3 id="macOS-安装与打开方法"><a href="#macOS-安装与打开方法" class="headerlink" title="macOS 安装与打开方法"></a>macOS 安装与打开方法</h3><p>从 Release 下载与自己 Mac 架构匹配的 DMG，双击打开后，把 <code>CiaForge.app</code> 拖进 <code>Applications</code>。如果首次打开被 Gatekeeper 拦住，请先确认安装包来自上面的 GitHub Release，然后在终端执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">xattr -dr com.apple.quarantine <span class="string">&quot;/Applications/CiaForge.app&quot;</span></span><br><span class="line">open <span class="string">&quot;/Applications/CiaForge.app&quot;</span></span><br></pre></td></tr></table></figure><p>第一条命令只移除 <strong>CiaForge.app 这个应用</strong> 的下载隔离属性；它不会关闭系统全局的 Gatekeeper。第二条命令会立即打开应用。之后就可以从“应用程序”中正常启动 CiaForge 了。</p><p>我目前只有一台 Apple Silicon（M 系列芯片）MacBook，因此只测试过 macOS ARM 版本。如果你有 Intel Mac 或 Windows 机器，欢迎帮忙测试对应版本；反馈时请带上系统版本、CPU 架构、CiaForge 版本和错误信息，会很有帮助。</p><p>至于 CiaForge 本身，我会继续把它当成一个“我自己真会用”的小工具来迭代：保持边界清晰、少一点麻烦、把从镜像到可安装文件这一步做得更顺手。</p>]]></content>
    
    
    <summary type="html">从 Vimm&#39;s Lair 下载 3DS 游戏后，面对 .cci 和 .3ds 镜像、却只有命令行工具可用时，我用 Codex、Tauri 和 Rust 做了一个跨平台桌面转换器 CiaForge。</summary>
    
    
    
    
    <category term="codex" scheme="https://knktc.com/tags/codex/"/>
    
    <category term="vibe-coding" scheme="https://knktc.com/tags/vibe-coding/"/>
    
    <category term="tauri" scheme="https://knktc.com/tags/tauri/"/>
    
    <category term="rust" scheme="https://knktc.com/tags/rust/"/>
    
    <category term="nintendo-3ds" scheme="https://knktc.com/tags/nintendo-3ds/"/>
    
    <category term="ciaforge" scheme="https://knktc.com/tags/ciaforge/"/>
    
  </entry>
  
  <entry>
    <title>我开始用 kftray 管理 Kubernetes port-forward 了</title>
    <link href="https://knktc.com/2026/08/19/kftray-manage-kubernetes-port-forwards/"/>
    <id>https://knktc.com/2026/08/19/kftray-manage-kubernetes-port-forwards/</id>
    <published>2026-08-19T02:00:00.000Z</published>
    <updated>2026-08-19T06:07:23.676Z</updated>
    
    <content type="html"><![CDATA[<p>最近在折腾多个 Kubernetes 环境时，有个小问题一直挺烦的。</p><p>有些服务只给集群内部用，平时想看个页面、调个接口，最直接的办法就是 <code>kubectl port-forward</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl -n monitoring port-forward svc/prometheus 9090:9090</span><br></pre></td></tr></table></figure><p>一两个服务当然没什么。可环境一多，情况就开始变得有点乱：这个服务在哪个 kubeconfig 里、哪个 namespace、要转到本地哪个端口，隔几天不用基本又要重新翻一遍。</p><p>以前我一般是临时写个 HTTPRoute，或者开个终端跑 <code>kubectl port-forward</code>。前者对“我就进去看一眼”的需求有点重，后者则是命令越攒越多，过两天自己都不太记得当时怎么转的。</p><p>后来发现了 <a href="https://kftray.app/">kftray</a>，用下来感觉还挺顺手，就记录一下。</p><span id="more"></span><p>它的项目在这里：<a href="https://github.com/hcavarsan/kftray">hcavarsan/kftray</a>。</p><p>下载时会看到 <code>kftray</code> 和 <code>kftui</code> 两个工具：前者是带界面的版本，平时就放在系统菜单栏里；后者是终端版。我自己目前只用了 <code>kftray</code>，所以这篇也只聊它。</p><h2 id="它解决的其实就是“别让我再记命令”"><a href="#它解决的其实就是“别让我再记命令”" class="headerlink" title="它解决的其实就是“别让我再记命令”"></a>它解决的其实就是“别让我再记命令”</h2><p>kftray 的思路很简单：把平时要用的 port-forward 保存成配置。</p><p>这样以后不用每次都手敲一大串命令，也不用去翻 Shell 历史。打开菜单栏，找到对应的服务，点一下就行。多个 kubeconfig、多个集群的配置也可以放在一起管理。</p><p>先放一张整体界面的图：</p><p><img src="overview.png" alt="kftray 配置总览"></p><p>我觉得它特别适合这种场景：手里有好几个环境，但每天真正要访问的也就是那几个内部服务，比如 Grafana、Prometheus、内部 API 或管理后台。</p><h2 id="配置一次，后面就省事了"><a href="#配置一次，后面就省事了" class="headerlink" title="配置一次，后面就省事了"></a>配置一次，后面就省事了</h2><p>新建配置时，填的内容其实就是平时 <code>kubectl port-forward</code> 命令里那些参数：先选 kubeconfig 和 context，再选 namespace、目标服务，以及本地和远端端口。</p><p>比较省心的是，kubeconfig 选好后，后面的 namespace 和目标服务会在表单里自动加载出来，直接选就行，不用自己再去记名字。</p><p><img src="add-config.png" alt="新建 port-forward 配置"></p><p>本地端口也不用太纠结。不填的话，kftray 会自动生成一个可用端口。现在界面里支持直接点击打开页面，大多数时候连 <code>localhost:xxxx</code> 都不用记，自然也没必要刻意给每个服务规划端口了。</p><h2 id="需要的时候点一下就能打开"><a href="#需要的时候点一下就能打开" class="headerlink" title="需要的时候点一下就能打开"></a>需要的时候点一下就能打开</h2><p>配置保存好之后，平时就不用管它了。想访问哪个服务，直接在菜单栏里把它打开。</p><p>对网页服务来说，我最喜欢的是它可以直接点开本地地址。以前还得记住转发的是 <code>localhost:xxxx</code>，现在转发起来之后直接点一下就进去了。</p><p><img src="click-to-open.png" alt="点击后直接打开转发地址"></p><p>这种体验说不上多复杂，但日常用起来确实舒服。尤其是临时想看一下 Grafana、Prometheus，或者调个只在集群内暴露的接口时，不用再开终端、找命令、复制端口，节奏会顺很多。</p><h2 id="不只可以转发-Service"><a href="#不只可以转发-Service" class="headerlink" title="不只可以转发 Service"></a>不只可以转发 Service</h2><p>配置界面里还可以选不同的 workload 类型：</p><p><img src="workload-type.png" alt="workload 类型选择"></p><p>我自己大部分时候还是直接选 Service，毕竟很多服务本来就是通过 Service 暴露的。不过有些场景需要直接找工作负载时，也可以在这里选对应类型。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>kftray 没有取代 <code>kubectl port-forward</code>，它本质上还是在帮你把那些参数和操作收起来。权限也还是用你本机 kubeconfig 里的那套，没权限的服务当然还是连不上。</p><p>但对我来说，这就够了。平时把常用的几个内部服务配好，之后需要访问时从菜单栏点一下，不用再记 context、namespace 和端口。</p><p>如果你也经常在“上次那条 port-forward 命令是什么来着”和“我就临时看一眼服务”之间来回切换，可以试一下它。</p>]]></content>
    
    
    <summary type="html">记录一下我最近在用的 Kubernetes port-forward 工具 kftray。</summary>
    
    
    
    
    <category term="kubernetes" scheme="https://knktc.com/tags/kubernetes/"/>
    
    <category term="kftray" scheme="https://knktc.com/tags/kftray/"/>
    
    <category term="port-forward" scheme="https://knktc.com/tags/port-forward/"/>
    
  </entry>
  
  <entry>
    <title>Headlamp 顶部的模糊搜索，是怎么实现的？</title>
    <link href="https://knktc.com/2026/07/29/headlamp-global-fuzzy-search/"/>
    <id>https://knktc.com/2026/07/29/headlamp-global-fuzzy-search/</id>
    <published>2026-07-29T14:00:00.000Z</published>
    <updated>2026-07-29T01:03:58.741Z</updated>
    
    <content type="html"><![CDATA[<p>最近在用 <a href="https://headlamp.dev/">Headlamp</a> 管理 Kubernetes 集群时，注意到页面顶部有一个全局搜索框。</p><p>输入 Pod 名称、namespace，甚至是资源类型，都能很快跳到目标资源。第一眼看上去，它很像是接入了某种集群搜索服务：把搜索词发给 Kubernetes API，然后由后端返回模糊匹配的结果。</p><p>出于好奇翻了一下实现，发现它的思路其实很直接：<strong>先把资源列表拉到前端，再在浏览器里做模糊搜索。</strong></p><span id="more"></span><p><img src="search-demo.png" alt="在搜索框中输入 envoy-gateway 后，Headlamp 同时返回多种 Kubernetes 资源"></p><h2 id="Kubernetes-API-并不提供通用模糊查询"><a href="#Kubernetes-API-并不提供通用模糊查询" class="headerlink" title="Kubernetes API 并不提供通用模糊查询"></a>Kubernetes API 并不提供通用模糊查询</h2><p>Kubernetes API 可以通过 label selector、field selector 等条件筛选资源。例如，可以按标签找一批 Pod。</p><p>但它并不支持下面这类通用查询：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">查找名称大概包含 nginx 的所有资源</span><br><span class="line">查找拼写接近 deploymnt 的 Deployment</span><br><span class="line">跨多种资源类型进行全文搜索</span><br></pre></td></tr></table></figure><p>所以 Headlamp 不会把用户输入直接交给 Kubernetes API 做模糊匹配。</p><h2 id="打开搜索后，先收集候选资源"><a href="#打开搜索后，先收集候选资源" class="headerlink" title="打开搜索后，先收集候选资源"></a>打开搜索后，先收集候选资源</h2><p>当点击顶部搜索框，或者按下默认快捷键 <code>/</code> 时，Headlamp 会开始准备搜索数据。它会读取当前集群里的多类 Kubernetes 资源，包括：</p><ul><li>Pod</li><li>Deployment</li><li>Service</li><li>Job 和 CronJob</li><li>ConfigMap</li><li>Namespace</li><li>StatefulSet、ReplicaSet</li><li>PVC、Ingress、Node、ServiceAccount 等</li></ul><p>对于 Pod 这类 namespace 级资源，如果当前集群允许访问多个 namespace，Headlamp 会针对每个 namespace 分别发起 List 请求，再把结果合并到前端的同一个资源列表中。</p><p>例如，允许访问的范围是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">default</span><br><span class="line">kube-system</span><br><span class="line">monitoring</span><br></pre></td></tr></table></figure><p>那么 Pod 数据大致就来自三个 namespace 的列表请求。若没有限制 namespace，则会使用集群范围的列表请求；最终能看到哪些资源，依然由 Kubernetes RBAC 决定。</p><h2 id="真正的搜索发生在浏览器里"><a href="#真正的搜索发生在浏览器里" class="headerlink" title="真正的搜索发生在浏览器里"></a>真正的搜索发生在浏览器里</h2><p>资源列表到达前端后，Headlamp 会把每个对象整理为可搜索项。除了资源名称外，其中还会带上资源类型、namespace、labels 和用于展示的辅助信息。</p><p>然后，它使用 <a href="https://www.fusejs.io/">Fuse.js</a> 做本地模糊匹配。</p><p>因此，输入：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">coredns kube-system</span><br></pre></td></tr></table></figure><p>并不是向 API Server 发送一个包含模糊条件的请求，而是在已加载的候选项中匹配。多个词的行为更接近 AND：结果需要同时匹配 <code>coredns</code> 和 <code>kube-system</code>，但两个词可以命中不同字段，例如一个命中资源名，另一个命中 namespace。</p><p>如果输入类似：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">app=coredns</span><br></pre></td></tr></table></figure><p>标签也会成为匹配线索。</p><p>为了控制误命中和结果数量，Headlamp 对 Fuse.js 使用了较严格的匹配阈值，并限制了最多展示的结果数。搜索结果中的命中片段也可以被高亮出来。</p><h2 id="它不只是-Kubernetes-资源搜索"><a href="#它不只是-Kubernetes-资源搜索" class="headerlink" title="它不只是 Kubernetes 资源搜索"></a>它不只是 Kubernetes 资源搜索</h2><p>Headlamp 顶部搜索框中混入的并不只有 Kubernetes 对象。它还会放入一些可以直接跳转或执行的项目，例如：</p><ul><li>集群切换</li><li>页面入口</li><li>namespace 设置</li><li>主题切换</li><li>快捷键设置</li><li>跳转到高级搜索的建议</li></ul><p>因此，它更像是“资源导航 + 命令面板”的组合，而不仅仅是一个 Pod 搜索框。</p><p><img src="search-theme.png" alt="搜索 theme 时，结果中同时出现主题切换、资源、高级搜索和 namespace 设置"></p><h2 id="为什么结果看起来很实时？"><a href="#为什么结果看起来很实时？" class="headerlink" title="为什么结果看起来很实时？"></a>为什么结果看起来很实时？</h2><p>初次获取资源列表后，Headlamp 还会借助 Kubernetes 的 Watch 机制持续接收资源变化。Pod 创建、更新或删除时，前端维护的资源集合也会随之刷新。</p><p>整个过程可以概括为：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Kubernetes List 获取初始状态</span><br><span class="line">        ↓</span><br><span class="line">Watch 接收后续资源变化</span><br><span class="line">        ↓</span><br><span class="line">前端维护当前资源视图</span><br><span class="line">        ↓</span><br><span class="line">Fuse.js 在本地完成模糊匹配</span><br><span class="line">        ↓</span><br><span class="line">跳转到资源详情或执行相应操作</span><br></pre></td></tr></table></figure><h2 id="这种实现适合什么场景？"><a href="#这种实现适合什么场景？" class="headerlink" title="这种实现适合什么场景？"></a>这种实现适合什么场景？</h2><p>这套方案的优点很明显：不需要额外部署搜索服务，搜索响应很快，也能把资源、页面和操作收进一个入口。</p><p>不过它并不是真正的集群级搜索引擎。资源量或集群数量很大时，压力主要不在 Fuse.js，而在于前端需要先拉取并持续维护大量资源清单。</p><p>所以 Headlamp 的顶部搜索更适合导航型需求：我大概知道自己要找什么资源，希望尽快跳过去。对于跨集群检索大量 YAML 内容、复杂全文查询、历史状态检索，或者几十万对象下的稳定低延迟搜索，则更适合使用独立的索引服务。</p><h2 id="一点思考"><a href="#一点思考" class="headerlink" title="一点思考"></a>一点思考</h2><p>Headlamp 的全局搜索没有依赖 Kubernetes API 的模糊查询能力，而是把 Kubernetes 的 List 和 Watch 能力与前端的 Fuse.js 搜索结合起来。一个看起来像“全局搜索”的功能，背后其实是资源缓存与前端交互的组合。</p><p>这让我想到，在云原生时代，很多基础设施都会尽量直接建立在 Kubernetes 原生能力之上：通过 List 获取初始状态，用 Watch 持续同步变化，再在自己的缓存或界面层完成所需的交互。这样做不一定能覆盖所有场景，却能少引入一套额外的组件、存储和运维成本，也天然沿用 Kubernetes 的 RBAC 与资源模型。</p><p>当然，真正的大规模全文检索仍然需要专门的索引服务；但当需求只是围绕当前可见资源做导航、筛选、聚合或简单的状态判断时，Headlamp 的思路很值得借鉴。实现功能时，不妨先问一句：能不能先用 Kubernetes 已有的 List、Watch、label 和资源状态把问题解决，再在本地维护一个足够小、足够新鲜的视图？</p><p>很多时候，答案是可以的。它不试图把自己做成搜索引擎，而是把当前可见的集群资源整理成一个足够顺手的导航入口；这种克制，反而让功能更贴合云原生系统本身的运行方式。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近在用 &lt;a href=&quot;https://headlamp.dev/&quot;&gt;Headlamp&lt;/a&gt; 管理 Kubernetes 集群时，注意到页面顶部有一个全局搜索框。&lt;/p&gt;
&lt;p&gt;输入 Pod 名称、namespace，甚至是资源类型，都能很快跳到目标资源。第一眼看上去，它很像是接入了某种集群搜索服务：把搜索词发给 Kubernetes API，然后由后端返回模糊匹配的结果。&lt;/p&gt;
&lt;p&gt;出于好奇翻了一下实现，发现它的思路其实很直接：&lt;strong&gt;先把资源列表拉到前端，再在浏览器里做模糊搜索。&lt;/strong&gt;&lt;/p&gt;</summary>
    
    
    
    
    <category term="kubernetes" scheme="https://knktc.com/tags/kubernetes/"/>
    
    <category term="headlamp" scheme="https://knktc.com/tags/headlamp/"/>
    
    <category term="搜索" scheme="https://knktc.com/tags/%E6%90%9C%E7%B4%A2/"/>
    
    <category term="fuse.js" scheme="https://knktc.com/tags/fuse-js/"/>
    
  </entry>
  
  <entry>
    <title>我用 Codex vibe coding 搓了一个 Chrome 插件：EnvMate</title>
    <link href="https://knktc.com/2026/06/12/envmate-chrome-extension/"/>
    <id>https://knktc.com/2026/06/12/envmate-chrome-extension/</id>
    <published>2026-06-12T03:20:00.000Z</published>
    <updated>2026-06-15T00:48:21.787Z</updated>
    
    <content type="html"><![CDATA[<p>最近用 Codex 搓的一个 Chrome 插件终于上架了，名字叫 <code>EnvMate</code>。</p><p>如果你想直接安装，可以去 Chrome 应用商店里搜 <code>EnvMate</code>，也可以直接点这个链接进去：</p><p><a href="https://chromewebstore.google.com/detail/envmate/mccdmjfnlkkjmcioapnehkmpgkblhiei">https://chromewebstore.google.com/detail/envmate/mccdmjfnlkkjmcioapnehkmpgkblhiei</a></p><p><img src="icon-128-display.png" alt="EnvMate logo"></p><p>它本质上是一个给“多环境切换”场景准备的 Chrome 插件，解决的是一个很具体、但又特别容易反复踩坑的问题：很多系统的开发、测试、预发、生产环境长得几乎一模一样，切来切去的时候，人很容易手滑。</p><p>所以这次我就想试试看，能不能用一套比较轻的方式，把“看清当前环境”和“顺手切换测试账号”这两件事一起解决掉。</p><p>先放一张整体配置页面的截图：</p><p><img src="envmate_overview.webp" alt="EnvMate 配置页总览"></p><span id="more"></span><h2 id="我为什么想做这个插件"><a href="#我为什么想做这个插件" class="headerlink" title="我为什么想做这个插件"></a>我为什么想做这个插件</h2><p>这些年做项目时，我越来越明显地感觉到，很多线上事故未必来自多复杂的技术问题，反而经常出在一些很朴素的地方，比如“看错环境了”。</p><p>同一个系统挂着好几套环境，页面结构、菜单位置、按钮样式都差不多。你明明以为自己在测试环境里点按钮，结果实际打开的是生产；或者本来只是想随手验证一下，最后却把操作落到了不该落的地方。</p><p>这种问题说起来不复杂，但只要你平时需要频繁在多套系统之间来回切换，就知道它有多烦。</p><p>另一个很实际的点，是最近一些内部测试系统开始往 <code>HTTPS</code> 迁移，但证书又是自签名的。这样一来，Chrome 原本那套还算顺手的用户名密码记忆和自动填充，在某些场景下就没那么稳定了。你得反复找测试账号、重新填账号密码，节奏很容易被打断。</p><p>所以我最后想做的，并不是一个很重的“环境管理平台”，而是一个足够轻、打开就能用的小插件：</p><ol><li>先让我一眼看出来当前到底在哪个环境。</li><li>再让我在需要的时候，能顺手把对应环境的测试账号填进去。</li></ol><p><code>EnvMate</code> 就是沿着这个思路做出来的。</p><h2 id="EnvMate-现在能做什么"><a href="#EnvMate-现在能做什么" class="headerlink" title="EnvMate 现在能做什么"></a>EnvMate 现在能做什么</h2><p>这个插件目前做的事情不算复杂，但基本都围着“减少误操作”和“提高切换效率”展开。</p><h3 id="1-按-URL-规则识别当前环境"><a href="#1-按-URL-规则识别当前环境" class="headerlink" title="1. 按 URL 规则识别当前环境"></a>1. 按 URL 规则识别当前环境</h3><p>你可以给不同环境配置 URL 匹配规则，比如通配符、前缀匹配或者正则。只要页面地址命中了对应规则，插件就能识别出当前页面属于哪个环境。</p><p>这样做的好处是，它不依赖页面一定要改代码，也不要求系统本身提供什么专门的环境标识。很多时候只要域名或者路径有规律，就已经够用了。</p><p><img src="env_url_pattern.webp" alt="EnvMate 的 URL 规则配置"></p><h3 id="2-用足够明显的方式把环境标出来"><a href="#2-用足够明显的方式把环境标出来" class="headerlink" title="2. 用足够明显的方式把环境标出来"></a>2. 用足够明显的方式把环境标出来</h3><p>识别出来之后，下一步就是“别让自己看漏”。</p><p>所以 <code>EnvMate</code> 现在支持几种比较直接的提示方式：</p><ul><li>页面角标</li><li>页面水印</li><li>页面标题前缀</li><li>角标和水印组合</li></ul><p>这些能力的重点其实不是“做得多精致”，而是“做得足够显眼”。</p><p>比如生产环境可以一直挂一个高对比度角标，测试环境可以铺一个淡一点的水印。这样就算页面本身几乎一模一样，你切回标签页或者重新聚焦窗口时，也能很快意识到自己现在到底在操作哪套环境。</p><p><img src="envmate_pageicon.webp" alt="页面角标效果"></p><p><img src="envmate_watermark.webp" alt="页面水印效果"></p><h3 id="3-给不同环境维护测试账号"><a href="#3-给不同环境维护测试账号" class="headerlink" title="3. 给不同环境维护测试账号"></a>3. 给不同环境维护测试账号</h3><p>光是“认出来”还不够，很多时候你还得“登进去”。</p><p>所以插件里也支持给不同环境维护测试账号。切到某个页面之后，可以直接从插件里选择对应账号来填充，不用再去翻文档、翻聊天记录，或者从密码管理器里慢慢找。</p><p>这一点对那些用了自签名 <code>HTTPS</code> 的内部系统尤其有用。因为浏览器原生自动填充在这些场景下不一定总是顺手，而把账号和环境绑定在一起，反而更符合日常测试时的使用习惯。</p><p>这里也顺便强调一下：插件只负责在你主动操作时填充信息，不会自动提交表单。</p><p><img src="env_autofill.webp" alt="插件弹窗里的环境信息和测试账号"></p><p><img src="envmate_testaccount.webp" alt="测试账号配置界面"></p><h3 id="4-用分组管理一批环境"><a href="#4-用分组管理一批环境" class="headerlink" title="4. 用分组管理一批环境"></a>4. 用分组管理一批环境</h3><p>如果你手上的系统不止一个，或者同一条业务线下面挂着开发、测试、预发、生产好几套环境，那配置很快就会变乱。</p><p>所以我在插件里也加了分组能力，可以按产品、项目或者业务线去拆开维护。这样后面环境再多一点，也不至于所有配置全堆在一起。</p><h2 id="这个插件适合什么人"><a href="#这个插件适合什么人" class="headerlink" title="这个插件适合什么人"></a>这个插件适合什么人</h2><p>如果你的日常工作里经常会遇到下面这些情况，那 <code>EnvMate</code> 大概率是有点用的：</p><ul><li>经常在开发、测试、预发、生产环境之间来回切换</li><li>多个系统长得太像，容易看错环境</li><li>需要频繁登录不同测试账号</li><li>内部系统逐步迁到自签名 <code>HTTPS</code>，浏览器原生自动填充没以前那么顺手</li></ul><p>它解决的不是一个特别宏大的问题，而是一个很具体、很高频、而且真的容易让人出错的问题。</p><h2 id="这次用-Codex-做，体验也挺有意思"><a href="#这次用-Codex-做，体验也挺有意思" class="headerlink" title="这次用 Codex 做，体验也挺有意思"></a>这次用 Codex 做，体验也挺有意思</h2><p>严格来说，这就是一次挺典型的 vibe coding。</p><p>因为我这次并不是先把所有细节都设计到很完整，再一点点照着实现，而是先把核心目标盯住，然后让 Codex 跟着我的想法一边写、一边改、一边对齐交互和细节。</p><p>这种过程和传统那种“先把需求文档写满，再按计划开发”的节奏不太一样。更像是你先把方向感讲清楚，然后不断把“不够顺手”的地方往前推，直到它真的开始像一个自己会用的工具。</p><p>对这种偏个人效率、偏工具型的项目来说，我自己觉得这种方式还挺合适的。它的好处不只是快，更重要的是，你可以比较低成本地把一个原本还只是模糊念头的东西，尽快落成一个能装进浏览器里、可以真用的插件。</p><h2 id="安装方法"><a href="#安装方法" class="headerlink" title="安装方法"></a>安装方法</h2><p>如果你只是想直接装起来用，可以去 Chrome 商店：</p><ul><li>Chrome Web Store: <a href="https://chromewebstore.google.com/detail/envmate/mccdmjfnlkkjmcioapnehkmpgkblhiei">https://chromewebstore.google.com/detail/envmate/mccdmjfnlkkjmcioapnehkmpgkblhiei</a></li></ul><p>如果你想顺手看看实现方式，也可以直接去 GitHub 上看源码：</p><ul><li>GitHub: <a href="https://github.com/knktc/EnvMate">https://github.com/knktc/EnvMate</a></li></ul><p>我还额外上传了一份到百度网盘。如果你这边直接下载插件不太方便，也可以用这个链接：</p><ul><li>百度网盘: <a href="https://pan.baidu.com/s/133Z4ofjbWXourmDe2ntzeg?pwd=fg42">https://pan.baidu.com/s/133Z4ofjbWXourmDe2ntzeg?pwd=fg42</a></li></ul><p>如果你是手动安装 <code>crx</code>，可以简单按下面这几个步骤来：</p><ol><li>在 Chrome 地址栏打开 <code>chrome://extensions/</code>。</li><li>打开右上角的“开发者模式”。</li><li>如果浏览器允许直接安装，就把下载好的 <code>crx</code> 文件拖进去，按提示确认安装。</li><li>如果你的 Chrome 不允许直接安装 <code>crx</code>，可以先把它解压出来，再点“加载已解压的扩展程序”，选择解压后的目录完成安装。</li></ol><p>如果你平时也经常在一堆看起来差不多的系统里切来切去，或者也正好被自签名 <code>HTTPS</code> 环境下不太稳定的账号填充体验折腾过，那这个插件也许正好能帮你少踩几个坑。</p><p>后面如果我再补一些更顺手的能力，比如更方便的环境切换辅助、更多页面识别细节，或者更适合团队共享的配置方式，我也会继续把它慢慢迭代下去。</p>]]></content>
    
    
    <summary type="html">记录我为什么做 EnvMate，以及这个 Chrome 插件是怎么帮我在开发、测试、预发和生产环境之间少犯错的。</summary>
    
    
    
    
    <category term="chrome" scheme="https://knktc.com/tags/chrome/"/>
    
    <category term="codex" scheme="https://knktc.com/tags/codex/"/>
    
    <category term="vibe-coding" scheme="https://knktc.com/tags/vibe-coding/"/>
    
    <category term="extension" scheme="https://knktc.com/tags/extension/"/>
    
    <category term="envmate" scheme="https://knktc.com/tags/envmate/"/>
    
  </entry>
  
  <entry>
    <title>给 PSP 3000 升级 6.61，并刷入 ARK-4 永久破解</title>
    <link href="https://knktc.com/2026/06/01/psp-3000-upgrade-to-6-61-and-install-ark-4/"/>
    <id>https://knktc.com/2026/06/01/psp-3000-upgrade-to-6-61-and-install-ark-4/</id>
    <published>2026-06-01T14:51:27.000Z</published>
    <updated>2026-08-03T02:00:04.891Z</updated>
    
    <content type="html"><![CDATA[<p>前段时间，我儿子把家里一台吃灰很多年的 PSP 翻了出来。</p><p>这是我当年用过的一台 PSP 3000。机器本身还在，但电池已经鼓包了，原来的山寨记忆棒也基本报废。换了新电池之后，我又买了一个记忆棒卡套，插上一张 TF 卡，总算把这台老机器重新点亮了。</p><p>机器能开机只是第一步。真正麻烦的是系统和破解环境都太老了。</p><p>我这台机器当年用的是 <code>PRO CFW</code>，那时候还做不到现在这样省心，每次彻底关机后都得重新刷一次。如今旧卡已经不能用了，原来的破解文件自然也跟着没了。于是我顺手查了一下现在 PSP 还在用什么方案，结果发现这个圈子居然一直还在更新。</p><p>现在比较成熟的一条路，是先把系统升级到官方最后一个正式版本 <code>6.61</code>，再安装 <code>ARK-4</code>。我这次就是按这个思路做的，整套流程走下来并不复杂，记录一下，后面如果还有人把老 PSP 翻出来，或许也能少踩点坑。</p><span id="more"></span><h2 id="先确认当前系统版本"><a href="#先确认当前系统版本" class="headerlink" title="先确认当前系统版本"></a>先确认当前系统版本</h2><p>开机后先到系统信息里看一眼。</p><p>我这台机器当时还是 <code>6.35</code>：</p><p><img src="IMG_3341.webp" alt="PSP 当前系统版本"></p><p>如果你的版本也比较老，建议先别急着折腾自制固件，先把官方系统升级到 <code>6.61</code>。这是索尼给 PSP 发布的最后一个官方固件版本，后面很多教程和工具默认也都是围绕 <code>6.61</code> 来写的。</p><h2 id="先升级到官方-6-61-固件"><a href="#先升级到官方-6-61-固件" class="headerlink" title="先升级到官方 6.61 固件"></a>先升级到官方 6.61 固件</h2><p>官方固件文件现在网上还能找到，我这次用的是这个整理站点：</p><p><a href="https://darthsternie.net/psp-firmwares/">https://darthsternie.net/psp-firmwares/</a></p><p>如果上面的地址无法访问，也可以从这个百度网盘备用地址获取：</p><p><a href="https://pan.baidu.com/s/11kOh_RLw0hOMQzV0i_6hUQ?pwd=38ac">https://pan.baidu.com/s/11kOh_RLw0hOMQzV0i_6hUQ?pwd=38ac</a></p><p>找到 <code>6.61</code> 对应的下载链接即可。有条件的话，下载后顺手校验一下文件 hash 会更稳一点。</p><p>在拷文件之前，建议先把 TF 卡插进记忆棒卡套，再插到 PSP 里格式化一次。这样目录结构和文件系统都会干净一点，后面少出一些莫名其妙的问题。</p><p>下载到的固件文件名一般是 <code>661.PBP</code>。把卡接到电脑后，在卡里创建下面这个目录：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/PSP/GAME/UPDATE</span><br></pre></td></tr></table></figure><p>然后把 <code>661.PBP</code> 重命名成 <code>EBOOT.PBP</code>，放到这个目录下面。</p><p>之后把卡重新插回 PSP，在“游戏”里的 <code>Memory Stick</code> 下就能看到升级程序了：</p><p><img src="IMG_3342.webp" alt="PSP 中的 6.61 升级程序"></p><p>按提示一路升级即可。整个过程不算久，完成后再看系统信息，就已经是 <code>6.61</code> 了：</p><p><img src="IMG_3330.webp" alt="升级到 6.61 后的系统信息"></p><p>这里多提醒一句：升级固件时一定要保证电量充足，最好插着电源，别在刷机过程中断电。</p><h2 id="再安装-ARK-4"><a href="#再安装-ARK-4" class="headerlink" title="再安装 ARK-4"></a>再安装 ARK-4</h2><p>系统升完之后，就可以开始装 <code>ARK-4</code> 了。</p><p>和很多年前在论坛里到处找破解包不一样，现在 <code>ARK-4</code> 的项目和发布版本都在 GitHub 上，资料也比以前集中得多：</p><ul><li>项目地址：<a href="https://github.com/PSP-Archive/ARK-4">https://github.com/PSP-Archive/ARK-4</a></li><li>发布页面：<a href="https://github.com/PSP-Archive/ARK-4/releases">https://github.com/PSP-Archive/ARK-4/releases</a></li></ul><p>按我写这篇文章时的情况，<code>2026-06-01</code> 能看到的最新正式版是 <code>v4.20.69 r206</code>。发布页面里也明确写了，这是 <code>ARK-4</code> 的最后一个官方版本，后续路线会转向 <code>ARK-5</code>。</p><p>下载 release 里的 <code>ARK4.zip</code> 即可：</p><p><img src="ARK4download.png" alt="ARK-4 GitHub release 下载页面"></p><h2 id="把-ARK-4-文件复制到记忆卡里"><a href="#把-ARK-4-文件复制到记忆卡里" class="headerlink" title="把 ARK-4 文件复制到记忆卡里"></a>把 ARK-4 文件复制到记忆卡里</h2><p>把 TF 卡重新接到电脑上，解压 <code>ARK4.zip</code>，然后把里面几个目录复制到对应位置。</p><p>需要的主要是这三项：</p><ol><li>把 <code>ARK_01234</code> 复制到 <code>/PSP/SAVEDATA/</code></li><li>把 <code>ARK_Loader</code> 复制到 <code>/PSP/GAME/</code></li><li>把 <code>ARK_cIPL</code> 复制到 <code>/PSP/GAME/</code>（注意这个文件夹在压缩包的 <code>PSP</code> 目录下）</li></ol><p>前两步是让系统先跑起来，第三步是给后面的永久破解做准备。</p><h2 id="先刷入临时-ARK-4"><a href="#先刷入临时-ARK-4" class="headerlink" title="先刷入临时 ARK-4"></a>先刷入临时 ARK-4</h2><p>把卡插回 PSP 后，到“游戏”里的 <code>Memory Stick</code> 下面，可以先看到 <code>ARK Loader</code>。</p><p>先运行它，把临时固件刷进去：</p><p><img src="IMG_3343.webp" alt="运行 ARK Loader"></p><p>刷好之后，再进系统信息，版本号会带上 <code>ARK-4 Live</code> 的标识。这时候其实已经可以正常跑游戏和自制程序了，只是如果彻底关机，下次还得重新执行一次 <code>ARK Loader</code>：</p><p><img src="IMG_3344.webp" alt="刷入临时 ARK-4 后的系统信息"></p><h2 id="再刷入-cIPL，做成永久破解"><a href="#再刷入-cIPL，做成永久破解" class="headerlink" title="再刷入 cIPL，做成永久破解"></a>再刷入 cIPL，做成永久破解</h2><p>如果你也想要“重启之后依然保持破解状态”的效果，就继续运行 <code>ARK cIPL Flasher</code>。</p><p>进入之后按照界面提示确认安装即可，整个过程很快，我这台机器大概几秒钟就完成了：</p><p><img src="IMG_3345.webp" alt="运行 ARK cIPL Flasher"></p><p>刷完之后再次查看系统信息，就能看到版本后面已经带上了 <code>cIPL</code>：</p><p><img src="IMG_3346.webp" alt="永久破解完成后的系统信息"></p><p>我这台 <code>PSP 3000</code> 到这里就已经实现了重启后依旧保持 ARK 的状态，不需要每次开机再手动刷一次。</p><h2 id="游戏文件放哪里"><a href="#游戏文件放哪里" class="headerlink" title="游戏文件放哪里"></a>游戏文件放哪里</h2><p>最后一步就简单了。</p><p>把下载好的 <code>PSP .iso</code> 或 <code>.cso</code> 游戏文件放到记忆卡根目录下的 <code>ISO</code> 文件夹里即可。如果没有这个目录，自己新建一个就行：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/ISO</span><br></pre></td></tr></table></figure><p>放好之后，在机器里就能直接看到游戏：</p><p><img src="IMG_3347.webp" alt="把 ISO 游戏放到记忆卡后即可识别"></p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>这次把这台老 PSP 重新折腾起来，最大的感受其实不是“破解终于成功了”，而是很多年前那种熟悉的感觉一下子又回来了。</p><p>老机器能重新开机、能重新进游戏，已经很开心；而现在这套升级和破解流程，比我当年折腾的时候也确实成熟了不少。至少对我这台 <code>PSP 3000</code> 来说，从官方 <code>6.35</code> 升到 <code>6.61</code>，再刷入 <code>ARK-4</code>，整个过程比预想中顺利很多。</p><p>如果你家里也正好还有一台吃灰的 PSP，不妨找个周末把它翻出来试试。说不定折腾完之后，最先被唤醒的不是主机，而是自己很多年前打游戏时的那段记忆。</p>]]></content>
    
    
    <summary type="html">记录一次把老 PSP 3000 从 6.35 升级到官方 6.61，再安装 ARK-4 并完成永久破解的实际过程。</summary>
    
    
    
    
    <category term="psp" scheme="https://knktc.com/tags/psp/"/>
    
    <category term="游戏机" scheme="https://knktc.com/tags/%E6%B8%B8%E6%88%8F%E6%9C%BA/"/>
    
    <category term="折腾" scheme="https://knktc.com/tags/%E6%8A%98%E8%85%BE/"/>
    
    <category term="固件" scheme="https://knktc.com/tags/%E5%9B%BA%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>我在实际项目中是怎么用 Nginx 的</title>
    <link href="https://knktc.com/2026/05/27/nginx-in-real-projects/"/>
    <id>https://knktc.com/2026/05/27/nginx-in-real-projects/</id>
    <published>2026-05-27T05:25:25.000Z</published>
    <updated>2026-05-27T05:33:02.005Z</updated>
    
    <content type="html"><![CDATA[<p>这几年做项目时，Nginx 基本一直都在。很多时候它不只是一个“转发请求的入口”，而是前端、后端、内外网边界之间那个最容易下手、也最容易快速见效的一层。</p><p>虽然这两年越来越多的场景开始用云原生方案，比如 Envoy Proxy 之类的组件来替代部分 Nginx 的工作，但回头看下来，很多问题最早还真就是靠 Nginx 先顶上、先跑通的。</p><p>所以想趁着还有印象，整理一下这些年在项目里实际用过的几种 Nginx 用法。它们不一定都算“标准答案”，但在真实环境里，确实都解决过问题。</p><span id="more"></span><h2 id="1-代理-SMTP-服务，让内网服务也能发邮件"><a href="#1-代理-SMTP-服务，让内网服务也能发邮件" class="headerlink" title="1. 代理 SMTP 服务，让内网服务也能发邮件"></a>1. 代理 SMTP 服务，让内网服务也能发邮件</h2><p>这个场景在客户环境里挺常见的。</p><p>有些系统部署在内网，业务服务本身不能直接访问外部网络，但偏偏又需要发邮件。长期来看，当然还是应该让对应节点具备访问 SMTP 服务器的能力；但如果只是临时救急，或者短期内没法推动网络策略调整，也可以让 Nginx 先做一层 TCP 代理。</p><p>配置大概像这样：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">load_module</span> /usr/lib/nginx/modules/ngx_stream_module.so;</span><br><span class="line"></span><br><span class="line"><span class="section">stream</span> &#123;</span><br><span class="line">    <span class="section">server</span> &#123;</span><br><span class="line">        <span class="attribute">listen</span> <span class="number">1587</span>;</span><br><span class="line">        <span class="attribute">proxy_pass</span> mail.example.com:<span class="number">587</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样一来，内网服务就不需要直接连外部 SMTP 服务器了，只要把 <code>Nginx 所在节点 IP + 1587</code> 当成 SMTP 地址使用即可。</p><p>这个方案的核心点在于：这里走的不是 HTTP 代理，而是 <code>stream</code> 模块提供的四层转发能力。</p><p>如果你用的是 Ubuntu 里通过 <code>apt</code> 安装的 Nginx，通常 <code>ngx_stream_module</code> 都已经编译进去了；如果是自己编译 Nginx，就要确认编译参数里带了 <code>--with-stream</code>。</p><h2 id="2-代理企业微信、钉钉、飞书这类外部通知接口"><a href="#2-代理企业微信、钉钉、飞书这类外部通知接口" class="headerlink" title="2. 代理企业微信、钉钉、飞书这类外部通知接口"></a>2. 代理企业微信、钉钉、飞书这类外部通知接口</h2><p>和发邮件是同一类问题。</p><p>很多部署在内网的服务，不只是要发邮件，还要给企业微信、钉钉、飞书这类企业通信工具发通知消息。服务本身出不了网时，也可以把这些请求统一转到一台可访问外网的 Nginx 节点上。</p><p>以企业微信机器人为例，它的 Webhook 地址一般长这样：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=3e8b437b-419f-46b2-9495-391cf17a6719</span><br></pre></td></tr></table></figure><p>如果内网服务不能直接访问外网，可以在 Nginx 上做一个反向代理：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line">    <span class="attribute">server_name</span> wechat-proxy;</span><br><span class="line"></span><br><span class="line">    <span class="attribute">location</span> /cgi-bin/webhook &#123;</span><br><span class="line">        <span class="attribute">proxy_pass</span> https://qyapi.weixin.qq.com;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样，内网服务发送消息时，就可以把原始地址替换成类似下面这个内部地址：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://wechat-proxy/cgi-bin/webhook/send?key=3e8b437b-419f-46b2-9495-391cf17a6719</span><br></pre></td></tr></table></figure><p>本质上，这还是“把不能出网的服务请求，借道一台能出网的节点发出去”。</p><p>这种做法的好处是落地快，应用侧改动也不大；缺点也很明显，它更像一种工程上的折中方案。如果后面这类依赖越来越多，还是应该把统一出口、代理策略或者网络规划做得更正式一些。</p><h2 id="3-用-auth-request-给静态资源和内部服务补一层认证"><a href="#3-用-auth-request-给静态资源和内部服务补一层认证" class="headerlink" title="3. 用 auth_request 给静态资源和内部服务补一层认证"></a>3. 用 <code>auth_request</code> 给静态资源和内部服务补一层认证</h2><p>还有一种情况也挺常见：有些内容本身很简单，比如 API 文档、模板文件、安装包，甚至是某个前端直接托管出来的小工具页。开发时大家往往觉得“这就是个静态文件”，于是直接通过 Nginx 暴露了。</p><p>但到了客户环境里，这类路径经常会被安全扫描盯上。哪怕文件本身不敏感，只要是“未认证即可访问”，往往就容易被判定为风险项。</p><p>这时候，<code>auth_request</code> 就很实用了。它可以让 Nginx 在真正返回资源之前，先去请求一个认证接口，认证通过后再继续返回内容。</p><p>例如：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">location</span> /docs &#123;</span><br><span class="line">    <span class="attribute">auth_request</span> /auth;</span><br><span class="line">    <span class="attribute">proxy_pass</span> http://frontend/docs;</span><br><span class="line">    <span class="attribute">proxy_set_header</span> Host $host;</span><br><span class="line">    <span class="attribute">error_page</span> <span class="number">401</span> = @<span class="literal">error</span>401;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="attribute">location</span> = /auth &#123;</span><br><span class="line">    <span class="attribute">proxy_pass</span> http://backend/auth;</span><br><span class="line">    <span class="attribute">proxy_pass_request_body</span> <span class="literal">off</span>;</span><br><span class="line">    <span class="attribute">proxy_set_header</span> Content-Length <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="attribute">proxy_set_header</span> X-Original-URI $request_uri;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="attribute">location</span> @<span class="literal">error</span>401 &#123;</span><br><span class="line">    <span class="attribute">return</span> <span class="number">302</span> /login;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个思路的好处是，后端只需要提供一个专门的鉴权接口，Nginx 就可以把认证逻辑挂到多个入口前面。</p><p>除了静态资源，这个方案也很适合那些“不想自己重复造认证逻辑”的内部服务。服务本身只管功能，对外访问统一经过 Nginx，由 Nginx 先做认证，再把请求放进去。</p><p>另外，认证接口还可以顺便返回一些认证后的上下文信息，再由 Nginx 透传给后端使用。比如认证接口返回了用户名：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">auth_request_set</span> $username $upstream_http_x_username;</span><br><span class="line"><span class="attribute">proxy_set_header</span> X-Username $username;</span><br></pre></td></tr></table></figure><p>这样后面的服务就可以直接读取 <code>X-Username</code>，不需要自己再重复解析一遍登录态。</p><h2 id="4-用-add-header-Set-Cookie-做一层够用的单点登录"><a href="#4-用-add-header-Set-Cookie-做一层够用的单点登录" class="headerlink" title="4. 用 add_header Set-Cookie 做一层够用的单点登录"></a>4. 用 <code>add_header Set-Cookie</code> 做一层够用的单点登录</h2><p>这些年接过不少客户环境里的单点登录需求，最大的感受其实不是“标准方案很多”，而是“真正完全按标准落地的并不多”。</p><p>很多项目一到集成阶段，认证链路里总会被加上各种定制要求。理论上可以讲 OAuth2、OIDC、SAML，但真到现场，往往还是得围着客户现有流程做适配。</p><p>在这种背景下，我们有时会采用一个比较直接的办法：让系统后端先为指定用户创建好登录态，再通过 Nginx 返回 <code>Set-Cookie</code>，把 session 强行种到当前域下，达到“点一个链接就登录”的效果。</p><p>假设系统用 <code>session_key</code> 这个 Cookie 保存登录态，那么可以在 Nginx 里加一个类似的入口：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">location</span> <span class="regexp">~ ^/set-session/(?&lt;session_key&gt;\w+)$</span> &#123;</span><br><span class="line">    <span class="attribute">add_header</span> Set-Cookie <span class="string">&quot;session_key=$session_key; HttpOnly; Max-Age=21600; Path=/; SameSite=Lax&quot;</span>;</span><br><span class="line">    <span class="attribute">return</span> <span class="number">302</span> /;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>配合方式也很直接：</p><ol><li>业务后端提供一个管理员接口，用来为指定用户创建 session。</li><li>这个接口返回生成好的 <code>session_key</code>。</li><li>调用方把 <code>session_key</code> 拼到 <code>/set-session/&lt;session_key&gt;</code> 这个地址里。</li><li>用户浏览器访问该地址后，Nginx 回写 Cookie，并跳转到首页。</li></ol><p>例如系统地址是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://example.com</span><br></pre></td></tr></table></figure><p>如果生成的 <code>session_key</code> 是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">d79769307e7e443ca7abfd780e89217e</span><br></pre></td></tr></table></figure><p>那么最终访问的地址就会是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://example.com/set-session/d79769307e7e443ca7abfd780e89217e</span><br></pre></td></tr></table></figure><p>浏览器访问后，就会在当前域下写入对应的 <code>session_key</code>，随后跳回首页，从用户视角看起来就像一次“无感登录”。</p><p>这套方式当然谈不上通用，也不一定适合所有系统，但在一些强定制、强集成、时间又比较紧的项目里，确实很好用。它最大的价值不在于优雅，而在于实现简单、改动集中，而且足够可控。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>回头看这些例子，会发现 Nginx 在项目里真正有价值的地方，往往不是“它能不能把请求转过去”，而是它能不能在系统边界上帮你快速补上一层能力。</p><p>有时候是补网络出口，有时候是补认证，有时候是补一段不那么标准、但又必须落地的集成逻辑。</p><p>所以我现在对 Nginx 的感觉一直挺朴素的：它未必是每个问题的最终解法，但在很多实际项目里，它确实是那个最先把事情顶起来的工具。</p>]]></content>
    
    
    <summary type="html">结合几个真实项目场景，记录 Nginx 在代理外网服务、统一认证和简化单点登录上的一些实用做法。</summary>
    
    
    
    
    <category term="nginx" scheme="https://knktc.com/tags/nginx/"/>
    
    <category term="运维" scheme="https://knktc.com/tags/%E8%BF%90%E7%BB%B4/"/>
    
    <category term="认证" scheme="https://knktc.com/tags/%E8%AE%A4%E8%AF%81/"/>
    
    <category term="代理" scheme="https://knktc.com/tags/%E4%BB%A3%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>如何注册一个可用的美区 Apple 账号</title>
    <link href="https://knktc.com/2026/05/22/register-us-apple-id/"/>
    <id>https://knktc.com/2026/05/22/register-us-apple-id/</id>
    <published>2026-05-22T03:30:00.000Z</published>
    <updated>2026-05-22T14:54:00.263Z</updated>
    
    <content type="html"><![CDATA[<p>最近 Codex 已经支持远程控制了，但 ChatGPT App 在国区还是没有上架。类似 Gemini 这种应用，在国区 App Store 里也不太好下。</p><p>如果你用的是 iPhone，那准备一个美区 Apple 账号，基本就成了绕不过去的事。</p><p>我这次用下来，比较省事的一条路不是直接注册美区账号，而是先搞一个全新的国区账号，再把地区切到美国。这样至少不用一上来就想办法找美国手机号接码，整体会顺不少。</p><span id="more"></span><p>先说两个前提：</p><ol><li>Apple 这类策略变得挺快的，今天能走通，不代表之后一直都能走通。</li><li>最好直接用新号折腾。老号如果挂着订阅、余额或者购买记录，换区的时候往往会很麻烦。</li></ol><p>Apple 账号注册地址：</p><p><a href="https://account.apple.com/account">https://account.apple.com/account</a></p><h2 id="先注册一个新的国区账号"><a href="#先注册一个新的国区账号" class="headerlink" title="先注册一个新的国区账号"></a>先注册一个新的国区账号</h2><p>不要一开始就直接选美国地区注册。现在如果这么干，很多时候会要求你用美国手机号验证，门槛一下就上去了。</p><p>所以更稳一点的办法，是先正常注册一个国区账号，用自己的手机号完成验证。</p><p><img src="appleaccount_1.png" alt="Apple 账号注册页面"></p><h2 id="在-iPhone-上登录这个新账号"><a href="#在-iPhone-上登录这个新账号" class="headerlink" title="在 iPhone 上登录这个新账号"></a>在 iPhone 上登录这个新账号</h2><p>注册完之后，打开 iPhone 上的 App Store，点右上角头像进去。</p><p>先把当前正在用的账号退出掉，再登录刚注册的这个新账号。</p><p><img src="logout.png" alt="退出当前 App Store 账号"></p><p>登进去之后可以先看一眼区域，这时候正常还是中国区。</p><p><img src="region-cn-1.png" alt="当前账号区域仍为中国"></p><h2 id="再把地区切到美国"><a href="#再把地区切到美国" class="headerlink" title="再把地区切到美国"></a>再把地区切到美国</h2><p>这一步才是关键。</p><p>切之前，一定注意要让手机全局翻墙到美国 IP，然后再进入账号设置里的“国家/地区”，把地区改成“美国”。</p><p>这里最重要的是“付款方式”这一项。如果你能看到“无”，直接选“无”就行，这样就不用填信用卡或者 PayPal 了。</p><p>如果没有看到“无”，那就表明你的 IP 还没翻到美国，可以检查下当前的网络，不行重启试试。</p><p><img src="choose_none.png" alt="付款方式选择“无”"></p><p>另外有几个细节也顺手注意一下：</p><ol><li>账单地址可以找个美国地址生成器随便生成一个能用的。</li><li>最好选免税州，比如俄勒冈州，后面真要付费会省一点事。</li><li>名字也尽量写成英文，能少掉一些奇怪的校验问题。</li></ol><p>把这些都填完保存，如果没报错，基本就已经切到美区了。</p><h2 id="看下是不是已经变成美区了"><a href="#看下是不是已经变成美区了" class="headerlink" title="看下是不是已经变成美区了"></a>看下是不是已经变成美区了</h2><p>切完以后再回到 App Store 搜一下，一般就能看到国区里没有的应用了，比如 ChatGPT 和 Gemini。</p><p><img src="chatgpt_and_gemini.png" alt="美区可搜索到 ChatGPT 和 Gemini"></p><p>像 Apple Arcade 这种内容，这时候通常也已经能看到了。</p><p><img src="arcade.png" alt="Apple Arcade 已可用"></p><p>顺便说一句，应用下载的时候一般不需要一直全局代理，所以也不用太担心流量问题。</p><h2 id="最后补几句"><a href="#最后补几句" class="headerlink" title="最后补几句"></a>最后补几句</h2><ol><li>这个美区账号的密码一定要记好，后面更新应用时大概率还会让你重新登录。</li><li>这种口子什么时候被 Apple 收紧不好说，能注册的时候尽早注册。</li><li>如果你只是为了下载少数几个应用，平时把主账号和这个外区账号分开用，会省心很多。</li></ol><p>如果你的目的就是装个 ChatGPT、Gemini 之类的应用，这套办法现在看下来还是比较直接的。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近 Codex 已经支持远程控制了，但 ChatGPT App 在国区还是没有上架。类似 Gemini 这种应用，在国区 App Store 里也不太好下。&lt;/p&gt;
&lt;p&gt;如果你用的是 iPhone，那准备一个美区 Apple 账号，基本就成了绕不过去的事。&lt;/p&gt;
&lt;p&gt;我这次用下来，比较省事的一条路不是直接注册美区账号，而是先搞一个全新的国区账号，再把地区切到美国。这样至少不用一上来就想办法找美国手机号接码，整体会顺不少。&lt;/p&gt;</summary>
    
    
    
    
    <category term="apple" scheme="https://knktc.com/tags/apple/"/>
    
    <category term="apple-id" scheme="https://knktc.com/tags/apple-id/"/>
    
    <category term="app-store" scheme="https://knktc.com/tags/app-store/"/>
    
    <category term="ios" scheme="https://knktc.com/tags/ios/"/>
    
  </entry>
  
  <entry>
    <title>我用 Codex vibe coding 搓了一个顺手的工具站：tools.knktc.com</title>
    <link href="https://knktc.com/2026/05/21/tools-knktc-codex-vibe-coding-intro/"/>
    <id>https://knktc.com/2026/05/21/tools-knktc-codex-vibe-coding-intro/</id>
    <published>2026-05-21T06:05:00.000Z</published>
    <updated>2026-05-21T05:58:46.287Z</updated>
    
    <content type="html"><![CDATA[<p>前段时间我一直有个很朴素的念头：平时排查问题时，总会反复打开一堆“小而碎”的在线工具。查一下出口 IP，转一下时间戳，看一下浏览器时区，顺手再格式化一段 JSON。每个需求都不大，但凑在一起，就很像在浏览器里打零工。</p><p>于是这次我换了个思路：不如直接用 Codex 来一把 vibe coding，把这些高频小工具收拢成一个自己看着顺眼、用起来顺手的小站。</p><p>它现在就在这里：</p><p><a href="https://tools.knktc.com/">https://tools.knktc.com</a></p><span id="more"></span><p>先放一张首页截图：</p><p><img src="home-zh.png" alt="tools.knktc.com 首页"></p><p>这个站目前不追求“大而全”，更像一个我自己会长期开着用的工具抽屉。思路很简单：</p><ol><li>常用的小工具尽量放在一个页面里，少跳转，少找书签。</li><li>能在浏览器本地完成的事情，就尽量本地完成。</li><li>少一点花里胡哨，多一点“打开就能用”。</li></ol><p>现在已经放进去的工具，基本都是平时开发和排查时最容易顺手掏出来的那些，比如：</p><ul><li>客户端 IP</li><li>IP 与掩码检查</li><li>Base64 转换</li><li>JSON Pretty</li><li>URL 编解码</li><li>时间戳转换</li><li>本地时间检测</li><li>浏览器信息与时区</li><li>UUID 生成器</li></ul><p>其中有几个我自己还挺喜欢。</p><p>先说“本地时间检测”。这个工具是拿浏览器当前时间去和服务端标准时间做对比，适合排查一些看起来莫名其妙、实际上和时间有关的问题，比如登录异常、接口签名失效、证书时间不对之类。</p><p><img src="clock-skew.png" alt="本地时间检测工具"></p><p>这种工具的乐趣就在于：平时你未必天天用，但真碰上问题的时候，它能帮你少绕两圈。</p><p>另一个我挺常用的是“浏览器信息与时区”。有时候用户反馈“我这里时间不对”“页面显示怪怪的”“怎么跟你那边不一样”，很多时候先看一下浏览器时区、语言、UA、屏幕尺寸，心里就会有底。</p><p><img src="browser-info.png" alt="浏览器信息与时区工具"></p><p>而且这块我顺手把 <code>Clock drift</code> 也塞进去了。也就是说，不只是看浏览器报了什么，还能顺便看一下它和服务端参考时间差了多少毫秒。这个细节很小，但排查起来挺省心。</p><p>这次用 Codex 来做，体验也挺有意思。和传统那种“先列个很完整的需求文档，再慢慢开工”的方式相比，vibe coding 更像是：</p><ul><li>先把想法说清楚；</li><li>再让 Codex 一边写，一边改，一边对齐细节；</li><li>不满意就继续调，直到页面、交互和功能都顺眼为止。</li></ul><p>整个过程有点像和一个不会喊累的搭子结对编程。你负责提出“这个地方应该再顺一点”“这里应该更直观”，它负责快速把想法变成能跑的东西。节奏对了以后，推进速度确实很舒服。</p><p>当然，这个站现在也还远没到“完成体”。我更愿意把它当成一个会持续长大的小工具箱：谁知道后面还会不会继续往里面塞一些更奇怪但又真的有用的小玩意。</p><p>如果你也刚好经常要查这些东西，可以直接收藏一下：</p><p><a href="https://tools.knktc.com/">https://tools.knktc.com</a></p><p>至少以后想查个 IP、转个时间戳、看下本地时间是不是飘了，不用再满世界翻页面了。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;前段时间我一直有个很朴素的念头：平时排查问题时，总会反复打开一堆“小而碎”的在线工具。查一下出口 IP，转一下时间戳，看一下浏览器时区，顺手再格式化一段 JSON。每个需求都不大，但凑在一起，就很像在浏览器里打零工。&lt;/p&gt;
&lt;p&gt;于是这次我换了个思路：不如直接用 Codex 来一把 vibe coding，把这些高频小工具收拢成一个自己看着顺眼、用起来顺手的小站。&lt;/p&gt;
&lt;p&gt;它现在就在这里：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tools.knktc.com/&quot;&gt;https://tools.knktc.com&lt;/a&gt;&lt;/p&gt;</summary>
    
    
    
    
    <category term="codex" scheme="https://knktc.com/tags/codex/"/>
    
    <category term="vibe-coding" scheme="https://knktc.com/tags/vibe-coding/"/>
    
    <category term="tools" scheme="https://knktc.com/tags/tools/"/>
    
    <category term="nextjs" scheme="https://knktc.com/tags/nextjs/"/>
    
    <category term="golang" scheme="https://knktc.com/tags/golang/"/>
    
  </entry>
  
  <entry>
    <title>关闭 VS Code 中 GitHub Copilot 自动添加 Co-authored-by</title>
    <link href="https://knktc.com/2026/05/06/disable-vscode-copilot-ai-coauthor/"/>
    <id>https://knktc.com/2026/05/06/disable-vscode-copilot-ai-coauthor/</id>
    <published>2026-05-06T14:00:00.000Z</published>
    <updated>2026-05-06T04:16:22.923Z</updated>
    
    <content type="html"><![CDATA[<p>最近发现一个有点微妙的小变化：通过 VS Code + GitHub Copilot 提交代码以后，提交信息里多了一行：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Co-authored-by: Copilot &lt;copilot@github.com&gt;</span><br></pre></td></tr></table></figure><span id="more"></span><p>现象大概是这样：</p><p><img src="info_info_commint.png" alt="提交信息中自动添加了 Copilot co-author"></p><p>我第一反应是：原来微软又偷偷搞了点小动作。</p><p>这个设置的意思是，当你使用 VS Code 里的 Copilot Chat 或 Agent 参与代码修改并提交时，VS Code 会自动给 commit message 加上 AI co-author 信息。对于某些协作场景，这也许算是一种透明披露；但对于日常个人提交来说，它很容易让提交历史变得有点吵，而且这个行为默认开启时并不算特别显眼。</p><p>关闭方法也很简单：</p><ol><li>打开 VS Code Settings。</li><li>搜索 <code>Add AI Co Author</code>。</li><li>找到 <code>Git: Add AI Co Author</code>。</li><li>将默认的 <code>chatAndAgent</code> 改成 <code>off</code>。</li></ol><p>配置界面如下：</p><p><img src="turnoffaicoauthor.png" alt="关闭 VS Code Git Add AI Co Author 设置"></p><p>改完以后，再通过 VS Code 提交代码，就不会继续自动追加这行：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Co-authored-by: Copilot &lt;copilot@github.com&gt;</span><br></pre></td></tr></table></figure><p>如果团队本来就希望保留 AI 参与痕迹，那可以继续开着；如果只是想保持提交信息干净一点，把它关掉就好。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近发现一个有点微妙的小变化：通过 VS Code + GitHub Copilot 提交代码以后，提交信息里多了一行：&lt;/p&gt;
&lt;figure class=&quot;highlight text&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;Co-authored-by: Copilot &amp;lt;copilot@github.com&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
    <category term="vscode" scheme="https://knktc.com/tags/vscode/"/>
    
    <category term="github" scheme="https://knktc.com/tags/github/"/>
    
    <category term="copilot" scheme="https://knktc.com/tags/copilot/"/>
    
    <category term="git" scheme="https://knktc.com/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>tiny-requestbin：一个用 Go 编写的轻量级 HTTP 请求调试工具</title>
    <link href="https://knktc.com/2026/03/07/tiny-requestbin-introduction/"/>
    <id>https://knktc.com/2026/03/07/tiny-requestbin-introduction/</id>
    <published>2026-03-07T04:25:23.000Z</published>
    <updated>2026-04-22T01:14:27.689Z</updated>
    
    <content type="html"><![CDATA[<p>最近整理自己的开源项目时，我重新看了一遍 <a href="https://github.com/knktc/tiny-requestbin">tiny-requestbin</a> 的 GitHub 页面，发现它已经不只是一个“能收请求的小工具”，而是一个非常适合本地调试、Webhook 联调和临时排查 HTTP 请求问题的轻量级工具。这个项目使用 Go 编写，目标非常明确：<strong>尽量少的依赖、尽量快地启动、尽量直观地查看收到的请求内容</strong>。</p><p>如果你经常需要验证第三方回调、调试自建服务的入站请求，或者只是想临时抓一把 HTTP 请求看看内容，那它应该会很顺手。</p><span id="more"></span><h2 id="这个项目是做什么的"><a href="#这个项目是做什么的" class="headerlink" title="这个项目是做什么的"></a>这个项目是做什么的</h2><p>tiny-requestbin 是一个 <strong>轻量级 HTTP 请求检查与调试工具</strong>。启动之后，它会捕获发往当前服务的 HTTP 请求，并把这些请求展示出来，方便开发者查看：</p><ul><li>请求方法</li><li>请求路径</li><li>请求头</li><li>请求体</li><li>以及整体请求的详细内容</li></ul><p>项目当前的几个核心特点也在 README 里写得很清楚：</p><ul><li><strong>轻量且快速</strong>：实现简单，依赖极少</li><li><strong>请求检查</strong>：可以查看传入 HTTP 请求的详细信息</li><li><strong>Web 界面</strong>：通过浏览器查看捕获的请求</li><li><strong>CLI 模式</strong>：也可以直接在终端打印请求内容</li><li><strong>本地运行</strong>：所有数据都留在本机，无需依赖外部服务</li></ul><p>我自己很喜欢这类“功能边界清晰”的工具：不追求大而全，而是把“看请求”这件事做好。</p><h2 id="安装方式很全"><a href="#安装方式很全" class="headerlink" title="安装方式很全"></a>安装方式很全</h2><p>tiny-requestbin 的仓库首页提供了几种常见安装方式，基本覆盖了本地调试和服务部署的主要场景。</p><h3 id="1-Docker-方式"><a href="#1-Docker-方式" class="headerlink" title="1. Docker 方式"></a>1. Docker 方式</h3><p>如果只是想快速跑起来，README 推荐直接使用 Docker：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -p 8282:8282 knktc/tiny-requestbin</span><br></pre></td></tr></table></figure><p>也可以带上自定义参数运行，例如自定义监听地址、端口和最大保存请求数：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -p 8282:8282 knktc/tiny-requestbin -port 8282 -listen 0.0.0.0 -max 1000</span><br></pre></td></tr></table></figure><p>此外仓库里也提供了 <code>docker-compose.yml</code>，所以用 <code>docker-compose up -d</code> 也能直接启动。</p><h3 id="2-Helm-安装"><a href="#2-Helm-安装" class="headerlink" title="2. Helm 安装"></a>2. Helm 安装</h3><p>如果你有 Kubernetes 环境，项目还提供了 Helm Chart，可以直接安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">helm install my-requestbin helm/tiny-requestbin/</span><br></pre></td></tr></table></figure><p>README 里还给了自定义 <code>config.max</code>、<code>service.type</code> 和启用 HTTPRoute 的示例，对 K8s 用户来说很友好。</p><h3 id="3-go-install"><a href="#3-go-install" class="headerlink" title="3. go install"></a>3. go install</h3><p>既然它本身就是 Go 项目，直接安装也很自然：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go install github.com/knktc/tiny-requestbin@latest</span><br></pre></td></tr></table></figure><h3 id="4-从源码构建"><a href="#4-从源码构建" class="headerlink" title="4. 从源码构建"></a>4. 从源码构建</h3><p>如果你想自己编译，也很简单：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/knktc/tiny-requestbin.git</span><br><span class="line"><span class="built_in">cd</span> tiny-requestbin</span><br><span class="line">go build</span><br></pre></td></tr></table></figure><h2 id="怎么使用"><a href="#怎么使用" class="headerlink" title="怎么使用"></a>怎么使用</h2><p>项目默认启动方式非常直接：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tiny-requestbin</span><br></pre></td></tr></table></figure><p>默认参数如下：</p><ul><li><code>-port</code>：监听端口，默认 <code>8282</code></li><li><code>-listen</code>：监听地址，默认 <code>127.0.0.1</code></li><li><code>-max</code>：最大保存请求数量，默认 <code>100</code></li><li><code>-cli</code>：是否启用 CLI 输出模式，默认 <code>false</code></li></ul><p>比如下面这个命令，会把服务监听在 <code>0.0.0.0:9000</code>，最多保存 1000 条请求，同时把请求内容打印到终端：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tiny-requestbin -port 9000 -listen 0.0.0.0 -max 1000 -cli</span><br></pre></td></tr></table></figure><p>它的工作流程也很简单：</p><ol><li>启动服务</li><li>把请求发到 <code>http://[监听地址]:[端口]/任意路径</code></li><li>在浏览器访问根路径查看已捕获请求</li><li>如果开启了 <code>-cli</code>，终端里也会同步输出请求内容</li></ol><p>这意味着它既适合本地临时排查，也很适合作为内网环境中的一个“小型请求接收站”。</p><h2 id="两种使用体验：终端和-Web"><a href="#两种使用体验：终端和-Web" class="headerlink" title="两种使用体验：终端和 Web"></a>两种使用体验：终端和 Web</h2><p>仓库首页给了两张截图，我也顺手放到这篇文章里，方便直接感受它的实际效果。</p><h3 id="CLI-模式截图"><a href="#CLI-模式截图" class="headerlink" title="CLI 模式截图"></a>CLI 模式截图</h3><p>启用 <code>-cli</code> 后，收到的 HTTP 请求会直接以格式化后的方式打印在终端里：</p><p><img src="run_in_cmd.png" alt="CLI 模式截图"></p><p>这个模式特别适合在服务器、容器或远程终端里快速观察请求，不需要额外打开浏览器。</p><h3 id="Web-界面截图"><a href="#Web-界面截图" class="headerlink" title="Web 界面截图"></a>Web 界面截图</h3><p>如果更喜欢图形化浏览方式，可以直接访问 Web 页面查看历史请求：</p><p><img src="webpage.png" alt="Web 界面截图"></p><p>从截图来看，界面风格非常克制，重点就是把请求内容清楚地展示出来，没有多余干扰。</p><h2 id="适合哪些场景"><a href="#适合哪些场景" class="headerlink" title="适合哪些场景"></a>适合哪些场景</h2><p>结合仓库首页信息，我觉得 tiny-requestbin 很适合下面这些场景：</p><ul><li>调试第三方平台的 Webhook 回调</li><li>验证某个服务到底发了什么 HTTP 请求</li><li>本地开发时快速搭一个请求接收端</li><li>在容器/Kubernetes 环境中临时观察请求流量</li><li>在终端环境下通过 <code>-cli</code> 模式直接查看请求细节</li></ul><p>尤其是它强调 <strong>Local Only</strong>，所有数据都保留在本机，这一点对很多内部调试场景来说非常安心。</p><h2 id="额外值得一提的几点"><a href="#额外值得一提的几点" class="headerlink" title="额外值得一提的几点"></a>额外值得一提的几点</h2><p>GitHub 页面里还有几个我觉得挺加分的信息：</p><ul><li>项目镜像支持 <strong>多架构</strong>，包括 <code>linux/amd64</code> 和 <code>linux/arm64</code></li><li>多架构镜像会通过 GitHub Actions 自动构建发布</li><li>仓库里同时提供了 Docker、Helm、源码构建几种入口</li><li>项目采用 <strong>MIT License</strong>，使用和二次分发都比较友好</li></ul><p>另外，README 里还提到这个项目最开始由 Gemini 起步，后续由 GitHub Copilot 持续修改，这一点也挺符合这个项目本身“轻量、直接、快速迭代”的气质。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>如果你正好在找一个 <strong>Go 写的、轻量、可本地运行、同时支持 Web 和 CLI 的 HTTP 请求调试工具</strong>，那我觉得 <a href="https://github.com/knktc/tiny-requestbin">tiny-requestbin</a> 值得试一试。</p><p>项目地址：</p><ul><li>GitHub: <a href="https://github.com/knktc/tiny-requestbin">https://github.com/knktc/tiny-requestbin</a></li></ul><p>欢迎去点个 star，也欢迎提 Issue 或 PR，一起把这个小工具打磨得更顺手。</p>]]></content>
    
    
    <summary type="html">一个适合本地调试、Webhook 联调和临时排查 HTTP 请求问题的轻量级 Go 工具，支持 Web 界面和 CLI 两种查看方式。</summary>
    
    
    
    
    <category term="go" scheme="https://knktc.com/tags/go/"/>
    
    <category term="tiny-requestbin" scheme="https://knktc.com/tags/tiny-requestbin/"/>
    
    <category term="requestbin" scheme="https://knktc.com/tags/requestbin/"/>
    
    <category term="开源项目" scheme="https://knktc.com/tags/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE/"/>
    
  </entry>
  
  <entry>
    <title>使用 pymemcache 别忘了设置 ignore_exc</title>
    <link href="https://knktc.com/2025/01/26/pymemcache-ignore-exc/"/>
    <id>https://knktc.com/2025/01/26/pymemcache-ignore-exc/</id>
    <published>2025-01-26T13:07:38.000Z</published>
    <updated>2026-04-22T01:14:27.744Z</updated>
    
    <content type="html"><![CDATA[<p>我们在把 Django 升级到 4.2 版本以后，开始把 python-memcached 给换成 pymemcache。结果换完之后，发现怎么默认配置情况下不能高可用了，如果配置了多个 memcached 的后端，挂了一个后，使用缓存相关的代码直接会报错。</p><p>查了下官方文档才发现，pymemcache 默认情况下遇到连接失败就是会抛出异常，除非是手动指定下 ignore_exc = True。</p><p>于是，将 Django CACHES 的配置修改成这样，在 OPTION 中加入相关配置就好了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">CACHES &#x3D; &#123;</span><br><span class="line">    &#39;default&#39;: &#123;</span><br><span class="line">        &#39;BACKEND&#39;: &#39;django.core.cache.backends.memcached.PyMemcacheCache&#39;,</span><br><span class="line">        &#39;LOCATION&#39;: [</span><br><span class="line">            &#39;host1:11211&#39;,</span><br><span class="line">            &#39;host2&#39;:11211,</span><br><span class="line">        ],</span><br><span class="line">        &#39;OPTIONS&#39;: &#123;</span><br><span class="line">            &#39;ignore_exc&#39;: True,  # 设置成 True</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Django 从 python-memcached 切换到 pymemcache 后，如果希望 memcached 节点故障时不直接抛异常，记得设置 ignore_exc=True。</summary>
    
    
    
    
    <category term="python" scheme="https://knktc.com/tags/python/"/>
    
    <category term="django" scheme="https://knktc.com/tags/django/"/>
    
    <category term="memcache" scheme="https://knktc.com/tags/memcache/"/>
    
    <category term="pymemcache" scheme="https://knktc.com/tags/pymemcache/"/>
    
    <category term="ignore_exc" scheme="https://knktc.com/tags/ignore-exc/"/>
    
    <category term="异常" scheme="https://knktc.com/tags/%E5%BC%82%E5%B8%B8/"/>
    
  </entry>
  
  <entry>
    <title>申威架构(sw_64)的Kylin V10系统上安装pandas</title>
    <link href="https://knktc.com/2023/05/06/install-pandas-on-kylin-v10-with-sw64-cpu/"/>
    <id>https://knktc.com/2023/05/06/install-pandas-on-kylin-v10-with-sw64-cpu/</id>
    <published>2023-05-06T06:08:14.000Z</published>
    <updated>2023-05-11T01:22:29.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近在做一些国产化适配的工作，让自己的系统能跑在申威架构的CPU上。这几年国产CPU如雨后春笋一般涌现出来，感慨一下，放我刚工作的那几年真是想都不敢想。</p><p>申威架构的CPU与ARM架构的CPU不同，更加小众，国内也就UOS和Kylin对其有支持。客户提供的操作系统是Kylin V10的SW_64版本，本文就记录下在这个架构CPU+Kylin V10环境下装Pandas遇到的问题。</p><span id="more"></span><h2 id="创建venv"><a href="#创建venv" class="headerlink" title="创建venv"></a>创建venv</h2><p>Kylin V10自带的python3版本是3.7.4，先创建个venv:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">python3 -m venv --copies knktc-env</span><br><span class="line">source knktc-env&#x2F;bin&#x2F;active</span><br></pre></td></tr></table></figure><h2 id="安装numpy"><a href="#安装numpy" class="headerlink" title="安装numpy"></a>安装numpy</h2><p>下意识地直接用pip来安装numpy: </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install numpy</span><br></pre></td></tr></table></figure><p>一顿输出后，报错了：</p><blockquote><p>error: #error Unknown CPU, please report this to numpy maintainers with information about your platform (OS, CPU and compiler)</p></blockquote><p>果然有够小众的，识别不出来这个CPU，所以编译无法进行。</p><p>网上查了半天，在UOS的论坛上发现有人讨论过，说无法用pip来编译，只能下载UOS预编译好的包。然后在Kylin这个系统上也搜索了下，果然有：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">yum search numpy</span><br><span class="line"></span><br><span class="line">输出：</span><br><span class="line">Last metadata expiration check: 5 days, 16:43:36 ago on Fri 05 May 2023 04:06:16 PM CST.</span><br><span class="line">&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D; Name Matched: numpy &#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;</span><br><span class="line">python3-numpy.sw_64 : A fast multidimensional array facility for Python</span><br></pre></td></tr></table></figure><p>于是先装下这个系统厂商提供的包：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install python3-numpy</span><br></pre></td></tr></table></figure><p>装好后需要手动拷贝下，把装到系统目录中的numpy给拷到venv中：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp -r &#x2F;usr&#x2F;lib&#x2F;python3.7&#x2F;site-packages&#x2F;numpy* knktc-env&#x2F;lib&#x2F;python3.7&#x2F;site-packages</span><br></pre></td></tr></table></figure><p>尝试看下版本:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&gt;&gt;&gt; import numpy</span><br><span class="line">&gt;&gt;&gt; numpy.__version__</span><br><span class="line">&#39;1.16.5&#39;</span><br></pre></td></tr></table></figure><p>好了，先把numpy的问题给解决了。</p><h2 id="安装Pandas"><a href="#安装Pandas" class="headerlink" title="安装Pandas"></a>安装Pandas</h2><p>要安装Pandas的话，这一步只好使用源码来编译了。</p><p>先把依赖给装下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">pip install setuptools --upgrade -i https:&#x2F;&#x2F;pypi.tuna.tsinghua.edu.cn&#x2F;simple</span><br><span class="line">pip install wheel --upgrade -i https:&#x2F;&#x2F;pypi.tuna.tsinghua.edu.cn&#x2F;simple</span><br><span class="line">pip install Cython --upgrade -i https:&#x2F;&#x2F;pypi.tuna.tsinghua.edu.cn&#x2F;simple</span><br><span class="line">pip install pytz&#x3D;&#x3D;2018.7 -i https:&#x2F;&#x2F;pypi.tuna.tsinghua.edu.cn&#x2F;simple</span><br><span class="line">pip install python-dateutil -i https:&#x2F;&#x2F;pypi.tuna.tsinghua.edu.cn&#x2F;simple</span><br></pre></td></tr></table></figure><p>然后去以下地址下载源码包：</p><p><a href="https://github.com/pandas-dev/pandas/releases">https://github.com/pandas-dev/pandas/releases</a></p><p>我们还在用1.1.5版本，就用这个地址来下载：</p><p><a href="https://github.com/pandas-dev/pandas/releases/download/v1.1.5/pandas-1.1.5.tar.gz">https://github.com/pandas-dev/pandas/releases/download/v1.1.5/pandas-1.1.5.tar.gz</a></p><p>下载后解压，随后进入到目录用最传统的方式安装即可：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd pandas-1.1.5&#x2F;</span><br><span class="line">python setup.py install</span><br></pre></td></tr></table></figure><p>注意，这个过程有点漫长，建议开个screen来进行编译安装。</p><p>虽然过程漫长，但居然没啥报错地就完成了。</p><p>最后再试试panda的版本：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&gt;&gt;&gt; import pandas</span><br><span class="line">&gt;&gt;&gt; pandas.__version__</span><br><span class="line">&#39;1.1.5&#39;</span><br></pre></td></tr></table></figure><p>嗯，应该可以了。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近在做一些国产化适配的工作，让自己的系统能跑在申威架构的CPU上。这几年国产CPU如雨后春笋一般涌现出来，感慨一下，放我刚工作的那几年真是想都不敢想。&lt;/p&gt;
&lt;p&gt;申威架构的CPU与ARM架构的CPU不同，更加小众，国内也就UOS和Kylin对其有支持。客户提供的操作系统是Kylin V10的SW_64版本，本文就记录下在这个架构CPU+Kylin V10环境下装Pandas遇到的问题。&lt;/p&gt;</summary>
    
    
    
    
    <category term="申威" scheme="https://knktc.com/tags/%E7%94%B3%E5%A8%81/"/>
    
    <category term="国产化" scheme="https://knktc.com/tags/%E5%9B%BD%E4%BA%A7%E5%8C%96/"/>
    
    <category term="sw_64" scheme="https://knktc.com/tags/sw-64/"/>
    
    <category term="sw64" scheme="https://knktc.com/tags/sw64/"/>
    
    <category term="麒麟" scheme="https://knktc.com/tags/%E9%BA%92%E9%BA%9F/"/>
    
    <category term="kylin" scheme="https://knktc.com/tags/kylin/"/>
    
    <category term="pandas" scheme="https://knktc.com/tags/pandas/"/>
    
    <category term="numpy" scheme="https://knktc.com/tags/numpy/"/>
    
  </entry>
  
  <entry>
    <title>修复升级Macos 13 Ventura后Git报Pemission Denied的问题</title>
    <link href="https://knktc.com/2022/10/27/fix-mac-os-13-ventura-git-ssh-permission-denied/"/>
    <id>https://knktc.com/2022/10/27/fix-mac-os-13-ventura-git-ssh-permission-denied/</id>
    <published>2022-10-27T04:41:08.000Z</published>
    <updated>2022-10-27T13:18:29.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近尝鲜升级了mac os 13 ventura，结果发现git push代码的时候怎么都不行，直接报了个错误: </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Permission denied (publickey).</span><br><span class="line">fatal: Could not read from remote repository.</span><br><span class="line"></span><br><span class="line">Please make sure you have the correct access rights</span><br></pre></td></tr></table></figure><span id="more"></span><p>在反复确认自己的公钥已经放到gitlab上后，开始google，参考了下网上的讨论：<a href="https://superuser.com/questions/1749364/git-ssh-permission-denied-in-macos-13-ventura">https://superuser.com/questions/1749364/git-ssh-permission-denied-in-macos-13-ventura</a></p><p>应该是因为mac os 13中自带的OpenSSH_9.0p1默认禁止了RSA/SHA-1算法导致的。</p><p>解决方法的话，要么是生成一个新的ssh密钥，替换下（成本可能有点高）：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t ed25519</span><br></pre></td></tr></table></figure><p>要么就是编辑 ~/.ssh/config配置文件，在文件中加入以下的配置即可(还可以应对服务端太老不支持新的加密算法的情况)：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Host *</span><br><span class="line">    HostkeyAlgorithms +ssh-rsa</span><br><span class="line">    PubkeyAcceptedAlgorithms +ssh-rsa</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近尝鲜升级了mac os 13 ventura，结果发现git push代码的时候怎么都不行，直接报了个错误: &lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;Permission denied (publickey).&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;fatal: Could not read from remote repository.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Please make sure you have the correct access rights&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
    <category term="git" scheme="https://knktc.com/tags/git/"/>
    
    <category term="macos" scheme="https://knktc.com/tags/macos/"/>
    
    <category term="ventura" scheme="https://knktc.com/tags/ventura/"/>
    
    <category term="ssh" scheme="https://knktc.com/tags/ssh/"/>
    
    <category term="permission" scheme="https://knktc.com/tags/permission/"/>
    
    <category term="denied" scheme="https://knktc.com/tags/denied/"/>
    
  </entry>
  
  <entry>
    <title>修复python-memcached在python3.8环境中报SyntaxWarning的问题</title>
    <link href="https://knktc.com/2022/08/10/fix-python-memcached-3-8-syntax-warning/"/>
    <id>https://knktc.com/2022/08/10/fix-python-memcached-3-8-syntax-warning/</id>
    <published>2022-08-10T13:57:45.000Z</published>
    <updated>2022-08-10T14:21:21.000Z</updated>
    
    <content type="html"><![CDATA[<p>我们一直使用python-memcached库来操作memcached，最近发现在python3.8的环境中，代码会报类似这样的警告：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">memcache.py:1303: SyntaxWarning: &quot;is&quot; with a literal. Did you mean &quot;&#x3D;&#x3D;&quot;?</span><br><span class="line">  if key is &#39;&#39;:</span><br><span class="line">memcache.py:1304: SyntaxWarning: &quot;is&quot; with a literal. Did you mean &quot;&#x3D;&#x3D;&quot;?</span><br><span class="line">  if key_extra_len is 0:</span><br></pre></td></tr></table></figure><span id="more"></span><p>查了下python-memcached的源码(其实也就一个文件)，定位到1303和1304行，代码如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">if key is &#39;&#39;:</span><br><span class="line">    if key_extra_len is 0:</span><br></pre></td></tr></table></figure><p>看来这样的使用方法在python3.8里面是不被接受的。</p><p>虽然只是warning，但日志中打印这些信息总归让人感觉很不爽，所以得想法修复下。</p><p>查到官方的github仓库，发现这个库已经有年头没有人管了，即使有人提出了修改的PR(<a href="https://github.com/linsomniac/python-memcached/issues/176">https://github.com/linsomniac/python-memcached/issues/176</a>)，也没有人来合并。</p><p>于是两条路：</p><ol><li>在项目中直接修改memcache.py文件，改掉这两行的问题，把is改成==就可以了。</li><li>因为我们会发个requirements.txt，所以我自己做了个修改的版本，放到了pypi上(<a href="https://pypi.org/project/python-memcached-py38fix/">https://pypi.org/project/python-memcached-py38fix/</a>)，如果有需要的可以直接下载：</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install python-memcached-py38fix</span><br></pre></td></tr></table></figure><p>看来下一步必须得考虑换成pymemcache了，毕竟Django都抛弃了对python-memcached的支持了。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;我们一直使用python-memcached库来操作memcached，最近发现在python3.8的环境中，代码会报类似这样的警告：&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;memcache.py:1303: SyntaxWarning: &amp;quot;is&amp;quot; with a literal. Did you mean &amp;quot;&amp;#x3D;&amp;#x3D;&amp;quot;?&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  if key is &amp;#39;&amp;#39;:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;memcache.py:1304: SyntaxWarning: &amp;quot;is&amp;quot; with a literal. Did you mean &amp;quot;&amp;#x3D;&amp;#x3D;&amp;quot;?&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  if key_extra_len is 0:&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
    <category term="python" scheme="https://knktc.com/tags/python/"/>
    
    <category term="memcached" scheme="https://knktc.com/tags/memcached/"/>
    
    <category term="SyntaxWarning" scheme="https://knktc.com/tags/SyntaxWarning/"/>
    
  </entry>
  
  <entry>
    <title>Nginx忽略404错误日志</title>
    <link href="https://knktc.com/2022/08/10/nginx-not-found-log-off/"/>
    <id>https://knktc.com/2022/08/10/nginx-not-found-log-off/</id>
    <published>2022-08-10T13:42:44.000Z</published>
    <updated>2022-08-10T13:52:07.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近尝试使用nginx来提供一些静态的json文件来作为配置，最为简单的做法就是提供下面这样的配置了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">location ~ ^&#x2F;myconf&#x2F;(?&lt;filename&gt;.*)$ &#123;</span><br><span class="line">    alias &#x2F;home&#x2F;knktc&#x2F;myconf&#x2F;$filename;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><span id="more"></span><p>但是问题来了，如果匹配不到配置文件的话，直接返回的是nginx的404页面，为了能让前端代码更容易地处理下这种情况，可以加点配置，让文件找不到的时候直接返回空的json对象，于是配置可以改成下面的这样:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">location ~ ^&#x2F;myconf&#x2F;(?&lt;filename&gt;.*)$ &#123;</span><br><span class="line">    alias &#x2F;home&#x2F;knktc&#x2F;myconf&#x2F;$filename;</span><br><span class="line">    error_page 404 &#x3D; @not_found_fallback;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">location @not_found_fallback &#123;</span><br><span class="line">        add_header Content-Type text&#x2F;plain;</span><br><span class="line">        return 200 &quot;&#123;&#125;&quot;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在的话，如果文件不存在的话，就可以返回一个空的json对象了。</p><p>但是问题又来了：这种情况下nginx的error.log里面又记录了一次404的错误事件，于是还需要继续改下配置，把not found的日志给关掉：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">location ~ ^&#x2F;myconf&#x2F;(?&lt;filename&gt;.*)$ &#123;</span><br><span class="line">    alias &#x2F;home&#x2F;knktc&#x2F;myconf&#x2F;$filename;</span><br><span class="line">    error_page 404 &#x3D; @not_found_fallback;</span><br><span class="line">    log_not_found off;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">location @not_found_fallback &#123;</span><br><span class="line">        add_header Content-Type text&#x2F;plain;</span><br><span class="line">        return 200 &quot;&#123;&#125;&quot;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这下就OK了。使用Nginx静态文件能力来做一些简单的接口还是很方便的。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近尝试使用nginx来提供一些静态的json文件来作为配置，最为简单的做法就是提供下面这样的配置了：&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;location ~ ^&amp;#x2F;myconf&amp;#x2F;(?&amp;lt;filename&amp;gt;.*)$ &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    alias &amp;#x2F;home&amp;#x2F;knktc&amp;#x2F;myconf&amp;#x2F;$filename;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
    <category term="nginx" scheme="https://knktc.com/tags/nginx/"/>
    
    <category term="404" scheme="https://knktc.com/tags/404/"/>
    
    <category term="log" scheme="https://knktc.com/tags/log/"/>
    
    <category term="json" scheme="https://knktc.com/tags/json/"/>
    
  </entry>
  
  <entry>
    <title>使用Certbot自动化生成https证书(ubuntu系统+cloudflare+nginx)</title>
    <link href="https://knktc.com/2022/06/16/certbot-on-ubuntu-cheatsheet/"/>
    <id>https://knktc.com/2022/06/16/certbot-on-ubuntu-cheatsheet/</id>
    <published>2022-06-16T13:45:36.000Z</published>
    <updated>2022-06-16T14:21:53.000Z</updated>
    
    <content type="html"><![CDATA[<p>用Certbot来自动生成Let’s Encrypt证书这件事儿，虽然官方和网上已经有了很详实的文档了，但是我还是想自己记录下，留作以后备查吧。</p><p>本文用的操作系统是ubuntu 20.04，DNS的话用的是Cloudflare的服务，web服务使用nginx来提供。然后，按照以下步骤进行就可以了。</p><span id="more"></span><h2 id="安装Certbot"><a href="#安装Certbot" class="headerlink" title="安装Certbot"></a>安装Certbot</h2><p>用以下命令安装就可以了，某些时候snap install的时候可能比较慢，有时候还可能需要重试几次：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">apt install snapd</span><br><span class="line">snap install core</span><br><span class="line">snap refresh core</span><br><span class="line">snap install --classic certbot</span><br></pre></td></tr></table></figure><p>安装完成后，运行下<code>certbot --version</code>命令，如果有版本号输出，就表明安装成功了。</p><h2 id="安装Cloudflare插件"><a href="#安装Cloudflare插件" class="headerlink" title="安装Cloudflare插件"></a>安装Cloudflare插件</h2><p>为了实现自动化地续签证书，需要安装下对应DNS服务商的插件才可以。我使用Cloudflare来做DNS解析，所以可以使用以下的命令来安装Cloudflare插件：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">snap set certbot trust-plugin-with-root=ok</span><br><span class="line"></span><br><span class="line">snap install certbot-dns-cloudflare</span><br></pre></td></tr></table></figure><h2 id="设置Cloudflare-API令牌"><a href="#设置Cloudflare-API令牌" class="headerlink" title="设置Cloudflare API令牌"></a>设置Cloudflare API令牌</h2><p>要使用Cloudflare插件，需要先登录到Cloudflare控制台来生成一个仅可操作DNS解析的API 令牌。可在登录Cloudflare后，跳转到这个地址来进行创建：<a href="">https://dash.cloudflare.com/profile/api-tokens</a></p><p>以下是操作过程的简单演示：</p><p><a href="https://imgtu.com/i/XHvupd"><img src="https://s1.ax1x.com/2022/06/16/XHvupd.png" alt="XHvupd.png"></a></p><p><a href="https://imgtu.com/i/XHvmfH"><img src="https://s1.ax1x.com/2022/06/16/XHvmfH.png" alt="XHvmfH.png"></a></p><p><a href="https://imgtu.com/i/XHvete"><img src="https://s1.ax1x.com/2022/06/16/XHvete.png" alt="XHvete.png"></a></p><p>创建完成后记得将出现的令牌复制下来。然后使用以下命令来创建配置文件：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p ~/.secrets/certbot</span><br><span class="line"></span><br><span class="line">vim ~/.secrets/certbot/cloudflare.ini</span><br></pre></td></tr></table></figure><p>填入如下格式的配置就可以了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dns_cloudflare_api_token &#x3D; 刚才获取到的API令牌</span><br></pre></td></tr></table></figure><p>编辑完成后，可以修改下这个文件的权限，否则执行certbot命令时会报一个警告说文件权限不安全：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chmod 600 ~/.secrets/certbot/cloudflare.ini</span><br></pre></td></tr></table></figure><h2 id="获取证书"><a href="#获取证书" class="headerlink" title="获取证书"></a>获取证书</h2><p>现在通过运行以下的命令就可以生成nginx所需的证书了：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">certbot certonly \</span><br><span class="line">  --dns-cloudflare \</span><br><span class="line">  --dns-cloudflare-credentials ~/.secrets/certbot/cloudflare.ini \</span><br><span class="line">  --dns-cloudflare-propagation-seconds 60 \</span><br><span class="line">  -d knktc.com</span><br></pre></td></tr></table></figure><p>注意，这里用了<code>--dns-cloudflare-propagation-seconds 60</code>把等待时间延长到了60秒，否则默认的10秒有些时候会导致校验失败。</p><p>生成的证书和私钥会放到/etc/letsencrypt/live/YOUR_HOST目录下。</p><p>贴一段nginx相关配置，备查：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">listen 443 ssl http2;</span><br><span class="line">server_name knktc.com;</span><br><span class="line">ssl_certificate /etc/letsencrypt/live/knktc.com/fullchain.pem; # managed by Certbot</span><br><span class="line">ssl_certificate_key /etc/letsencrypt/live/knktc.com/privkey.pem; # managed by Certbot</span><br><span class="line"></span><br><span class="line">ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;</span><br><span class="line">ssl_ciphers         HIGH:!aNULL:!MD5;</span><br><span class="line">keepalive_timeout   70;</span><br><span class="line">ssl_session_timeout  5m;</span><br></pre></td></tr></table></figure><h2 id="定时任务"><a href="#定时任务" class="headerlink" title="定时任务"></a>定时任务</h2><p>通过以下命令可以查看certbot的定时任务是否添加成功了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl list-timers</span><br></pre></td></tr></table></figure><p>如果要在每次生成新的证书后自动reload下nginx，可以加个hook脚本：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim &#x2F;etc&#x2F;letsencrypt&#x2F;renewal-hooks&#x2F;post&#x2F;reload-nginx</span><br></pre></td></tr></table></figure><p>加入以下的脚本：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">#!&#x2F;bin&#x2F;sh</span><br><span class="line"></span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>然后给这个脚本文件加上权限：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chmod +x &#x2F;etc&#x2F;letsencrypt&#x2F;renewal-hooks&#x2F;post&#x2F;reload-nginx</span><br></pre></td></tr></table></figure><p>这样的话，每次生成新的证书后，就会自动reload下nginx让新的证书生效了。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;用Certbot来自动生成Let’s Encrypt证书这件事儿，虽然官方和网上已经有了很详实的文档了，但是我还是想自己记录下，留作以后备查吧。&lt;/p&gt;
&lt;p&gt;本文用的操作系统是ubuntu 20.04，DNS的话用的是Cloudflare的服务，web服务使用nginx来提供。然后，按照以下步骤进行就可以了。&lt;/p&gt;</summary>
    
    
    
    
    <category term="certbot" scheme="https://knktc.com/tags/certbot/"/>
    
    <category term="ubuntu" scheme="https://knktc.com/tags/ubuntu/"/>
    
    <category term="ssl" scheme="https://knktc.com/tags/ssl/"/>
    
    <category term="https" scheme="https://knktc.com/tags/https/"/>
    
    <category term="letsencrypt" scheme="https://knktc.com/tags/letsencrypt/"/>
    
    <category term="cloudflare" scheme="https://knktc.com/tags/cloudflare/"/>
    
    <category term="nginx" scheme="https://knktc.com/tags/nginx/"/>
    
  </entry>
  
  <entry>
    <title>使用镜像来加快Poetry的安装</title>
    <link href="https://knktc.com/2022/04/22/set-mirror-when-installing-poerty/"/>
    <id>https://knktc.com/2022/04/22/set-mirror-when-installing-poerty/</id>
    <published>2022-04-22T05:52:52.000Z</published>
    <updated>2022-04-25T05:03:15.000Z</updated>
    
    <content type="html"><![CDATA[<p>首先添加下全局的pip代理：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip config --user setglobal.index-url https:&#x2F;&#x2F;pypi.tuna.tsinghua.edu.cn&#x2F;simple</span><br></pre></td></tr></table></figure><p>然后下载安装脚本到本地(比如保存成install-poetry.py文件)：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -sSL https:&#x2F;&#x2F;install.python-poetry.org -o install-poetry.py</span><br></pre></td></tr></table></figure><p>打开脚本进行编辑，找到如下这段代码：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">def pip(self, *args, **kwargs) -&gt; subprocess.CompletedProcess:</span><br><span class="line">        return self.python(&quot;-m&quot;, &quot;pip&quot;, &quot;--isolated&quot;, *args, **kwargs)</span><br></pre></td></tr></table></figure><p>把里面的”–isolated”删除掉，然后安装脚本使用pip时就会自动使用之前配置好的镜像了:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 install install-poetry.py</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;首先添加下全局的pip代理：&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;pip config --user setglobal.</summary>
      
    
    
    
    
    <category term="镜像" scheme="https://knktc.com/tags/%E9%95%9C%E5%83%8F/"/>
    
    <category term="poetry" scheme="https://knktc.com/tags/poetry/"/>
    
    <category term="pip" scheme="https://knktc.com/tags/pip/"/>
    
  </entry>
  
  <entry>
    <title>使用Celery来广播任务</title>
    <link href="https://knktc.com/2022/03/26/use-celery-broadcast-queue/"/>
    <id>https://knktc.com/2022/03/26/use-celery-broadcast-queue/</id>
    <published>2022-03-26T13:35:13.000Z</published>
    <updated>2022-03-26T15:15:15.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近琢磨给自己的系统加上个Agent来实现配置变更和监控信息采集等任务，因为这个系统是多实例部署的，所以这些Agent可能需要同时运行相同的任务。因为我们正好在用celery，celery支持广播任务，所有的worker都能收到相同的任务，正好就能用这个特性来实现Agent的功能了。</p><span id="more"></span><p>为了测试下广播任务的效果，先写个示例任务：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># filename: agent.py</span></span><br><span class="line"><span class="keyword">import</span> datetime</span><br><span class="line"><span class="keyword">from</span> celery <span class="keyword">import</span> shared_task</span><br><span class="line"></span><br><span class="line"><span class="meta">@shared_task(<span class="params">bind=<span class="literal">True</span></span>)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">test</span>(<span class="params">self</span>):</span></span><br><span class="line">    name = self.request.hostname</span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">f&#x27;/tmp/<span class="subst">&#123;name&#125;</span>&#x27;</span>, <span class="string">&#x27;w&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line">        f.write(<span class="built_in">str</span>(datetime.datetime.now()))</span><br></pre></td></tr></table></figure><p>稍微解释下，test这个任务会获取执行该任务的worker的hostname，然后以该hostname为文件名在/tmp目录下创建出来文件，文件中包含有执行任务的时间。</p><p>也就是说，如果测试ok的话，在这个test任务执行的时候，在/tmp目录下应该会同时生成多个文件：有几个worker绑定到这个队列就会有几个文件。</p><p>然后在celery的app配置代码中加入以下的代码：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> kombu.common <span class="keyword">import</span> Broadcast</span><br><span class="line"></span><br><span class="line">app.conf.task_queues = (Broadcast(<span class="string">&#x27;broadcast_tasks&#x27;</span>),)</span><br><span class="line">app.conf.task_routes = &#123;</span><br><span class="line">    <span class="string">&#x27;agent.test&#x27;</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;queue&#x27;</span>: <span class="string">&#x27;broadcast_tasks&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;exchange&#x27;</span>: <span class="string">&#x27;broadcast_tasks&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里定义了一个名为broadcast_tasks的队列，然后agent.test的任务都会通过broadcast_tasks这个exchange来发出。</p><p>在celery worker方面，试着启动这么两个worker绑定broadcast_tasks队列，这两个worker一个叫agent1，一个叫agent2：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">celery -A boss2_manager worker --pidfile=/var/run/celeryworker_agent1.pid -Q broadcast_tasks -n agent1@%h</span><br><span class="line">celery -A boss2_manager worker --pidfile=/var/run/celeryworker_agent2.pid -Q broadcast_tasks -n agent2@%h</span><br></pre></td></tr></table></figure><p>celery worker启动后，如果使用了RabbitMQ作为broker的话，可以在管理界面中看到，广播类型的队列对于RabbitMQ来说就是一个fanout类型的exchange外加多个队列：启动一个worker就创建一个队列绑定到这个fanout exchange上。</p><p>试着直接用delay方法调用下这个test任务：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> agent <span class="keyword">import</span> test</span><br><span class="line"></span><br><span class="line">test.delay()</span><br></pre></td></tr></table></figure><p>然后去/tmp目录下看看，发现已经出现了两个文件，命名分别为：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">agent1@knktc-rmbp.local</span><br><span class="line">agent2@knktc-rmbp.local</span><br></pre></td></tr></table></figure><p>打开文件看看内容，两个文件中记录的时间相差不大。</p><p>OK，就用这个方法来做Agent吧。</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li>Celery Broadcast的官方文档(吐槽下，最近官方把原来的celeryproject.org域名给搞丢了，最后只好搞了个新的~)：<a href="https://docs.celeryq.dev/en/stable/userguide/routing.html#broadcast">https://docs.celeryq.dev/en/stable/userguide/routing.html#broadcast</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近琢磨给自己的系统加上个Agent来实现配置变更和监控信息采集等任务，因为这个系统是多实例部署的，所以这些Agent可能需要同时运行相同的任务。因为我们正好在用celery，celery支持广播任务，所有的worker都能收到相同的任务，正好就能用这个特性来实现Agent的功能了。&lt;/p&gt;</summary>
    
    
    
    
    <category term="celery" scheme="https://knktc.com/tags/celery/"/>
    
    <category term="rabbitmq" scheme="https://knktc.com/tags/rabbitmq/"/>
    
    <category term="广播" scheme="https://knktc.com/tags/%E5%B9%BF%E6%92%AD/"/>
    
    <category term="broadcast" scheme="https://knktc.com/tags/broadcast/"/>
    
  </entry>
  
  <entry>
    <title>Ubuntu系统中如何导入SNMP MIB库文件</title>
    <link href="https://knktc.com/2022/03/23/where-to-place-mib-files-in-ubuntu/"/>
    <id>https://knktc.com/2022/03/23/where-to-place-mib-files-in-ubuntu/</id>
    <published>2022-03-23T14:39:41.000Z</published>
    <updated>2022-03-23T15:10:08.000Z</updated>
    
    <content type="html"><![CDATA[<p>我这边的一个系统用了telegraf的snmp_trap插件来采集snmp trap告警，然后再转换为http格式吐出进入下一个环节。这个插件配置起来倒是简单，但是其中对于OID的翻译依赖于snmptranslate命令的结果，而如果让snmptranslate命令能正常翻译，又依赖于MIB库文件的正确放置和配置。最近在用这套东西来采集H3C交换机设备的告警，正好记录下在ubuntu环境下导入MIB库文件的方法。</p><span id="more"></span><h1 id="配置前"><a href="#配置前" class="headerlink" title="配置前"></a>配置前</h1><p>在进行配置前，可以先尝试运行下snmptranslate命令(注意，这里的-L n参数可以忽略输出的错误信息)：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">snmptranslate -L n .1.3.6.1.4.1.25506.2.38.1.6.3.0.1</span><br></pre></td></tr></table></figure><p>这个示例中的OID来自H3C的交换机设备，如果没导入MIB库的话，结果只会是：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SNMPv2-SMI::enterprises.25506.2.38.1.6.3.0.1</span><br></pre></td></tr></table></figure><p>很明显，光看这个还是没法知道设备发了个啥信息过来。</p><h1 id="导入和配置"><a href="#导入和配置" class="headerlink" title="导入和配置"></a>导入和配置</h1><p>先从H3C官方搞到MIB库文件，然后把这些mib文件解压到一个目录中。在这个示例中，我们把这些mib文件都放置到/usr/share/mibs/h3c_mibs目录下。</p><p>放好MIB库文件后，就可以开始配置snmp.conf这个配置文件了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim &#x2F;etc&#x2F;snmp&#x2F;snmp.conf</span><br></pre></td></tr></table></figure><p>(如果没有这个文件，可能需要先使用apt install snmp命令来安装下相关的组件。)</p><p>配置文件中原始的配置内容是这样的：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># As the snmp packages come without MIB files due to license reasons, loading</span><br><span class="line"># of MIBs is disabled by default. If you added the MIBs you can reenable</span><br><span class="line"># loading them by commenting out the following line.</span><br><span class="line">mibs :</span><br></pre></td></tr></table></figure><p>为了能让新加的MIB库生效，需要做下修改：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># mibs :</span><br><span class="line"></span><br><span class="line">mibdirs +&#x2F;usr&#x2F;share&#x2F;mibs&#x2F;h3c_mibs</span><br><span class="line">mibdirs +&#x2F;usr&#x2F;share&#x2F;mibs&#x2F;another_mib_dir  # for example</span><br><span class="line"></span><br><span class="line">mibs +ALL</span><br></pre></td></tr></table></figure><p>稍微解释下：</p><ul><li>注释掉了原有的mib : 这行，让系统可以加载第三方的库；</li><li>mibdirs参数用于指定加载的MIB库路径，如果有多个MIB库的目录，可以多次使用这个参数</li><li>mibs +ALL参数指定了在所有的mib文件中来进行查找。</li></ul><p>配置文件修改完成后，保存退出即可，无需重启啥服务。</p><h1 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h1><p>再次测试翻译OID，在正确放置mib文件和正确配置的情况下，是可以成功翻译出结果的：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@knktc.com:&#x2F;root# snmptranslate -L n .1.3.6.1.4.1.25506.2.38.1.6.3.0.1</span><br><span class="line">HH3C-TRAP-MIB::hh3cPeriodicalTrap</span><br></pre></td></tr></table></figure><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li>telegraf的snmp trap插件官网是：<a href="https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp_trap">https://github.com/influxdata/telegraf/tree/master/plugins/inputs/snmp_trap</a></li><li>snmp.conf配置文件的更多内容可参考man page的帮助：<a href="https://linux.die.net/man/5/snmp.conf">https://linux.die.net/man/5/snmp.conf</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;我这边的一个系统用了telegraf的snmp_trap插件来采集snmp trap告警，然后再转换为http格式吐出进入下一个环节。这个插件配置起来倒是简单，但是其中对于OID的翻译依赖于snmptranslate命令的结果，而如果让snmptranslate命令能正常翻译，又依赖于MIB库文件的正确放置和配置。最近在用这套东西来采集H3C交换机设备的告警，正好记录下在ubuntu环境下导入MIB库文件的方法。&lt;/p&gt;</summary>
    
    
    
    
    <category term="ubuntu" scheme="https://knktc.com/tags/ubuntu/"/>
    
    <category term="mib" scheme="https://knktc.com/tags/mib/"/>
    
    <category term="snmp" scheme="https://knktc.com/tags/snmp/"/>
    
    <category term="mibs" scheme="https://knktc.com/tags/mibs/"/>
    
    <category term="telegraf" scheme="https://knktc.com/tags/telegraf/"/>
    
  </entry>
  
</feed>
