使用docker-compose搭建nextcloud+Nginx+MySQL+Redis

需要配置可以直接跳到docker-compose

前言

去年使用闲置的阿里云小鸡给协会搭建了一个公用的网盘,采用的是nextcloud作为框架,选择nextcloud的原因主要是因为它可以使用docker进行部署,而且又是开源的,没有免费版、捐赠版或者企业版之类的版本区别,只要能够部署,那就能用。

之前部署的时候匆匆忙忙,单纯为了抓紧时间上线就赶鸭子上架,搭建了一个最简陋的docker版nextcloud,虽然用起来没有什么问题,但是每次自己访问都会感觉到性能捉急,想要改善一下现状,利用有限的硬件资源发挥最大的性能。

所以最近,挑了一个大家不咋使用的时间把网盘下线了一个星期,升级了网盘的部署方式和添加了一些功能。

稍微带过一下安装docker的方法

以Ubuntu 20.04为例,其他操作系统可以自行查找资料。

  • 首先是卸载原有的Docker。这里要讲一个题外话,就是docker在老版本中是叫做Docker的,新版就把大写D改为了小写的d。

    1
    2
    3
    4
    5
    apt-get remove docker \
    docker-engine \
    docker.io \
    containerd \
    runc
  • 安装新Docker

    1
    2
    3
    4
    # 官方脚本 使用阿里云源
    curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
    # 或国内daocloud源
    curl -sSL https://get.daocloud.io/docker | sh
  • 而本文需要使用docker-compose,所以这里还要安装一下,也比较简单,执行脚本就行了。

    1
    2
    3
    4
    5
    6
    7
    8
    # 下载 Docker Compose 的当前稳定版本
    sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    # 下载 Docker Compose 的当前稳定版本
    sudo chmod +x /usr/local/bin/docker-compose
    # 创建软链
    sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
    # 测试是否安装成功
    docker-compose --version

    但是使用以上方法安装比较繁琐,也比较难以管理,所以我推荐使用python的pip安装。

    1
    2
    3
    4
    # 使用 pip 安装 docker-compose
    sudo pip install docker-compose
    # 测试是否安装成功
    docker-compose --version

原有的单容器nextcloud搭建方法

docker的优越性想必都比较清楚了,独立运维、即开即用、不影响原有环境等等。

如果本地机器性能比较高,并且使用的人数、压力都没有太大的话,那么使用nextcloud独立容器的安装就足够了。

nextcloud独立容器版本使用的是apache做web服务器,自带SQLite作为数据库,也算是够用。

下面给一下单容器安装nextcloud的运行命令:

1
2
3
4
5
6
docker run -d \
--restart=always \
--name nextcloud \
-p 7788:80 \
-v ~/nextcloud:/var/www/html \
docker.io/nextcloud

稍微解释一下这几个参数:

1
2
3
4
5
6
7
run                   # 运行镜像创建容器示例,后面跟着创建容器参数
-d # 在后台运行
--restart=always # 总是随着宿主机重启而启动
--name nextcloud # 容器的名字,可以不用解释吧
-p 7788:80 # 将容器的80端口映射到宿主机的7788端口
-v ~/nextcloud:/var/www/html # 将容器的/var/www/html映射到宿主机的~/nextcloud
docker.io/nextcloud # 创建容器需要用到的镜像

等待docker执行创建容器实例完成后,则可以通过http://ip:7788来访问nextcloud了。但是这不是本文的重点,本文的目标是一台满血的nextcloud


所谓满血

自己搭建过nextcloud的人可能都知道,nextcloud不仅可以在默认配置下使用,还可以通过加入各种各样的底层组件来提高它运行的性能。

而这次搭建的这台nextcloud则是与单镜像搭建不同,采用了:

  1. fpm——fastCgi作为呈现层——版本的nextcloud
  2. nginx作为前置反向代理服务器提供https安全
  3. mysql/mariadb作为数据库,提高数据可靠性以及读写性能
  4. redis作为热点数据缓存服务器以及文件锁管理器

通过加入这些组件可以提高在多用户处理场景时系统的处理能力。

