对象存储

简要概述

基于 minio-go 类库,对接兼容亚马逊 S3 标准的对象存储,通过封装 ObjstoreBucket 接口,以便在应用层简化使用。

已测试以下服务:

  1. 腾讯云 COS
  2. 阿里云 OSS
  3. Cloudflare R2

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 字节长度的密钥文件地址

是否支持三种加密方式,不同的云提供商可能存在差异,已知如下:

  1. 腾讯云COS支持:SSE-KMS、SSE-C、SSE-S3
  2. 阿里云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)