需求:
编辑常用的命令,在某台主机上远程发送命令到其他主机并执行。能够查看操作记录、执行状态。
后续扩展
开发累积命令,小命令组合->大命令,中间件,远程部署、升级、监控、运维多个不同产品集群,多用户,CICD流程,安全防护,开发质量分析等
系统:
centos7
语言:
golang、shell等
表
通用字段:
id、created_at、modified_at、deleted_at
主机表(host)
user(账号)、ip、passwd
操作表(operation)
name、description、language(golang、shell)、input(输入字段名称及类型)、impl(硬编码实现、shell语句)【通过input达到的可能的扩展:多版本、安装、卸载、升级、回滚、重试】
操作记录表(operation_record)
target(user:1234@ip)、operation_id、operation_name、params(参数)、result(结果)
配置代理服务器
下载golang依赖包时,如果不能翻墙,需要配置代理服务器来加速包的下载安装
阿里云: export GOPROXY=https://mirrors.aliyun.com/goproxy/
nexus社区:export GOPROXY=https://gonexus.dev
goproxy.io:export GOPROXY=https://goproxy.io/
athens:export GOPROXY=https://athens.azurefd.net
官方提供的(jfrog,golang):
export GOPROXY=https://gocenter.io
export GOPROXY=https://proxy.golang.org
七牛云:export GOPROXY=https://goproxy.cn
go mod管理
go mod 管理
echo 'package main // import "infra_cbb"' >main.go
go mod init
初始化
go mod tidy
整理依赖包
go mod vendor
项目中创建vendor目录来保存依赖包
包搜索顺序
- vendor的层级搜索
规则是:
从引用文件所在的vendor路径下面搜索,
如果没有找到,那么从上层目录的vendor路径下面搜索,
直到src的vendor路径下面搜索。
- modules
Go 1.11版本支持临时环境变量GO111MODULE,通过该环境变量来控制依赖包的管理方式。当GO111MODULE的值为on时,那么就会使用modules功能,
这种模式下,$GOPATH不再作为build时导入的角色,依赖包会存放在$GOPATH/pkg/mod目录下。
工程中的依赖包也会从此目录下查找。
有关该功能的介绍,可以看Go1.1.1新功能module的介绍及使用。
- 查找顺序
GO111MODULE=off时,如果一个包在vendor和$GOPATH下都存在,那么使用顺序为:
优先使用vendor目录下面的包,
如果vendor下面没有搜索到,再搜索$GOPATH/src下面的包,
如果$GOPATH下面没有搜索到,那么搜索$GOROOT/src下面的包,
要么完整使用vendor下面的包,要么完整使用$GOPATH下面的包,不会混合使用。
常用替换
go.mod中加入
replace (
cloud.google.com/go => github.com/googleapis/google-cloud-go v0.43.0
golang.org/x/crypto => github.com/golang/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/exp => github.com/golang/exp v0.0.0-20190731235908-ec7cb31e5a56
golang.org/x/image => github.com/golang/image v0.0.0-20190802002840-cff245a6509b
golang.org/x/lint => github.com/golang/lint v0.0.0-20190409202823-959b441ac422
golang.org/x/mobile => github.com/golang/mobile v0.0.0-20190806162312-597adff16ade
golang.org/x/mod => github.com/golang/mod v0.1.0
golang.org/x/net => github.com/golang/net v0.0.0-20190724013045-ca1201d0de80
golang.org/x/oauth2 => github.com/golang/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/sync => github.com/golang/sync v0.0.0-20190423024810-112230192c58
golang.org/x/sys => github.com/golang/sys v0.0.0-20190804053845-51ab0e2deafa
golang.org/x/text => github.com/golang/text v0.3.2
golang.org/x/time => github.com/golang/time v0.0.0-20190308202827-9d24e82272b4
golang.org/x/tools => github.com/golang/tools v0.0.0-20190808195139-e713427fea3f
golang.org/x/xerrors => github.com/golang/xerrors v0.0.0-20190717185122-a985d3407aa7
google.golang.org/api => github.com/googleapis/google-api-go-client v0.7.0
google.golang.org/appengine => github.com/golang/appengine v1.6.1
google.golang.org/genproto => github.com/google/go-genproto v0.0.0-20190801165951-fa694d86fc64
google.golang.org/grpc => github.com/grpc/grpc-go v1.22.1
)
通过xorm逆向生成models代码
下载编译xorm
go get github.com/go-sql-driver/mysql
go get github.com/go-xorm/cmd/xorm
执行
../../../../../bin/xorm reverse mysql "root:root@tcp(192.168.199.107:3308)/infra_cbb?charset=utf8" templates/goxorm ~/workspace_personal/infra_cbb
说明
"root:root@tcp(192.168.199.107:3308)/infra_cbb?charset=utf8"
"账号:密码@tcp(ip:port)/database?charset=utf8"
这样生成了host.go、operation.go、operation_record.go 先放个models文件夹里
指针与取地址
知道一个变量的内存地址,就可以用指针去操作它
A类型,* A 指针类型,* A a 指针类型的变量a(a只能存地址),B类型,B b B类型的变量b(b存放具体值),B{xxx}B类型实例对象,&B{xxx} B类型实例对象的地址
封装启动器
思想:将各个启动项封装为对象,加入启动列表,程序启动调用各个对象的启动方法。声明启动上下文,将启动项的对象实例写入。
- 创建common文件夹放一些基础类
- 封装个自己的日志系统,日志是快速定位问题,同时也是明确责任是否甩锅的基础中的基础,必须逻辑清晰,后面日志分析也方便。先简单定义两个,在common文件夹里编写log.go
go get github.com/sirupsen/logrus
package common
import (
"fmt"
"github.com/sirupsen/logrus"
)
// 流程名
const flowBoot = "启动器"
func logInfo(flow, operation, input interface{} ,args ...interface{}) {
template := fmt.Sprintf("流程:%v,操作:%v,状态:成功,输入:%v", flow, operation, input)
logrus.Infof(template, args...)
}
func logPanic(flow, operation, input, err interface{},args ...interface{}) {
template := fmt.Sprintf("流程:%v,操作:%v,状态:失败,输入:%v,错误:%v", flow, operation, input, err)
logrus.Panicf(template, args...)
}
- 在common文件夹里starter.go编写Starter接口,统一启动器的方法,先简单写几个方法
type Starter interface {
Name() string // 名称
Load() // 加载
Instance() interface{}// 获取客户端实例
Order() int // 加载顺序
}
- starter.go里继续编写启动注册器(内部就是个数组),接收启动器的注册、被调用时查询(add、get),因为启动器们有优先级,所以单独为[]Stater定义了类型并实现了sort功能需要的接口Len、Less、Swap
/**
* 启动器注册器
*/
type starterRegister struct {
starters starters
}
/**
* 注册方法
*/
func (this *starterRegister) Add(starter Starter) {
this.starters = append(this.starters, starter)
}
func (this *starterRegister)Get() starters {
return this.starters
}
// 实例
var StarterRegister = new(starterRegister)
// 数组实现排序相关方法
type starters []Starter
func (this starters) Len() int {
return len(this)
}
func (this starters) Less(i, j int) bool {
return this[i].Order() < this[j].Order()
}
func (this starters) Swap(i, j int) {
this[i], this[j] = this[j], this[I]
}
- 在common文件夹里编写boot.go,定义一个程序启动器+全局上下文(为了像ioc容器那样有个全局容器),定义Run方法调度启动器
package common
import (
"sort"
)
// 定义app上下文
type appCtx map[string]interface{}
// app上下文实例
var AppCtx appCtx = make(appCtx)
// 定义一个启动器
type BootApplication struct {}
// 启动器运行
func (this *BootApplication)Run() {
starters:=StarterRegister.Get()
sort.Sort(starters)
for _, starter := range starters {
starter.Load()
if starter.Instance() != nil {
AppCtx[starter.Name()]=starter.Instance()
logInfo(flowBoot,"%v写入AppCtx","nil",starter.Name())
}
}
}
- 编写程序入口,创建文件夹brun,里面创建main.go调一下程序启动器
package main // import "infra_cbb"
import "infra_cbb/common"
func main() {
logrus.Info("程序启动^_^")
new(common.BootApplication).Run()
}
配置加载启动器
因为用配置文件比较通用、方便,更好的动态配置略微麻烦些,先放一个配置文件在brun文件夹里config.yaml(yaml格式也很通用,用这个什么结构化都支持了)。在common文件夹里创建starter_viper.go,里面init方法会自动执行,直接注册到启动注册器数组里
go get github.com/spf13/viper
package common
import (
"github.com/spf13/viper"
)
/**
* 注册到启动注册器
*/
func init() {
StarterRegister.Add(new(ViperStarter))
}
type ViperStarter struct {}
func (v *ViperStarter) Name() string {
return "ViperStarter"
}
func (v *ViperStarter) Load() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("brun/")
err := viper.ReadInConfig()
if err != nil {
logPanic(flowBoot,"加载%v","configFile->%v",
err,v.Name(),viper.ConfigFileUsed())
}
logInfo(flowBoot,"加载%v","configFile->%v",v.Name(),viper.ConfigFileUsed())
}
func (v *ViperStarter) Instance() interface{} {
return nil
}
func (v *ViperStarter) Order() int {
return 0 // 顺位0
}
xorm数据库ORM包
- 因为用到数据库这个必须有,先在brun文件夹里的config.yaml编写配置
db:
driverName: mysql
database: infra_cbb
uname: root
passwd: root
addr: 192.168.199.107:3308
connMaxLifetime: 12
maxIdleConns: 1
maxOpenConns: 3
showSQL: true
- 在common文件夹编写starter_xorm.go,把xorm的单例客户端加入全局上下文
go get github.com/go-sql-driver/mysql
go get github.com/go-xorm/xorm
package common
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/go-xorm/xorm"
"github.com/spf13/viper"
"time"
"xorm.io/core"
)
/**
* 注册到启动注册器
*/
func init() {
StarterRegister.Add(new(XormStarter))
}
type XormStarter struct{
engine *xorm.Engine
}
func (x *XormStarter) Name() string {
return "XormStarter"
}
func (x *XormStarter) Load() {
// 获取配置
driverName := viper.GetString("db.driverName")
uname := viper.GetString("db.uname")
passwd := viper.GetString("db.passwd")
addr := viper.GetString("db.addr")
database := viper.GetString("db.database")
showSQL := viper.GetBool("db.showSQL")
maxOpenConn := viper.GetInt("db.maxOpenConns")
maxIdleConns:= viper.GetInt("db.maxIdleConns")
connMaxLifetime:=viper.GetDuration("db.connMaxLifetime")
dataSource := fmt.Sprintf("%v:%v@tcp(%v)/%v?charset=utf8&parseTime=true&loc=Local",
uname, passwd, addr, database)
sqlLogLevel := core.LOG_DEBUG
// 创建连接
engine, err := xorm.NewEngine(driverName, dataSource)
if err != nil {
logPanic(flowBoot,"加载%v","driverName->%v;dataSource->%v",
err,x.Name(),driverName,dataSource)
}
//设置日志显示
engine.ShowSQL(showSQL)
engine.SetLogLevel(sqlLogLevel)
//设置连接池
engine.SetMaxOpenConns(maxOpenConn)
engine.SetMaxIdleConns(maxIdleConns)
engine.SetConnMaxLifetime(connMaxLifetime * time.Hour)
x.engine = engine
logInfo(flowBoot,"加载%v",
"driverName->%v;dataSource->%v;showSQL->%v;SQLLogLevel->%v;maxOpenConn->%v;maxIdleConns->%v;connMaxLifetime->%v",
x.Name(),driverName,dataSource,showSQL,sqlLogLevel,maxOpenConn,maxIdleConns,connMaxLifetime * time.Hour)
}
func (x *XormStarter) Instance() interface{} {
return x.engine // 返回单例
}
func (x *XormStarter) Order() int {
return 1 // 顺位1
}
验证启动器
执行main.go debug一下
![](https://img.haomeiwen.com/i10280146/4c7911c7154d7d20.png)
通过查看自己打印的日志,看看程序的各个变量都对了没有
目前代码目录
![](https://img.haomeiwen.com/i10280146/471848569e39b1f3.png)
存在问题:可以看到我们要依赖注入时,须将实力放入appCtx这个map中,用到时再根据key获取并强转类型,key容易写错,强制转换也比较麻烦。
于是,搜索了一些依赖注入的框架,发现go-spring还不错,下面使用该包进行项目的改造。
网友评论