目录

「TrueNAS」听我说谢谢你

TrueNAS安装及配置。

TrueNAS

RAID 简介

RAID (Redundant Array of Independent/InexpensiveDisks),独立磁盘冗余阵列,简称为「磁盘阵列」,是一种将多块独立的硬盘(物理硬盘)按不同的组合方式形成一个硬盘组(逻辑硬盘),从而提供比单块硬盘更大的存储容量、更高的可靠性和 更快的读写性能等。

模式

  • RAID 0

简单地说,RAID0主要通过将多块硬盘“串联”起来,从而形成一个更大容量的逻辑硬盘。RAID0通过“条带化(striping)”将数据分成不同的 数据块,并依次将这些数据块写到不同的硬盘上。因为数据分布在不同的硬盘上,所以数据吞吐量得到大大提升。但是,很容易看出RAID0没有任何数据冗余, 因此其可靠性不高。

https://assets.yirami.xyz/review/truenas/RAID0.png

  • RAID 1

如果说RAID 0是RAID中一种只注重存储容量而没有任何容错的极端形式,那么RAID1则是有充分容错而不关心存储利用率的另一种极端表现。RAID1通过“镜像 (mirroring)”,将每一份数据都同时写到多块硬盘(一般是两块)上去,从而实现了数据的完全备份。因此,RAID1 支持―“热替换”,在不断电的情况下对故障磁盘进行更换。一般情况下,RAID1 控制器在读取数据时支持负载平衡,允许数据从不同磁盘上同时读取,从而提高数据的读取速度;但是,RAID1在写数据的性能没有改善。

https://assets.yirami.xyz/review/truenas/RAID1.png

  • RAID 2

RAID 2以比特(bit)为单位,将数据―“条带化(striping)”分布存储在不同硬盘上;同时,将不同硬盘上同一位置的数据位用海明码进行编码,并将这些 编码数据保存在另外一些硬盘的相同位置上,从而实现错误检查和恢复。因为技术实施上的复杂性,商业环境中很少采用RAID2。

https://assets.yirami.xyz/review/truenas/RAID2.png

  • RAID 3

与RAID 2类似,不同的是:1)以字节(byte)为单位进行―条带化‖处理;2)以奇偶校验码取代海明码。RAID3的读写性能都还不错,而且存储利用率也相当高,可达到(n-1)/n。但是对于随机读写操作,奇偶盘会成为写操作的瓶颈。

https://assets.yirami.xyz/review/truenas/RAID3.png

  • RAID 4

与RAID 3的分布结构类似,不同的是RAID 4以数据块(block)为单位进行奇偶校验码的计算。另外,与RAID2和RAID3不同的是,RAID4中各个磁盘是独立操作的,并不要求各个磁盘的磁头同步转动。因此,RAID4允许多个I/O请求并行处理。

https://assets.yirami.xyz/review/truenas/RAID4.png

  • RAID 5

RAID 3和RAID 4都存在同一个问题,就是奇偶校验码放在同一个硬盘上,容易造成写操作的瓶颈。RAID5与RAID4基本相同,但是其将奇偶校验码分开存放到不同的硬盘上去,从而减少了写奇偶校验码带来瓶颈的可能性。

https://assets.yirami.xyz/review/truenas/RAID5.png

  • RAID 6

在RAID 5的基础上,RAID 6又另外增加了一组奇偶校验码,从而获得更高的容错性,最多允许同时有两块硬盘出现故障。但是,新增加的奇偶校验计算同时也带来了写操作性能上的损耗。

https://assets.yirami.xyz/review/truenas/RAID6.png

  • RAID 7

RAID 7并非公开的RAID标准,而是Storage Computer Corporation的专利硬件产品名称,RAID 7是以RAID 3及RAID 4为基础所发展,但是经过强化以解决原来的一些限制。另外,在实现中使用大量的缓冲存储器以及用以实现异步数组管理的专用即时处理器,使得RAID 7可以同时处理大量的IO要求,所以性能甚至超越了许多其他RAID标准的实现产品。但也因为如此,在价格方面非常的高昂。

  • RAID 0+1

