腾讯云 Provider 是 ZenOps 项目中第二个完整实现的云平台 Provider,提供了对腾讯云 CVM 和 CDB 服务的查询能力。
实现时间: 2025-12-09
internal/provider/tencent/
├── client.go # 腾讯云客户端封装 (66 行)
├── cvm.go # CVM 实例查询 (217 行)
├── cdb.go # CDB 数据库查询 (178 行)
├── provider.go # Provider 接口实现 (111 行)
└── init.go # Provider 注册 (7 行)
cmd/
└── query_tencent.go # CLI 命令 (416 行)
总计: 995 行代码
采用懒加载模式管理 CVM 和 CDB 客户端:
type Client struct {
SecretID string
SecretKey string
Region string
cvmClient *cvm.Client // 懒加载
cdbClient *cdb.Client // 懒加载
}CVM 客户端获取:
func (c *Client) GetCVMClient() (*cvm.Client, error) {
if c.cvmClient != nil {
return c.cvmClient, nil
}
credential := common.NewCredential(c.SecretID, c.SecretKey)
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "cvm.tencentcloudapi.com"
client, err := cvm.NewClient(credential, c.Region, cpf)
if err != nil {
return nil, fmt.Errorf("failed to create CVM client: %w", err)
}
c.cvmClient = client
return client, nil
}优势:
- 延迟初始化,节省资源
- 统一错误处理
- 支持多区域
Provider.ListInstances
↓
ListCVMInstances (多区域聚合)
↓
listCVMInstancesInRegion (单区域查询)
↓
client.DescribeInstances (SDK 调用)
↓
convertCVMToInstance (数据转换)
↓
返回统一的 Instance 模型
request := cvm.NewDescribeInstancesRequest()
if opts.PageSize > 0 {
limit := int64(opts.PageSize)
request.Limit = &limit
}
if opts.PageNum > 1 {
offset := int64((opts.PageNum - 1) * opts.PageSize)
request.Offset = &offset
}关键映射:
| 腾讯云字段 | 统一模型字段 | 处理逻辑 |
|---|---|---|
| InstanceId | ID | 直接映射 |
| InstanceName | Name | 直接映射 |
| InstanceType | InstanceType | 直接映射 |
| InstanceState | Status | 直接映射 |
| PrivateIpAddresses | PrivateIP | 数组转换 |
| PublicIpAddresses | PublicIP | 数组转换 |
| Placement.Zone | Zone | 嵌套字段 |
| Tags | Tags | map 转换 |
| CreatedTime | Metadata | 时间字符串存储 |
// 遍历所有区域查找实例
for region, client := range p.clients {
logx.Debug("Searching instance in region", ...)
response, err := cvmClient.DescribeInstances(request)
if err != nil {
logx.Warn("Failed to describe instance", ...)
continue // 继续查询其他区域
}
if len(response.Response.InstanceSet) > 0 {
return convertCVMToInstance(...)
}
}- 分页参数类型: CDB 使用
uint64,CVM 使用int64 - 状态值: CDB 使用整数状态码,需要转换
- 端口处理: CDB 提供
Vport字段
func convertCDBStatus(status int64) string {
switch status {
case 0:
return "Creating"
case 1:
return "Running"
case 4, 5:
return "Isolated"
default:
return fmt.Sprintf("Unknown(%d)", status)
}
}基于项目实际需求,简化了 Database 模型:
database := &model.Database{
Provider: "tencent",
Region: region,
Tags: make(map[string]string),
ID: *inst.InstanceId,
Name: *inst.InstanceName,
Engine: "MySQL",
EngineVersion: *inst.EngineVersion,
Status: convertCDBStatus(*inst.Status),
Port: int(*inst.Vport),
Endpoint: *inst.Vip,
}func (p *TencentProvider) Initialize(config map[string]any) error {
// 1. 解析配置
secretID := config["secret_id"].(string)
secretKey := config["secret_key"].(string)
regions := config["regions"].([]any)
// 2. 创建多区域客户端
for _, r := range regions {
region := r.(string)
p.regions = append(p.regions, region)
p.clients[region] = NewClient(secretID, secretKey, region)
}
return nil
}func (p *TencentProvider) HealthCheck(ctx context.Context) error {
for region, client := range p.clients {
_, err := client.GetCVMClient()
if err == nil {
return nil // 至少一个区域可用即通过
}
}
return fmt.Errorf("all regions failed health check")
}zenops query tencent
├── cvm
│ ├── list
│ └── get <instance-id>
└── cdb
├── list
└── get <instance-id>
if tencentFetchAll {
pageNum := 1
pageSize := tencentPageSize
if pageSize <= 0 {
pageSize = 100 // 优化的默认值
}
for {
opts := &provider.QueryOptions{
Region: tencentRegion,
PageSize: pageSize,
PageNum: pageNum,
}
pageInstances, err := p.ListInstances(ctx, opts)
if err != nil {
return fmt.Errorf("failed to list instances (page %d): %w", pageNum, err)
}
instances = append(instances, pageInstances...)
if len(pageInstances) < pageSize {
break // 最后一页
}
pageNum++
}
}func getTencentConfig(accountName string) (*config.ProviderConfig, error) {
if accountName == "" {
// 查找第一个启用的账号
for _, acc := range cfg.Providers.Tencent {
if acc.Enabled {
return &acc, nil
}
}
return &cfg.Providers.Tencent[0], nil
}
// 查找指定账号
for _, acc := range cfg.Providers.Tencent {
if acc.Name == accountName {
return &acc, nil
}
}
return nil, fmt.Errorf("tencent account '%s' not found", accountName)
}问题: 腾讯云 SDK 统一使用同一套 common 包,无版本冲突
解决方案: 直接使用最新版本即可
import (
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
cdb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdb/v20170320"
)问题: CDB API 的 Limit 和 Offset 使用 uint64,而 CVM 使用 int64
解决方案: 在各自的实现中使用正确的类型
// CDB
limit := uint64(opts.PageSize)
request.Limit = &limit
// CVM
limit := int64(opts.PageSize)
request.Limit = &limit问题: 腾讯云返回时间字符串,统一模型使用 time.Time
解决方案: 将时间存储在 Metadata 中
if inst.CreatedTime != nil {
instance.Metadata["created_time"] = *inst.CreatedTime
}问题: SDK 返回 Tag 数组,模型使用 map
解决方案: 遍历转换
if inst.Tags != nil {
for _, tag := range inst.Tags {
if tag.Key != nil && tag.Value != nil {
instance.Tags[*tag.Key] = *tag.Value
}
}
}| 维度 | 腾讯云 | 阿里云 | 备注 |
|---|---|---|---|
| 代码行数 | 579 | 617 | 相近 |
| CLI 行数 | 416 | 320 | 腾讯云更详细 |
| SDK 版本 | 统一版本 | 需要区分 v1/v2 | 腾讯云更简单 |
| 分页类型 | uint64/int64 | int32 | 需注意类型 |
| 时间处理 | 字符串 | 字符串 | 都需转换 |
| 多区域 | 支持 | 支持 | 实现相似 |
- 单区域查询: ~500ms (取决于网络和实例数)
- 多区域聚合: 串行查询,时间 = 区域数 × 单区域时间
- 分页查询: 每页 ~200-300ms
- 客户端缓存: 每区域约 50KB
- 实例数据: 每个实例约 2KB
- 总体: 查询 1000 实例约占用 2MB
- 区域过滤: 使用
--region参数减少查询范围 - 并发查询: 可改进为 goroutine 并发查询多区域
- 结果缓存: 可添加本地缓存减少 API 调用
$ make build
Build complete: ./bin/zenops$ ./bin/zenops query tencent --help
查询腾讯云的 CVM 实例、CDB 数据库等资源信息。
$ ./bin/zenops query tencent cvm --help
查询腾讯云 CVM (云服务器) 实例。
$ ./bin/zenops query tencent cdb --help
查询腾讯云 CDB (云数据库) 实例。以添加 CLB (负载均衡) 为例:
- 创建
clb.go文件 - 实现
ListCLB和GetCLB方法 - 在
provider.go中添加接口方法 - 在 CLI 中添加
clb子命令
- 实例启停操作
- 配置修改
- 监控数据获取
- 成本分析
- 并发查询多区域
- 结果本地缓存
- 支持更多 CDB 引擎类型
- 支持 CLB、VPC 等更多服务
- 实时监控数据
- 资源关系图谱
- 成本优化建议
- v0.1.0 (2025-12-09): 初始实现,支持 CVM 和 CDB
- @eryajf - 项目发起人
- Claude (Anthropic) - AI 开发助手