<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>jicheng1014 (哥有石头)</title>
    <link>https://ruby-china.org/jicheng1014</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>没买海外 vps 时如何解决 kamal deploy 的 EOF 问题</title>
      <description>&lt;p&gt;最近在部署 &lt;code&gt;3qruok.com&lt;/code&gt; 的时候被墙又戏耍了，在国内环境下使用 Docker，网络问题始终是绕不开的痛点。即便你本地开启了系统的 TUN 模式或全局代理，在使用 Kamal 进行部署时，依然可能遇到下面这种让人冒火的“玄学”报错：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG [1ecc2695] ERROR: failed to build: failed to solve: failed to fetch anonymous token: 
Get "[https://auth.docker.io/token?scope=repository%3Alibrary%2Fruby%3Apull&amp;amp;service=registry.docker.io](https://auth.docker.io/token?scope=repository%3Alibrary%2Fruby%3Apull&amp;amp;service=registry.docker.io)": EOF
...
docker stdout: #0 building with "kamal-local-docker-container" instance using docker-container driver
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;明明机器已经开启了 TUN 模式，为什么 Docker 构建时还是拿不到 Token 导致 EOF？&lt;/p&gt;
&lt;h2 id="核心矛盾：消失的网络继承"&gt;核心矛盾：消失的网络继承&lt;/h2&gt;
&lt;p&gt;问题的根源在于 Kamal 默认的构建机制。Kamal 在打包时会创建一个名为 &lt;code&gt;kamal-local-docker-container&lt;/code&gt; 的构建器，它使用的是 &lt;strong&gt;&lt;code&gt;docker-container&lt;/code&gt; 驱动&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="什么是 docker-container 驱动？"&gt;什么是 docker-container 驱动？&lt;/h3&gt;
&lt;p&gt;它的本质是在你的 Docker 中启动一个独立的 &lt;strong&gt;BuildKit 容器&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;它的使命&lt;/strong&gt;：实现跨平台构建（Multi-platform builds）。它内部集成了一个独立的 &lt;strong&gt;QEMU 模拟器&lt;/strong&gt;，这使得它能完全脱离宿主机硬件架构的影响。无论你的 Mac 是 Intel 还是 M 系列芯片，它都能为你“直出”目标环境（如 x86）的镜像。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;它的副作用&lt;/strong&gt;：由于它运行在一个高度隔离的容器内，它拥有&lt;strong&gt;完全独立的网络命名空间&lt;/strong&gt;。它不会自动继承宿主机的 TUN 代理或环境变量，甚至会忽略你在 Docker Desktop 界面设置的镜像源（Registry Mirrors）。这导致它在尝试拉取基础镜像（如 &lt;code&gt;ruby:slim&lt;/code&gt;）时，依然在直接撞墙。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="解决方案：回归宿主机驱动"&gt;解决方案：回归宿主机驱动&lt;/h2&gt;
&lt;p&gt;要解决这个问题，最简单的办法是让构建任务交还给宿主机的 Docker Daemon 来处理，从而直接利用宿主机已经配置好的网络环境。&lt;/p&gt;
&lt;h3 id="操作步骤"&gt;操作步骤&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改 &lt;code&gt;deploy.yml&lt;/code&gt;&lt;/strong&gt;
在配置文件中明确指定 &lt;code&gt;builder&lt;/code&gt; 的驱动为 &lt;code&gt;docker&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder:
  driver: docker
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;提交配置（关键点）&lt;/strong&gt;
Kamal 部署时会检查 Git 状态。如果 &lt;code&gt;deploy.yml&lt;/code&gt; 的修改没有被 &lt;code&gt;git add&lt;/code&gt;，Kamal 可能会读取旧的配置导致修改不生效：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add config/deploy.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;清理旧的构建器&lt;/strong&gt;
删掉那个由于网络原因卡死的旧构建实例，强制 Kamal 重新初始化：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker buildx rm kamal-local-docker-container
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;重新运行部署&lt;/strong&gt;
执行 &lt;code&gt;kamal deploy&lt;/code&gt;，此时构建进程将直接调用本地 Docker 环境。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;
&lt;h2 id="深度解析：为什么 driver: docker 依然能打出 x86 镜像？"&gt;深度解析：为什么 &lt;code&gt;driver: docker&lt;/code&gt; 依然能打出 x86 镜像？&lt;/h2&gt;
&lt;p&gt;你可能会担心：如果不用 &lt;code&gt;docker-container&lt;/code&gt; 驱动，我在 ARM 架构的 Mac 上还能打出生产环境需要的 x86 镜像吗？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;答案是肯定的。&lt;/strong&gt; 这是因为 &lt;strong&gt;Docker Desktop for Mac&lt;/strong&gt; 已经在其底层的 Linux 虚拟机里注册了 QEMU。当你切换到 &lt;code&gt;driver: docker&lt;/code&gt; 时：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;架构模拟&lt;/strong&gt;：依然由 Docker 虚拟层底层的 QEMU 负责，跨平台打包能力依然存在。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;网络控制&lt;/strong&gt;：构建进程此时运行在宿主机 Docker 的“亲生”环境下，它能完美识别并使用你本地调教好的代理或镜像站。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;在 Kamal 部署流程中，如果你遭遇了 Docker 认证阶段的 EOF 错误，通常是因为默认的构建容器陷入了“网络孤岛”。&lt;/p&gt;

&lt;p&gt;将 &lt;code&gt;builder&lt;/code&gt; 的驱动修改为 &lt;code&gt;docker&lt;/code&gt; 是目前国内开发者最务实的解决方案。它通过牺牲一点点构建隔离性，换取了对宿主机网络环境的完美继承，让部署过程不再卡在第一步。&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Wed, 18 Mar 2026 11:40:51 +0800</pubDate>
      <link>https://ruby-china.org/topics/44521</link>
      <guid>https://ruby-china.org/topics/44521</guid>
    </item>
    <item>
      <title>vibe 了个很少依赖的跨平台服务器监控，开源，必须开源</title>
      <description>&lt;p&gt;[TLDR]&lt;/p&gt;

&lt;p&gt;地址是 &lt;a href="https://github.com/jicheng1014/ssh_server_oss" rel="nofollow" target="_blank"&gt;https://github.com/jicheng1014/ssh_server_oss&lt;/a&gt;    有兴趣的老哥可以 star 下，产品截图放在文末&lt;/p&gt;

&lt;p&gt;最简单的运行方式是  代码克隆下来，之后运行 ./start-prod.sh    跑在 docker 上。&lt;/p&gt;

&lt;p&gt;[正文]&lt;/p&gt;

&lt;p&gt;年底了，偶然间终于想起之前买的几台 Bandwagon 服务器，结果登不上去了。仔细研究后发现是硬盘满了没注意，导致服务挂死。再加上几个月前，Tencent 的某台服务器意外 100% CPU 持续占用，也是直到登录不上才发现。&lt;/p&gt;

&lt;p&gt;痛定思痛，我发现跨服务商的基础监控还是很有必要搞一下的。&lt;/p&gt;

&lt;p&gt;市面上调研了一圈：&lt;/p&gt;

&lt;p&gt;要么需要在 Client 装 Agent（重型且繁琐）；&lt;/p&gt;

&lt;p&gt;要么功能特别复杂，配置一大堆；&lt;/p&gt;

&lt;p&gt;还有一些 Saas 服务，我看不明白他们怎么处理私钥，实在不敢把 SSH Key 交出去。&lt;/p&gt;

&lt;p&gt;作为一个 Rails 老兵，左手 Cursor 右手 Claude，这点简单的需求，直接 Vibe 一下就好了。前两天在朋友圈发了下，反响还不错，今天整理一下发到社区。&lt;/p&gt;

&lt;p&gt;代码其实没啥意思，反正都是 Vibe 出来的，主打一个快和解决问题。&lt;/p&gt;

&lt;p&gt;为什么要造这个轮子？
虽然市面上有 Prometheus + Grafana、Zabbix 等成熟方案，但对于个人开发者或小团队，这个方案胜在简单粗暴：&lt;/p&gt;

&lt;p&gt;基于 Rails 8.1&lt;/p&gt;

&lt;p&gt;代码结构清晰，维护成本极低。&lt;/p&gt;

&lt;p&gt;Rails 8 原生组件全家桶：Active Record、Action Cable、Solid Queue 开箱即用，体验极度丝滑。&lt;/p&gt;

&lt;p&gt;获取原理以及实现都及其简单 (这也是最核心的优势)&lt;/p&gt;

&lt;p&gt;远端服务器零依赖：不需要在目标服务器安装任何 Agent，不需要 Docker，不需要开放额外端口。&lt;/p&gt;

&lt;p&gt;纯 SSH 交互：直接通过 net-ssh 执行系统原生命令（top、free、df）获取指标。&lt;/p&gt;

&lt;p&gt;只要服务器能 SSH 连上，就能监控，逻辑透明，没有任何黑盒。&lt;/p&gt;

&lt;p&gt;轻量级部署&lt;/p&gt;

&lt;p&gt;直接使用 SQLite，无需维护单独的数据库服务。&lt;/p&gt;

&lt;p&gt;单机 Docker 部署，资源占用极低，跑在任何一台闲置小鸡上都能监控你所有的机器。&lt;/p&gt;

&lt;p&gt;🛠️ 技术栈 (Bleeding Edge)
直接上了最新的技术栈，尝鲜 Rails 8 的新特性：&lt;/p&gt;

&lt;p&gt;后端框架：Rails 8.1&lt;/p&gt;

&lt;p&gt;前端样式：TailwindCSS v4 (最新的!)&lt;/p&gt;

&lt;p&gt;前端交互：Hotwire (Turbo + Stimulus)&lt;/p&gt;

&lt;p&gt;后台任务：Solid Queue&lt;/p&gt;

&lt;p&gt;缓存：Solid Cache&lt;/p&gt;

&lt;p&gt;WebSocket: Solid Cable&lt;/p&gt;

&lt;p&gt;数据库：SQLite 3&lt;/p&gt;

&lt;p&gt;SSH 连接：net-ssh gem&lt;/p&gt;

&lt;p&gt;部署：Kamal (Docker) &lt;/p&gt;

&lt;p&gt;📸 截图预览
界面主打一个直观，只看最核心的 CPU、内存和磁盘：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/jicheng1014/10c5a387-16b4-4cd2-921b-ed28155f777a.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/jicheng1014/dafbe2d3-1bd4-42bc-aaa5-0df1e5268ef5.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/jicheng1014/1493f8fb-786c-4802-b343-805cc36e78d1.png!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Wed, 21 Jan 2026 16:16:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/44459</link>
      <guid>https://ruby-china.org/topics/44459</guid>
    </item>
    <item>
      <title>ransack 在使用的时候，可能需要注意 distinct 的影响</title>
      <description>&lt;p&gt;同步发在了自己的 blog  3qruok.com 的 &lt;a href="https://www.3qruok.com/posts/ransack-zai-shi-yong-de-shi-hou-ke-neng-xu-yao-zhu-yi-distinct-de-ying-xiang" rel="nofollow" target="_blank" title=""&gt;ransack 在使用的时候，可能需要注意 distinct 的影响&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;TLDR:&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;当你涉及搜索的模型比较多的时候（比如 千万），建议搜索页面初始时不要使用 result(distinct: true). 如果可能，穷举搜索条件，在不需要 join 表的搜索避免使用 distinct&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;废话文学：&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在我们使用 ransack 的时候，我们经常习惯性的弄出一个类似这样的写法&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Student.ransack(params[:q]).result(distinct: true)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;有可能有人不太清楚为啥这里会有一个  &lt;code&gt;distinct: true&lt;/code&gt;, 有时候似乎不加也没有影响&lt;/p&gt;

&lt;p&gt;实际上的原因是  这里是为了防止你在搜索的时候，搜索的是子条件，然而满足子条件是需要 join 表的，这样会导致主查询的数据变为多条  &lt;/p&gt;

&lt;p&gt;举个例子 &lt;/p&gt;

&lt;p&gt;Students   是学生表     &lt;code&gt;has_many&lt;/code&gt; &lt;code&gt;book_rents&lt;/code&gt;, 表结构为 &lt;code&gt;id, name, gender&lt;/code&gt;
BookRents  是借书记录，记录了学生借出的书名， &lt;code&gt;belongs_to&lt;/code&gt; &lt;code&gt;student&lt;/code&gt;, 表结构为 &lt;code&gt;id, student_id, book_name&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;假设 搜索  name_eq , 其实加不加 distinct 是没有影响的，因为查询不涉及到 join 表&lt;/p&gt;

