<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://isay.me/feed.xml" rel="self" type="application/atom+xml" /><link href="https://isay.me/" rel="alternate" type="text/html" /><updated>2026-02-26T10:08:08+08:00</updated><id>https://isay.me/feed.xml</id><title type="html">自说Me话</title><subtitle>ik0r&apos;s Individual Blog. 自说Me话,一个前端开发新手的网络记录,同喜欢网络,Linux,前端开发,爱折腾的人共同分享,记录折腾心得还有学习过程.</subtitle><entry><title type="html">PVE HBA 卡直通</title><link href="https://isay.me/2024/05/pve-hba-card-passthrough.html" rel="alternate" type="text/html" title="PVE HBA 卡直通" /><published>2024-05-12T00:00:00+08:00</published><updated>2024-05-12T00:00:00+08:00</updated><id>https://isay.me/2024/05/pve-hba-card-passthrough</id><content type="html" xml:base="https://isay.me/2024/05/pve-hba-card-passthrough.html"><![CDATA[<p>最近购入了一张 HBA 卡，计划将 HBA 卡和外置的硬盘笼进行连接，然后将 HBA 卡直通给黑群晖，由群晖管理所有的硬盘。在配置直通的过程中遇到了一些问题，所以在这里记录下。</p>

<p>在配置直通前，需要确认 HBA 卡已经正常加载了。在 pve shell 中通过 <code class="language-plaintext highlighter-rouge">lspci</code> 命令列出所有的设备。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
02:00.0 Serial Attached SCSI controller: Broadcom / LSI SAS2308 PCI-Express Fusion-MPT SAS-2 (rev 05)
...
</code></pre></div></div>
<p>在上面的输出中，可以看到 HBA 卡正常加载了。</p>

<p>接下来是开启 iommu，开启 iommu 的配置之前写过一篇直通 sata 控制器的总结，步骤是一样的。可以参考 <a href="/2023/05/pve-enable-iommu-and-sata-controller-passthrough.html">PVE 启用 IOMMU 功能为虚拟机开启 sata 控制器直通</a></p>

<p>开启 iommu 后，找到要直通的虚拟机，就可以在虚拟机的硬件配置中添加 pci 设备了。</p>

<p><img src="/uploads/2024/05/2024051201-01.png" alt="" /></p>

<p><img src="/uploads/2024/05/2024051201-02.png" alt="" /></p>

<p>此时需要确认下， HBA 卡对应的 iommu 组号有没有和别的设备重复。如果 HBA 卡的 iommu 组号没有重复，此时添加完 pci 设备后， 重启虚拟机应该就完成 HBA 的直通了。</p>

<p>在我的配置过程中，HBA 卡的 iommu 组号是和别的设备有重复的。如果直接添加 pci 设备重启虚拟机的话，会导致虚拟机卡住启动失败。所以还需要再进行一些额外的配置，参考了 pve 官方文档 <a href="https://pve.proxmox.com/wiki/PCI_Passthrough#Verifying_IOMMU_parameters">PCI Passthrough</a> 成功解决。</p>

<p>第一步，按照 pve 文档描述，需要确认 <code class="language-plaintext highlighter-rouge">IOMMU interrupt remapping</code> 功能是否支持。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>dmesg | <span class="nb">grep</span> <span class="s1">'remapping'</span>
<span class="o">[</span>    0.106362] DMAR-IR: Queued invalidation will be enabled to support x2apic and Intr-remapping.
<span class="o">[</span>    0.106821] DMAR-IR: Enabled IRQ remapping <span class="k">in </span>x2apic mode
</code></pre></div></div>

<p>从上面输出，可以看到 <code class="language-plaintext highlighter-rouge">DMAR-IR: Enabled IRQ remapping in x2apic mode</code> 代表是支持的。如果是 amd CPU, 输出应该是 <code class="language-plaintext highlighter-rouge">AMD-Vi: Interrupt remapping enabled</code>.</p>

<p>下一步是确认主板支持 <code class="language-plaintext highlighter-rouge">ACS(Access Control Services)</code>, 不一样的主板好像选项所在位置不一样，经过确认我的主板是开启了的。</p>

<p>下一步是添加内核启动参数，修改 <code class="language-plaintext highlighter-rouge">/etc/default/grub</code> 文件，在 <code class="language-plaintext highlighter-rouge">GRUB_CMDLINE_LINUX_DEFAULT</code> 参数最后追加 <code class="language-plaintext highlighter-rouge">pcie_acs_override=downstream</code>. 我的参数配置完成后是这样的</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt pcie_acs_override=downstream"
</code></pre></div></div>

<p>上面配置中的 <code class="language-plaintext highlighter-rouge">iommu=pt</code> 看网上的解释是说可以提高未直通设备PCIe的性能，所以先加上了，但是不是必须的。</p>

<p>下一步是更新 grub</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>update-grub
</code></pre></div></div>

<p>grub 更新完成后，重启 pve 系统，此时再到虚拟机的硬件配置中添加 pci 设备，不同设备的 iommu 组号应该就不一样了。 此时将 HBA 卡添加后再启动虚拟机，正常情况下就可以启动成功，并且 HBA 卡直通成功了。</p>]]></content><author><name></name></author><category term="PVE" /><category term="HBA" /><summary type="html"><![CDATA[最近购入了一张 HBA 卡，计划将 HBA 卡和外置的硬盘笼进行连接，然后将 HBA 卡直通给黑群晖，由群晖管理所有的硬盘。在配置直通的过程中遇到了一些问题，所以在这里记录下。]]></summary></entry><entry><title type="html">使用 traefik 作为反向代理工具</title><link href="https://isay.me/2024/04/traefik-as-reverse-proxy.html" rel="alternate" type="text/html" title="使用 traefik 作为反向代理工具" /><published>2024-04-30T00:00:00+08:00</published><updated>2024-04-30T00:00:00+08:00</updated><id>https://isay.me/2024/04/traefik-as-reverse-proxy</id><content type="html" xml:base="https://isay.me/2024/04/traefik-as-reverse-proxy.html"><![CDATA[<p>很久之前写过一篇使用 traefik 的文章 <a href="/2023/05/traefik-as-reverse-proxy.html">traefik 作为反向代理</a>. 但是现在回过头来看, 发现当时由于刚开始使用 traefik 一些理解其实是有问题的, 而且也走了一些弯路. 正值 traefik 正式发布 3.0 版本之际, 所以这次重新写这篇文章, 也是对 traefik 的使用作一些总结.</p>

<p>在写具体的使用之前, 先说下从开始使用 traefik 到现在的一些感受.</p>

<p>最大的一个感受是, 如果服务是通过 docker 部署的, 使用 traefik 配合 docker 可以极大减少反向代理配置的维护成本. 体现在两个地方, ① docker 容器的 IP traefik 可以自动感知, 所以也就不需要启动容器时指定固定IP, 让 docker 随机分配就好, 也不需要在转发规则中指定某个域名指向具体的 IP, traefik 会统一维护好. ② traefik 通过读取 docker 容器的 label 确定转发规则, 所以可以将转发配置和容器本身的配置集中在一起, 比如可以放在 <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> 中. 而像 nginx 这样的工具, 容器服务的配置是在容器侧. 转发规则的配置是在 nginx 侧, 时间久了就不容易维护了.</p>

<p>说一下我使用 traefik 的场景, 我使用 docker 部署 traefik, 同时会在 docker 中部署一些其它的服务, 比如 vaultwarden 等. 用 docker 部署的服务会通过 label 的方式配置转发规则. 除了 docker 配置的服务外, 我还会在 traefik 中配置一些静态的转发规则, 转发到一些非 docker 部署的服务上, 比如 pve 管理端, openwrt 等.</p>

<p>traefik 启动时的配置文件分为静态配置和动态配置.</p>
<ul>
  <li>静态配置是配置监听端口、动态配置目录、docker 容器和网络等全局性的配置, 可以写在配置文件中, 也可以写在 traefik 启动时的参数中.</li>
  <li>动态配置基本上就是转发规则相关的配置, 可以写在配置文件中, 也可以写在 docker label 中.</li>
</ul>

<p>所以我把静态配置文件写在静态配置文件中, docker 容器的配置写在 label 中, 非 docker 容器的配置写在动态配置文件中.</p>

<p>静态配置内容如下. 如果想看每个配置代表的含义, 可以参考 traefik 配置文件的模板 <a href="https://github.com/traefik/traefik/blob/master/traefik.sample.toml"><code class="language-plaintext highlighter-rouge">traefik.sample.toml</code>
</a>. 在下面的配置中, 我只开启了 https 端口, 所有 http 转发都启用了 <code class="language-plaintext highlighter-rouge">compress</code> middleware, 这个 middleware 会对所有响应的数据进行压缩, 如果响应的数据比较大, 可以提高响应速度.</p>

<blockquote>
  <p>下面的配置文件中, 有一些关于 entryPoint, router, service, middleware 等的概念, 这些概念的含义可以参考 traefik 的官方文档 <a href="https://doc.traefik.io/traefik/getting-started/concepts/">Concepts</a></p>
