如何通過容器搭建穩定可靠的私有網路硬碟(NextCloud)

2020-08-09 21:43:45

本文使用「署名 4.0 國際 (CC BY 4.0)」許可協定,歡迎轉載、或重新修改使用,但需要註明來源。 署名 4.0 國際 (CC BY 4.0)

本文作者: 蘇洋

建立時間: 2020年08月09日
統計字數: 8238字
閱讀時間: 17分鐘閱讀
本文鏈接: https://soulteary.com/2020/08/09/use-docker-to-build-a-stable-and-reliable-private-network-disk.html


如何通過容器搭建穩定可靠的私有網路硬碟(NextCloud)

前一陣 SIGIR 2020 國際資訊檢索研究和發展大會有一個有意思的需求,需要支援幾百位國內外學者能夠快速上傳自己的會議視訊,並支援對視訊進行快速的網路分發(線上播放)。

考慮到網路存取品質和檔案外連播放訴求,我們所熟知的成熟的國內網路硬碟服務被排除在外,又因爲國內存取海外網路硬碟服務不暢快,所以海外網路硬碟也被排除在外。我們之前常常使用的 SKYNAS (羣暉)映象因爲線上版本對人數有限制,所以也不能解決這個需求。

於是自建一個簡單的網路硬碟服務的需求也被提上了日程。

選型思考

在做選型的時候,也遇到了一些客觀限制。

由於有外連播放訴求,需要讓檔案能夠直接對外提供服務,並需要考慮頻寬限制和儲存、流量成本,所以最好能夠將檔案上傳至物件儲存,或者直接使用 CDN 對外公開存取。最起碼能夠支援使用 API / CLI 進行同步。

由於存在多使用者視訊檔的上傳/更新管理,所以應用最好能夠支援 OAuth 授權自動建立賬號,或者支援 API / CLI 進行使用者建立,減少人爲幹預和「客服」環節。

由於我們需要同時提供全球使用者使用,所以程式最好還能夠根據地區額外提供不同的存取地址,讓使用者自主選擇近源存取,避免 CDN 排程出現意外狀況。

由於視訊上傳者來自全球各地,所以視訊內容需要後期審覈人員參與內容檢查,檔案至少要能夠提供完整的審覈列表。

綜合各種因素之後,我們選擇了 OwnCloud 的開源版本 NextCloud。

步驟一:基礎搭建