&lt;p&gt;但是&lt;/p&gt;

&lt;p&gt;此时如果查询 借了“ruby 镐头书”的学生，则    搜索条件为。params[q][:book_rents_book_name_eq] = "ruby 镐头书”&lt;/p&gt;

&lt;p&gt;如果不加 distinct   则查询记录，假设某个学生借了两次这本书，则最终结果就会出现两个相同的 student&lt;/p&gt;

&lt;p&gt;官方为了避免沙雕，所以一般情况下，在 demo 的时候都默认了加入了  distinct: true， &lt;/p&gt;

&lt;p&gt;而大多数人，选择了直接抄。。。&lt;/p&gt;

&lt;p&gt;然而当数据量变大的时候，问题就变得严重了。&lt;/p&gt;

&lt;p&gt;一般在系统中，初始的搜索页面，数据量往往是非常巨大的。如果你的初始 index 页面的数据量上了 千万级别后 就会有个非常尴尬的地方 &lt;/p&gt;

&lt;p&gt;系统会首先读默认的搜索结果&lt;/p&gt;

&lt;p&gt;于是一个非常尴尬的事情出现了：&lt;/p&gt;

&lt;p&gt;你可能有上亿的数据，虽然你做了分页，但是 ransack 会首先执行一次 distinct 你的 id，再去 count 算总数&lt;/p&gt;

&lt;p&gt;这意味着数据库实际上是把整个表的 id 先加载出来再去重，而这一步，会比较漫长。&lt;/p&gt;

&lt;p&gt;然而实际的情况是，默认搜索情况下，是不会涉及到 join 表的，只可能涉及到展示数据时候 includes 表，这样 distinct 的运算完全是浪费掉了。&lt;/p&gt;

&lt;p&gt;那么怎么解决呢？  &lt;/p&gt;

&lt;p&gt;对症下药咯，加个判断，在默认情况下，result 的 distinct 为 false 或者直接不配置 distinct 即可，&lt;/p&gt;

&lt;p&gt;更好的方式是  你需要实现一个  need_using_distinct?(params[:q]) 的方法，将需要 joins 表的情况都考虑清楚，这样每次搜索的时候，就会根据你的查询进行优化，避免浪费性能&lt;/p&gt;

&lt;p&gt;那么，这么做可以优化多少呢？额 千万级别的数据，从原来的查询时间是 几十秒 到现在 0.x 秒&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Thu, 17 Apr 2025 10:50:13 +0800</pubDate>
      <link>https://ruby-china.org/topics/44134</link>
      <guid>https://ruby-china.org/topics/44134</guid>
    </item>
    <item>
      <title>rails world 2024 的视频已经全部放出啦</title>
      <description>&lt;p&gt;最近的 rails world 大会的视频已经全部上啦&lt;/p&gt;

&lt;p&gt;兄弟们可以研究一下啦 &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/playlist?list=PLHFP2OPUpCeb182aDN5cKZTuyjn3Tdbqx" rel="nofollow" target="_blank"&gt;https://www.youtube.com/playlist?list=PLHFP2OPUpCeb182aDN5cKZTuyjn3Tdbqx&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;另外，通过 superails 的油管频道，知道了 大会的进程管理 &lt;a href="https://app.railsworld.com/" rel="nofollow" target="_blank"&gt;https://app.railsworld.com/&lt;/a&gt;   也是 rails 写的  当然是开源的啦&lt;/p&gt;

&lt;p&gt;感觉非常有研究价值，具体的 repo 在这里  &lt;a href="https://github.com/TelosLabs/rails-world" rel="nofollow" target="_blank"&gt;https://github.com/TelosLabs/rails-world&lt;/a&gt;&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Sat, 19 Oct 2024 12:07:10 +0800</pubDate>
      <link>https://ruby-china.org/topics/43922</link>
      <guid>https://ruby-china.org/topics/43922</guid>
    </item>
    <item>
      <title>Rails 前端 Turbo 的未来的花样：Morphing</title>
      <description>&lt;p&gt;最近在看 Rails world 2023 的视频，这里谈到了 Turbo 的进一步的未来，有事件的朋友可以看下这个   &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=m97UsXa6HFg" rel="nofollow" target="_blank" title=""&gt;https://www.youtube.com/watch?v=m97UsXa6HFg&lt;/a&gt;
&lt;span class="embed-responsive embed-responsive-16by9"&gt;&lt;iframe class="embed-responsive-item" src="//www.youtube.com/embed/m97UsXa6HFg" allowfullscreen=""&gt;&lt;/iframe&gt;&lt;/span&gt;   &lt;/p&gt;

&lt;p&gt;看了之后，我觉得 Rails 的前端继续在邪路上偏离主流一发不可收拾了&lt;/p&gt;

&lt;p&gt;另付补充文字&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.37signals.com/a-happier-happy-path-in-turbo-with-morphing/" rel="nofollow" target="_blank" title=""&gt;https://dev.37signals.com/a-happier-happy-path-in-turbo-with-morphing/&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;大致说下视频的内容：&lt;/p&gt;

&lt;p&gt;Jorge 总结了下 现有的 Turbo Stream 和 Turbo frame 的问题：兔子洞，到处都是代码片段，还需要对不同的 View 的情况下考虑，这让程序员很不开心。&lt;/p&gt;

&lt;p&gt;他认为程序员写页面的时候，最开心的就是  给出请求，之后服务器给出整个返回 就行了。但是这么的话对展现力确实有很大的影响，不会保持浏览器滚动条、其他 dom 节点状态，会有白屏，位置视口变动等问题&lt;/p&gt;

&lt;p&gt;那么有没有解决方案，既可以解决程序员写的爽（即只写全部渲染页面），又保留浏览器滚动条、其他 dom 节点状态的方式呢？&lt;/p&gt;

&lt;p&gt;答案是有，他参考了其他框架（phoenix 的 Liveview？）采取的  morphing 的技术，即使用浏览器将服务器返回的整个页面的的 dom 树，对比请求之前页面之前的 dom 树，之后将 dom 树的不同的东西更新上就行了。其实这个就比较类似 react 的 虚拟节点更新，只不过这里不是虚拟节点，而是真实的 dom 树，无所谓，现在的浏览器都可以很快很好完成这个工作&lt;/p&gt;

&lt;p&gt;利用 morphing 技术，浏览器会找到不同的 dom，更新它，并保持之前的 scrollbar 位置等信息了。大家就可以不写兔子洞，就只管整体渲染一次页面就好了。&lt;/p&gt;

&lt;p&gt;视频演示了下在 basecamp 的 类似看板的地方，将滚动条拉到了面板的最右侧，点击添加新的面板，页面的滚动条并未消失，而是直接在原来最右侧的地方又新增了一个，整个效果就如同使用 react 那种感觉一样。然而这种使用 morphing 的技术则不需要重新写任何代码，就用之前的 render 整个页面的那个即可。&lt;/p&gt;

&lt;p&gt;之后另外使用 broadcast 也实现了类似的效果，即不需要重新写其他 view，就复用即可&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;总结下：就是就前端方面 rails 就只写最普通的 view 爽就完了，剩下的靠 morph 来更新页面的同时，保持之前的浏览器状态，获得 One Page Application 的体验，与主流前端那套是 say goodbye 了。&lt;/p&gt;

&lt;p&gt;我自己的感觉是浪费的拿点传输大小和 CPU 消耗，对比现在的 网络和计算机性能，真的无所谓了。只不过国内的各路卷王，怕是不会用在生产环境的。&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Fri, 27 Oct 2023 17:37:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/43425</link>
      <guid>https://ruby-china.org/topics/43425</guid>
    </item>
    <item>
      <title>Kamal 的讨论与问题汇总</title>
      <description>&lt;p&gt;自己在技术上向随着年纪变大，变得越来越保守，只不过对 rails 的东西还算是比较感兴趣
最近就想试试刚出的部署工具 kamal (MRSK)，利用 docker 和 ssh 技术 做所谓的 0 downtime 的部署，&lt;/p&gt;

&lt;p&gt;我在自己的练习过程中遇到了很多问题，我感觉很多人也会都遇到，就集中下资源，解决，我会在后续中尽量的修改这个帖子&lt;/p&gt;

&lt;p&gt;我先汇总下最烦躁的网络问题，并再次强调： “强者从不抱怨环境”&lt;/p&gt;
&lt;h2 id="问题 1： docker register 拉不了镜像怎么解决？"&gt;&lt;em&gt;问题 1：docker register 拉不了镜像怎么解决？&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;遇到的第一个问题是，docker hub 镜像拉取慢&lt;/p&gt;

&lt;p&gt;国内 docker hub 的连通性基本为 0，但是考虑到 docker 镜像通常都比较大，所以用代理也不是很快， 
如果不嫌弃 aliyun 的话    可以在 aliyun 容器镜像服务 里，有个镜像加速器，之后将这个加速器，配置到 docker 的 config 里
&lt;img src="https://l.ruby-china.com/photo/jicheng1014/38c713ac-10e4-4054-abc0-a6fdfb9dd295.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我这使用的是 docker desktop，所以可以在设置理的 docker engine 编辑代理
&lt;img src="https://l.ruby-china.com/photo/jicheng1014/678d3d21-fe0d-4c0a-a0a1-13eb51517e06.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="问题 2： 想要推私有镜像， 但是 docker hub 连通性太差？"&gt;&lt;em&gt;问题 2：想要推私有镜像，但是 docker hub 连通性太差？&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;docker hub 免费版本提供一个 私有仓库，但是仍然还是这个问题：国内推实在是太慢了...
这里仍然是如果不嫌弃 aliyun 的话，aliyun 有个对应的私有服务，服务也是在“容器镜像服务”板块&lt;/p&gt;

&lt;p&gt;个人版本可以拥有 3 个命名空间，300 个项目，目前免费。企业版 1000+ 个项目，费用是 7k+ 一年，
&lt;img src="https://l.ruby-china.com/photo/jicheng1014/2c862055-c1bc-4e5a-8237-26f2b6216978.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="问题 3： Rails 7.1 给的 dockerfile 在构建的时候慢的吐血。。。"&gt;&lt;em&gt;问题 3：Rails 7.1 给的 dockerfile 在构建的时候慢的吐血。。。&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;实际上是因为安装的时候，去找了 debian 的源，替换就好了，我使用的仍然是 aliyun 的源。。。。&lt;/p&gt;

&lt;p&gt;我的解决方案是 创建一个  base_for_mainland_china，之后其他的都由 from base 改为 from base_for_mainland_china &lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ARG RUBY_VERSION=3.2.2
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base

# 创建 base_for_mainland_china
FROM base as base_for_mainland_china
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources &amp;amp;&amp;amp; \
    sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources


# 其他...
# 将  FROM base as build 替换为下列语句
FROM base_for_mainland_china as build

# ...

# Final stage for app image
# 将最后的 from base 替换为下列语句
FROM base_for_mainland_china

#...

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样，就能避免拉去软件过慢的情况了。&lt;/p&gt;

&lt;p&gt;后续慢慢继续更新&lt;/p&gt;
&lt;h2 id="问题 4： 由于 ssh 到服务器采用的是 非 22 端口， 导致 accessories 在部署的时候，环境变量无法正确安装， 导致部署失败"&gt;&lt;em&gt;问题 4：由于 ssh 到服务器采用的是 非 22 端口，导致 accessories 在部署的时候，环境变量无法正确安装，导致部署失败&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;我用的机器的 ssh 不是标准 22 端口 所以配置 accessories 的时候如下所示&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;accessories:
  db:
    image: postgres:15
    host: 222.222.222.222:12345
    port: 5432
    env:
      clear:
        POSTGRES_USER: 'blog'
        POSTGRES_DB: 'blog_production'
      secret:
        - POSTGRES_PASSWORD
    directories:
      - data:/var/lib/postgresql/data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有个坑，port 并不是 ssh 的端口，而是最后 docker 暴露的端口，需要注意下&lt;/p&gt;

