大家好,我是张晋涛。
本篇我来为你介绍一个我个人很喜欢的,通用策略引擎,名叫 OPA,全称是 Open Policy Agent。
在具体聊 OPA 之前,我们先来聊一下为什么需要一个通用策略引擎,以及 OPA 解决了什么问题。
OPA 解决了什么问题 在实际的生产环境中很多场景中都需要策略控制,比如:
需要策略控制哪些资源可部署到 Kubernetes 中;
img 但是对于这些场景或者软件来说,配置它们的策略是需要与该软件进行耦合的,彼此是不统一,不通用的。管理起来也会比较混乱,带来了不小的维护成本。
OPA 的出现可以将各处配置的策略进行统一,极大的降低了维护成本 。以及将策略与对应的软件/服务进行解耦,方便进行移植/复用。
img OPA 的发展过程 OPA 最初是由 Styra 公司在 2016 年创建并开源的项目,目前该公司的主要产品就是提供可视化策略控制及策略执行的可视化 Dashboard 服务的。
OPA 首次进入 CNCF 并成为 sandbox 级别的项目是在 2018 年, 在 2021 年的 2 月份便已经从 CNCF 毕业,这个过程相对来说还是比较快的,由此也可以看出 OPA 是一个比较活跃且应用广泛的项目。
OPA 是什么 前面我们已经介绍过 Open Policy Agent (OPA) 是一种开源的通用策略引擎,可在整个堆栈中实现统一、上下文感知的策略控制。
OPA 可将策略决策与应用程序的业务逻辑分离(解耦),透过现象看本质,策略就是一组规则,请求发送到引擎,引擎根据规则来进行决策。
img 图 3 ,OPA 的策略解耦示例
OPA 并不负责具体任务的执行,它仅负责决策 ,需要决策的请求通过 JSON 的方式传递给 OPA ,在 OPA 决策后,也会将结果以 JSON 的形式返回。
Rego OPA 中的策略是以 Rego 这种 DSL(Domain Specific Language) 来表示的。
Rego 受 Datalog(https://en./wiki/Datalog) 的启发,并且扩展了 Datalog 对于结构化文档模型的支持,方便以 JSON 的方式对数据进行处理。
Rego 允许策略制定者可以专注于返回内容的查询而不是如何执行查询。同时 OPA 中也内置了执行规则时的优化,用户可以默认使用。
Rego 允许我们使用规则(if-then)封装和重用逻辑,规则可以是完整的或者是部分的。
每个规则都是由头部和主体组成。在 Rego 中,如果规则主体对于某些变量赋值为真,那么我们说规则头为真。可以通过绝对路径引用任何加载到 OPA 中的规则来查询它的值。规则的路径总是:data..(规则生成的所有值都可以通过全局 data 变量进行查询。例如,下方示例中的 data.example.rules.any_public_networks
完整规则是将单个值分配给变量的 if-then 语句。 img 图 4 ,Rego 完整规则示例
部分规则是生成一组值并将该组分配给变量的 if-then 语句。 img 图 5 ,Rego 部分规则示例
逻辑或是要在 Rego 中定义多个具有相同名称的规则。(查询中将多个表达式连接在一起时,表示的是逻辑 AND) img 图 6 ,Rego 规则或的完全规则和部分规则示例图
OPA 的使用 OPA 的使用还是比较简单的,我们来看一下。
安装 OPA 二进制方式 我们可以直接从 OPA 的 release 页面下载其二进制进行使用
➜ ~ wget -q -O ~/bin/opa https://github.com/open-policy-agent/opa/releases/download/v0.35.0/opa_linux_amd64_static ➜ ~ chmod +x ~/bin/opa ➜ ~ opa version Version: 0.35.0 Build Commit: a54537a Build Timestamp: 2021-12-01T02:11:47Z Build Hostname: 9e4cf671a460 Go Version: go1.17.3 WebAssembly: unavailable
容器 我们可以使用其官方镜像
➜ ~ docker run --rm openpolicyagent/opa:0.35.0 version Version: 0.35.0 Build Commit: a54537a Build Timestamp: 2021-12-01T02:10:31Z Build Hostname: 4ee9b086e5de Go Version: go1.17.3 WebAssembly: available
OPA 交互 opa eval 最简单的命令是 opa eval
,当然我们除了能使用它进行策略的执行外,还可以用来做表达式计算。
img 图 7 , opa eval 的使用帮助
➜ ~ opa eval '6+6' { 'result': [ { 'expressions': [ { 'value': 12, 'text': '6+6', 'location': { 'row': 1, 'col': 1 } } ] } ] }
opa run opa run
会启动一个交互式 shell ( REPL) 。我们可以使用 REPL 来试验策略并构建新的原型。
➜ ~ opa run OPA 0.35.0 (commit a54537a, built at 2021-12-01T02:11:47Z) Run 'help' to see a list of commands and check for updates. > true true > ['Hello' , 'OPA' ] [ 'Hello' , 'OPA' ] > pi := 3.14 Rule 'pi' defined in package repl. Type 'show' to see rules. > show package repl pi := 3.14 > pi > 1true
我们也可以将策略直接加载进去,或者 将 OPA 作为一个服务运行并通过 HTTP 执行查询。默认情况下,OPA 会监听在 8181 端口。
➜ ~ opa run --server {'addrs':[':8181'],'diagnostic-addrs':[],'level':'info','msg':'Initializing server.','time':'2021-12-07T01:12:47+08:00'}
打开浏览器也可以看到一个简单的查询窗口
img opa 作为 go 的库使用 OPA 可以作为库嵌入到 Go 程序中。将 OPA 嵌入为库的最简单方法是导入 github.com/open-policy-agent/opa/rego
包。通过 rego.New
函数用来创建一个可以准备或评估的对象, PrepareForEval()
以获取可执行查询。
以下是一个简单的示例:
➜ opa tree . ├── data ├── go.mod ├── go.sum ├── input.json ├── k8s-label.rego └── main.go 1 directory, 5 files
这里的策略文件是来校验 INPUT 中是否包含名为 domain 的 label , 以及该 label 是否以 moelove-
开头。
package kubernetes.validating.existence deny[msg] { not input.request.object.metadata.labels.domain msg := 'Every resource must have a domain label' } deny[msg] { value := input.request.object.metadata.labels.domain not startswith(value, 'moelove-' ) msg := sprintf('domain label must start with `moelove-`; found `%v`' , [value]) }
INPUT 文件, 以 Kubernetes 的 AdmissionReview 为例 { 'kind' : 'AdmissionReview' , 'request' : { 'kind' : { 'kind' : 'Pod' , 'version' : 'v1' }, 'object' : { 'metadata' : { 'name' : 'myapp' , 'labels' : { 'domain' : 'opa' } }, 'spec' : { 'containers' : [ { 'image' : 'alpine' , 'name' : 'alpine' } ] } } } }
package mainimport ( 'context' 'encoding/json' 'fmt' 'log' 'os' 'github.com/open-policy-agent/opa/rego' )func main () { ctx := context.Background() // Construct a Rego object that can be prepared or evaluated. r := rego.New( rego.Query(os.Args[2 ]), rego.Load([]string {os.Args[1 ]}, nil )) // Create a prepared query that can be evaluated. query, err := r.PrepareForEval(ctx) if err != nil { log.Fatal(err) } // Load the input document from stdin. var input interface {} dec := json.NewDecoder(os.Stdin) dec.UseNumber() if err := dec.Decode(&input); err != nil { log.Fatal(err) } rs, err := query.Eval(ctx, rego.EvalInput(input)) if err != nil { log.Fatal(err) } fmt.Println(rs) }
执行结果如下:
➜ opa go run main.go k8s-label.rego 'data' < input.json [{[map[kubernetes:map[validating:map[existence:map[deny:[domain label must start with `moelove-`; found `opa`]]]]]] map[]}]
总结 以上便是对于 OPA 的一个大致介绍,OPA 的应用场景有很多,后续文章中将为大家分享 OPA 在 Kubernetes 中的应用,和将 OPA 应用与 CI/CD pipeline 的场景。敬请期待。