Featured image of post Garage 对象存储的安装和使用

Garage 对象存储的安装和使用

介绍轻量级 S3 兼容的对象存储 Garage 项目的自托管部署和使用方法。

背景

前几年 Oracle Cloud 云服务推出的时候,申请了日区(ap-tokyo-1)永久免费资源。包括免费主机和共计 200GB 的块存储额度。

闲来无事,整理一下:保留一台 Ubuntu Linux 实例,实例初始存储空间 50G 本地磁盘,剩余 150G 额度创建一个块存储卷通过iSCSI挂载到实例上。
实例配置: 1 OCPU,1GB Mem,0.48Gbps Bandwidth,整体性能一般,当然能免费使用也没什么可说的。
创建好实例并装好 Docker 后一直没什么实际用处,有段时间用来挂载小雅Alist看视频。

最近心血来潮来折腾个人博客。调查了一番之后选择用 Hugo + Markdown 生成静态博客。如何搭建静态博客暂且不表,其中涉及到图片等资源如何存储的问题。图片可以选择跟博客内容一起发布和托管,但这不符合折腾的精神。必须要搞一个图床!另一个好处时如果文章要同步发布到多个平台时不需要处理图片链接问题。

关于图床,有一些自托管部署的开源项目,但目前以 S3 为代表的对象存储被广泛应用,非常适合用于搭建图床。各大云服务商都有推出自己的对象存储服务,有的有免费存储空间额度。但是一般免费额度都比较小,一般在 20G 以下。考虑到 Oracle 免费实例上有 150G 的存储空间,那有没有可以自己搭建的 S3 兼容的开源项目呢?

那必须是有的,比较流行的是 Ceph 和 Minio。Ceph 生态庞大但是比较重量级,不适合个人用户。Minio 对 S3 的兼容性最好,支持用 Docker 部署单实例使用,部署和使用都很简单。在实例上安装体验了一下,发现即使没存储内容时也很容易把内存占满,所以只能放弃了。

经过一番搜索后,看到有人推荐比 Minio 更轻量级的开源项目 Garage。Garage 是一个法国团队开发的 S3 兼容的对象存储,比较小众没什么知名度,网上的资料也比较少。这里记录一下 Garage 的安装和使用。Garage 的介绍可以参考官方文档,文档比较简明扼要,全读一遍也费不了多少时间。

准备

我用 Linuxserver.io 的 SWAG (Secure Web Application Gateway) Docker 镜像在实例上安装 Nginx 环境。其中需要购入一个域名,这里以 mydomin.com 指代。SWAG 配置好域名服务商的认证信息后可以自动申请和管理 Let’s Encrypt SSL 证书。非常适合个人使用。详细的安装使用说明参考官方文档

Docker 创建一个名字为 lsio 的 Bridge 类型的网络,所有 Docker 容器都使用这个网络,这样就可以通过容器名称相互访问。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
docker run -d \
  --name=swag \
  --cap-add=NET_ADMIN \
  -e PUID=$(id -u) \
  -e PGID=$(id -u) \
  -e TZ=Asia/Singapore \
  -e URL=mydomain.com \
  -e 'SUBDOMAINS=\*,\*.web-garage' \
  -e VALIDATION=dns \
  -e DNSPLUGIN=aliyun \
  -e EMAIL=[email protected] \
  -p 443:443 \
  -v /etc/swag:/config \
  --restart always \
  --net=lsio \
  --dns-search=. \
  lscr.io/linuxserver/swag:latest

这里我使用的是阿里云托管域名。并且指定用通配符为所有二级域名 *.mydomain.com 和所有三级域名 *.web-garage.mydomain.com 申请SSL证书。
虚拟云网络里面添加入站规则,确保 443 端口能被公网访问。

安装 Garage

个人使用的话用 Docker 镜像安装单实例即可。

准备配置文件

这里配置文件路径为 /etc/garage/garage.toml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "lmdb"

