5.10. 使用 scorecard 工具验证 Operator
作为 Operator 作者,您可以使用 Operator SDK 中的 scorecard 工具来执行以下任务:
- 验证您的 Operator 项目没有语法错误,并正确打包
- 查看有关如何改进 Operator 的建议
5.10.1. 关于 scorecard 工具
虽然 Operator SDK bundle validate
子命令可为内容和结构验证本地捆绑包目录和远程捆绑包镜像,但您可以使用 scorecard
命令基于配置文件和测试镜像对 Operator 运行测试。这些测试在由 scorecard 配置并组成执行的测试镜像中实施。
Scorecard 假设它是在可以访问已配置的 Kubernetes 集群(如 OpenShift Container Platform)的情况下运行的。Scorecard 在 pod 中运行每个测试,从中聚合 pod 日志并将测试结果发送到控制台。Scorecard 内置了基本测试和 Operator Lifecycle Manager(OLM)测试,同时还提供了执行自定义测试定义的方法。
Scorecard 工作流
- 创建任何相关的自定义资源(CR)和 Operator 所需的所有资源
- 在 Operator 部署中创建代理容器,记录对 API 服务器的调用并运行测试
- 检查 CR 中的参数
Scorecard 测试不会假定要测试的 Operator 状态。为 Operator 创建 Operator 和 CR 超出了 scorecard 本身的范围。但是,如果测试是为创建资源而设计的,则 scorecard 测试可以创建其所需的任何资源。
scorecard
命令语法
$ operator-sdk scorecard <bundle_dir_or_image> [flags]
Scorecard 需要一个位置参数,它是指向 Operator 捆绑包的磁盘路径或捆绑包镜像的名称。
如需有关标记的更多信息,请运行:
$ operator-sdk scorecard -h
5.10.2. Scorecard 配置
Scorecard 工具使用一个配置来供您配置内部插件以及几个全局配置选项。测试是由名为 config.yaml
的配置文件驱动的,该文件由 make bundle
命令生成,位于 bundle/
目录中:
./bundle ... └── tests └── scorecard └── config.yaml
Scorecard 配置文件示例
kind: Configuration apiversion: scorecard.operatorframework.io/v1alpha3 metadata: name: config stages: - parallel: true tests: - image: quay.io/operator-framework/scorecard-test:v1.22.2 entrypoint: - scorecard-test - basic-check-spec labels: suite: basic test: basic-check-spec-test - image: quay.io/operator-framework/scorecard-test:v1.22.2 entrypoint: - scorecard-test - olm-bundle-validation labels: suite: olm test: olm-bundle-validation-test
配置文件定义 scorecard 可执行的每个测试。Scorecard 配置文件的以下字段定义测试,如下所示:
配置字段 | 描述 |
---|---|
| 测试实现测试的容器镜像名称 |
| 测试镜像中调用的命令和参数来执行测试 |
| 选择要运行的测试的 scorecard 定义或自定义标签 |
5.10.3. 内置 scorecard 测试
Scorecard 附带预定义的测试,这些测试被放在套件中:基本测试套件和 Operator Lifecycle Manager(OLM)套件。
测试 | 描述 | 短名称 |
---|---|---|
Spec Block Exists |
此测试会检查集群中创建的自定义资源(CR)以确保所有 CR 都有一个 |
|
测试 | 描述 | 短名称 |
---|---|---|
捆绑包验证 | 此测试会验证传递给 scorecard 的捆绑包中的捆绑包清单。如果捆绑包内容包含错误,那么测试结果输出中将包括验证器日志以及验证库中的错误消息。 |
|
Provided APIs Have Validation |
此测试会验证提供的 CR 的自定义资源定义(CRD)是否包含一个验证部分,并且 CR 中检测到的每个 |
|
Owned CRDs Have Resources Listed |
此测试确保通过 |
|
Spec Fields With Descriptors |
此测试会验证 CRs |
|
Status Fields With Descriptors |
此测试会验证 CRs |
|
5.10.4. 运行 scorecard 工具
Operator SDK 在运行 init
命令后生成一组默认 Kustomize 文件。生成的默认 bundle/tests/scorecard/config.yaml
文件可立即用于针对 Operator 运行 scorecard 工具,或者您可以根据测试规格修改该文件。
先决条件
- 使用 Operator SDK 生成的 operator 项目
流程
为 Operator 生成或重新生成捆绑包清单和元数据:
$ make bundle
此命令自动将 scorecard 注解添加到捆绑包元数据中,由
scorecard
命令用来运行测试。针对 Operator 捆绑包的磁盘路径或捆绑包镜像的名称运行 scorecard:
$ operator-sdk scorecard <bundle_dir_or_image>
5.10.5. Scorecard 输出
scorecard
命令的 --output
标志指定 scorecard 结果输出格式: text
或 json
。
例 5.15. JSON 输出片断示例
{ "apiVersion": "scorecard.operatorframework.io/v1alpha3", "kind": "TestList", "items": [ { "kind": "Test", "apiVersion": "scorecard.operatorframework.io/v1alpha3", "spec": { "image": "quay.io/operator-framework/scorecard-test:v1.22.2", "entrypoint": [ "scorecard-test", "olm-bundle-validation" ], "labels": { "suite": "olm", "test": "olm-bundle-validation-test" } }, "status": { "results": [ { "name": "olm-bundle-validation", "log": "time=\"2020-06-10T19:02:49Z\" level=debug msg=\"Found manifests directory\" name=bundle-test\ntime=\"2020-06-10T19:02:49Z\" level=debug msg=\"Found metadata directory\" name=bundle-test\ntime=\"2020-06-10T19:02:49Z\" level=debug msg=\"Getting mediaType info from manifests directory\" name=bundle-test\ntime=\"2020-06-10T19:02:49Z\" level=info msg=\"Found annotations file\" name=bundle-test\ntime=\"2020-06-10T19:02:49Z\" level=info msg=\"Could not find optional dependencies file\" name=bundle-test\n", "state": "pass" } ] } } ] }
例 5.16. 文本输出片段示例
-------------------------------------------------------------------------------- Image: quay.io/operator-framework/scorecard-test:v1.22.2 Entrypoint: [scorecard-test olm-bundle-validation] Labels: "suite":"olm" "test":"olm-bundle-validation-test" Results: Name: olm-bundle-validation State: pass Log: time="2020-07-15T03:19:02Z" level=debug msg="Found manifests directory" name=bundle-test time="2020-07-15T03:19:02Z" level=debug msg="Found metadata directory" name=bundle-test time="2020-07-15T03:19:02Z" level=debug msg="Getting mediaType info from manifests directory" name=bundle-test time="2020-07-15T03:19:02Z" level=info msg="Found annotations file" name=bundle-test time="2020-07-15T03:19:02Z" level=info msg="Could not find optional dependencies file" name=bundle-test
输出格式 spec 与 Test
类型布局匹配。
5.10.6. 选择测试
Scorecard 测试通过将 --selector
CLI 标志设置为一组标签字符串来选择。如果没有提供选择器标志,则运行 scorecard 配置文件中的所有测试。
测试通过 scorecard 聚合并写入标准输出或 stdout 以序列方式运行。
流程
要选择单个测试(如
basic-check-spec-test
),使用--selector
标志来指定测试:$ operator-sdk scorecard <bundle_dir_or_image> \ -o text \ --selector=test=basic-check-spec-test
要选择一组测试(如
olm
),请指定所有 OLM 测试使用的标签:$ operator-sdk scorecard <bundle_dir_or_image> \ -o text \ --selector=suite=olm
要选择多个测试,按照以下语法使用
selector
标记指定测试名称:$ operator-sdk scorecard <bundle_dir_or_image> \ -o text \ --selector='test in (basic-check-spec-test,olm-bundle-validation-test)'
5.10.7. 启用并行测试
作为 Operator 作者,您可以使用 scorecard 配置文件为测试定义独立阶段。阶段会根据配置文件中定义的顺序按照顺序运行。一个阶段(stage)包含测试列表以及一个可配置的 parallel
设置。
默认情况,或当阶段把 parallel
明确设置为 false
时,阶段中的测试会按配置文件中定义的顺序运行。每次只运行一个测试有助于保证两个测试间不会相互交互和冲突。
但是,如果测试被设计为完全隔离,则可以实现并行化。
流程
要并行运行一组隔离测试,在同一个阶段中包括它们,并把
parallel
设置为true
:apiVersion: scorecard.operatorframework.io/v1alpha3 kind: Configuration metadata: name: config stages: - parallel: true 1 tests: - entrypoint: - scorecard-test - basic-check-spec image: quay.io/operator-framework/scorecard-test:v1.22.2 labels: suite: basic test: basic-check-spec-test - entrypoint: - scorecard-test - olm-bundle-validation image: quay.io/operator-framework/scorecard-test:v1.22.2 labels: suite: olm test: olm-bundle-validation-test
- 1
- 启用并行测试
所有并行阶段中的测试都会同时执行,scorecard 会在进入下一阶段前等待所有测试完成。这使得测试可以更快地运行。
5.10.8. 自定义 scorecard 测试
scorecard 工具可按照以下强制约定运行自定义测试:
- 测试在容器镜像内实施
- 测试可以接受包含命令和参数的入口点
-
测试以 JSON 格式生成
v1alpha3
scorecard 输出,在测试输出中没有无关的日志信息 -
测试可在
/bundle
的共享挂载点获取捆绑包内容 - 测试可以使用集群内客户端连接访问 Kubernetes API
如果测试镜像遵循上述指南,则可以使用其他编程语言编写自定义测试。
以下示例显示了在 Go 中写入的自定义测试镜像:
例 5.17. 自定义 scorecard 测试示例
// Copyright 2020 The Operator-SDK Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "encoding/json" "fmt" "log" "os" scapiv1alpha3 "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3" apimanifests "github.com/operator-framework/api/pkg/manifests" ) // This is the custom scorecard test example binary // As with the Redhat scorecard test image, the bundle that is under // test is expected to be mounted so that tests can inspect the // bundle contents as part of their test implementations. // The actual test is to be run is named and that name is passed // as an argument to this binary. This argument mechanism allows // this binary to run various tests all from within a single // test image. const PodBundleRoot = "/bundle" func main() { entrypoint := os.Args[1:] if len(entrypoint) == 0 { log.Fatal("Test name argument is required") } // Read the pod's untar'd bundle from a well-known path. cfg, err := apimanifests.GetBundleFromDir(PodBundleRoot) if err != nil { log.Fatal(err.Error()) } var result scapiv1alpha3.TestStatus // Names of the custom tests which would be passed in the // `operator-sdk` command. switch entrypoint[0] { case CustomTest1Name: result = CustomTest1(cfg) case CustomTest2Name: result = CustomTest2(cfg) default: result = printValidTests() } // Convert scapiv1alpha3.TestResult to json. prettyJSON, err := json.MarshalIndent(result, "", " ") if err != nil { log.Fatal("Failed to generate json", err) } fmt.Printf("%s\n", string(prettyJSON)) } // printValidTests will print out full list of test names to give a hint to the end user on what the valid tests are. func printValidTests() scapiv1alpha3.TestStatus { result := scapiv1alpha3.TestResult{} result.State = scapiv1alpha3.FailState result.Errors = make([]string, 0) result.Suggestions = make([]string, 0) str := fmt.Sprintf("Valid tests for this image include: %s %s", CustomTest1Name, CustomTest2Name) result.Errors = append(result.Errors, str) return scapiv1alpha3.TestStatus{ Results: []scapiv1alpha3.TestResult{result}, } } const ( CustomTest1Name = "customtest1" CustomTest2Name = "customtest2" ) // Define any operator specific custom tests here. // CustomTest1 and CustomTest2 are example test functions. Relevant operator specific // test logic is to be implemented in similarly. func CustomTest1(bundle *apimanifests.Bundle) scapiv1alpha3.TestStatus { r := scapiv1alpha3.TestResult{} r.Name = CustomTest1Name r.State = scapiv1alpha3.PassState r.Errors = make([]string, 0) r.Suggestions = make([]string, 0) almExamples := bundle.CSV.GetAnnotations()["alm-examples"] if almExamples == "" { fmt.Println("no alm-examples in the bundle CSV") } return wrapResult(r) } func CustomTest2(bundle *apimanifests.Bundle) scapiv1alpha3.TestStatus { r := scapiv1alpha3.TestResult{} r.Name = CustomTest2Name r.State = scapiv1alpha3.PassState r.Errors = make([]string, 0) r.Suggestions = make([]string, 0) almExamples := bundle.CSV.GetAnnotations()["alm-examples"] if almExamples == "" { fmt.Println("no alm-examples in the bundle CSV") } return wrapResult(r) } func wrapResult(r scapiv1alpha3.TestResult) scapiv1alpha3.TestStatus { return scapiv1alpha3.TestStatus{ Results: []scapiv1alpha3.TestResult{r}, } }