将 ownCloud 迁移到 Docker 容器

信息
本文已于 2023 年 7 月 3 日全面翻新。新版配置更加简单,如有问题欢迎在评论区指出。

继之前将博客迁移到 Docker 容器后,最近由于我在 Vultr 的 ownCloud 服务器性能出现严重问题,甚至在同步时出现宕机,我终于抽空把 ownCloud 迁移到了 HostDare 的VPS。总体来说迁移过程是非常简单的,而 ownCloud 也提供了官方的 Docker 支持,下面我会分享从零开始到HTTPS加密等具体的步骤。

致谢:本文思路主要来自于官方手册官博文章、以及众多的 StackOverflow 问答,在此表示感谢。

  • 一台能胜任工作的 VPS
    • 什么叫“能胜任工作”?这里说点题外话,之前在 Vultr 的VPS只有1vCPU/250G HDD/1G的配置,这也导致了网页访问缓慢和突发需求时宕机。再加上 Vultr 家没有特别的网络线路,导致使用起来非常的糟心。
  • 本文使用 Ubuntu 22.04 LTS,当然别的发行版也没问题,毕竟是 Docker。但是配置的路径等差别就需要朱军自己来判断了
  • 防火墙开通 80、443 和 8080 端口
  • SSH sudo 访问权限到原始机器和目标机器
  • (可选)如果需要HTTPS的话,需要开通域名并解析到你的目标机器

ownCloud 官方的 Docker 容器中已经包含了 MariaDB 数据库和 Redis 缓存,配置完成后即可使用,无需额外操心。

这里可以打开官方文档,选择自己服务器的发行版,然后按照文档上的步骤进行安装。

请注意,如果你是以非 root 身份运行下面的脚本,安装完成后可以将自己加到 docker 用户组内,这样无需使用 sudo 也可以执行 Docker 命令

以 Ubuntu 为例:

1
2
sudo groupadd docker
sudo usermod -aG docker $USER

完成后重新登录或者重启机器,让新权限生效。

另外,可以将 Docker 设为自动启动,这样服务器重启后 Docker 会一起启动。

1
2
sudo systemctl enable docker.service
sudo systemctl enable containerd.service

在进一步操作之前,我们需要新建一个文件夹来存放配置文件。可以放在任何地方。

提示
下面所有的 example 等字样均为示例,需要替换成你自己的内容。
1
2
3
sudo mkdir -p /srv/owncloud
sudo chown $USER /srv/owncloud
cd /srv/owncloud

完成后,从官方 GitHub 下载 docker compose 配置文件并创建一个 .env 文件来安全储存需要的环境变量。

警告
务必修改管理员账号的密码,以免遭受攻击!
1
2
3
4
5
6
7
8
wget https://raw.githubusercontent.com/owncloud/docs-server/master/modules/admin_manual/examples/installation/docker/docker-compose.yml
cat << EOF > .env
OWNCLOUD_VERSION=latest
OWNCLOUD_DOMAIN=localhost:8080
ADMIN_USERNAME=admin
ADMIN_PASSWORD=example(务必要修改这个)
HTTP_PORT=8080
EOF

接下来运行 docker compose 并测试 ownCloud 是否正常工作

docker compose up -d

初次启动时可能需要一些时间,你可以通过执行以下命令来了解容器的运行状态

docker ps

当三个容器(ownCloud、MariaDB、Redis)的状态 (Status) 都显示 Healthy 的时候,就可以继续下一步了。

很简单,浏览器打开 example.com:8080(或者IP地址:8080),查看 ownCloud 是否正常运行。

正常情况下应该看到的页面

正常情况下应该看到的页面

到这里为止我们的 ownCloud 实例已经正常工作了。当然,如果这么简单的话我也不会专门写一篇文章。接下来我们要做的就是迁移数据并为我们的实例申请免费的 Let’s Encrypt 证书。

首先,将原始机器的 ownCloud 进入维护模式。

sudo -u www-data ./occ maintenance:mode --on

然后执行以下命令,导出 ownCloud 相关的数据库。回车后你会被要求输入数据库用户的密码。请根据自己服务器的实际情况修改用户名和数据库名称。