</blockquote>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">[</span><span class="n">entryPoints</span><span class="k">]</span>
  <span class="k">[</span><span class="n">entryPoints</span><span class="k">.</span><span class="n">websecure</span><span class="k">]</span> <span class="c"># websecure 是这个 entryPoint 的名字</span>
    <span class="n">address</span> <span class="o">=</span><span class="w"> </span><span class="s">":443"</span> <span class="c"># websecure 这个 entryPoint 监听的端口</span>
    <span class="k">[</span><span class="n">entryPoints</span><span class="k">.</span><span class="n">websecure</span><span class="k">.</span><span class="n">http</span><span class="k">]</span>
      <span class="n">middlewares</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">"shared-compress@file"</span><span class="p">]</span> <span class="c"># 所有请求都使用 shared-compress middleware</span>
      <span class="n">tls</span> <span class="o">=</span><span class="w"> </span><span class="kc">true</span> <span class="c"># websecure 这个 entryPoint 开启 https</span>
  <span class="c"># 如果同时监听 http 的话, 可以再定义一个 web entryPoint</span>
  <span class="k">[</span><span class="n">entryPoints</span><span class="k">.</span><span class="n">web</span><span class="k">]</span>
    <span class="n">address</span> <span class="o">=</span><span class="w"> </span><span class="s">":80"</span>
    <span class="k">[</span><span class="n">entryPoints</span><span class="k">.</span><span class="n">websecure</span><span class="k">.</span><span class="n">http</span><span class="k">]</span>
      <span class="n">middlewares</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">"shared-compress@file"</span><span class="p">]</span> <span class="c"># 所有请求都使用 shared-compress middleware</span>
<span class="k">[</span><span class="n">log</span><span class="k">]</span> <span class="c"># 开启日志</span>
<span class="k">[</span><span class="n">api</span><span class="k">]</span> <span class="c"># 开启 traefik dashboard</span>
<span class="k">[</span><span class="n">ping</span><span class="k">]</span> <span class="c"># 开启 ping 接口</span>
<span class="k">[</span><span class="n">providers</span><span class="k">.</span><span class="n">docker</span><span class="k">]</span> <span class="c"># 监听 docker 容器</span>
  <span class="c"># 默认不转发, 只有通过label `traefik.enable=true` 时才转发</span>
  <span class="n">exposedByDefault</span> <span class="o">=</span><span class="w"> </span><span class="kc">false</span>
  <span class="n">network</span> <span class="o">=</span><span class="w"> </span><span class="s">"traefik"</span>
<span class="k">[</span><span class="n">providers</span><span class="k">.</span><span class="n">file</span><span class="k">]</span> <span class="c"># 监听动态配置文件</span>
  <span class="c"># 动态配置文件目录</span>
  <span class="n">directory</span> <span class="o">=</span><span class="w"> </span><span class="s">"/etc/traefik/dynamic"</span>
  <span class="c"># 动态配置文件目录变化时, 自动更新 traefik 配置</span>
  <span class="n">watch</span> <span class="o">=</span><span class="w"> </span><span class="kc">true</span>
<span class="k">[</span><span class="n">serversTransport</span><span class="k">]</span>
  <span class="c"># 内部转发时, 跳过证书验证</span>
  <span class="c"># 因为像 PVE 这种服务只支持 https 访问且使用是自签证书, 如果验证证书的话, 会导致转发失败.</span>
  <span class="n">insecureSkipVerify</span> <span class="o">=</span><span class="w"> </span><span class="kc">true</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">shared-compress</code> middleware 配置如下</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">[</span><span class="n">http</span><span class="k">.</span><span class="n">middlewares</span><span class="k">.</span><span class="n">shared-compress</span><span class="k">]</span>
  <span class="n">compress</span> <span class="o">=</span><span class="w"> </span><span class="kc">true</span>
</code></pre></div></div>

<p>traefik 本身服务的 docker-compose 配置及注释如下</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3'</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">traefik</span><span class="pi">:</span>
    <span class="c1"># The official v2 Traefik docker image</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">traefik:2.11.2</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">traefik</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">443:443"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">80:80"</span> <span class="c1"># 如果对外暴露多个端口的话, 这里可以定义多个端口. 冒号前是 host 端口, 冒号后是容器端口</span>
    <span class="na">healthcheck</span><span class="pi">:</span>
      <span class="na">test</span><span class="pi">:</span> <span class="s">traefik healthcheck --ping</span>
      <span class="na">interval</span><span class="pi">:</span> <span class="s">3s</span>
      <span class="na">retries</span><span class="pi">:</span> <span class="m">10</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="c1"># So that Traefik can listen to the Docker events</span>
      <span class="pi">-</span> <span class="s">/var/run/docker.sock:/var/run/docker.sock:ro</span>
      <span class="c1"># 静态配置映射</span>
      <span class="pi">-</span> <span class="s">/path/to/static.toml:/etc/traefik/traefik.toml:ro</span>
      <span class="c1"># 动态配置映射</span>
      <span class="pi">-</span> <span class="s">/path/to/dynamic:/etc/traefik/dynamic:ro</span>
      <span class="c1"># https 证书目录映射</span>
      <span class="pi">-</span> <span class="s">/path/to/certs:/etc/traefik/certs:ro</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">traefik</span> <span class="c1"># 加入已经提前创建好的 traefik 网络</span>
    <span class="na">labels</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">traefik.enable=true</span>
      <span class="c1"># 新增了一个 router, 名字叫 dashboard, 使用 websecure entryPoint</span>
      <span class="pi">-</span> <span class="s">traefik.http.routers.dashboard.entrypoints=websecure</span>
      <span class="c1"># 转发规则, 匹配域名 traefik.example.com</span>
      <span class="pi">-</span> <span class="s">traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)</span>
      <span class="c1"># 转发到 traefik dashboard 服务</span>
      <span class="pi">-</span> <span class="s">traefik.http.routers.dashboard.service=api@internal</span>
<span class="na">networks</span><span class="pi">:</span>
  <span class="na">traefik</span><span class="pi">:</span>
    <span class="na">external</span><span class="pi">:</span> <span class="kc">true</span> <span class="c1"># 这里声明 traefik 是外部已经已经创建好的网络</span>
</code></pre></div></div>

<p>开启了 https, 那么证书应该怎么配置呢? 我是使用的 acme.sh 生成证书, 然后放在 traefik 中使用. 首先通过上面的目录映射, 将证书目录映射到 traefik 容器中, 然后在动态配置目录下增加对应的配置</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">[[</span><span class="n">tls</span><span class="k">.</span><span class="n">certificates</span><span class="k">]]</span>
  <span class="n">certFile</span> <span class="o">=</span><span class="w"> </span><span class="s">"/etc/traefik/certs/foo.example.com/foo.example.com.cer"</span>
  <span class="n">keyFile</span> <span class="o">=</span><span class="w"> </span><span class="s">"/etc/traefik/certs/foo.example.com/foo.example.com.key"</span>

<span class="c"># 如果有多个配置, 可以继续增加</span>
<span class="k">[[</span><span class="n">tls</span><span class="k">.</span><span class="n">certificates</span><span class="k">]]</span>
  <span class="n">certFile</span> <span class="o">=</span><span class="w"> </span><span class="s">"/etc/traefik/certs/bar.example.com/bar.example.com.cer"</span>
  <span class="n">keyFile</span> <span class="o">=</span><span class="w"> </span><span class="s">"/etc/traefik/certs/bar.example.com/bar.example.com.key"</span>
</code></pre></div></div>

<p>traefik 本身的配置完了, 下面分别讲下 docker 容器动态配置, 以及非 docker 容器的静态配置.</p>

<p>docker 容器动态配置以 whoami 为例子</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3'</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">whoami</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">traefik/whoami</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">whoami</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">traefik</span> <span class="c1"># 使用 traefik 网络, 可以让 traefik 在容器网络内部进行转发</span>
    <span class="na">labels</span><span class="pi">:</span>
      <span class="c1"># 启用 traefik 转发</span>
      <span class="pi">-</span> <span class="s">traefik.enable=true</span>
      <span class="c1"># 转发使用在上面静态配置中定义好的 websecure entryPoint, 也就是 443 端口</span>
      <span class="pi">-</span> <span class="s">traefik.http.routers.whoami.entrypoints=websecure</span>
      <span class="c1"># 转发规则, 匹配域名 whoami.example.com</span>
      <span class="pi">-</span> <span class="s">traefik.http.routers.whoami.rule=Host(`whoami.example.com`)</span>
      <span class="c1"># 转发到目标服务的端口, 如果 docker 容器 expose 了1个端口, 大多数情况是不需要这个配置的</span>
      <span class="c1"># 如果 expose 了多个端口, 可以显式告诉 traefik 转发到哪个端口上</span>
      <span class="pi">-</span> <span class="s">traefik.http.services.whoami.loadbalancer.server.port=80</span>
<span class="na">networks</span><span class="pi">:</span>
  <span class="na">traefik</span><span class="pi">:</span>
    <span class="na">external</span><span class="pi">:</span> <span class="kc">true</span> <span class="c1"># 这里声明 traefik 是外部已经已经创建好的网络</span>
</code></pre></div></div>

