本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2020年08月09日 统计字数: 8589字 阅读时间: 18分钟阅读 本文链接: 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](https://soulteary.com/tags/traefik.html)) ```yaml 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` 进行安装。 ![默认安装界面](https://attachment.soulteary.com/2020/08/09/default-install.png) 应用默认使用的数据库为 SQLite,可以满足单人使用,但是在多人读写场景下,我们需要考虑数据安全,使用 MySQL 进行替换,在配置中添加下面的内容,重新启动应用即可。 ```yaml 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 ``` ![数据库配置界面](https://attachment.soulteary.com/2020/08/09/database-setting.png) 如果你在安装界面勾选了安装办公应用将能够看到应用安装界面。 ![应用安装界面](https://attachment.soulteary.com/2020/08/09/app-install.png) 一切就绪之后,会看到欢迎界面。 ![安装完毕的欢迎界面](https://attachment.soulteary.com/2020/08/09/welcome.png) ## 步骤二:配置健康检查,限制输出日志 为了保障应用的健康运行,我们需要添加健康检查脚本,让应用能够在异常退出的时候尝试自我恢复。 因为程序除了会保存文件日志外,还会持续在标准输出中产生日志,所以我们也需要对其标准输出日志进行限制,避免磁盘空间双倍浪费。 ```yaml 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 '/', '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 域名或者其他区域的域名进行访问的时候,会看到“通过不被信任的域名访问”的警告,并无法访问相关资源文件和网盘界面。 ![默认只支持单个域名访问](https://attachment.soulteary.com/2020/08/09/trusted_domains.png) 这时我们需要修改配置文件中的 **trusted\_domains** 字段,将所有域名添加进去: ```php array ( 0 => 'nextcloud-cn.lab.com', 1 => 'nextcloud.lab.com', ), ... ``` 然后将配置文件挂载到容器中: ```yaml 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 进行区域加速访问(推荐使用)。 ## 步骤五:修改配置文件运行模式 一切就绪后,我们启动应用,会发现程序无法正常运行,临时去掉健康检查后,我们会看到下面的提示。 ![应用配置文件权限不正确](https://attachment.soulteary.com/2020/08/09/dir_permissions.png) 解决方法也很简单,无需修改容器和启动脚本,只需要在配置文件中再多添加一行内容: ```php true, ``` 然后再次启动应用,使用非安装域名访问,可以看到正常的登录界面。 ![使用非安装域名可以正常访问](https://attachment.soulteary.com/2020/08/09/normal-login.png) ## 步骤六:去除用户目录默认文件 应用会在用户创建第一次登录时初始化用户目录。并在目录中准备使用手册、示例文件,对于一场严肃的学术会议而言,这些内容最好去掉,可以省掉一些不必要的麻烦。 ![默认安装完毕后的示例文件](https://attachment.soulteary.com/2020/08/09/default-files.png) 这里需要修改配置为: ```php '', ... ``` ## 步骤六:支持 SLB 等HTTPS 网关代理 之前的文章中提到过我们的 HTTPS 最佳实践,将 HTTPS 服务部分挪至 SLB 网关处统一管理,应用一律提供 HTTP 接口,所以这里需要多添加一句配置,让服务支持被 HTTPS 网关进行代理: ```php 'https', ... ``` ## 步骤七:批量创建/删除用户 NextCloud 支持 OAuth Server 模式,但是却不支持 OAuth Client 模式,所以我们并不能直接将其和我们现有的 OAuth Server 关联在一起,所以这里就要寻找支持“编程式”操作用户的接口,或者改一个接口出来了。 庆幸的是程序自带一个 CLI ,支持操作用户资源 ```bash occ user:add occ user:delete ... ``` 这里可以使用你熟悉的语言,做一个 OAuth Proxy Server,在容器外操作 NextCloud 关键命令如下: ```bash # 创建用户 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 配置如下: ```yaml 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 '/', '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