依赖关系

由于这个搭建思路并不是nextcloud官方提供的解决方案(本身也不应该官方去解决这种事情),所以需要自己配置多个容器并将其连接起来,使其之间可以互相访问和操作,运用每个组件的优势提高整体系统的性能。

本次一共会使用到以下几个镜像:

  1. mariadb(目前mysql都过渡到mariadb了)
  2. redis
  3. nextcloud:fpm
  4. nginx

建议提前挂代理通过docker pull来下载,以免搭建的时候还要去拉去镜像而占用过多时间。

1
2
3
4
5
# 配置代理
# 例如
export ALL_PROXY=socks5://127.0.0.1:1080 # 前提是在1080端口运行了你自己配置的代理服务,不然的话建议使用国内的镜像源
# 下载镜像
docker pull mariadb redis nextcloud:fpm nginx

更换镜像源可以看看这篇Docker中国源 - 简书

其中需要明确一下每个容器在这一套系统中扮演的角色。

nextcloud作为此次的主角,也就是主体业务(app),它在存取数据的时候是会用到数据库(db)和缓存(cache)系统的,那么可以得出nextcloud依赖于mysql和redis。

而mysql和redis之间各司其职,其中一个挂了不会影响到另一个,所以优先级相同,之间没有依赖关系。

nginx作为代理业务和用户之间沟通的主体,首先是需要业务(app)正常运行才能够正常提供它自己本身的服务,所以nginx依赖于nextcloud。

1
2
3
4
5
6
7
# 依赖关系如下
/-> mysql
nginx -> nextcloud -|
\-> redis

# 启动顺序则需要反过来
redix&mysql -> nextcloud -> nginx

docker-compose配置文件

docker-compose是一个代理用户去管理docker的一个工具,使用docker-compose.yml配置文件就可以系统、方便地部署多容器项目,因为我们这次搭建的nextcloud也是基于多容器的,所以使用docker-compose进行部署和调试以及重建最为方便。

完整的docker-compose配置参数文档可以参考下面的这个文档,讲的还是比较细致,推荐给要深入docker-compose的同学学习。

Docker Compose 配置文件详解 - 简书

以下就是本文输出的docker-compose配置文件。

注:参数的解释会通过备注的形式写在配置中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
version: '3.4'
services:

db: # 这里只是给每个容器单独配置一个名称
image: mariadb # 具体的镜像名称,可以使用“:”指定镜像的版本
restart: unless-stopped # 重启的选项,分为no、on-failure、on-failure:x、always、unless-stopped,具体可以自行搜索查看区别
expose:
# expose仅将指定的端口暴露给links的容器,而不对宿主机开放。
# 和ports的区别在于,ports可以映射宿主机别的端口到容器中。
- "3306"
volumes:
# volumes指的是将宿主机的路径映射到容器中的指定位置
- ./db:/var/lib/mysql
environment:
# environment可以对容器创建指定多个环境变量
- MYSQL_ROOT_PASSWORD=root_password # 这里配置root密码
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=user_name # 这里配置一个非root账户给nextcloud使用
- MYSQL_PASSWORD=user_password # 这里配置上面那个账号的密码

cache:
image: redis
restart: unless-stopped
expose:
- "6379"
volumes:
- ./cache:/data
command: redis-server --requirepass 'redis_password' # 这里的redis_password换成你要配置的redis密码
# command指的是启动容器后代替默认启动指令来启动服务的指令

app:
image: nextcloud:fpm
restart: unless-stopped
expose:
- "9000"
volumes:
- ./app/html:/var/www/html
- ./app/data:/var/www/html/data
- ./app/config:/var/www/html/config
- ./app/custom_apps:/var/www/html/custom_apps
links:
# links将容器与当前容器链接起来,以使得当前容器可以访问目标容器expose的端口
# 格式为 容器的原名:映射到当前容器中的名称
- db:db
- cache:cache
depends_on:
# 依赖的容器列表,只有这些容器都成功启动了,才会启动当前容器
- db
- cache