<p>非 docker 容器的静态配置, 以 pve 管理端为例子</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">[</span><span class="n">http</span><span class="k">.</span><span class="n">routers</span><span class="k">.</span><span class="n">pve</span><span class="k">]</span> <span class="c"># 定义这个服务的名字</span>
  <span class="n">entryPoints</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">"websecure"</span><span class="p">]</span> <span class="c"># 定义使用的 entryPoint, 如果要同时监听 http 和 https, 可以在这里定义多个 entryPoint</span>
  <span class="n">rule</span> <span class="o">=</span><span class="w"> </span><span class="s">"Host(`pve.example.com`)"</span> <span class="c"># 转发规则, 匹配域名 pve.example.com</span>
  <span class="n">service</span> <span class="o">=</span><span class="w"> </span><span class="s">"pve@file"</span> <span class="c"># 转发到 `pve@file` 服务上, 这个服务在下面的动态配置文件中定义. 这里的 `@file` `@docker` 是区分服务注册源是什么</span>

<span class="c"># 定义 pve@file 服务, 名字不一定和上面的 routers 里的名字一样, 但是要和上面指向的 service 的名字一样</span>
<span class="k">[[</span><span class="n">http</span><span class="k">.</span><span class="n">services</span><span class="k">.</span><span class="n">pve</span><span class="k">.</span><span class="n">loadBalancer</span><span class="k">.</span><span class="n">servers</span><span class="k">]]</span> <span class="c"># 定义 pve@file 这个服务的 ip 地址</span>
  <span class="n">url</span> <span class="o">=</span><span class="w"> </span><span class="s">"https://192.168.100.11:8006"</span>
<span class="k">[[</span><span class="n">http</span><span class="k">.</span><span class="n">services</span><span class="k">.</span><span class="n">pve</span><span class="k">.</span><span class="n">loadBalancer</span><span class="k">.</span><span class="n">servers</span><span class="k">]]</span> <span class="c"># 定义 pve@file 这个服务的 ip 地址</span>
  <span class="n">url</span> <span class="o">=</span><span class="w"> </span><span class="s">"https://192.168.100.10:8006"</span> <span class="c"># 如果有多个 ip 地址, 可以在这里定义多个, traefik 会负责做负载均衡</span>
</code></pre></div></div>

<p>最后, 总结一下, 使用 traefik 作为反向代理工具, 可以极大减少反向代理配置的维护成本, 并且可以将转发规则集中在一起, 方便管理. 如果你之前使用的是 nginx, nginx proxy manager 之类的工具, 可以参考这篇文章, 看看如何使用 traefik 减少维护成本.</p>]]></content><author><name></name></author><category term="traefik" /><summary type="html"><![CDATA[很久之前写过一篇使用 traefik 的文章 traefik 作为反向代理. 但是现在回过头来看, 发现当时由于刚开始使用 traefik 一些理解其实是有问题的, 而且也走了一些弯路. 正值 traefik 正式发布 3.0 版本之际, 所以这次重新写这篇文章, 也是对 traefik 的使用作一些总结.]]></summary></entry><entry><title type="html">PVE LXC 非特权容器挂载宿主 cifs 目录权限 nobody 问题解决方案</title><link href="https://isay.me/2024/04/unprevileged-lxc-mount-host-cifs-nobody.html" rel="alternate" type="text/html" title="PVE LXC 非特权容器挂载宿主 cifs 目录权限 nobody 问题解决方案" /><published>2024-04-23T00:00:00+08:00</published><updated>2024-04-23T00:00:00+08:00</updated><id>https://isay.me/2024/04/unprevileged-lxc-mount-host-cifs-nobody</id><content type="html" xml:base="https://isay.me/2024/04/unprevileged-lxc-mount-host-cifs-nobody.html"><![CDATA[<p>之前写过一篇文章 <a href="/2024/04/lxc-mount-host-dir.html">PVE lxc 容器挂载宿主目录</a> 介绍如何在 lxc 容器中挂载宿主目录.</p>

<p>在安装 jellyfin 时需要将宿主机中的 cifs 目录挂载到 lxc 容器中, 但是在 lxc 容器中通过 <code class="language-plaintext highlighter-rouge">ls -al</code> 命令查看挂载的目录, 可以发现权限属于 <code class="language-plaintext highlighter-rouge">nobody:nogroup</code>, 所以在 lxc 容器中是无法对挂载的目录中的文件进行修改的.</p>

<p>那么出现这种情况的原因是什么呢. lxc 非特权容器使用了新内核特性 user namespaces, 所有的容器内部 UID（用户 ID）和 GID（组 ID）都被映射到了宿主机上不同的ID，通常 root（UID 0）变成了 100000，1 变成了 100001, 依此类推. 挂载的目录在宿主机中的权限为 <code class="language-plaintext highlighter-rouge">0:0</code>, 而 lxc 容器在宿主中的权限实际为 <code class="language-plaintext highlighter-rouge">100000:100000</code>, 当然 lxc 容器就没有权限对目录中的内容进行修改了.</p>

<p>下面介绍如何配置能在 lxc 容器中对挂载的目录进行修改, 参考了 PVE 官方文档 <a href="https://pve.proxmox.com/wiki/Unprivileged_LXC_containers">Unprivileged LXC containers</a> 以及博客文章 <a href="http://www.icharm.me/unprivileged-lxc/index.zh-cn.html">LXC非特权容器绑定宿主机目录nobody</a>, 针对我的使用场景进行了一些细微的修改.</p>

<p><strong>首先</strong> 在宿主机中创建用户和用户组 <code class="language-plaintext highlighter-rouge">lxc_root</code>, 方便后面使用. 由于在宿主机中新建用户默认是从 1000 开始. 为了防止和普通用户冲突我这里设置 <code class="language-plaintext highlighter-rouge">lxc_root</code> 使用的 uid 和 gid 都是 2000.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>groupadd <span class="nt">-u</span> 2000 lxc_root
useradd lxc_root <span class="nt">-u</span> 2000 <span class="nt">-g</span> 2000
</code></pre></div></div>

<p><strong>然后</strong> 映射宿主到容器的 uid, 编辑 <code class="language-plaintext highlighter-rouge">/etc/pve/lxc/ID.conf</code>, 添加如下内容</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># uid map: from uid 0 map 2000 uids (in the ct) to the range starting 100000 (on the host), so 0..2000 (ct) → 100000..102000 (host)
lxc.idmap = u 0 100000 2000
lxc.idmap = g 0 100000 2000
# we map 1 uid starting from uid 2000 onto 2000, so 2000 → 2000
lxc.idmap = u 2000 2000 1
lxc.idmap = g 2000 2000 1
# we map the rest of 65535 from 2001 upto 102001, so 2001..65535 → 102001..165535
lxc.idmap = u 2001 102001 63535
lxc.idmap = g 2001 102001 63535
</code></pre></div></div>

<p><strong>然后</strong>更新宿主机的配置文件 <code class="language-plaintext highlighter-rouge">/etc/subuid</code> 和 <code class="language-plaintext highlighter-rouge">/etc/subgid</code></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s1">'root:2000:1'</span> <span class="o">&gt;&gt;</span> /etc/subuid
<span class="nb">echo</span> <span class="s1">'root:2000:1'</span> <span class="o">&gt;&gt;</span> /etc/subgid
</code></pre></div></div>

<p>uid 和 gid 的映射完成后, <strong>然后</strong> 在宿主机中修改挂载 cifs 时的 uid 和 gid</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 设置 cifs 挂载时的uid和gid</span>
pvesm <span class="nb">set</span> &lt;storagename&gt; <span class="nt">-options</span> <span class="nv">uid</span><span class="o">=</span>2000,gid<span class="o">=</span>2000
</code></pre></div></div>

<p>此时, 查看宿主机 <code class="language-plaintext highlighter-rouge">/etc/pve/storage.cfg</code> 文件可以看到 options 已经被加入进去了</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> /etc/pve/storage.cfg

cifs: resources0
        path /mnt/pve/resources0
        server 192.168.100.21
        share resources0
        content iso
        options <span class="nv">uid</span><span class="o">=</span>2000,gid<span class="o">=</span>2000
        username user
</code></pre></div></div>

<p><strong>接下来</strong>是重新挂载目录, 应用新的 options</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 停止当前的 storage</span>
pvesm <span class="nb">set</span> &lt;storage&gt; <span class="nt">-disable</span> 1

<span class="c"># 使用 umount 卸载目录挂载</span>
umount /path/to/storage

<span class="c"># 最后重新挂载目录</span>
pvesm <span class="nb">set</span> &lt;storage&gt; <span class="nt">-disable</span> 0
</code></pre></div></div>

<p>操作完成后, 可以通过 <code class="language-plaintext highlighter-rouge">mount</code> 命令进行确认新的挂载是否使用了 options <code class="language-plaintext highlighter-rouge">uid=2000,gid=2000</code></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>mount
//192.168.100.21/resources0 on /mnt/pve/resources0 <span class="nb">type </span>cifs <span class="o">(</span>rw,relatime,vers<span class="o">=</span>3.1.1,cache<span class="o">=</span>strict,username<span class="o">=</span>user,uid<span class="o">=</span>2000,noforceuid,gid<span class="o">=</span>2000,noforcegid,addr<span class="o">=</span>192.168.100.21,file_mode<span class="o">=</span>0755,dir_mode<span class="o">=</span>0755,soft,nounix,serverino,mapposix,rsize<span class="o">=</span>4194304,wsize<span class="o">=</span>4194304,bsize<span class="o">=</span>1048576,echo_interval<span class="o">=</span>60,actimeo<span class="o">=</span>1,closetimeo<span class="o">=</span>1<span class="o">)</span>
</code></pre></div></div>

