对象存储
4 分钟阅读
简要概述
基于 minio-go 类库,对接兼容亚马逊 S3 标准的对象存储,通过封装 ObjstoreBucket
接口,以便在应用层简化使用。
已测试以下服务:
ObjstoreBucket
// ObjstoreBucket 抽象化包装,以简化使用,读写操作权限
type ObjstoreBucket interface {
io.Closer
ObjstoreBucketReader
// Name 获取默认的 bucket 名称
Name() string
// Upload 用于上传对象到默认的 bucket 里
Upload(ctx context.Context, objectKey string, r io.Reader) (ObjstoreAttributes, error)
// Delete 用于删除对象在默认的 bucket 里
Delete(ctx context.Context, objectKey string) error
// CopyTo 用于拷贝对象在默认 bucket 里
CopyTo(ctx context.Context, srcObjectKey, dstObjectKey string) (ObjstoreAttributes, error)
}
// ObjstoreBucketReader 抽象化包装,以简化使用,只读操作权限
type ObjstoreBucketReader interface {
// Get 用于获取默认 bucket 的对象内容
Get(ctx context.Context, objectKey string) (io.ReadCloser, ObjstoreAttributes, error)
// Iter 用于遍历默认 bucket 里的对象文件
Iter(ctx context.Context, dir string, f func(string) error) error
// GetRange 用于获取默认 bucket 中对象指定位置的内容
GetRange(ctx context.Context, objectKey string, start, end int64) (io.ReadCloser, ObjstoreAttributes, error)
// Exists 用于判断默认 bucket 是否存在该对象
Exists(ctx context.Context, objectKey string) (bool, error)
// Attributes 用于获取默认 bucket 中对象的额外属性
Attributes(ctx context.Context, objectKey string) (ObjstoreAttributes, error)
// IsObjNotFoundErr 错误是否为查询的对象不存在
IsObjNotFoundErr(err error) bool
}
ObjstoreAttributes
// ObjstoreAttributes 对象属性信息,如:last_modified、etag 等
type ObjstoreAttributes struct {
// ETag 对象文件内容的 md5 值
ETag string `json:"etag"`
// LastModified 对象文件最近被修改时间
LastModified time.Time `json:"last_modified"`
// Size 对象文件大小,单位 bytes
Size int64 `json:"size"`
// UserMetadata 用户额外定义该对象的元数据,以 "x-amz-meta-*" 请求头返回
UserMetadata map[string]string `json:"user_metadata"`
// UserTags 用户定义对象文件关联的标签
UserTags map[string]string `json:"user_tags"`
// VersionID 用于说明本次文件版本号
VersionID string `json:"version_id"`
}
功能点支持情况
方法名 | 功能点 | 腾讯云 | 阿里云 | Cloudflare |
---|---|---|---|---|
Upload | 上传文件 | 是 | 是 | 是 |
Delete | 删除对下 | 是 | 是 | 是 |
Get | 获取文件 | 是 | 是 | 是 |
Iter | 遍历文件 | 是 | 是 | 是 |
GetRange | 获取文件 | 是 | 是 | 是 |
Exists | 文件是否存在 | 是 | 是 | 是 |
SSE-KMS | 使用 KMS 加密 | 是 | 是 | 是 |
SSE-C | 使用自定义密钥 | 是 | 否 | 否 |
SSE-S3 | 使用提供商密钥 | 是 | 是 | 是 |
put_user_metadata | 用户自定义元数据 | 是 | 是 | 是 |
put_user_tags | 用户自定义标签 | 是 | 否 | 否 |
配置示例
最小化配置
objstore:
enable: true
type: s3
config:
bucket: "uptime-syncds-1251023941"
endpoint: "cos.ap-guangzhou.myqcloud.com"
access_key: ""
secret_key: ""
导出默认值
objstore:
enable: true
type: s3
config:
bucket: ""
endpoint: ""
region: ""
access_key: ""
insecure: true
secret_key: ""
put_user_metadata: {}
put_user_tags: {}
http_config:
idle_conn_timeout: 1m
signature_version: v4
list_objects_version: v2
bucket_lookup_type: auto
part_size: 67108864
sse_config:
type: ""
kms_key_id: ""
kms_encryption_context: {}
encryption_key: ""
配置说明
Config
名称 | 类型 | 说明 |
---|---|---|
bucket | string | 默认 bucket 名称 |
endpoint | string | 连接对象存储服务端的地址 |
region | string | 对象存储的区域 |
access_key | string | 对象存储的授权ID,支持环境变量 |
insecure | bool | 使用 http 或 https 连接对象存储服务端 |
secret_key | string | 对象存储的授权密钥,支持环境变量 |
session_token | string | 对象存储的授权密钥,支持环境变量 |
put_user_metadata | map[string]string | 上传文件时用户添加的元数据,在 http 获取对象时,以: x-amz-meta-{label}={value} 请求头返回 |
put_user_tags | map[string]string | 上传文件时用户添加的标签 |
http_config | HTTPConfig | 用于控制客户端通过 http 协议连接服务端的一些能力 |
signature_version | string | 客户端请求 s3 服务端使用的签名算法版本,取值:v2、v4,默认:v4 |
list_objects_version | string | 查询对象文件列表使用的接口版本,取值:v1、v2,默认:v2 |
bucket_lookup_type | string | 用于表示 bucket 的 URL 风格,可取值:auto、virtual-hosted、path |
part_size | uint64 | 用于大文件分块上传,单位字节,默认为:1024 * 1024 * 64,既64MB,最大分块不要超过 10000 个文件 |
sse_config | SSEConfig | 用于配置对象存储服务端加密 |
其中 access_key、secret_key 也可使用以下环境变量代替:
access_key | secret_key | session_token |
---|---|---|
AWS_ACCESS_KEY_ID | AWS_SECRET_ACCESS_KEY | AWS_SESSION_TOKEN |
AWS_ACCESS_KEY | AWS_SECRET_KEY | - |
MINIO_ACCESS_KEY | MINIO_SECRET_KEY | - |
MINIO_ROOT_USER | MINIO_ROOT_PASSWORD | - |
其中 access_key、secret_key 也可以使用 aws 的配置文件:
文件地址优先读取环境变量 “AWS_SHARED_CREDENTIALS_FILE” 如果为空,则使用以下默认地址:
$HOME/.aws/credentials
示例格式:
[default]
aws_access_key_id={aws_access_key_id}
aws_secret_access_key={aws_secret_access_key}
HTTPConfig
名称 | 类型 | 说明 |
---|---|---|
tls_client_config | TLSConfig | 允许你自定义 TLS 配置,以满足特定的需求,例如指定根证书、跳过证书验证、设置密码套件等 |
tls_handshake_timeout | time.Duration | 可以控制在建立 TLS 握手过程中等待的最大时间,客户端和服务器之间进行密钥交换和协商加密参数等操作,如果在指定的超时时间内未完成握手,客户端可以终止连接或采取其他处理方式 |
disable_keep_alives | bool | HTTP 的 keep-alive 是一种机制,允许客户端在单个 TCP 连接上发送多个 HTTP 请求,而无需为每个请求都建立和关闭连接,是否禁用 HTTP 的 keep-alive 功能,这样每个 HTTP 请求都会使用一个新的连接,意味着每次请求都需要建立和关闭连接 |
disable_compression | bool | 如果开启则请求中不会包含 “Accept-Encoding: gzip” 的请求头,即禁止了请求压缩,这意味着即使服务端返回的响应使用了gzip压缩,Transport也不会自动解压缩响应体 |
max_idle_conns | int | 可以控制在空闲连接池中保持的最大连接数,超过这个数量的空闲连接将被关闭,通过使用 keep-alive 机制,客户端可以在多次请求之间重用已经建立的连接,以减少每次请求的连接建立和断开的开销 |
max_idle_conns_per_host | int | 可以针对每个主机控制保持的最大空闲连接数,这可以使每个主机具有独立的连接池,而不是使用全局的连接池,每个主机可以独立地管理和复用空闲连接,以优化连接的使用和性能 |
max_conns_per_host | int | 用于可选地限制每个主机的总连接数,包括处于拨号、活动和空闲状态的连接 |
idle_conn_timeout | time.Duration | 空闲连接的超时时间,指定空闲连接在关闭之前保持的最长时间 |
response_header_timeout | time.Duration | 客户端在发送请求后等待服务器响应头的时间,如果在指定的超时时间内未收到响应头,客户端可以终止连接或采取其他处理方式 |
expect_continue_timeout | time.Duration | 用于在完全发送请求头后,等待服务器首次响应头的时间 |
max_response_header_bytes | int | 可以控制接收和处理服务器响应头的大小,响应头中包含了诸如状态码、响应头字段等信息,如果服务器的响应头超过了指定的最大字节数,那么将会触发一个错误,导致请求失败 |
write_buffer_size | int | 用于控制写缓冲区大小,它是用于临时存储要发送到传输层的数据的内存区域,设置为 0,则会使用默认值(目前为4KB) |
read_buffer_size | int | 用于控制读缓冲区大小,它是用于临时存储从传输层读取的数据的内存区域,设置为 0,则会使用默认值(目前为4KB) |
force_attempt_http2 | bool | 在配置了 Dial、DialTLS、DialContext 函数或 TLSClientConfig 时,会禁用 HTTP/2,这时可以配置开启,也会尝试使用 HTTP/2 协议进行升级,不过仍然需要确保服务器支持 HTTP/2 协议才能成功升级 |
TLSConfig
名称 | 类型 | 说明 |
---|---|---|
server_name | string | 进行 TLS 握手时,客户端会检查服务器返回的证书中的主机名与客户端期望的主机名是否匹配 |
insecure_skip_verify | bool | 默认情况下,客户端会验证服务器的证书链和主机名,以确保建立安全的 TLS 连接,避免被中间人攻击 |
min_version | string | 最低支持的 tls 版本,取值范围:TLSv1 TLSv1.1 TLSv1.2 TLSv1.3 |
max_version | string | 最高支持的 tls 版本,取值范围:TLSv1 TLSv1.1 TLSv1.2 TLSv1.3 |
ca_file | string | 用于定义客户端在验证服务器证书时使用的根证书,一般在自签证书时使用 |
cert_file | string | 客户端证书公钥 |
key_file | string | 客户端证书私钥 |
SSEConfig
名称 | 类型 | 说明 |
---|---|---|
type | string | 用于对象加密,可取值:SSE-KMS、SSE-C、SSE-S3 |
kms_key_id | string | x |
kms_encryption_context | map[string]string | x |
encryption_key | string | 配合 SSE-C 使用,指向存放 32 字节长度的密钥文件地址 |
是否支持三种加密方式,不同的云提供商可能存在差异,已知如下:
- 腾讯云COS支持:SSE-KMS、SSE-C、SSE-S3
- 阿里云OSS支持:SSE-KMS、SSE-S3
- 使用KMS托管密钥:SSE-KMS
一般需先在云提供商购买密钥管理服务,才可以使用。
- 客户自定义密钥:SSE-C
会把密钥通过 base64 加密,并通过 http 请求头传递,所以这种情况下建议使用 HTTPS 进行传输,参考文档。
名称 | 示例 | 说明 |
---|---|---|
X-Amz-Server-Side-Encryption-Customer-Key-Md5 | wlnU57TLDGlZNVzlkvopog== | 服务端使用此标头进行消息完整性检查以确保加密密钥传输无误 |
X-Amz-Server-Side-Encryption-Customer-Algorithm | AES256 | 指定加密算法,这里一定是 AES256 |
X-Amz-Server-Side-Encryption-Customer-Key | QUZtQXdJQlFpSWFyZEk0NURoNXlxM3NoZzVQUzVvN1I= | 加密密钥的 base64 编码 |
使用场景
使用腾讯云低频存储
# 对象存储配置
objstore:
enable: true
type: s3
config:
bucket: "uptime-syncds-1251023941"
endpoint: "cos.ap-guangzhou.myqcloud.com"
region: "ap-guangzhou"
access_key: ""
secret_key: ""
put_user_metadata:
x-amz-storage-class: "STANDARD_IA"
通过添加用户自定义元数据 “x-amz-storage-class” 为 “STANDARD_IA” 实现,存储类型参考文档。
使用托管的密钥加密
# 对象存储配置
objstore:
enable: true
type: s3
config:
bucket: "uptime-syncds-1251023941"
endpoint: "cos.ap-guangzhou.myqcloud.com"
region: "ap-guangzhou"
access_key: ""
secret_key: ""
sse_config:
type: "SSE-S3"
可以直接使用各云提供商托管的密钥进行服务端加密。使用上通过设置 “sse_config” 类型为 “SSE-S3”,当上传的所有对象均以各云提供商托管的密钥进行加密数据。
使用自定义加密密钥
- 对象存储配置部分
# 对象存储配置
objstore:
enable: true
type: s3
config:
bucket: "uptime-syncds-1251023941"
endpoint: "cos.ap-guangzhou.myqcloud.com"
region: "ap-guangzhou"
access_key: ""
secret_key: ""
sse_config:
type: "SSE-C"
encryption_key: "/opt/config/secret.key"
使用 “SSE-C” 加密类型,配置密钥路径为 “/opt/config/secret.key”,内容必须为长度 32 字节,如:“AFmAwIBQiIardI45Dh5yq3shg5PS5o7RYou”。
可通过 cat /opt/config/secret.key | wc -c
结果必须等于 32 如果为 33 则注意末尾隐藏的换行符号 \n
。
- 生成 secret.key 内容
以下以字母大小写、数字组合生成 32 字节长度的随机字符串:
package main
import (
"crypto/rand"
"fmt"
"log"
"math/big"
)
func generateSSECKey() (string, error) {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
charsetLen := big.NewInt(int64(len(charset)))
key := make([]byte, 32)
for i := range key {
randomIndex, err := rand.Int(rand.Reader, charsetLen)
if err != nil {
return "", err
}
key[i] = charset[randomIndex.Int64()]
}
return string(key), nil
}
func main() {
ssecKey, err := generateSSECKey()
if err != nil {
log.Fatal(err)
}
fmt.Print(ssecKey)
}
使用 go 编译,并把结果写入文件 /opt/config/secret.key
中。
go run a.go > /opt/config/secret.key
最后修改 30.10.2023: chore: update objstore (09b9c64)