proxy: # 叫做proxy是因为是作为代理来提供服务
image: nginx
restart: unless-stopped
expose:
- "80"
ports:
# ports可将容器内的端口映射到宿主机上
# 这里是将容器的443端口映射到宿主机的7788端口
- 7788:443
volumes:
- ./app/html:/var/www/html
- ./proxy/conf.d:/etc/nginx/conf.d:ro
- ./proxy/ssl_certs:/etc/nginx/ssl_certs:ro
links:
- app:app
depends_on:
- app

把这些内容保存到docker-compose.yml文件中,然后将其放置在某个文件夹中,例如~/super_nextcloud/,我们之后就在这个路径下做后续的操作。

Nginx配置文件

接着我们要准备一下nginx的配置。需要创建两个文件夹,一个是./proxy/conf.d,一个是./proxy/ssl_certs
其中conf.d放置nginx的配置文件,可以起名叫做nextcloud.confssl_certs放置域名对应的SSL证书的pem和key文件。

1
mkdir -p ./proxy/conf.d ./proxy/ssl_certs

具体可以参考以下的配置来写nextcloud.conf,其中域名和SSL证书文件的名字需要替换成你自己的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
upstream php-handler {
server app:9000;
}

server {
listen 80;
listen [::]:80;
server_name 这里填写自己的域名;
# enforce https
return 301 https://$server_name:443$request_uri;
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name 这里填写自己的域名;

# Use Mozilla's guidelines for SSL/TLS settings
# https://mozilla.github.io/server-side-tls/ssl-config-generator/
# NOTE: some settings below might be redundant
ssl_certificate /etc/nginx/ssl_certs/这里填写SSL证书的名字.pem;
ssl_certificate_key /etc/nginx/ssl_certs/这里填写SSL证书的名字.key;

# Add headers to serve security related headers
# Before enabling Strict-Transport-Security headers please read into this
# topic first.
#add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
#
# WARNING: Only add the preload option once you read about
# the consequences in https://hstspreload.org/. This option
# will add the domain to a hardcoded list that is shipped
# in all major browsers and getting removed from this list
# could take several months.
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
add_header Referrer-Policy no-referrer;
add_header Strict-Transport-Security 15552000;
#add_header X-Frame-Options SAMEORIGIN;


# Remove X-Powered-By, which is an information leak
fastcgi_hide_header X-Powered-By;

# Path to the root of your installation
root /var/www/html;



# The following 2 rules are only needed for the user_webfinger app.
# Uncomment it if you're planning to use this app.
#rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
#rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;

# The following rule is only needed for the Social app.
# Uncomment it if you're planning to use this app.
#rewrite ^/.well-known/webfinger /public.php?service=webfinger last;

location = /.well-known/carddav {
return 301 $scheme://$host:$server_port/remote.php/dav;
}
location = /.well-known/caldav {
return 301 $scheme://$host:$server_port/remote.php/dav;
}

# set max upload size
client_max_body_size 512M;
fastcgi_buffers 64 4K;

# Enable gzip but do not remove ETag headers
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

# Uncomment if your server is build with the ngx_pagespeed module
# This module is currently not supported.
#pagespeed off;

location / {
rewrite ^ /index.php$request_uri;
}

location ~ ^\/(?:build|tests|config|lib|3rdparty|templates|data)\/ {
deny all;
}
location ~ ^\/(?:\.|autotest|occ|issue|indie|db_|console) {
deny all;
}

location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+)\.php(?:$|\/) {
fastcgi_split_path_info ^(.+?\.php)(\/.*|)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param HTTPS on;
# Avoid sending the security headers twice
fastcgi_param modHeadersAvailable true;
# Enable pretty urls
fastcgi_param front_controller_active true;
fastcgi_pass php-handler;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
}

location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) {
try_files $uri/ =404;
index index.php;
}