mysqldump --single-transaction -h localhost -u example -p exampleDB > owncloud-dbbackup_`date +"%Y%m%d"`.bak
忘了我的数据库用户、密码、数据库名字怎么办?
别担心,相关配置可以在 (ownCloud安装目录)/config/config.php 里找到。
相关字串有:dbnamedbhostdbuserdbpassword

如果你仔细观察官方提供的 yml 文件,你会发现 ownCloud 实例使用了一个 volume 命令创建和本地磁盘的映射,这样我们的文件就不会在 Docker 容器退出后消失。那么文件究竟在哪呢?下面是 yml 文件的一个节选:

1
2
3
4
services:
  owncloud:
    volumes:
      - files:/mnt/data

那么我们的文件在哪里呢?事实上他们被存放在了 /var/lib/docker/volumes/(ownCloud 实例名)_files。具体的文件夹名称似乎是根据你在第二步时候文件夹起的名字决定的(需要DP)。接下来的教程里我们假设 ownCloud 文件储存在 /var/lib/docker/volumes/example_files

由于我们新的 ownCloud 实例在 Docker 容器里,正常 bash 里的命令是不会影响容器的。我们需要先进入 ownCloud 容器的 bash。

首先,cd 到你 docker-compose.yml 的目录,然后执行

docker compose exec owncloud occ maintenance:mode --on
提示

如果我们需要和 ownCloud 容器交互,只需要

  1. cd 到 ownCloud docker-compose.yml 的目录
  2. 在命令前面加 docker compose exec owncloud

现在我们知道了复制的目的地,就可以开始着手复制了。

仔细观察以下不难发现,
原始机器的 (ownCloud安装目录)/data 对应的是
目标机器的 /var/lib/docker/volumes/example_files/_data/files
由于二者文件夹路径不一样,我们复制的时候要多加小心。

执行以下命令:

