背景#

作为一个重度 RSS 用户,我每天通过 Miniflux 阅读大量技术文章。阅读过程中经常遇到一个问题:读到好文章想收藏,但收藏后很难再找到;想分享到 Telegram 频道,又需要手动复制链接。

之前在 Miniflux 中"starred"的文章只是静静躺在那里,既没有归档到独立的书签管理器,也没有推送到任何地方。我需要一条自动化的信息管道来解决这个问题:

  1. 自动存档:Miniflux 中保存的文章自动同步到 KaraKeep(书签管理器)
  2. 精选推送:在 KaraKeep 中给文章打上 telegram 标签后,自动推送到 Telegram 频道
  3. 零手动操作:除了阅读和打标签,其他环节全自动

部署环境#

我的 Homelab 由两个 K3s 集群组成,通过 Tailscale 互联:

集群 位置 已有服务 新增服务
oracle-k3s Oracle Cloud ARM Miniflux, Homepage, IT-Tools KaraKeep, Redpanda Connect
k3s-homelab Proxmox 虚拟机 Grafana, ArgoCD, Vault Gotify

外部流量路径:Internet → Cloudflare DNS → Cloudflare Tunnel → Traefik → Services

架构设计#

最终的信息管道架构如下:

┌─────────────────────────── Oracle K3s ──────────────────────────────┐
│                                                                     │
│  ┌──────────┐   Webhook     ┌──────────────────┐   KaraKeep API    │
│  │ Miniflux │ ────────────► │ Redpanda Connect │ ───────────────►  │
│  │          │  POST         │  (webhook-to-     │                   │
│  │          │  /save_entry  │   karakeep)       │   ┌──────────┐   │
│  └──────────┘               │                  │   │ KaraKeep │   │
│                              │                  │   │ + Chrome  │   │
│                              │  每5分钟轮询     │◄──│ + Meili   │   │
│                              │  tag=telegram    │   └──────────┘   │
│                              │  内存去重 (24h)  │                   │
│                              └────────┬─────────┘                   │
│                                       │                             │
└───────────────────────────────────────┼─────────────────────────────┘
                                        │ Gotify API
                                        │ (通过 Tailscale 跨集群)
                                        ▼
┌─────────────────────────── Homelab K3s ─────────────────────────────┐
│                                                                      │
│  ┌────────┐   Telegram Plugin    ┌──────────────────┐               │
│  │ Gotify │ ───────────────────► │ Telegram Channel │               │
│  └────────┘                      └──────────────────┘               │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

核心数据流:

流程 触发方式 路径
文章存档 Miniflux Webhook (用户保存文章时) Miniflux → Redpanda Connect → KaraKeep API
精选推送 定时轮询 (每 5 分钟) KaraKeep API → Redpanda Connect → Gotify → Telegram

技术选型#

KaraKeep — 书签管理器#

KaraKeep(前身 Hoarder)是一个自托管的书签管理器,支持:

  • AI 自动标签和摘要(可选接入 OpenAI/Ollama)
  • 全文搜索(内置 Meilisearch)
  • 完整的 REST API
  • 网页快照(内置 Headless Chrome)

为什么不用 Linkding 或 Wallabag?KaraKeep 的 API 更简洁,部署一体化(web + chrome + meilisearch 三容器),适合 K8s Sidecar 模式。

Redpanda Connect — 数据管道#

Redpanda Connect(前身 Benthos)是一个流处理引擎,用声明式 YAML 定义数据管道。选择它而非 n8n/Node-RED 的原因:

  • 极轻量:单个 Go 二进制,~50MB 内存
  • 声明式配置:YAML 定义输入/处理/输出,无需写代码
  • 内置去重cache + dedupe 处理器,支持内存缓存
  • 双管道运行:单个 Pod 内可运行多个独立管道

Gotify — 推送通知#

Gotify 是一个轻量的自托管推送通知服务,支持通过插件将消息转发到 Telegram。部署在 Homelab 而非 Oracle 的原因:

  • Gotify 使用文件存储(SQLite),NFS 持久卷更可靠
  • 作为通知中枢,未来可接收更多 Homelab 内部告警

实现细节#

KaraKeep 部署 (Oracle K3s)#

KaraKeep 采用三容器 Sidecar 模式部署:

# cloud/oracle/manifests/rss-system/karakeep.yaml
containers:
  - name: karakeep        # 主应用 (Next.js)
    image: ghcr.io/karakeep-app/karakeep:release
    ports:
      - containerPort: 3000
    env:
      - name: NEXTAUTH_URL
        value: "https://keep.meirong.dev"
      - name: BROWSER_WEB_URL
        value: "http://localhost:9222"
      - name: MEILI_ADDR
        value: "http://localhost:7700"
      # ... 密钥通过 ExternalSecret 从 Vault 注入

  - name: chrome           # Headless Chrome (网页快照)
    image: gcr.io/zenika-hub/alpine-chrome:124
    command: ["chromium-browser"]
    args: ["--headless", "--no-sandbox", "--remote-debugging-port=9222"]

  - name: meilisearch      # 全文搜索引擎
    image: getmeili/meilisearch:v1.13.3
    env:
      - name: MEILI_MAX_INDEXING_THREADS
        value: "1"         # Oracle ARM 免费实例资源有限