# Adding the cache control header for js, css and map files
# Make sure it is BELOW the PHP block
location ~ \.(?:css|js|woff2?|svg|gif|map)$ {
try_files $uri /index.php$request_uri;
add_header Cache-Control "public, max-age=15778463";
# Add headers to serve security related headers (It is intended to
# have those duplicated to the ones above)
# Before enabling Strict-Transport-Security headers please read into
# this topic first.
#add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
#
# WARNING: Only add the preload option once you read about
# the consequences in https://hstspreload.org/. This option
# will add the domain to a hardcoded list that is shipped
# in all major browsers and getting removed from this list
# could take several months.
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
add_header Referrer-Policy no-referrer;

# Optional: Don't log access to assets
access_log off;
}

location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap)$ {
try_files $uri /index.php$request_uri;
# Optional: Don't log access to other assets
access_log off;
}
}

文件都准备好之后,文件结构如下:

1
2
3
4
5
6
7
8
super_nextcloud
├── docker-compose.yml
└── proxy
├── conf.d
│   └── nextcloud.conf
└── ssl_certs
├── SSL证书.key
└── SSL证书.pem

创建&启动容器

此时准备工作已经完成。使用docker-compose代理对docker的容器进行操作可以使用下面的命令:

1
2
3
4
5
docker-compose up -d    # 创建所有容器并按顺序启动
docker-compose down # 停止所有容器并删除
docker-compose stop # 停止所有容器
docker-compose start # 按顺序启动所有容器
docker-compose restart # 停止所有容器并按顺序启动所有容器

所以这里我们使用docker-compose up -d启动我们的服务。在docker-compose输出的提示中,所有目标都显示为done后,我们可以使用netstat查看nginx容器是否在监听7788端口:

1
2
❯ netstat -tnlp | grep 7788
tcp6 0 0 :::7788 :::* LISTEN 5155/docker-proxy

如果没有输出,那就是创建容器有问题。可以通过docker ps -a查看哪些容器在疯狂重启,然后通过docker logs [容器名or容器ID]进到这个容器中查看容器服务日志,自行排障。

如果有输出,并且和上面的内容大致相同,那么恭喜你,构建已经成功大半了。


配置项目

配置站点

从地址和端口进入站点,通常是https+域名+端口,如果是内网搭建并且不在意域名以及https的小锁头的话,则可以直接通过ip来访问,但是需要注意的是,在nginx中配置了特定域名后,它会检查访问时是否是正确的域名,如果不是正确的域名,那么就会拒绝访问网盘资源。

进入后,我们就需要进行网盘首次配置的一些操作,包括管理员的配置、数据库的配置、应用程序的配置等等。
其中需要注意的是,在数据库配置部分,数据库的地址需要填写我们在docker的links中映射marindb的主机名称,即db;而账号密码则是在配置数据库时制定的数据库用户密码以及数据库名称。

使用上一步配置好的管理员账号和密码登录网盘,点击头像进入设置,现将站点的参数配置好后再配置本账号的参数。

修改Nginx配置

由于在网站上线后,需要配置一系列安全参数,所以需要我们再次去修改一下Nginx的配置文件。

proxy/conf.d/nextcloud.conf中找到这行(也就是上面那个nginx配置的41行),将前面的注释解开,然后重启nginx容器即可(也可以直接使用docker-compose restart重启整个项目)。

1
2
3
4
5
6
7
8
#add_header X-Frame-Options SAMEORIGIN;

# 如果需要使用手机APP来访问的话,还需要将54 54 59行解开注释

#rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
#rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;

#rewrite ^/.well-known/webfinger /public.php?service=webfinger last;

配置缓存

由于nextcloud默认使用的缓存机制是APCu,我们需要到配置中修改其使用Redis作为缓存。

app/config/config.php中的内容按照如下方法改动:

1
2
3
4
5
6
7
8
9
10
//'memcache.local' => '\\OC\\Memcache\\APCu' // 用本地式缓存使用APCu // 注释这行 
'memcache.local' => '\\OC\\Memcache\\Redis',
'memcache.distributed' => '\\OC\\Memcache\\Redis', // 分布式缓存使用Redis
'memcache.locking' => '\\OC\\Memcache\\Redis', // 启用Redis缓存的文件锁
'redis' => array(
'host' => 'cache', // 这里和mariadb的逻辑相同,填写容器links时映射的主机名
'port' => 6379,
'password' => 'redis_password' // 这是之前在配置docker-compose时配置的redis密码
),
'filelocking.enabled' => 'true',

