Closed14

Sieveを使ってKubernetes Custom Controllerをテストしようとしてみた

junya0530junya0530

まず、python3 start_porting.py ssa-nginx-controller を実行すると以下のディレクトリがSieve 内に作成されます。これをイジっていきます。

examples/
  |- ssa-nginx-controller/
    |- config.json
    |- build/
      |- build.sh
    |- deploy/
      |- deploy.sh
    |- oracle/
    |- test/

手順は以下の流れとなります。

  1. config.jsonを埋める
  2. ControllerをBuildするためのDockerfileと、必要なステップをbuild.shに記載する
  3. ControllerのDeployに必要なyamlファイルと、Deployのステップを deploy.sh に記載する。
  4. テスト用ワークロードを用意する
junya0530junya0530

config.jsonの中身はこれ。

{
    "name": "ssa-nginx-controller",
    "github_link": "https://github.com/jnytnai0613/ssa-nginx-controller",
    "commit": "e97ac968411a713a51782d186e1372d0e0da86bd",
    "kubernetes_version": "v1.26.1",
    "controller_runtime_version": "v0.14.1",
    "client_go_version": "v0.26.0",
    "dockerfile_path": "Dockerfile",
    "controller_image_name": "junya0530/ssa-nginx-controller:v1.0.0",
    "test_command": "python3 ssa-nginx-controller/test/test.py",
    "custom_resource_definitions": [
        "ssanginx"
    ],
    "annotated_reconcile_functions": {
        "controllers/ssanginx_controller.go": "github.com/jnytnai0613/ssa-nginx-controller/controllers.(*SSANginxReconciler).Reconcile"
    },
    "controller_pod_label": "ssa-nginx-controller",
    "controller_deployment_file_path": "ssa-nginx-controller/deploy/manager.yaml"
}

以下のドキュメントに記載の通りに、各フィールドを埋めていけばいいです。

https://github.com/sieve-project/sieve/blob/main/docs/port.md

ハマりどころは、kubernetes_versionです。
バージョンはなんでもいいと言うわけではなく、現在のSieveはv1.18.9とv1.23.1が許可されています。
このどちらかにしないと後続のpython3 build.py -c ssa-nginx-controller -m testの実行に失敗します。

https://github.com/sieve-project/sieve/blob/main/build.py#L24

今回は最新のk8sを使いたいので、以下のように書き換えました。
K8S_VER_TO_APIMACHINERY_VER = {"v1.18.9": "v0.18.9", "v1.23.1": "v0.23.1", "v1.26.1": "v0.26.0"}
バージョンの制限は解除されています。
https://github.com/sieve-project/sieve/commit/8e3fe64218539a097156c4a068c0f7a83b92a4f2

junya0530junya0530

次に、Build用のディレクトリbuild内のファイルを作成します。
まずDockerfileを用意します。
ポイントとしては、RUN go mod downloadがある場合、コメントアウトしてください。
こけます。

# Build the manager binary
FROM golang:1.19 as builder

WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
#RUN go mod download

# Copy the go source
COPY main.go main.go
COPY api/ api/
COPY controllers/ controllers/
COPY pkg/ pkg/
COPY sieve-dependency/ sieve-dependency/

# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go

FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532

ENTRYPOINT ["/manager"]

その後、以下のようにbuild.shファイルを準備します。
イメージはPublicなリモートリポジトリにpushするようにします。

#!/bin/bash
set -x

export IMG=junya0530/ssa-nginx-controller:v1.0.0
docker build -t ${IMG} . --no-cache
junya0530junya0530

次に、Deploy用のディレクトリdeploy内のファイルを作成します。
ここはcontrollerが動くために必要なリソース用のyamlを置いていきます。
今回は以下のように配置しました。

├── cert-manager.yaml
├── crd.yaml
├── deploy.sh
├── manager.yaml
├── role.yaml
├── sa.yaml
└── webhook.yaml

yamlファイルが配置できたら、deploy.shファイルを記載します。
ここには上記yamlファイルを順番にデプロイするようにkubectlコマンドを記載していけばいいです。

#!/bin/bash
set -x

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml
sleep 30
kubectl apply -f crd.yaml
kubectl apply -f sa.yaml
kubectl apply -f role.yaml
kubectl apply -f cert-manager.yaml
kubectl apply -f webhook.yaml
kubectl apply -f manager.yaml

なお、テストの際にはcontrollerのServiceAccountに紐づくroleに、APIグループ coordination.k8s.ioのleases resourceへの権限を与えてあげる必要があります。

- apiGroups:
  - coordination.k8s.io
  resources:
  - leases
  verbs:
  - create
  - update
  - patch
  - delete
  - get
  - list
  - watch
junya0530junya0530

最後に、Test用ディレクトリのtest内のファイルを作成します。
まず、CR用のyamlを置きます。今回はyamlファイルは1つのみです。

├── ssanginx_v1_ssanginx.yaml
└── test.py

