<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://lzc6244.github.io//feed.xml" rel="self" type="application/atom+xml" /><link href="https://lzc6244.github.io//" rel="alternate" type="text/html" /><updated>2026-07-09T13:42:20+00:00</updated><id>https://lzc6244.github.io//feed.xml</id><title type="html">看看你极限深蓝的 Blog</title><subtitle>看看你极限深蓝的 Blog</subtitle><author><name>看看你极限深蓝</name></author><entry><title type="html">docker iptables 出站白名单管理系统</title><link href="https://lzc6244.github.io//2026/03/07/docker-iptables-%E5%87%BA%E7%AB%99%E7%99%BD%E5%90%8D%E5%8D%95%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F.html" rel="alternate" type="text/html" title="docker iptables 出站白名单管理系统" /><published>2026-03-07T00:00:00+00:00</published><updated>2026-03-07T00:00:00+00:00</updated><id>https://lzc6244.github.io//2026/03/07/docker-iptables-%E5%87%BA%E7%AB%99%E7%99%BD%E5%90%8D%E5%8D%95%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F</id><content type="html" xml:base="https://lzc6244.github.io//2026/03/07/docker-iptables-%E5%87%BA%E7%AB%99%E7%99%BD%E5%90%8D%E5%8D%95%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F.html"><![CDATA[<h1 id="docker-iptables-出站白名单管理系统">docker iptables 出站白名单管理系统</h1>

<p>一个基于 iptables + ipset 的高性能出站流量白名单管理方案，支持动态域名解析、静态 IP 白名单、自动 IP 过期清理等特性。</p>

<blockquote>
  <p><strong>应用场景</strong>：适用于 Docker Compose 容器环境中对外发请求进行白名单控制，屏蔽非授权的出站访问，提升容器安全性。</p>
</blockquote>

<hr />

<h1 id="目录">目录</h1>

<ul>
  <li>
    <p>功能特性</p>
  </li>
  <li>
    <p>完整脚本</p>

    <ul>
      <li>
        <p>iptables-whitelist.sh</p>
      </li>
      <li>
        <p>iptables-whitelist.yaml</p>
      </li>
    </ul>
  </li>
  <li>
    <p>快速开始</p>

    <ul>
      <li>
        <p>安装步骤</p>
      </li>
      <li>
        <p>Docker Compose 使用</p>
      </li>
    </ul>
  </li>
  <li>
    <p>配置说明</p>
  </li>
  <li>
    <p>设计理念</p>
  </li>
  <li>
    <p>技术架构</p>
  </li>
  <li>
    <p>优点分析</p>
  </li>
  <li>
    <p>缺点与局限</p>
  </li>
  <li>
    <p>性能指标</p>
  </li>
  <li>
    <p>常见问题</p>
  </li>
  <li>
    <p>总结</p>
  </li>
</ul>

<hr />

<h1 id="功能特性">功能特性</h1>

<h2 id="核心功能">核心功能</h2>

<ul>
  <li>
    <p>✅ <strong>静态 IP 白名单</strong>：支持单个 IP 或 CIDR 网段格式</p>
  </li>
  <li>
    <p>✅ <strong>动态域名白名单</strong>：定时解析域名 IP，自动更新白名单</p>
  </li>
  <li>
    <p>✅ <strong>IP 自动过期</strong>：使用 ipset timeout 特性，自动清理长时间未更新的 IP</p>
  </li>
  <li>
    <p>✅ <strong>DNS 负载均衡</strong>：随机选择 DNS 服务器，避免单一 DNS 缓存问题</p>
  </li>
  <li>
    <p>✅ <strong>连接保护</strong>：已建立的 TCP 连接不受 IP 白名单变化影响</p>
  </li>
  <li>
    <p>✅ <strong>日志管理</strong>：自动日志轮转，避免磁盘占满</p>
  </li>
  <li>
    <p>✅ <strong>配置分离</strong>：YAML 配置文件，易于维护和版本控制</p>
  </li>
</ul>

<h2 id="安全特性">安全特性</h2>

<ul>
  <li>
    <p>🔒 <strong>默认拒绝</strong>：所有出站流量默认拒绝，只允许白名单流量</p>
  </li>
  <li>
    <p>🔒 <strong>最小权限</strong>：只允许明确配置的 IP 和域名</p>
  </li>
  <li>
    <p>🔒 <strong>配置验证</strong>：自动检查配置参数合法性</p>
  </li>
  <li>
    <p>🔒 <strong>超时保护</strong>：DNS 查询超时机制，避免进程阻塞</p>
  </li>
</ul>

<hr />

<h1 id="完整脚本">完整脚本</h1>

<h2 id="iptables-whitelistsh">iptables-whitelist.sh</h2>

<p><a href="/file-viewer/?file=/files/2026/2026-03-07-docker-ip-白名单-iptables-whitelist.sh">iptables-whitelist.sh</a></p>

<h2 id="iptables-whitelistyaml">iptables-whitelist.yaml</h2>

<p><a href="/file-viewer/?file=/files/2026/2026-03-07-docker-ip-白名单-iptables-whitelist.yaml">iptables-whitelist.yaml</a></p>

<hr />

<h1 id="快速开始">快速开始</h1>

<h2 id="安装步骤">安装步骤</h2>

<ol>
  <li>
    <p><strong>创建脚本文件</strong></p>

    <pre><code class="language-Bash"># 创建目录
mkdir -p /etc/iptables-whitelist

# 复制上面的脚本内容到文件
cat &gt; /usr/local/bin/iptables-whitelist.sh &lt;&lt; 'EOF'
# 粘贴上面 "iptables-whitelist.sh" 章节的完整脚本内容
EOF

# 赋予执行权限
chmod +x /usr/local/bin/iptables-whitelist.sh
</code></pre>
  </li>
  <li>
    <p><strong>创建配置文件</strong></p>

    <pre><code class="language-Bash"># 复制上面的配置内容到文件
cat &gt; /etc/iptables-whitelist/config.yaml &lt;&lt; 'EOF'
# 粘贴上面 "iptables-whitelist.yaml" 章节的完整配置内容
EOF
</code></pre>
  </li>
  <li>
    <p><strong>编辑配置文件</strong></p>

    <pre><code class="language-Bash"># 根据实际需求修改配置
vim /etc/iptables-whitelist/config.yaml
</code></pre>
  </li>
  <li>
    <p><strong>运行脚本</strong></p>

    <pre><code class="language-Bash">/usr/local/bin/iptables-whitelist.sh
</code></pre>
  </li>
  <li>
    <p><strong>验证运行状态</strong></p>

    <pre><code class="language-Bash"># 进入容器
docker compose exec -it &lt;container_name&gt; bash

# 在容器内查看 ipset 列表
ipset list

# 在容器内查看 iptables 规则
iptables -L OUTPUT -v -n

# 在容器内查看日志
tail -f /var/log/iptables-whitelist/watcher.log
</code></pre>
  </li>
</ol>

<h2 id="docker-compose-使用">Docker Compose 使用</h2>

<h3 id="1-dockerfile-配置">1. Dockerfile 配置</h3>

<p>在 Dockerfile 中安装必要的依赖包：</p>

<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> your-base-image</span>

