📑

controller-runtimeのenvtestでaudit logを有効化する方法

2023/08/18に公開

はじめに

こんにちは、サイボウズNecoチームの三村です。

Kubebuilderなどを用いてKubernetesのカスタムコントローラを作成する際、envtestを用いてテストを書くことがあります。
envtestは、Kubernetesのコントローラをテストする際にKubernetesのクラスタを起動することなく、KubernetesのAPIを利用することができるパッケージで、controller-runtimeに含まれています。
この記事では、envtestにおいてkube-apiserverのaudit logを有効化する方法を紹介します。
audit logを有効化することで、全APIリクエストのログを出力できるため、テストのデバッグや分析に役立ちます。

この記事では、Kubebuilderを用いてカスタムコントローラを作成していることを前提としたサンプルコードを掲載しています。テストコードは[1]で紹介されているコードをベースにしています。

audit logの有効化

envtestのsuite_test.goがあるディレクトリに、以下のようなpolicy.yamlを作成します。
audit logのPolicyの書き方については、こちらを参照してください。
https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/

以下のサンプルでは、全てのタイプのリクエストに対して、メタデータレベルのログを出力するようにしています。

policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
  - RequestReceived
omitManagedFields: true
rules:
  - level: Metadata
    verbs: ["get", "watch", "list", "patch", "update","create", "delete", "deletecollection"]

次に、suite_test.goのBeforeSuiteの中で[2]、以下の様なコードを追加してkube-apiserverのaudit logを有効にするオプションを設定します。

suite_test.go
var _ = BeforeSuite(func() {
	logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))

	By("bootstrapping test environment")
	testEnv = &envtest.Environment{
		CRDDirectoryPaths:     []string{filepath.Join("..", "..", "config", "crd", "bases")},
		ErrorIfCRDPathMissing: true,
	}

	var err error
+	workingDir, err := os.Getwd()
+	Expect(err).NotTo(HaveOccurred())
+	apiServer := testEnv.ControlPlane.GetAPIServer()
+	apiServer.Out = GinkgoWriter
+	apiServer.Err = GinkgoWriter
+	apiServer.Configure().
+		Append("audit-policy-file", filepath.Join(workingDir, "policy.yaml")).
+		Append("audit-log-path", "-")
        

この設定により、audit logが標準出力に出力されるようになります。
このままではテストのログとaudit logがどちらも標準出力に出力されてしまうため、以下の様にgrepするとaudit logのみを出力できます。

make test | grep "audit.k8s.io/v1"

audit logのユーザーを区別する

ここまでで、envtestを実行する際のkube-apiserverのaudit logの有効化ができました。
しかしこのままでは、envtestでテストを実行する際に、コントローラが発行するリクエストと、envtestで記述するテストで発行するリクエストのログが区別できません。
ログを区別可能にするために、Impersonateという仕組みを利用します。
ImpersonateはKubernetesの認証の機能で、リクエストを発行したユーザーを偽装して、別のユーザーになりすますことができます。
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation
このImpersonateの設定をしUserNameをコントローラとenvtestで分けることで、ログを区別することができます。

suite_test.goのBeforeSuiteの中に、さらに以下のコードを追加します。

suite_test.go
var _ = BeforeSuite(func() {
	logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))

	By("bootstrapping test environment")
	testEnv = &envtest.Environment{
		CRDDirectoryPaths:     []string{filepath.Join("..", "..", "config", "crd", "bases")},
		ErrorIfCRDPathMissing: true,
	}

	var err error
	workingDir, err := os.Getwd()
	Expect(err).NotTo(HaveOccurred())
	apiServer := testEnv.ControlPlane.GetAPIServer()
	apiServer.Out = GinkgoWriter
	apiServer.Err = GinkgoWriter
	apiServer.Configure().
		Append("audit-policy-file", filepath.Join(workingDir, "policy.yaml")).
		Append("audit-log-path", "-")

+	cfg.Impersonate = rest.ImpersonationConfig{
+		UserName: "test",
+		Groups:   []string{"system:masters", "system:authenticated"},
+	}
	// cfg is defined in this file globally.
	cfg, err = testEnv.Start()
	Expect(err).NotTo(HaveOccurred())
	Expect(cfg).NotTo(BeNil())

コントローラのテストコードでも同様に、managerに渡すcfgにImpersonateの設定を追加します。

markdownview_controller_test.go
+	    cfg.Impersonate = rest.ImpersonationConfig{
+		    UserName: "controller",
+		    Groups:   []string{"system:masters", "system:authenticated"},
+	    }
        mgr, err := ctrl.NewManager(cfg, ctrl.Options{
            Scheme: scheme,
        })
        Expect(err).ToNot(HaveOccurred())

この状態でテストを実行すると以下の様なaudit logが出力され、先程設定したimpersonatedUserのフィールドが追加されます。

{
  "kind": "Event",
  "apiVersion": "audit.k8s.io/v1",
  "level": "RequestResponse",
  "auditID": "c836b9fc-55b1-4a92-8e19-d7669cf033f6",
  "stage": "ResponseComplete",
  "requestURI": "/api/v1/namespaces/test/services/viewer-sample?fieldManager=markdown-view-controller&force=true",
  "verb": "patch",
  "user": {
    "username": "admin",
    "groups": [
      "system:masters",
      "system:authenticated"
    ]
  },
  "impersonatedUser": {
    "username": "test",
    "groups": [
      "system:masters",
      "system:authenticated"
    ]
  },
  "sourceIPs": [
    "127.0.0.1"
  ],
  ...

このようにImpersonateを設定することで、envtestでテストを実行する際に、コントローラが発行するリクエストはImpersonate.UserName:controller、envtestで記述するテストで発行するリクエストはImpersonate.UserName:testがログに記録されるようになりました。これにより、envtestでテストを実行する際にログを区別できるようになります。

まとめ

envtestでテストを書く際に、kube-apiserverのaudit logを有効化する方法を紹介しました。
また、コントローラとテストで発行するリクエストのログを区別する方法も紹介しました。
audit logを分析することにより、envtestに問題があった際の原因特定がしやすくなります。

脚注
  1. コントローラーのテスト - つくって学ぶKubebuilder https://zoetrope.github.io/kubebuilder-training/controller-runtime/controller_test.html ↩︎

  2. テストスイートにGinkgoを使用している想定で記載しています。controller-genが生成するenvtestではGinkgoが使用されています。 ↩︎

サイボウズ Necoチーム 😺

Discussion