踩坑:KaraKeep 的 Docker Hub 镜像 karakeep/karakeep 不存在,必须用 GitHub Container Registry 的 ghcr.io/karakeep-app/karakeep:release

踩坑:Meilisearch 在 Oracle ARM 免费实例上频繁 OOM CrashLoopBackOff,报错 Resource temporarily unavailable (os error 11)。解决方法:将内存限制提高到 768Mi 并设置 MEILI_MAX_INDEXING_THREADS=1 限制索引线程数。

Redpanda Connect 双管道配置#

Redpanda Connect 的核心是两条独立管道,运行在同一个 Pod 的两个容器中:

管道 1:Webhook → KaraKeep(文章存档)

# connect.yaml — 接收 Miniflux Webhook,保存到 KaraKeep
input:
  http_server:
    path: /save_entry
    allowed_verbs: ["POST"]

pipeline:
  processors:
    - mapping: |
        root.type = "link"
        root.url = this.entry.url
        root.title = this.entry.title        

output:
  http_client:
    url: "http://karakeep.rss-system.svc:3000/api/v1/bookmarks"
    verb: POST
    headers:
      Authorization: "Bearer ${KARAKEEP_API_KEY}"
      Content-Type: application/json

管道 2:KaraKeep → Gotify(精选推送)

# poll-karakeep.yaml — 轮询 KaraKeep tag=telegram 书签,推送到 Gotify
input:
  generate:
    interval: "5m"
    mapping: 'root = {}'

pipeline:
  processors:
    # 1. 拉取所有未归档书签
    - http:
        url: "http://karakeep.rss-system.svc:3000/api/v1/bookmarks?favourited=false&archived=false"
        verb: GET
        headers:
          Authorization: "Bearer ${KARAKEEP_API_KEY}"

    # 2. 筛选带 telegram 标签的
    - mapping: |
        root = this.bookmarks.filter(b -> b.tags.any(t -> t.name == "telegram"))        

    # 3. 展开数组,逐条处理
    - unarchive:
        format: json_array

    # 4. 内存缓存去重 (24h TTL)
    - cache:
        operator: set
        resource: dedup_cache
        key: ${! this.id }
        value: "seen"
    - dedupe:
        cache: dedup_cache
        key: ${! this.id }

    # 5. 构造 Gotify 消息
    - mapping: |
        root.title = "📌 " + this.title.or(this.content.title).or("New Bookmark")
        root.message = this.url.or(this.content.url).or("")
        root.priority = 5        

output:
  http_client:
    url: "${GOTIFY_URL}/message"
    verb: POST
    headers:
      X-Gotify-Key: "${GOTIFY_TOKEN}"
      Content-Type: application/json

resources:
  caches:
    dedup_cache:
      memory:
        default_ttl: "24h"

为什么用两个容器而非两个 stream 模式管道? Redpanda Connect 的 stream 模式适合同构管道,而这两条管道的输入类型完全不同(HTTP server vs 定时生成),分容器更清晰且互不干扰。

Gotify 部署 (Homelab K3s)#

Gotify 部署相对简单,通过 ArgoCD GitOps 管理:

# k8s/helm/manifests/gotify.yaml
containers:
  - name: gotify
    image: gotify/server:latest
    ports:
      - containerPort: 80
    env:
      - name: GOTIFY_DEFAULTUSER_PASS
        valueFrom:
          secretKeyRef:
            name: gotify-secret
            key: default-user-password
    volumeMounts:
      - name: data
        mountPath: /app/data

密钥通过 External Secrets Operator 从 HashiCorp Vault 同步到 K8s Secret。

跨集群通信#

Oracle K3s 上的 Redpanda Connect 需要调用 Homelab K3s 上的 Gotify API。两个集群通过 Tailscale 互联:

Redpanda Connect (oracle-k3s)
    → http://gotify.personal-services.svc.cluster.local:80  ✗ 跨集群不可达
    → https://notify.meirong.dev                             ✗ 经过 Cloudflare Tunnel 增加延迟
    → http://100.x.x.x:80                                   ✓ 通过 Tailscale 直连

实际实现中,Gotify URL 存储在 Vault 的 homelab/redpanda-connect 密钥路径下,通过 ExternalSecret 注入为环境变量 GOTIFY_URL

SSO 保护#

