CMDB
同步腾讯云的CVM
- 常见的CMDB设计模式
- 类云管CMDB设计方案与流程
- 资源管理模块开发
- 云商凭证管理模块开发
CMDB需求介绍
prd
常见的CMDB设计模式
model_base
nosql_base
CMDB模块划分与业务流程设计
flow
资源管理模块
- 接口的定义
- Save
- Search
- 业务控制器的实现(GRPC)
- mongodb insertone
- mongodb find
- gorestful 的 api接口
- search
- swagger 文档
- 完成模块加载并启动
资源同步器开发
资源同步器接口定义
统一各个云厂商对资源定义的差异
type ResourceProvider interface {
// 资源同步, 1000 ECS, Stream 接口
// SyncRequest 请求同步的参数 Region
// ResourceHandler 处理完一个就交给Handler一个
Sync(context.Context, *SyncRequest, *ResourceHandler) error
}
腾讯云资源同步器
- 准备一个腾讯云账号
- 云商API文档与SDK文档的使用
- API Explorer 使用这个浏览并请求 腾讯云API
package main
import (
"fmt"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
)
func main() {
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
credential := common.NewCredential(
"SecretId",
"SecretKey",
)
// 实例化一个client选项,可选的,没有特殊需求可以跳过
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "cvm.tencentcloudapi.com"
// 实例化要请求产品的client对象,clientProfile是可选的
client, _ := cvm.NewClient(credential, "", cpf)
// 实例化一个请求对象,每个接口都会对应一个request对象
request := cvm.NewDescribeInstancesRequest()
// 返回的resp是一个DescribeInstancesResponse的实例,与请求对象对应
response, err := client.DescribeInstances(request)
if _, ok := err.(*errors.TencentCloudSDKError); ok {
fmt.Printf("An API error has returned: %s", err)
return
}
if err != nil {
panic(err)
}
// 输出json格式的字符串回包
fmt.Printf("%s", response.ToJsonString())
}
- 腾讯云CVM SDK 测试
- 需要创建一个CVM
- 开发腾讯云CVM资源同步器(provider)
- 云商账号管理模块功能开发(secret)
- 云商账号资源同步功能开发(secret sync resource)
流式API: websocket使用
服务端实现:
// 使用云商账号凭证 来实现资源同步
// 我们有多个返回, 需要使用 websocket 来 把当前的 http链接 升降成 websocket协议(tcp封装)
func (h *handler) SyncResource(r *restful.Request, w *restful.Response) {
// w http.ResponseWriter 浏览器处理不了 之定义header
socket, err := h.Upgrader().Upgrade(w, r.Request, nil)
if err != nil {
response.Failed(w, err)
}
defer socket.Close()
req := &secret.SyncResourceRequest{}
h.controller.SyncResource(r.Request.Context(), req, func(sr *secret.SyncResponse) {
err := socket.WriteJSON(sr)
if err != nil {
h.log.Error().Msgf("websocket write error, %s", err)
}
})
}
使用客户端测试(Web)
// websocket链接
const connect = () => {
// 建立websocket链接
let socket = new WebSocket(`ws://127.0.0.1:8020/cmdb/api/v1/secret/ws/cllfqm5mjd0ku666k150/sync_resource`);
socket.onopen = function (e) {
console.log('socket open')
console.log(e)
};
socket.onmessage = function (event) {
// server 发送给terminal的数据
console.log(event)
term.write(event.data)
};
socket.onclose = function (event) {
if (event.wasClean) {
term.write(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
} else {
// e.g. server process killed or network down
// event.code is usually 1006 in this case
term.write('[close] Connection died');
}
};
socket.onerror = function (error) {
term.write(`[error]`);
};
}
编写mock模块
// 使用云商凭证同步资源 Stream
func (i *impl) SyncResource(
ctx context.Context,
in *secret.SyncResourceRequest,
hook secret.SyncResourceHandler) error {
for i := 1; i < 51; i++ {
hook(&secret.SyncResponse{
Id: fmt.Sprintf("cvm%d", i),
Name: fmt.Sprintf("cvm%d", i),
})
time.Sleep(1 * time.Second)
}
return nil
}
// _ "gitee.com/go-course/go12/devcloud-mini/cmdb/apps/secret/mock"
_ "gitee.com/go-course/go12/devcloud-mini/cmdb/apps/secret/impl"
关于敏感信息存储加密
// IbkhUeQczqSMgEvQI1avP5e4fG1XMG1FpsOWfvYoF5Y=
// h4TU/S8lMOUyx20ZJEwX8p6w8eD1/03V17l0QsnIKIs=
func TestEncrypt(t *testing.T) {
s := secret.NewSecret(secret.NewCreateSecretRequest())
s.Spec.Value = "123456"
if err := s.Encrypt("test"); err != nil {
t.Fatal(err)
}
t.Log(s)
if err := s.Decrypt("test"); err != nil {
t.Fatal(err)
}
t.Log(s)
}
脱敏:
GET /cmdb/api/v1/secret/clpu14tmjd0jgd01qnb0 HTTP/1.1
Host: 127.0.0.1:8020
Authorization: Bearer EBB4TpK6dbR5CUsyCSnPFYdD
Content-Type: application/json
Content-Length: 81
{
"id": "clpu14tmjd0jgd01qnb0",
"domain": "",
"namespace": "",
"spec": {
"type": 0,
"key": "AKIDGYjokXBg3F5pOm4BA4oNm14zmR7i7OXX",
"value": "a0IU*****",
"regions": [
"ap-nanjing"
],
"resource_types": [
0
]
}
}
关于同步任务的执行设计
可以参考: go cron
接入Trace(Opentelemetry)
安装Jaeger服务
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
-e COLLECTOR_OTLP_ENABLED=true \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
UI界面: http://localhost:16686/search
初始化Tracer, 使用Jaeger作为后端
type Config struct {
Provider TRACE_PROVIDER `toml:"provider" json:"provider" yaml:"provider" env:"TRACE_PROVIDER"`
Endpoint string `toml:"endpoint" json:"endpoint" yaml:"endpoint" env:"TRACE_PROVIDER_ENDPOINT"`
Enabled bool `toml:"enabled" json:"enabled" yaml:"enabled" env:"TRACE_ENABLED"`
ioc.ObjectImpl
}
func (t *Config) Init() error {
ep := t.Endpoint
if ep == "" || !t.Enabled {
return nil
}
exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(ep)))
if err != nil {
return err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource.Default()),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{}))
return nil
}
通过下面的配置开启
[trace]
enabled = true
endpoint = "http://localhost:14268/api/traces"
通过 ioc 开启trace
[app]
name = "cmdb"
key = "this is your app key"
[app.http]
web_framework = "go-restful"
host = "127.0.0.1"
port = 8020
enable_api_doc = true
api_doc_path="/swagger_docs"
enable_trace=true
[app.grpc]
host = "127.0.0.1"
port = 18020
enable_trace=true
[mongodb]
endpoints = ["127.0.0.1:27017"]
username = "admin"
password = "123456"
database = "cmdb"
enable_trace=true
[mcenter]
address = "127.0.0.1:18010"
client_id = "Z6OB8gLvF62VKEadAks6Gw1s"
client_secret = "jJPW5qeYLr0qS5Wvb3cqzrSeqmWaHmf7"
[log]
level = "debug"
[log.file]
enable = true
file_path = "logs/cmdb.log"
[trace]
enabled = true
endpoint = "http://localhost:14268/api/traces"
启动服务
导入trace配置
_ "github.com/infraboard/mcube/ioc/config/trace"
- cmdb 开启完Trace
- mcenter 已要开启trace
GET /cmdb/api/v1/secret/clpu14tmjd0jgd01qnb0 HTTP/1.1
Host: 127.0.0.1:8020
Authorization: Bearer mJRdh9W2DDNs9T7oDpz4OOC4
埋点
// 埋点,添加自定义trace
// 全局providre, ioc SetTracerProvider 已经设置好了 全局provider
p := otel.GetTracerProvider()
_, span := p.Tracer("cmdb").Start(r.Request.Context(), "ListSecret",
oteltrace.WithAttributes(attribute.String("username", "admin")),
)
defer span.End()
// ctx 需要继续往后传递(这个context里面包含 trace id)
// 因为没有后续逻辑 这里丢弃了context