前一阵 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 和一些临时性的“图床/网盘”吧。
–EOF