<p>在上面的输出中, 可以看到挂载 options 中有 <code class="language-plaintext highlighter-rouge">uid=2000,gid=2000</code>, 说明配置的 storage 挂载选项正确生效了. 也可以通过以下命令对挂载的目录进行确认.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> <span class="nt">-al</span> /mnt/pve
drwxr-xr-x 8 root     root     4096 Apr 23 08:05 <span class="nb">.</span>
drwxr-xr-x 3 root     root     4096 Mar 27 22:54 ..
drwxr-xr-x 2 lxc_root lxc_root    0 Apr 23 21:51 resources0
</code></pre></div></div>

<p>可以从上面的输出中看到, <code class="language-plaintext highlighter-rouge">resources0</code> 目录权限属于 <code class="language-plaintext highlighter-rouge">lxc_root:lxc_root</code> 了.</p>

<p><strong>然后</strong>把 lxc 容器重启. 如果一切正常的话, 在 lxc 容器中查看被挂载的目录权限应该是 <code class="language-plaintext highlighter-rouge">2000:2000</code>, 修改文件也可以成功了.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> <span class="nt">-al</span> /media
root@DeamonLocal:~# <span class="nb">ls</span> <span class="nt">-al</span> /media/
total 8
drwxr-xr-x  4 root root 4096 Apr 22 14:13 <span class="nb">.</span>
drwxr-xr-x 19 root root 4096 Apr 23 15:00 ..
drwxr-xr-x  2 2000 2000    0 Apr 23 13:51 resources0
</code></pre></div></div>

<p><strong>最后</strong>想说的是, 如果要挂载的目录不是 cifs 目录的话, 就不需要像上面这么麻烦, 修改权限直接使用 <code class="language-plaintext highlighter-rouge">chown -R lxc_root:lxc_root /path/to/storage</code> 就行了.</p>]]></content><author><name></name></author><category term="PVE" /><category term="lxc" /><summary type="html"><![CDATA[之前写过一篇文章 PVE lxc 容器挂载宿主目录 介绍如何在 lxc 容器中挂载宿主目录.]]></summary></entry><entry><title type="html">为 PVE lxc 中的 docker 容器添加 nvidia GPU 支持</title><link href="https://isay.me/2024/04/pve-lxc-docker-nvidia-gpu.html" rel="alternate" type="text/html" title="为 PVE lxc 中的 docker 容器添加 nvidia GPU 支持" /><published>2024-04-22T00:00:00+08:00</published><updated>2024-04-22T00:00:00+08:00</updated><id>https://isay.me/2024/04/pve-lxc-docker-nvidia-gpu</id><content type="html" xml:base="https://isay.me/2024/04/pve-lxc-docker-nvidia-gpu.html"><![CDATA[<p>本篇介绍如何在 lxc 容器中的 docker 容器中能够使用 nvidia GPU. 如果可以在 docker 容器中使用 nvidia GPU, 那么就可以在 docker 容器中安装 jellyfin 并启用硬件转码了.</p>

<p>之前写过一篇在 PVE lxc 中直接通过 linux 包管理工具安装 jellyfin 的文章 <a href="/2023/07/pve-lxc-install-jellyfin.html">PVE LXC 安装 Jellyfin</a>, 如果不需要在 docker 中安装 jellyfin 的话可以进行参考.</p>

<p>在 lxc 容器中的 docker 容器中使用 nvidia GPU, 首先需要在 lxc 容器中安装 nvidia 显卡驱动, 之前也写过一篇文章 <a href="/2023/07/pve-lxc-nvidia-gpu-passthrough.html">PVE LXC 容器直通 Nvidia 显卡</a>, 可以进行参考.</p>

<p>下面开始介绍如何为 docker 容器添加 nvidia GPU 支持. 安装过程参考了 nvidia 的官方文档 <a href="https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html#installing-with-apt">https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html#installing-with-apt</a>.</p>

<p><strong>首先</strong>执行以下命令为 apt 添加 <code class="language-plaintext highlighter-rouge">nvidia-container-toolkit</code> 源信息</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-fsSL</span> https://nvidia.github.io/libnvidia-container/gpgkey | gpg <span class="nt">--dearmor</span> <span class="nt">-o</span> /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl <span class="nt">-s</span> <span class="nt">-L</span> https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | <span class="nb">sed</span> <span class="s1">'s#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g'</span> | <span class="nb">tee</span> /etc/apt/sources.list.d/nvidia-container-toolkit.list
</code></pre></div></div>

<p><strong>然后</strong>更新 apt 并安装 <code class="language-plaintext highlighter-rouge">nvidia-container-toolkit</code>. 这里官方文档里只写了安装 <code class="language-plaintext highlighter-rouge">nvidia-container-toolkit</code>, 实测 <code class="language-plaintext highlighter-rouge">nvidia-container-runtime</code> 也是需要安装的.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt update
apt <span class="nb">install </span>nvidia-container-toolkit nvidia-container-runtime
</code></pre></div></div>

<p><strong>然后</strong>是配置 <code class="language-plaintext highlighter-rouge">nvidia-container-runtime</code></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nvidia-ctk runtime configure <span class="nt">--runtime</span><span class="o">=</span>docker
</code></pre></div></div>

<p>运行此命令会在 <code class="language-plaintext highlighter-rouge">/etc/docker/daemon.json</code> 中写入 nvidia runtime 相关信息.</p>

<p><strong>然后</strong>是重启 docker.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl restart docker.service
</code></pre></div></div>

<p><strong>最后</strong>是验证 nvidia GPU 是否正常使用</p>

<p>通过上面一系列操作, 此时 <code class="language-plaintext highlighter-rouge">nvidia-container-toolkit</code> 相关配置已经完成, 可以通过下面的命令, 对 docker 中的 nvidia GPU 进行测试. 官方的测试命令如下</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">--rm</span> <span class="nt">--runtime</span><span class="o">=</span>nvidia <span class="nt">--gpus</span> all ubuntu nvidia-smi
</code></pre></div></div>

<p>由于我是需要在 docker 中安装 jellyfin, 并且 ubuntu 镜像比较大下载时间较长, 所以我直接用了 jellyfin 镜像来测试. 测试方法是先在后台运行一个 jellyfin 容器, 然后通过 <code class="language-plaintext highlighter-rouge">docker exec</code> 连接到 jellyfin 容器并运行 <code class="language-plaintext highlighter-rouge">nvidia-smi</code> 命令, 查看输出结果.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">--rm</span> <span class="nt">--name</span> jellyfin-test <span class="nt">--runtime</span><span class="o">=</span>nvidia <span class="nt">--gpus</span> all <span class="nt">-d</span> jellyfin/jellyfin
docker <span class="nb">exec</span> <span class="nt">-it</span> jellyfin-test nvidia-smi
</code></pre></div></div>

<p>我在运行上面第一条命令时, 遇到了错误, 错误信息如下</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error running hook #0: error running hook: exit status 1, stdout: , stderr: Auto-detected mode as 'legacy'
nvidia-container-cli: mount error: failed to add device rules: unable to find any existing device filters attached to the cgroup: bpf_prog_query(BPF_CGROUP_DEVICE) failed: operation not permitted: unknown
</code></pre></div></div>

<p>通过搜索找到了下面的两篇讨论, 参考了其中的一个方案, 并成功解决. 相关文档</p>

<ul>
  <li><a href="https://forum.proxmox.com/threads/docker-is-unable-to-access-gpu-in-lxc-gpu-passthrough.125066/">https://forum.proxmox.com/threads/docker-is-unable-to-access-gpu-in-lxc-gpu-passthrough.125066/</a></li>
  <li><a href="https://discuss.linuxcontainers.org/t/how-to-build-nvidia-docker-inside-lxd-lxc-container/17582/6">https://discuss.linuxcontainers.org/t/how-to-build-nvidia-docker-inside-lxd-lxc-container/17582/6</a></li>
</ul>

<p>修复方案就是在 lxc 容器下修改 <code class="language-plaintext highlighter-rouge">nvidia-container-runtime</code> 配置文件 <code class="language-plaintext highlighter-rouge">/etc/nvidia-container-runtime/config.toml</code>, 将其中的 <code class="language-plaintext highlighter-rouge"># no-cgroups = false</code> 修改为 <code class="language-plaintext highlighter-rouge">no-cgroups = true</code>.</p>

<p>通过上面的修复后, 重新启动 docker, 并再次测试</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl restart docker.service
docker run <span class="nt">--rm</span> <span class="nt">--name</span> jellyfin-test <span class="nt">--runtime</span><span class="o">=</span>nvidia <span class="nt">--gpus</span> all <span class="nt">-d</span> jellyfin/jellyfin
docker <span class="nb">exec</span> <span class="nt">-it</span> jellyfin-test nvidia-smi
</code></pre></div></div>

