devops

腾讯蓝鲸cmdb二次开发文档

目录

1 本地启动cmdb

2 cmdb数据库表设计

3 代码模块介绍

4 功能介绍

5 请求调用流程

6 新增接口

7 cmdb如何操作mongo数据库

一  本地启动cmdb

 服务器启动回顾

部署cmdb的时候,输入./start.sh 就可以启动12个服务,那么针对这12个服务进行分析一下。首先看一下启动后输出的内容:

image.png

这里的配置参数,是根据运行的配置脚本所生成的,如:

image.png

生成配置文件位置:

configcenter/src/bin/build/3.2.17/cmdb_adminserver/configures/*

二  打开cmdb项目,把上一步cmdb_adminserver/configures/下生成的所有配置文件覆盖到configcenter/resources/configures/目录中 (即替换配置文件)。

然后修改configcenter/resources/configures/migrate.conf文件 

image.png

三、本地启动项目


cmdb是微服务框架,把平台拆分成了12个服务,本次我们启动hostServer服务,其他服务启动类似。

启动hostServer的main方法(host.go文件):

image.png

启动的的时候配置参数:Program arguments

例如:

–addrport=127.0.0.1:60001 –logtostderr=false –log-dir=./logs –v=3 –regdiscv=127.0.0.1:2181

每个服务启动时 Program arguments 参数不同 可参考服务启动时显示的参数进行配置

image.png

二 cmdb数据库表设计

表名关联业务接口备注
cc_ApplicationBase基础资源-业务信息存储  
cc_AsstDes模型管理-关联类型数据存储  
cc_History  
cc_HostBase基础资源-主机数据存储  
cc_HostFavourite  
cc_idgenerator生成ID的,记录每种记录最大的ID,类似mysql 主键的auto_increment  
cc_InsAsst  
cc_ModuleBase业务资源-业务拓扑中的叶子节点数据  
cc_ModuleHostConfig主机分配到具体业务模块表,bk_module_id是cc_ModuleBase.bk_module_id  
cc_Netcollect_Device  
cc_Netcollect_Property  
cc_ObjAsst模型与模型之间的关联关系,见模型管理-模型拓扑  
cc_ObjAttDes模型的所有字段  
cc_ObjClassification存储模型分组数据,针对模型管理-新建分组业务  
cc_ObjDes存储模型数据,针对模型管理-模型-新建模型业务  
cc_ObjectBase自定义模型的实例  
cc_ObjectUnique  
cc_UserGroupPrivilege当添加用户权限的时候,
表里面才显示数据
 通过group_id字段关联
cc_UserGroup添加用户权限管理  
cc_UserCustom用户自定义收藏模块 index_v4_recently
存储最近添加唯一标识

cc_UserAPI
  
cc_TopoGraphics存储模块在拓扑图中的位置
 用户删除位置,数据表还是保留当前字段,位置置空
  

cc_System
系统  
cc_Subscription事件推送  
cc_SetBase业务资源-> 业务拓扑  
cc_PropertyGroup   
cc_Process业务资源-> 进程管理 存储进程 bk_biz_id 对应 cc_ApplicationBase

表中的bk_biz_id
cc_ProcOpTask  
cc_ProcInstanceModel  
cc_ProcInstanceDetail  

cc_Proc2Module
  
cc_Privilege  
cc_PlatBase  

 三 模块介绍

ui:                            前端代码 

web_server                 后端服务,所有请求入口

api_server :             接口层,这一层是系统的api服务网关 

scene_server/         业务场景模块,基于资源管理对应用场景的封装

     admin_server     把配置文件放入zk

     event_server      事件订阅

     host_server        主机

     topo_server        模型,实例,业务,模块等

     proc_server        进程  

source_controller   :源管理模块,把资源类型进行了抽象,每一类资源由一类微服务进程来管理

       auditController   审计

       hostController    主机

       objectController 对象

       procController    进程

storage:                  提供系统所需的资源存储等系统服务

apimachinery :      根据要操作的资源生成新的url,调用source_controller

 四、

请求执行过程:

api_server

1  http   —>   api_server(网关)  

2 api_server(网关)   –>  scene_server下的xxServer/service/service.go文件中

scene_server

3  service.go文件接收路由并调用响应的方法A

4  方法A处理逻辑包括验证头信息请求参数,最后调用apimachinery

apimachinery

5 在apimachinery中会生成一个新的URL 并根据这个URL向source_controller发起请求

source_controller

6 source_controller/XXcontroller/service/service.go接收请求 并调用响应方法B

storage

7  B方法调用了storage中的方法

 ——————————————

v3.2.17

蓝鲸CMDB文档

本地启动cmdb

一、服务器启动回顾

部署cmdb的时候,输入./start.sh 就可以启动12个服务,那么针对这12个服务进行分析一下。首先看一下启动后输出的内容:

image.png

这里的配置参数,是根据运行的配置脚本所生成的,如:

image.png

生成配置文件位置:

configcenter/src/bin/build/3.2.17/cmdb_adminserver/configures/*

二  打开cmdb项目,把上一步cmdb_adminserver/configures/下生成的所有配置文件覆盖到configcenter/resources/configures/目录中 (即替换配置文件)。

然后修改configcenter/resources/configures/migrate.conf文件 

image.png

三、本地启动项目


cmdb是微服务框架,把平台拆分成了12个服务,本次我们启动hostServer服务,其他服务启动类似。

启动hostServer的main方法(host.go文件):

image.png

启动的的时候配置参数:Program arguments

例如:

–addrport=127.0.0.1:60001 –logtostderr=false –log-dir=./logs –v=3 –regdiscv=127.0.0.1:2181

每个服务启动时 Program arguments 参数不同 可参考服务启动时显示的参数进行配置

image.png

CMDB数据库表设计

1表功能

表名关联业务接口备注
cc_ApplicationBase基础资源-业务信息存储  
cc_AsstDes模型管理-关联类型数据存储  
cc_History  
cc_HostBase基础资源-主机数据存储  
cc_HostFavourite  
cc_idgenerator生成ID的,记录每种记录最大的ID,类似mysql 主键的auto_increment  
cc_InsAsst  
cc_ModuleBase业务资源-业务拓扑中的叶子节点数据  
cc_ModuleHostConfig主机分配到具体业务模块表,bk_module_id是cc_ModuleBase.bk_module_id  
cc_Netcollect_Device  
cc_Netcollect_Property  
cc_ObjAsst模型与模型之间的关联关系,见模型管理-模型拓扑  
cc_ObjAttDes模型的所有字段  
cc_ObjClassification存储模型分组数据,针对模型管理-新建分组业务  
cc_ObjDes存储模型数据,针对模型管理-模型-新建模型业务  
cc_ObjectBase自定义模型的实例  
cc_ObjectUnique  
cc_UserGroupPrivilege当添加用户权限的时候,
表里面才显示数据
 通过group_id字段关联
cc_UserGroup添加用户权限管理  
cc_UserCustom用户自定义收藏模块 index_v4_recently
存储最近添加唯一标识

cc_UserAPI
  
cc_TopoGraphics存储模块在拓扑图中的位置
 用户删除位置,数据表还是保留当前字段,位置置空
  

cc_System
系统  
cc_Subscription事件推送  
cc_SetBase业务资源-> 业务拓扑  
cc_PropertyGroup   
cc_Process业务资源-> 进程管理 存储进程 bk_biz_id 对应 cc_ApplicationBase

表中的bk_biz_id
cc_ProcOpTask  
cc_ProcInstanceModel  
cc_ProcInstanceDetail  

cc_Proc2Module
  
cc_Privilege  
cc_PlatBase  

2 名词介绍

资源名称:

biz:   业务线

set:集群

module:模块

host:主机

字段描述:

bk_supplier_id :供应商ID  默认为0 ,不做变动

bk_host_id  :     主机id

bk_obj_id.   :     模型id, 模型的唯一标识,如,szone,redis,set,module

关键词:

association 关联类型

object_association模型关联

inst_association     实例关联

privilege              权限

inst                      模型实例

object                 对象:如 module,set等

classification      模型分类

custom_query:userapi  自定义api

favorites            主机收藏

模块介绍

ui:                            前端代码 

web_server                 后端服务,所有请求入口

api_server :             接口层,这一层是系统的api服务网关 

scene_server/         业务场景模块,基于资源管理对应用场景的封装

     admin_server     把配置文件放入zk

     event_server      事件订阅

     host_server        主机

     topo_server        模型,实例,业务,模块等

     proc_server        进程  

source_controller   :源管理模块,把资源类型进行了抽象,每一类资源由一类微服务进程来管理

       auditController   审计

       hostController    主机

       objectController 对象

       procController    进程

storage:                  提供系统所需的资源存储等系统服务

apimachinery :      根据要操作的资源生成新的url,调用source_controller

功能介绍

1 常用方法

法/文件作用位置详细位置
FindSingleObject(bk_obj_id)查找是否存在此模型Topo_servertopo_server  /core/operation/object.go
condition.CreateCondition()创建一个结构体存储查询条件common/condition/condition.go 
dbInst :=   logic.Instance.       Table(tName).        Find(condition).            Sort(sort).              Start(uint64(skip)).                  Limit(uint64(limit))将查询条件赋值给结构体赋值解析:logic   是一个结构体,Instance是结构体中的一个字段Instance同时还是一个接口,Table是其中一个实现方法Table()的返回值是CollectionCollection是一个结构体,挂在其结体下的方法有findSort,Start,Limit返回值是 Find这个结构体src/source_controller/hostcontroller/logics/object.go GetObjectByCondition()方法
dbInst.All() 即Find.All()dbInst.One() 即Find.One()查询数据库解析:one() 和All()这两个方法都挂在Find结构体下,而在上一行中已经将查询条件赋值给Find,所以当调用All方法时,可直接从Find中取值进行查询尽行查询例如:f.dbc.DB(f.dbname).C(f.collName).Find(f.filter).One(result)src/storage/dal/mongo/mgo_mongo.go 

2 UI:

前端 VUE:

cmdb vue组件库:https://magicbox.bk.tencent.com/components_vue/2.0/example/index.html#/

前端vue 代码规范使用 Eslint插件,如不按照规范编写则编译报错:

部分规则如下:

不要有多个连续空行。

关键字后面要有一个空格,

函数参数列表括号前面要有一个空格。

始终使用 === 不使用 ==,

逗号后面有一个空格。

else 与它的大括号同行

始终处理函数的 err 参数

浏览器全局变量始终添加前缀 window.

var 声明,每个声明占一行

无多余逗号

逗号必须放在当前行的末尾

. 应当与属性同行

文件以空行结尾

键名和键值之间要有空格

构造函数的名字以大写字母开始

…..等等

若要关闭Eslint验证:

src/ui/build/webpack.base.conf.js 文件中 注释掉eslint

image.png

前端请求示例:

请求调用流程:getHostList()——>search()—> getHostList()  ———>searchHost()   
方法位置ui/src/views/resource/index.vue ui/src/components/hosts/table/index.vueui/src/store/modules/api/host-search.js   
功能页面:获取所有主机调用子组件中的方法接收参数 ,发送http请求   

文件介绍

位置作用    
src/ui/src/componentsVue组件,方便复用    
ui/src/store/modules/api/*封装URL,接收参数,向后台发请求    
      
      
src/ui/src/views /*所有页面    
ui/src/views/index/index.vue首页    
ui/src/views/business/index.vue基础资源/业务    
ui/src/views/resource/index.vue基础资源/主机    
ui/src/views/hosts/index.vue业务资源/主机    
ui/src/views/eventpush/index.vue模型管理/事件推送    
ui/src/views/topology/index.vue业务资源/业务拓扑    
ui/src/views/custom-query/index.vue 业务资源/动态分组    
ui/src/views/process/index.vue业务资源/进程管理    
ui/src/views/audit/index.vue审计与分析/操作审计    
ui/src/views/model-association/index.vue模型管理/关联模型    
ui/src/views/model-manage/index.vue模型管理/模型    
ui/src/views/model-topology/index.vue模型管理/模型拓扑    
ui/src/views/business-model/index.vue模型管理/业务模型    
      
      
      

3 web_server

文件作用    
src/web_server/service/service.go接收所有请求    
src/web_server/middleware/login.go中间件:判断请求是否已经登陆,以及是否需要转发到api_server    
      

4 api_server:网关,对请求分类,转发进入sence_server

文件作用    
api_server/service/v3/uri.go对请求进行分类:topo,host,event,proc    
api_server/service/v3/service.go根据请求类别,对请求进行转发    
      

5 sence_server:业务处理,调用apimachinery 向资源层发起请求

文件做用隶属接口       
XX__server/service/service.go接收路由请求,请求入口        
XX__server/service/*请求响应方法        
topo_server/service/*对set,module,biz,privite相关请求进行响应        
topo_server/service/inst_module.gotopo_server/service/inst_set.gotopo_server/service/inst_business 对模块,集群,业务初步逻辑处理        
topo_server/core/operation/inst_module.go对modul进一步逻辑处理ModuleOperationInterface       
topo_server/core/operation/inst.go对逻辑处理结束的对象,向下级服务发起crud请求:module,set,biz,自定义模型,等对象        
topo_server/service/privilege.go用户权限相关请求进行响应        
topo_server/core/privilege/privilege.go对用户权限的逻辑处理        

6 apimachinery :向资源层发起请求

文件/方法作用    
apimachinery/objcontroller/inst/inst.go对object的增删改查请求进行处理:module ,set,biz,自定义对象 等    
apimachinery/objcontroller/privilege/api.go权限,角色相关接口    
apimachinery/objcontroller/privilege/privilege.go权限,角色,相关接口实现方法    

7 controller :对资源(数据库)进行操作

操作方式:

在每个微服务下通常有三个文件夹 :app,service,logics

image.png

Service文件夹: 下主要是请求入口,已及请求的响应方法,进行逻辑处理,然后调用logics文件夹下的方法

Logics文件夹:  下通常有一个都有一个logics.go文件,logics.go文件中都有一个结构体Logics 如:

                type Logics struct{

                    Instance dal.RDB

                   } 

              Logics文件夹中的所有方法都挂在结构体Logics下,如 func (lgc *Logics) DelModule(); 

       Logics结构体中的Instance 是对数据操作的一些接口如:

        type RDB interface {

                   Clone() RDB

                   // Table collection 操作 

                   Table(collection string) Table

                   // StartTransaction 开启新事务

                  StartTransaction(ctx context.Context) (RDB, error)

                   }

       所以通过结构体Logics,Logics文件夹下的方法可以实现对数据库的操作。

文件作用    
auditcontroller收集和查询 操作记录(审计)    
hostcontroller主机相关操作    
objectcontrollerSet, module,biz,自定义模型等操作    
proccontroller进程相关操作    
source_controller/objectcontroller/service/inst_action.go对实例进行增删改查:module,set,biz等    
source_controller/objectcontroller/service/common.go对数据库操作的通用方法    
objectcontroller/service/*对权限,角色,模型,biz相关接口进行响应    
objectcontroller/service/privilege_action.go对用户组权限进行增删改查    
      
      

8 common

文件     
src/common/*封装了对数据库操作的方法    
src/common/condition封装查询条件,如sort,limit,start,fiels    
src/common/conf读取解析配置文件    
src/common/mapstr将condition的fields字段转换成map    
      

请求调用流程

1 执行流程

HTTP—-  /*  —-web_server

http请求首先进入web_server 在其中会验证登陆等信息

web_server———/api/v3/*——–> api_server 

web_server中/api/v3的请求会被转发到api_server,在api_server中请求被分类,以host资源为例:/api/v3/*   会转换成/host/v3/* 向下一层服务发起请求

api_server—-/host/v3/*——->scene_server—-调用—>apimachinery(类似SDK)

api_server向scene_server发起请求,在scene_server中处理业务逻辑,scene_server调用apimachinery向下一级服务发起请求,在apimachinery中url会根据资源类型作出更改,以更改后的url向资源层发请求

apimachinery ———> source_controller ———调用—— storage

apimachinery向source_controller发起请求,在source_controller中调用storage封装的方法完成对数据库的操作

——————————————————————————————————-

ps:由于api_server只处理url路径是 /api/v3/* 的请求,所以部分请求 url 不是 /api/v3/开头则不会进入api_server,而是进入web_server进行处理。web_server通过调用apimachinery,向source_controller发起请求 来完成对资源的操作。

2  介绍

api_server 将收到的请求处理,分为四类转发进入sence_server下的四个微服务,分别是,topo,host,proc,event 四大类。

例如:/api/v3/biz/…在api_server   中请求会被转成    /topo/v3/biz/…

topo类包括:audit,biz,topo,identifier,inst,module,object,objects,/object/attr,objectatt,set,                        privilege

host类包括:host,hosts,userapi,usercustom,/findmany/modulehost

event类包括:event

proc类包括:proc

3 CMDB API请求示例

ps:每个微服务下都有一个../service/service.go文件作为请求入口

3.1 获取主机基础信息详情

  • API: GET /api/{version}/hosts/{bk_supplier_account}/{bk_host_id}
  • http://localhost:8083/api/v3/hosts/0/77
  • 功能说明:获取77号主机基础信息详情
  • input body: 无

 http请求——–>  api_server

api_server: 在src/api_server/service/v3/service.go 中接收所有跟路径是/api/v3/ 的请求

                    请求变成/host/v3/hosts/0/77 进入下一个服务

api_server ——> scene_server : 

scene_server:    在src/scene_server/host_server/service/service.go   接收所有跟路径是/host/v3 的路由

                           其中/hosts/{bk_supplier_account}/{bk_host_id} 接收到请求 并调用     

                           GetHostInstanceProperties()方法:获取参数,处理参数

                          在 src/apimachinery/hostcontroller/host/api.go文件GetHostByID()方法中 进入下一级服务,                            跳转路径:/host/v3/host/77

 scene_server——-source_controller:

 source_controller:在src/source_controller/hostcontroller/service/service.go文件中接收所有跟路径   

                                为/host/v3的路由,/host/v3/host/{bk_host_id} 接收请求,并调用GetHostByID()方法

                                 最后调用storage,src/storage/dal/mongo/mgo_mongo.go文件中One()方法完成数据库

                               查询 。

3.2 查询业务线

  API: POST /api/{version}/biz/search/{bk_supplier_account}

  参数:{ “condition”: {}}

   condition 参数为业务的任意属性,如果不写代表搜索全部数据

  例:  http://localhost:8083/api/v3/biz/search/0

    apiServer 接收请求并转换成:/topo/v3/app/search/0

    topo_server 接收请求并调用 SearchBusiness方法—— 调用FindBusiness()(实现方法在本 页)—-  FindOriginInst(实现方法在本页)……

                            最后调用在 src/apimachinery/objcontroller/inst/inst.go。SearchObjects方法  

   请求变成: /insts/biz/search

   TopoConroller

   调用SearchInstObjects方法响应,然后调用src/source_controller/objectcontroller/service/common.go        GetObjectByCondition方法

 3.3 查询模型实例

API POST /api/{version}/inst/association/search/owner/{bk_supplier_account}/object/{bk_obj_id}

 参数{

   “condition”:{

        “bk_weblogic”:[

            {

                “field”:”bk_inst_name”,

                “operator”:”$regex”,

                “value”:”qq”

            }

        ]

    }

}

例:http://localhost:8083/api/v3/inst/association/search/owner/0/object/redis

api_server 收到请求转化成 /topo/v3/inst/association/search/owner/0/object/redis进入下一服务

业务场景 :topo_server

     src/scene_server/topo_server/service/service_initfunc.go 接收请求 转到           

     SearchInstByAssociation方法—    FindInstByAssociationInst—-FindInst—-FindOriginInst—    

                      c.clientSet.ObjectController().Instance().SearchObjects()—-

    src/apimachinery/objcontroller/inst/inst.go SearchObjects()生成URL:/insts/object/search 进入   

     下一级服务.

资源管理 :object_controller

     /insts/{obj_type}/search 接收请求 调用响应方法SearchInstObjects()——  GetHostByCondition—all()              

3.4 条件查询主机 

API: POST /api/{version}/hosts/search

参数:可根据根据set 、host、biz、module等模型属性进行查询

{
    "page":{
        "start":0,
        "limit":10,
        "sort":"bk_host_id"
    },
    "pattern":"",
    "bk_biz_id":2,
    "ip":{
        "flag":"bk_host_innerip|bk_host_outerip",
        "exact":1,
        "data":[
        ]
    },
    "condition":[
        {
            "bk_obj_id":"host",
            "fields":[
            ],
            "condition":[
            ]
        },
        {
            "bk_obj_id":"module",
            "fields":[
            ],
            "condition":[
            ]
        },
        {
            "bk_obj_id":"set",
            "fields":[
            ],
            "condition":[
            ]
        },
        {
            "bk_obj_id":"biz",
            "fields":[
            ],
            "condition":[
                {
                    "field":"default",
                    "operator":"$ne",
                    "value":1
                }
            ]
        }
    ]
}

示例:

localhost:8083/api/v3/hosts/search

参数:{}

api_sever 接收请求 (生成url:/host/v3/hosts/search)—->

sence_service(src/scene_server/host_server/service/service.go )——SearchHost()—s.Logics.SearchHost()—-SearchHostByConds()—-sh.searchByHostConds()——-sh.lgc.CoreAPI.HostController().Host().GetHosts:  path=src/apimachinery/hostcontroller/host/api.go

url==/hosts/search

进入 host_controller

src/source_controller/hostcontroller/service/service.go—–GetHosts()–GetObjectByCondition()—all()

3.5 创建模块

POST /api/{version}/module/{bk_biz_id}/{bk_set_id}

input body:

{

    “bk_module_name”:”cc_module”,

    “bk_supplier_account”:”0″,

    “bk_parent_id”:0

}

示例:

localhost:8083/api/v3/module/3/2

参数:{ “bk_module_name”:”devops”}

http  ———> api_server —-  跳入下一服务,URL:/topo/v3/module/3/2

//查询模型类型是否存在

topo_server :/module/{app_id}/{set_id} 接收上一请求——>CreateModule()方法

                        ——-FindSingleObject()//查询 — 跳入下一服务,url:/insts/module/search

object_controller:/insts/{obj_type}/search 接收上一请求——>SearchInstObjects                       

//创建模型

topo_server:    ——ModuleOperation().CreateModule()

object_controller:         /insts/{obj_type}接收请求

3.6 创建集群

API: POST /api/{version}/set/{bk_biz_id}

参数

{

   “bk_set_name”:”wefd”,  集群名字

   “bk_parent_id”:0,          集群上级id 可以是业务线 ,或者其他,具体参照拓扑模型

   “bk_biz_id”:1               业务线

}

示例:localhost:8083/api/v3/set/3

topo_server  /set/{app_id} 接收请求

新增接口流程

1 简介版

查询主机:localhost:8083/api/v3/hosts/selectAll

1  url 设计: 以/api/v3/{type}/….  开头的请求会被web_server转发进入 api_server

2  type 代表资源类型 根据type 类型 被转发进入sence_server

3  在host_server中编写请求 /host/v3/hosts/selectAll的入口,以及响应方法selectAll(),在selectAll()中处理 

    调用资源层完成,业务逻辑的处理

4  selectAll()方法调用src/apimachinery  中的httpclient 向controller发起请求

     所以在src/apimachinery/hostcontroller/host/api.go 中编写方法 ,使用httpClient向host_controller 发起请

    求

    请求的url 设计没有规定 如 subPath := “/hosts/myJob

     则完整请求是 localhost:8083/host/v3/hosts/myJob

5 在hostcontroller中 编写请求    /host/v3/hosts/myJob的 路由入口

6  编写myJob的响应方法 查询数据 返回数据

ps  : 从第4步开始 src/apimachinery  中的httpclient 向controller发起请求 只是纯粹的对资源进行操作,不包含业务逻辑,所以 一般情况下 只需要变动sence_server,无需变动apimachinery 和controller

2 实操版

1  url 设计: 以/api/v3/{type}/….  开头的请求会被web_server转发进入 api_server

2  type 代表资源类型 可以为:

biz,topo,identifier,inst,module,objectset,privilege,host,hosts,userapi,usercustom, event proc等,具体请见src/api_server/service/v3/uri.go 中资源划分

3 新增接口查询主机,假设 url设计为:localhost:8083/api/v3/hosts/selectAll,在api_server中 会根据url中的关键词hosts ,判定要操作的资源是host,所以url  从 /api/v3/hosts/selectAll     变成  /host/v3/hosts/selectAll

4 在host_server中,src/scene_server/host_server/service/service.go 接收所有/host/{version}

的请求

所以在此文件中编写/host/v3/hosts/selectAll请求入口

api.Route(api.POST(“/hosts/selectAll).To(s.SelectAllHost))

—————-

响应方法:

func (s *Service) SelectAllHost(req *restful.Request, resp *restful.Response) {

//获取头信息

pheader := req.Request.Header

// new 出一个结构体 存储查询参数

body := new(meta.HostCommonSearch)

//获取讲请求中的参数 解析赋值到 结构体body中

json.NewDecoder(req.Request.Body).Decode(body)

// 获得头信息pheader 和 传递的参数body后  调用下个方法SearchHost() 进行查询

host, err := s.Logics.SearchHost(pheader, body)

}


Ps: SearchHost()方法挂在结构体Logics下,Logics结构体中有接口可以调用下一个微服务

func (lgc *Logics)SearchHost(phader http.Header,body Body) (*metadata.GetHostsResult, error) {

   //从传入的body中 获取到参数 赋值在 结构体 metadata.QueryInput中

   query := metadata.QueryInput{

      Start:     body.start,

      Limit:     body.limit,

      Sort:      common.BKHostInnerIPField,

      Fields:    common.BKHostInnerIPField,

   }

//  把查询参数query 传给apimacheniry,由apimacheniry 向controller发起请求

  lgc.CoreAPI.HostController().Host().MyjobSpimachinery()

}

编写 MyjobSpimachinery方法:

在src/apimachinery/hostcontroller/host/api.go 中编写 方法 MyjobSpimachinery(),

在MyjobSpimachinery中

         subPath为要跳转的路由入口,可自己设计

         metadata.GetHostsResult返回值类型

         .Do() 进行跳转

         .Into() 把查询结果放进new 出来的结构体,并return

代码如下:

func (t *hostctrl) MyjobSpimachinery(ctx context.Context, h http.Header, opt *metadata.QueryInput) (resp *metadata.GetHostsResult, err error) {
   resp = new(metadata.GetHostsResult)
   subPath := "/hosts/myJob"
   err = t.client.Post().
      WithContext(ctx).
      Body(opt).
      SubResource(subPath).
      WithHeaders(h).
      Do().
      Into(resp)
   return
}

——————————————————————-

5  在src/source_controller/hostcontroller/service/service.go中接收所有请求是/host/v3 的请求

所以在此文件中编写/hosts/myJob 的入口

api.Route(api.POST(“/hosts/myJob”).To(s.MyJob))

响应方法:

func (s *Service) MyJob(req *restful.Request, resp *restful.Response) {

   //获取参数调用数据库进行查询

}

6.

CMDB中对mongo的操作

1、

cmdb对mongo的常规操作进行了一层封装,方便之后的CRUD,在代码中发现最常见的一种查询方式如下:

lgc.Instance.Table(“表名”). Find(map) .All(ctx,&results);

map代表查询条件,通过这行代码完成查询并且把查询结果赋值给results,下面将对这行代码进行解释

观察结构体Logics

type Logics struct {

   Instance dal.RDB

   Cache    *redis.Client

   *backbone.Engine

}

发现Logics下 Instance dal.RDB 是一个interface ,其实现方法 有对db的一些操作  如下

type RDB interface {

   Clone() RDB

   // Table collection 操作

   Table(collection string) Table

   // StartTransaction 开启新事务

   StartTransaction(ctx context.Context) (RDB, error)

   // Commit 提交事务

   Commit(context.Context) error

   // Abort 取消事务

   Abort(context.Context) error

}

其中Table(collection string) Table是一个interface,其实现方法有

type Table interface {

   // Find 查询多个并反序列化到 Result

   Find(filter Filter) Find

   // Aggregate 聚合查询

   AggregateOne(ctx context.Context, pipeline interface{}, result interface{}) error

   AggregateAll(ctx context.Context, pipeline interface{}, result interface{}) error

   // Insert 插入数据, docs 可以为 单个数据 或者 多个数据

   Insert(ctx context.Context, docs interface{}) error

   // Update 更新数据

   Update(ctx context.Context, filter Filter, doc interface{}) error

   // Delete 删除数据

   Delete(ctx context.Context, filter Filter) error

}

观察上面结构体总结如下:

Logics   是一个结构体,Instance是结构体中的一个字段。

即:Logics.Instance

Instance同时还是一个接口,Table是其中一个实现方法。

 即 :Logics.Instance.Table(“表名”)

Table(name String)的返回值是Collection,Collection是一个结构体指定了要操作的表名。

Find(map)方法挂在Collection结构体下,Find方法将Collection 和map(查询条件)放入Find结构对应的字段。 返回Find结构体 。

即: inst :=  Logics.Instance.Table(“表名”).Find(map)

此时inst 是一个指定了查寻条件和 要操作的表名的Find结构体

All()方法 挂在Find结构体下:  

 Logics.Instance.Table(“表名”).Find(map).all(results)

进行最终查询。

在以上的操作中只是为了将各种查询条件放入Find结构中,最终All()方法从Find结构体中取出查询条件进行查询,将结果赋值给results;

All 方法代码如下:

func (f *Find) All(result interface{}) error {

   f.dbc.Refresh()

   query := f.dbc.DB(f.dbname).C(f.collName).Find(f.filter)

   query = query.Select(f.projection)

   query = query.Skip(int(f.start))

   query = query.Limit(int(f.limit))

   query = query.Sort(f.sort…)

    //执行查询,若是查询出错 返回错误信息

   return query.All(result)

}

实操:

1 查询内网ip是121.21.13.14的主机

首先创建一个map存放查询条件

condition:=make(map[string]interface{})

condition[“bk_host_innerip”]=“121.21.13.14”

condition[“bk_host_id”]=“host”

然后定义一个可变的 map数组接收查询结果

results := make([]map[string]interface{}, 0)

最后进行查询

lgc.Instance.

   //指定查询的表

   Table(“cc_HostBase”).

          //查询条件   

             Find(condition)

                 //进行查询

                .All(ctx,&results);

find方法只是为了将查询条件赋值给Find结构体,最后的All方法从Find结构体中取出查询条件编写查询语句进行查询

2  插入一条信息进入表cc_HostBase中

//创建插入信息

inputc := input.(map[string]interface{})

inputc[“name”]=“张三”

//开始插入

lgc.Instance.

   //指定查询的表

   Table(“cc_HostBase”).

                 //进行查询

                .insert(ctx,inputc);

3 在上文查询语句 Logics.Instance.Table(“表名”).Find(map).all(results)中,将查询条件放在map中,但并不能完全满足需求,例如:查询主机id 不等于”23″的主机,或者大于/小于某个值时都无法用简单的map进行封装查询条件,而在cmdb中还有另外一种存放查询条件的结构体

3.1 查询条件放在结构体condition中

type condition struct {

   start        int64

   limit        int64

   sort         string

   fields       []Field

   or           []OR

   filterFields []string

}

// 上面这个结构体 调用下面这个接口的实现方法

type Condition interface {

   SetPage(page types.MapStr) error

   SetStart(start int64)

   GetStart() int64

   SetLimit(limit int64)

   GetLimit() int64

   SetSort(sort string)

   GetSort() string

   SetFields(fields []string)

   GetFields() []string

   Field(fieldName string) Field

   NewOR() OR

   Parse(data types.MapStr) error

   ToMapStr() types.MapStr

}

接口Condition 有Field 字段,其中字段fields 是个interface 其实现方法有

type Field interface {

   Eq(val interface{}) Condition

   NotEq(val interface{}) Condition

   Like(val interface{}) Condition

   In(val interface{}) Condition

   NotIn(val interface{}) Condition

   Lt(val interface{}) Condition

   Lte(val interface{}) Condition

   Gt(val interface{}) Condition

   NotGt(val interface{}) Condition

   Gte(val interface{}) Condition

   ToMapStr() types.MapStr

}

所以:cond.Field(字段”).NotEq(“条件”)

如:

// 创建condition结构体

cond := condition.CreateCondition()

//查询内网ip不等于121.21.13.14的机器

cond.Field(“bk_host_innerIp”).NotEq(“121.21.13.14”)

//创建结构体condMapStr

var condMapStr mapstr.MapStr= mapstr.New()

//将conf 进行一些转化

condMapStr.Merge(cond.ToMapStr())

//condMapStr放着查询条件,作为参数放入Find中进行查询

err:= lgc.Instance.Table(“cc_HostBase”). Find(condMapStr) .All(ctx,&results);

Redis在cmdb中的作用

redis在cmdb中主要用于事件订阅

1 redis的ident文件夹下中存放的信息有:

   业务信息,主机信息,模块信息,集群信息,进程信息

image.png
image.png

2 redis中的信息同步:

  2.1 以上信息并非是实时更新,event_server负责从mongo数据库中同步最新的数据,大概每隔1分钟向redis中同步一次信息。

   2.2  在event_server启动时调用app.Run()方法——   errCh <- distribution.Start(ctx, cache, db, rpccli)————chErr <- ih.StartHandleInsts()中代码片段:

go func() {

   ih.fetchHostCache()

   for range time.Tick(time.Second * 60) {

      ih.fetchHostCache()

   }

}()

可设置redis中的信息多久和mongo同步一次 

 2.3 新增订阅事件,删除订阅事件,会时时同步到redis,redis中记录的只是 订阅事件id

image.png

3 event_server 直接调用storage 操作数据库,不再调用controller 

event_server : 事件订阅服务

请求入口:src/scene_server/event_server/service/service.go:

    //查询订阅事件

api.Route(api.POST(“/subscribe/search/{ownerID}/{appID}”).To(s.Query))

//只测试连通性

api.Route(api.POST(“/subscribe/ping”).To(s.Ping))

//测试推送

api.Route(api.POST(“/subscribe/telnet”).To(s.Telnet))

//新增订阅事件

api.Route(api.POST(“/subscribe/{ownerID}/{appID}”).To(s.Subscribe))

//删除订阅事件

api.Route(api.DELETE(“/subscribe/{ownerID}/{appID}/{subscribeID}”).To(s.UnSubscribe))

//修改订阅事件

api.Route(api.PUT(“/subscribe/{ownerID}/{appID}/{subscribeID}”).To(s.Rebook))

4 生产者–消费者

1生产者:

type Service struct {

   Core     *backbone.Engine

   Instance dal.RDB

   Cache    *redis.Client

}

在结构体Service中有  Cache    *redis.Client 

当对资源进行增删改查时,在controller中操作成功后,会向redis中新增一个消息 如下:

ec := eventclient.NewEventContextByReq(req.Request.Header, cli.Cache)

ec.InsertEvent(metadata.EventTypeInstData, objType, metadata.EventActionUpdate, newData)

lpush(key,value) 将消息放入redis

这时在redis中,如图4个消息存放于队列中等待被消费:

image.png

2 消费者

在 src/scene_server/event_server/identifier/identifier.go :

handleInstLoop方法:

在服务启动的时候 调用handleInstLoop()方法,其中for循环中ih.popEventInst()会和redis建立连接,并从redis中根据key获取到值

for {

   event := ih.popEventInst()

   if nil == event {

      time.Sleep(time.Second * 2)

      continue

   } 

上面for循环内的代码会不停的向redis建立连接取值,当value为空时,继续建立连接会造成资源浪费,所以在ih.popEventInst()中,从redis中取值时并未使用rpop,而是使用了阻塞命令brpop,当value为空时线程会阻塞在那

image.png

审计信息的记录

8/10

审计操作是在业务逻辑层完成的操作:

Sence_server ————调用———— src/apimachinery/auditcontroller ——跳转——— audit_controller

在业务逻辑层中(server)处理业务后,根据返回的结果新增操作的审计信息

apimachinery 内封装的有新增审计信息方法:

新增业务操作      AddBusinessLog()

查询审计信息   GetAuditLog ()

新增主机操作   AddHostLog()

新增模块操作   AddModuleLog()

新增对象操作   AddObjectLog()

新增进程操作   AddProcLog()

新增集群操作   AddSetLog()

server 调用apimachinery 封装的方法,上述方法之外的操作不记录审计。

具体新增审计代码(1):

type Service struct {

   *options.Config

   *backbone.Engine

   *logics.Logics

   disc discovery.DiscoveryInterface

}

type Engine struct {

   sync.Mutex

   ServerInfo types.ServerInfo

   CoreAPI    apimachinery.ClientSetInterface

   SvcDisc    ServiceDiscoverInterface

   Language   language.CCLanguageIf

   CCErr      errors.CCErrorIf

}

aResult, err := service.CoreAPI.AuditController().AddHostLogs(context.Background(), common.BKDefaultOwnerID, appID, user, pheader, log)

通过结构体Service   调用CoreAPI 即apimachinery 内封装的方法 

9/10

具体新增审计代码(2)

在topo_server 中 

src/scene_server/topo_server/service/service_initfunc.go 可以接收请求有:

新增biz,

新增module,

新增set,

新增Inst 

以上请求的响应方法处理逻辑之后,最终都要调用src/scene_server/topo_server/core/operation/inst.go下的

func (c *commonInst) CreateInst(){}方法。

在CreateInst中 新增审计信息 如下:

NewSupplementary().Audit(…).CommitCreateLog(…)

CommitCreateLog方法———commitSnapshot()在commitSnapshot中根据参数不同调用AuditController()中封装好的审计方法

image.png

src/common/tablenames.go   所用到的表名,每个表名在这里都有一个变量表示;数据库查询时不直接写查询的表名,而是使用表名所代表的变量

association 关联关系

在topo_server;

src/scene_server/topo_server/service/service_initfunc.go:中有着所有关联关系的入口,分类如下

1  关联类型:创建一种关联关系用于实例间关联事被引用

image.png

查询关联类型

“/topo/association/type/action/search

 新增关联类型

 “/topo/association/type/action/create

更新一个关联类型

  “/topo/association/type/{id}/action/update

删除一个关联类型

  “/topo/association/type/{id}/action/delete

2 主机和模型实例关联关系

image.png

/inst/association/action/search

/inst/association/action/create

/inst/association/{association_id}/action/delete

10/10

3模型拓扑关联关系

image.png

/object/association/action/search


/object/association/action/create


/object/association/{id}/action/update


/object/association/{id}/action/delete

权限管理

1 创建角色,添加成员

image.png

创建角色时,选择拥有此角色的成员;  也称新建用户分组

topo_server.  API: POST /api/{version}/topo/privilege/group/{bk_supplier_account}

参数:分组名,分组成员

{

    “group_name”:”管理员”,

    “user_list”:”owen;tt”

}

src/apimachinery/objcontroller/privilege/privilege.go:  CreateUserGroup() 跳转进入object——controller

image.png

src/source_controller/objectcontroller/service/service.go:接收请求

api.Route(api.POST(“/privilege/group/{bk_supplier_account}”).To(s.CreateUserGroup))

表:cc_Usergroup

image.png

2 添加的成员必须时 已经存在的账户  :查询现有的的用户

web_server :ws.GET(“/user/list”, s.GetUserList)

————— src/web_server/middleware/user/public.go :GetUserList

———— — —  src/web_server/middleware/user/plugins/method/self/userinfo.go :GetUserList

image.png

在cmdb开源代码中,不支持多用户,在代码固定了一个 admin 用户

3  编辑用户分组权限

image.png

API: POST /api/{version}/topo/privilege/group/detail/{bk_supplier_account}/{group_id}

参数:

{bk_middleware: {

    bk_tomcat: [“search”, “update”, “delete”], 

        redis: [“search”, “update”, “delete”]

}

}

4 查询用户权限:

API: GET /api/{version}/topo/privilege/user/detail/{bk_supplier_account}/{user_name}

http://localhost:8083/api/v3/topo/privilege/user/detail/0/admin  获取用户admin的权限

apisix路由地址重写

路径匹配改写

比如请求网关是 /test/order/all, 然后后端接收的路径应该是 /order/all, 中间的test只是充当服务名的标识。

路径 填  /test/*    匹配/test/ 这种请求, /test 是不满足的

路径改写选正则改写

匹配 /^test/(.*)   表示匹配/test/之后的部分,作为第一个匹配到的值,变量名为$1

转发路径模板 /$1    即取上一个匹配后的参数拼接转发

 

下一步 选择上游,选择已经建好的上游

SaltStack 运维通关手册–SaltStack State 框架

配置和启用 Salt State Tree

Salt State 系统的核心是 SLS 或 Salt State 文件。 SLS 表示系统应处于的状态,并设置为使用一种简单的格式包含此数据。 这通常称为配置管理。
States 状态存储在 master 上的文本文件中,并通过 master 文件服务器按需传送到 minions。 状态文件的集合即构成 State Tree。
要在 Salt 中开始使用集中式的状态系统,必须首先设置 Salt File Server。 编辑 master 配置文件(file_roots)并添加以下内容:

file_roots:
  base:
    - /etc/salt/srv/salt/base

重启 salt-master

pkill salt-master
salt-master -d

准备 top file 文件
我们之前在 master 上配置的 file_roots 目录下(如果没有配置,默认为 /srv/salt)创建了一个名为 top.sls 的文件,现在修改这个文件。
修改文件: /etc/salt/srv/salt/base/top.sls

base:
  "*":
    - webserver

top file 文件被划分为多个不同的环境(稍后讨论)。默认环境是 base。 在 base 环境下,定义了一系列 minion 匹配映射关系; 现在只需简单的指定为所有主机 *。
创建一个 sls 文件,路径 /etc/salt/srv/salt/base/webserver.sls

nginx:
  pkg:
    - installed

第一行称为 ID 声明,可以是一个任意标识符。 在上面的这种情况下,它定义了要安装的包的名称。
第二行称为 state 声明,它定义了我们正在使用哪些 Salt States。 在此示例中,我们使用 pkg state 来确保安装了给定的包。
第三行称为 Function 声明,它定义了在 pkg state 模块中要调用的函数。
Salt 会将文件以指定的渲染器进行渲染格式化数据,默认为 Jinja2 模板中使用 YAML。
接下来,让我们运行我们创建的状态文件。 在 master 服务器上打开终端并运行:

salt '*' state.apply

www.sublimetext.com:
----------
          ID: nginx
    Function: pkg.installed
      Result: True
     Comment: Package nginx is already installed
     Started: 22:57:25.504406
    Duration: 65.675 ms
     Changes:

Summary for www.sublimetext.com
------------
Succeeded: 1
Failed:    0
------------
Total states run:     1
Total run time:  65.765 ms

我们的 Master 指示所有目标 minions 运行 state.apply。 在没有任何 SLS 目标的情况下执行此功能时,minion 将下载 top file 文件并尝试匹配其中的表达式。 当 minion 与表达式匹配时,将为其列出的模块下载、编译和执行。
在没有指定任何 SLS 文件名称的情况下调用 state.apply 的效果相当于运行 state.highstate
在指定了 SLS 文件名称的情况下调用的 state.apply 将运行 state.sls

命令执行完成后,minion 将报告所有采取的行动和做的所有更改的摘要。
state 文件目录结构规则
如上面的示例中,SLS 文件 webserver.sls 简称为 webserver。 在 top.sls 或 Include 声明中引用时,SLS 文件的命名空间遵循一些简单的规则:
.sls 的后缀被丢弃了 (也就是说 webserver.sls 变成了 webserver)。

子目录被做了更好的组织,其中:
每个子目录在 Salt 状态和命令行中用点(遵循 Python 导入模型)表示。 文件系统上的 webserver/dev.sls,在 Salt 中称为 webserver.dev。由于斜杠表示为点,因此 SLS 文件不能在名称中包含点(SLS 文件后缀的点除外)。 你将无法正确匹配 SLS 文件 webserver_1.0.sls,因为 webserver_1.0 将与目录 /webserver_1/0.sls 匹配
子目录中名为 init.sls 的文件将通过目录的路径引用。 因此,webserver/init.sls 称作 webserver。
如果 webserver.sls 和 webserver/init.sls 都存在,webserver/init.sls 将被忽略,webserver.sls 将被作为合法的 webserver 使用。
更复杂的 STATES、REQUISITES
require 关键字用以进行 state 依赖,表明多个 state 直接的依存关系。编辑 /etc/salt/srv/salt/base/webserver.sls 文件:

nginx:
  pkg.installed: [] # 安装 nginx 软件包

  file.absent:  # 删除影响 service 模块的文件
    - names:
      - /etc/init/nginx.conf
      - /etc/nginx/conf.d/default.conf

/etc/nginx/nginx.conf:
  file.managed:
    - source: salt://webserver/nginx.conf
    - require:
      - pkg: nginx

nginx service:
  service.running:
    - name: nginx
    - watch:
      - file: /etc/nginx/nginx.conf
      - file: /etc/nginx/conf.d/default.conf
    - require:
      - pkg: nginx

/var/www/index.html:
  file:
    - managed
    - source: salt://webserver/index.html
    - require:
      - pkg: nginx

/var/www/index.html 是一个 ID 声明,在实例中同样作为 file state 的目标位置存在。
file 表示该示例使用 Salt 的 file state。
managed 表示使用 managed function 将从 master 服务器下载文件并将其安装在指定的位置。
absent 表示使用 absent function 来删除指定的文件。
source 作为 managed 函数的参数传递进去。
watch 表示监听某个任务,当任务状态为 Changes 时,会操作重启 nginx。
require 表示这个 state 任务依赖别的 state 任务。
pkg 作为 一个 require 依赖,指向了一个 state 和一个 ID,及我们声明的 pkg 安装 nginx 部分,该声明告诉 Salt 在安装 nginx 服务之前不要安装 HTML 文件。

创建目录 /etc/salt/srv/salt/base/webserver/,然后创建 source 源文件 index.html,输入如下内容:

<p>Hello,SaltStack</p>

编辑 Nginx 主配置文件 /etc/salt/srv/salt/base/webserver/nginx.conf:

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;
    server {
        listen       80;
        location / {
            root   /var/www;
            index  index.html index.htm;
        }
    }
    include /etc/nginx/conf.d/*.conf;
}

执行命令 salt ‘*’ state.apply webserver

image.png

测试是否正常访问 http 服务

curl http://127.0.0.1

 

image.png

模板、包含和扩展
在处理复杂业务逻辑时,SLS 模块可能需要编程逻辑或内联执行。 这是通过模块模板实现的。 使用的默认模块模板系统是 Jinja2,这可以通过更改 master 配置中的 renderer 值来配置。
测试文件 /etc/salt/srv/salt/base/test_temp.sls 

{% for usr in 'moe','larry','curly' %}
{{ usr }}:
  group:
    - present
  user:
    - present
    - groups:
      - {{ usr }}
    - require:
      - group: {{ usr }}
{% endfor %}

示例中,我们使用 for 循环遍历一个列表,分别调用 group 和 user 模块创建对应的组和用户。
sls 文件写法遵循 yaml 和 jinjia2 语法即可。
执行 salt ‘*’ state.apply test_temp

image.png

查看任务是否确实完成。

cat /etc/group | tail -3

 

image.png

在 SLS 模块中使用环境变量
测试文件 /etc/salt/srv/salt/base/test_env_var.sls 

{% set myenvvar = salt['environ.get']('MYENVVAR') %}
{% if myenvvar %}

创建一个从环境变量获取内容的文件:
  file.managed:
    - name: /tmp/hello
    - contents: {{ salt['environ.get']('MYENVVAR') }}

{% else %}

Fail - 没有指定环境输入:
  test.fail_without_changes

{% endif %}

进入目录 /etc/salt/srv/salt/base/,然后执行命令 MYENVVAR=”world” salt-call state.template test_env_var

image.png

如果不执行环境变量,salt-call state.template test_env_var

image.png

在 SLS 模块中使用 grains
测试文件 /etc/salt/srv/salt/base/test_grains.sls 

echo 服务器ID:{{ grains.id }}: cmd.run

salt '*' state.apply test_grains

 

image.png

在模板中调用 Salt 模块的方法
由 minion 加载的所有 Salt 模块都可在模板系统中使用。 这允许在目标系统上实时收集数据。 它还允许从 sls 模块中轻松运行 shell 命令。
测试示例 /etc/salt/srv/salt/base/test_salt_mod.sls: 

调用 Salt module:
  cmd.run:
    - name: echo "Salt State 调用 module {{ salt['network.hw_addr']('eth0') }}"

示例中,我们只想 cmd.run 模块,但传入参数中调用了 network.hw_addr 获取 eth0 的 mac 地址。
执行 salt ‘*’ state.apply test_salt_mod

image.png

include 导入其他文件中的任务
新建目录 /etc/salt/srv/salt/base/python
创建文件 /etc/salt/srv/salt/base/python/python-libs.sls 

python-dateutil: pkg.installed

调用 pkg.installed 安装 python-dateutil 软件
创建文件 /etc/salt/srv/salt/base/python/django.sls

include:
  - python.python-libs

django:
  pip.installed:
    - require:
        - pkg: python-dateutil

示例中,我们通过 include 包含了 python/python-libs.sls 声明的任务,并通过 require 依赖了它。
include 的方法与 python 导入模块类似,忽略 .sls 后缀,并通过 . 作为连接符
执行 salt ‘*’ state.apply python.django

image.png

extend 用来修改已经定义的任务
我们可以使用 Extend 修改以前的声明。
首先创建目录 /etc/salt/srv/salt/base/web/,然后定义一个 nginx 安装的 state 文件 /etc/salt/srv/salt/base/web/nginx.sls 

nginx:
  pkg.installed: []
  file.absent:
  - name: /etc/init/nginx.conf

定义一个推送 nginx 文件,扩展 nginx 重启的 state 文件 /etc/salt/srv/salt/base/web/web.sls

include:
  - web.nginx

extend:
  nginx:
    service:
      - running
      - watch:
          - file: nginx_config

nginx_config:
  file.managed:
    - source: salt://fileroot/nginx/a.conf
    - name: /etc/nginx/conf.d/a.conf

新建存放 nginx 配置文件的目录,mkdir -p /etc/salt/srv/salt/base/fileroot/nginx/
新建 nginx 配置文件 touch /etc/salt/srv/salt/base/fileroot/nginx/a.conf
执行 state 文件

salt '*' state.apply web.web

 

image.png

我们可以看到 nginx 执行了重启操作。
开发自定义的 State 模块
在使用状态模块之前,必须将其分发给各 minions。 可以通过将它们放入 salt://_states/ 中来完成。 然后可以通过运行 saltutil.sync_states 或 saltutil.sync_all 将它们手动分发给 minions。 或者,在运行 highstate 状态时,自定义状态类型将自动同步。
注意:使用文件名中携带连字符的状态模块会导致 !pyobjects 例程出现问题。 请坚持使用下划线的最佳做法。
任何已与 minions 同步的自定义状态(与 Salt 的默认状态集中的名称之一相同的)将替换具有相同名称的默认状态。 请注意,状态模块的名称根据其文件名默认为同一个(即 foo.py 变为状态模块 foo ),但是可以使用 virtual 函数覆盖其名称。
自定义 state 模块遵循的步骤
设置返回数据字典并执行任何必要的输入验证(类型检查,寻找互斥参数的使用等)。
检查是否需要进行更改。 最好通过附带的执行模块中的信息收集功能来完成此操作。 该状态应该能够使用该函数的返回值来判断该 minion 是否已经处于所需状态。
如果步骤 2 发现 minion 已经处于所需状态,则立即退出,并返回 True 结果,而无需进行任何更改。
如果步骤 2 发现确实需要进行更改,请检查状态是否在测试模式下运行(即使用 test=True)。 如果是这样,则退出并返回值为 None 的结果、相关注释和(如有可能)将进行哪些更改的描述。
执行所需的更改。 应该再次使用附带的执行模块中的函数来完成此操作。 如果该函数的结果足以告诉您是否发生了错误,则可以退出并返回 False 结果和相关注释以说明发生了什么。
再次从步骤 2 执行相同的检查,以确认 minion 是否处于所需状态。 就像在第 2 步中一样,此函数应该能够通过其返回数据来告诉您是否需要进行更改。
设置返回数据并返回。
自定义 state 调用 salt 组件
状态模块可以使用 __salt__ 调用 salt 执行模块。 使用 __grains__ 调用 salt 数据。 使用 __states__ 调用 salt state 模块。 类似 ret = __states__[‘file.managed’](name=’/tmp/myfile’, source=’salt://myfile’)
自定义 state 返回数据
name:与传递给状态的 name 参数值相同。
changes:一个描述变更的字典。 每个被更改的事物都应该是一个键,其值则作为另一个字典,其中包含旧/新值的键称为 old 和 new。
result: 取值为三个值之一。 如果操作成功,则为 True;否则为 False;如果状态以测试模式运行,则为 None,即 test=True;如果状态未以测试模式运行,则将进行更改。
自定义 state 模块示例
自定义执行模块文件:/etc/salt/srv/salt/base/_modules/my_custom_module.py: 

import random
def current_state(*k, **kw):
    return random.choice([True, False])

def change_state(*k, **kw):
    return random.choice([True, False])

创建目录 /etc/salt/srv/salt/base/_states/,然后添加自定义 state 模块文件:/etc/salt/srv/salt/base/_states/custom_state.py

import salt.exceptions

def enforce_custom_thing(name, foo, bar=True):
    '''
    定义一个 state 状态

    这个状态模块做一个定制的事情。它调用执行模块
    `my_custom_module` 来检查当前系统并执行任何
    需要更改。

    name
        The thing to do something to
    foo
        一个请求参数
    bar : True
        一个带默认值的参数
    '''
    ret = {
        'name': name,
        'changes': {},
        'result': False,
        'comment': '',
        }

    # 做基本的数据确认,参数核对.
    if bar == True and foo.startswith('Foo'):
        raise salt.exceptions.SaltInvocationError(
            'Argument "foo" cannot start with "Foo" if argument "bar" is True.')

    # 核对当前的系统状态,是否需要做修改
    current_state = __salt__['my_custom_module.current_state'](name)

    if current_state == foo:
        ret['result'] = True
        ret['comment'] = 'System already in the correct state'
        return ret

    # 如果我们运行在 `test=true` 状态下, 将不做任何更改.
    if __opts__['test'] == True:
        ret['comment'] = 'The state of "{0}" will be changed.'.format(name)
        ret['changes'] = {
            'old': current_state,
            'new': 'Description, diff, whatever of the new state',
        }

        #  如果运行在 test 模式,返回 `None`
        ret['result'] = None

        return ret

    # 最后,执行修改并返回结果
    new_state = __salt__['my_custom_module.change_state'](name, foo)

    ret['comment'] = 'The state of "{0}" was changed!'.format(name)

    ret['changes'] = {
        'old': current_state,
        'new': new_state,
    }

    ret['result'] = True

    return ret

调用自定义 state 模块文件:/etc/salt/srv/salt/base/call_custom_state.sls

测试自定义 state 模块:
  custom_state:
    - enforce_custom_thing      # 调用自定义 state 模块中的函数
    - name: a_value
    - foo: Foo
    - bar: False

执行:

salt '*' saltutil.sync_all
salt '*' state.apply call_custom_state
image.png

作者:Jachin111
链接:https://www.jianshu.com/p/ecf22d0184bf
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Rancher 2.x集群销毁及卸载清理

使用docker方式安装的Rancher 2.x集群在执行完后,会在宿主机上生成一些文件。删除镜像时,在执行docker rm rancher时,不会删除生成的文件,会导致重装rancher集群不成功

1. web控制台删除集群
可不操作,直接命令行删除容器也行

2. 命令行清理rancher相关容器
# 停止
docker stop $(docker ps -q)
# 删除
docker rm $(docker ps -aq)

# 删除容器存储
docker volume rm $(docker volume ls -q)
3. 卸载K8s components和secrets
for mount in $(mount | grep tmpfs | grep '/var/lib/kubelet' | awk '{ print $3 }') /var/lib/kubelet /var/lib/rancher; do umount $mount; done

4. 清理残留文件和文件夹

rm -rf /etc/ceph \
       /etc/cni \
       /etc/kubernetes \
       /opt/cni \
       /opt/rke \
       /run/secrets/kubernetes.io \
       /run/calico \
       /run/flannel \
       /var/lib/calico \
       /var/lib/etcd \
       /var/lib/cni \
       /var/lib/kubelet \
       /var/lib/rancher/rke/log \
       /var/log/containers \
       /var/log/pods \
       /var/run/calico
5. 重启机器
reboot

k8s中的command和docker的entrypoint区别

Docker Entrypoint & Cmd
先回顾下CMD指令的含义,CMD指令可以指定容器启动时要执行的命令,但它可以被docker run命令的参数覆盖掉。

ENTRYPOINT 指令和CMD类似,它也可用户指定容器启动时要执行的命令,但如果dockerfile中也有CMD指令,CMD中的参数会被附加到ENTRYPOINT 指令的后面。 如果这时docker run命令带了参数,这个参数会覆盖掉CMD指令的参数,并也会附加到ENTRYPOINT 指令的后面。这样当容器启动后,会执行ENTRYPOINT 指令的参数部分。

可以看出,相对来说ENTRYPOINT指令优先级更高。我们来看个例子,下面是Dockerfile的内容:

### test
FROM ubuntu
MAINTAINER hello
RUN echo hello1 > test1.txt
RUN echo hello2 > /test2.txt
EXPOSE 80
ENTRYPOINT [“echo”]
CMD [“defaultvalue”]

假设通过该Dockerfile构建的镜像名为 myimage。

当运行 docker run myimage 输出的内容是 defaultvalue,可以看出CMD指令的参数得确是被添加到ENTRYPOINT指令的后面,然后被执行。
当运行docker run myimage hello world 输出的内容是 hello world ,可以看出docker run命令的参数得确是被添加到ENTRYPOINT指令的后面,然后被执行,这时CMD指令被覆盖了。
另外我们可以在docker run命令中通过 –entrypoint 覆盖dockerfile文件中的ENTRYPOINT设置,如:
docker run –entrypoint=”echo” myimage good 结果输出good
注意,不管是哪种方式,创建容器后,通过 docker ps –no-trunc查看容器信息时,COMMAND列会显示最终生效的启动命令。

此外,很多的数据库软件的docker镜像,一般在entrypoint的位置会设置一个docker-entrypoint.sh文件,此文件位于/usr/local/bin位置,用于在容器初次启动的时候进行数据库的初始化操作。
Kubernetes Command & args
下表总结了Docker和Kubernetes使用的字段名称:

当你覆盖默认的Entrypoint和Cmd时,将应用以下规则:

如果不为容器提供command或args参数,则使用Docker镜像中定义的默认值。
如果提供command但没有提供args参数,则仅使用提供的command。Docker镜像中定义的默认EntryPoint和默认Cmd将被忽略。
如果仅为容器提供args,则Docker镜像中定义的默认Entrypoint将与您提供的args一起运行。
如果提供command和args,则将忽略Docker镜像中定义的默认Entrypoint和默认Cmd。 您的command与 args一起运行。
可以看到,k8s利用了Dockerfile的覆盖机制,使用command和args参数有选择性的覆盖了Docker镜像中的Entrypoint和Cmd启动参数,下面是一些例子:

 

使用command和args的例子:

apiVersion: v1
kind: Pod
metadata:
name: command-demo
labels:
purpose: demonstrate-command
spec:
containers:
– name: command-demo-container
image: debian
command: [“printenv”]
args: [“HOSTNAME”, “KUBERNETES_PORT”]
restartPolicy: OnFailure
参考资料:
https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/

Docker 时区问题

一、问题描述
遇到docker时间不一致,大多是因为默认时区没有设置导致,一般在宿主机上使用 date 命令看到的是 CTS 时间,进入docker后使用 date 命令查看的是 UTC 时间。
CTS: China Standard Time,UTC+8:00 中国沿海时间(北京时间)
UTC: Universal Time Coordinated 世界协调时间

所以我们需要修改 docker 容器的时区,针对不同场景修改的方式也不一样,大家可以参考一下选择最适合自己的修改方式

二、修改 Dockerfile 文件方式更改时区
适用场景:如果我们是用 Dockerfile 方式构建的镜像,那么我们可以修改 Dockerfile 让构建的镜像直接为东八区

在 Dockerfile 文件中 添加

ENV TZ=Asia/Shanghai

重新构建镜像,查看时间,发现已经是东八区,成功。

三、新增 run 参数更改时区
适用场景:不想重新构建镜像,删除容器重新部署对程序本身没有影响

删除 容器

docker rm -f tomcat
run 的时候加上参数

-e TZ=”Asia/Shanghai”
例如

docker run -e TZ=”Asia/Shanghai” -p 8090:8090 -d –name ch ch/ch
-e 时区改为东八区,默认时区为0区

-p 暴露端口 前面的端口(docker tomcat 容器端口) 后面的端口(宿主机器端口,我们要在浏览器访问的端口)

-d 后台运行(不会在控制台输出日志)

完成后 查看时间,已经是东八区,成功

四、修改 tomcat 配置文件更改时区
适用场景:tomcat 容器运行的程序,不想重新构建镜像容器

注意:交付的Docker映像已修剪到最低限度-因此,拉取的容器未安装任何编辑器,所以无法 进入到容器编辑配置文件。

所以得将配置文件 copy 一份到本地

docker cp tomcat:/usr/local/tomcat/bin/catalina.sh .

修改 catalina.sh 配置文件

找到 JAVA_OPTS=”$JAVA_OPTS -Djava.protocol.handler.pkgs=org.apache.catalina.webresources” 这一行 行尾添加 Duser.timezone=GMT+08

JAVA_OPTS=”$JAVA_OPTS -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Duser.timezone=GMT+08″
然后将修改后的配置文件,覆盖掉容器内的

docker cp .\catalina.sh tomcat:/usr/local/tomcat/bin/catalina.sh
最后重启容器

docker restart tomcat
发现时区已经改为 东八区 ,成功。
————————————————
版权声明:本文为CSDN博主「码农农码一生」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/chenhao_c_h/article/details/110041401

jenkins BlueOcean修复Pipeline不支持中文的问题

问题

在Jenkins的BlueOcean中,修改Pipeline,结果发现,如果编写过程中,凡是能导致jenkinsFile有中文信息的,点击 save and run之后 Console 中出现错误,无法保存。

分析

根据错误信息,定位到问题来源jenkins-js-extension.js,该问题和 js的btoa和atob 不支持unicode有关。

解决

  1. 将jenkins的./webapps/plugins/blueocean-pipeline-editor/WEB-INF/lib/blueocean-pipeline-editor.jar下载到本地,
  2. 解压出jenkins-js-extension.js,搜索btoa,有两处一样的代码,搜索atob也是一样的,成对出现。
  3. 修改代码,将两处encode和decode修改为以下结果:
  4. 将修改之后的jenkins-js-extension.js拖入blueocean-pipeline-editor.jar。
  5. 将修改之后的blueocean-pipeline-editor.jar上传到Jenkins的原处:./webapps/plugins/blueocean-pipeline-editor/WEB-INF/lib/blueocean-pipeline-editor.jar
var Base64 = {
    encode: function encode(data) {
        return btoa(unescape(encodeURIComponent(data)));
    },
    decode: function decode(str) {
        return decodeURIComponent(escape(atob(str)));
    }
};

An error occurred during installation: No such plugin: cloudbees-folder

在启动jenkins时候报错

An error occurred during installation: No such plugin: cloudbees-folder
1
字面意思是没有找到cloudbees-folder这个插件。有一些文章说下载这个插件到本地就好了。

然而jenkins启动的时候不仅仅有这一个插件。

https://github.com/jenkinsci/docker/issues/424 github issues里有一些讨论。似乎重启jenkins就可以了

# 访问这个地址就是重启
http://yourhost:8080/restart
1
2
然后我这里看来并不管用。

最后无意中发现我拉取的镜像是jenkins…并非jenkins/jenkins。我用的是docker…

当尝试拉取后者并启动之后,就没有这个报错了。

k8s创建Deployment报错:no matches for kind “Deployment“ in version “extensions/v1beta1“

报错类型:

[root@master ~]# kubectl create -f lzb-test.yaml
error: unable to recognize “lzb-test.yaml”: no matches for kind “Deployment” in version “extensions/v1beta1”
解决:

修改yaml文件:


apiVersion: extensions/v1beta1
kind: Deployment
metadata:
修改如下:


apiVersion: apps/v1
kind: Deployment
这个主要是由于版本升级的原因

我的 k8s 版本是 1.18.5

在这个版本中 Deployment 已经启用extensions/v1beta1

DaemonSet, Deployment, StatefulSet, and ReplicaSet resources will no longer be served from extensions/v1beta1, apps/v1beta1, or apps/v1beta2 by default in v1.16.