使用以下命令进入nextcloud的docker容器,并将文件归属确认改为www-data:

1
2
3
4
5
6
docker exec -it [nextcloud容器的容器ID] /bin/bash
# 进入docker容器之后
ls -l /var/www/html/config/config.php
-rw-r----- 1 www-data www-data 1646 Mar 30 07:27 /var/www/html/config/config.php
# 如果提示所有权不是www-data,那么就要改回www-data
chown www-data:www-data /var/www/html/config/config.php

这时候再去访问nextcloud的页面,就是在使用redis做缓存了。

配置cron定时任务

由于nextcloud内部的文件和配置会在用户操作过程中产生变化,所以需要启用一个定时任务去定期整理和归档这些数据到数据库或者应用到配置中。

先确定自己的nextcloud容器的容器ID或者容器名字,可以使用docker ps -a查看。

然后使用crontab -evim /etc/crontab打开crontab的配置进行编辑,加入如下内容:

1
2
# run nextcloud cron task every 5 min
*/5 * * * * docker exec -u 33 [容器ID或容器名] php -f /var/www/html/cron.php 2>&1 /dev/null

建议两个地方都加一次,避免配置不生效。


至此,站点的基本内容已经配置完毕,可以满足个人用于网盘、webdav等使用场景了。

手动安装拓展

nextcloud另一个吸引人的地方就是可以安装很多拓展,但是由于nextcloud是从github上安装拓展,国内的网络连接github又有许许多多的困难,所以直接在页面上点击安装按钮是绝对会报cURL的错的。

所以我想出了一个曲线救国的方法,虽然比直接点击安装要多了几步,但着实可行,实施起来也没有什么阻碍。(反倒是之前为了实现直接点页面安装而各种配置代理浪费了不少时间,而且还有搞不出来…)

手动安装拓展程序的步骤大致可以分为下面几个步骤:

  1. 确保当前客户机环境可通过代理访问github;
  2. 进入nextcloud的应用页面,找到自己想装的拓展应用;
  3. 点击想要安装的应用展开详情,进入“访问网站”链接,此时一般会去到插件的github页面或者nextcloud官方发布插件页面
  4. 进入github项目的release页面,下载最新的插件更新版本到当前本机。按照nextcloud的插件发布标准应该是一个tar.gz文件;
  5. 将下载下来的文件从nextcloud个人文件页面上传到网盘中;
  6. 进入nextcloud容器。docker exec -u 33 -it [container ID or container name] /bin/bash,其中[container ID or container name]要换成真实容器的ID或名字;
  7. 到路径下找到自己上传的拓展插件。例如/var/www/html/data/[username]/files/nextcloud_extension/metadata.tar.gz,其中[username]要换成用户的名字。这里装的是matadata插件,用于查看图片各种元数据的;
  8. 将其复制到/var/www/html/appscp /var/www/html/data/[username]/files/nextcloud_extension/metadata.tar.gz /var/www/html/apps
  9. 解压后删除源压缩文件cd /var/www/html/apps && tar -xzf metadata.tar.gz && rm metadata.tar.gz
  10. 进入nextcloud页面的【应用】-【已禁用的应用】页面,将其启用。

按照以上步骤来操作就可以做到实现手动安装插件的目的了。

总结

经历了这次搭建网盘,算是初级入门了docker容器的部署,以及docker-compose的配置入门。

只能说,在开始一个项目之前,还有许多东西需要实现考虑和准备好,做好缺陷预防。这样在真正上手做的时候,才会尽可能的少出差错,以及处变不惊。


后记

本来以为这个网盘搭好之后可以养老地给自己和协会使用几年,结果最近阿里云发了续费通知,上去一看发现之前的轻量级应用服务器学生机已经不在学生优惠计划中了(只剩下了一个OSS,基本没啥用),续费一年得1k多,所以只能另寻他处或者项目搁浅了,哎。。

Author: SmallXeon
Link: https://hexo.chensmallx.top/2021/04/08/nextcloud-on-docker/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.