<p>如果输出类似下面的内容, 就表示 docker 容器中的 nvidia GPU 可以正常使用了</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+-----------------------------------------------------------------------------+
| NVIDIA-SMI 525.147.05   Driver Version: 525.147.05   CUDA Version: 12.0     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  NVIDIA T400 4GB     Off  | 00000000:01:00.0 Off |                  N/A |
| 38%   32C    P8    N/A /  31W |      1MiB /  4096MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+
</code></pre></div></div>]]></content><author><name></name></author><category term="PVE" /><category term="lxc" /><category term="nvidia" /><category term="docker" /><summary type="html"><![CDATA[本篇介绍如何在 lxc 容器中的 docker 容器中能够使用 nvidia GPU. 如果可以在 docker 容器中使用 nvidia GPU, 那么就可以在 docker 容器中安装 jellyfin 并启用硬件转码了.]]></summary></entry><entry><title type="html">Proxmox VE直通硬盘（全盘映射方式）</title><link href="https://isay.me/2024/04/pve-harddisk-passthrough.html" rel="alternate" type="text/html" title="Proxmox VE直通硬盘（全盘映射方式）" /><published>2024-04-21T00:00:00+08:00</published><updated>2024-04-21T00:00:00+08:00</updated><id>https://isay.me/2024/04/pve-harddisk-passthrough</id><content type="html" xml:base="https://isay.me/2024/04/pve-harddisk-passthrough.html"><![CDATA[<p>使用PVE有时为了方便，需要将硬盘直通，一般有两种方式，一是硬件直通，一是全盘映射，这里介绍第二种，方法如下：</p>

<p><strong>一、打开PVE管理网页Shell</strong></p>

<p>输入</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> /dev/disk/by-id
</code></pre></div></div>

<p>查看存储设备的id</p>

<p><img src="/uploads/2024/04/2024042101-01.png" alt="" /></p>

<p>图上划红线即为硬盘ID号，复制下来</p>

<p><strong>二、硬盘映射</strong></p>

<p><strong>注意：这里需要将100换成虚拟机的真实ID，sata1这里也可以换成未占用的id数（PVE支持satat0-5）</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qm <span class="nb">set </span>100 <span class="nt">-sata1</span> /dev/disk/by-id/ata-WDC_XXXX_XXXX_XXXX
</code></pre></div></div>

<p>如果返回以下信息,说明已成功映射</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>update VM 100: <span class="nt">-sata1</span> /dev/disk/by-id/ata-WDC_XXXX_XXXX_XXXX
</code></pre></div></div>

<p><strong>三、确定是否成功</strong></p>

<p>进入PVE对应虚拟机的硬件页面，查看是否硬盘是否已经在虚拟机里，如图所示说明已成功，这时打开虚拟机就能找到对应硬盘。</p>

<p><img src="/uploads/2024/04/2024042101-02.png" alt="" /></p>]]></content><author><name></name></author><category term="PVE" /><summary type="html"><![CDATA[使用PVE有时为了方便，需要将硬盘直通，一般有两种方式，一是硬件直通，一是全盘映射，这里介绍第二种，方法如下：]]></summary></entry><entry><title type="html">PVE lxc 容器挂载宿主目录</title><link href="https://isay.me/2024/04/lxc-mount-host-dir.html" rel="alternate" type="text/html" title="PVE lxc 容器挂载宿主目录" /><published>2024-04-14T00:00:00+08:00</published><updated>2024-04-14T00:00:00+08:00</updated><id>https://isay.me/2024/04/lxc-mount-host-dir</id><content type="html" xml:base="https://isay.me/2024/04/lxc-mount-host-dir.html"><![CDATA[<p><strong>命令行方式</strong>, 在 PVE shell 中运行以下命令</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pct set PID -mp0 /path/to/host/dir0,mp=/path/to/lxc/dir0
pct set PID -mp1 /path/to/host/dir1,mp=/path/to/lxc/dir1
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">-mp0</code>, <code class="language-plaintext highlighter-rouge">-mp1</code> 指对 lxc 容器设置的第几个文件夹. 从 <code class="language-plaintext highlighter-rouge">mp0</code> 开始, 依次递增.</p>

<p><code class="language-plaintext highlighter-rouge">mp=</code> 后面的路径表示在 lxc 容器中的位置.</p>

<p><strong>直接修改配置文件方式</strong>, 除命令行方式外, 还可以直接修改 lxc 窗口的配置文件</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat 510.conf
arch: amd64
cores: 1
hostname: JellyfinServer
memory: 512
mp0: /mnt/pve/path0/,mp=/mnt/path0
mp1: /mnt/pve/path1/,mp=/mnt/path1
</code></pre></div></div>

<p>返回结果中的 <code class="language-plaintext highlighter-rouge">mp0</code>, <code class="language-plaintext highlighter-rouge">mp1</code> 就是已经配置的内容.</p>]]></content><author><name></name></author><category term="PVE" /><category term="lxc" /><summary type="html"><![CDATA[命令行方式, 在 PVE shell 中运行以下命令]]></summary></entry><entry><title type="html">PVE删除local-lvm，并把空间合并到local</title><link href="https://isay.me/2024/04/pve-remove-local-lvm.html" rel="alternate" type="text/html" title="PVE删除local-lvm，并把空间合并到local" /><published>2024-04-14T00:00:00+08:00</published><updated>2024-04-14T00:00:00+08:00</updated><id>https://isay.me/2024/04/pve-remove-local-lvm</id><content type="html" xml:base="https://isay.me/2024/04/pve-remove-local-lvm.html"><![CDATA[<p>PVE 安装完系统后, 会自动把系统所在硬盘划分为 <code class="language-plaintext highlighter-rouge">local</code> 和 <code class="language-plaintext highlighter-rouge">local-lvm</code> 两个部分, 但是有时候并不需要 <code class="language-plaintext highlighter-rouge">local-lvm</code>, 那么应该如何删掉 <code class="language-plaintext highlighter-rouge">local-lvm</code>, 然后把原来 <code class="language-plaintext highlighter-rouge">local-lvm</code> 占用的空间和 <code class="language-plaintext highlighter-rouge">local</code> 进行合并呢.</p>

<p><strong>提醒</strong>: 由于 <code class="language-plaintext highlighter-rouge">local-lvm</code> 默认是用来存储虚拟机镜像的, 所以如果已经有存在的虚拟机并且存储位置正好是 <code class="language-plaintext highlighter-rouge">local-lvm</code>, 就需要先进行虚拟机备份, 待操作完成后再进行恢复.
因为删掉 <code class="language-plaintext highlighter-rouge">local-lvm</code> 后, 原来的虚拟机都会消失.</p>

<p>下面开始进行操作.</p>

<p><strong>首先</strong>编辑 <code class="language-plaintext highlighter-rouge">local</code>, 将原来 <code class="language-plaintext highlighter-rouge">local-lvm</code> 存储的内容和 <code class="language-plaintext highlighter-rouge">local</code> 进行合并.</p>

<p>可以直接通过 webui 进行操作, 如下图</p>

<p><img src="/uploads/2024/04/2024041402-01.jpg" alt="" /></p>

<p>也可以直接编辑 <code class="language-plaintext highlighter-rouge">/etc/pve/storage.cfg</code> 文件, 将 <code class="language-plaintext highlighter-rouge">local-lvm</code> 部分的 content 内容 <code class="language-plaintext highlighter-rouge">rootdir,images</code> 两项与 <code class="language-plaintext highlighter-rouge">local</code> 的 content 内容进行合并, 然后删掉 <code class="language-plaintext highlighter-rouge">local-lvm</code> 相关配置.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat /etc/pve/storage.cfg
dir: local
        path /var/lib/vz
        content vztmpl,backup,iso,rootdir,images
</code></pre></div></div>

<p><strong>然后</strong>执行如下命令, 删掉 <code class="language-plaintext highlighter-rouge">local-lvm</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lvremove pve/data
</code></pre></div></div>

<p><strong>然后</strong>将剩余空间全部扩充到 local 中</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lvextend -l+100%FREE /dev/mapper/pve-root
resize2fs /dev/mapper/pve-root
</code></pre></div></div>

<p><strong>然后</strong>到数据中心中, 删掉 <code class="language-plaintext highlighter-rouge">local-lvm</code> 所在的目录配置即可.
<img src="/uploads/2024/04/2024041402-02.jpg" alt="" /></p>

<p><strong>最后</strong> 可以通过 <code class="language-plaintext highlighter-rouge">lvs</code> 或者 <code class="language-plaintext highlighter-rouge">df -h</code> 命令确认新的空间分布情况.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ lvs
  LV   VG  Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  root pve -wi-ao---- &lt;25.25g
  swap pve -wi-ao----   3.50g
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ df -h
Filesystem                    Size  Used Avail Use% Mounted on
udev                          7.7G     0  7.7G   0% /dev
tmpfs                         1.6G  1.3M  1.6G   1% /run
/dev/mapper/pve-root           25G  5.4G   19G  23% /
tmpfs                         7.8G   46M  7.7G   1% /dev/shm
tmpfs                         5.0M     0  5.0M   0% /run/lock
efivarfs                      512K  115K  393K  23% /sys/firmware/efi/efivars
</code></pre></div></div>