<span class="c"># 安装 iptables 相关依赖</span>
<span class="k">RUN </span>apt update <span class="o">&amp;&amp;</span> 
    apt install -y iptables iproute2 ipset dnsutils coreutils &amp;&amp; 
    rm -rf /var/lib/apt/lists/*

# 复制脚本和配置文件
<span class="k">COPY</span><span class="s"> iptables-whitelist.sh /app/iptables-whitelist.sh</span>
<span class="k">COPY</span><span class="s"> iptables-whitelist.yaml /etc/iptables-whitelist/config.yaml</span>
<span class="k">COPY</span><span class="s"> start.sh /app/start.sh</span>

<span class="c"># 赋予执行权限</span>
<span class="k">RUN </span><span class="nb">chmod</span> +x /app/iptables-whitelist.sh /app/start.sh

<span class="c"># 应用启动脚本（包含白名单初始化）</span>
<span class="k">ENTRYPOINT</span><span class="s"> ["/app/start.sh"]</span>
</code></pre></div></div>

<h3 id="2-startsh-启动脚本">2. start.sh 启动脚本</h3>

<p>iptables-whitelist.sh 是后台运行的守护进程，应通过启动脚本调用：</p>

<pre><code class="language-Bash">#!/bin/bash
set -e

echo "[$(date '+%Y-%m-%d %H:%M:%S')] ℹ️  初始化出站白名单..."

if [ -f /app/iptables-whitelist.sh ]; then
    bash /app/iptables-whitelist.sh
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ✅ 出站白名单已启动"
else
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ⚠️  iptables-whitelist.sh 不存在，跳过"
fi

# 启动主应用
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 🚀 启动主应用..."
exec /path/to/your/application
</code></pre>

<h3 id="3-docker-composeyaml-配置">3. docker-compose.yaml 配置</h3>

<pre><code class="language-YAML">name: your-project-name

services:
  app:
    build: .
    restart: always
    # 开启 NET_ADMIN 和 NET_RAW 能力，允许容器内操作 iptables
    # 不使用 privileged: true，遵循最小权限原则
    cap_add:
      - NET_ADMIN
      - NET_RAW
    volumes:
      # 挂载启动脚本
      - ./start.sh:/app/start.sh
      # 挂载白名单脚本（可选，也可在构建时 COPY）
      - ./iptables-whitelist.sh:/app/iptables-whitelist.sh
      # 挂载配置文件
      - ./iptables-whitelist.yaml:/etc/iptables-whitelist/config.yaml
      # 挂载日志目录
      - ./logs:/var/log/iptables-whitelist
      # 其他应用数据卷
      - ./data:/opt/app/data
</code></pre>

<h3 id="4-依赖说明">4. 依赖说明</h3>

<p>本脚本需要在 Docker 镜像中预先安装以下依赖包：</p>

<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 在 Dockerfile 中添加</span>
<span class="k">RUN </span>apt update <span class="o">&amp;&amp;</span> 
    apt install -y iptables iproute2 ipset dnsutils coreutils &amp;&amp; 
    rm -rf /var/lib/apt/lists/*
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>依赖包</th>
      <th>用途</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">iptables</code></td>
      <td>防火墙规则管理</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ipset</code></td>
      <td>IP 集合管理（高性能）</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">dnsutils</code></td>
      <td>提供 dig 命令用于域名解析</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">coreutils</code></td>
      <td>提供 truncate 命令用于日志轮转</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">iproute2</code></td>
      <td>网络工具（可选）</td>
    </tr>
  </tbody>
</table>

<h3 id="5-权限说明">5. 权限说明</h3>

<p><strong>NET_ADMIN</strong>：允许执行网络管理操作，包括：</p>

<ul>
  <li>
    <p>配置 iptables 规则</p>
  </li>
  <li>
    <p>创建和管理 ipset</p>
  </li>
  <li>
    <p>修改路由表</p>
  </li>
</ul>

<p><strong>NET_RAW</strong>：允许使用原始套接字，包括：</p>

<ul>
  <li>
    <p>ping 命令（ICMP）</p>
  </li>
  <li>
    <p>某些网络诊断工具</p>
  </li>
</ul>

<p><strong>为什么不用 privileged: true？</strong></p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">privileged: true</code> 授予容器所有权限，存在安全风险</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">cap_add</code> 只授予必要的权限，符合最小权限原则</p>
  </li>
  <li>
    <p>更好的安全隔离</p>
  </li>
</ul>

<hr />

<h1 id="配置说明">配置说明</h1>

<h2 id="配置文件结构">配置文件结构</h2>

<pre><code class="language-YAML"># 静态 IP 白名单（支持单个 IP 和 CIDR 网段）
static_ips:
  - 192.168.1.100        # 单个服务器 IP 示例
  - 10.0.1.0/24          # 内网网段示例

# 域名白名单
domains:
  - name: api.example.com
    desc: "API 服务"
  - name: cdn.example.com
    desc: "CDN 节点"

# 内网网段（RFC1918 私有地址）
private_networks:
  - 10.0.0.0/8
  - 172.16.0.0/12
  - 192.168.0.0/16

# DNS 服务器（用于域名解析）
dns_servers:
  - 8.8.8.8
  - 8.8.4.4
  - 1.1.1.1
  - 223.5.5.5

# 定时配置
update_interval: 30      # 更新间隔（秒），最小 30
ipset_timeout: 3600      # IP 过期时间（秒）

# 日志配置
log_file: /var/log/iptables-whitelist/watcher.log
log_max_size: 10485760   # 10MB
log_backup_count: 5
</code></pre>

<h2 id="参数说明">参数说明</h2>

<table>
  <thead>
    <tr>
      <th>参数</th>
      <th>说明</th>
      <th>默认值</th>
      <th>最小值</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">update_interval</code></td>
      <td>域名解析更新间隔（秒）</td>
      <td>30</td>
      <td>30</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ipset_timeout</code></td>
      <td>IP 过期时间（秒）</td>
      <td>3600</td>
      <td>-</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">log_max_size</code></td>
      <td>日志文件最大大小（字节）</td>
      <td>10485760</td>
      <td>-</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">log_backup_count</code></td>
      <td>日志文件保留份数</td>
      <td>5</td>
      <td>-</td>
    </tr>
  </tbody>
</table>

<hr />

<h1 id="设计理念">设计理念</h1>

<h2 id="1-配置与逻辑分离">1. 配置与逻辑分离</h2>

<p><strong>设计思想</strong>：将配置数据与脚本逻辑分离，使用 YAML 格式存储配置。</p>

<p><strong>优势</strong>：</p>

<ul>
  <li>
    <p>修改配置无需编辑脚本</p>
  </li>
  <li>
    <p>版本控制 diff 清晰</p>
  </li>
  <li>
    <p>支持配置注释和分组</p>
  </li>
  <li>
    <p>便于配置管理和审计</p>
  </li>
</ul>

<h2 id="2-ipset-替代传统-iptables-规则">2. ipset 替代传统 iptables 规则</h2>

<p><strong>设计思想</strong>：使用 ipset 的哈希表存储 IP 地址，通过一条 iptables 规则匹配整个集合。</p>

<p><strong>传统方式</strong>：</p>

<pre><code class="language-Bash"># 每个IP一条规则
iptables -A OUTPUT -d 192.168.1.100 -j ACCEPT
iptables -A OUTPUT -d 10.0.1.50 -j ACCEPT
# ... N 条规则
</code></pre>

<p><strong>本方案</strong>：</p>

<pre><code class="language-Bash"># 1 条规则 + ipset 哈希表
iptables -A OUTPUT -m set --match-set whitelist_dns dst -j ACCEPT
</code></pre>

<p><strong>优势</strong>：</p>

<ul>
  <li>
    <p>O(1) 哈希查找 vs O(n) 线性查找</p>
  </li>
  <li>
    <p>动态更新无需重建规则</p>
  </li>
  <li>
    <p>内存占用降低 50%+</p>
  </li>
</ul>

<h2 id="3-分层-ipset-设计">3. 分层 ipset 设计</h2>

<p><strong>设计思想</strong>：按照用途将 IP 分类存储到不同的 ipset。</p>

<table>
  <thead>
    <tr>
      <th>ipset 名称</th>
      <th>用途</th>
      <th>timeout</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">whitelist_domain</code></td>
      <td>动态域名解析的 IP</td>
      <td>✅ 是</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">whitelist_static</code></td>
      <td>静态 IP 白名单</td>
      <td>❌ 否</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">whitelist_dns</code></td>
      <td>DNS 服务器</td>
      <td>❌ 否</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">whitelist_private</code></td>
      <td>内网网段</td>
      <td>❌ 否</td>
    </tr>
  </tbody>
</table>

<p><strong>优势</strong>：</p>

<ul>
  <li>
    <p>职责清晰，便于管理</p>
  </li>
  <li>
    <p>支持不同的过期策略</p>
  </li>
  <li>
    <p>灵活的访问控制</p>
  </li>
</ul>

<h2 id="4-守护进程容错设计">4. 守护进程容错设计</h2>

<p><strong>设计思想</strong>：使用独立子进程运行域名解析任务，与主进程解耦。</p>

<pre><code class="language-Bash">(
  set +e                    # 遇到错误不退出
  trap '' HUP TERM INT      # 忽略信号
  
  while true; do
    update_once
    sleep "$UPDATE_INTERVAL"
  done
) &lt;/dev/null &amp;              # 关闭标准输入
disown                      # 从 shell 分离
</code></pre>

<p><strong>优势</strong>：</p>

<ul>
  <li>
    <p>单次 DNS 失败不影响整体</p>
  </li>
  <li>
    <p>主进程退出不影响守护进程</p>
  </li>
  <li>
    <p>容器重启后自动恢复</p>
  </li>
</ul>

<h2 id="5-dns-缓存规避策略">5. DNS 缓存规避策略</h2>

<p><strong>设计思想</strong>：每次解析任务随机选择一个 DNS 服务器，所有域名共用该 DNS。</p>

<pre><code class="language-Bash"># 每次任务随机选择一个 DNS
local dns_server=$(get_random_dns)

# 所有域名使用同一个 DNS
for domain in $DOMAINS; do
  dig "@$dns_server" "$domain"
done
</code></pre>

<p><strong>优势</strong>：</p>

<ul>
  <li>
    <p>避免单一 DNS 缓存</p>
  </li>
  <li>
    <p>提高 DNS 解析成功率</p>
  </li>
  <li>
    <p>8 个国内外 DNS 服务器</p>
  </li>
</ul>

<h2 id="6-连接保护机制">6. 连接保护机制</h2>

<p><strong>设计思想</strong>：使用 conntrack 模块保护已建立的连接。</p>

<pre><code class="language-Bash">iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
</code></pre>

<p><strong>优势</strong>：</p>

<ul>
  <li>
    <p>IP 白名单变化不影响已有连接</p>
  </li>
  <li>
    <p>流式请求不会中断</p>
  </li>
  <li>
    <p>TCP 长连接不受影响</p>
  </li>
</ul>

<h2 id="7-安全的日志轮转">7. 安全的日志轮转</h2>

<p><strong>设计思想</strong>：使用 <code class="language-plaintext highlighter-rouge">cp + truncate</code> 替代 <code class="language-plaintext highlighter-rouge">mv</code> 进行日志轮转。</p>

<pre><code class="language-Bash"># ❌ 错误方式：破坏文件描述符
mv "$LOG_FILE" "${LOG_FILE}.1"

# ✅ 正确方式：保留文件描述符
cp "$LOG_FILE" "${LOG_FILE}.1"
truncate -s 0 "$LOG_FILE"
</code></pre>

<p><strong>优势</strong>：</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">cp</code> 保留原文件，守护进程不受影响</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">mv</code> 会破坏文件描述符，导致崩溃</p>
  </li>
</ul>

<hr />

<h1 id="技术架构">技术架构</h1>

<h2 id="架构图">架构图</h2>

<p><img src="/imgs/JeKyll/2026/2026-03-07-docker-ip-白名单-架构图.png" alt="" /></p>

<pre><code class="language-Plaintext">┌─────────────────────────────────────────────────────────────┐
│                     iptables-whitelist.sh                   │
├─────────────────────────────────────────────────────────────┤
│  主进程                                                    │
│  ├── 初始化 ipset                                          │
│  ├── 配置 iptables 规则                                     │
│  └── 启动守护进程                                          │
├─────────────────────────────────────────────────────────────┤
│  守护进程 (子进程)                                         │
│  └── while true:                                           │
│      ├── 随机选择 DNS 服务器                               │
│      ├── 遍历域名列表                                      │
│      ├── dig 解析 IP                                       │
│      ├── 更新 ipset                                        │
│      ├── 输出快照                                          │
│      ├── 日志轮转                                          │
│      └── sleep N 秒                                        │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                    iptables + ipset                         │
├─────────────────────────────────────────────────────────────┤
│  规则 1: ACCEPT -o lo                                      │
│  规则 2: ACCEPT ESTABLISHED,RELATED                        │
│  规则 3: ACCEPT whitelist_private (10.0.0.0/8, ...)       │
│  规则 4: ACCEPT whitelist_dns --dport 53                  │
│  规则 5: ACCEPT whitelist_static                           │
│  规则 6: ACCEPT whitelist_domain (动态 IP)                 │
│  规则 7: DROP (默认拒绝)                                   │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                        ipset 集合                           │
├─────────────────────────────────────────────────────────────┤
│  whitelist_domain  (hash:ip, timeout=3600)                │
│  whitelist_static   (hash:ip)                              │
│  whitelist_dns      (hash:ip)                              │
│  whitelist_private  (hash:net)                             │
└─────────────────────────────────────────────────────────────┘
</code></pre>

<h2 id="数据流">数据流</h2>

<p><img src="/imgs/JeKyll/2026/2026-03-07-docker-ip-白名单-数据流.png" alt="" /></p>

<pre><code class="language-Plaintext">配置文件 (YAML)
    │
    ▼
解析配置 → 读取 static_ips, domains, dns_servers
    │
    ▼
初始化 ipset → 创建 4 个 ipset
    │
    ▼
配置 iptables → 创建 7 条规则
    │
    ▼
启动守护进程 ────┐
    │             │
    ▼             ▼
定时循环        首次执行
    │
    ├─ 随机选择 DNS 服务器
    ├─ 遍历域名列表
    ├─ dig 解析 IP
    ├─ 更新 ipset (带 timeout)
    ├─ 输出快照到日志
    ├─ 检查日志大小
    ├─ 必要时轮转日志
    └─ sleep N 秒
</code></pre>

<hr />

<h1 id="优点分析">优点分析</h1>

<h2 id="1-性能优势">1. 性能优势</h2>

<table>
  <thead>
    <tr>
      <th>对比项</th>
      <th>传统方案</th>
      <th>本方案</th>
      <th>提升</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>规则数量</td>
      <td>19+N 条</td>
      <td>7 条</td>
      <td>减少 60%+</td>
    </tr>
    <tr>
      <td>匹配复杂度</td>
      <td>O(n)</td>
      <td>O(1)</td>
      <td>哈希查找</td>
    </tr>
    <tr>
      <td>内存占用</td>
      <td>~100 字节/规则</td>
      <td>哈希表</td>
      <td>节省 50%+</td>
    </tr>
    <tr>
      <td>动态更新</td>
      <td>需重建规则</td>
      <td>直接操作</td>
      <td>无需重建</td>
    </tr>
  </tbody>
</table>

<h2 id="2-可维护性">2. 可维护性</h2>

<p><strong>YAML 配置文件</strong></p>

<pre><code class="language-YAML">domains:
  - name: api.example.com
    desc: "生产环境 API"
  - name: api-dev.example.com
    desc: "开发环境 API"
</code></pre>

<p><strong>对比环境变量</strong></p>

<pre><code class="language-Bash">ZSKJ_WHITE_LIST_DOMAIN=api.example.com,api-dev.example.com
</code></pre>

<p><strong>优势</strong>：</p>

<ul>
  <li>
    <p>支持注释和描述</p>
  </li>
  <li>
    <p>结构清晰，易于理解</p>
  </li>
  <li>
    <p>diff 友好</p>
  </li>
  <li>
    <p>便于分组管理</p>
  </li>
</ul>

<h2 id="3-高可用性">3. 高可用性</h2>

<p><strong>守护进程容错</strong></p>

<pre><code class="language-Bash"># 单次 DNS 失败跳过该域名，继续下一个
if ! raw_ips=$(timeout 5 dig ...); then
  log_to_file "⚠️  $domain: dig 超时"
  continue  # 跳过，继续下一个
fi
</code></pre>

<p><strong>连接保护</strong></p>

<pre><code class="language-Bash"># 已建立的连接不受 IP 变化影响
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
</code></pre>

<h2 id="4-自动化运维">4. 自动化运维</h2>

<p><strong>IP 自动过期</strong></p>

<ul>
  <li>
    <p>使用 ipset timeout 特性</p>
  </li>
  <li>
    <p>长时间未更新的 IP 自动清理</p>
  </li>
  <li>
    <p>无需手动维护</p>
  </li>
</ul>

<p><strong>日志自动轮转</strong></p>

<ul>
  <li>
    <p>日志文件超过 10MB 自动轮转</p>
  </li>
  <li>
    <p>保留最近 5 份备份</p>
  </li>
  <li>
    <p>自动删除最老备份</p>
  </li>
</ul>

<h2 id="5-安全性">5. 安全性</h2>

<p><strong>默认拒绝策略</strong></p>

<pre><code class="language-Bash">iptables -P OUTPUT DROP
</code></pre>

<p><strong>配置验证</strong></p>

<pre><code class="language-Bash"># UPDATE_INTERVAL 最小值检查
if [ "$UPDATE_INTERVAL" -lt 30 ]; then
  UPDATE_INTERVAL=300
fi
</code></pre>

<p><strong>超时保护</strong></p>

<pre><code class="language-Bash"># DNS 查询超时 5 秒
timeout 5 dig +short "@$dns_server" "$domain"
</code></pre>

<hr />

<h1 id="缺点与局限">缺点与局限</h1>

<h2 id="1-仅支持-ipv4">1. 仅支持 IPv4</h2>

<p><strong>局限</strong>：当前版本只支持 IPv4 地址解析和白名单。</p>

<p><strong>解决方案</strong>：</p>

<ul>
  <li>
    <p>如需 IPv6 支持，需要增加 AAAA 记录解析</p>
  </li>
  <li>
    <p>创建对应的 ipset (hash:ip family inet6)</p>
  </li>
</ul>

<h2 id="2-依赖外部-dns">2. 依赖外部 DNS</h2>

<p><strong>局限</strong>：域名解析依赖外部 DNS 服务器，DNS 故障会影响解析。</p>

<p><strong>缓解措施</strong>：</p>

<ul>
  <li>
    <p>配置 8 个不同的 DNS 服务器</p>
  </li>
  <li>
    <p>随机选择降低单点故障风险</p>
  </li>
  <li>
    <p>已建立的连接不受影响</p>
  </li>
</ul>

<h2 id="3-配置文件依赖">3. 配置文件依赖</h2>

<p><strong>局限</strong>：必须存在配置文件才能运行。</p>

<p><strong>优势</strong>：</p>

<ul>
  <li>
    <p>强制规范化配置</p>
  </li>
  <li>
    <p>避免配置散落在环境变量中</p>
  </li>
  <li>
    <p>便于配置审计和版本控制</p>
  </li>
</ul>

<h2 id="4-需要特定容器能力">4. 需要特定容器能力</h2>

<p><strong>局限</strong>：修改 iptables 需要 CAP_NET_ADMIN 权限。</p>

<p><strong>Docker Compose 推荐配置</strong>：</p>

<pre><code class="language-YAML">services:
  app:
    cap_add:
      - NET_ADMIN    # 必需：操作 iptables 和 ipset
      - NET_RAW      # 可选：用于 ping 等 ICMP 工具
    # 不推荐使用 privileged: true（安全风险过大）
</code></pre>

<p><strong>注意</strong>：</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">NET_ADMIN</code> 是必需的，用于管理防火墙规则</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">NET_RAW</code> 是可选的，仅在需要 ICMP（ping）时需要</p>
  </li>
  <li>
    <p>使用 <code class="language-plaintext highlighter-rouge">cap_add</code> 而非 <code class="language-plaintext highlighter-rouge">privileged: true</code> 遵循最小权限原则</p>
  </li>
</ul>

<h2 id="5-单机方案">5. 单机方案</h2>

<p><strong>局限</strong>：当前方案为单机部署，不支持多主机同步。</p>

<p><strong>扩展方向</strong>：</p>

<ul>
  <li>
    <p>使用 etcd/consul 存储配置</p>
  </li>
  <li>
    <p>通过配置中心同步白名单</p>
  </li>
  <li>
    <p>实现集群统一管理</p>
  </li>
</ul>

<hr />

<h1 id="性能指标">性能指标</h1>

<h2 id="资源占用">资源占用</h2>

<table>
  <thead>
    <tr>
      <th>指标</th>
      <th>值</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>iptables 规则数</td>
      <td>7 条（固定）</td>
    </tr>
    <tr>
      <td>ipset 最大容量</td>
      <td>65536 个 IP</td>
    </tr>
    <tr>
      <td>内存占用</td>
      <td>~2-5 MB</td>
    </tr>
    <tr>
      <td>CPU 占用</td>
      <td>&lt; 1%（空闲时）</td>
    </tr>
    <tr>
      <td>磁盘占用</td>
      <td>~10 MB（日志）</td>
    </tr>
  </tbody>
</table>

<h2 id="性能测试">性能测试</h2>

<table>
  <thead>
    <tr>
      <th>场景</th>
      <th>传统方案</th>
      <th>本方案</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>100 个 IP 白名单</td>
      <td>100 条规则</td>
      <td>1 条规则</td>
    </tr>
    <tr>
      <td>添加新 IP</td>
      <td>需重建规则</td>
      <td>直接添加</td>
    </tr>
    <tr>
      <td>删除过期 IP</td>
      <td>需重建规则</td>
      <td>自动过期</td>
    </tr>
    <tr>
      <td>规则匹配时间</td>
      <td>O(n)</td>
      <td>O(1)</td>
    </tr>
  </tbody>
</table>

<h2 id="实际运行效果需要在容器内执行">实际运行效果（需要在容器内执行）</h2>

<pre><code class="language-Bash"># 进入容器
docker compose exec -it &lt;container_name&gt; bash

# 规则数量
$ iptables -L OUTPUT | wc -l
7

# ipset 统计
$ ipset list whitelist_domain
Name: whitelist_domain
Type: hash:ip
Size in memory: 824
References: 1
Number of entries: 9

# 日志轮转
$ ls -lh /var/log/iptables-whitelist/
-rw-r--r-- 1 root root 9.8M watcher.log
-rw-r--r-- 1 root root 9.9M watcher.log.1
</code></pre>

<hr />

<h1 id="常见问题">常见问题</h1>

<h2 id="q1-修改配置文件后如何生效">Q1: 修改配置文件后如何生效？</h2>

<p><strong>A</strong>: 重启容器</p>

<pre><code class="language-Bash"># 重启容器
docker compose restart
</code></pre>

<h2 id="q2-如何查看当前白名单">Q2: 如何查看当前白名单？</h2>

<p><strong>A</strong>: 进入容器后使用 ipset 命令</p>

<pre><code class="language-Bash"># 先进入容器
docker compose exec -it &lt;container_name&gt; bash

# 在容器内查看所有 ipset
ipset list

# 在容器内查看特定 ipset
ipset list whitelist_domain
ipset list whitelist_static
</code></pre>

<h2 id="q3-为什么某个-ip-无法访问">Q3: 为什么某个 IP 无法访问？</h2>

<p><strong>A</strong>: 排查步骤（需要在容器内执行）</p>

<pre><code class="language-Bash"># 1. 进入容器
docker compose exec -it &lt;container_name&gt; bash

# 2. 检查 IP 是否在白名单
ipset test whitelist_domain 192.168.1.100

# 3. 检查 iptables 规则
iptables -L OUTPUT -v -n

# 4. 查看日志
tail -f /var/log/iptables-whitelist/watcher.log
</code></pre>

<h2 id="q4-如何临时禁用某个域名">Q4: 如何临时禁用某个域名？</h2>

<p><strong>A</strong>: 从配置文件中删除或注释</p>

<pre><code class="language-YAML">domains:
  # - name: temp.example.com
  #   desc: "临时禁用"
</code></pre>

<h2 id="q5-支持-ipv6-吗">Q5: 支持 IPv6 吗？</h2>

<p><strong>A</strong>: 当前版本不支持 IPv6，需要扩展实现。</p>

<h2 id="q6-update_interval-设置多少合适">Q6: update_interval 设置多少合适？</h2>

<p><strong>A</strong>: 建议值</p>

<ul>
  <li>
    <p>生产环境：60-300 秒</p>
  </li>
  <li>
    <p>开发环境：30-60 秒</p>
  </li>
  <li>
    <p>最小值：30 秒（强制限制）</p>
  </li>
</ul>

<hr />

<h1 id="总结">总结</h1>

<p>本方案是一个<strong>生产级的 iptables 出站白名单管理系统</strong>，具有以下特点：</p>

<ul>
  <li>
    <p>✅ <strong>高性能</strong>：ipset O(1) 查找，比传统方案快 10-100 倍</p>
  </li>
  <li>
    <p>✅ <strong>高可用</strong>：守护进程容错，连接保护，自动故障恢复</p>
  </li>
  <li>
    <p>✅ <strong>易维护</strong>：YAML 配置文件，结构清晰，版本控制友好</p>
  </li>
  <li>
    <p>✅ <strong>自动化</strong>：IP 自动过期，日志自动轮转，无需人工干预</p>
  </li>
  <li>
    <p>✅ <strong>安全性</strong>：默认拒绝，最小权限，配置验证</p>
  </li>
</ul>

<p><strong>适用场景</strong>：</p>

<ul>
  <li>
    <p>云服务器出站流量控制</p>
  </li>
  <li>
    <p>容器环境网络隔离</p>
  </li>
  <li>
    <p>多租户 IP 白名单管理</p>
  </li>
  <li>
    <p>动态域名 IP 解析</p>
  </li>
</ul>

<p><strong>不适用场景</strong>：</p>

<ul>
  <li>
    <p>需要 IPv6 支持的环境（需扩展）</p>
  </li>
  <li>
    <p>集群统一管理（需配合配置中心）</p>
  </li>
  <li>
    <p>复杂的 L7 层访问控制（需使用 WAF/代理）</p>
  </li>
</ul>]]></content><author><name>看看你极限深蓝</name></author><category term="Docker" /><summary type="html"><![CDATA[docker iptables 出站白名单管理系统]]></summary></entry><entry><title type="html">Git 配置</title><link href="https://lzc6244.github.io//2026/03/01/Git-%E9%85%8D%E7%BD%AE.html" rel="alternate" type="text/html" title="Git 配置" /><published>2026-03-01T00:00:00+00:00</published><updated>2026-03-01T00:00:00+00:00</updated><id>https://lzc6244.github.io//2026/03/01/Git-%E9%85%8D%E7%BD%AE</id><content type="html" xml:base="https://lzc6244.github.io//2026/03/01/Git-%E9%85%8D%E7%BD%AE.html"><![CDATA[<h1 id="基本配置">基本配置</h1>

<p>查看当前 git 配置</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config <span class="nt">--list</span> <span class="nt">--local</span>
</code></pre></div></div>

<p>在当前项目为 git 配置 user 和 mail</p>

<p>样例如下</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config user.name <span class="s2">"xxx"</span>
git config user.email <span class="s2">"xxxx"</span>
</code></pre></div></div>

<h2 id="全局配置持久化保存账号密码-token-凭据-https">全局配置持久化保存账号密码 token 凭据 (https)</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config <span class="nt">--global</span> credential.helper store
</code></pre></div></div>

<p>使用带账号密码的命令 clone 仓库：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 模板命令</span>
git clone https://username:password@github.com/username/repository
</code></pre></div></div>

<p>使用带 token 的命令 clone 仓库：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># github 模板命令</span>
git clone https://token@github.com/username/repository

<span class="c"># gitlab 模板命令</span>
git clone https://oauth2:token@github.com/username/repository


<span class="c"># demo</span>
<span class="c"># github_pat_xxxxxx</span>
git clone https://github_pat_xxxxxx@github.com/LZC6244/maida_leetcode.git
</code></pre></div></div>

<h2 id="使用-ssh-clone-推荐">使用 ssh clone （推荐）</h2>

<blockquote class="tip">
多人混用机器推荐
</blockquote>

<ol>
  <li>
    <p>需要提前在 git 平台配置 ssh 公钥</p>
  </li>
  <li>
    <p>使用 ssh git clone 项目下来，如 <code class="language-plaintext highlighter-rouge">ssh://</code><code class="language-plaintext highlighter-rouge">git@xxx.com/demo.git</code> 或者 <code class="language-plaintext highlighter-rouge">ssh://git@ip:port/demo.git</code></p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 临时一次性指定 ssh 私钥路径</span>
<span class="nv">GIT_SSH_COMMAND</span><span class="o">=</span><span class="s2">"ssh -i ~/.ssh/gitlab_id_rsa"</span> 
git clone ssh://git@xxx.com/demo.git
   
<span class="c"># 临时一次性指定 ssh 私钥路径</span>
<span class="nv">GIT_SSH_COMMAND</span><span class="o">=</span><span class="s2">"ssh -i ~/.ssh/gitlab_id_rsa"</span> 
git clone ssh://git@ip:port/demo.git
</code></pre></div>    </div>
  </li>
  <li>
    <p>进入项目进行配置：<code class="language-plaintext highlighter-rouge">cd demo</code></p>
  </li>
  <li>
    <p>配置全局凭证，使用用户全局的 <code class="language-plaintext highlighter-rouge">~/.ssh/config</code>，写入如以下内容</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host xxx-git
  HostName domain/ip
  Port 22
  User git
  <span class="c"># 私钥路径</span>
  IdentityFile ~/.ssh/gitlab_id_rsa
  <span class="c"># 强制使用指定密钥</span>
  IdentitiesOnly <span class="nb">yes</span>  
</code></pre></div>    </div>

    <p>此时 git clone 命令为： <code class="language-plaintext highlighter-rouge">git clone git@xxx-git/demo.git</code> ，使用上述配置的别名</p>

    <p>git commit 等之前还需要进行下面的操作，配置用户名和邮箱</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config user.name <span class="s2">"xxx"</span>
git config user.email <span class="s2">"xxx@xxxx.com"</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>配置独立凭证，不使用用户全局的 <code class="language-plaintext highlighter-rouge">~/.ssh/config</code></p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 在项目根路径执行下面的命令</span>
   
<span class="c"># 配置当前项目的 SSH 命令：指定私钥</span>
git config core.sshCommand <span class="s2">"ssh -i ~/.ssh/你的私钥文件（如gitlab_id_rsa）"</span>
<span class="c"># 如果使用 git 地址为 ip:port ，需要指定端口</span>
git config core.sshCommand <span class="s2">"ssh -i ~/.ssh/你的私钥文件（如gitlab_id_rsa） -p 22"</span>
   
<span class="c"># 查看 .git/config 文件</span>
<span class="nb">cat</span> .git/config
</code></pre></div>    </div>

    <p>查看 .git/config 文件，<code class="language-plaintext highlighter-rouge">cat .git/config</code>，会看到类似下面的新增配置段</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>core]
    repositoryformatversion <span class="o">=</span> 0
    filemode <span class="o">=</span> <span class="nb">true
    </span>bare <span class="o">=</span> <span class="nb">false
    </span>logallrefupdates <span class="o">=</span> <span class="nb">true</span>
    <span class="c"># 新增的项目级配置</span>
    sshCommand <span class="o">=</span> ssh <span class="nt">-i</span> ~/.ssh/gitlab_id_rsa <span class="nt">-p</span> 22
<span class="o">[</span>remote <span class="s2">"origin"</span><span class="o">]</span>
    url <span class="o">=</span> ssh://git@ip:port/demo.git
    fetch <span class="o">=</span> +refs/heads/<span class="k">*</span>:refs/remotes/origin/<span class="k">*</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>后续使用：直接执行 git 命令即可，如：<code class="language-plaintext highlighter-rouge">git pull</code>、<code class="language-plaintext highlighter-rouge">git push</code></p>
  </li>
</ol>

<h3 id="可选将当前-https-clone-的项目更改为-ssh-地址">【可选】将当前 https clone 的项目更改为 ssh 地址</h3>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ssh 地址为：ssh://git@xxx.com/demo.git 或者 ssh://git@ip:port/demo.git</span>
<span class="k">git</span> <span class="s">remote</span> <span class="s">set-url</span> <span class="s">origin</span> <span class="s">ssh://git@ip:port/demo.git</span>

<span class="c1"># 确认是否更改成功</span>
<span class="s">git</span> <span class="s">remote</span> <span class="s">-v</span>
</code></pre></div></div>

<p><img src="/imgs/JeKyll/2026/2026-03-01-Git-配置-001.png" alt="" /></p>

<h1 id="配置代理">配置代理</h1>

<p>以下的 http://127.0.0.1:7078 请根据时间情况替换代理地址和端口</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 方案 1 ：临时在当前终端配置 http 和 https 代理</span>
<span class="c"># 设置 HTTP 代理</span>
<span class="nb">export </span><span class="nv">http_proxy</span><span class="o">=</span>http://127.0.0.1:7078
<span class="c"># 设置 HTTPS 代理</span>
<span class="nb">export </span><span class="nv">https_proxy</span><span class="o">=</span>http://127.0.0.1:7078





<span class="c"># 方案 2 ：为当前 git 项目配置代理</span>
git config http.proxy http://127.0.0.1:7078
git config https.proxy http://127.0.0.1:7078

<span class="c"># 为当前 git 项目取消配置代理</span>
git config <span class="nt">--unset</span> http.proxy
git config <span class="nt">--unset</span> https.proxy






<span class="c"># 方案 3 ：git 配置全局代理</span>
git config <span class="nt">--global</span> http.proxy http://127.0.0.1:7078
git config <span class="nt">--global</span> https.proxy http://127.0.0.1:7078

<span class="c"># git 取消配置全局代理</span>
git config <span class="nt">--global</span> <span class="nt">--unset</span> http.proxy
git config <span class="nt">--global</span> <span class="nt">--unset</span> https.proxy
</code></pre></div></div>

<h1 id="刷新远程分支列表">刷新远程分支列表</h1>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 此处以 origin 远程源（默认）为例</span>
git remote update origin <span class="nt">--prune</span>
</code></pre></div></div>

<p>参考文档</p>

<ul>
  <li><a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#keeping-your-personal-access-tokens-secure">github 管理个人访问令牌</a></li>
</ul>]]></content><author><name>看看你极限深蓝</name></author><category term="Tools" /><category term="Git" /><summary type="html"><![CDATA[基本配置]]></summary></entry><entry><title type="html">docker compose 网络数达到上限解决方案</title><link href="https://lzc6244.github.io//2026/03/01/docker-compose-%E7%BD%91%E7%BB%9C%E6%95%B0%E8%BE%BE%E5%88%B0%E4%B8%8A%E9%99%90%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.html" rel="alternate" type="text/html" title="docker compose 网络数达到上限解决方案" /><published>2026-03-01T00:00:00+00:00</published><updated>2026-03-01T00:00:00+00:00</updated><id>https://lzc6244.github.io//2026/03/01/docker-compose-%E7%BD%91%E7%BB%9C%E6%95%B0%E8%BE%BE%E5%88%B0%E4%B8%8A%E9%99%90%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88</id><content type="html" xml:base="https://lzc6244.github.io//2026/03/01/docker-compose-%E7%BD%91%E7%BB%9C%E6%95%B0%E8%BE%BE%E5%88%B0%E4%B8%8A%E9%99%90%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.html"><![CDATA[<h1 id="方案">方案</h1>

<p>以下为不需要重启 docker 增加网络数上限方案：指定新的子网</p>

<p>参考样例：</p>

<pre><code class="language-YAML"># 默认可用内网网段：172.16.0.0/12 ; 192.168.0.0/16
# 下面是几个划分小网段的示例，不用过度分配网段避免资源浪费


# 可用主机地址：172.17.0.1 ～ 172.17.0.14
# default 表示随着 docker compose 项目名称自动命名 docker 网络名称
networks:
  default:
    ipam:
      config:
        - subnet: 172.17.0.0/28 


# or        


# 可用主机地址：172.17.0.17 ～ 172.17.0.30        
networks:
  target-network:
    ipam:
      config:
        - subnet: 172.17.0.16/28
</code></pre>

<p><br /></p>

<blockquote class="info">
优化：子网可以指定如【172.17.0.0/28】和【172.17.0.16/28】等小范围子网，合理安排资源
</blockquote>

<h1 id="参考文档">参考文档</h1>
<ul>
  <li><a href="/2025/02/22/docker-%E6%9F%A5%E7%9C%8B%E5%B7%B2%E6%9C%89%E7%BD%91%E7%BB%9C%E7%9A%84%E5%86%85%E7%BD%91-ip.html">docker 查看已有网络的内网 ip</a></li>
  <li>https://docs.docker.com/compose/compose-file/06-networks/#ipam</li>
</ul>]]></content><author><name>看看你极限深蓝</name></author><category term="Docker" /><summary type="html"><![CDATA[方案]]></summary></entry><entry><title type="html">配置 https://ip:port 访问</title><link href="https://lzc6244.github.io//2026/02/07/%E9%85%8D%E7%BD%AE-https-ip-port-%E8%AE%BF%E9%97%AE.html" rel="alternate" type="text/html" title="配置 https://ip:port 访问" /><published>2026-02-07T00:00:00+00:00</published><updated>2026-02-07T00:00:00+00:00</updated><id>https://lzc6244.github.io//2026/02/07/%E9%85%8D%E7%BD%AE-https-ip-port-%E8%AE%BF%E9%97%AE</id><content type="html" xml:base="https://lzc6244.github.io//2026/02/07/%E9%85%8D%E7%BD%AE-https-ip-port-%E8%AE%BF%E9%97%AE.html"><![CDATA[<h1 id="需求">需求</h1>

<p>为 <code class="language-plaintext highlighter-rouge">http://ip:port</code> 服务配置 https 方式访问：<code class="language-plaintext highlighter-rouge">https://ip:port</code></p>

<p>注：可在 nginx 层面将 http 流量转到 https</p>

<h1 id="方案">方案</h1>

<p>本文选择【方案一：使用自签名 https 证书 + 手动信任】</p>

<p>【方案二：使用内网域名 + 自建私有 CA】<span style="color:orange">需要客户端手动安装证书来信任</span></p>

<p>根据实际场景选择方案即可，本文不赘述</p>

<h2 id="方案一使用自签名-https-证书--手动信任">方案一：使用自签名 https 证书 + 手动信任</h2>

<ol>
  <li>
    <p>生成 https 证书</p>

    <pre><code class="language-Shell"># 生成私钥
openssl genrsa -out server.key 2048

# 生成证书（CN 填部署服务器的 IP 地址）
openssl req -new -x509 -key server.key -out server.crt -days 365 -subj "/CN=192.168.1.100"
</code></pre>
  </li>
  <li>
    <p>nginx 配置</p>

    <pre><code class="language-Nginx">server {
    listen 443 ssl;
    server_name 192.168.1.100;  # 或你的公网 IP

    ssl_certificate /path/to/server.crt;
    ssl_certificate_key /path/to/server.key;

    location / {
        proxy_pass http://127.0.0.1:8080;  # 转发到你的后端 HTTP 服务
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
</code></pre>
  </li>
  <li>
    <p>重新加载 nginx 配置：<code class="language-plaintext highlighter-rouge">nginx -s reload</code></p>
  </li>
  <li>
    <p>（可选）HTTP 自动跳转到 HTTPS，新增 server 块</p>

    <pre><code class="language-Nginx">server {
    listen 80;
    server_name 192.168.1.100;
    return 301 https://192.168.1.100$request_uri;
}
</code></pre>
  </li>
</ol>

<h2 id="方案二使用内网域名--自建私有-ca">方案二：使用内网域名 + 自建私有 CA</h2>

<p>步骤：</p>

<ol>
  <li>
    <p>搭建一个私有 CA（如用 mkcert 或 Smallstep）</p>
  </li>
  <li>
    <p>用该 CA 为内网服务（如 myapp.local）签发证书</p>
  </li>
  <li>
    <p>将私有 CA 的根证书安装到所有客户端（开发机、手机等）</p>
  </li>
</ol>

<p>客户端/浏览器将不再提示不安全的证书</p>]]></content><author><name>看看你极限深蓝</name></author><category term="Other" /><summary type="html"><![CDATA[需求]]></summary></entry><entry><title type="html">pgsql 数据结构设计样例-文件夹方案设计</title><link href="https://lzc6244.github.io//2026/01/31/pgsql-%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%9F%A5%E8%AF%A2%E8%A1%A8%E8%AE%BE%E8%AE%A1.html" rel="alternate" type="text/html" title="pgsql 数据结构设计样例-文件夹方案设计" /><published>2026-01-31T00:00:00+00:00</published><updated>2026-01-31T00:00:00+00:00</updated><id>https://lzc6244.github.io//2026/01/31/pgsql-%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%9F%A5%E8%AF%A2%E8%A1%A8%E8%AE%BE%E8%AE%A1</id><content type="html" xml:base="https://lzc6244.github.io//2026/01/31/pgsql-%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%9F%A5%E8%AF%A2%E8%A1%A8%E8%AE%BE%E8%AE%A1.html"><![CDATA[<h1 id="文件夹方案设计">文件夹方案设计</h1>

<h2 id="概述">概述</h2>

<p><code class="language-plaintext highlighter-rouge">应用</code>与文件夹属于树形结构关系</p>

<ul>
  <li>
    <p>文件夹可嵌套子文件夹</p>
  </li>
  <li>
    <p>文件夹下可包含<code class="language-plaintext highlighter-rouge">应用</code>或子文件夹</p>
  </li>
  <li>
    <p>应用可以不归属于任意文件夹</p>
  </li>
</ul>

<h3 id="方案对比">方案对比</h3>

<h4 id="路径枚举-path-enumeration--ltree">路径枚举 (Path Enumeration / LTREE)</h4>

<p>在文件夹表中增加一个 <code class="language-plaintext highlighter-rouge">path</code> 字段，存储从根节点到当前节点的完整路径（如 <code class="language-plaintext highlighter-rouge">1.2.5</code>）。PostgreSQL 官方提供了一个强大的扩展插件 <code class="language-plaintext highlighter-rouge">ltree</code>。</p>

<p>表结构： <code class="language-plaintext highlighter-rouge">path ltree</code> 索引字段。</p>

<p>查询是否可删： 检查是否存在 <code class="language-plaintext highlighter-rouge">path &lt;@ '1.2.4'</code>（表示路径以 1.2.4 开头的文件夹）关联的应用。</p>

<p>优点： 查询极其简单，无需递归，性能极高。</p>

<p>缺点： 移动文件夹（修改父级）时，需要递归更新所有子节点的 <code class="language-plaintext highlighter-rouge">path</code>。</p>

<h4 id="闭包表-closure-table">闭包表 (Closure Table)</h4>

<p>创建一张独立的关联表 <code class="language-plaintext highlighter-rouge">folder_relations</code>，记录树中所有节点之间的祖先-后代关系（不仅仅是父子，而是跨级记录）。</p>

<p>表结构： <code class="language-plaintext highlighter-rouge">ancestor_id</code>, <code class="language-plaintext highlighter-rouge">descendant_id</code>, <code class="language-plaintext highlighter-rouge">depth</code>。</p>

<p>查询是否可删： 只需要一条简单的 Join：<code class="language-plaintext highlighter-rouge">SELECT 1 FROM apps a JOIN folder_relations r ON a.folder_id = r.descendant_id WHERE r.ancestor_id = 4 LIMIT 1</code>。</p>

<p>优点： 彻底消除递归，在处理极深、极广的树时性能最稳。</p>

<p>缺点： 存储开销大（$N$ 个节点可能产生 $N^2$ 条关系记录），维护逻辑复杂。</p>

<h4 id="嵌套集合-nested-sets">嵌套集合 (Nested Sets)</h4>

<p>通过两个数字 <code class="language-plaintext highlighter-rouge">lft</code> (左值) 和 <code class="language-plaintext highlighter-rouge">rgt</code> (右值) 标记节点的范围。子节点的范围一定在父节点的范围内。</p>

<p>表结构： <code class="language-plaintext highlighter-rouge">lft INTEGER, rgt INTEGER</code>。</p>

<p>查询是否可删： 查找 <code class="language-plaintext highlighter-rouge">folder_id</code> 对应的应用，其文件夹的 <code class="language-plaintext highlighter-rouge">lft</code> 必须在目标文件夹的 <code class="language-plaintext highlighter-rouge">[lft, rgt]</code> 之间。</p>

<p>优点： 极其擅长查询整个子树。</p>

<p>缺点： 插入或移动节点会导致全表大量数据的 <code class="language-plaintext highlighter-rouge">lft/rgt</code> 重新计算，写性能很差。</p>

<h4 id="方案横评">方案横评</h4>

<table>
  <thead>
    <tr>
      <th>维度</th>
      <th>递归 CTE (邻接表)</th>
      <th>路径枚举 (LTREE)</th>
      <th>闭包表 (Closure)</th>
      <th>嵌套集合 (Nested Sets)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>查询子树性能</td>
      <td>中（依赖树深度）</td>
      <td>高</td>
      <td>极高</td>
      <td>极高</td>
    </tr>
    <tr>
      <td>判定可删(短路)</td>
      <td>较好</td>
      <td>极好</td>
      <td>极好</td>
      <td>极好</td>
    </tr>
    <tr>
      <td>增删改性能</td>
      <td>极高（只改父ID）</td>
      <td>中（需更新子路径）</td>
      <td>低（需维护关联表）</td>
      <td>极低（需重算全表）</td>
    </tr>
    <tr>
      <td>存储开销</td>
      <td>极小</td>
      <td>中</td>
      <td>高</td>
      <td>小</td>
    </tr>
    <tr>
      <td>实现复杂度</td>
      <td>简单（纯 SQL）</td>
      <td>中（需插件支持）</td>
      <td>复杂（需触发器/业务逻辑）</td>
      <td>极复杂</td>
    </tr>
    <tr>
      <td>应用场景</td>
      <td>通用型/频繁重命名</td>
      <td>目录树/文件系统</td>
      <td>深度社交关系/超大规模树</td>
      <td>极少变动的分类目录</td>
    </tr>
  </tbody>
</table>

<h4 id="结论">结论</h4>

<p>拟定采用 PostgreSQL 的递归 CTE（WITH RECURSIVE）实现树形结构的查询</p>

<h3 id="文件夹查询">文件夹查询</h3>

<ul>
  <li>
    <p>需要返回文件夹是否可删除标志字段给前端，如：can_delete</p>
  </li>
  <li>
    <p>查询文件夹下所有子孙节点（含应用 / 子文件夹）</p>
  </li>
  <li>
    <p>查询<code class="language-plaintext highlighter-rouge">应用</code>所属的完整路径</p>
  </li>
</ul>

<p>查询限制</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">应用</code>和文件夹是单独的界面（独立的接口，各自翻页）或者混合展示（各自查询 page_size 条，根据更新时间倒排组合应用和文件夹数据，记录应用和文件夹的更新时间，翻页时根据本次查询得到应用和文件夹的更新时间或者 None 进行查询，寻找早于此次应用和文件夹的更新时间的数据）</p>
  </li>
  <li>
    <p>返回的数据按照更新时间倒序</p>
  </li>
  <li>
    <p>条数限制：暂定 100</p>
  </li>
</ul>

<h3 id="文件夹更新">文件夹更新</h3>

<p>仅在新增或者删除<code class="language-plaintext highlighter-rouge">应用/文件夹</code>时同步更新当前文件夹的更新时间</p>

<p>本期设计不联动更新上级文件夹更新时间</p>

<h3 id="文件夹删除">文件夹删除</h3>

<p>仅允许删除不含<code class="language-plaintext highlighter-rouge">应用</code>的空文件夹，允许含有空文件夹嵌套</p>

<h1 id="应用文件夹方案设计">应用文件夹方案设计</h1>

<h2 id="简易-demo">简易 demo</h2>

<h3 id="数据构造">数据构造</h3>

<p>新建数据库 test ，然后建表进行验证</p>

<p>外键的使用需要权衡，考虑高并发场景不建议使用外键</p>

<p><strong><del>PostgreSQL 外键的默认行为是 RESTRICT，具体表现为：</del></strong></p>

<ul>
  <li>
    <p><del>ON DELETE RESTRICT：如果尝试删除被引用的文件夹（folders 表中的记录），但该文件夹仍被子文件夹（parent_id 关联）或应用（apps 表的 folder_id 关联）引用，数据库会直接拒绝删除操作，并抛出外键约束冲突错误。</del></p>
  </li>
  <li>
    <p><del>ON UPDATE RESTRICT：如果尝试修改被引用的 folders.id（主键），但该 ID 仍被其他记录引用，同样会被拒绝。</del></p>
  </li>
</ul>

<p>```Plain Text
– 1. 文件夹表：自关联结构
CREATE TABLE folders (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    parent_id INTEGER REFERENCES folders(id),
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);</p>

<p>– 2. 应用表：关联文件夹
CREATE TABLE apps (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    folder_id INTEGER REFERENCES folders(id),
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);</p>

<p>– 为常用查询路径建立索引
CREATE INDEX idx_folders_parent ON folders(parent_id);
CREATE INDEX idx_apps_folder ON apps(folder_id);</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>


```Plain Text
-- 插入文件夹（5级嵌套）
INSERT INTO folders (id, name, parent_id) VALUES
(1, '研发部', NULL),         -- Level 1
(2, '中间件组', 1),          -- Level 2
(3, '数据库组', 2),          -- Level 3
(4, 'PostgreSQL小组', 3),    -- Level 4
(5, '插件开发支队', 4),      -- Level 5 (包含应用：不可删)
(6, '备份脚本支队', 4),      -- Level 5 (空文件夹：可删)
(7, '废弃归档区', NULL),     -- Level 1 (空文件夹：可删)
(8, '嵌套空目录A', 7),       -- Level 2
(9, '嵌套空目录B', 8);       -- Level 3

-- 插入应用
INSERT INTO apps (name, folder_id) VALUES
('看板应用', NULL),          -- 不属于任何文件夹
('监控系统', 1),             -- 研发部下的应用
('Pg_Stat_Tool', 5),         -- 5层深处的应用
('Test_Runner', 5),          -- 5层深处的应用（用于测试同级重名约束）
('video', 4),
('music', 4);
</code></pre></div></div>

<h3 id="查询语句">查询语句</h3>

<h4 id="查询当前文件夹下包含的应用和文件夹">查询当前文件夹下包含的应用和文件夹</h4>

<h4 id="查询树形应用和文件夹">查询树形应用和文件夹</h4>

<p>递归查询指定文件夹下的所有子文件夹</p>

<p>并关联查出这些文件夹下的所有应用</p>

<p>```Plain Text
WITH RECURSIVE sub_tree AS (
    – 初始节点：指定查询的文件夹
    SELECT id, name, parent_id, 1 as level
    FROM folders
    WHERE id = 1  – 假设查询【研发部】</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>UNION ALL

-- 递归下钻
SELECT f.id, f.name, f.parent_id, st.level + 1
FROM folders f
INNER JOIN sub_tree st ON f.parent_id = st.id ) -- 汇总结果：包含子文件夹信息和它们关联的应用 SELECT 'folder' as type, id, name, parent_id, level FROM sub_tree UNION ALL SELECT 'app' as type, a.id, a.name, a.folder_id, st.level FROM apps a JOIN sub_tree st ON a.folder_id = st.id; ```
</code></pre></div></div>

<h4 id="判断文件夹是否可删">判断文件夹是否可删</h4>

<p>查询指定文件夹 id 是否存在应用，返回文件夹是否可删除标志</p>

<p>知识库同理</p>

<p>短路判定：文件夹是否可删除</p>

<p>只要递归搜索到该文件夹或其任意深度的子文件夹中存在 1条应用记录</p>

<p>立即停止递归返回</p>

<p><code class="language-plaintext highlighter-rouge">Plain Text
SELECT NOT EXISTS (
    -- 外层 EXISTS 保证了只要内层返回一行，整个查询立即停止
    SELECT 1 
    FROM (
        WITH RECURSIVE folder_tree AS (
            -- 只查询 ID，内存占用极小
            SELECT id FROM folders WHERE id = 4
            UNION ALL
            SELECT f.id FROM folders f
            JOIN folder_tree ft ON f.parent_id = ft.id
        )
        SELECT 1 FROM folder_tree ft
        -- 核心：将应用检查放在这里
        -- 数据库会针对每一个查到的 ft.id 去 apps 表索引里探测
        WHERE EXISTS (SELECT 1 FROM apps a WHERE a.folder_id = ft.id)
        -- 一旦找到一个有应用的文件夹，立即返回 1 给外层 EXISTS
        LIMIT 1
    ) search_result
) AS is_deletable;
</code></p>

<h1 id="参考文档">参考文档</h1>

<ul>
  <li>https://www.postgresql.org/docs/15/queries-with.html</li>
</ul>]]></content><author><name>看看你极限深蓝</name></author><category term="Database" /><category term="PostgreSQL" /><summary type="html"><![CDATA[文件夹方案设计]]></summary></entry><entry><title type="html">dozzle - web 可视化 docker 日志</title><link href="https://lzc6244.github.io//2026/01/24/dozzle-web-%E5%8F%AF%E8%A7%86%E5%8C%96-docker-%E6%97%A5%E5%BF%97.html" rel="alternate" type="text/html" title="dozzle - web 可视化 docker 日志" /><published>2026-01-24T00:00:00+00:00</published><updated>2026-01-24T00:00:00+00:00</updated><id>https://lzc6244.github.io//2026/01/24/dozzle-web-%E5%8F%AF%E8%A7%86%E5%8C%96-docker-%E6%97%A5%E5%BF%97</id><content type="html" xml:base="https://lzc6244.github.io//2026/01/24/dozzle-web-%E5%8F%AF%E8%A7%86%E5%8C%96-docker-%E6%97%A5%E5%BF%97.html"><![CDATA[<h1 id="介绍">介绍</h1>

<p>Dozzle 是由 Docker OSS 赞助的开源项目。它是一个日志查看器，旨在简化容器的监控和调试。这个轻量级的、基于 Web 的应用程序通过直观的用户界面提供实时日志流、过滤和搜索功能。</p>

<p>开源地址：https://github.com/amir20/dozzle</p>

<h1 id="部署">部署</h1>

<h2 id="证书管理">证书管理</h2>

<p>公网暴露 dozzle agent 节点建议更改默认证书</p>

<p>不然别人指定明确的 agent ip 和 端口能够访问到该 agent 节点信息</p>

<p>官方证书生成命令</p>

<pre><code class="language-TOML">openssl genpkey -algorithm Ed25519 -out key.pem
openssl req -new -key key.pem -out request.csr -subj "/C=US/ST=California/L=San Francisco/O=My Company"
openssl x509 -req -in request.csr -signkey key.pem -out cert.pem -days 365
</code></pre>

<p>参考文档：https://dozzle.dev/guide/agent</p>

<h2 id="docker-compose">docker compose</h2>

<p><br /></p>

<h3 id="独立-docker-模式">独立 docker 模式</h3>

<p>参考文档：https://dozzle.dev/guide/getting-started</p>

<h3 id="agent-代理模式">agent 代理模式</h3>

<p>通过 agent 代理模式可以方便查看其他机器节点的 docker 日志</p>

<p>先启动 agent 节点再启动 master ui 节点</p>

<pre><code class="language-YAML">name: dozzle-master

services:
  dozzle:
    image: amir20/dozzle:latest
    restart: always
    environment:
      DOZZLE_REMOTE_AGENT: agent节点ip1:7007,agent节点ip2:7007
      DOZZLE_AUTH_PROVIDER: simple
      DOZZLE_CERT: /certs/cert.pem
      DOZZLE_KEY: /certs/key.pem
    volumes:
      - ./users-master.yml:/data/users.yml
      - ./certs:/certs
    ports:
      - 8006:8080 # Dozzle UI port

networks:
  default:
    ipam:
      config:
        - subnet: 172.18.100.0/28
</code></pre>

<pre><code class="language-YAML">name: dozzle-agent

services:
  dozzle:
    image: amir20/dozzle:latest
    command: agent
    restart: always
    healthcheck:
      test: ["CMD", "/dozzle", "healthcheck"]
      interval: 5s
      retries: 5
      start_period: 5s
      start_interval: 5s
    environment:
      DOZZLE_HOSTNAME: 自定义机器显示名称
      DOZZLE_CERT: /certs/cert.pem
      DOZZLE_KEY: /certs/key.pem
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./config/certs:/certs
    ports:
      - 7007:7007

networks:
  default:
    ipam:
      config:
        - subnet: 172.18.100.16/28
</code></pre>

<h1 id="用户配置文件样例">用户配置文件样例</h1>

<p>users-master.yml 样例如下</p>

<pre><code class="language-YAML"># 无需重启，直接往下面加用户就行
# 无需重启，更新用户重新登录即可生效

users:
    admin:
        email: ""
        name: 管理员
        password: xxxxxxxxxxxxxxxxxxxxxxxx
        filter: ""
        roles: ""

    guest:
        email: ""
        name: 访客
        password: xxxxxxxxxxxxxxxxxxxxxxxxxxx
        filter: "name=k8s_*"
        roles: download
</code></pre>

<h1 id="账号管理">账号管理</h1>

<p>添加用户无需重启 master 服务</p>

<p>用户权限更新，该用户重新登录即可生效</p>

<p>用户验证参考文档：https://dozzle.dev/guide/authentication#setting-specific-roles-for-users</p>

<ol>
  <li>
    <p>在项目路径下执行下面生成密码命令</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -it --rm 
  -v key.pem:/certs/key.pem 
  -v cert.pem:/certs/cert.pem 
  amir20/dozzle:v8.14.9 
  generate --name 用户显示名称 
  --password 密码 
  --user-roles download 
  账号英文名称
</code></pre></div>    </div>

    <p>上面的命令挂载 key.pem 和 cert.pem 指定了自定义证书</p>
  </li>
  <li>
    <p>将生成的账号密码加入当前项目的 <code class="language-plaintext highlighter-rouge">compose/config/users-master.yml</code> 文件中</p>

    <p><img src="/imgs/JeKyll/2026/2026-01-24-dozzle-web-可视化-docker-日志-001.png" alt="" /></p>
  </li>
  <li>
    <p>根据实际情况配置用户对 docker 容器的可见权限</p>

    <p>如：配置用户只能访问 sqlbot_* 的 docker 容器</p>

    <pre><code class="language-TOML">filter: "name=k8s_*"
</code></pre>
  </li>
</ol>

<p>配置多个则类似如下</p>

<pre><code class="language-TOML">filter: "name=k8s_*,name=name2,name=name3"
</code></pre>

<p><img src="/imgs/JeKyll/2026/2026-01-24-dozzle-web-可视化-docker-日志-002.png" alt="" /></p>

<h1 id="参考">参考</h1>

<ol>
  <li><a href="https://dozzle.dev/guide/filters">过滤容器</a></li>
</ol>]]></content><author><name>看看你极限深蓝</name></author><category term="Docker" /><summary type="html"><![CDATA[介绍]]></summary></entry><entry><title type="html">minio</title><link href="https://lzc6244.github.io//2026/01/24/minio.html" rel="alternate" type="text/html" title="minio" /><published>2026-01-24T00:00:00+00:00</published><updated>2026-01-24T00:00:00+00:00</updated><id>https://lzc6244.github.io//2026/01/24/minio</id><content type="html" xml:base="https://lzc6244.github.io//2026/01/24/minio.html"><![CDATA[<h1 id="部署">部署</h1>

<h2 id="docker-compose">docker compose</h2>

<p>示例如下</p>

<pre><code class="language-YAML">name: minio

services:

  minio:
    image: minio/minio:RELEASE.2024-04-18T19-09-19Z
    restart: always
    environment:
      MINIO_ROOT_USER: minioadmin(minio_admin)(xxxxx)
      MINIO_ROOT_PASSWORD: xxxxxxxx
    ports:
      - 9001:9001
      - 9000:9000
    volumes:
      - ../volumes/minio/data:/minio_data
      - ../volumes/minio/entrypoint.sh:/entrypoint.sh
    # command: minio server /minio_data --console-address ":9001"
    entrypoint: [ "/bin/bash" ]
    command: [ "-x", "-c", "/entrypoint.sh" ]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3

networks:
  default:
    ipam:
      config:
        - subnet: 172.18.5.0/28
    driver: bridge
</code></pre>

<p>在 minio 启动时自动创建指定 bucket 的脚本示例如下</p>

<p>如不需要自动创建，可以在 docker compose 中将启动命令更改为使用 <code class="language-plaintext highlighter-rouge">command: minio server /minio_data --console-address ":9001"</code></p>

<pre><code class="language-Bash">#!/bin/sh

# 后台启动 minio 服务
# /usr/bin/docker-entrypoint.sh minio server /data --console-address ":9001" &amp;
minio server /minio_data --console-address ":9001" &amp;

# 等待 minio 服务就绪
wait_for_minio() {
  echo "等待 minio 服务启动..."
  while ! mc config host add myminio http://localhost:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" --api S3v4 &gt; /dev/null 2&gt;&amp;1; do
    sleep 2
  done
  echo "minio 服务已就绪"
}

# 创建私有 bucket ，忽略重名
create_private_bucket() {
  local bucket_name="*$1*"
  if ! mc ls myminio/"$bucket_name" &gt; /dev/null 2&gt;&amp;1; then
    echo "创建私有 bucket: $bucket_name"
    mc mb myminio/"$bucket_name"
  else
    echo "跳过已存在私有 bucket: $bucket_name"
  fi
}

# 创建公开 bucket ，忽略重名
create_public_read_bucket() {
  local bucket_name="*$1*"
  if ! mc ls myminio/"$bucket_name" &gt; /dev/null 2&gt;&amp;1; then
    echo "创建公开根路径私有 bucket: $bucket_name"
    mc mb myminio/"$bucket_name"
    mc anonymous set download myminio/"$bucket_name"
    echo "已设置根路径公开可读: $bucket_name"
  else
    echo "跳过已存在公开根路径私有 bucket: $bucket_name"
  fi
}

# 执行初始化流程
wait_for_minio
create_private_bucket "$MINIO_BUCKETS_PRIVATE"
create_public_read_bucket MINIO_BUCKETS_PUBLIC_READ

# 等待 minio 服务主进程（保持容器运行）
wait $!
</code></pre>

<h1 id="nginx-代理转发">nginx 代理转发</h1>

<p>配置示例</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># nginx 配置参考地址：https://min.io/docs/minio/linux/integrations/setup-nginx-proxy-with-minio.html
</span>
<span class="n">upstream</span> <span class="n">minio</span>-<span class="n">s3</span> {
     <span class="n">server</span> <span class="n">master</span>:<span class="m">19000</span> <span class="n">weight</span>=<span class="m">1</span>;
    <span class="c">#  server slave1:19000 weight=4;
</span>}

<span class="n">server</span> {

    <span class="n">listen</span> <span class="m">29000</span>;

    <span class="c"># Allow special characters in headers
</span>    <span class="n">ignore_invalid_headers</span> <span class="n">off</span>;
    <span class="c"># Allow any size file to be uploaded.
</span>    <span class="c"># Set to a value such as 1000m; to restrict file size to a specific value
</span>    <span class="n">client_max_body_size</span> <span class="m">0</span>;
    <span class="c"># Disable buffering
</span>    <span class="n">proxy_buffering</span> <span class="n">off</span>;
    <span class="n">proxy_request_buffering</span> <span class="n">off</span>;

    <span class="n">location</span> / {

      <span class="n">proxy_set_header</span> <span class="n">Host</span> $<span class="n">http_host</span>;
      <span class="n">proxy_set_header</span> <span class="n">X</span>-<span class="n">Real</span>-<span class="n">IP</span> $<span class="n">remote_addr</span>;
      <span class="n">proxy_set_header</span> <span class="n">X</span>-<span class="n">Forwarded</span>-<span class="n">For</span> $<span class="n">proxy_add_x_forwarded_for</span>;
      <span class="n">proxy_set_header</span> <span class="n">X</span>-<span class="n">Forwarded</span>-<span class="n">Proto</span> $<span class="n">scheme</span>;

      <span class="c"># Default is HTTP/1, keepalive is only enabled in HTTP/1.1
</span>      <span class="n">proxy_http_version</span> <span class="m">1</span>.<span class="m">1</span>;
      <span class="n">proxy_set_header</span> <span class="n">Connection</span> <span class="s2">""</span>;
      <span class="n">chunked_transfer_encoding</span> <span class="n">off</span>;

      <span class="n">proxy_pass</span> <span class="n">http</span>://<span class="n">minio</span>-<span class="n">s3</span>; <span class="c"># This uses the upstream directive definition to load balance
</span>
      <span class="c"># 节点异常配置
</span>      <span class="n">proxy_connect_timeout</span> <span class="m">30</span><span class="n">s</span>;
      <span class="n">proxy_read_timeout</span> <span class="m">60</span><span class="n">s</span>;
      <span class="n">proxy_send_timeout</span> <span class="m">60</span><span class="n">s</span>;
      <span class="n">proxy_next_upstream</span> <span class="n">error</span> <span class="n">timeout</span> <span class="n">invalid_header</span> <span class="n">http_500</span> <span class="n">http_502</span> <span class="n">http_503</span> <span class="n">http_504</span>;
      <span class="n">proxy_next_upstream_tries</span> <span class="m">2</span>;
      
    }
}
</code></pre></div></div>

<h1 id="特定需求">特定需求</h1>

<h2 id="为指定-bucket-配置公开读鉴权写">为指定 bucket 配置公开读，鉴权写</h2>

<p>登入 minio web 控制台，如：http://ip:port(9001)/</p>

<p>进入 buckets 管理界面，选择要操作的 bucket，先将其改/确认其为 private 私有</p>

<p><img src="/imgs/JeKyll/2026/2026-01-24-minio-005.png" alt="" /></p>

<p><img src="/imgs/JeKyll/2026/2026-01-24-minio-004.png" alt="" /></p>

<p><img src="/imgs/JeKyll/2026/2026-01-24-minio-002.png" alt="" /></p>

<p>添加一条匿名访问规则，允许访问该 bucket 全部路径，配置为 readonly</p>

<p><img src="/imgs/JeKyll/2026/2026-01-24-minio-006.png" alt="" /></p>

<p><img src="/imgs/JeKyll/2026/2026-01-24-minio-003.png" alt="" /></p>

<h3 id="验证">验证</h3>

<h4 id="发包工具apipost验证">发包工具（apipost）验证</h4>

<p><img src="/imgs/JeKyll/2026/2026-01-24-minio-001.png" alt="" /></p>

<h4 id="curl-验证">curl 验证</h4>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">--location</span> <span class="nt">--request</span> PUT <span class="s1">'http://ip:port/bucket_name/3.png'</span> 
<span class="nt">--header</span> <span class="s1">'Content-Type: application/octet-stream'</span> 
<span class="nt">--data-binary</span> <span class="s1">'@C:UsersxxxxDownloads3.png'</span>
</code></pre></div></div>

<h1 id="参考文档">参考文档</h1>

<ul>
  <li><a href="https://min.io/docs/minio/linux/integrations/setup-nginx-proxy-with-minio.html">nginx 配置参考地址</a></li>
</ul>]]></content><author><name>看看你极限深蓝</name></author><category term="Other" /><summary type="html"><![CDATA[部署]]></summary></entry><entry><title type="html">xinference 部署</title><link href="https://lzc6244.github.io//2026/01/24/xinference-%E9%83%A8%E7%BD%B2.html" rel="alternate" type="text/html" title="xinference 部署" /><published>2026-01-24T00:00:00+00:00</published><updated>2026-01-24T00:00:00+00:00</updated><id>https://lzc6244.github.io//2026/01/24/xinference-%E9%83%A8%E7%BD%B2</id><content type="html" xml:base="https://lzc6244.github.io//2026/01/24/xinference-%E9%83%A8%E7%BD%B2.html"><![CDATA[<h1 id="概要">概要</h1>

<p>xinference 版本：0.16.1</p>

<h2 id="权限">权限</h2>

<p>目前，Xinference 内部定义了以下几个接口权限：</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">models:list</code>: 获取模型列表和信息的权限。</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">models:read</code>: 使用模型的权限。</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">models:register</code>: 注册模型的权限。</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">models:unregister</code>: 取消注册模型的权限。</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">models:start</code>: 启动模型的权限。</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">models:stop</code>: 停止模型的权限。</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">admin</code>: 管理员拥有所有接口的权限。</p>
  </li>
</ul>

<h2 id="配置说明">配置说明</h2>

<p>目录结构大致如下</p>

<p>```Plain Text
.
├── commands
│   ├── auth_config.json
│   ├── entrypoint.sh
├── docker-compose.yml
└── logs
    └── backend.log</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>


### docker-compose.yml

docker compose 配置文件

样例如下

```YAML
name: docker_xinference

services:
  backend:
    image: xprobe/xinference:v0.16.1
    container_name: docker_xinference_backend
    entrypoint: [ "bash", "-c", "/home/work/commands/entrypoint.sh" ]
    restart: always
    volumes:
      - /mnt/data/ftpuser/zsftp/public/model:/home/data
      - ./commands:/home/work/commands
      - ./logs:/home/work/logs
      - /mnt/data/.xinference:/root/.xinference
      - /mnt/data/.cache/huggingface:/root/.cache/huggingface
      - /mnt/data/.cache/modelscope:/root/.cache/modelscope
    ports:
      - 19997:9997
    environment:
      - TZ=Asia/Shanghai
      # 通过魔搭下载模型 modelscope
      - XINFERENCE_MODEL_SRC=modelscope
    deploy:
      resources:
          reservations:
             devices:
             - driver: nvidia
               capabilities: [gpu]
               #device_ids: ['0','1']
               count: all
    networks:
      - docker-xinference-network

networks:
  docker-xinference-network:
    ipam:
      config:
        - subnet: 172.18.2.16/28
    driver: bridge
    name: docker-xinference-network
</code></pre></div></div>

<h3 id="logs">logs</h3>

<h4 id="backendlog">backend.log</h4>

<p>xinference 控制台输出日志</p>

<h3 id="commands">commands</h3>

<h4 id="entrypointsh">entrypoint.sh</h4>

<p>docker compose 中 xinference 服务的启动脚本，配置指定模型随 xinference 服务自动启动</p>

<p>样例如下</p>

<pre><code class="language-Bash">#!/bin/bash

set -e -x

# ADMIN API_KEY
ADMIN_API_KEY="sk-xxxx"

# 启动 xinference 服务
xinference-local -H 0.0.0.0 --auth-config /home/work/commands/auth_config.json &gt; /home/work/logs/backend.log 2&gt;&amp;1 &amp;

# 获取上面执行的后台命令的进程 ID
command_1=$!

echo "command_1: ${command_1}"

# sleep 30

# 设置最大尝试次数
max_attempts=30
# 初始化尝试次数
attempts=0
# 重试间隔秒数
attempt_interval=5

# 循环等待，直到 curl 命令返回成功状态码或达到最大尝试次数
while [ ${attempts} -lt ${max_attempts} ]; do
    # 尝试连接服务
    http_code=$(curl --request GET 
      --url 'http://127.0.0.1:9997/' 
      -o /dev/null -w "%{http_code}") || true

    echo "curl 命令返回状态码：${http_code}"
    
    # 如果状态码为 000，则表示服务未启动
    if [ ${http_code} -eq 000 ]; then

        # 增加尝试次数
        let attempts+=1
        echo "xinference 服务未启动，状态码：${http_code}，尝试次数：${attempts}"
        # 等待一段时间再次尝试
        sleep ${attempt_interval}

    else
        echo "xinference 服务已启动，开始启动模型"
        # 服务已经启动，跳出循环
        break
    fi
done

if [ ${attempts} -ge ${max_attempts} ]; then
    echo "xinference 服务启动失败，已达到最大尝试次数 ${max_attempts}"
    exit 1
fi

# 通用模型配置
xinference launch --model-name bge-large-zh-v1.5 --model-type embedding --model-uid common-dev-bge-large-zh-v1.5 --replica 1 --gpu-idx 1 --api-key ${ADMIN_API_KEY}

xinference launch --model-name bge-reranker-large --model-type rerank --model-uid common-dev-bge-reranker-large --replica 1 --gpu-idx 1 --api-key ${ADMIN_API_KEY}

# 等待 command_1 完成，保持前台进程，避免结束当前脚本，导致 docker compsoe 重启服务
wait $command_1
</code></pre>

<h4 id="auth_configjson">auth_config.json</h4>

<p>账号、密码、权限配置文件</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">auth_config</code>: 这个字段配置与安全相关的信息。</p>

    <blockquote>
      <ul>
        <li>
          <p><code class="language-plaintext highlighter-rouge">algorithm</code>: 用于令牌生成与解析的算法。推荐使用 <code class="language-plaintext highlighter-rouge">HS</code> 系列算法，例如 <code class="language-plaintext highlighter-rouge">HS256</code>，<code class="language-plaintext highlighter-rouge">HS384</code> 或者 <code class="language-plaintext highlighter-rouge">HS512</code> 算法。</p>
        </li>
        <li>
          <p><code class="language-plaintext highlighter-rouge">secret_key</code>: 用于令牌生成和解析的密钥。可以使用该命令生成适配 <code class="language-plaintext highlighter-rouge">HS</code> 系列算法的密钥：<code class="language-plaintext highlighter-rouge">openssl rand -hex 32</code> 。</p>
        </li>
        <li>
          <p><code class="language-plaintext highlighter-rouge">token_expire_in_minutes</code>: 保留字段，表示令牌失效时间。目前 Xinference 开源版本不会检查令牌过期时间。</p>
        </li>
      </ul>

    </blockquote>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">user_config</code>: 这个字段用来配置用户和权限信息。每个用户信息由以下字段组成：</p>

    <blockquote>
      <ul>
        <li>
          <p><code class="language-plaintext highlighter-rouge">username</code>: 字符串，表示用户名</p>
        </li>
        <li>
          <p><code class="language-plaintext highlighter-rouge">password</code>: 字符串，表示密码</p>
        </li>
        <li>
          <p><code class="language-plaintext highlighter-rouge">permissions</code>: 字符串列表，表示该用户拥有的权限。权限描述如上权限部分文档所述。</p>
        </li>
        <li>
          <p><code class="language-plaintext highlighter-rouge">api_keys</code>: 字符串列表，表示该用户拥有的 api-key 。用户可以通过这些 api-key ，无需登录步骤即可访问 xinference 接口。这里的 api_key 组成与 <code class="language-plaintext highlighter-rouge">OPENAI_API_KEY</code> 相似，总是以 <code class="language-plaintext highlighter-rouge">sk-</code> 开头，后跟 13 个数字、大小写字母。</p>
        </li>
      </ul>

    </blockquote>
  </li>
</ul>

<p>生成 OPENAI_API_KEY-like 字符串代码</p>

<pre><code class="language-Python"># -*- coding: utf-8 -*-
import random
import string

*def* generate_openai_api_key():
    prefix = "sk-"
    characters = string.ascii_letters + string.digits
    key = prefix + ''.join(random.choice(characters) for _ in range(13))
    return key

if __name__ == '__main__':
    # 使用函数生成一个 OPENAI_API_KEY-like 字符串
    openai_api_key = generate_openai_api_key()
    print(*f*'生成 OPENAI_API_KEY: {openai_api_key}')
</code></pre>

<p>auth_config.json 样例如下</p>

<pre><code class="language-JSON">{
  "auth_config": {
    "algorithm": "HS256",
    "secret_key": "eeb9c4c368383c50ead336688aa5cd9e68287ee486f44307a84ff6208953c60e",
    "token_expire_in_minutes": 300
  },
  "user_config": [
    {
      "username": "admin",
      "password": "xxxx",
      "permissions": [
        "admin"
      ],
      "api_keys": [
        "sk-xxxx"
      ]
    }
  ]
}
</code></pre>

<h2 id="日志轮转">日志轮转</h2>

<p>/etc/logrotate.d/xinference.conf</p>

<pre><code class="language-SQL">/mnt/data/xxx.log {
    rotate 5
    size 100M
    nomail
    missingok
    notifempty
    compress
    delaycompress
    create 755 user_1 user_1
}
</code></pre>

<h1 id="参考文档">参考文档</h1>

<ul>
  <li><a href="https://inference.readthedocs.io/zh-cn/stable/user_guide/auth_system.html">OAuth2 系统</a></li>
  <li><a href="/2024/11/16/linux-%E6%97%A5%E5%BF%97%E8%BD%AE%E8%BD%AC-logrotate-%E6%89%8B%E5%86%8C.html">linux 日志轮转 logrotate 手册</a></li>
</ul>]]></content><author><name>看看你极限深蓝</name></author><category term="Other" /><summary type="html"><![CDATA[概要]]></summary></entry><entry><title type="html">SSH 限制 IP 段登录</title><link href="https://lzc6244.github.io//2026/01/10/SSH-%E9%99%90%E5%88%B6-IP-%E6%AE%B5%E7%99%BB%E5%BD%95.html" rel="alternate" type="text/html" title="SSH 限制 IP 段登录" /><published>2026-01-10T00:00:00+00:00</published><updated>2026-01-10T00:00:00+00:00</updated><id>https://lzc6244.github.io//2026/01/10/SSH-%E9%99%90%E5%88%B6-IP-%E6%AE%B5%E7%99%BB%E5%BD%95</id><content type="html" xml:base="https://lzc6244.github.io//2026/01/10/SSH-%E9%99%90%E5%88%B6-IP-%E6%AE%B5%E7%99%BB%E5%BD%95.html"><![CDATA[<h1 id="步骤">步骤</h1>

<ul>
  <li>编辑 ssh 配置文件：<code class="language-plaintext highlighter-rouge">sudo vim /etc/ssh/sshd_config</code></li>
</ul>

<p>加入下面内容</p>

<pre><code class="language-Shell"># 多条记录表示多条规则
# * 表示所有用户
AllowUsers *@xx.xx.xx.0/24
# 允许 user 用户从 xx.xx.xx.0/24 网段访问
AllowUsers user@xx.xx.xx.0/24
</code></pre>

<ul>
  <li>
    <p>验证配置是否正确：<code class="language-plaintext highlighter-rouge">sudo sshd -t</code></p>
  </li>
  <li>
    <p>重启 ssh 服务</p>
  </li>
</ul>

<p><span style="color:red">重启前建议保留当前 ssh 连接，避免 ip 段配置错误</span></p>

<p><span style="color:red">ssh 连不上就完了，这样还能用已有 ssh 连接进行挽救</span></p>

<p>执行命令：<code class="language-plaintext highlighter-rouge">sudo systemctl restart ssh</code></p>]]></content><author><name>看看你极限深蓝</name></author><category term="Ubuntu" /><summary type="html"><![CDATA[步骤]]></summary></entry><entry><title type="html">pyenv 入门</title><link href="https://lzc6244.github.io//2025/12/13/pyenv-%E5%85%A5%E9%97%A8.html" rel="alternate" type="text/html" title="pyenv 入门" /><published>2025-12-13T00:00:00+00:00</published><updated>2025-12-13T00:00:00+00:00</updated><id>https://lzc6244.github.io//2025/12/13/pyenv-%E5%85%A5%E9%97%A8</id><content type="html" xml:base="https://lzc6244.github.io//2025/12/13/pyenv-%E5%85%A5%E9%97%A8.html"><![CDATA[<h1 id="概要">概要</h1>

<p>pyenv git 地址：https://github.com/pyenv/pyenv</p>

<h1 id="安装-pyenv">安装 pyenv</h1>

<pre><code class="language-Bash"># 安装 pyenv
curl https://pyenv.run | bash
 
# 在 ~/.bashrc 末尾添加以下 pyenv 配置，配置其自动初始化和环境变量（poetry 安装后会提示）
export PYENV_ROOT="$HOME/.pyenv" 
[[ -d $PYENV_ROOT/bin ]] &amp;&amp; export PATH="$PYENV_ROOT/bin:$PATH" 
eval "$(pyenv init -)" 
eval "$(pyenv virtualenv-init -)"
 
# 应用 ~/.bashrc 配置
source ~/.bashrc
 
 
# （建议提前换源）提前安装系统依赖（ubuntu）
sudo apt update
sudo apt install make build-essential libssl-dev zlib1g-dev 
libbz2-dev libreadline-dev libsqlite3-dev curl git 
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev -y
 
 
# 编译安装 python3.10 ，每次运行会从 python 官网下载安装包，无缓存
# 由于编译安装可能会出现缺失系统依赖情况，请手动下载相应 python 安装包
# 创建缓存文件夹 mkdir -p ~/.pyenv/cache 后将安装包压缩包存入此路径即可
pyenv install 3.10

# 全局配置 python 版本为 3.10
pyenv global 3.10
# 在当前路径配置 python 版本为 3.10
# pyenv local 3.10

# 查看、确认当前 pyenv 配置的 python 版本
pyenv versions


# 创建虚拟环境
pyenv virtualenv 3.12 my-venv
</code></pre>

<h1 id="更新-python-版本列表">更新 python 版本列表</h1>

<p>寻找不知道指定/最新 python 版本时执行更新 python 版本列表操作</p>

<pre><code class="language-Shell">pyenv update
</code></pre>

<h1 id="安装-python">安装 python</h1>

<h2 id="直接联网安装">直接联网安装</h2>

<pre><code class="language-Shell"># 安装指定版本 python
pyenv install 3.10.11
# 列出 pyenv 当前已安装可用 python 版本
pyenv versions
</code></pre>

<h2 id="通过本地-python-安装包缓存安装">通过本地 python 安装包缓存安装</h2>

<p>python-build 会将下载的包文件缓存在<code class="language-plaintext highlighter-rouge">PYTHON_BUILD_CACHE_PATH</code>环境变量指定的位置。</p>

<p>默认值为<code class="language-plaintext highlighter-rouge">~/.pyenv/cache</code>，因此只需创建该目录即可启用缓存。</p>

<h3 id="linux">linux</h3>

<p>创建 <code class="language-plaintext highlighter-rouge">~/.pyenv/cache</code> 然后下载指定 python 版本的 tar.xz 源码压缩包即可</p>

<h3 id="windows">windows</h3>

<p>Windows 使用的是 pyenv-win ，创建 <code class="language-plaintext highlighter-rouge">C:Users用户名.pyenvpyenv-wininstall_cache</code> 后下载指定 python 版本的 exe 即可</p>

<h3 id="步骤">步骤</h3>

<p>以 windows 为例，如下</p>

<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 安装指定版本 python</span>
pyenv install 3.10.11
<span class="c"># 列出 pyenv 当前已安装可用 python 版本</span>
pyenv versions

<span class="c"># 配置全局或者当前路径 python 版本</span>
<span class="c"># 指定全局 python 版本</span>
pyenv global 3.10.11
<span class="c"># 指定当前路径 python 版本</span>
pyenv local 3.10.11

<span class="c"># 校验全局 python 版本配置</span>
python -c "import sys;packages_path='site-packages';print(f'Python 版本：{sys.version}nPython 路径：{sys.executable}nsite-packages 路径：{next(path for path in sys.path if packages_path in path)}')"
</code></pre></div></div>

<p><img src="/imgs/JeKyll/2025/2025-12-13-pyenv-入门-001.png" alt="" /></p>

<h1 id="参考文档">参考文档</h1>

<ul>
  <li><a href="https://github.com/pyenv/pyenv/wiki#suggested-build-environment">python 依赖安装</a></li>
  <li><a href="https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md">pvenv python-build</a></li>
</ul>]]></content><author><name>看看你极限深蓝</name></author><category term="Python" /><summary type="html"><![CDATA[概要]]></summary></entry></feed>