我拥有自己的一台云服务器,主要用来部署一些我个人所需要的服务。由于使用了 Docker Compose,相比起直接部署,更加便于管理,备份等。
本文只是个人的一些实践。我不是计算机专业的,也没有系统学习过相关知识,所有的内容都是自己自学摸索或网络搜索学习得来的,因此难免存在一些差错,在一些专有名词的使用上也可能会出现错误,欢迎讨论与指正。
文中一些终端的截图是我 ssh 到服务器上截取的,由于没有太多配置(服务器上一般也用不到这些美化配置),因此颜色可能比较单调。
使用的原因
我个人刚开始接触云服务器时,可以说只是计算机的小白,完全没有系统化地学习过,甚至能够理解的操作也仅限于复制黏贴等简单操作,就连写一个脚本都要每次上网搜索如判断语句的写法。
当时我购买服务器的理由我记得不是很清楚了,但是很明显,我能干的事情并不多,能独立解决的问题更少,因此一开始只能够完全按照文档所说的内容进行安装,直接复制黏贴,修改一下文档提到需要修改的部分,同时很多时候也会因为一些细节原因,安装失败。有时候一个很简单的应用我要折腾一整天才能部署成功。
直到有一天,我看到了一篇介绍 服务器搭建服务的文章,里面介绍可以通过 Docker Compose 来进行管理。在此之前,我只是知道 Docker 与 Docker Compose,但是从未使用过。因此我也借此机会了解了一下它的基础操作,并尝试了一番,发现相比起我之前的那些步骤,确实方便了许多。
优势
通过 Docker 搭配上 Docker Compose,可以只依靠一个文件,就管理好整个服务器的应用,不需要在迁移或出现问题重装了系统后,如果没有完整系统的备份,只能一个一个应用按照文档重装,这样很可能会出现这样那样的一些小意外,需要耗费大量的时间。同时完整的备份需要消耗大量的空间,而服务器上的空间购买起来也并不便宜,下载到本地会由于带宽的限制需要不少的时间。因此综合来看使用 Docker 进行管理会更加方便。
缺点
Docker 具有一定的学习成本,需要一定的时间来熟悉 Docker 的操作。同时,Docker 的每一个 image 都需要占据不小的空间,且运行的服务如果较多,对 CPU 的压力也不小,这对于个人服务器的性能与容量都提出了一定的要求。
另外,由于运行的应用还需要备份一些数据文件,因此也不是真的只有一个文件需要备份而已,只是恢复的时候会比较简单,相对而言备份文件的大小会更小。所以,具体最终选择的选项还是需要进行一定的权衡来决定。
适合什么情形
那么,什么情况下才需要配置 Docker 来管理自己的服务器呢?
我个人认为,如果这台服务器上只有 web 服务器与一两个简单的应用,且用户并不熟悉 Docker,或是服务器性能有限,无法支持 Docker 部署多个应用,那么其实直接在宿主机上跑这些应用也是十分可行的。不过如果应用数量较多,那么宿主机的空间可能会变得杂乱起来,各种配置可能会出现干扰,导致出现问题时的重新部署耗时也会显著增加。这时就可以考虑使用 Docker 来进行部署与管理了。
当然,这些只是我个人使用下来的一些体验,由于没有系统学习或是培训过,可能会与实际产生一定的偏差。具体还是要看每个人不同的使用方式与习惯再决定是否要使用 Docker。
下文中所有操作,操作系统为 Ubuntu 20.04 LTS,用户名为 ubuntu,家目录为 /home/ubuntu,具有 sudo 权限,使用的 shell 为 bash,只开放了 80(http)、443(https) 和 ssh 修改过的非默认端口这三个端口。Docker 相关的内容在家目录下的 docker 目录,备份在家目录下的 backup 目录。所有的域名都使用 example.com 代替。
安装与配置简介
安装
首先需要在服务器上安装 Docker。对于不同的系统,安装的方式也不尽相同,具体可以参考 官网 上的具体操作步骤。最好参照官网的步骤,而不是直接使用默认源里的版本,因为那个很可能是旧版本。
安装完成后,为了能够更方便地使用与管理,建议一起安装 Docker Compose。建议按照 Docker Compose 官方建议的安装方式 进行安装,即
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose在完成安装后,可以通过加上 --version 的方式判断是否安装成功。