为了获取更好的I/O吞吐率或者可靠性,将不同的RAID标准级别混合产生的组合方式叫做嵌套式RAID,或者混合RAID。RAID0+1 是先将硬盘分 为若干组,每组以RAID0的方式组成―条带化‖的硬盘阵列,然后将这些组RAID0的硬盘阵列以RAID1的方式组成一个大的硬盘阵列。

https://assets.yirami.xyz/review/truenas/RAID01.png

  • RAID 10

类似于RAID 0+1, RAID 10则是先“镜像”(RAID 1)、后“条带化”(RAID0)。RAID0+1和RAID10性能上并无太大区别,但是RAID10在可靠性上要好于RAID0+1。这是因为在 RAID10中,任何一块硬盘出现故障不会影响到整个磁盘阵列,即整个系统仍将以RAID10的方式运行;而RAID0+1中,一个硬盘出现故障则会导致 其所在的RAID0子阵列全部无法正常工作,从而影响到整个RAID0+1磁盘阵列 – 在只有两组RAID0子阵列的情况下,整个系统将完全降级为RAID0级别。

https://assets.yirami.xyz/review/truenas/RAID10.png

  • RAID 50

RAID 5与RAID 0的组合,先作RAID 5,再作RAID 0,也就是对多组RAID 5彼此构成Stripe访问。由于RAID 50是以RAID 5为基础,而RAID 5至少需要3颗硬盘,因此要以多组RAID 5构成RAID 50,至少需要6颗硬盘。以RAID 50最小的6颗硬盘配置为例,先把6颗硬盘分为2组,每组3颗构成RAID 5,如此就得到两组RAID 5,然后再把两组RAID 5构成RAID 0。RAID 50在底层的任一组或多组RAID 5中出现1颗硬盘损坏时,仍能维持运作,不过如果任一组RAID 5中出现2颗或2颗以上硬盘损毁,整组RAID 50就会失效。RAID 50由于在上层把多组RAID 5构成Stripe,性能比起单纯的RAID 5高,容量利用率比RAID5要低。比如同样使用9颗硬盘,由各3颗RAID 5再组成RAID 0的RAID 50,每组RAID 5浪费一颗硬盘,利用率为(1-3/9),RAID 5则为(1-1/9)。

https://assets.yirami.xyz/review/truenas/RAID50.png

  • RAID 53

它拥有一个镜像条带数组,硬盘里其中一个条带就是一个是由3组以上的RAID 5组成RAID 3硬盘阵列。

  • RAID 60

RAID 6与RAID 0的组合:先作RAID 6,再作RAID 0。换句话说,就是对两组以上的RAID 6作Stripe访问。RAID 6至少需具备4颗硬盘,所以RAID 60的最小需求是8颗硬盘。 由于底层是以RAID 6组成,所以RAID 60可以容许任一组RAID 6中损毁最多2颗硬盘,而系统仍能维持运作;不过只要底层任一组RAID 6中损毁3颗硬盘,整组RAID 60就会失效,当然这种情况的概率相当低。比起单纯的RAID 6,RAID 60的上层透过结合多组RAID 6构成Stripe访问,因此性能较高。不过使用门槛高,而且容量利用率低是较大的问题。

基准

https://assets.yirami.xyz/review/truenas/raid_benchmark.png

预设

端口

安装

应用

SCALE

Docker

SCALE 24.10 开始,TrueNAS 的应用后端从 Kubernets 转为 Docker

准备

TrueNAS SCALE 25.10 PASSED;

系统默认限制 apt 包管理器的使用,可以使用 install-dev-tools 命令进入开发者模式来放开限制。该工具会自动解除对某些目录的保护并安装一些常用工具,但在版本升级后不会被保留,需要重新安装。

新版 TrueNAS 已经无需使用 apt 来手动安装 docker 相关组件,仅需在 Apps -> Configuration -> Choose Pool 设置合适的存储池,即可自动启用容器服务。

注意:文档警告不要使用 TrueNAS Web Shell 来进行包管理,因为中间件的更改可能导致系统无法访问。