&lt;p&gt;我在测试项目部署的时候出现了这个问题
&lt;img src="https://l.ruby-china.com/photo/jicheng1014/836a1765-01e4-41d1-a6fc-d2a8e61c7aea.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;说是没有找到 .kamal/env/accessories/blog-db.env&lt;/p&gt;

&lt;p&gt;之后结合源码，发现应该是 accessories 的环境变量没同步，&lt;/p&gt;

&lt;p&gt;kamal 同步是使用的 &lt;code&gt;kamal env push&lt;/code&gt;  进行部署 环境变量，翻看代码，发现问题出在这里&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 在 kamal/lib/cli/env.rb 中

      on(KAMAL.accessory_hosts) do
        KAMAL.accessories_on(host).each do |accessory|
          accessory_config = KAMAL.config.accessory(accessory)
          execute *KAMAL.accessory(accessory).make_env_directory
          upload! StringIO.new(accessory_config.env_file), accessory_config.host_env_file_path, mode: 400
        end
      end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;里面的 &lt;code&gt;KAMAL.accessories_on(host)&lt;/code&gt; 返回的是 &lt;code&gt;[]&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;而 这个 accessories_on 的实现是 &lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# kamal/lib/kamal/commander.rb 中
  def accessories_on(host)
    config.accessories.select { |accessory| accessory.hosts.include?(host.to_s) }.map(&amp;amp;:name)
  end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时运行的时候，参数 host 是 SSHKit 提供的，host.to_s 是只有 ip 的，而 accessory.hosts 是包含了 自定义端口的数组，所以筛选代码类似&lt;/p&gt;

&lt;p&gt;&lt;code&gt;["222.222.222.222:1234"].include?("222.222.222.222")&lt;/code&gt; 此时注定没有返回&lt;/p&gt;

&lt;p&gt;如何解决？ &lt;/p&gt;

&lt;p&gt;目前我是直接改 kamal 的源代码，将 accessories_on 这个代码换成 &lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def accessories_on(host)
  # config.accessories.select { |accessory| accessory.hosts.include?(host.to_s) }.map(&amp;amp;:name)
  config.accessories.select { |accessory| accessory.hosts.any? {|raw_host|  raw_host.include?(host.to_s)} }.map(&amp;amp;:name)
end
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="问题 5： 部署的时候程序报错， 导致部署失败， 再次部署的时候出现了 “ERROR (Kamal::Cli::LockError): Deploy lock found”"&gt;问题 5：部署的时候程序报错，导致部署失败，再次部署的时候出现了“ERROR (Kamal::Cli::LockError): Deploy lock found”&lt;/h2&gt;
&lt;p&gt;kamal 在部署的时候使用了文件锁，所以这里需要将锁清除一下，运行 &lt;code&gt;kamal lock release&lt;/code&gt; 即可清除&lt;/p&gt;
&lt;h2 id="参考链接"&gt;参考链接&lt;/h2&gt;
&lt;p&gt;如何在 aws 上使用 kamal 部署 &lt;a href="https://jetrockets.com/blog/how-to-use-basecamp-s-kamal-with-aws-and-github" rel="nofollow" target="_blank"&gt;https://jetrockets.com/blog/how-to-use-basecamp-s-kamal-with-aws-and-github&lt;/a&gt;&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Fri, 13 Oct 2023 00:18:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/43396</link>
      <guid>https://ruby-china.org/topics/43396</guid>
    </item>
    <item>
      <title>[ChatGPT] 5.1 这段时间做了个 answer 42, 基于 rails7 最新的技术栈打造</title>
      <description>&lt;hr&gt;

&lt;p&gt;👋 喜爱 Ruby 的程序员们，五一假期以及闲暇时间，我做了一个很酷的东西：Answer42 的 Web 版本 ChatGPT！🤖&lt;/p&gt;

&lt;p&gt;这个 ChatGPT 是基于 Rails7、tailwindcss 和 turbo 开发的，它有以下优点：&lt;/p&gt;

&lt;p&gt;💡 可以使用自己的 API Key，也可以共享使用服务器的 Key。由于贫穷，服务器的 Key 有限制，每天只能进行 100 个上下文对话，但整体服务器每天有 1 万次上下文对话的限制。&lt;/p&gt;

&lt;p&gt;💡 不用担心 API Key 被封。虽然部署到了境内服务器，但是我们使用了远端代理节点访问 OpenAI。&lt;/p&gt;

&lt;p&gt;💡 自定义度高，你可以选择逐字输出或整体输出，也可以自定义上下文条数。&lt;/p&gt;

&lt;p&gt;💡 可以局部删除部分对话，避免对生成的对话产生影响。&lt;/p&gt;

&lt;p&gt;💡 可以保留历史记录，直接发起新的会话。&lt;/p&gt;