这样就是安装成功了,之后如果是国内的服务器,则可以通过修改镜像源来加快拉取镜像的速度。
这里以 上海交通大学源 为例,在 /etc/docker/daemon.json 文件中(如果没有就新建)加入
{
"registry-mirrors": ["https://docker.mirrors.sjtug.sjtu.edu.cn"]
}配置文件说明
Docker Compose 需要一个名为 docker-compose.yml 的文件来进行配置。由于一般只是使用别人打包好的镜像,因此不需要学习如何自己创建一个 image。
首先来熟悉一下配置文件的格式。它使用的是 YAML 格式,非常简单易懂,通过不同的缩进来区别层级关系。
在第一行,需要通过 version: "3" 来指明需要的最低 Docker Compose 的版本。一般 3 就足够了,如果需要其他的可以参照 官方表格 来进行选择。
随后,需要一行 services: 来囊括所需要的一切服务。以下的内容如果没有具体指明,均写在 services 的子集中。
接下来就是一个个服务了,每一个都是单独的一个单元,命名可以随意,但是不要重复,例如两个数据库可以分别命名为 service1sql 与 service2sql。这里通过 RSSHub 的官方示例来简单介绍一下具体的写法。
version: '3'
services:
rsshub:
image: diygod/rsshub
restart: always
ports:
- '1200:1200'
environment:
NODE_ENV: production
CACHE_TYPE: redis
REDIS_URL: 'redis://redis:6379/'
PUPPETEER_WS_ENDPOINT: 'ws://browserless:3000'
depends_on:
- redis
- browserless
browserless:
# See issue 6680
image: browserless/chrome:1.43-chrome-stable
restart: always
redis:
image: redis:alpine
restart: always
volumes:
- redis-data:/data
volumes:
redis-data:其中的 rsshub,browserless 和 redis 都是服务的名称。每一项服务下面都有 image,指的是具体在构建时需要拉取的镜像,所有的镜像可以在 官网 上搜索找到,一般下面还会有 README 文件指导如何使用配置。

在 browserless 中镜像名称后面还有一个冒号,后面所接的内容是具体拉取镜像的标签,可以理解为不同的版本,也可以在网站上查到。
restart: always 指的是在服务停止后是否重新启动,而 always 指的就是会不断尝试重启,保证服务一直在线。它还有其他不同的选项,不过一般用 always 就可以满足绝大部分的需求了。如果不写,那默认是 no,即退出时不会自动重启。
ports 是端口映射,就是将容器的端口与宿主机的端口进行绑定。绑定后,宿主机的这一端口就会被占用,因此不能进行重复绑定。其中的 1200:1200 中,左侧的为宿主机的端口号,右侧的则是容器的。如此一来,通过访问 localhost:1200,即可访问到 rsshub 中的 1200 端口,也就能够访问其服务了。
volumes 是为了持久化 Docker 中的文件数据而存在的。它能够将容器中的文件或目录映射到宿主机中来,双方互相都能够访问到这部分文件或目录。备份也就是针对通过 volumes 绑定来的文件目录。不然,只要这一服务重启,所有之前进行的操作都将会被消除。绑定依旧是左侧为宿主机,右侧为容器。这里可以是相对路径,也可以是绝对路径。
另外还可以写为卷标,即 redis 中的写法。这样,需要在 services 同级添加一项 volumes 来指定卷标。

