如何通过容器搭建稳定可靠的私有网盘(NextCloud)
本文使用「署名 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
如果你在安装界面勾选了安装办公应用将能够看到应用安装界面。
一切就绪之后,会看到欢迎界面。
步骤二:配置健康检查,限制输出日志
为了保障应用的健康运行,我们需要添加健康检查脚本,让应用能够在异常退出的时候尝试自我恢复。
因为程序除了会保存文件日志外,还会持续在标准输出中产生日志,所以我们也需要对其标准输出日志进行限制,避免磁盘空间双倍浪费。
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 域名或者其他区域的域名进行访问的时候,会看到“通过不被信任的域名访问”的警告,并无法访问相关资源文件和网盘界面。
这时我们需要修改配置文件中的 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 进行区域加速访问(推荐使用)。
步骤五:修改配置文件运行模式
一切就绪后,我们启动应用,会发现程序无法正常运行,临时去掉健康检查后,我们会看到下面的提示。
解决方法也很简单,无需修改容器和启动脚本,只需要在配置文件中再多添加一行内容:
<?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 和一些临时性的“图床/网盘”吧。
- MYSQL常用SQL汇总
- Linux 下设置SVN DIFF
- 剑指 offer代码解析——面试题38数字在排序数组中出现的次数
- 剑指 offer代码解析——面试题37两个链表的第一个公共结点
- 剑指 offer代码解析——面试题34丑数
- 剑指 offer代码解析——面试题35第一个只出现一次的字符
- Python内置数据结构之集合
- 剑指 offer代码解析——面试题32统计1到n中1出现的次数
- 剑指 offer代码解析——面试题26复杂链表的复制
- Linux软件安装(二)——RPM与YUM
- linux软件安装(一)——源码安装
- 利用LUA协程实现FUTURE模式
- Python内置数据结构之字典(完整版)
- Maven使用详解
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- [数据结构] 队列的链式存储实现
- [数据结构] 两个有序线性表的合并
- Spring 简介,带你打开 Spring 的大门
- go test 测试用例那些事(二) mock
- [数据结构]链式存储: 多项式求和
- [Web] CSS 中 Display(显示) 与 Visibility(可见性)的区别与用法
- [数据结构] 二叉搜索树的CURD(增删改查)操作
- Python 为什么用 # 号作注释符?
- Logging with ElasticSearch, Kibana, ASP.NET Core and Docker
- 【DB笔试面试843】在Oracle中,解释GLOBAL_NAMES设为TRUE的用途。
- SpringBoot 返回 json 数据以及数据封装(万字长文)
- 内核级木马与病毒攻防:Linux可执行文件的ELF格式描述
- 「 思考 」 React Hooks 的设计哲学
- JMeter之Json提取器详解
- nodejs 图片处理工具 gm 的使用!