TrueNAS Web Shell 界面的 System settings -> Services 中可以开启 SSH 访问。若需密码登录,还需配合修改 /etc/ssh/sshd_config 文件。

vim /etc/ssh/sshd_config
# edit PermitRootLogin and PasswordAuthentication
systemctl restart ssh

若是简单需求,可以直接赋予 aptdpkg 可执行权限。

chmod +x /usr/bin/apt*
chmod +x /usr/bin/dpkg

# 禁用官方源,加入国内源
vim /etc/apt/sources.list

apt update && apt install -y docker.io docker-compose

温馨提示:

  • TrueNAS 的默认 shell/usr/bin/zsh (参见 Credentials -> Users -> root -> Edit -> Additional Details -> Shell),因此在为 Tabby 设置工作目录时得参考用于 zsh 的配置
安装应用

实际部署配置备份在 Cloudflare R2

  • ddns-go

    services:
      ddns-go:
        image: jeessy/ddns-go:latest
        container_name: ddns-go
        restart: unless-stopped
        ports:
          - "19081:9876"
        volumes:
          - /path/to/your/ddnsgo:/root
        environment:
          - TZ=Asia/Shanghai
        deploy:
          resources:
            limits:
              cpus: '0.5'
              memory: 32M
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost:9876"]
          interval: 30s
          timeout: 5s
          retries: 3
  • traefik

    • docker-compose.yml

      services:
        traefik:
          image: traefik:v3
          container_name: traefik
          restart: unless-stopped
          ports:
            - "80:80"
            - "443:443"
          volumes:
            - /var/run/docker.sock:/var/run/docker.sock:ro
            - /path/to/your/certs:/certs
            - ./config/traefik.yml:/traefik.yml
            - ./config/dynamic.yml:/dynamic.yml
          environment:
            - CF_API_EMAIL=${CF_DNS_EMAIL}
            - CLOUDFLARE_DNS_API_TOKEN=${CF_API_TOKEN}
            - CLOUDFLARE_ZONE_API_TOKEN=${CF_API_TOKEN}
          labels:
            - "traefik.enable=true"
          deploy:
            resources:
              limits:
                cpus: '0.5'
                memory: 256M
          healthcheck:
            test: ["CMD-SHELL", "wget -qO- http://localhost:8080/api/rawdata"]
            interval: 3s
            retries: 10
    • traefik.yml

      api:
        dashboard: true
        insecure: true  # 关闭该配置无法访问仪表板,原因未知,因此停止相关端口映射来替  代
      
      entryPoints:
        http:
          address: ":80"
        https:
          address: ":443"
      
      providers:
        docker:
          endpoint: "unix:///var/run/docker.sock"
          exposedByDefault: false
        file:
          filename: /dynamic.yml
          watch: true
      
      certificatesResolvers:
        le:
          acme:
            email: ${CF_DNS_EMAIL}
            storage: /certs/acme.json
            dnsChallenge:
              provider: cloudflare
              delayBeforeCheck: 30
              resolvers:
                - "1.1.1.1:53"
                - "8.8.8.8:53"
      
      global:
        sendAnonymousUsage: false
        checkNewVersion: false
    • dynamic.yml

      http:
        routers:
          traefik-dashboard:
            entryPoints:
              - http
            rule: "Host(`aio.traefik.yirami.xyz`)"
            service: dashboard@internal
      
          traefik-dashboard-api:
            entryPoints:
              - http
            rule: "Host(`aio.traefik.yirami.xyz`) && PathPrefix(`/api`)"
            service: api@internal
      
          traefik-dashboard-secure:
            entryPoints:
              - https
            rule: "Host(`traefik.aio.yirami.xyz`)"
            service: dashboard@internal
            tls:
              certResolver: le
              domains:
                - main: ${CF_DNS_DOMAIN}
                  sans:
                    - ${CF_DNS_DOMAIN_LIST}
      
          traefik-dashboard-api-secure:
            entryPoints:
              - https
            rule: "Host(`traefik.aio.yirami.xyz`) && PathPrefix(`/api`)"
            service: api@internal
            tls:
              certResolver: le
      
          nas-aio:
            entryPoints:
              - https
            rule: "Host(`nas.aio.yirami.xyz`)"
            service: nas-aio-service
            tls:
              certResolver: le
      
        services:
          nas-aio-service:
            loadBalancer:
              servers:
                - url: "http://aio.nas.yirami.xyz:19080"
  • mihomo

    • docker-compose.yml

      version: '3.8'
      services:
        mihomo-server:
          image: ghcr.io/metacubex/metacubexd-server:v1.267.0
          container_name: mihomo-server
          restart: unless-stopped
          networks:
            - traefik_proxy
          ports:
            - "10808:7890"  # mixed proxy port
            - "19180:8080"  # dashboard UI + /api/control agent API
            # - '9090:9090' # mihomo Clash API + WebSocket (UI endpoint target)
          environment:
            - TZ=Asia/Shanghai
            - DEFAULT_BACKEND_URL=${MIHOMO_API_URL}
            - CONTROL_TOKEN=${MIHOMO_CTRL_TOKEN}
            - CLASH_SECRET=${MIHOMO_API_SECRET}
          volumes:
            - /path/to/your/mihomo/metacubexd-data:/data
          labels:
            - "traefik.enable=true"
            # UI
            - "traefik.http.routers.mihomo-ui.rule=Host(`your.mihomo.ui.domain`)"
            ...
            - "traefik.http.routers.mihomo-ui.service=mihomo-ui-service"
            - "traefik.http.services.mihomo-ui-service.loadbalancer.server.port=8080"
            # API
            - "traefik.http.routers.mihomo-api.rule=Host(`your.mihomo.api.domain`)"
            ...
            - "traefik.http.routers.mihomo-api.service=mihomo-api-service"
            - "traefik.http.services.mihomo-api-service.loadbalancer.server.port=9090"
          deploy:
            ...
          healthcheck:
            test: ["CMD", "wget", "-qO-", "http://localhost:8080/api/control/health"]
            ...
    • config.yaml

      mixed-port: ?           # 混合代理端口
      allow-lan: true         # 允许内网其他服务连接
      bind-address: '*'       # 监听所有网卡接口
      mode: rule              # 智能分流规则模式
      
      # 外部控制(供健康检查及前端面板连接)
      external-controller: 0.0.0.0:9090 
      secret: "?"
      
      # 纯端口代理,不需要开启 TUN
      tun:
        enable: false
  • qbittorrent

    注意:Docker 的子网更新后,为避免网页端经 Authelia 认证后还需二次登录,需要同步更新子网网段白名单,或直接设置为 DockerB 类子网 172.16.0.0/12

    services:
      qbittorrent:
        image: lscr.io/linuxserver/qbittorrent:5.1.4-libtorrentv1
        container_name: qbittorrent
        stop_grace_period: "35s"
        restart: unless-stopped
        networks:
          - traefik_proxy
        ports:
          # - 19085:8080  # only for debug, must access by traefik
          - 36881:36881
          - 36881:36881/udp
        volumes:
          - /path/to/your/qb:/config
          - /path/to/your/QDL:/downloads
        environment:
          - PUID=1000
          - PGID=1000
          - TZ=Etc/UTC
          - WEBUI_PORT=8080
          - TORRENTING_PORT=36881
        labels:
          - "traefik.enable=true"
          - "traefik.docker.network=traefik_proxy"
          - "traefik.http.routers.qb.rule=Host(`bt.aio.yirami.xyz`)"
          - "traefik.http.routers.qb.entrypoints=websecure"
          - "traefik.http.routers.qb.tls=true"
          - "traefik.http.routers.qb.tls.certresolver=le"
          - "traefik.http.routers.qb.middlewares=authelia-auth"
          - "traefik.http.services.qb.loadbalancer.server.port=8080"
        deploy:
          resources:
            limits:
              cpus: '1'
              memory: 4G
        healthcheck:
          test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8080 || exit 1"]
          interval: 30s
          timeout: 10s
          retries: 3
          start_period: 60s
  • aria2-pro

    参考博客

    services:
      aria2:
        container_name: aria2
        image: p3terx/aria2-pro
        restart: unless-stopped
        ports:
          - 6800:6800
          - 36888:6888
          - 36888:6888/udp
        environment:
          - PUID=1000
          - PGID=1000
          - UMASK_SET=022
          - RPC_SECRET=${ARIA2_RPC_SECRET}
          - RPC_PORT=6800
          - LISTEN_PORT=6888
          - DISK_CACHE=64M
          - IPV6_MODE=false
          - UPDATE_TRACKERS=true
          - CUSTOM_TRACKER_URL=
          - TZ=Asia/Shanghai
        volumes:
          - /path/to/your/aria2/config:/config
          - /path/to/your/aria2/downloads:/downloads
        deploy:
          resources:
            limits:
              cpus: '0.5'
              memory: 128M
        healthcheck:
          test: ["CMD-SHELL", "wget -qO /tmp/health.json --header='Content-Type: application/json' --post-data='{\"jsonrpc\":\"2.0\",\"method\":\"aria2.getVersion\",\"id\":\"health\",\"params\":[\"token:${ARIA2_RPC_SECRET}\"]}' http://localhost:6800/jsonrpc && grep -q 'version' /tmp/health.json"]
          interval: 30s
          timeout: 10s
          retries: 3
          start_period: 15s
        logging:
          options:
            max-size: "1m"
    
      ariang:
        container_name: ariang
        image: p3terx/ariang
        restart: unless-stopped
        ports:
          - 36880:6880
        depends_on:
          - aria2
        deploy:
          resources:
            limits:
              cpus: '0.5'
              memory: 8M
        healthcheck:
          disable: true
        logging:
          driver: json-file
          options:
            max-size: 1m
  • cloudreve

    services:
      cloudreve:
        container_name: cloudreve
        image: cloudreve/cloudreve:latest
        profiles: ["disable"]  # 默认不启动
        restart: unless-stopped
        ports:
          - "19086:5212"
        volumes:
          - /path/to/your/cloudreve/conf.ini:/cloudreve/conf.ini
          - /path/to/your/cloudreve/cloudreve.db:/cloudreve/cloudreve.db
          - /path/to/your/cloudreve/uploads:/cloudreve/uploads
          - /path/to/your/cloudreve/avatar:/cloudreve/avatar
          - /path/to/your/aria2/downloads:/data
        depends_on:
          - aria2
        deploy:
          resources:
            limits:
              cpus: '0.5'
              memory: 2G
        healthcheck:
          test: ["CMD-SHELL", "wget --no-verbose --spider -q -t 1 http://localhost:5212/api/v3/site/config || exit 1"]
          interval: 30s
          timeout: 10s
          retries: 3
          start_period: 30s
  • tinymediamanager

    注意:刮削时需要注意容器指定的权限与主机文件目录的权限匹配,否则可能无法存入刮削信息!

    services:
      tinymediamanager:
        image: tinymediamanager/tinymediamanager:latest
        container_name: tinymediamanager
        restart: unless-stopped
        environment:
          - USER_ID=1000
          - GROUP_ID=1000
          - ALLOW_DIRECT_VNC=true
          - LC_ALL=en_US.UTF-8
          - LANG=en_US.UTF-8
          - PASSWORD=${TMM_PASSWD}
        volumes:
          - /path/to/your/tmm/data:/data
          - /path/to/your/tmm/addons:/media/addons
          - /path/to/your/tmm/movies:/media/movies
          - /path/to/your/tmm/tvshows:/media/tv_shows
        ports:
          - 19087:5900 # VNC port
          - 19088:4000 # Web UI
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.tmm.rule=Host(`tmm.aio.yirami.xyz`)"
          - "traefik.http.routers.tmm.entrypoints=https"
          - "traefik.http.routers.tmm.tls=true"
          - "traefik.http.routers.tmm.tls.certresolver=le"
          - "traefik.http.services.tmm.loadbalancer.server.port=4000"    
        deploy:
          resources:
            limits:
              cpus: '1.0'
              memory: 1G
        healthcheck:
          test: ["CMD", "wget", "--spider", "-q", "http://localhost:4000"]
          interval: 30s
          timeout: 10s
          retries: 5
          start_period: 60s
  • mdc-ng

    services:
      mdc:
        image: mdcng/mdc:latest
        container_name: mdc
        restart: unless-stopped
        networks:
          - traefik_proxy
        environment:
          - TZ=Asia/Shanghai
          - PUID=1000
          - PGID=1000
          # - MDC_USERNAME=yirami
          # - MDC_PASSWORD=${MDC_PASSWD}
        volumes:
          - /path/to/your/mdc/config:/config
          - /path/to/your/mdc/media:/media
        # ports:
        #   - 9208:9208
        labels:
          - "traefik.enable=true"
          - "traefik.docker.network=traefik_proxy"
          - "traefik.http.routers.mdc.rule=Host(`mdc.aio.yirami.xyz`)"
          - "traefik.http.routers.mdc.entrypoints=websecure"
          - "traefik.http.routers.mdc.tls=true"
          - "traefik.http.routers.mdc.tls.certresolver=le"
          - "traefik.http.routers.mdc.middlewares=authelia-auth"
          - "traefik.http.services.mdc.loadbalancer.server.port=9208"
        depends_on:
          flaresolverr:
            condition: service_healthy
        deploy:
          resources:
            limits:
              cpus: '1.0'
              memory: 512M
        healthcheck:
          test: ["CMD-SHELL", "wget --spider -q http://localhost:9208/ || exit 1"]
          interval: 30s
          timeout: 10s
          retries: 5
          start_period: 30s
    
      flaresolverr:
        image: flaresolverr/flaresolverr:latest
        container_name: flaresolverr
        restart: unless-stopped
        networks:
          - traefik_proxy
        environment:
          - LOG_LEVEL=info
          - TZ=Asia/Shanghai
          - PROXY_URL=http://aio.mihomo.yirami.xyz:10808
        deploy:
          resources:
            limits:
              cpus: '0.5'
              memory: 512M
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost:8191/"]
          interval: 10s
          timeout: 5s
          retries: 3
          start_period: 15s