environment 里指定需要的环境变量。具体哪些需要指定一般会在镜像的介绍页面指出,按照自己的需要书写。
depends_on 用于指定依赖,即指定该服务必须需要的其他服务,镜像的介绍页一般也会指出具体的内容。
docker-compose.yml 文件基本就是这么一些内容,接下来就可以进行实际操作了。
具体配置
权限设置
首先,Docker 默认只有 root 账户有权使用。由于在服务器上使用 root 并不是一个好习惯,因此建议将一个普通用户加入 docker 用户组来进行后续的操作。
sudo usermod -a -G docker username我先介绍一下我的配置方式。
nginx
我会通过一个 nginx 容器,使用反向代理来管理所有的容器,因此只需要映射该镜像的 80 与 443 端口到宿主机即可,不会过多占用宿主机的端口。
web:
image: nginx:alpine
restart: always
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./nginx/conf:/etc/nginx/sites
- ./nginx/ssl/cert:/etc/letsencrypt
ports:
- "80:80"
- "443:443"这样其他的服务就能够不用对外暴露端口了。之后简单编写一下 nginx 的配置文件,对于 nginx 熟悉的可以直接跳过了。
由于 nginx 代理的每个应用我都写了单独的一个 conf 文件,因此需要先在默认配置中把它们 include 进来。
include /etc/nginx/sites/*.conf;随后,在宿主机中的 nginx/conf 文件夹中,每个应用配置一个 conf 文件

而 conf 文件中的内容大致为
server{
listen 80;
server_name example.com;
index index.php index.html index.htm;
location / {
proxy_pass http://name:8888;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}而如果如下文一样配置了 https 的话,那么配置建议改为
server{
listen 443 ssl;
server_name example.com;
index index.php index.html index.htm;
client_max_body_size 128M;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://name:8888;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name example.com;
rewrite ^(.*)$ https://example.com;
}其中,example.com 如果是本地部署的话,需要改为 127.0.0.1, 而其他需要根据不同的 Docker 配置进行更换。需要注意的是,name 需要改为应用在 docker-compose.yml 中被起的名字,后面的 8888 也是需要根据应用占用的端口的需要进行配置。
如果需要更加具体的配置可以考虑学习一下 nginx。
Let's Encrypt
由于需要保证数据传输的安全性,开启 https 是必然的。SSL 证书可以通过 Let's Encrypt 非常简单快捷地申请到。当然,前提是需要拥有域名。域名相关的内容这里就不详细展开介绍了,如果没有域名,那么接下来这段就不必参照我的做法来操作了。
申请 Let's Encrypt 的证书最简单的方法是通过 certbot 来进行。只需要根据在 官网 上,选择相应的系统与使用的应用,即可获取到操作步骤。默认情况下,是每个域名都对应一张证书,不过也可以选择申请通配符证书,更加方便。
首先,在 用户文档 中查看是否支持自己使用的 DNS 服务商。如果没有,则可以直接跳转到手动申请了。

在选择了支持的服务商后,首先需要申请执行所需的 api token。这一步骤每个服务商都不尽相同,因此可以在指南中点开自己使用的服务商,并按照说明操作。不过需要注意的是,文档只有英文版,没有中文。
申请完成后,不要急着关闭页面,一般来说申请到的 token 只会显示一次,如果关掉页面而没有保存下来的话,将无法找回。因此千万记得关闭前保存好自己的 token。另外,建议跟着文档选择权限,一般不需要给予完整权限。
然后,创建一个文件,在其中填写下申请到的 api token。这里以我使用的 cloudflare 为例。不同的服务商请根据文档填写。
# Cloudflare API token used by Certbot
dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567这里我将文件保存为 cloudflare.ini。文件名称没有关系,只是在后面脚本中记得更改即可。
准备完成后,就是
#!/bin/bash
docker run -it --rm --name certbot \
-v "/home/ubuntu/docker/nginx/ssl/cert:/etc/letsencrypt" \
-v "/home/ubuntu/docker/nginx/ssl/var:/var/lib/letsencrypt" \
-v "/home/ubuntu/docker/nginx/ssl/var:/var/lib/letsencrypt" \
-v "/home/ubuntu/docker/nginx/ssl/cloudflare.ini:/cloudflare.ini" \
-v "/home/ubuntu/docker/nginx/conf:/etc/nginx/conf.d" \
certbot/dns-cloudflare certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /cloudflare.ini \
-d *.example.com \
-d example.com \运行这个脚本,就可以全自动化部署 SSL 证书了。
如果只申请 *.example.com,是不会自动申请 example.com 这个域名的,因此还要额外加上。
如果是手动申请,那么需要将脚本改为
#!/bin/bash
docker run -it --rm --name certbot \
certbot/certbot certonly \
--manual并按照运行后所说明进行后续操作。
要注意的是,需要在部署完成后,运行 docker-compose restart,重新部署的证书才能生效。
如果一切顺利,那么在打开网页后,域名左侧会有一把小锁,点开可以查看自己的证书。 Let's Encrypt 证书的有效期是 90 天,要注意证书的过期时间,定时运行脚本。


其他
配置完 nginx 后,基础内容就已经完成了,接下来就是找到自己喜欢的,感兴趣的容器,并按照 README 进行配置,由于步骤各不相同,这里就不再赘述了。最后,为每一个应用写一份 nginx 的配置文件,再启动容器,就能通过浏览器或其他形式访问了。
使用操作
命令简介
简单介绍几条会使用到的命令。命令分两类,一类是 Docker 的命令,还有一类是 Docker Compose 的命令。区别在于命令是 docker 还是 docker-compose。由于这里主要介绍的是 Docker Compose,因此介绍的命令也以 Docker Compose 的为主。
docker-compose 命令会向上递归搜索 docker-compose.yml 文件来执行,因此并不一定需要每次都回到根目录来执行命令。
docker-compose config:验证并查看配置文件。建议在执行以下命令前先执行该命令,确保不会出现错误。docker-compose up:根据配置文件,创建并启动容器。这一步会自动拉取所需要的镜像。可以在后面加上-d来使该命令在后台运行。docker-compose down:根据配置文件停止运行容器。docker-compose start/stop/restart/pull [image name]:启动/停止/重启/拉取镜像。如果不添加名称则根据配置文件操作所有镜像。docker-compose ps:列出所有运行中的容器。可以用来查看运行的情况。
因此一次完整的操作所需要的步骤包括:
- 编辑好配置文件以及各镜像所需的配置。
- 先通过
docker-compose config来检查配置是否出错。 - 使用
docker-compose up来查看是否能够正常运行,以及是否能满足需求。 - 首先
ctrl + c停止运行,再使用docker-compose up -d在后台运行。 - 如果需要修改配置,首先
docker-compose down停下现有服务,修改完成后返回到第二步,重新检查并启动。 - 如果需要更新,运行
docker-compose pull获取最新镜像,再docker-compose down+docker-compose up -d或是直接docker-compose restart来重启。
备份与还原
我这里使用了一个 Shell 脚本来进行简单的备份。它的原理是把 Docker 需要的所有文件都打包成一个压缩文件,按照日期来命名,并通过 gpg 进行加密,最后放在另一个备份文件夹中。这个文件需要被放在 docker 目录下。
#!/bin/bash
tmp=$HOME/.tmp-backup
sudo rm -rf $tmp
mkdir $tmp
cd $HOME/docker
sudo tar -jcpf $tmp/rec-`/usr/bin/date "+%y-%m-%d"`.tar.bz2 .
sudo chown ubuntu:ubuntu $tmp/rec-`/usr/bin/date "+%y-%m-%d"`.tar.bz2
sudo mv -v $tmp/rec-`/usr/bin/date "+%y-%m-%d"`.tar.bz2 $HOME/test/
gpg -e -r pcrab -o $HOME/backup/rec-`/usr/bin/date "+%y-%m-%d"`.tar.bz2.gpg $tmp/rec-`/usr/bin/date "+%y-%m-%d"`.tar.bz2
sudo rm -rf $tmp
unset tmp如果需要在压缩时查看压缩了哪些文件,可以在 tar 的参数列表中加入 -v。
另外,我会将这一压缩包上传至阿里云的 OSS 进行最后的备份。上传不消耗流量,如果需要恢复备份,由于我有一台阿里云的服务器,内网下载也不消耗流量,因此最后只需要购买存储容量包即可,用起来也非常方便。
如果需要定时自动备份,还可以通过 crontab 进行。执行 crontab -e 指令,修改配置文件即可。
# m h dom mon dow command
0 2 * * * /bin/bash /home/ubuntu/docker/backup.sh 其中,最先的五个字符分别为 分钟,小时,日,月,星期。我这样配置就是每天凌晨两点运行脚本,自动进行备份。
如果需要还原,则可以通过 sudo tar -jxpf filename.tar.bz2 来解压缩。注意压缩与解压缩的时候,必须要通过 root 权限来进行操作,并加上 -p,来保证权限与用户,用户组等属性的正确保留。
清理
在运行一段时间,经历过升级后,会发现服务器的储存空间被严重占用。出现这种情况很可能是 Docker 在运行期间产生的大量文件,以及更新后未被删除的旧镜像导致的,因此需要对其进行清理。
清理的方式非常简单。首先通过 docker system df 命令来查看具体的占用情况

如果发现需要进行清理,那么可以通过 docker system prune 来进行清理。如果需要更彻底的清理,可以加上 -a 与 --volumes 参数。


另外,删除是需要一定时间来执行的,因此如果输入了 y 并会车后,终端看似卡死,实际上正在执行删除操作,需要耐心等待执行完毕。执行完成后会显示清理掉的内容以及清理了多少空间。


总结
总体来说,使用 Docker Compose 来管理并没有想象中那么复杂。虽然需要一定的学习成本,但是具体使用起来就非常简单便捷了,只需找到对应的镜像,并按照说明进行配置即可,简单快捷。当然如果没有提供镜像,那么可能需要自己制作,这就需要更高的学习成本,可能比起直接在本机部署更加困难一些。
这里我也只是简单介绍了一下 Docker Compose 在服务器上部署的一些基础方式,也希望这篇文章能够给需要的人提供一些参考与帮助。