次にtest.pyを作成します。今回はCRの再作成、CRに紐づくDeploymentリソースのスケールをテストしてみます。なお、CRおよびCRに紐づくリソースがdefault以外のnamespaceにデプロイされる場合は、wait_for_関数にnamespaceも忘れずに指定します。

import sys
import os
import time

current = os.path.dirname(os.path.realpath(__file__))
sieve_root = os.path.dirname(os.path.dirname(current))
sys.path.append(sieve_root)

from sieve_test_driver.test_framework import new_built_in_workload
from sieve_common.common import RUNNING, TERMINATED

time.sleep(30)
test_cases = {
    "recreate": new_built_in_workload()
    .cmd("kubectl apply -f ssa-nginx-controller/test/ssanginx_v1_ssanginx.yaml")
    .wait_for_pod_status("nginx-*", RUNNING, namespace="ssa-nginx-controller-system")
    .cmd("kubectl delete -f ssa-nginx-controller/test/ssanginx_v1_ssanginx.yaml")
    .wait_for_pod_status("nginx-*", TERMINATED, namespace="ssa-nginx-controller-system")
    .cmd("kubectl apply -f ssa-nginx-controller/test/ssanginx_v1_ssanginx.yaml")
    .wait_for_pod_status("nginx-*", RUNNING, namespace="ssa-nginx-controller-system"),
    "scaleup-scaledown": new_built_in_workload(70)
    .cmd("kubectl apply -f ssa-nginx-controller/test/ssanginx_v1_ssanginx.yaml")
    .wait_for_pod_number("nginx-", 3, namespace="ssa-nginx-controller-system")
    .cmd(
        'kubectl -n ssa-nginx-controller-system patch SSANginx ssanginx-sample --type merge -p=\'{"spec":{"deploymentSpec":{"replicas":5}}}\''
    )
    .wait_for_pod_number("nginx-", 5, namespace="ssa-nginx-controller-system")
    .cmd(
        'kubectl -n ssa-nginx-controller-system patch SSANginx ssanginx-sample --type merge -p=\'{"spec":{"deploymentSpec":{"replicas":3}}}\''
    )
    .wait_for_pod_number("nginx-", 3, namespace="ssa-nginx-controller-system"),
}

test_cases[sys.argv[1]].run(sys.argv[2])
junya0530junya0530

いよいよテストです。
以下のコマンドでtest可能です。

# Kubernetesパッケージをビルド
python3 build.py -m test -v v1.26.1 -p True -r docker.io/junya0530

# Controllerビルド
python3 build.py -c ssa-nginx-controller -m test -p True -r docker.io/junya0530

# kindクラスタ作成して、ControllerとCRデプロイ、テストを行う
python3 sieve.py -c ssa-nginx-controller -w recreate -m generate-oracle -r docker.io/junya0530

オプションの説明は以下の通り。

  • -m: mode
  • -v: ビルドするk8sクラスタバージョン
  • -p: ビルドしたk8sクラスタやcontrollerをdockerリポジトリにpushするか否か
  • -r: pushまたはpullするdockerリポジトリ
junya0530junya0530

ちょっと分かってきたので、再オープンして作業継続する。

junya0530junya0530

実際にテストを行ったログは以下の通り。単純にCRの再作成をしただけ。

$ python3 sieve.py -c ssa-nginx-controller -w recreate -m generate-oracle -r docker.io/junya0530
:
deployment.apps/ssa-nginx-controller-controller-manager created
Wait for the controller pod to be ready...
[OK] Controller deployed
Running test workload...

kubectl apply -f ssa-nginx-controller/test/ssanginx_v1_ssanginx.yaml
ssanginx.ssanginx.jnytnai0613.github.io/ssanginx-sample created
2023-02-20 07:56:17.157930
wait until pod nginx-* becomes Running...
wait takes 25.367305 seconds
2023-02-20 07:56:42.525436

kubectl delete -f ssa-nginx-controller/test/ssanginx_v1_ssanginx.yaml
ssanginx.ssanginx.jnytnai0613.github.io "ssanginx-sample" deleted
2023-02-20 07:56:42.842835
wait until pod nginx-* becomes Terminated...
wait takes 40.340633 seconds
2023-02-20 07:57:23.183631

kubectl apply -f ssa-nginx-controller/test/ssanginx_v1_ssanginx.yaml
ssanginx.ssanginx.jnytnai0613.github.io/ssanginx-sample created
2023-02-20 07:57:23.600185
wait until pod nginx-* becomes Running...
wait takes 15.133279 seconds
2023-02-20 07:57:38.733637

wait for final grace period 50 seconds