預設搭建一個 NextCloud 網路硬碟並不難,使用下面 下麪的 compose 設定,可以分分鐘啓動一個屬於你的網路硬碟。(慣例使用 Traefik

version: "3.6"

services:

  nextcloud:
    image: nextcloud:19.0.1
    restart: always
    expose:
      - 80
    volumes:
      # Linux 環境下使用
      # - /etc/localtime:/etc/localtime:ro
      # - /etc/timezone:/etc/timezone:ro
      - ./data:/var/www/html/data:rw
    extra_hosts:
      - "nextcloud.lab.com:127.0.0.1"
      - "nextcloud-cn.lab.com:127.0.0.1"
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
      - "traefik.http.routers.www-nextcloud.entrypoints=http"
      - "traefik.http.routers.www-nextcloud.rule=Host(`nextcloud.lab.com`,`nextcloud-cn.lab.com`)"
      - "traefik.http.routers.www-nextcloud.middlewares=https-redirect@file"
      - "traefik.http.routers.ssl-nextcloud.entrypoints=https"
      - "traefik.http.routers.ssl-nextcloud.tls=true"
      - "traefik.http.routers.ssl-nextcloud.rule=Host(`nextcloud.lab.com`,`nextcloud-cn.lab.com`)"
      - "traefik.http.routers.ssl-nextcloud.middlewares=content-compress@file"
      - "traefik.http.services.www-nextcloud-backend.loadbalancer.server.scheme=http"
      - "traefik.http.services.www-nextcloud-backend.loadbalancer.server.port=80"

networks:
  traefik:
    external: true

這裏例子中,我們使用 Traefik 系結了兩個域名到 NextCloud ,分別是:

  • nextcloud.lab.com
  • nextcloud-cn.lab.com

應用啓動之後,存取任意域名即可開始應用安裝,因爲要滿足「使用者自主選擇近源」站點存取,所以我們使用 nextcloud-cn.lab.com 進行安裝。

默认安装界面

應用預設使用的數據庫爲 SQLite,可以滿足單人使用,但是在多人讀寫場景下,我們需要考慮數據安全,使用 MySQL 進行替換,在設定中新增下面 下麪的內容,重新啓動應用即可。

version: "3.6"

services:

  nextcloud:
    image: nextcloud:19.0.1
    ...

  db:
    image: mariadb
    container_name: database
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    restart: always
    volumes:
      - ./db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=nextcloud
      - MYSQL_PASSWORD=nextcloud
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
    networks:
      - traefik

networks:
  traefik:
    external: true

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-06dBT4R5-1596980561218)(https://attachment.soulteary.com/2020/08/09/database-setting.png)]

如果你在安裝介面勾選了安裝辦公應用將能夠看到應用安裝介面。

应用安装界面

一切就緒之後,會看到歡迎介面。

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-NH404hr4-1596980561221)(https://attachment.soulteary.com/2020/08/09/welcome.png)]

步驟二:設定健康檢查,限制輸出日誌

爲了保障應用的健康執行,我們需要新增健康檢查指令碼,讓應用能夠在異常退出的時候嘗試自我恢復。

因爲程式除了會儲存檔案日誌外,還會持續在標準輸出中產生日誌,所以我們也需要對其標準輸出日誌進行限制,避免磁碟空間雙倍浪費。

version: "3.6"

services:

  nextcloud:
    image: nextcloud:19.0.1
    restart: always
    ...
    healthcheck:
      test: ["CMD-SHELL", "curl -f localhost/status.php || exit 1"]
      interval: 5s
      retries: 12
    logging:
        driver: "json-file"
        options:
            max-size: "1m"

  db:
    image: mariadb
    ...
    healthcheck:
      test: mysqladmin ping -h localhost -p$$MYSQL_ROOT_PASSWORD && test '0' -eq $$(ps aux | awk '{print $$11}' | grep -c -e '^mysql$$')
      interval: 5s
      retries: 12
    logging:
        driver: "json-file"
        options:
            max-size: "1m"

步驟三:獲取程式設定

不論是進入容器拷貝出當前設定,還是使用 docker cp 命令將設定直接複製到宿主機,當程式安裝完畢之後,預設的設定會類似這樣 config/config.php

<?php
$CONFIG = array (
  'htaccess.RewriteBase' => '/',
  'memcache.local' => '\\OC\\Memcache\\APCu',
  'apps_paths' => 
  array (
    0 => 
    array (
      'path' => '/var/www/html/apps',
      'url' => '/apps',
      'writable' => false,
    ),
    1 => 
    array (
      'path' => '/var/www/html/custom_apps',
      'url' => '/custom_apps',
      'writable' => true,
    ),
  ),
  'instanceid' => 'oc12d1pw63hc',
  'passwordsalt' => 'B9Wt09NV2wWOCGr+bFCOelMrQ1nmiJ',
  'secret' => '6kHNmytBYJUPp3ee9L0NYBE+xnGtPTqlzAAUQ4sjkCNjg04c',
  'trusted_domains' => 
  array (
    0 => 'nextcloud-cn.lab.com',
  ),
  'datadirectory' => '/var/www/html/data',
  'dbtype' => 'mysql',
  'version' => '19.0.1.1',
  'overwrite.cli.url' => 'http://nextcloud-cn.lab.com',
  'dbname' => 'nextcloud',
  'dbhost' => 'database',
  'dbport' => '',
  'dbtableprefix' => 'oc_',
  'mysql.utf8mb4' => true,
  'dbuser' => 'nextcloud',
  'dbpassword' => 'nextcloud',
  'installed' => true,
);

當有了設定之後,我們接下來就可以繼續進行定製化設定了。

步驟四:支援多個域名,以及全站加速

應用預設只支援單個域名存取,當我們使用我們預期使用的 CDN 域名或者其他區域的域名進行存取的時候,會看到「通過不被信任的域名存取」的警告,並無法存取相關資原始檔和網路硬碟介面。

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-qtXAonOS-1596980561222)(https://attachment.soulteary.com/2020/08/09/trusted_domains.png)]

這時我們需要修改組態檔中的 trusted_domains 欄位,將所有域名新增進去:

<?php
$CONFIG = array (
  'trusted_domains' => 
  array (
    0 => 'nextcloud-cn.lab.com',
    1 => 'nextcloud.lab.com',
  ),
...

然後將組態檔掛載到容器中:

version: "3.6"

services:

  nextcloud:
    image: nextcloud:19.0.1
    restart: always
    expose:
      - 80
    volumes:
	  ...
      - ./config.php:/var/www/html/config/config.php:rw
...

現如今的不論國內國外, CDN 產品早已支援「全站加速」模式,所以我們只需要將 CDN 加速域名和「區域存取」域名進行區分,即可解決「上/下行頻寬低成本擴容」、「區域加速存取」的需求。

這裏還可以做進一步優化,將使用者根據區域進行分堆,然後將上傳檔案從不同的區域分別同步於 OSS,再通過 OSS 搭配不同區域的 CDN 進行區域加速存取(推薦使用)。

步驟五:修改組態檔執行模式

一切就緒後,我們啓動應用,會發現程式無法正常執行,臨時去掉健康檢查後,我們會看到下面 下麪的提示。
[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-GB6Wv4O7-1596980561223)(https://attachment.soulteary.com/2020/08/09/dir_permissions.png)]

解決方法也很簡單,無需修改容器和啓動指令碼,只需要在組態檔中再多新增一行內容:

<?php
$CONFIG = array (
  'config_is_read_only' => true,

然後再次啓動應用,使用非安裝域名存取,可以看到正常的登錄介面。

使用非安装域名可以正常访问

步驟六:去除使用者目錄預設檔案

應用會在使用者建立第一次登錄時初始化使用者目錄。並在目錄中準備使用手冊、範例檔案,對於一場嚴肅的學術會議而言,這些內容最好去掉,可以省掉一些不必要的麻煩。

默认安装完毕后的示例文件

這裏需要修改設定爲:

<?php
$CONFIG = array (
  'skeletondirectory' => '',
...

步驟六:支援 SLB 等HTTPS 閘道器代理

之前的文章中提到過我們的 HTTPS 最佳實踐,將 HTTPS 服務部分挪至 SLB 閘道器處統一管理,應用一律提供 HTTP 介面,所以這裏需要多新增一句設定,讓服務支援被 HTTPS 閘道器進行代理:

<?php
$CONFIG = array (
  'overwriteprotocol' => 'https',
...

步驟七:批次建立/刪除使用者

NextCloud 支援 OAuth Server 模式,但是卻不支援 OAuth Client 模式,所以我們並不能直接將其和我們現有的 OAuth Server 關聯在一起,所以這裏就要尋找支援「程式設計式」操作使用者的介面,或者改一個介面出來了。

慶幸的是程式自帶一個 CLI ,支援操作使用者資源

occ user:add
occ user:delete
...

這裏可以使用你熟悉的語言,做一個 OAuth Proxy Server,在容器外操作 NextCloud 關鍵命令如下:

# 建立使用者
OC_PASS=GENERATE_BY_YOUR_APP php /var/www/html/occ user:add 'username' --password-from-env --group='username' --display-name='username'
# 刪除使用者
php /var/www/html/occ user:delete username

最終設定

爲了方便「伸手黨」,這裏將上面的設定彙總,最終的 compose 設定如下:

version: "3.6"

services:

  nextcloud:
    image: nextcloud:19.0.1
    restart: always
    expose:
      - 80
    volumes:
      # Linux 環境下使用
      # - /etc/localtime:/etc/localtime:ro
      # - /etc/timezone:/etc/timezone:ro
      - ./data:/var/www/html/data:rw
      - ./config.php:/var/www/html/config/config.php:rw
    extra_hosts:
      - "nextcloud.lab.com:127.0.0.1"
      - "nextcloud-cn.lab.com:127.0.0.1"
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
      - "traefik.http.routers.www-nextcloud.entrypoints=http"
      - "traefik.http.routers.www-nextcloud.rule=Host(`nextcloud.lab.com`,`nextcloud-cn.lab.com`)"
      - "traefik.http.routers.www-nextcloud.middlewares=https-redirect@file"
      - "traefik.http.routers.ssl-nextcloud.entrypoints=https"
      - "traefik.http.routers.ssl-nextcloud.tls=true"
      - "traefik.http.routers.ssl-nextcloud.rule=Host(`nextcloud.lab.com`,`nextcloud-cn.lab.com`)"
      - "traefik.http.routers.ssl-nextcloud.middlewares=content-compress@file"
      - "traefik.http.services.www-nextcloud-backend.loadbalancer.server.scheme=http"
      - "traefik.http.services.www-nextcloud-backend.loadbalancer.server.port=80"
    # healthcheck:
    #   test: ["CMD-SHELL", "curl -f localhost/status.php || exit 1"]
    #   interval: 5s
    #   retries: 12
    logging:
        driver: "json-file"
        options:
            max-size: "1m"

  db:
    image: mariadb
    container_name: database
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    restart: always
    volumes:
      - ./db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=nextcloud
      - MYSQL_PASSWORD=nextcloud
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
    networks:
      - traefik
    healthcheck:
      test: mysqladmin ping -h localhost -p$$MYSQL_ROOT_PASSWORD && test '0' -eq $$(ps aux | awk '{print $$11}' | grep -c -e '^mysql$$')
      interval: 5s
      retries: 12

networks:
  traefik:
    external: true

NextCloud 最終使用的設定內容:

<?php
$CONFIG = array (
  'htaccess.RewriteBase' => '/',
  'memcache.local' => '\\OC\\Memcache\\APCu',
  'apps_paths' => 
  array (
    0 => 
    array (
      'path' => '/var/www/html/apps',
      'url' => '/apps',
      'writable' => false,
    ),
    1 => 
    array (
      'path' => '/var/www/html/custom_apps',
      'url' => '/custom_apps',
      'writable' => true,
    ),
  ),
  'instanceid' => 'oc12d1pw63hc',
  'passwordsalt' => 'B9Wt09NV2wWOCGr+bFCOelMrQ1nmiJ',
  'secret' => '6kHNmytBYJUPp3ee9L0NYBE+xnGtPTqlzAAUQ4sjkCNjg04c',
  'trusted_domains' => 
  array (
    0 => 'nextcloud-cn.lab.com',
    1 => 'nextcloud.lab.com',
  ),
  'config_is_read_only' => true,
  'default_language' => 'en',
  'datadirectory' => '/var/www/html/data',
  'dbtype' => 'mysql',
  'version' => '19.0.1.1',
  'overwrite.cli.url' => 'http://nextcloud-cn.lab.com',
  'dbname' => 'nextcloud',
  'dbhost' => 'database',
  'dbport' => '',
  'dbtableprefix' => 'oc_',
  'mysql.utf8mb4' => true,
  'dbuser' => 'nextcloud',
  'dbpassword' => 'nextcloud',
  'installed' => true,
);

最後

NextCloud 是一款值得深挖的網路硬碟程式,設定非常靈活,應用支援的設定項也比較合理。

或許有一天,我會使用它替換掉正在使用的 SkyNAS 和一些臨時性的「圖牀/網路硬碟」吧。

–EOF


我現在有一個小小的折騰羣,裏面聚集了一些喜歡折騰的小夥伴。

在不發廣告的情況下,我們在裏面會一起聊聊軟體、HomeLab、程式設計上的一些問題,也會在羣裡不定期的分享一些技術沙龍的資料。

喜歡折騰的小夥伴歡迎掃碼新增好友。(請註明來源和目的,否則不會通過審覈)

關於折騰羣入羣的那些事