本篇是系列中的第三篇内容,我们继续聊聊如何把一个简化过的私有云环境部署在笔记本里,以满足低成本、低功耗、低延时的实验环境。

在前两篇文章中,我们聊过了基础虚拟化相关的前置准备、以及为了避免在搭建过程中盲人摸象,而准备的监控服务。接下来我们来进行另外一个基础技术设施的搭建,网络存储服务。

写在前面

考虑到实际能够使用的资源有限,并且我们不希望监控、日志等基础服务的数据和这个通用的存储服务产生循环依赖,导致升级维护的时候“产生死锁”现象。所以,对于之前提到的监控日志(非归档数据)并暂时并不会使用这个方式进行存储。以及,因为资源有限,我们也不会使用块存储方案来提供云上的数据库数据落地方案(下文中会提到具体原因)。

本文中技术方案、应用架构的设计考虑,更多是出于对接下来各种基础技术设施日常使用是否方便,容器集群中应用集成使用体验是否良好、相关应用维护是否简单出发。

此外,在正式搭建网络存储的之前,我们需要先了解下什么是“网络存储”,以及这类服务有哪些典型“代表产品”,可以相对低成本的拿来即用。

存储技术选型

在我们熟悉的公有云平台上,网络存储类型一般会分为:块存储、对象存储、文件存储三类。其他更多的品类,则更多是基于这三类进行了一些不同侧重功能的场景定制,赋予了更强的业务能力。

《A map of storage options in Google Cloud》一文配图

上图简单的概括了三大类存储方案的应用场景,至于为什么适合这样的场景,下图中的性能偏好则简明的揭示了原因。

《The Performance of Storage Systems: 3 Criteria to Take into Account》一文配图

不过,我猜如果你没有折腾或者从事过云产品相关开发,可能单从上面两张图还并不能够理解这三种存储之间的差异,以及在本文的语境下,为什么会选择某些方案,舍弃某些方案。下面我们就来展开聊聊。

块存储

现实中的“块存储”设备

块存储(Block Storage),可以通俗的理解为最底层的存储技术,一般会伴随之前文章中提到的基础虚拟化技术一同使用,在某些角度你可以把它看作更灵活的纯软件实现的底层 Raid 技术,但是拥有更迅速的灾难恢复重建能力(大容量Raid重建真的非常慢),并且不像 Raid 需要相同规格的存储磁盘,可以进行不同规格磁盘混用。不过这些相较之 Raid 更强大的能力,需要有强大、稳定的基础网络、以及适量冗余副本来提供,所以这一技术非常适合有海量资源冗余的公有云平台。

因为拥有比较低的操作延时、支持配置非常大容量的存储额度,并且拥有数据自动恢复的能力,所以块存储非常适合作为传统数据库的底层存储技术来使用。以及,当我们在创建公有云云磁盘的时候,可以动态的对磁盘进行扩缩容,为服务器添加市面上购买不到的超大规格容量磁盘,底层也是使用到了块存储技术。

本文的试验环境限制在一台拥有双磁盘位的笔记本,本身存储总量就非常有限,在这个场景下,不论是走 Raid 还是走 Ceph 都会存在资源浪费、并且也因为磁盘副本数量不够多,达不到预期的效果。所以,本文并不适合采用块存储技术配合前两篇文章中提到的虚拟机们一起使用。

如果你对块存储好奇,想实际体验一下效果,可以试试曾经提到的 Proxmox VE 中,这套虚拟化系统默认就支持安装使用 Ceph 作为存储后端。如果你只是想了解块存储技术是如何工作的,我推荐你简单阅读 Ceph 官方关于 RBD 的文档

此外,在多年折腾的过程中,有一个成本节约的小技巧:存储和计算分离 ,如果你有灵活的、大量数据的存储和使用需求,可以考虑单独购置一台设备用于 “NAS” 需求,和你的计算设备走网络连接即可,如果你担心家用路由性能跟不上,可以在设备之间添加一台百十来块钱的交换机。(感觉此处应有一篇番外篇)