Preparing to copy...
Copying from container - 512B
Copying from container - 1.027kB
Copying from container - 1.536kB
Copying from container - 2.048kB
Successfully copied 2.56kB to /home/taniaijunya/sieve/sieve_learn_results/ssa-nginx-controller/recreate/generate-oracle/learn.yaml/sieve-server.log
Generating controller family list...
Generating state update summary...
Generating end state...
Generating canonicalized state update summary...
Generating canonicalized end state...
Generating canonicalized state mask (for generating test plans)...
Copying controller family to the oracle dir...
Sanity checking the sieve log sieve_learn_results/ssa-nginx-controller/recreate/generate-oracle/learn.yaml/sieve-server.log...
Running base pass...
Running optional pass: hear-read-overlap-filtering...
<e, s> pairs: 0 -> 0
0 operator_hear vertices
0 operator_write vertices
0 edges from operator_hear to operator_write
0 edges from operator_write to operator_hear
Running optional pass: causality-filtering...
0 -> 0 edges
Running optional pass: reversed-effect-filtering...
0 -> 0 edges
Running stale state detectable pass...
.formatd -> .formatd edges
Generated 0 stale-state test plan(s) in sieve_learn_results/ssa-nginx-controller/recreate/generate-oracle/learn.yaml/stale-state
Running optional pass: overwrite-filtering...
0 -> 0 receipts
Running optional pass: causality-filtering...
0 -> 0 receipts
Running unobserved state detectable pass...
0 -> 0 receipts
Generated 0 unobserved-state test plan(s) in sieve_learn_results/ssa-nginx-controller/recreate/generate-oracle/learn.yaml/unobserved-state
Running optional pass:  effective-write-filtering...
0 -> 0 writes
Running optional pass:  no-error-write-filtering...
0 -> 0 writes
Running intermediate state detectable pass...
0 -> 0 writes
Generated 0 intermediate-state test plan(s) in sieve_learn_results/ssa-nginx-controller/recreate/generate-oracle/learn.yaml/intermediate-state
Deleting cluster "kind" ...
Total time: 709.9614770412445 seconds
junya0530junya0530

さて、次は上記のテスト中にapiserverを止めたり、再起動するスクリプト書いてみる。

junya0530junya0530

こんな感じで、テストプランを作成。

workload: recreate
actions:
- actionType: restartController
  controllerLabel: ssa-nginx-controller
  trigger:
    definitions:
    - triggerName: trigger1
      condition:
        conditionType: onObjectDelete
        resourceKey: deployment/ssa-nginx-controller-system/nginx
        occurrence: 1
      observationPoint:
        when: afterControllerWrite
        by: github.com/jnytnai0613/ssa-nginx-controller/controllers.(*SSANginxReconciler).Reconcile
    expression: trigger1

injection is not completedと出て、テストできないように思える。

$ python3 sieve.py -c ssa-nginx-controller -w recreate -m test -p bug_reproduction_test_plans/ssa-nginx-controller-intermediate-state-1.yaml -r docker.io/junya0530
:
deployment.apps/ssa-nginx-controller-controller-manager created
Wait for the controller pod to be ready...
[OK] Controller deployed
Running test workload...

kubectl apply -f ssa-nginx-controller/test/ssanginx_v1_ssanginx.yaml
ssanginx.ssanginx.jnytnai0613.github.io/ssanginx-sample created
2023-02-20 11:02:59.667516
wait until pod nginx-* becomes Running...
wait takes 25.194107 seconds
2023-02-20 11:03:24.861771

kubectl delete -f ssa-nginx-controller/test/ssanginx_v1_ssanginx.yaml
ssanginx.ssanginx.jnytnai0613.github.io "ssanginx-sample" deleted
2023-02-20 11:03:25.026385
wait until pod nginx-* becomes Terminated...
wait takes 40.180968 seconds
2023-02-20 11:04:05.207478

kubectl apply -f ssa-nginx-controller/test/ssanginx_v1_ssanginx.yaml
ssanginx.ssanginx.jnytnai0613.github.io/ssanginx-sample created
2023-02-20 11:04:05.522456
wait until pod nginx-* becomes Running...
wait takes 15.110422 seconds
2023-02-20 11:04:20.633013

wait for final grace period 50 seconds
Preparing to copy...
Copying from container - 512B
Copying from container - 32.77kB
Copying from container - 65.54kB
Copying from container - 98.3kB
Copying from container - 131.1kB
Copying from container - 163.8kB
Copying from container - 196.6kB
Copying from container - 229.4kB
Copying from container - 262kB
Copying from container - 262.1kB
Copying from container - 262.7kB
Successfully copied 263.2kB to /home/taniaijunya/sieve/sieve_test_results/ssa-nginx-controller/recreate/test/ssa-nginx-controller-intermediate-state-1.yaml/sieve-server.log
Generating controller family list...
Generating state update summary...
Generating end state...
Skipping endpoints/default/ssa-nginx-controller-webhook-service for state-update-summary checker
Skipping endpointslice/default/ssa-nginx-controller-webhook-service-* for state-update-summary checker
injection is not completed
[PERTURBATION DESCRIPTION]
Sieve restarts the controller ssa-nginx-controller when the trigger expression trigger1 is satisfied, where
trigger1 is satisfied after the controller ssa-nginx-controller issues:
delete deployment/ssa-nginx-controller-system/nginx with the 1st occurrence.

Deleting cluster "kind" ...
Total time: 315.3483335971832 seconds
このスクラップは2023/02/20にクローズされました