小破解

v1.36.0 passed

docker compose stop mdc

docker cp mdc:/usr/local/bin/mdc_ng_app /tmp/mdc_ng_app

# 关闭每一小时自动 ping 所有数据源
printf '\xc3' | dd of=/tmp/mdc_ng_app bs=1 seek=$((0xe2cc10)) conv=notrunc
# 关闭了对 tt-013 的访问
printf '\xc3' | dd of=/tmp/mdc_ng_app bs=1 seek=$((0x1DF91D0)) conv=notrunc

docker cp /tmp/mdc_ng_app mdc:/usr/local/bin/mdc_ng_app
docker compose start mdc
启动服务
# 后台启动
docker compose up -d

# 更新并后台启动
docker compose pull && docker compose up -d

维护

版本升级

数据集管理

迁移

问题的起因是最初使用 SCALE Apps 功能时,将 Kubernets(k3s) 存储初始化在存储池的根目录,即 ix-applications 数据集,而该存储池的根目录还同时创建了多个其它数据集。此前,为了方便都是将该存储池直接通过 SAMBA 共享,而从 SCALE 22.02 升级到 SCALE 23.10 后提示不允许将该数据集通过 SAMBA 共享,因此考虑在存储池下重新建立一个数据集,并将原来的多个数据集迁移到该新建数据集下,分享时仅分享该新建数据集。

同一存储池内的数据集迁移(路径层次更改)可以使用 rename 命令。它是一个原子操作重命名,可以直接修改数据集的路径名,且完全保留原数据集属性。

一个典型的操作流程如下:

# 1) 确认数据集结构
zfs list -r <your_pool_name>
# 2) 停止相关的共享、应用服务
# 3) 创建临时快照(可选)
zfs snapshot -r <your_pool_name>/<your_dataset>@before_migration

# 4) 迁移
zfs rename <your_pool_name>/<your_dataset> <your_pool_name>/<new_dataset>/<your_dataset>
# 5) 权限检查(NFSv4 or Unix?)
# 6) 修改依赖的共享、应用路径
# 7) 恢复共享及应用

参考