<p>参考文章: <a href="https://gist.github.com/dergachev/6828967">https://gist.github.com/dergachev/6828967</a></p>]]></content><author><name></name></author><category term="PVE" /><summary type="html"><![CDATA[PVE 安装完系统后, 会自动把系统所在硬盘划分为 local 和 local-lvm 两个部分, 但是有时候并不需要 local-lvm, 那么应该如何删掉 local-lvm, 然后把原来 local-lvm 占用的空间和 local 进行合并呢.]]></summary></entry><entry><title type="html">PVE 重装后做的几件事</title><link href="https://isay.me/2024/04/after-pve-reinstall.html" rel="alternate" type="text/html" title="PVE 重装后做的几件事" /><published>2024-04-08T00:00:00+08:00</published><updated>2024-04-08T00:00:00+08:00</updated><id>https://isay.me/2024/04/after-pve-reinstall</id><content type="html" xml:base="https://isay.me/2024/04/after-pve-reinstall.html"><![CDATA[<h1 id="背景">背景</h1>
<p>小区楼要更新配电箱, 所以需要进行断电. 在断电前我把 PVE 关机. 但是在来电后, 重启 PVE 失败. 经过排查后发现是 PVE 的系统 U 盘损坏.
经过 fsck 检查后, 仍然无法启动. 正好手头有一个备用 U 盘, 所以只好重装 PVE 系统.</p>

<p>之前 PVE 系统是 7.x, 这次就趁机会直接安装最新的 8.x 版本. 安装后当然所有的虚拟机及相关配置都没有了.</p>

<p>但是因为所有的虚拟机及lxc 容器都单独放在了一个硬盘上, 接下来需要做的就是将这些虚拟机及 lxc 容器进行恢复.</p>

<h1 id="恢复虚拟机数据磁盘及备份磁盘自动挂载">恢复虚拟机数据磁盘及备份磁盘自动挂载</h1>
<p>系统重装完成后, 共有 2 块 nvme 硬盘, 再加一个 sata 控制器下的两个 hdd 硬盘. 现在要做的是把这 2 块 nvme 硬盘挂载到 PVE 系统中作为镜像盘和备份盘.</p>

<p>如果是未使用的磁盘, 可以直接在 PVE 中配置挂载. 但是因为磁盘是已经使用过的, 所以就不能在 PVE 中配置自动挂载, 而要手动处理了.
<img src="/uploads/2024/04/2024040801-01.png" alt="" /></p>

<p>PVE 配置自动挂载实际是使用的 systemd 的功能, 所以手动挂载也是一样的.</p>

<ol>
  <li>
    <p>在 <code class="language-plaintext highlighter-rouge">/mnt/pve/</code> 下新建 <code class="language-plaintext highlighter-rouge">nvme0</code> 及 <code class="language-plaintext highlighter-rouge">nvme1</code> 两个空目录作为挂载点.</p>

    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nb">mkdir</span> <span class="nt">-p</span> /mnt/pve/nvme0
 <span class="nb">mkdir</span> <span class="nt">-p</span> /mnt/pve/nvme1
</code></pre></div>    </div>
  </li>
  <li>
    <p>在 <code class="language-plaintext highlighter-rouge">/etc/systemd/system</code> 上分别新建 <code class="language-plaintext highlighter-rouge">mnt-pve-nvme0.mount</code> 及 <code class="language-plaintext highlighter-rouge">mnt-pve-nvme1.mount</code> 文件, 用于 systemd 挂载磁盘.</p>

    <p><code class="language-plaintext highlighter-rouge">mnt-pve-nvme0.mount</code> 内容如下</p>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> [Install]
 WantedBy=multi-user.target

 [Mount]
 Options=defaults
 Type=ext4
 What=/dev/disk/by-uuid/d9b8de77-2020-4b27-b213-8a3158acda69
 Where=/mnt/pve/nvme0

 [Unit]
 Description=Mount storage 'nvme0' under /mnt/pve
</code></pre></div>    </div>

    <p><code class="language-plaintext highlighter-rouge">mnt-pve-nvme1.mount</code> 内容如下</p>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> [Install]
 WantedBy=multi-user.target

 [Mount]
 Options=defaults
 Type=ext4
 What=/dev/disk/by-uuid/e6dc0972-1361-4502-b5cc-596e6eb7be88
 Where=/mnt/pve/nvme1

 [Unit]
 Description=Mount storage 'nvme1' under /mnt/pve
</code></pre></div>    </div>

    <p>上面文件中的 by-uuid 后面的一串 uuid 可以通过命令查到</p>

    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-al</span> /dev/disk/by-uuid/
 total 0
 drwxr-xr-x 2 root root 180 Apr  7 20:00 <span class="nb">.</span>
 drwxr-xr-x 7 root root 140 Mar 28 22:56 ..
 lrwxrwxrwx 1 root root  15 Mar 28 22:52 d9b8de77-2020-4b27-b213-8a3158acda69 -&gt; ../../nvme1n1p1
 lrwxrwxrwx 1 root root  15 Mar 28 22:52 e6dc0972-1361-4502-b5cc-596e6eb7be88 -&gt; ../../nvme0n1p1
</code></pre></div>    </div>

    <p>可以看到两块硬盘的 uuid 分别是什么, 填写到上面的 systemd 配置文件中即可.</p>

    <p>那么如何确定上面的 <code class="language-plaintext highlighter-rouge">nvme1n1p1</code> 和 <code class="language-plaintext highlighter-rouge">nvme0n1p1</code> 分别是哪块硬盘呢. 这个可以到 PVE web ui 中查看. 可能通过磁盘大小, 以及型号确定对应的硬盘.</p>

    <p><img src="/uploads/2024/04/2024040801-02.png" alt="" /></p>
  </li>
  <li>
    <p>设置自动挂载配置文件生效</p>

    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> systemctl <span class="nb">enable</span> <span class="nt">--now</span> mnt-pve-nvme0.mount
 systemctl <span class="nb">enable</span> <span class="nt">--now</span> mnt-pve-nvme1.mount
</code></pre></div>    </div>

    <p>命令运行成功后, 正常情况下就可以在 PVE webui 中看到已经挂载好的目录了.</p>

    <p><img src="/uploads/2024/04/2024040801-05.png" alt="" /></p>

    <p>通过 systemd 命令也可以查看状态</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ systemctl status mnt-pve-nvme0.mount
 ● mnt-pve-nvme0.mount - Mount storage 'nvme0' under /mnt/pve
     Loaded: loaded (/etc/systemd/system/mnt-pve-nvme0.mount; enabled; preset: enabled)
     Active: active (mounted) since Thu 2024-03-28 22:52:33 CST; 1 week 3 days ago
       Where: /mnt/pve/nvme0
       What: /dev/nvme1n1p1
       Tasks: 0 (limit: 18888)
     Memory: 1008.0K
         CPU: 24ms
     CGroup: /system.slice/mnt-pve-nvme0.mount

 Mar 28 22:52:33 pve systemd[1]: mnt-pve-nvme0.mount: Directory /mnt/pve/nvme0 to mount over is not empty, mounting anyway.
 Mar 28 22:52:33 pve systemd[1]: Mounting mnt-pve-nvme0.mount - Mount storage 'nvme0' under /mnt/pve...
 Mar 28 22:52:33 pve systemd[1]: Mounted mnt-pve-nvme0.mount - Mount storage 'nvme0' under /mnt/pve.
</code></pre></div>    </div>
  </li>
  <li>
    <p>完成镜像盘和备份盘的挂载后, 下一步是配置数据中心的存储</p>

    <p><img src="/uploads/2024/04/2024040801-03.png" alt="" /></p>

    <p><img src="/uploads/2024/04/2024040801-04.png" alt="" /></p>

    <p>在添加目录时, id 我输入的是 <code class="language-plaintext highlighter-rouge">nvme0</code> 和 <code class="language-plaintext highlighter-rouge">nvme1</code>, 目录我输入的是 <code class="language-plaintext highlighter-rouge">/mnt/pve/nvme0</code> 和 <code class="language-plaintext highlighter-rouge">/mnt/pve/nvme1</code>. 内容选择好目录对应的存储类型即可. 我的 nvme0 负责硬盘映像和模板, nvme1 负责备份文件.</p>
  </li>
</ol>

<h1 id="恢复-sata-控制器直通">恢复 sata 控制器直通</h1>
<p>恢复完了磁盘挂载, 正常来说下一步是恢复虚拟机和 lxc 容器了. 但是由于我的其中一个虚拟机直通了 sata 控制器, 所以还需要进行一些额外的处理</p>

<p>sata 控制器的直通, 可以查看之前已经写的文章 <a href="/2023/05/pve-enable-iommu-and-sata-controller-passthrough.html">PVE 启用 IOMMU 功能为虚拟机开启 sata 控制器直通</a></p>

<h1 id="恢复虚拟机及容器">恢复虚拟机及容器</h1>
<p>这个没有什么需要说的, 我的 PVE 虚拟机开启了自动备份, 备份位置是一块单独的硬盘. 所以重装 PVE 系统后, 通过上面的操作重新挂载硬盘后, 就可以到备份盘中将需要的虚拟机有 lxc 容器进行恢复了.</p>

<p>PVE 开启自动备份位置及相关配置如下图</p>

<p><img src="/uploads/2024/04/2024040801-06.png" alt="" /></p>