cd (ownCloud安装目录) 
rsync -azvP --exclude 'owncloud.log' data/* root@(目标机器IP):/var/lib/docker/volumes/example_files/_data/files

回车后,你会被要求输入目标机器 root 账户的密码。

复制的过程可能会有数十分钟甚至数小时,取决于你文件的大小、目标和原始机器的带宽等。不如开局 DOTA、去R6反反恐、下楼散个步… 你可以用同样的方法传输数据库的备份文件,在此不再赘述。

信息

rsync 是很多 Linux 发行版内置的一个同步/备份软件。简单解释一下参数:

-a:存档模式,等于 -rlptgoD。这样会复制子目录、保留软链接、权限、修改日期、用户组、拥有人、设备文件、特殊文件
-z:启用压缩。(虽然效果可能只是个位数百分比,但好歹也是宝贵的流量呀)
-v:众所周知的“啰嗦模式”
-P:保存部分传输的文件并显示进度
--exclude 'xxx':不要传输特定的文件/文件夹。在这里我们不需要传输 owncloud.log,节省可观的时间和流量。(你是不会相信我的log文件竟然有10G多大!)

首先修改以下文件:/var/lib/docker/volumes/example_files/_data/config/config.php 找到以下字串:passwordsaltsecret 。从原始机器的配置文件中复制并覆写这两个字串(否则用户将无法登录)。然后执行以下命令导入数据库:

docker compose exec owncloud mysql -h db -u owncloud -p owncloud < ~/owncloud-dbbackup_xxxxx.bak

你会被要求输入密码,默认是 owncloud

执行这个命令让服务器重新扫描所有用户的文件。否则你的文件不会出现在客户端:

docker compose exec owncloud occ files:scan --all

执行以下命令通知客户端服务器已经迁移:

docker-compose exec owncloud occ maintenance:data-fingerprint

经过以上步骤你的文件和配置应该已经完美导入到新的服务器了。

用户不见了?
这可能是数据库导出/导入不完全导致的。如果服务器只有少数几个人用的话,可能手工重新创建更快一些。只要用户名一致,文件就仍然可以读取。注意,创建用户名后可能要重新扫描文件他们才会出来。

本来呢我是想请出我们的老熟人 Nginx 的,但是经过研究发现有个更好、更方便的方案,下面给大家介绍一下。

首先创建一个 Traefik 的文件夹,类似我们 ownCloud 的文件夹,用来存放配置。接下来我们以 /srv/traefik 为例

1
2
3
sudo mkdir -p /srv/traefik && sudo chown $USER /srv/traefik
touch /srv/traefik/compose.yml
touch /srv/traefik/acme.json && chmod 600 /srv/traefik/acme.json

在 Docker 新建一个专用的“局域网”,这样可以分隔出反代网站的流量。(名字可以随便起,我们接下来用 traefik_proxy 作为示范。

docker network create traefik_proxy

编辑 /srv/traefik/compose.yml,复制进去以下内容

提示
务必把第 28 行的邮箱地址改成你自己的。
 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
version: '3'

services:
  proxy:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    # Here's the network we created:
    networks:
      - traefik_proxy
    command:
      # Only for development environment
      # - "--log.level=DEBUG"
      - "--api.insecure=true"
      # Get Docker as the provider
      - "--providers.docker=true"
      # Avoid that all containers are exposed
      - "--providers.docker.exposedbydefault=false"
      # Settle the ports for the entry points
      - "--entrypoints.web.address
      - "--entrypoints.web-secure.address=:443"
      # Settle the autentification method to http challenge
      - "--certificatesresolvers.myhttpchallenge.acme.httpchallenge=true"
      - "--certificatesresolvers.myhttpchallenge.acme.httpchallenge.entrypoint=web"
      # Uncomment this to get a fake certificate when testing
      #- "--certificatesresolvers.myhttpchallenge.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
      # Settle letsencrypt as the certificate provider
      - "--certificatesresolvers.myhttpchallenge.acme.email=example@contoso.com"
      - "--certificatesresolvers.myhttpchallenge.acme.storage=/acme.json"
    # The traefik entryPoints
    ports:
      - 80:80
      - 443:443
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock
      - type: bind
        source: ${PWD}/acme.json
        target: /acme.json

networks: 
  traefik_proxy: 
    external: true

下面稍微解释一下配置内容。

  • 第 10 行的 traefik_proxy 需要和之前创建的网络名称对应。
  • 31 行 - 33 行的 port 将主机的 80、443 端口映射到 Traefik 容器 80、443 端口,用作 HTTP 访问。
    • 事实上 Traefik 是用户和你服务器上各种服务/网站的一个中间人(代理)。每当用户访问你的网站时,Traefik 会根据用户请求的网址等条件将相对应的服务/网站呈现给用户。
      Traefik 示意图
      Image from Traefik.io
  • 16 行启用 Docker 反代,18 行默认禁用 Docker 代理,这样 Traefik 不会自动反代所有的 Docker 容器。Traefik 支持多种后端,包括 Docker、Kubernetes、静态文件等。通过第 36 行,我们把系统的 Docker socket 暴露给 Traefik,这样 Traefik 可以自动监听 Docker 容器的启动/停止等事件并配置反代。
  • 23 - 29 行配置了 Let’s Encrypt 的证书。Traefik 集成了 Let’s Encrypt,并且可以自动给所有反代的服务申请证书。 这里我们使用 HTTP Challenge,可以兼容 CloudFlare 等 CDN。
  • 最后三行使用了 traefik_proxy 这个“外部”网络。这里的外部不是指互联网,而是“这个docker-compose配置之外”。

接下来就是修改 ownCloud 的 yml 文件,接入 Traefik。由于修改地方比较多,在这里我贴上完整的 yml 文件,供大家参考。

提示
在这里要注意修改第 46、52 行里的网址。这里需要填入你 ownCloud 网站的地址。
 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
version: '3'

volumes:
  files:
    driver: local
  mysql:
    driver: local
  redis:
    driver: local

services:
  owncloud:
    image: owncloud/server:${OWNCLOUD_VERSION}
    container_name: owncloud_server
    restart: unless-stopped
    ports:
      - ${HTTP_PORT}:8080
    depends_on:
      - db
      - redis
    environment:
      - OWNCLOUD_DOMAIN=${OWNCLOUD_DOMAIN}
      - OWNCLOUD_DB_TYPE=mysql
      - OWNCLOUD_DB_NAME=owncloud
      - OWNCLOUD_DB_USERNAME=owncloud
      - OWNCLOUD_DB_PASSWORD=owncloud
      - OWNCLOUD_DB_HOST=db
      - OWNCLOUD_ADMIN_USERNAME=${ADMIN_USERNAME}
      - OWNCLOUD_ADMIN_PASSWORD=${ADMIN_PASSWORD}
      - OWNCLOUD_MYSQL_UTF8MB4=true
      - OWNCLOUD_REDIS_ENABLED=true
      - OWNCLOUD_REDIS_HOST=redis
    healthcheck:
      test: ["CMD", "/usr/bin/healthcheck"]
      interval: 30s
      timeout: 10s
      retries: 5
    volumes:
      - files:/mnt/data
    networks:
      - traefik_proxy
      - owncloud_internal
    labels:
      - "traefik.enable=true"
      # Get the routes from http
      - "traefik.http.routers.owncloud.rule=Host(`owncloud.example.com`)"
      - "traefik.http.routers.owncloud.entrypoints=web"
      # Redirect these routes to https
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      - "traefik.http.routers.owncloud.middlewares=redirect-to-https@docker"
      # Get the routes from https
      - "traefik.http.routers.owncloud-secured.rule=Host(`owncloud.example.com`)"
      - "traefik.http.routers.owncloud-secured.entrypoints=web-secure"
      # Apply autentificiation with http challenge
      - "traefik.http.routers.owncloud-secured.tls=true"
      - "traefik.http.routers.owncloud-secured.tls.certresolver=myhttpchallenge"

  db:
    image: mariadb:10.6
    container_name: owncloud_mariadb
    restart: unless-stopped
    environment:
      - MARIADB_ROOT_PASSWORD=owncloud
      - MARIADB_USERNAME=owncloud
      - MARIADB_PASSWORD=owncloud
      - MARIADB_DATABASE=owncloud
    command: ["--max-allowed-packet=128M", "--innodb-log-file-size=64M"]
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-u", "root", "--password=owncloud"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - mysql:/var/lib/mysql
    networks:
      - owncloud_internal

  redis:
    image: redis:6
    container_name: owncloud_redis
    restart: unless-stopped
    command: ["--databases", "1"]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - redis:/data
    networks:
      - owncloud_internal

networks:
    traefik_proxy:
      external: true
    owncloud_internal:

可以看到,在 43-56 行,我们做的事情主要有:

  • 将 ownCloud 加入先前创建的 Traefik 反代网络里。

  • 通过 labels 指令启用了 Traefik 对 ownCloud 容器的反代。由于之前的配置,我们必须特别指定启用反代,否则 Traefik 会无视这个 ownCloud 实例。

  • 设定 HTTP 路由规则:任何访问 owncloud.example.com 域名的用户都会进到这个 ownCloud 实例。另外我们还加入了 HTTP 到 HTTPS 的重定向。

  • 创建了名为 owncloud_internal (你可以用别的名字)的“内网”并把 ownCloud、Redis 和 MariaDB 容器添加进去。 因为我们不需要,也不应该把 Redis 和 DB 暴露给外面,我们完全可以把他们另外加到一个“内部”网络,让他们在这个网络里通信。对于 Traefik(和用户)来说,Redis 和 DB 是透明的,他们只看到 ownCloud。

    如果你还是有点困惑的话,那下面请参看我用幼儿园水平画的一个示意图。

    Traefik 与 ownCloud 之间关系的示意图

重启 ownCloud 服务器让配置生效 – cd 到 ownCloud yml 文件的目录,然后执行

docker compose up -d

如果没有问题的话,在ownCloud 启动完毕后 Traefik 会自动将其反代并申请 HTTPS 证书。

恭喜!现在你应该有一个 Docker 化、Let’s Encrypt 证书加密的 ownCloud 实例了。

相信大家已经感受到了 Docker 带来的便利,那么在 ownCloud 新版本释出后,如何更新呢?这非常简单,因为我们在 yml 里面(其实是 .env 里面)定义的 ownCloud 版本是 latest(即最新版),我们只需要 cd 到 ownCloud 所在的文件夹,执行

docker compose pull
docker compose up -d

就更新完毕。

同理,我们可以用这个方法去升级 Traefik。

ownCloud 的文件和配置都在 /var/lib/docker/volumes/example_files/_data/ 文件夹下,分别是 configdata 两个文件夹。 如果需要备份的话你可以用 rsync + cron 或者其他办法。

这篇教程就到此为止,由于编写仓促,如果我有遗漏的地方,或者你有更多问题,欢迎在下面评论。

相关内容