本物のクラスタを利用してkubectl pluginをテストする
kubectlのプラグインを開発していると、ユニットテストだけでなく、本物のKubernetesクラスタを利用したテストが欲しくなります。プラグインの振る舞いが複雑な場合は自動テストがあると安心してリリースできます。
本稿では、本物のKubernetesクラスタを利用してkubectlプラグインをテストする方法を考えます。
テストの基本形
本物のクラスタを利用してプラグインをテストするための構成を下図に示します。
必要なのは以下の3つです。
テストの流れは以下のようになります。
- クラスタを作成する。
- 必要なリソースをデプロイする。
- kubectlを実行する。
- 間接的にkubectl pluginが実行される。
- 実行結果が期待通りか検証する。
このようなテストはプラグインのリリース前に手動でやっていることが多いと思います。自動テストを導入することで、
- リリース前ではなくPull Requestの契機で不具合を検出できる
- クリーンな環境でテストできるので信頼性が高い
- 手動では手間のかかる組合せテストが可能になる(複数のクラスタバージョンなど)
といったメリットがあります。
例: kubectl-treeのテストを書いてみる
ここでは、kubectl-treeというプラグインをテストする例を考えます。これはクラスタにあるリソースを木構造で表示してくれる便利なプラグインです。
簡単のため、テストシナリオはMakefileで書くことにします。Makefileをよく知らない方はシェルスクリプトだと思って読んでみてください。
クラスタの作成とリソースのデプロイ
まずは新しいクラスタを作成するターゲットを定義します。Kindを利用すると、Docker上に簡単にクラスタを作成できます。
CLUSTER_NAME := kubectl-tree-e2e-test OUTPUT_DIR := $(CURDIR)/output KUBECONFIG := $(OUTPUT_DIR)/kubeconfig.yaml export KUBECONFIG .PHONY: cluster cluster: $(OUTPUT_DIR)/kubeconfig.yaml $(OUTPUT_DIR)/kubeconfig.yaml: kind create cluster --name $(CLUSTER_NAME) kubectl cluster-info .PHONY: delete-cluster delete-cluster: kind delete cluster --name $(CLUSTER_NAME) -rm $(KUBECONFIG)
これでmakeを実行すると新しいクラスタが作成される仕組みができました。実行にはDockerとKindが必要です。
% make kind create cluster --name kubectl-tree-e2e-test Creating cluster "kubectl-tree-e2e-test" ... ✓ Ensuring node image (kindest/node:v1.17.0) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-kubectl-tree-e2e-test" You can now use your cluster with: kubectl cluster-info --context kind-kubectl-tree-e2e-test Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/ kubectl cluster-info Kubernetes master is running at https://127.0.0.1:32771 KubeDNS is running at https://127.0.0.1:32771/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. % make delete-cluster kind delete cluster --name kubectl-tree-e2e-test Deleting cluster "kubectl-tree-e2e-test" ... rm /kubectl-tree-e2e-test/output/kubeconfig.yaml
テストを行うにはクラスタだけでなく適当なリソースも必要です。ここではechoserverをデプロイすることにします。以下のようにkubectl applyコマンドを追記します。
.PHONY: cluster cluster: $(OUTPUT_DIR)/kubeconfig.yaml $(OUTPUT_DIR)/kubeconfig.yaml: # create a cluster kind create cluster --name $(CLUSTER_NAME) kubectl cluster-info # deploy the echoserver kubectl apply -f fixture.yaml # wait for the deployment kubectl -n echoserver rollout status deployment/echoserver
これで、テストに必要なクラスタとリソースが揃いました。
プラグインの実行
kubectl treeコマンドを実行すると以下のような結果が表示されます。
% kubectl tree -n echoserver deployment echoserver NAMESPACE NAME READY REASON AGE echoserver Deployment/echoserver - 59s echoserver └─ReplicaSet/echoserver-5d8cc8d48 - 48s echoserver └─Pod/echoserver-5d8cc8d48-bcvs4 True 46s
ここでは簡単のため、grepで必要な文字列が表示されているかチェックします。もっと複雑な条件判定が必要な場合はスクリプトを書く方がよいでしょう。
.PHONY: test test: cluster # run kubectl-tree kubectl tree -n echoserver deployment echoserver | tee $(OUTPUT_DIR)/actual # make sure the output contains the expected lines egrep --color '^echoserver +Deployment/echoserver' $(OUTPUT_DIR)/actual egrep --color '^echoserver +└─ReplicaSet/echoserver-' $(OUTPUT_DIR)/actual egrep --color '^echoserver +└─Pod/echoserver-' $(OUTPUT_DIR)/actual
makeを実行してみましょう。以下のようにテストに合格するはずです。
% make # run kubectl-tree kubectl tree -n echoserver deployment echoserver | tee /kubectl-tree-e2e-test/output/actual NAMESPACE NAME READY REASON AGE echoserver Deployment/echoserver - 5m4s echoserver └─ReplicaSet/echoserver-5d8cc8d48 - 4m56s echoserver └─Pod/echoserver-5d8cc8d48-nk47g True 4m55s # check actual output egrep '^echoserver +Deployment/echoserver' output/actual echoserver Deployment/echoserver - 5m4s egrep '^echoserver +└─ReplicaSet/echoserver-' output/actual echoserver └─ReplicaSet/echoserver-5d8cc8d48 - 4m56s egrep '^echoserver +└─Pod/echoserver-' output/actual echoserver └─Pod/echoserver-5d8cc8d48-nk47g True 4m55s
これでローカルでのテストは完了です。
CIで実行する
品質を継続的に向上させるにはCIが不可欠です。ここではGitHub Actionsでテストを実行します。
GitHub ActionsのUbuntu 18.04にはすでにKindがインストールされていますが、バージョンが古いので改めて最新版をインストールすることにします。CIの最初のステップでは以下のツールをインストールします。
- Kind
- krew
- kubectl-tree
それからmakeを実行します。
最終的に、workflowは以下のようになります。
name: test on: [push] jobs: build: name: test # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/software-installed-on-github-hosted-runners#ubuntu-1804-lts runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v1 # https://kind.sigs.k8s.io/docs/user/quick-start/ - run: | wget -q -O ./kind "https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-linux-amd64" chmod +x ./kind sudo mv ./kind /usr/local/bin/kind kind version # https://github.com/kubernetes-sigs/krew - run: | ( set -x; cd "$(mktemp -d)" && curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/download/v0.3.3/krew.{tar.gz,yaml}" && tar zxvf krew.tar.gz && KREW=./krew-"$(uname | tr '[:upper:]' '[:lower:]')_amd64" && "$KREW" install --manifest=krew.yaml --archive=krew.tar.gz && "$KREW" update ) # https://github.com/ahmetb/kubectl-tree - run: PATH=$PATH:$HOME/.krew/bin kubectl krew install tree - run: PATH=$PATH:$HOME/.krew/bin make
ここまでの内容は下記のリポジトリにまとめてあります。CIの実行時間や実行結果が気になる方はぜひご覧ください。
例:kubeloginの受け入れテスト
拙作のkubeloginでは、本物のKubernetesクラスタとOpenID Connect OPを利用したテストを下図の構成で行っています。
テストを支える裏方の仕組みは https://github.com/int128/kubelogin/tree/master/acceptance_test をご覧ください。
まとめ
DockerとKindを利用すると、本物のクラスタを用いたkubectl pluginのテストを実現できます。
決済手段を選択するビジネスルールを考える
お店やネットで買い物する時のビジネスルールが複雑になってきたので書き出してみました。
以下の順に評価して条件を満たす決済手段で支払います。
- USD/EUR建ての場合:Sony Bank WALLET
- ANA FESTAの場合:ソラチカカードVisa(5%割引)
- ビックカメラの場合:ビックカメラSuicaカード(10%)
- アトレやJR定期券の場合:JREカード(5%)
- 投資信託の場合:楽天カード(1%)→楽天証券
- コード決済が使える場合:Origami Pay(1%割引)→Kyash Visa(1%)→EPOS(1.5%)
- コード決済が使える場合:楽天Pay(1%)→Kyash Visa(1%)→EPOS(1.5%)
- Visa決済が使える場合:Kyash Visa(1%)→EPOS(1.5%)
- プリペイドVisaがダメな場合:EPOS (1.5%)
- 東京メトロ乗車の場合:ソラチカPASMO
- Suicaが使える場合:Suica → JREカードオートチャージ (3%)
- PayPayが使える場合:PayPay (0.5%)
- 現金
ただし、以下の例外条件があります。
- コード決済のキャンペーン期間中は還元率の高い決済手段を優先的に選択します。例えば、2/4時点では一部店舗でPayPay(40%)があります。
- Kyash Visaカードのポイント付与対象外(航空券や鉄道など)の場合はEPOSカード(or 楽天カード)で支払います。
- Kyash Visaカードの月間支払いが12万円を超えた場合はEPOSカード(or 楽天カード)で支払います。
- EPOSカードの年間支払いが100万円を超えた場合は楽天カードで支払います。
また、支払い前に以下を考慮します。
- 決済とは別にdポイントを付与できる場合があります。
- 西友ネットスーパーやApple Storeなどの場合はRebatesを経由してから発注します。
- ポイントサイトのキャンペーンがある場合があります。(最近あまり見ていない)
上記はFeliCa/NFC決済を考慮していません。FeliCa/NFC決済が使える場合はビジネスルールが変わってくると思います。
もし、幅広い決済手段に対応したプロキシがあったとしても、これらのビジネスルールを実装してテストするのは大変そうです。
kindでクラスタが起動しない原因を調べる
kind create cluster
コマンドでKubernetesクラスタが起動しない場合、以下のようなメッセージが表示されます。
✗ Starting control-plane 🕹️ ERROR: failed to create cluster: failed to init node with kubeadm: command "docker exec --privileged kind-control-plane kubeadm init --ignore-preflight-errors=all --config=/kind/kubeadm.conf --skip-token-print --v=6" failed with error: exit status 1
クラスタが起動しない原因を調査するには以下の方法があります。
- kindコマンドのログレベルを上げる
- kindノードコンテナの内部に入って、Control Planeのログを調査する
kindコマンドのログ
kindコマンドに -v
オプションを渡すと詳細なログが表示されるようになります。例えば -v10
を渡すと以下のようなログが表示されます。
Creating cluster "kubelogin-acceptance-test" ... DEBUG: docker/images.go:70] Pulling image: kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62 ... ✓ Ensuring node image (kindest/node:v1.17.0) 🖼 ✓ Preparing nodes 📦 DEBUG: config/config.go:90] Using kubeadm config: apiVersion: kubeadm.k8s.io/v1beta2 clusterName: kind controlPlaneEndpoint: 172.17.0.3:6443 controllerManager: extraArgs: enable-hostpath-provisioner: "true" kind: ClusterConfiguration kubernetesVersion: v1.17.0 (以下略)
kindコマンドのログからは、kubeadmに渡す設定ファイルの内容、Control Planeで使うイメージ、Control Planeの各設定ファイルのパスなどが分かります。kindコンテナの内部に入って調査する時の材料になります。
kindノードコンテナの内部にあるログ
kindではノードコンテナの内部でKubernetesクラスタが動いています。ノードコンテナのIDや名前は以下のようにdocker psコマンドで確認できます。
% docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 38ea3c1f766a kindest/node:v1.17.0 "/usr/local/bin/entr…" About a minute ago Up About a minute 127.0.0.1:32768->6443/tcp kind-control-plane
kindノードコンテナの内部ではsystemdが動いています。systemdのログは以下のようにdocker logsコマンドで確認できます。
% docker logs kind-control-plane INFO: ensuring we can execute /bin/mount even with userns-remap INFO: remounting /sys read-only (中略) Welcome to Ubuntu 19.10! [ OK ] Started Dispatch Password …ts to Console Directory Watch. [ OK ] Listening on Journal Socket. Mounting FUSE Control File System... Starting Remount Root and Kernel File Systems... (以下は抜粋) Starting containerd container runtime... [ OK ] Started kubelet: The Kubernetes Node Agent. [ OK ] Started containerd container runtime.
このように、ノードコンテナの起動時にsystemdがcontainerdやkubeletを実行して、Control Planeの各コンポーネントが実行される流れになっています。
各コンポーネントのログを確認するには、以下のようにノードコンテナの内部に入る必要があります。
% docker exec -it kind-control-plane /bin/bash
kindノードコンテナの内部では以下のプロセスが動いています。
- apiserver(コンテナとして動作)
- kube-controller-manager(コンテナとして動作)
- kube-scheduler(コンテナとして動作)
- etcd(コンテナとして動作)
- kubelet(プロセスとして動作)
これらが立ち上がると以下のコンテナも動き始めます。
- kube-proxy
- coredns
- kindnet
- local-path-provisioner
各コンテナのマニフェストは /etc/kubernetes/manifests/
にあります。
# ls -la /etc/kubernetes/manifests/ total 24 drwxr-xr-x 1 root root 4096 Jan 24 11:53 . drwxr-xr-x 1 root root 4096 Jan 24 11:53 .. -rw------- 1 root root 1805 Jan 24 11:53 etcd.yaml -rw------- 1 root root 3204 Jan 24 11:53 kube-apiserver.yaml -rw------- 1 root root 3090 Jan 24 11:53 kube-controller-manager.yaml -rw------- 1 root root 1120 Jan 24 11:53 kube-scheduler.yaml
また、各コンテナのログは /var/log/containers/
にあります。
# ls -la /var/log/containers/ total 28 drwxr-xr-x 2 root root 4096 Jan 24 12:45 . drwxr-xr-x 4 root root 4096 Jan 24 11:53 .. lrwxrwxrwx 1 root root 114 Jan 24 11:53 etcd-... lrwxrwxrwx 1 root root 136 Jan 24 12:45 kube-apiserver-... lrwxrwxrwx 1 root root 152 Jan 24 11:53 kube-controller-manager-... lrwxrwxrwx 1 root root 134 Jan 24 11:53 kube-scheduler-...
kubeletのログはどこに出力されるか分かりませんでした。詳しい人教えてください。 kubeletのログは下記のコマンドで確認できます。
# journalctl -u kubelet Jan 27 01:25:58 kubelogin-acceptance-test-control-plane systemd[1]: Started kubelet: The Kubernetes Node Agent. (以下略)
これらの設定ファイルやログを参照すると、クラスタが起動しない原因が分かると思います。例えば、apiserverの引数が間違っている場合はapiserverコンテナに以下のログが出ます。
# cat /var/log/containers/kube-apiserver-... 2020-01-24T11:56:18.6797274Z stderr F I0124 11:56:18.679446 1 server.go:596] external host was not specified, using 172.17.0.3 2020-01-24T11:56:18.6807181Z stderr F I0124 11:56:18.680394 1 server.go:150] Version: v1.17.0 2020-01-24T11:56:18.9807349Z stderr F Error: invalid authentication config: 'oidc-issuer-url' ("http://localhost") has invalid scheme ("http"), require 'https' 2020-01-24T11:56:18.9827157Z stderr F Usage: 2020-01-24T11:56:18.9827978Z stderr F kube-apiserver [flags] 2020-01-24T11:56:18.9828244Z stderr F 2020-01-24T11:56:18.9828494Z stderr F Generic flags: 2020-01-24T11:56:18.9828729Z stderr F (以下略)
kindにはログを一括エクスポートする機能があります。詳しくは https://kind.sigs.k8s.io/docs/user/quick-start/#exporting-cluster-logs を参照してください。
See Also
- kindの内部構造は https://kind.sigs.k8s.io/docs/design/initial/ が参考になります。
- kubeadmの動作は https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/ が参考になります。