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.yamlScorecard 配置文件示例
kind: Configuration
apiversion: scorecard.operatorframework.io/v1alpha3
metadata:
name: config
stages:
- parallel: true
tests:
- image: quay.io/operator-framework/scorecard-test:v1.28.0
entrypoint:
- scorecard-test
- basic-check-spec
labels:
suite: basic
test: basic-check-spec-test
- image: quay.io/operator-framework/scorecard-test:v1.28.0
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)套件。
表 5.16. 基本测试套件
| 测试 | 描述 | 短名称 |
|---|---|---|
| Spec Block Exists |
此测试会检查集群中创建的自定义资源(CR)以确保所有 CR 都有一个 |
|
表 5.17. OLM 测试套件
| 测试 | 描述 | 短名称 |
|---|---|---|
| 捆绑包验证 | 此测试会验证传递给 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.28.0",
"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.28.0 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.28.0 labels: suite: basic test: basic-check-spec-test - entrypoint: - scorecard-test - olm-bundle-validation image: quay.io/operator-framework/scorecard-test:v1.28.0 labels: suite: olm test: olm-bundle-validation-test- 1
- 启用并行测试
所有并行阶段中的测试都会同时执行,scorecard 会在进入下一阶段前等待所有测试完成。这使得测试可以更快地运行。
5.10.8. 自定义 scorecard 测试
scorecard 工具可按照以下强制约定运行自定义测试:
- 测试在容器镜像内实施
- 测试可以接受包含命令和参数的入口点
-
测试以 JSON 格式生成
v1alpha3scorecard 输出,在测试输出中没有无关的日志信息 -
测试可在
/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},
}
}