<p><img src="/uploads/2024/04/2024040801-07.png" alt="" /></p>]]></content><author><name></name></author><category term="PVE" /><summary type="html"><![CDATA[背景 小区楼要更新配电箱, 所以需要进行断电. 在断电前我把 PVE 关机. 但是在来电后, 重启 PVE 失败. 经过排查后发现是 PVE 的系统 U 盘损坏. 经过 fsck 检查后, 仍然无法启动. 正好手头有一个备用 U 盘, 所以只好重装 PVE 系统.]]></summary></entry><entry><title type="html">使用 systemd 自动启动 ssh-agent</title><link href="https://isay.me/2023/07/systemd-start-ssh-agent.html" rel="alternate" type="text/html" title="使用 systemd 自动启动 ssh-agent" /><published>2023-07-17T00:00:00+08:00</published><updated>2023-07-17T00:00:00+08:00</updated><id>https://isay.me/2023/07/systemd-start-ssh-agent</id><content type="html" xml:base="https://isay.me/2023/07/systemd-start-ssh-agent.html"><![CDATA[<p>最近在整理自己的 sshkey, 看了网上大家的讨论, 虽然 sshkey 普遍用在无密码登录场景, 但是最好也给 sshkey 加一个密码. 加密码的好处是为了更好的安全性, 即使密钥丢失, 没有密码也可以保证安全. 至于 ssh 连接时需要输入密码的问题, 可以使用 ssh-agent 来解决.</p>

<p>所以搜索了一些关于 ssh-agent 的知识, 打算重新生成一批 sshkey 并添加密码, 然后交给 ssh-agent 进行管理.</p>

<p>本文记录一下在 linux 中如何使用 systemd 在用户登录时自动启动 ssh-agent 并常驻后台, 方便 ssh 连接时进行使用.</p>

<p><strong>首先</strong> 是新建 systemd unit <code class="language-plaintext highlighter-rouge">~/.config/systemd/user/ssh-agent.service</code> 文件如下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=SSH key agent

[Service]
Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK
# ExecStartPost=/usr/bin/ssh-add -t 1h

[Install]
WantedBy=default.target
</code></pre></div></div>

<p>由于是 systemd用户服务单元, 所以创建位置为 <code class="language-plaintext highlighter-rouge">~/.config/systemd/user/ssh-agent.service</code>.</p>

<p>当设置 <code class="language-plaintext highlighter-rouge">Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket</code> 时，它将为systemd用户服务单元中的 <code class="language-plaintext highlighter-rouge">SSH_AUTH_SOCK</code> 环境变量指定一个特定的值。设置<code class="language-plaintext highlighter-rouge">SSH_AUTH_SOCK</code> 环境变量为 <code class="language-plaintext highlighter-rouge">%t/ssh-agent.socket</code> 的目的是告诉SSH客户端在与ssh-agent进行通信时使用这个套接字文件。SSH客户端将通过该套接字与ssh-agent建立连接，以请求私钥并进行身份验证。<code class="language-plaintext highlighter-rouge">%t</code> 是systemd的一个模式，代表了服务单元的运行时目录。对于用户服务单元，<code class="language-plaintext highlighter-rouge">%t</code> 指代了当前用户的运行时目录。对于用户服务单元，<code class="language-plaintext highlighter-rouge">%t</code> 通常会被替换为当前用户的运行时目录, 用户运行时目录的默认位置是<code class="language-plaintext highlighter-rouge"> /run/user/&lt;user-id&gt;</code>，其中 <code class="language-plaintext highlighter-rouge">&lt;user-id&gt;</code> 是当前用户的用户ID. 因此，当使用<code class="language-plaintext highlighter-rouge">%t</code> 模式时，例如 <code class="language-plaintext highlighter-rouge">%t/ssh-agent.socket</code> ，它将被展开为当前用户的运行时目录加上后续的路径。例如，如果当前用户的用户ID是1000，那么 <code class="language-plaintext highlighter-rouge">%t/ssh-agent.socket</code> 会被展开为 <code class="language-plaintext highlighter-rouge">/run/user/1000/ssh-agent.socket</code>.</p>

<p><code class="language-plaintext highlighter-rouge">ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK</code> 的作用是让 ssh-agent 以守护进程（daemon）模式运行.</p>

<p><code class="language-plaintext highlighter-rouge">ssh-add -t 1h</code> 命令，它的作用是将私钥添加到ssh-agent代理中，并设置私钥的有效时间为1小时（1h）。这样，在成功登录到远程服务器后，私钥将在ssh-agent中保持有效状态1小时，而无需再次输入密码。此功能可以提高安全性，因为私钥在有效时间过后将自动从代理中移除，减少了私钥长时间暴露在内存中的风险。一旦有效时间到期，就需要重新输入密码来重新加载私钥到ssh-agent，以继续使用该私钥进行SSH连接。</p>

<p>但是看上面 <code class="language-plaintext highlighter-rouge">ExecStartPost</code> 被注释掉了，在这里被注释的原因是，如果私钥都是无密码的那就没问题，但是如果私钥是有密码的，那么这个命令会出现一个输入密码的 prompt, 由于 systemd 无法输入密码， 所以会导致启动失败，因此 <code class="language-plaintext highlighter-rouge">ExecStartPost</code> 在这里注释掉。</p>

<p>当 ssh-agent 启动成功后，后续可以执行 <code class="language-plaintext highlighter-rouge">ssh-add</code> 命令，手动将 <code class="language-plaintext highlighter-rouge">~/.ssh</code> 目录下的私钥添加到 ssh-agent 中并输入密码。添加成功后， 后续再通过 ssh 连接时就不需要输入密码了。</p>

<p><strong>然后</strong> 是配置 ssh 使用 ssh-agent</p>

<p>配置 ssh 使用 ssh-agent 有两种方案</p>

<p>第一种方案是在 shell 启动时, 执行 <code class="language-plaintext highlighter-rouge">eval $(ssh-agent -s)</code>.</p>

<p><code class="language-plaintext highlighter-rouge">ssh-agent -s</code> 输出内容如下所示</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SSH_AUTH_SOCK=/var/folders/g9/yczk5ymn4k16g211z5pn9bdr0000gn/T//ssh-HPXEJnNTleTH/agent.21286; export SSH_AUTH_SOCK;
SSH_AGENT_PID=21287; export SSH_AGENT_PID;
echo Agent pid 21287;
</code></pre></div></div>

<p>但是如果通过 <code class="language-plaintext highlighter-rouge">eval</code> 执行, 就会在 shell 中输出一名话 <code class="language-plaintext highlighter-rouge">Agent pid 21287</code>. 所以看起来可能会比较烦.</p>

<p>另一种方案是 <code class="language-plaintext highlighter-rouge">.bashrc</code> 等文件中对 <code class="language-plaintext highlighter-rouge">SSH_AUTH_SOCK</code> 变量进行赋值.</p>

<p>在上面 systemd unit 文件中, 使用了 <code class="language-plaintext highlighter-rouge">Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket</code> 指定 socket 路径, 所以可以在 <code class="language-plaintext highlighter-rouge">.bashrc</code> 等文件中使用 <code class="language-plaintext highlighter-rouge">export SSH_AUTH_SOCK="${XDG_RUNTIME_DIR}/ssh-agent.socket"</code> 来指定环境变量以便 ssh 来读取.</p>

<p><code class="language-plaintext highlighter-rouge">XDG_RUNTIME_DIR</code> 是一个环境变量，用于指定当前用户的运行时目录（runtime directory）。该目录用于存储运行时数据，例如套接字文件、临时文件等。一般情况下，<code class="language-plaintext highlighter-rouge">XDG_RUNTIME_DIR</code> 的值是 <code class="language-plaintext highlighter-rouge">/run/user/&lt;user-id&gt;</code> ，其中 <code class="language-plaintext highlighter-rouge">&lt;user-id&gt;</code> 是当前用户的用户ID。可以看到 <code class="language-plaintext highlighter-rouge">XDG_RUNTIME_DIR</code> 和 systemd unit 文件中的 <code class="language-plaintext highlighter-rouge">%t</code> 定义基本一致.</p>

<p>如果你的系统没有 <code class="language-plaintext highlighter-rouge">XDG_RUNTIME_DIR</code> 这个变量, 那就执行一下 <code class="language-plaintext highlighter-rouge">ssh-agent -s</code> 看下 <code class="language-plaintext highlighter-rouge">SSH_AUTH_SOCK</code> 的值是多少, 写到 <code class="language-plaintext highlighter-rouge">.bashrc</code> 等文件里就行了.</p>

<p><strong>最后</strong> 是使服务单元生效。</p>

<p>在终端中运行以下命令：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl --user enable ssh-agent
</code></pre></div></div>

<p>这将使 <code class="language-plaintext highlighter-rouge">ssh-agent.service</code> 成为用户的默认服务。</p>

<p>启动服务。在终端中运行以下命令：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl --user start ssh-agent
</code></pre></div></div>

<p>这将启动 ssh-agent 并将其绑定到 <code class="language-plaintext highlighter-rouge">SSH_AUTH_SOCK</code> 路径。</p>

<p>现在，当登录到用户帐户时，ssh-agent 将自动启动，并且可以通过 <code class="language-plaintext highlighter-rouge">ssh-add</code> 命令添加私钥并将其保存在代理中。这样，在使用 ssh 连接到远程服务器时将不再需要每次输入密码。</p>

<hr />

<p><strong>后记</strong></p>

<p>在 PVE LXC 容器中， 由于是使用的 root 用户登录，按照上面的步骤配置时， 当执行 <code class="language-plaintext highlighter-rouge">systemctl --user enable ssh-agent</code> 时会遇到报错</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Failed to connect to bus: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined (consider using --machine=@.host --user to connect to bus of other user)
</code></pre></div></div>