对象存储

对象存储(Object Storage)可以理解为面向文件的 KEY-VALUE 数据库。如果说块存储是面向底层操作系统,运行在“系统级别”,那么对象存储则是运行在“应用级别”,服务于应用。(当然,在一些场景下,我们也可能选择使用诸如 s3fs 之类的手段,将其挂载为本地磁盘使用。)

《File Storage vs. Object Storage:Understanding Differences, Applications and Benefits》配图

有的时候,我们会对文件存储和对象存储产生模糊,其实本质上来说这两种存储都是“对象存储”,差异在于文件存储贴近我们一般使用的操作系统,具备目录层级,以及以单一文件进行逻辑存储和读取。而对象存储则是将文件的元信息和具体数据剥离开,分别进行存取,在对象存储底层,其实并没有传统文件存储的层级概念。

目前业界普遍采用的产品方案,是提供无限逼近、兼容已经形成事实规范的 Amazon S3 API,提供 HTTP REST API 给用户使用。目前鲜有具备一定用户量的应用软件,在有外部文件存储需求,而不支持通过 “S3 协议” 进行能力扩展的。

相比较传统文件操作系统(包含 NFS)而言,因为采用 KEY-VALUE 思路进行设计,所以对象存储拥有了极其变态的水平扩展能力,理论来说,你可以通过水平扩容来实现需要无穷多的文件的存储的场景(简直是黄金搭档),比如我们熟悉的:网盘类应用、在线数据表格类应用、拥有大量日志文件的数据分析场景、拥有大量片段文件的直点播场景,以及配合 CDN 直接使用的场景。

前文已经提到了种种对象存储的优势,而接下来我们将要陆续使用的服务,多数都支持使用 S3 协议进行数据存储,能够让我们符合“十二要素应用”的最佳实践:Treat backing services as attached resources,让服务更加灵活。(搁在更早之前,我们一般称之为代码和数据分离。)

《The Twelve-Factor App - Treat backing services as attached resources》

虽然前文提到的 Ceph 也支持对象存储,但是考虑到前文提到的资源利用率低原因,以及之前已经使用了很久 MinIO,对它有信任和了解,所以本篇文章将采用 MinIO 作为统一的存储网关,配合定时同步实现数据冗余,确保数据安全。 此外,在较新的 MinIO 版本里,控制台也越来越完善,对于全局数据管理也更加直观,有利于我们对于整体应用存储数据有一个概览。

文件存储

现实世界中的“文件存储”

提到文件存储(File Storage),消费场景的认知一般是网盘类应用,包括大型商业公司的“资源盘”、“下载盘”,中小型公司基于协作效率提供的“同步盘”,以及近几年遍地开花的 “NAS 软硬件一体的设备”(譬如:群晖、QNAP、WD Cloud 等)

**而在云环境的语境下,文件存储则代表了一个超大规模,可以无限弹性和扩展的高性能云原生的分布式,对使用场景、使用客户端高度兼容的文件系统。**你可以通过网络将你的文件系统挂载在任何场景使用,比如适合分布式计算,但是又需要共享数据的场景;你可以将这个文件系统用于生产时的任何阶段,挂载在操作系统上,挂载到容器内,挂载到支持类似 NFS 协议的应用内,让你的应用即使不在一个网络环境里,也能够通过这个方式快速的进行文件粒度的数据交换。而交互形式,其实和本地的文件管理没有太大差别,只是“容量接近无穷”、“文件数量接近无限”。

文件存储和对象存储的界限其实一直以来都比较模糊,不少对象存储工具其实本身就附带了文件存储的功能。如果根据消费者场景来界定的话:文件存储是更高级的抽象,粒度更细,多数场景是被应用接近用户侧消费的;而对象存储则不纠结存储的数据内容,更加灵活,多数厂家是被应用接近开发侧消费的。