replication_mode = "none"

compression_level = 2

rpc_bind_addr = "[::]:3901"
rpc_public_addr = "127.0.0.1:3901"
rpc_secret = "$(openssl rand -hex 32)"

[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
root_domain = ".s3-garage.mydomain.com"

[s3_web]
bind_addr = "[::]:3902"
root_domain = ".web-garage.mydomain.com"
index = "index.html"

其中 rpc_secret 可以用 openssl rand -hex 32 生成,3901 是集群节点内部通信访问端口,3900 是 S3 API 访问端口,Garage 不支持访问策略,所以使用 S3 API 时必须要使用 Secret Key 访问和操作 Bucket。也就是说无法在浏览器中通过 https://<bucket>.s3-garage.mydomain.com/<key>https://s3-garage.mydomain.com/<bucket>/<key> 访问内容。
那要如何在公网匿名访问内容呢?那就得要用到 3902 端口了,这里指的是 Garage 支持托管静态网站,比如说把 Hugo 站点上传到 Garage Bucket,就可以通过 https://<bucket>.web-garage.mydomain.com 访问网站了。那已知 Object Key 的情况下就可以通过 https://<bucket>.web-garage.mydomain.com/<key> 变相访问任意文件了。

创建容器

1
2
3
4
5
6
7
8
docker run -d \
  --name garaged \
  --restart always \
  --network lsio \
  -v /etc/garage/garage.toml:/etc/garage.toml \
  -v /mnt/iscsi/garage/meta:/var/lib/garage/meta \
  -v /mnt/iscsi/garage/data:/var/lib/garage/data \
  dxflrs/garage:v0.9.3

其中 /mnt/iscsi 是块存储卷的挂载路径,在此创建 Garage 的 metadata 目录。为了安全起见,不映射任何公开端口,统一用域名走 Nginx HTTPS 443 端口访问。
至此,容器就跑起来了。容器内部不包含 SHELL 程序,不能使用 docker exec -it garaged /bin/sh 进行交互式访问。需要使用 docker exec garaged /garage,所有的命令都可以用 --help 自解释。
可以用 docker logs garaged -f 查看日志。
可以用 docker exec garaged /garage status 查看节点状态。下面的步骤会用到当前节点 ID。
picture 0

初始化节点

在使用之前需要先初始化节点,即配置当前节点的区域和存储空间信息。

方法:

1
docker exec garaged /garage layout assign -z <zone> -c <capacity> <NODE ID>

示例:

1
docker exec garaged /garage layout assign -z jp -c 50G aabf22f2b4bc9a1b

最后需要使用上述配置生效。

1
docker exec garaged /garage layout apply --version=1

对于新的配置需要指定 --version 参数,在之前的版本号基础上 +1,因为是新创建的节点,版本号相当于 0,所以这里是 --version=1

再使用 garage status 查看状态信息。
picture 1

至此,当前节点初始化完成,可以创建存储桶(Bucket)和访问密钥(Key)了。

初始化存储桶

创建 Bucket 很简单,指定 bucket name 就可以了。

1
docker exec garaged /garage bucket create blog

这里创建了一个名为 blog 的 Bucket。

可以用下列命令检查 Bucket 状态。

1
2
docker exec garaged /garage bucket list
docker exec garaged /garage bucket info blog

要访问上述创建的 Bucket,还需要先创建 API Key。一个 Key 可以配置为访问多个 Bucket,一个 Bucket 可以被多个 Key 访问。

1
docker exec garaged /garage key create blog-app-key

这里创建一个名为 blog-app-key 的访问密钥,记录下输出的 Key IDSecret key

可以用下列命令检查 Key 状态。

1
2
docker exec garaged /garage key list
docker exec garaged /garage key info blog-app-key

接下来授权 Key 访问 Bucket。

1
2
3
4
5
6
docker exec garaged /garage bucket allow \
    --read \
    --write \
    --owner \
    blog \
    --key blog-app-key

至此可以使用各种 S3 兼容的客户端和库访问存储桶了。需要注意的点:

  1. region 固定为 garage
  2. 需要配置 force_path_style=true,以使用 https://s3-garage.mydomain.com/<bucket> 的形式,而不是 https://<bucket>.s3-garage.mydomain.com。后者需要为每个桶都要配置域名。

最后要允许存储桶能被公开匿名访问。Garage 不支持访问策略,只能通过静态网站托管来间接实现。

1
docker exec garaged /garage bucket website --allow blog

至此,只需要添加域名 blog.web-garage.mydomain.com 就可以访问 blog 桶里面的文件了。访问方式:https://blog.web-garage.mydomain.com/<key>

最终用 garage status 查看信息时是这样的。
picture 2

使用 Garage

添加子域名

上述步骤提及到三个域名:s3-garage.mydomain.comweb-garage.mydomain.comblog.web-garage.mydomain.com。其中 web-garage.mydomain.com 并不会实际使用到。
在域名托管商为 mydomain.com 添加两个子域名 s3-garage.mydomain.comblog.web-garage.mydomain.com,都指向当前实例的公网IP。

Nginx 反向代理

在 SWAG 容器的 /config/swag/nginx/proxy-confs 目录下创建以下两个文件,然后重启容器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# s3-garage.subdomain.conf

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name s3-garage.mydomain.com;

    include /config/nginx/ssl.conf;

    client_max_body_size 0;

    location / {
        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app garaged;
        set $upstream_port 3900;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;

        # Disable buffering to a temporary file.
        proxy_max_temp_file_size 0;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# web-garage.subdomain.conf

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name *.web-garage.mydomain.com;

    include /config/nginx/ssl.conf;

    client_max_body_size 0;

    location / {
        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app garaged;
        set $upstream_port 3902;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
    }
}

之后就可以通过 https://s3-garage.mydomain.comhttps://blog.web-garage.mydomain.com 访问 Garage 了。

客户端工具

可以和 Garage 集成的工具:

  • awscli
  • minio-client
  • s3cmd
  • rclone
  • alist
  • Cyberduck
  • WinSCP

Cloudflare Workers 里面推荐使用 aws4fetch 库。

对于使用 VS Code 写 Markdown 的用户,推荐 Markdown Image 插件。该插件以粘贴的形式自动上传至云服务并生成可访问链接。详情可参见插件说明

Markdown Image 插件实际使用配置如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    "markdown-image.base.uploadMethod": "S3",
    "markdown-image.s3.endpoint": "https://s3-garage.mydomain.com",
    "markdown-image.s3.region": "garage",
    "markdown-image.s3.bucketName": "blog",
    "markdown-image.s3.accessKeyId": "<Key ID>",
    "markdown-image.s3.secretAccessKey": "<Secret key>",
    "markdown-image.s3.cdn": "https://blog.web-garage.mydomain.com/${filepath}",
    "markdown-image.s3.config": {
        "forcePathStyle": true,
    }

总结

个人用户自建 S3 存储可以突破云厂商的免费额度限制,在国内的环境下还可以避免被恶意刷流量。并且 S3 已经是对象存储的事实标准,将来如果想要迁移到其他地方也是非常方便的。

对于 Garage、Minio 这种开源项目,更加倾向于简洁性,包括简单的架构,简单的部署和维护。那么自然地就不可能有极致的性能了,比如说高吞吐和大量小文件的场景就会有瓶颈。当然个人用户一般也没有极致的性能需求。

Minio 是 Go 语言实现的,知名度较高,生态也比较丰富,使用也比较方便。
Garage 是 Rust 语言实现的,相较而言比较小众,更谈不上生态,自身甚至不包括 WebUI 管理界面。

但如果你想在一台性能较弱的主机上部署 S3 兼容服务,那 Garage 或许是个不错的选择。