&lt;p&gt;现在，我们邀请你尝试一下这个 ChatGPT，欢迎提出任何意见！点击 [&lt;a href="https://answer42.allchuan.com" rel="nofollow" target="_blank"&gt;https://answer42.allchuan.com&lt;/a&gt;] 开始使用吧！👨‍💻&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/jicheng1014/5d7bc823-5793-478a-b1d7-130171c3174d.png!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Tue, 09 May 2023 15:16:43 +0800</pubDate>
      <link>https://ruby-china.org/topics/43062</link>
      <guid>https://ruby-china.org/topics/43062</guid>
    </item>
    <item>
      <title>不知道为何，最近我连 ruby-china 的 gems 镜像不太稳定，不知道大家是否遇见了</title>
      <description>&lt;p&gt;最近在更新 gems 的时候，偶尔会遇见 timeout 的情况，感觉 gems.ruby-china.com 不太稳定&lt;/p&gt;

&lt;p&gt;想着白嫖了 ruby-china 这么多年 gems 镜像，还是很不好意思的，如果官方需要 donate, 可以开个活动嘛&lt;/p&gt;

&lt;p&gt;另附国内还是有一批人也在做 rubygems 的镜像，大家如果有需要的话，可以试试&lt;/p&gt;

&lt;p&gt;&lt;a href="http://mirrors.ustc.edu.cn/help/rubygems.html" rel="nofollow" target="_blank"&gt;http://mirrors.ustc.edu.cn/help/rubygems.html&lt;/a&gt;   这个是中科大的镜像，比较稳定&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mirrors.tuna.tsinghua.edu.cn/help/rubygems/" rel="nofollow" target="_blank"&gt;https://mirrors.tuna.tsinghua.edu.cn/help/rubygems/&lt;/a&gt;    这个是清华的镜像，他们经费比较足&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Wed, 08 Mar 2023 09:57:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/42927</link>
      <guid>https://ruby-china.org/topics/42927</guid>
    </item>
    <item>
      <title>【内网穿透】1apm，提供一键内网穿透，基于 ssh，无需安装软件</title>
      <description>&lt;p&gt;&lt;a href="http://www.1apm.com" rel="nofollow" target="_blank" title=""&gt;www.1apm.com&lt;/a&gt; 这个域名买了很久了，一直没有利用起来，最近也是因为口罩原因在家，于是抽着不忙，做了一个比较实用的 内网穿透 服务。&lt;/p&gt;

&lt;p&gt;整个项目技术架构为 ruby on rails 7 + tailwindcss3 + alpinejs，感觉开发起来还是很顺，特别是 alpinejs，这玩意在简单逻辑上还挺好用的&lt;/p&gt;

&lt;p&gt;下面是 &lt;a href="http://www.1apm.com" rel="nofollow" target="_blank" title=""&gt;www.1apm.com&lt;/a&gt; 的简介&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;&lt;a href="http://www.1APM.com" rel="nofollow" target="_blank" title=""&gt;www.1APM.com&lt;/a&gt; 是从一个已经挂掉的项目 nuapi 的一个子功能 内网穿透 得到的灵感，通过一个简单的网页，你可以将你的内网服务暴露到公网上，通过一个域名访问。&lt;/p&gt;

&lt;p&gt;这个工具不同于 ngrok 或者 frp 的最大特点就是在做内网穿透的时候，它是不需要安装任何软件的，只需一句命令行，通过系统自带的 ssh tunnel 来提供内网穿透服务。&lt;/p&gt;

&lt;p&gt;这带来了个显而易见的 优点：无需准备，直接穿透&lt;/p&gt;

&lt;p&gt;在开发过程中，有无数的细节等待着我们。我一直认为，除了核心目标，其他的工作，尽量不要麻烦自己。我在开发时最烦的就是与第三方进行对接，举个简单的例子：&lt;/p&gt;

&lt;p&gt;微信的登录
微信需要能访问到我的服务，要想让三方服务访问到，就必须准备公网可见的服务器，这还处在开发阶段，服务器都不一定申请下来了。&lt;/p&gt;

&lt;p&gt;服务器下来了，也得 配环境、配数据库、配域名、还要配那个倒霉的 https 证书！对接一行代码没写，就已经浪费了好几个小时。
然而等将程序部署到之后，如果一旦对接失败（这很正常，很难做到首次对接一次成功），又得去远端翻日志翻数据，再切回本地根据之前的数据再模拟一遍访问，再查找 bug，改 bug，提交代码再重新部署。往往就改了一行，就要花几十分钟做这些重复动作，接着再出现问题，只能换个姿势再来一次，代码没搞多少，一天就已经结束了。&lt;/p&gt;

&lt;p&gt;正所谓 因为太麻烦，所以搞不定。&lt;/p&gt;

&lt;p&gt;有了 1apm 提供的 基于 ssh 的内网穿透，这些操作都可以省略，只需要在开发机上运行对应指令，即可得到公网可见，直连本机端口，支持 https 的网址！&lt;/p&gt;

&lt;p&gt;不仅可以让三方服务微信向你发起请求，更可以直接在增加断点调试，哪里有问题，可以当场查看下变量。工具方便快捷，解决起问题就会得心应手。&lt;/p&gt;

&lt;p&gt;1apm 内网穿透本身不装任何软件，这样就省去了带来的安全性问题 不引入新的软件，用完了直接结束命令即可。&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Thu, 17 Nov 2022 09:48:46 +0800</pubDate>
      <link>https://ruby-china.org/topics/42751</link>
      <guid>https://ruby-china.org/topics/42751</guid>
    </item>
    <item>
      <title>试图解决 “接口在我这里 ok， 为啥到你这就不行了！” 的对接问题</title>
      <description>&lt;h2 id="TLDR"&gt;TLDR&lt;/h2&gt;
&lt;p&gt;（VUE + Rails 实现的）NUAPI (&lt;a href="https://www.nuapi.com" rel="nofollow" target="_blank" title=""&gt;www.nuapi.com&lt;/a&gt;) 的 &lt;strong&gt;域名转发&lt;/strong&gt;可以辅助解决这类“接口在我这里 ok，为啥到你这就不行了”问题。&lt;/p&gt;

&lt;p&gt;我们做了一个简单的视频，可以让你快速了解我们是咋解决的   &lt;a href="https://www.bilibili.com/video/BV1fY4y1s79e/" rel="nofollow" target="_blank" title=""&gt;视频地址&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果你想试用一下 NUAPI &lt;a href="https://www.nuapi.com" rel="nofollow" target="_blank" title=""&gt;www.nuapi.com&lt;/a&gt; ，可以用这个邀请码注册  &lt;code&gt;RUBYDOMAIN&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;另外 NUAPI 也有个不错的 &lt;strong&gt;端口转发&lt;/strong&gt; 的功能可以参考这篇帖子 &lt;a href="https://ruby-china.org/topics/42241" title=""&gt;[内网穿透调试] 使用 NUAPI，0 安装，5 秒钟拥有线上地址调试本地端口&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="正文"&gt;正文&lt;/h2&gt;
&lt;p&gt;在我们软件开发的时候，前后端经常遇到的问题是 后端自信的发布到测试环境后，自己测试是没有任何问题的，但是一旦到了前端测试的时候，就遇到了各种各样的问题。&lt;/p&gt;

&lt;p&gt;我这说几个常见的问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;经常遇到的前端传参数与预期不同的问题&lt;/li&gt;
&lt;li&gt;经常遇到的后端接口返回值与预期不同的问题&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些问题本质上来说解决起来比起技术角度，更多的都是所谓的“繁琐”。要找个大家都 ok 的时间，把涉及到的人拉过来说明问题，再等他们解决，这是十分拖沓的。万一中途出了啥问题，或者本身与某些人有过节，那简直是一场灾难。&lt;/p&gt;

&lt;p&gt;其实我们转过头来想，这些问题，是不是可以通过一些技术手段来解决？&lt;/p&gt;

&lt;p&gt;我们最近研发了 &lt;a href="https://www.nuapi.com" rel="nofollow" target="_blank" title=""&gt;www.nuapi.com&lt;/a&gt; ，这里面的功能"域名转发" 可以较好的解决这些问题：&lt;/p&gt;
&lt;h3 id="NUAPI 的域名转发原理"&gt;NUAPI 的域名转发原理&lt;/h3&gt;
&lt;p&gt;传统上，我们的访问 如下图所示&lt;/p&gt;

&lt;p&gt;&lt;img src="https://image.nuapi.com/20220328133904.png" title="" alt="传统访问流程"&gt;&lt;/p&gt;

&lt;p&gt;我们可以将 API 服务器 做一次 域名转发，即 变为以下的访问流程&lt;/p&gt;

&lt;p&gt;&lt;img src="https://image.nuapi.com/20220328133953.png" title="" alt=" NUAPI 域名转发访问流程"&gt;&lt;/p&gt;

&lt;p&gt;此时，nuapi 即可将访问的请求内容，以及 API 服务器的返回接口，均记录下来，并且可以记录下来的请求的日志。&lt;/p&gt;
&lt;h3 id="如何使用"&gt;如何使用&lt;/h3&gt;
&lt;p&gt;首先在 NUAPI 中新建域名转发，假设我的后端域名地址是  &lt;a href="https://api.github.com" rel="nofollow" target="_blank"&gt;https://api.github.com&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;则我可以在 NUAPI 中的新建域名转发中填写  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://api.github.com/" rel="nofollow" target="_blank"&gt;https://api.github.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://image.nuapi.com/20220324171829.png" title="" alt="20220324171829"&gt;&lt;/p&gt;

&lt;p&gt;创建完毕后，NUAPI 会返回代理域名&lt;/p&gt;

&lt;p&gt;&lt;img src="https://image.nuapi.com/20220324171910.png" title="" alt="20220324171910"&gt;&lt;/p&gt;

&lt;p&gt;点击域名，即可进入日志模式&lt;/p&gt;

&lt;p&gt;&lt;img src="https://image.nuapi.com/20220324171949.png" title="" alt="20220324171949"&gt;&lt;/p&gt;

&lt;p&gt;我们会记录所有经过 nuapi 域名转发的所有请求，以及请求后返回的 body。这时，如果我们想看看到底请求了什么内容，则可以直接登录 nuapi.com 系统 进行查看。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://image.nuapi.com/20220328143234.png" title="" alt="NUAPI 查看请求的 body 以及 返回的 body"&gt;&lt;/p&gt;

&lt;p&gt;这里还有一篇文章可以供大家参考 &lt;a href="https://support.qq.com/products/364823/blog/590837" rel="nofollow" target="_blank" title=""&gt;抓包工具 Charles 与 Nuapi 对比，替代 Charles
 &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;当然，知道自己传了数据，收到了什么数据，可以推测出可能出现的问题在哪里，那有没有更好的方式来解决“在我这里跑 ok，为啥在你那不行”的方案呢？   &lt;/p&gt;

&lt;p&gt;有的，就是我们的请求对比功能：&lt;/p&gt;
&lt;h3 id="请求对比"&gt;请求对比&lt;/h3&gt;
&lt;p&gt;经常出现这种问题：后端用 POSTMAN 访问测试环境，一切 ok，前端接入后， "请求跟 postman 一模一样，但是返回死活报错！“   &lt;/p&gt;

&lt;p&gt;此时我们可以用 NUAPI 的请求对比功能，查询两者区别： &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;首先让后端用 POSTMAN，将请求的 URL 的域名替换为 NUAPI 的转发域名，再次发送请求&lt;/li&gt;
&lt;li&gt;让前端或者 app 端，进行相关操作 引起对应的 请求&lt;/li&gt;
&lt;li&gt;在 &lt;a href="http://www.nuapi.com" rel="nofollow" target="_blank" title=""&gt;www.nuapi.com&lt;/a&gt; 中找到这两次请求，之后点击 加入对比，nuapi 就会将两次请求的所有信息进行一一对比，不同的内容我们就会立即显示出来&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这样，就可以轻易的看出，这两个请求是否一样，如果不一样，是哪些内容部一样了
&lt;img src="https://image.nuapi.com/20220325104652.png" title="" alt="请求的对比"&gt;&lt;/p&gt;
&lt;h3 id="分享"&gt;分享&lt;/h3&gt;
&lt;p&gt;当我们想把一条请求记录分享给其他人的时候，只需要生成一个简单的链接即可，不需要查看的人强制注册 NUAPI 用户，仅拥有 URL 地址即可访问：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://image.nuapi.com/20220328005414.png" title="" alt="20220328005414"&gt;&lt;/p&gt;

&lt;p&gt;当使用分享链接打开地址的时候，他会看到类似这样的界面，此界面无需 NUAPI 即可查看&lt;/p&gt;

&lt;p&gt;&lt;img src="https://image.nuapi.com/20220328005641.png" title="" alt="20220328005641"&gt;&lt;/p&gt;

&lt;p&gt;这样，你可以轻松的将你认为有问题的请求，发送给你的前端或后端。这个分享详细的描绘了整个请求：从请求的时间，到请求的 header 参数，body 参数，以及服务器返回的 header 和 body。&lt;/p&gt;
&lt;h2 id="结束语"&gt;结束语&lt;/h2&gt;
&lt;p&gt;NUAPI 是一个致力于帮助开发人员解决在开发测试的时候遇到的各种问题（包括内网访问、数据对比，mock 请求）的工具，希望通过我们的这个产品，能够为大家的开发期间的沟通带来顺滑的体验。欢迎大家试用，目前 NUAPI &lt;a href="https://www.nuapi.com" rel="nofollow" target="_blank" title=""&gt;www.nuapi.com&lt;/a&gt; 处在试用阶段，所有功能均为免费，大家可以使用 邀请码 &lt;code&gt;RUBYDOMAIN&lt;/code&gt; 注试用，有任何意见或建议可以在本贴留言，我将持续关注。&lt;/p&gt;

&lt;p&gt;NUAPI &lt;a href="https://www.nuapi.com" rel="nofollow" target="_blank" title=""&gt;www.nuapi.com&lt;/a&gt; 的技术栈为 前端 Vue3.2，后端 Ruby on Rails + 少量 Node，使用 K8S 部署在阿里云上。也欢迎大家交流技术实现部分的问题。&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Mon, 28 Mar 2022 15:24:24 +0800</pubDate>
      <link>https://ruby-china.org/topics/42263</link>
      <guid>https://ruby-china.org/topics/42263</guid>
    </item>
    <item>
      <title>使用 NUAPI， 5 秒钟拥有线上地址连接本地端口，用来调试微信公众号</title>
      <description>&lt;p&gt;之前在群里吹牛说的 NUAPI，这里分享下  它的一个功能：不需要安装其他软件，凭借系统自带的 ssh 即可实现的“内网穿透”功能&lt;/p&gt;

&lt;p&gt;TLDR:&lt;/p&gt;

&lt;p&gt;NUAPI (&lt;a href="https://www.nuapi.com" rel="nofollow" target="_blank"&gt;https://www.nuapi.com&lt;/a&gt;) 核心功能之一的“端口转发”功能，提供了只依赖 ssh 的快速穿透方式：无需安装其他软件，可在命令行 ssh 一键转发本地端口到公网 https 域名，用于您的开发。&lt;/p&gt;

&lt;p&gt;坛友 注册码：RUBYSSH&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;众所周知，在我们集成第三方 webhook，或者处理某些特殊 js 的时候，都会遇到一个比较麻烦的事情： “需要一个公网可见的地址”，另外还有不少国内的朋友还会遇到另一个问题：“需要一个备案过的域名”。&lt;/p&gt;

&lt;p&gt;作为典型的微信公众号，js 接口就有下面的规则：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;需要有域名&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;需要 ICP 备案&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;需要鉴权&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;其实在项目刚进行开发的时候，这些东西需要凑齐，还是要花一定的时间的。尴尬的是，有时候这些东西并不是你在一开始就准备好了的，比如此时公司运维休假无法开新的外网可见机器，比如手上只有能在境外使用的域名。抑或是更简单的情况：我现在只想在本地立即开发，不想去碰 nginx，域名，服务器。&lt;/p&gt;

&lt;p&gt;此时 NUAPI 的端口转发即可实现 本地端口直接公网可见，且分配了一个 支持 https 访问的域名&lt;/p&gt;

&lt;p&gt;按下列操作即可：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://imgtu.com/i/qnDJ54" rel="nofollow" target="_blank" title=""&gt;&lt;img src="https://s1.ax1x.com/2022/03/21/qnDJ54.md.png" title="" alt="qnDJ54.md.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;假设你的本地端口是 3000 端口&lt;/p&gt;

&lt;p&gt;使用上面生成的命令在终端执行即可，  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://imgtu.com/i/qnDQK0" rel="nofollow" target="_blank" title=""&gt;&lt;img src="https://s1.ax1x.com/2022/03/21/qnDQK0.md.png" title="" alt="qnDQK0.md.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ssh -R 0:127.0.0.1:3000 nussh.com # 1ZVSDV 是您的授权码，请您妥善保管   &lt;/p&gt;

&lt;p&gt;执行并根据提示输入授权码后，即可以实现 端口的转发，即 &lt;a href="https://291eb686.in.nuapi.com" rel="nofollow" target="_blank"&gt;https://291eb686.in.nuapi.com&lt;/a&gt; 可以穿透到本地的 3000 端口   &lt;/p&gt;

&lt;p&gt;&lt;a href="https://imgtu.com/i/qnsvUU" rel="nofollow" target="_blank" title=""&gt;&lt;img src="https://s1.ax1x.com/2022/03/21/qnsvUU.png" title="" alt="qnsvUU.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;现在 NUAPI 开启内测，邀请码 RUBYSSH 欢迎各位大哥试用&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;ps: &lt;/p&gt;

&lt;p&gt;nuapi.com 前端是 vue3  后端 ruby on rails, 使用 k8s 部署在阿里云上，有任何技术相关的问题，欢迎交流&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Mon, 21 Mar 2022 16:04:35 +0800</pubDate>
      <link>https://ruby-china.org/topics/42241</link>
      <guid>https://ruby-china.org/topics/42241</guid>
    </item>
    <item>
      <title>为何 我从 Webpacker 切换 到 js-bundling 和 css-bundling</title>
      <description>&lt;p&gt;文章同步发布在了我的 blog  &lt;a href="http://blog.3qruok.com/posts/12" rel="nofollow" target="_blank"&gt;http://blog.3qruok.com/posts/12&lt;/a&gt; 中&lt;/p&gt;

&lt;p&gt;之前在自己的项目中一直使用的 rails 自带的 webpacker 用来处理下面的前端策略：
用 webpacker 处理：tailwindcss + scss + turbo + stimulusjs + action cable 的结构。随着 rails 7.0  的正式发布，再参看了 DHH 的对新处理前端的方式的介绍，我觉得 他提供的第二种访问，即 jsbundling，cssbundling 的处理方式，比较适合我现在的项目。&lt;/p&gt;

&lt;p&gt;为何我会抛弃 webpacker？理由主要有如下几点： &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;webpacker 处在专业前端看不懂，不专业的后端配不了的尴尬位置。虽然说 webpacker 尽量做到了开箱可用，但是仍然有时候我需要去对齐进行调整。在这个时候我就是完全懵逼的状态，这个时候我让专业的前端来看，却因为 webpacker 的封装的原因，我的同事无法提供有效的帮助。&lt;/li&gt;
&lt;li&gt;webpacker 运行起来比较卡。这也许是我机器的原因，webpacker 封装的 webpack 在 16G 内存感觉下还是不太行。但是同等情况下使用 vite 来搞纯前端 vue 就没这种感觉。&lt;/li&gt;
&lt;li&gt;webpacker 已不是 rails 推荐的方式了。作为 rails 的重要生态组成部分，一旦不被 rails 推荐，可能以后收到的维护会变少。而前端方案变化太快了，日新月异，虽然现在 仍然是 webpack 的天下，但是谁也不好说以后前端打包方案会不会倒向 rollup 或者退回到 grunt 啥的。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;那又说到了，为啥要转化到 jsbundling 和 cssbundling，而不是 rails 官方最推荐的默认方式：importmap  呢？&lt;/p&gt;

&lt;p&gt;那就要分析 importmap 的优缺点了。这是我总结的 importmap 的优劣。&lt;/p&gt;

&lt;p&gt;优势： 
因为 importmap 是完全基于浏览器的，所以启动一个完整的项目，是可以脱离 nodejs 环境的。即我们可以完全通过 importmap 对应的 cdn 地址，也可以实现不同 npm 包，直接的 import 引用。另外由于不需要本地处理 js，也就没有裁剪，压缩啥的在本地运行，会大大加速了开发的速度。&lt;/p&gt;

&lt;p&gt;劣势：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;兼容性。虽然说 现在主流的浏览器都支持了 import map，但是我没办法确认是否老版本的 android, ios，特别是老版本的微信内置的浏览器，是否支持 importmap &lt;/li&gt;
&lt;li&gt;目前 importmap 暂时不能自动引入依赖文件，如果有某个 js 依赖其他 js，则需要一直手动调用，会比较麻烦。&lt;/li&gt;
&lt;li&gt;无法快速支持 react 的 jsx  或者 vue 的 .vue 文件。如果本身引用了这种前端框架，因为 import map 由于是浏览器级别的引入，所以对这种非 js 标准模式的文件的时候会有一定的问题。虽然可以通过引入其他 js，换一种方式写 jsx 语法之类的来解决这个问题，但是这东西又需要额外的成本了。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;所以我选择了第二种方式  js-bundling 和 css-bundling 来解决我前端问题。&lt;/p&gt;

&lt;p&gt;js-bundling 和 css-bundling 是如何解决这些前端的问题的呢？
其实就是 他们只做为 rails 与前端世界的桥梁，专业的事交给专业的工具来做，bundling 只提供将这些产物于 rails 的传统前端做一个链接。&lt;/p&gt;

&lt;p&gt;其实 js-bundling 和 css-bundling 只是一些脚手架脚本，他们唯一做的事情，就是将 你用专业的前端工具生成的产物，连接到 rails 的 sprocket 中去。&lt;/p&gt;

&lt;p&gt;我们以  js-bundling 为例，当你使用 js-bundling 之后，他会推荐你使用 esbuild 或者 rollup 或者 webpack 来处理你的 js，之后将处理好的产物放在 assets/builds/ 中  之后同时 js-bundling 也会生成一个 assets/config/manifest.js 文件，在这里指定 所有的 assets/builds 映射到 assets/ 上。最重要的是，js-bundling 会在你的 assets:precompile 中做一个钩子，在你 assets:precompile 的时候调用 package.json 中的 对前端的 build 指令，你可以打开 package.json 看 到，这里的 dev 实际上就是用 你选择的 或 esbuild, 或 rollup 或 webpack, 将你的前端文件，最后打包到 assets/builds 文件夹里去。&lt;/p&gt;

&lt;p&gt;最后，就是 rails 4 时代大家比较熟悉的事情。将你生成的前端文件，算 hash，生成文件，丢到 public/assets 下。&lt;/p&gt;

&lt;p&gt;css-bundling 和 js-bundling 的效果几乎一样，唯一的区别是，他的脚手架代码 换成了 build 时候使用 sass 或者 tailwindcss 或者 postcss 而已。&lt;/p&gt;

&lt;p&gt;那么，js-bundling，css-bundling 的优势是什么呢？&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;我认为最大的好处就是  合理解耦了现代前端工具与 rails 框架。现代前端方案变的太快了，百花齐放百家争鸣，谁也不知道最后的赢家是谁，与其挨个适配，不如万变不离其宗，就抓住所有的前端打包工具的最终目的，都是打包出产物，我就处理好产物与 rails 的关系即可。&lt;/li&gt;
&lt;li&gt;专业的事情交给专业的工具，遇到问题，交给专业的人，比如我 jsbundling 选择 webpack 来处理产物，webpack 遇到的问题就是 webpack 的知识体系内的问题，找个 webpack 配置工程师就解决战斗了，这个人甚至可以不需要懂 ruby 懂 rails。&lt;/li&gt;
&lt;li&gt;可以灵活的更换前端打包规则。比如我嫌弃 webpack 运算速度慢，就可以使用 esbuild，我嫌 esbuild 兼容性差，我也可以开发的时候用 esbuild，production 环境用 rollup，只要我将最后的产物扔到 assets/builds 文件夹里就行。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;那有没有劣势呢？我觉得有以下几点值得注意：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;你需要一个比较完备的知识体系，需要了解现代 前端打包的本质目的是什么。&lt;/li&gt;
&lt;li&gt;你需要保持清醒，js-bundling 或者 css-bundling 只是胶水，真正起作用的，是 esbuild, 或者 webpack，如果打包出问题了，你需要了解这些前端的打包知识，或者你有一个能够提供前端打包支持的同事&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;当我确认我能够客服这些劣势后，我认为 js-bundling 和 css-bundling 给我带来的优势是非常大的，于是我选择了这种前端模式&lt;/p&gt;

&lt;p&gt;最后，我采取的模式是  js-bundling 配套 esbuild 处理 js , css-bundling 结合 postcss 处理 css。&lt;/p&gt;

&lt;p&gt;在这里需要额外说明的是，当我使用了 esbuild 后，原来默认的 webpack 的读取文件的方式就不可用了。在我的架构中，主要影响的是 stimulujs 的引用和  channels 的引用。原来那种获取当前目录下的所有文件，以及批量导入当前目录所有文件的语法，实际是基于 webpack 的，现在都需要手动将每个文件引用，抽空我再写下这个改造过程。&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Sun, 16 Jan 2022 17:02:34 +0800</pubDate>
      <link>https://ruby-china.org/topics/42081</link>
      <guid>https://ruby-china.org/topics/42081</guid>
    </item>
    <item>
      <title>解决 bundle install 时，因为网络原因无法安装 基于 github 的 gem 的问题</title>
      <description>&lt;p&gt;文章来自我自己的 blog: &lt;a href="https://blog.3qruok.com/posts/9" rel="nofollow" target="_blank"&gt;https://blog.3qruok.com/posts/9&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;==========&lt;/p&gt;

&lt;p&gt;随着 docker 部署越来越流行，现在特别大的概率出现使用 Docker 部署 rails 的项目。但是身在国内，难免会被网络状况影响。而由于 docker 打包是基于 docker 镜像本身的，而在镜像本身里，不太好处理网络互联互通的问题。&lt;/p&gt;

&lt;p&gt;常规方式有下面几种：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;. 在部署机的交换机中就解决网络互联互通问题
即部署机天然具有互联互通。这种解决方案适合部署机在自己的内网当中。此时我们只需要从上层的路由器处理好相关业务即可。如果条件允许，其实这种解决方案应是最优解。但是如果不具备条件，如部署机是在云平台上，那就没办法了。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;. 将部署机直接部署到 hk 或 singapore
这种方式的好处在于 hk 或者 Singapore 的节点接入大陆的速度都还不错，连接 github 以及其他国外服务也很方便，是不错的选择。但缺点就是 费用稍高，再就是远离了生产环境，build 好的镜像回推又涉及跨网络处理。最后就是合规问题，有的项目可能会有要求。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我最近找到了第三种方案：利用 coding.net 同步 github 仓库的功能
这种方案是当以上两种条件均不满足时的策略，缺点是 多了一层对 coding 有依赖，并且会对 gemfile 进行一点点修改&lt;/p&gt;

&lt;p&gt;整体的思路就是查找 Gemfile 中 使用了 github 地址的项目，在 coding 中建立镜像，再在 gemfile 中做修改&lt;/p&gt;

&lt;p&gt;具体方案如下：
第一步：在 coding.net 中新建仓库，选择 从外部导入仓库&lt;/p&gt;

&lt;p&gt;确定建立这个仓库，并重命名仓库，我这里强烈建议，将原来的 github 地址的“/”替换为“-”（如 github 里的地址是 guard/listen  则这里仓库名称改写为 guard-listen）以方便我们在 Gemfile 中做整体替换。另外需要确保新建的仓库打开了自动同步&lt;/p&gt;

&lt;p&gt;之后，coding 就会帮我们自动镜像对应的仓库了。&lt;/p&gt;

&lt;p&gt;第二步，修改 Gemfile，将具体的 github 地址改为 coding 的地址
这里就说明到最开始我为何重命名的时候有规律了。一般情况下，我们的 Gemfile 长这样&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "3.0.3"

gem "listen", github: "guard/listen"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时我们看到这里的重点 git_source(:github) &lt;/p&gt;

&lt;p&gt;我们可以改造这里，变为 &lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git_source(:github) do |repo_name| 
  if ENV["GEM_GITHUB_PROXY"] == "1"
    "https://e.coding.net/gems-github-mirror/gems-mirror/#{repo_name.split("/").join("-")}.git"   # 此处需要确定你的 coding 仓库地址规则
  else
    "https://github.com/#{repo_name}.git" 
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再次强调，由于我之前设置 coding 的 repository 的名称是 将 / 替换为了 "-"，所以我这里就可以直接生成对应的 coding 的仓库地址。&lt;/p&gt;

&lt;p&gt;之后我们在 dockerfile 中加入环境变量 GEM_GITHUB_PROXY 就可以只在 docker 打包的时候，使用对应的代理了。如果你想一直走 coding 的代理，自然可以改造，直接返回 coding 的地址。&lt;/p&gt;

&lt;p&gt;总结这个方式的优缺点&lt;/p&gt;

&lt;p&gt;优点：
不依赖于其他网络技术，即可实现更新 Gemfile 中强依赖 github 的仓库。&lt;/p&gt;

&lt;p&gt;缺点： &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;需要自己手动建立 github 对应的 coding 的仓库。 &lt;/li&gt;
&lt;li&gt;需要少量改变代码。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最后感慨一下，感觉网络互联互通问题是国内 docker 技术推广的最大的障碍。从拉镜像，到给镜像里装依赖，再到推镜像，再到部署，部署里又从搭建 k8s 到部署封装 helm 到最后到运维，基本上都会遇见网络互联互通问题。如果有哪家厂商能够从头到脚提供一揽子解决方案，或透明或超级简单配置后就能解决这些问题，肯定会超级受欢迎，至少我肯定会愿意付费使用。&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Wed, 29 Dec 2021 02:01:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/42039</link>
      <guid>https://ruby-china.org/topics/42039</guid>
    </item>
    <item>
      <title>这两天对 view_component 的一些感悟</title>
      <description>&lt;p&gt;同步发在利用 Rails 7 新搭的 blog &lt;a href="http://blog.3qruok.com/posts/7" rel="nofollow" target="_blank"&gt;http://blog.3qruok.com/posts/7&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;=======&lt;/p&gt;

&lt;p&gt;最近在写 blog 和写 pushconfig 这个项目的时候，发现了原来排斥使用的 view_component 其实还是不错的&lt;/p&gt;

&lt;p&gt;view_component 是 github 推出的一个 view 级别的抽象组件。&lt;/p&gt;

&lt;p&gt;对我来说，view_component 最大的好处是帮我隔离了 view 的复杂，将 rails 原来的 view 的各种魔法都隔离的出来，所有的参数，甚至包括 current_user 这种变量。在我的使用中，但凡于 view_component 相关的对象，都会作为 view_component 的 initialize 参数传入进去。通过这样的方式，我可以控制 view_compnent 组件的复杂性。另外一个不错的内容来自于测试，我可以不用依赖比较麻烦的用户登录来渲染整个页面，之后再测试这整个页面的局部，而是直接将变量传进去，并且只渲染 view_component 这个对象本身生成的 html，从而进行测试。&lt;/p&gt;

&lt;p&gt;当然 view_component 的介绍里，他的渲染还比 partial 的渲染快很多，但这对于我来说，其实影响并没有那么大。&lt;/p&gt;

&lt;p&gt;当然 view_component 在我的使用中也遇到了两个地方不太好：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;目录结构&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;目前我使用的是 view_component 默认推荐的目录结构，这家伙会把 view 文件和 init 文件放在同一个目录下，这样会造成 components 文件夹的文件比较多。看起来会比较混乱&lt;/p&gt;

&lt;p&gt;﻿&lt;/p&gt;

&lt;p&gt;另外一个比较尴尬的问题是 tubor stream 在 model 里直接推送 view_component 需要特殊的技巧，因为 view_component 这家伙在渲染的时候是需要   view_context 的，而这个东西是在 controller 的对象里才有的，而如果是在 model 里，这里默认情况下是没有 view_context 的，我们无法使用类似
MessageComponent.new(message: message).render_in(view_context)
的语法的，那么怎么处理这个问题呢？
参考
&lt;a href="https://github.com/github/view_component/issues/1106" rel="nofollow" target="_blank"&gt;https://github.com/github/view_component/issues/1106&lt;/a&gt;
给出的解决方案是&lt;/p&gt;

&lt;p&gt;broadcast_append_to(
  'posts',
  html: PostComponent.new(post: self).render_in(ActionController::Base.new.view_context)
)&lt;/p&gt;

&lt;p&gt;这个我自己没试，只是贴在这里了&lt;/p&gt;

&lt;p&gt;另外关于 view_component  关于  form_for   form_with 的兼容性问题，其实我没有太在官网 known issues 上看懂，回头弄明白了再补充下吧&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Mon, 27 Dec 2021 01:12:14 +0800</pubDate>
      <link>https://ruby-china.org/topics/42030</link>
      <guid>https://ruby-china.org/topics/42030</guid>
    </item>
    <item>
      <title>成年人全都要，如何共用 Bootstrap 与 Tailwind CSS</title>
      <description>&lt;p&gt;tailwindcss 作为最近流行的前端工具，能让开发前端的效率健步如飞， &lt;/p&gt;

&lt;p&gt;但是由于 tailwindcss 里几乎没有成套的组件，所以从 0 开发比较吃力&lt;/p&gt;

&lt;p&gt;说到成套的组件，还是 bootstrap 强，但是 bootstrap 就没有 tailwindcss 那么灵活&lt;/p&gt;

&lt;p&gt;那么，有没有一种方案，能让这两种混用呢？&lt;/p&gt;

&lt;p&gt;最近 tailwindcss lab 推出的视频&lt;/p&gt;

&lt;p&gt;&lt;span class="embed-responsive embed-responsive-16by9"&gt;&lt;iframe class="embed-responsive-item" src="//www.youtube.com/embed/oG6XPy1t1KA" allowfullscreen=""&gt;&lt;/iframe&gt;&lt;/span&gt; &lt;/p&gt;

&lt;p&gt;提供了一种思路：&lt;/p&gt;

&lt;p&gt;类似  esbuild 的那种搞法，就让 tailwindcss 独立运行，最终生成独立的 css 文件，之后再交给其他工具处理就好了&lt;/p&gt;

&lt;p&gt;那么  如何折腾呢？&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;webpack 不再直接处理 tailwind, 转而使用 tailwindcss 自行处理&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -D tailwindcss postcss    # 注意  是 -D， 以开发模式安装   这样就可以避开 webpacker 里  postcss7 的坑
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;修改 tailwind.config&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = {
  mode: 'jit',
  purge: ['./app/**/*.html.erb', './app/**/*.html.haml', './app/helpers/**/*.rb', './app/**/**/*.js',],
  darkMode: false, // or 'media' or 'class'
  corePlugins: {
    preflight: false,
  },
  prefix: 'tw-',
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有两个重点，第一个重点：corePlugins 里  关闭了 预设 preflight,    因为每家都有自己的 normalize, 因为是以 bootstrap 为基础，所以关闭 tailwind 的预设。第二个重点是  prefix, 这样就将  所有 tailwinds 的 class 加了个前缀 "tw-"  如  原来 container 就变为了  tw-container   这样就可以避免跟 bootstrap 产生冲突&lt;/p&gt;

&lt;p&gt;plus，这里需要采用 jit 模式运行，所以需要有 java 环境&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在命令行里执行  &lt;code&gt;npx tailwindcss --output app/assets/stylesheets/tailwind.css --watch&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这样，就可以自动生成你使用的 tailwind.css 文件了 &lt;/p&gt;

&lt;p&gt;接着，你就可以用 assets 那一套来处理这个 css 文件了&lt;/p&gt;

&lt;p&gt;部署的时候有两种选择，一种是 开发的时候就把这个 tailwind.css 写进版本库中，就当成是自己写的文件就好   或者是  忽略这个文件，但是在 precompile 之前   先运行 npx tailwindcss --output app/assets/stylesheets/tailwind.css   生成干净的文件即可&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Tue, 12 Oct 2021 00:22:49 +0800</pubDate>
      <link>https://ruby-china.org/topics/41756</link>
      <guid>https://ruby-china.org/topics/41756</guid>
    </item>
    <item>
      <title>省钱之旅路漫漫，论我在阿里云 k8s 的一次实践</title>
      <description>&lt;h2 id="前置说明"&gt;前置说明&lt;/h2&gt;
&lt;p&gt;如果你的业务在快速增长，且你的技术栈比较单一（比如就只有 rails 和 前端 nginx）, 请你直接忽略掉这篇文章
本文主要对象为：业务较为稳定，捉摸着省钱的，开发任务不重的团队。至于技术栈，无所谓复杂不复杂&lt;/p&gt;

&lt;p&gt;我花了两周的时间，才摸到了 k8s 的基本规则，并理论验证了阿里云 k8s 在高可用的情况下做到省钱。 
把我的痛苦经历写出来，希望能对想碰这块的朋友做出一点贡献。&lt;/p&gt;
&lt;h2 id="为啥想起来研究 k8s"&gt;为啥想起来研究 k8s&lt;/h2&gt;
&lt;p&gt;想省钱。利用 k8s 的弹性扩容：在低峰期开固定的资源，之后在高峰期再怼临时资源，高峰回落，关闭多余资源。&lt;/p&gt;

&lt;p&gt;虽然很多运维使用 k8s 的原因是因为他能隔离环境，以及比较轻松就能实现的滚动更新， 
但是对于我这种 单一技术栈 rails 的人来说，意义反而不大。&lt;/p&gt;
&lt;h2 id="依次遇到的困难"&gt;依次遇到的困难&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;k8s 知识的复杂度
这是最大的困难，概念太多。我并非运维出生，很多概念太多太复杂，这里仅仅指出以下名词，这是我觉得必须要知道的&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;master
worker
node
pod
service
ingress-controller  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;搭建 k8s 上我的选择&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最开始的想法是自建 k8s。对于入门 k8s 的人来说，这简直是史诗级灾难，最后发现最简单的方式就是利用 rancher，先用一个 docker 跑起来，之后在 rancher 的指引下，新建一个集群，这种操作只用点鼠标以及拷贝命令执行，集群几乎就是点击就送。&lt;/p&gt;

&lt;p&gt;但是，如果自建 k8s，除非你的 k8s 有完善的管理工具可以去开启或关闭你的 worker 节点，否则，k8s 本身的弹性伸缩 pod，对你是没有意义的，因为 pod 是跑在机器上的，pod 减少了，机器没减少，费用并不会减少，这点一定要想清楚。&lt;/p&gt;

&lt;p&gt;rancher 这个 k8s 管理工具其实已经实现了 aws 的 EC2 以及 ALIYUN 的 ECS 的伸缩。但是这里有个问题，ECS 启动，是在是慢的抠脚，从 ECS 启动到 k8s 初始化完默认的 pods，最终能在这个 node 上跑自己的 pod，前前后后要 3-5 分钟。这种长时间的等待，是肯定没办法接受的。&lt;/p&gt;

&lt;p&gt;另外，自建集群还有一个烦恼：如果你要实现高可用，那 master 就不能只有一台，且 master 是不能当 node 使用的。这就意味着，至少我得出两台 2C2U 的机器做 master 节点。&lt;/p&gt;

&lt;p&gt;那么，我就走向了另一种方式，托管式的  k8s，因为我司使用的是 aliyun，所以我这就以阿里云的 托管 k8s 作为解说&lt;/p&gt;

&lt;p&gt;阿里云的 k8s 我主要研究了两种&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ack   即 阿里云官方提供 master，我们提供 ECS 当 worker 节点即可。&lt;/li&gt;
&lt;li&gt;ask   即 阿里云官方提供 master，以及虚拟的 worker 节点，我们启动的 POD 会跑在一个叫做 ECI 的东西上，而 ECI 就是个容器。&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;阿里云 托管 k8s 的费用简析&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;固定支出&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;咱先说啥都不干的情况下的费用&lt;/p&gt;

&lt;p&gt;两个负载均衡：一个作为内网 API SERVER 的负载均衡，即你的 worker 之间的互相通信，这个没办法省。还一个是 ingress 使用的外网的负载均衡，如果你的集群是对外暴露服务，那也没办法省，二者这个还要被收流量费         忽略流量费，两个负载均衡都选最便宜的，总价大概是 0.12 * 24 * 30 = 86.4   流量是 8 毛 1G   咱估算一个月跑 100G   就 160 块&lt;/p&gt;

&lt;p&gt;SNAT，用来 worker 节点上上网工作。这个费用是  0.24 每小时  再 加上个 CU 的计算。一个月算下地就是 172 + CU，咱估算成 200。但是，如果你有另外一台机器配了外网 IP，且你知道如何自建 SNAT 的话，这个钱可以省，只不过需要多算下这台机器的成本，比如你预估是 80 块&lt;/p&gt;

&lt;p&gt;小破存储日志  可以几乎忽略，咱按 20 算&lt;/p&gt;

&lt;p&gt;最终，固定支出是  160 + (200 | 80) + 20 = 380 |  260&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;工作节点支出&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;说到了重点，ASK 和 ACK 的重要差别，worker 节点的费用：&lt;/p&gt;

&lt;p&gt;从维护性角度上来说，ask 明显更利于 管理，毕竟少了一层 worker 的管理。&lt;/p&gt;

&lt;p&gt;但是，同志们，ASK 使用的 ECI 是史诗级的贵，ECI 只有 按量收费 一种模式，其中 CPU 的收费是接近 0.45/小时/1vCPU，内存是接近 0.05 每小时。也就是说，单纯的 1C1G 的 ECI，跑满一个月，收费是  150 元/月。同时我看了 ECS,   如果是包年包月，1C1G 大概是 30 块左右，如果是按量付费，大概是 80 块左右，所以在 1C1G 的情况下 ECI 的收费是 ECS 按量的 2 倍，包年包月的 4 倍&lt;/p&gt;

&lt;p&gt;ASK 的模式是  所有的服务都会按照 ECI 来跑。比如我在 k8s 肯定是要跑 PUMA 提供服务的，比如我们现在的服务 &lt;a href="http://www.admqr.com" rel="nofollow" target="_blank" title=""&gt;www.admqr.com&lt;/a&gt; 的 puma 是跑了两台包年包月的 4C8G 的机器上的，那如果我要在 ASK 的 k8s 上跑，哪怕不算 k8s 的固定费用，我想打平 ECS 的成本，则只能开两个 1C2G 的 ECI，且不能有任何弹性扩充。这明显的感觉就是非常之不划算。&lt;/p&gt;

&lt;p&gt;所以，阿里这种 ASK 的 k8s，应该重点是想解决土豪玩家解决部署难，运维难的问题，而不是穷玩家解决省钱的问题&lt;/p&gt;

&lt;p&gt;那么  ACK 模式吧&lt;/p&gt;

&lt;p&gt;ACK 模式的 k8s 是将 ECS 加入到工作节点，看起来似乎是 跟 私有部署 rancher 差不多的弹性扩展方式，然而，阿里云还是考虑到了这个扩容慢的问题，ACK 上，通过增加
 virtual-kubelet-autoscaler 应用，就可以实现在 ACK 模式下，直接跑启动起来非常快的 ECI 了&lt;/p&gt;

&lt;p&gt;等等，刚不是说 ECI 贵的抠脚么，咋还用？&lt;/p&gt;

&lt;p&gt;重点来了，咱可以把常驻的 pod，跑在我们开的 ECS 上呀，而且，咱可以利用 k8s 开了的 SLB 对抗抢占式实例的释放风险，直接开抢占式实例的 ECS 来降低成本！&lt;/p&gt;

&lt;p&gt;这里有个小技巧，阿里云在创建 ACK k8s 的时候会默认新建 ecs，此时非常坑的只能选择又贵又坑的实例，但是其实你可以向我一样，选择 加入已有实例，之后再在 ECS 面板上，开通两台抢占式实例，之后限制抢占式实例的最高限价为 按量收费，比如我这里开通的 2c4u 的服务器，目前的价格是 0.07 每小时，运气好的话一个月只要 50 块 最贵估计也不会超过 300&lt;/p&gt;

&lt;p&gt;所以，在没有弹性的时候，运气最好的情况两台就是 100 块&lt;/p&gt;

&lt;p&gt;virtual-kubelet-autoscaler 的作用就是，当你的 ecs 因为资源问题，没办法再添加新的 pod 的时候，他会帮你开启 pod 所需的能达到你 pod 硬件条件的 ECI，咱只需要这个 ECI 启动时间不超过 6 个小时，就相当于省下了 1C1G 不停运转的钱&lt;/p&gt;

&lt;p&gt;最后  我拿 &lt;a href="http://www.admqr.com" rel="nofollow" target="_blank" title=""&gt;www.admqr.com&lt;/a&gt; 来规划下，ack 的使用情况  目前毛驴是使用了 2 台 4C8G 跑 puma ,和 1 个 sidekiq，1 台 4c8g 跑 4 个 sidekiq,   平时他们的 cpu 都不会超过 10% 高峰期出现在 9-11 点  晚 7-10 点  以及 0-1 点的 sidekiq 结算时间&lt;/p&gt;

&lt;p&gt;于是乎，puma 我平时跑 0.25 CPU 最大 0.5 个 CPU   1G 的内存  cpu 超过 80，内存 80% 开始扩展    为了高可用，咱闲时就跑 2 个 pods 就好，估计第一个扩展 pod 开启都不需要 ECI。因为 sidekiq 本身不需要做高可用，所以就平时跑一个 0.5 CPU 1G 即可，需要时候再扩展都来得及。&lt;/p&gt;

&lt;p&gt;压力时刻 ECI 会开启，根据之前的 pod 的性能约束，其实它最多也只会开启 0.5 1G 的那种 ECI 型号，因为之前咱算了下，高峰时间是 2 + 3 + 1 即 6 个小时。好处是，ECI 是按秒计费，估计实际时间应该在 5 个小时左右。咱按高峰时刻，会启动 2 个 0.5 CPU 1G 内存的机器，即 1C2G 的费用*6 个小时 &lt;/p&gt;

&lt;p&gt;综上所述，类似 www.admqr.com 这种项目跑在 k8s, &lt;/p&gt;

&lt;p&gt;最终使用 ACK 的 worker 的估算成本，就是 &lt;/p&gt;

&lt;p&gt;100 + 1C2G 的小时费用* 6 小时 &lt;em&gt;30   即 100 + （0.45 + 0.1）&lt;/em&gt; 30 = 100 ECS+ 16 弹性 ECI = 116 块&lt;/p&gt;

&lt;p&gt;最终，咱再加上  K8S 在阿里的固定支出 300 左右（260 ~ 380）,  最终 k8s 的全部支出估计在 500 左右&lt;/p&gt;

&lt;p&gt;我们再回过头来看看 ACK 与现在 传统的 ECS 省钱省在哪里：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;原来常驻的两台 4C8G 的  puma，被 2 台 2C4G  + 偶尔启动的 0.5C1G 的 ECI 代替了&lt;/li&gt;
&lt;li&gt;原来的 跑 sidekiq 的主力机型 4C8G，被偶尔启动的 ECI 以及 前面两台 2C4G 的机器跑完 puma 剩余的性能，代替了&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;但是 k8s 多出了固定支出 SNAT + 2 个负载均衡。严格意义上来说，只是多了一个负载均衡，因为本质上，ecs 这种传统模式，最终还是要接一个负载均衡才能规避单点风险，另外 SNAT 收费这块，我真没经验到底会跑出多少钱，作为省钱小能手，我一般选择开一台机器做手动 SNAT &lt;/p&gt;

&lt;p&gt;那么传统的，费用是多少呢？&lt;/p&gt;

&lt;p&gt;三台 4c8g  虽然我们开的是包年包月，但是按照最省钱的方式的话，可以以抢占模式开通性能突发性能型，之后开启突破性能积分
目前华南 C 区是 0.18 / 小时   算下地就是 130, 再加上性能突破，咱按 150 算，即 3 台是 450 块，再加上一个 SLB，差不多就是 500 多了呗&lt;/p&gt;

&lt;p&gt;当然这么便宜是有风险的，抢占式服务器，会有 3% 的几率释放，如果要完全避免这种风险，就可以用的倒霉的 包年包月计算型，ecs 的费用差不多就是  1200 元最后估计 1300 &lt;/p&gt;
&lt;h2 id="结论"&gt;结论&lt;/h2&gt;
&lt;p&gt;在想做到极致成本控制的情况下，其实 能弹性伸缩的 ACK 以最省钱的模式运行 和 传统的 ECS 以抢占式实例 + 性能突发 + 性能突破，最后的费用是差不多的。如果你之前是搞了 抢占式 + 性能突发，说实在话，单纯为了省钱折腾 k8s，没有太大意义。如果是其他方式运行的 ecs，则 k8s 能省点钱，但是需要你付出极大的运维代价（我被 k8s 折磨的死去火来的两个星期，现在天快亮了我还在含泪发帖），之后你还能收货常见的部署统一，滚动发布，进一步健壮系统的 k8s 带来的好处。&lt;/p&gt;

&lt;p&gt;k8s 就算在坑爹的硬件条件下，还是可以保持了高可用，哪怕节点被回收，只要不是同时回收两个节点，系统都可以自行恢复。量突然增大，只要前面的 SLB 不倒，系统也可以做到自动扩展。&lt;/p&gt;

&lt;p&gt;ecs 在 slb 的加持下可以做到某个 ecs 挂了还能残血支撑，但是性能就没办法自动恢复，需要人工参与，流量增大因为没弹性，就一点办法没有了。&lt;/p&gt;
&lt;h2 id="后记"&gt;后记&lt;/h2&gt;
&lt;p&gt;k8s 真是太复杂了，经过大量折腾，最后得出这个结论，一度让我觉得我浪费了 2 周的时间。然而在跟 ruby-china 的朋友在群里吹牛 k8s 后，最后觉得还是值了，毕竟技多不压身，祝各位坛友学富五车&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Sat, 11 Sep 2021 04:20:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/41673</link>
      <guid>https://ruby-china.org/topics/41673</guid>
    </item>
    <item>
      <title>puma 只会配 phased-restart ? 是时候配一个 hot restart 了</title>
      <description>&lt;p&gt;之前我介绍了 puma 的三种启动方式，  &lt;a href="https://ruby-china.org/topics/41619" title=""&gt;normal，hot, 以及 phased 启动方式&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;虽然从效果上来说，phased restart 启动方式最好，配置也最简单，只需要替换 kill 信号量到 SIGUSR1 就可以了，&lt;/p&gt;

&lt;p&gt;但是 phased restart 也有非常明显的问题：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;当 gemfile 有更新的时候，phased 更新会出现找不到 gem 的 问题&lt;/li&gt;
&lt;li&gt;无法修改 config/puma.rb 文件，以及部分 yml 修改无效&lt;/li&gt;
&lt;li&gt;新老程序的 worker 会同时存在，有的业务场景一旦新逻辑上线，再走老逻辑就会出问题&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;那么   退而求其次，我们能否在保证 0 downtime 的情况下解决这个问题呢？&lt;/p&gt;

&lt;p&gt;答案是     利用 socket.activation 实现 hot restart。建立了 socket.activation 之后，在 puma.service 在重启的时候，消息会先进入这个 socket.activation 进行处理，此时访问并不会返回失败，而是 holding 中，当 puma.service 重启完毕之后 这个 socket 就会将存储的连接丢给 puma.service 来处理。&lt;/p&gt;

&lt;p&gt;我这里举个例子   比如  我们原来有一个  叫做 puma.service 的 systemd 服务，为了建立对应的 socket  我们需要在 /etc/systemd/system/ 中建立 puma.socket , 注意  这里名字必须和 service 对应 &lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Puma HTTP Server Accept Sockets

&lt;span class="o"&gt;[&lt;/span&gt;Socket]
&lt;span class="nv"&gt;ListenStream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0:9292   

&lt;span class="c"&gt;# AF_UNIX domain socket&lt;/span&gt;
&lt;span class="c"&gt;# SocketUser, SocketGroup, etc. may be needed for Unix domain sockets&lt;/span&gt;
&lt;span class="c"&gt;# ListenStream=/run/puma.sock&lt;/span&gt;

&lt;span class="c"&gt;# Socket options matching Puma defaults&lt;/span&gt;
&lt;span class="nv"&gt;NoDelay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;ReusePort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;Backlog&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1024

&lt;span class="o"&gt;[&lt;/span&gt;Install]
&lt;span class="nv"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sockets.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;特别注意的是，这里的 ListenStream 必须和 puma.rb 中的 bind 对应起来（不对应 socket 就不知道往哪转发了）&lt;/p&gt;

&lt;p&gt;之后我们要修改 /etc/systemd/system/puma.service   将依赖添加进去&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=Puma HTTP Server
After=network.target
Requires=puma.socket     # 看这里  添加这个依赖

[Service]
# Puma supports systemd's `Type=notify` and watchdog service
# monitoring, if the [sd_notify](https://github.com/agis/ruby-sdnotify) gem is installed,
# as of Puma 5.1 or later.
# On earlier versions of Puma or JRuby, change this to `Type=simple` and remove
# the `WatchdogSec` line.
Type=notify

# If your Puma process locks up, systemd's watchdog will restart it within seconds.
WatchdogSec=10

User=deploy
WorkingDirectory=/var/www/pushconfig/current
ExecStart=/home/deploy/.rvm/bin/rvm 2.7.3 do bundle exec puma --keep-file-descriptors -C /var/www/pushconfig/shared/puma.rb
# 注意这里的--kep-file-descriptor 如果是使用的 bundle exec， 一定要加这个参数

# ExecReload=/bin/kill -TSTP $MAINPID
ExecReload=/bin/kill -SIGUSR2 $MAINPID    #注意这里，可以换为 USR2， 用来启动 

StandardOutput=append:/var/www/pushconfig/shared/log/puma_access.log
StandardError=append:/var/www/pushconfig/shared/log/puma_error.log

# ......
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后  需要运行以下指令&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; systemctl daemon-reload          # 用于重新加载 systemd，毕竟文件变动了&lt;/li&gt;
&lt;li&gt;systemctl enable puma.socket puma.service    # 启用 socket 的重启后自动运行&lt;/li&gt;
&lt;li&gt;systemctl start puma.socket  puma.service     # 启动&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当然在进行这些操作之前，建议你先停止老的版本的 puma.service&lt;/p&gt;

&lt;p&gt;当启动完毕之后   puma 的 hot restart 功能就设置完毕了    此时，无论你如何重启，用户都不会直接 503 &lt;/p&gt;

&lt;p&gt;如何验证呢？你在这个时候可以 stop puma.service 甚至 kill 掉 puma.service 的主进程！&lt;/p&gt;

&lt;p&gt;按照常规，这个时候你访问网站 网站的 nginx 肯定是报 503，但是现在，感觉是访问卡了一下，之后正常 200 了！&lt;/p&gt;

&lt;p&gt;为何出现这个情况呢？是因为 puma.socket 首先会先将你的访问 hold 住，同时去 找自己同名的 service 去处理，如果 service 没启动，就会启动这个 service&lt;/p&gt;

&lt;p&gt;如果你是用 stop 命令 来关闭 service  这个时候你也会看到 一个 warning&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy@rifle:/etc/systemd/system$ sudo systemctl stop PumaSSLGuala.service
Warning: Stopping PumaSSLGuala.service, but it can still be activated by:
  PumaSSLGuala.socket
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当启动完毕 service 后    socket 就会将之前暂存的连接丢给  service 继续处理。于是之前的访问就能收到 新启动的 puma 返回的 200   &lt;/p&gt;

&lt;p&gt;你看 连 kill 都没问题，restart 自然不在话下&lt;/p&gt;

&lt;p&gt;可以自行测试，重启 puma 的时候，另外个 terminal 拼命访问，感觉就像是网突然卡了一下，接着正常了。&lt;/p&gt;

&lt;p&gt;这是我配置时候总结出来的一些注意点&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;jruby 不支持 socket activation，所以如果你是用 jruby 跑的，对不起 用不了&lt;/li&gt;
&lt;li&gt;socket 里 bind，必须和 puma 中的 puma bind 的地址是一致的。比如你 puma.rb 监听 9292 端口，那你 socket 就必须是 9292&lt;/li&gt;
&lt;li&gt;socket 和 service 必须同名。比如 你的 puma 的  service 叫 app.service 那你的 socket 就得是 app.socket&lt;/li&gt;
&lt;li&gt;最后，也是坑了我一阵的，是如果你跟我一样  在操作 puma 的  service 里，是以  bundle exec 来启动 puma 的，请务必在后面加入  &lt;code&gt;--keep-file-descriptors&lt;/code&gt; 参数，否则 puma 就启动失败，报  `for_fd': Bad file descriptor - not a socket file descriptor 这个错&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最后说下 capistrano-puma，其实我并不是手写 systemd 的 service，而是使用的  capistrano-puma 的 systemd 帮我生成 service 的文件。&lt;/p&gt;

&lt;p&gt;如果你跟我一样，也是用 capistrano-puma 生成的  puma 的 systemd 服务，那么遗憾的告诉你  capistrano-puma 目前（2021 年 08 月 26 日，5.0.4 版本）还不支持    好消息是  已经有网友发了个 PR &lt;a href="https://github.com/seuros/capistrano-puma/pull/324" rel="nofollow" target="_blank"&gt;https://github.com/seuros/capistrano-puma/pull/324&lt;/a&gt;   可以生成这种 hot restart 的方式的   坐等官方 merge&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Thu, 26 Aug 2021 18:54:37 +0800</pubDate>
      <link>https://ruby-china.org/topics/41620</link>
      <guid>https://ruby-china.org/topics/41620</guid>
    </item>
    <item>
      <title>如何 0 downtime 重启？ puma 的重启方式讲解</title>
      <description>&lt;p&gt;今天有小伙伴又在问 puma 在部署时候的重启方式，怎样做到 0 downtime，我这刚好整理了一篇笔记  分享给大家&lt;/p&gt;
&lt;h2 id="参考文章"&gt;参考文章&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/puma/puma/blob/master/docs/restart.md" rel="nofollow" target="_blank" title=""&gt;puma/restart.md at master · puma/puma&lt;/a&gt;
&lt;a href="https://5xruby.tw/posts/puma-zero-downtime" rel="nofollow" target="_blank" title=""&gt;puma 关于 systemd 的 0down 方案&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="puma 的重启种类"&gt;puma 的重启种类&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Normal Restart&lt;/li&gt;
&lt;li&gt;Hot Restart&lt;/li&gt;
&lt;li&gt;Phased-Restart to rescue&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="Normal Restart"&gt;Normal Restart&lt;/h2&gt;
&lt;p&gt;就是完全正常的重启&lt;/p&gt;
&lt;h3 id="优势"&gt;优势&lt;/h3&gt;
&lt;p&gt;完全的生命周期，跟 stop 之后 start 一样，所有的东西都会重新加载&lt;/p&gt;
&lt;h3 id="劣势"&gt;劣势&lt;/h3&gt;
&lt;p&gt;服务中断，会有 503 Service Unavailable 的情况&lt;/p&gt;
&lt;h2 id="Hot Restart"&gt;Hot Restart&lt;/h2&gt;
&lt;p&gt;通过额外的 Server socket，确保 puma 在重启的过程中，可以持续的接受请求，并等待处理，puma 重启成功之后，将 socket 堆积的请求再发送出去&lt;/p&gt;
&lt;h3 id="优势"&gt;优势&lt;/h3&gt;
&lt;p&gt;不会终止服务，请求连接也会得到保留。cluster 模式 和 simple 模式均可以使用，个人认为比较简单&lt;/p&gt;

&lt;p&gt;使用 preload_app！可以加快启动速度&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;啥是 preload_app!
在 cluster 模式下
preload_app! 实现 将 master 进程的所有代码 在 fork 之前都 load 起来，这样就可以使用操作系统的  copy-on-write 技术，有效的降低内存使用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="劣势"&gt;劣势&lt;/h3&gt;
&lt;p&gt;在重启期间，进来的连接处于挂起状态 hanging, 如果时间过长，则上层如 nginx 会来个 502&lt;/p&gt;
&lt;h3 id="启动方式"&gt;启动方式&lt;/h3&gt;
&lt;p&gt;满足任意条件即可启动&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;给 puma 进程发送 &lt;strong&gt;&lt;em&gt;SIGUSR2&lt;/em&gt;&lt;/strong&gt; 信号 即 kill -SIGUSR2 进程号&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;向 puma status/control server 请求 /restart&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;什么是 puma status/control server? 
在 puma 启动的时候 可以带入参数来启动一个控制 url 
类似 puma --control-url tcp://127.0.0.1:9293 --control-token foo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;pumactl restart&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="Phased restart"&gt;Phased restart&lt;/h2&gt;
&lt;p&gt;Phased restart 名为 阶段重启，这种重启方式只适合跑在 cluster 模式下。工作机制是 : 先杀死一个老版本的 worker，再启动一个新的 worker，以此重复，直至所有的 worker 都是新版本，这种方式 master process 即 puma 的控制进程本身，是不会重启的，重启的只有 worker 进程。这种方式不适合配置有更新，以及添加新 Gem 的情况&lt;/p&gt;
&lt;h3 id="优势"&gt;优势&lt;/h3&gt;
&lt;p&gt;在 puma 重启过程中，系统也是可以真正的处理请求，即 既接受了新的请求进来，请求进来后也会有 worker 来处理并返回结果（但是 是新是旧 worker 是随机的）&lt;/p&gt;
&lt;h3 id="劣势"&gt;劣势&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;只支持 puma 的 cluster 模式&lt;/li&gt;
&lt;li&gt;因为 puma 的 master process 没有重启，所以 gem 以及配置文件的更新（puma 的配置文件）是不行的，且 puma 的事件 on_restart  也是不会产生的&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;puma.rb 配置文件，是禁止使用 preload_app! 而需要使用  prune_bundler 的&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;为啥不能用 preload_app!   ？因为主进程不重启，只是单启 worker&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;prune_bundler 代替了 preload, 来进行 gem 的装载&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="启动方式"&gt;启动方式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;kill -SIGUSR1 主进程&lt;/li&gt;
&lt;li&gt;向 puma status/control server 请求 /phased-restart&lt;/li&gt;
&lt;li&gt;pumactl restart&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Thu, 26 Aug 2021 12:42:00 +0800</pubDate>
      <link>https://ruby-china.org/topics/41619</link>
      <guid>https://ruby-china.org/topics/41619</guid>
    </item>
    <item>
      <title>统一渠道通知系统 pushconfig.com， 任意切换发送通知至钉钉， 飞书， 邮件的工具</title>
      <description>&lt;p&gt;这段时间由于种种原因，公司要求 IM 从钉钉 切换到飞书&lt;/p&gt;

&lt;p&gt;感觉飞书文档啥的确实比钉钉好用，但是由于之前开发的系统，大量的嵌入了钉钉机器人，导致迁移起来非常痛苦， &lt;/p&gt;

&lt;p&gt;于是想寻找一下市场上是否有种产品能通用的发送 钉钉 或者 飞书的，之后惊奇的发现居然没有这种产品&lt;/p&gt;

&lt;p&gt;程序员的需求程序员满足， &lt;/p&gt;

&lt;p&gt;我们在 https 证书过期通知 &lt;a href="http://www.sslguala.com" rel="nofollow" target="_blank" title=""&gt;www.sslguala.com&lt;/a&gt; 的基础上，搞出了 通用推送通知系统 pushconfig  &lt;a href="http://www.pushconfig.com" rel="nofollow" target="_blank" title=""&gt;www.pushconfig.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://ohiofile.ce04.com/activestoragetest/4vjmimmwhp9k1kmc6pr638vpmgrn" title="" alt="pushconfig 作用"&gt;&lt;/p&gt;

&lt;p&gt;开发人员可以用统一的地址，统一的格式，将信息推送到 pushconfig,  之后可以在 pushconfig 上绑定各种通知渠道&lt;/p&gt;

&lt;p&gt;下图是 pushconfig.com 的工作流程&lt;/p&gt;

&lt;p&gt;&lt;img src="https://ohiofile.ce04.com/activestoragetest/ua25r91c1c1wnyig2km22sn6f7a4" title="" alt="pushconfig.com 工作流程"&gt;&lt;/p&gt;
&lt;h2 id="产品特点"&gt;产品特点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;统一的发送地址，统一的参数格式&lt;/li&gt;
&lt;li&gt;支持国内常见 通知渠道：钉钉 飞书 企业微信 bark email webhook（其实就是 sslguala.com 支持的通知渠道）&lt;/li&gt;
&lt;li&gt;可以先发送内容，再决定是否转发到某个接收频道&lt;/li&gt;
&lt;li&gt;支持一条内容发送多个接收频道&lt;/li&gt;
&lt;li&gt;若想增强转发的内容，可以修改官方的转发模板（模板采用 liquid 语法）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="具体说明"&gt;具体说明&lt;/h2&gt;
&lt;p&gt;可以参见 &lt;a href="https://jsr0zkjnvx.feishu.cn/docs/doccnzJhEA1aLzdgfe1vTm0000d#" rel="nofollow" target="_blank" title=""&gt;www.pushconfig.com 使用说明&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="技术栈"&gt;技术栈&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;前端 vue3.2, element plus,  tailwindcss2&lt;/li&gt;
&lt;li&gt;后端 rails 6.1 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;公司已同意日后修改掉部分公司专用服务器代码后开源&lt;/p&gt;
&lt;h2 id="公司其他常用工具类产品"&gt;公司其他常用工具类产品&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.admqr.com" rel="nofollow" target="_blank" title=""&gt;www.admqr.com 毛驴短链&lt;/a&gt; ，用来快速生成带统计功能的短连接地址&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.sslguala.com" rel="nofollow" target="_blank" title=""&gt;www.sslguala.com ssl 挂啦&lt;/a&gt;, 用来批量监控网站 https 证书是否快过期，过期之前发推送通知提醒更换&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;欢迎大家试用，有问题或建议欢迎评论交流&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Wed, 25 Aug 2021 15:15:13 +0800</pubDate>
      <link>https://ruby-china.org/topics/41616</link>
      <guid>https://ruby-china.org/topics/41616</guid>
    </item>
    <item>
      <title>妈妈再也不用担心我忘记更新 SSL 证书了</title>
      <description>&lt;p&gt;撸了个自动检测证书快过期报警的网站 ssl 挂啦？&lt;a href="https://www.sslguala.com" rel="nofollow" target="_blank"&gt;https://www.sslguala.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;虽然大多数时候合格的韵味都能保证自动更新 ssl 证书，但是还是架不住域名多的时候有时候忘记配置了，或者有时候因为用了第三方服务，没办法接入自动更新 ssl. 所以还是有检测公司众多 https 地址的需求&lt;/p&gt;

&lt;p&gt;当然检测本身很简单，写脚本实现也很容易，但是每次配置各种通知机器人特别是加签，以及不停的添加新的地址，实在是繁琐。&lt;/p&gt;

&lt;p&gt;所以趁着开发压力不大，跟同事抽空一起撸了这个 自动检测 域名的 ssl 是否过期的网站&lt;/p&gt;

&lt;p&gt;ssl 证书挂啦 域名也很好记，全拼音... &lt;a href="https://www.sslguala.com" rel="nofollow" target="_blank"&gt;https://www.sslguala.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;系统 4 小时自动检测一次 ssl 证书，在 14, 7, 3, 1, 0 天的时候发送警报到通知通道&lt;/p&gt;

&lt;p&gt;不需要域名的控制权 直接添加 domain 地址即可&lt;/p&gt;

&lt;p&gt;通知通道 支持主流 IM 机器人，包括：钉钉 飞书 企业微信 支持 email 提醒&lt;/p&gt;

&lt;p&gt;使用的技术栈是 vue3 + rails&lt;/p&gt;

&lt;p&gt;欢迎有需要的朋友试用&lt;/p&gt;</description>
      <author>jicheng1014</author>
      <pubDate>Sat, 26 Jun 2021 11:36:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/41413</link>
      <guid>https://ruby-china.org/topics/41413</guid>
    </item>
  </channel>
</rss>