考虑到我们后续搭建的云环境中的应用,有一些并不支持 S3,但是依旧需要可靠的外部存储,我会使用 NextCloud 和 MinIO 来提供这个能力。

环境准备

和前文一样,为了方便读者的使用,我将下面的配置上传到了 GitHub ,可以自取。和基础篇中一样,为了省事,我在 DHCP 中配置了几条规则,给这台专门用于存储服务的虚拟机起了一个名字“storage.lab.com”,方便后续调试和访问:

address=/storage.lab.com/10.11.12.150
address=/*.storage.lab.com/10.11.12.150
address=/*.console.storage.lab.com/10.11.12.150

搭建通用存储网关 MinIO

MinIO 是我很喜欢的一款应用软件,它本身体积小巧,几十兆的身材之下,可以支撑上百T、甚至更多的数据存储和管理。并且 IO 性能出众,甚至可以用于 TensorFlow 计算场景,或者 Spark、Hadoop 分析型场景使用。本文资源有限的场景下,则更是适合它“施展拳脚”。

MinIO 官方定位:多云对象存储工具

我们在《配置容器仓库》《从零开始使用开源文档/Wiki软件 Outline(一)》《从零开始使用开源文档/Wiki软件 Outline(二)》三篇文章中,其实都提到了关于 MinIO 的使用。

然而之前并未详细展开,所以这次趁着搭建存储服务的机会好好聊聊关于它的使用。

搭建服务网关:Traefik

考虑到有一些应用需要 HTTPS 方式接入,以及我希望后续使用统一的用户登陆网关进行登陆,所以在存储服务的搭建过程中,我们需要使用到 Traefik 灵活的网关接入能力。

Traefik 的官方定位:云原生应用代理

因为之前已经提过很多次 Traefik 如何使用了,这里为了节约篇幅,我已经将配置上传到 GitHub ,自行取用即可。

关于 HTTPS 证书的获取,你可以根据自己的实际情况来处理,既可以选择在云平台购买、免费申请以年为单位的证书,也可以考虑使用 Traefik 自动申请并续签免费的 HTTPS 证书,还可以考虑为了一劳永逸,生成属于自己的自签名证书。之前的几篇文章里介绍的比较详细了,这里就不展开了,有需求的同学可以自取:

如果你想快速的折腾支持泛解析的 HTTPS 自签名证书,或许这个项目会适合你:https://github.com/soulteary/certs-maker

本文使用的默认配置中,在 Traefik 代理 HTTPS 时,配置了 SNI 功能。所以你在使用的时候,需要根据自己的域名,预先生成对应的证书。如果你和本文一样,采用自签名证书,可以直接使用示例代码中的容器编排文件快速生成这些域名所需要的证书文件。

docker-compose -f docker-compose.make-certs.yml up

Traefik 默认控制台

一切就绪后,使用 docker-compose up -d 启动 Traefik,然后在浏览器中访问 https://traefik.console.storage.lab.com/ 就能看到我们熟悉的 Traefik 控制台啦,在这个管理界面中,你可以直观的了解到使用 Traefik 提供接入能力的所有服务的状态,以及帮助你快速的排查为什么服务接入过程中有异常。

搭建存储应用:MinIO

为了节约篇幅,我将完整的 MinIO 配置也放在了 GitHub 中,完整的配置由三个部分构成:

  • MinIO 主应用配置:docker-compose.yml
  • MinIO 初始化使用的客户端配置: docker-compose.init.yml
  • 为了方便维护而编写的 MinIO 环境变量配置:.env

作为用户,其实只需要关注最后一个 .env 文件即可:

# == MinIO
# optional: Set a publicly accessible domain name to manage the content stored in Outline
DOCKER_MINIO_IMAGE_NAME=minio/minio:RELEASE.2021-11-03T03-36-36Z
DOCKER_MINIO_HOSTNAME=s3.storage.lab.com
DOCKER_MINIO_ADMIN_DOMAIN=s3.console.storage.lab.com
MINIO_BROWSER=on
MINIO_BROWSER_REDIRECT_URL=https://${DOCKER_MINIO_ADMIN_DOMAIN}
# Select `Lowercase a-z and numbers` and 16-bit string length https://onlinerandomtools.com/generate-random-string
MINIO_ROOT_USER=6m2lx2ffmbr9ikod
# Select `Lowercase a-z and numbers` and 64-bit string length https://onlinerandomtools.com/generate-random-string
MINIO_ROOT_PASSWORD=2k78fpraq7rs5xlrti5p6cvb767a691h3jqi47ihbu75cx23twkzpok86sf1aw1e
MINIO_REGION_NAME=cn-homelab-1

# == MinIO Client
DOCKER_MINIO_CLIENT_IMAGE_NAME=minio/mc:RELEASE.2021-09-02T09-21-27Z
DEFAULT_S3_UPLOAD_BUCKET_NAME=public

你可以根据自己的需求,修改这个配置里定义的“访问域名”、“管理域名”、“管理员账号、管理员密码、Bucket” 等应用需要使用的配置。在修改完毕之后,先执行 docker-compose up -d,启动 MinIO 主应用。

使用 docker-compose logs -f 查看日志,能够看到类似下面的日志,则说明应用启动正常:

minio  | API: http://172.18.0.2:9000  http://127.0.0.1:9000 
minio  | 
minio  | Console: https://s3.console.storage.lab.com 
minio  | 
minio  | Documentation: https://docs.min.io

接着执行 docker-compose -f docker-compose.init.yml up ,如果没有意外,你将会看到类似下面的内容:

minio-minio-client-1  | Removed `local` successfully.
minio-minio-client-1  | Added `local` successfully.
minio-minio-client-1  | Bucket created successfully `local/public/`.
minio-minio-client-1  | Access permission for `local/public` is set to `public`
minio-minio-client-1 exited with code 0

则应用初始化完毕,在浏览器中打开 https://s3.console.storage.lab.com/ ,使用配置中的账号密码登陆,就能够看到 MinIO 的控制台啦,因为目前我们还没有上传内容,所以只能看到我们刚刚初始化时创建的一个用户,以及一个全局公开的储存桶。

MinIO 默认控制台

从左侧的导航菜单选择 Bucket,可以看到我们刚刚初始化的存储桶(Bucket),点击红色的浏览按钮(这里的交互颜色欠妥),就可以进入在线的对象管理工具啦。

MinIO 创建储存桶

这里可以理解为一个简单的在线“网盘”,我们可以在这里随便上传一张图片。

MinIO 在线文件上传

接着在浏览器或者应用里,就可以使用之前配置服务域名来访问这个资源啦:

# 访问格式: 服务域名/存储桶名称/文件名称
http://s3.storage.lab.com/public/rj5k3y73.jpeg
# 或者
https://s3.storage.lab.com/public/rj5k3y73.jpeg

如果你希望不同的应用之间的文件可以充分隔离,可以在侧边栏选择“用户”菜单,然后为不同的用户创建属于它们自己的用户。

MinIO 为不同应用创建不同的用户

搭建同步应用:Syncthing

在日常使用过程中,我们经常会遇到需要向不同设备分发数据的需求,这里的思路有很多种:

  • 使用脚本批量调用 SCPRsync 命令进行 “1toN” 的操作。
  • 使用 curlwget 等方式从目标服务器反向从源服务器下载数据。
  • 使用 CI/CD 的方案,通过 Agent 的方式向不同的服务器执行命令,进行同步(本质同上面两条)。
  • 或者使用本文使用的 P2P 的方案,进行集群间快速数据同步。
  • 等等…

之所以考虑选择 Syncthing,是因为它可以使用 P2P 的方案进行多机之间的数据同步。理论来说,你可以通过水平扩展机器节点,来成倍的提升数据传输速度。比如下图的示例中,向服务器分发数据,因为有7个节点,所以极限状况下,可以提升 6倍以上的速度。而且,如果你家里有独立的 NAS 设备,这个方案也可以方便快速的将你计划长期存储的数据进行快速的迁移。

美丽的 P2P 传输拓扑

很早之前, 我购买了 Resilio File Sync(BTSync)的授权,但是因为一些原因,这个软件在国内使用起来越来越不方便。于是活跃的开源项目 Syncthing 便成了不二的替代品。和上文一样,我将 Syncthing 的配置文件,也上传到了 GitHub 中,简单说明一下这几个文件的用途:

  • 主应用容器配置:docker-compose.yml
  • 可选的服务发现组件:docker-compose.discosrv.yml
  • 共用的环境变量配置文件:.env
  • 追求更高性能转发的配置目录: host-mode

和前文中的 MinIO 一样,一般情况下,我们只需要关注其中的 .env 配置文件即可。

# == Syncthing
DOCKER_SYNCTHING_IMAGE_NAME=syncthing/syncthing:1.18
DOCKER_SYNCTHING_HOSTNAME=syncthing-on-storage
DOCKER_SYNCTHING_DOMAIN=syncthing.storage.lab.com

# == discosrv
DOCKER_DISCOSRV_IMAGE_NAME=syncthing/discosrv:1.18.0
DOCKER_DISCOSRV_HOSTNAME=discosrv-on-storage

这里的配置相比较之前更简单一些,我们只需要关注和修改其中的域名即可。在配置修改完毕之后,我们使用 docker-compose up -d 启动 syncthing 主应用,稍等片刻当应用出现类似下面的日志的时候,我们的应用就启动完毕了(通过 docker-compose logs -f 查看)。

... ...
syncthing-on-storage  | [start] 08:36:24 INFO: Archiving a copy of old config file format at: /var/syncthing/config/config.xml.v0
syncthing-on-storage  | [V6PE5] 08:36:24 INFO: My ID: V6PE5XT-UKDCFHM-....
syncthing-on-storage  | [V6PE5] 08:36:25 INFO: Single thread SHA256 performance is 2093 MB/s using minio/sha256-simd (590 MB/s using crypto/sha256).
syncthing-on-storage  | [V6PE5] 08:36:25 INFO: Hashing performance is 1346.02 MB/s
syncthing-on-storage  | [V6PE5] 08:36:25 INFO: Running database migration 1...
... ...
syncthing-on-storage  | [V6PE5] 08:36:25 INFO: TCP listener ([::]:22000) starting
syncthing-on-storage  | [V6PE5] 08:36:25 INFO: Ready to synchronize "Default Folder" (default) (sendreceive)
syncthing-on-storage  | [V6PE5] 08:36:25 INFO: Relay listener (dynamic+https://relays.syncthing.net/endpoint) starting
syncthing-on-storage  | 2021/11/04 08:36:25 failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 2048 kiB, got: 416 kiB). See https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size for details.
syncthing-on-storage  | [V6PE5] 08:36:25 INFO: QUIC listener ([::]:22000) starting
syncthing-on-storage  | [V6PE5] 08:36:25 INFO: Loading HTTPS certificate: open /var/syncthing/config/https-cert.pem: no such file or directory
syncthing-on-storage  | [V6PE5] 08:36:25 INFO: Creating new HTTPS certificate
syncthing-on-storage  | [V6PE5] 08:36:25 INFO: GUI and API listening on [::]:8384
syncthing-on-storage  | [V6PE5] 08:36:25 INFO: Access the GUI via the following URL: http://127.0.0.1:8384/
syncthing-on-storage  | [V6PE5] 08:36:25 INFO: My name is "syncthing-on-storage"
syncthing-on-storage  | [V6PE5] 08:36:25 INFO: Completed initial scan of sendreceive folder "Default Folder" (default)
...

在应用启动完毕之后,我们可以通过之前设置的域名在浏览器中访问啦:syncthing.storage.lab.com

Syncthing 默认界面

如果我们只在内网环境使用,设备网络环境比较固定。比如在本文的环境下,可以考虑参考下面的配置将依赖公开服务的选项都关闭。然后点击右上角的菜单按钮,重启软件。

Syncthing 设置内网环境使用

但是如果使用上面的方式配置,我们所有的客户端之间的信任和关联,都将需要手动配置,为了简单直接一些,这里我们可以再多配置一个“专属于它的服务发现应用”,帮助我们进行一些自动化配置。

这里因为 Traefik 和 Syncthing 服务发现的证书传递存在一些问题,所以最简单的方案是使用 host-mode 中的服务发现配置,将配置复制到同级目录中。

执行 docker-compose -f docker-compose.discosrv.yml up -d ,启动 syncthing 配套的服务发现应用。然后使用 docker-compose -f docker-compose.discosrv.yml logs -f 查看日志,会看到服务输出了类似下面的内容。

discosrv-on-storage   | stdiscosrv v1.18.0 "Fermium Flea" (go1.16.5 linux-amd64) docker@syncthing.net 2021-06-21 20:53:50 UTC [noupgrade, purego]
discosrv-on-storage   | Failed to load keypair. Generating one, this might take a while...
discosrv-on-storage   | Server device ID is LTOWNZI-DZHR7LD-S43HOB7-XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX

然后根据我们配置文件中配置的域名,做一个简单的字符串拼合,就可以得到类似下面的服务发现端口地址了:

https://storage.lab.com:8080/v2/?id=LTOWNZI-DZHR7LD-S43HOB7-XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX

将这个地址复制粘贴到所有我们要使用的 syncthing 客户端和服务端的链接配置中,在私有网络中的设备就都能够拥有探测彼此的能力了。

Syncthing 配置私有服务发现

软件的使用非常简单,我们在需要进行同步的设备上,参考上文使用容器部署 Syncthing 服务端(客户端)即可,在我们的笔记本或者手机上安装 Syncthing 客户端即可,然后在服务端之间,复制彼此的 ID 进行配对,点击确认就好了。

Syncthing 添加设备

在添加完毕设备之后,我们可以新增共享文件夹,也可以编辑默认文件夹,将文件夹分配给我们已经配对完成的设备。

Syncthing 添加共享文件夹

在配置完毕共享服务,以及共享目录之后,随便放置几个大尺寸的文件,来体验一下速度吧。在内网环境下,轻轻松松能达到 50MB/s 或以上的传输速度。

Syncthing 内网传输速度

搭建网盘应用:NextCloud

如果你希望在后续应用之间,有一个类似我们平常使用的网盘界面的工具,可以考虑使用 NextCloud 这款开源软件。关于它的基础搭建之前的文章中有提过:《如何通过容器搭建稳定可靠的私有网盘(NextCloud)》,基础搭建和配置便不再赘述。

《如何通过容器搭建稳定可靠的私有网盘(NextCloud)》配图

这里,我们可以参考官方文档,添加一小段配置在 config.php 配置文件中,让它使用我们上文提到的 S3 服务,将所有的数据更可控的进行结构化存储。

'objectstore' => [
        'class' => '\\OC\\Files\\ObjectStore\\S3',
        'arguments' => [
                'bucket' => 'nextcloud',
                'autocreate' => true,
                'key'    => 'YOUR_KEY',
                'secret' => 'YOUR_SECRET',
                'hostname' => 'example.com',
                'port' => 1234,
                'use_ssl' => true,
                'region' => 'optional',
                // required for some non Amazon S3 implementations
                'use_path_style'=>true
        ],
],

最后

在写“装在笔记本里的私有云环境”这个系列的内容时,稍不注意文章字数就会超过各种平台允许最大字数,所以不得已将本篇文章进行了分拆。

迄今为止,我们已经完成了存储服务一半的搭建内容。

下一篇文章里,我们将结合目前所有的基础设施,完成对存储服务的监控集成、数据备份、以及试着实现一个最简单的,支持文件缓存、图片处理的 CDN 服务来复用我们已经搭建好的设施。

–EOF