KaraKeep 和 Gotify 都通过 oauth2-proxy + Traefik ForwardAuth 实现 SSO 保护:

# HTTPRoute 中添加 SSO 过滤器
rules:
  - matches:
      - path:
          type: PathPrefix
          value: /
    filters:
      - type: ExtensionRef
        extensionRef:
          group: traefik.io
          kind: Middleware
          name: sso-forwardauth
    backendRefs:
      - name: karakeep
        port: 3000

由于 Traefik Gateway API 要求 Middleware 与 HTTPRoute 在同一命名空间,需要在 rss-system 命名空间额外创建 sso-forwardauth Middleware:

# cloud/oracle/manifests/base/traefik-config.yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: sso-forwardauth
  namespace: rss-system    # 必须与 KaraKeep HTTPRoute 同命名空间
spec:
  forwardAuth:
    address: http://oauth2-proxy.auth-system.svc.cluster.local:4180/
    trustForwardHeader: true

踩坑:最初忘了在 rss-system 命名空间创建 Middleware,导致 keep.meirong.dev 返回 404,而非预期的 302 SSO 重定向。Traefik 找不到 Middleware 时直接返回 404 而不是报错,排查时容易误以为是路由配置问题。

网关与 DNS 配置#

服务 集群 域名 Cloudflare Tunnel
KaraKeep oracle-k3s keep.meirong.dev Oracle Tunnel
Gotify homelab notify.meirong.dev Homelab Tunnel

两个域名分别通过各自集群的 Cloudflare Tunnel 暴露,Terraform 管理 DNS 记录和 Tunnel 入站规则。

部署流程#

完整的部署步骤:

# 1. 在 Vault 中创建密钥
vault kv put secret/homelab/karakeep \
  nextauth_secret=$(openssl rand -hex 32) \
  meili_master_key=$(openssl rand -hex 16)

vault kv put secret/homelab/gotify \
  default_user_password="<password>"

vault kv put secret/homelab/redpanda-connect \
  karakeep_api_key="" \
  gotify_url="" \
  gotify_token=""

# 2. 部署 Oracle K3s 服务
kubectl --context oracle-k3s apply -k cloud/oracle/manifests/

# 3. 部署 Homelab Gotify (ArgoCD 自动同步)
git add k8s/helm/manifests/gotify.yaml
git commit -m "feat: add gotify push notification service"
git push  # ArgoCD 3 分钟内自动部署

# 4. 配置 Cloudflare DNS
cd cloudflare/terraform && just apply     # notify.meirong.dev
cd cloud/oracle/cloudflare && just apply  # keep.meirong.dev

# 5. 部署后手动配置
# - 登录 KaraKeep UI → 生成 API Key → 更新 Vault
# - 登录 Gotify UI → 创建 App → 获取 Token → 更新 Vault
# - 登录 Miniflux → Settings → Webhook URL: http://redpanda-connect.rss-system.svc:4195/save_entry
# - 安装 Gotify Telegram 插件

最终效果#

服务 URL 状态
KaraKeep keep.meirong.dev SSO 保护 ✓
Gotify notify.meirong.dev SSO 保护 ✓
IT-Tools tool.meirong.dev 公开访问 ✓
Squoosh squoosh.meirong.dev 公开访问 ✓

信息流验证:

  1. 在 Miniflux 中保存文章 → KaraKeep 自动创建书签 ✓
  2. 在 KaraKeep 中给书签添加 telegram 标签 → 5 分钟内 Gotify 收到通知 ✓
  3. Gotify 通过 Telegram 插件转发到频道 ✓
  4. 重复书签不会重复推送(内存去重 24h TTL) ✓

踩坑总结#

问题 现象 原因 解决
KaraKeep 镜像拉取失败 ImagePullBackOff Docker Hub 上没有 karakeep/karakeep 改用 ghcr.io/karakeep-app/karakeep:release
Meilisearch 崩溃 CrashLoopBackOffos error 11 Oracle ARM 免费实例内存不足 内存限制 768Mi + MEILI_MAX_INDEXING_THREADS=1
keep.meirong.dev 404 浏览器返回 404 rss-system 命名空间缺少 sso-forwardauth Middleware 在 traefik-config.yaml 中添加
ArgoCD 覆盖手动变更 kubectl apply 后 filter 消失 selfHeal: true 从旧 Git 版本恢复 必须先 git push 再等 ArgoCD 同步

下一步#

  • 配置 Miniflux Webhook 触发保存
  • 安装 Gotify Telegram 插件并关联频道
  • 在 KaraKeep 中创建 API Key 更新到 Vault
  • 考虑接入 Ollama 实现 AI 自动摘要和标签

本文是 Homelab 系列的一部分。上一篇:在K3s上部署ZITADEL实现SSO单点登录