<p>此时可以选择直接以 root 用户身份运行 ssh-agent.</p>

<p>systemd unit 文件 <code class="language-plaintext highlighter-rouge">/etc/systemd/system/ssh-agent.service</code> 内容如下</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=SSH key agent

[Service]
Environment=SSH_AUTH_SOCK=/var/run/user/ssh-agent.socket
ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK
# ExecStartPost=/usr/bin/ssh-add -t 1h

[Install]
WantedBy=default.target
</code></pre></div></div>

<p>systemd 启用命令</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl enable ssh-agent.service
systemctl start ssh-agent.service
</code></pre></div></div>]]></content><author><name></name></author><category term="Systemd" /><category term="SSH" /><summary type="html"><![CDATA[最近在整理自己的 sshkey, 看了网上大家的讨论, 虽然 sshkey 普遍用在无密码登录场景, 但是最好也给 sshkey 加一个密码. 加密码的好处是为了更好的安全性, 即使密钥丢失, 没有密码也可以保证安全. 至于 ssh 连接时需要输入密码的问题, 可以使用 ssh-agent 来解决.]]></summary></entry><entry><title type="html">PVE LXC 安装 ZeroTier 并开启 site-to-site networking</title><link href="https://isay.me/2023/07/pve-lxc-enable-zerotier-site-to-site-networking.html" rel="alternate" type="text/html" title="PVE LXC 安装 ZeroTier 并开启 site-to-site networking" /><published>2023-07-16T00:00:00+08:00</published><updated>2023-07-16T00:00:00+08:00</updated><id>https://isay.me/2023/07/pve-lxc-enable-zerotier-site-to-site-networking</id><content type="html" xml:base="https://isay.me/2023/07/pve-lxc-enable-zerotier-site-to-site-networking.html"><![CDATA[<p>前面两篇介绍了如何在 PVE LXC 容器下安装 tailscale 并开启 <code class="language-plaintext highlighter-rouge">site-to-site networking</code>.</p>

<p>和 tailscale 齐名的就是 zerotier 了, 所以本篇来介绍一下在 PVE LXC 容器下怎么安装 zerotier, 标题中的 <code class="language-plaintext highlighter-rouge">site-to-site networking</code> 是我复用了 tailscale 里的名称, 因为我希望在 zerotier 中也实现一样的效果.</p>

<p><strong>首先</strong> 建立 lxc 容器, 并做好相应的配置
选择新建一个非特权CT容器, 模板我选择的是 <code class="language-plaintext highlighter-rouge">Debian 11(bullseye)</code>, 建立容器时, 网卡名称最好是叫 <code class="language-plaintext highlighter-rouge">eth0</code>, 因为后面配置 <code class="language-plaintext highlighter-rouge">site-to-site networking</code> 时会用到.</p>

<p>由于是非特权容器, 容器启动后会缺少 zerotier 运行需要的东西, 所以先不启动而是对容器作一些配置修改.</p>

<p>在 pve 宿主中, 确认 <code class="language-plaintext highlighter-rouge">/dev/net/tun</code> 存在并获取对应的信息, 具体命令和返回如下</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@pve:~# ls -al /dev/net/tun
crw-rw-rw- 1 root root 10, 200 Jun 30 23:08 /dev/net/tun
</code></pre></div></div>

<p>记录其中的 <code class="language-plaintext highlighter-rouge">10, 200</code> 这两个数字, 后面需要用到.</p>

<p>然后修改 <code class="language-plaintext highlighter-rouge">/etc/pve/lxc/CTID.conf</code> 文件, 新增如下两行</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
</code></pre></div></div>

<p>上面的 <code class="language-plaintext highlighter-rouge">10:200</code> 需要和前面使用 <code class="language-plaintext highlighter-rouge">ls -al /dev/net/tun</code> 获取的结果对应起来.</p>

<p><strong>然后</strong> 是开启 lxc 的 IP 转发功能</p>

<p>启动 lxc 容器后, 编辑 <code class="language-plaintext highlighter-rouge">/etc/sysctl.conf</code> 文件, 将以下两行的注释去掉. 如果没有这两行, 需要添加</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
</code></pre></div></div>

<p>编辑完成后, 使用 <code class="language-plaintext highlighter-rouge">sysctl</code> 命令 reload</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sysctl -p /etc/sysctl.conf
</code></pre></div></div>

<p><strong>然后</strong> 是安装 zerotier</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt update &amp;&amp; apt install curl pgp iptables iptables-persistent
curl -s https://install.zerotier.com | bash
</code></pre></div></div>

<p>如果你的源速度比较慢, 可以先换成速度比较快的源再安装.</p>

<p><strong>然后</strong> 是加入 zerotier 网络</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zerotier-cli join &lt;NETWORK ID&gt;
</code></pre></div></div>

<p>如果是加入的一个 <code class="language-plaintext highlighter-rouge">private network</code>, 需要在 zerotier 后台点击允许, 可以按需分配一个IP地址. 至于 zerotier 如何创建网络并选择子网前缀这些我就不再讲了.</p>

<p><strong>然后</strong> 是开启 <code class="language-plaintext highlighter-rouge">site-to-site networking</code></p>

<p>zerotier 官方文档地址 <a href="https://docs.zerotier.com/route-between-phys-and-virt/">Route between ZeroTier and Physical Networks</a>, 下面的内容是结合官方文档以及个人的实际情况举例.</p>

<p>由于是两个网络之间建立 <code class="language-plaintext highlighter-rouge">site-to-site networking</code>, 所以我这边会以两个网络举例.</p>

<p>网络 A 为 <code class="language-plaintext highlighter-rouge">192.168.100.0/24</code>, 安装 zerotier 的机器 A IP 为 <code class="language-plaintext highlighter-rouge">192.168.100.17</code>, 网络 B 为 <code class="language-plaintext highlighter-rouge">192.168.88.0/24</code>, 安装 zerotier 的机器 B IP 为 <code class="language-plaintext highlighter-rouge">192.168.88.17</code>.</p>

<p>在 zerotier 后台配置到两个网络的静态路由, 我给两个 zerotier 客户端分配的IP分别为 <code class="language-plaintext highlighter-rouge">10.244.100.17</code> 和 <code class="language-plaintext highlighter-rouge">10.244.88.17</code>, 在 zerotier 后台配置静态路由如下</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>10.244.0.0/16 (LAN) # 这条是选择 zerotier 子网前缀后自动生成的
192.168.100.0/24 via 10.244.100.17
192.168.88.0/24 via 10.244.88.17
</code></pre></div></div>

<p><strong>然后</strong> 是客户端配置 iptables</p>

<p>通过 <code class="language-plaintext highlighter-rouge">ip a</code> 命令可以查询到 zerotier 虚拟网卡的名称是什么, 这里假设 zerotier 网卡名称为 <code class="language-plaintext highlighter-rouge">ztabcdef</code>, 物理网卡名称为 <code class="language-plaintext highlighter-rouge">eth0</code>. 对以下变量进行赋值</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PHY_IFACE=eth0; ZT_IFACE=ztabcdef
</code></pre></div></div>

<p>分别在两个 zerotier 客户端上运行下面 3 条 iptables 规则.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables -t nat -A POSTROUTING -o $PHY_IFACE -j MASQUERADE
iptables -A FORWARD -i $PHY_IFACE -o $ZT_IFACE -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i $ZT_IFACE -o $PHY_IFACE -j ACCEPT
</code></pre></div></div>

<p><strong>然后</strong> 是配置各个子网的静态路由.</p>

<p>在两个网络的网关(路由器)中, 分别添加一条到另一个网络的静态路由, 分别指向 <code class="language-plaintext highlighter-rouge">192.168.100.17</code> 以及 <code class="language-plaintext highlighter-rouge">192.168.88.17</code> 中. 我的网关是 RouterOS 路由器, 以 RouterOS 路由器举例</p>

<p>网络 A 网关执行命令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/ip route add dst-address=192.168.88.0 gateway=192.168.100.17
</code></pre></div></div>

<p>网络 B 网关执行命令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/ip route add dst-address=192.168.100.0 gateway=192.168.88.17
</code></pre></div></div>

<p>此时正常情况下, 网络 A 和 B 中未安装 zerotier 的设备就可以访问对方网络中的服务了.</p>

<p><strong>最后</strong> 是固化 iptables 规则, 方便重启后规则仍然生效</p>

<p>由于之前已经安装了 iptables-persistent, 所以可以把 iptables 规则固化到 <code class="language-plaintext highlighter-rouge">/etc/iptables/rules.v4</code> 文件中, 重启后 iptables-persistent 会自动读取这个文件并执行相应的规则. 执行如下命令</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables-save &gt; /etc/iptables/rules.v4
</code></pre></div></div>

<p>执行命令后, 查看 <code class="language-plaintext highlighter-rouge">/etc/iptables/rules.v4</code> 文件就可以看到对应的内容.</p>]]></content><author><name></name></author><category term="ZeroTier" /><category term="PVE" /><category term="LXC" /><summary type="html"><![CDATA[前面两篇介绍了如何在 PVE LXC 容器下安装 tailscale 并开启 site-to-site networking.]]></summary></entry></feed>