Kubernetes v1.17 から kubeadm token list で -o が指定できるようになった

[Kubernetes v1.17.0がリリース](https://kubernetes.io/blog/2019/12/09/kubernetes-1-17-release-announcement/) されましたね。

本稿では、v1.17 のエンハンスの中で、地味ながらちょっと嬉しいかもしれない `kubeadm token list` の `-o` オプション の対応について記載します。

## kubeadm token list とは

まず、 `kubeadm token list` とは kubeadmがクラスター構築時に生成する bootstrapトークンの一覧を表示する機能です。 `kubadm init --upload-certs` 実施後に実行すると下記のように表示されます。

```
# kubeadm token list
TOKEN                     TTL       EXPIRES                     USAGES                   DESCRIPTION                                           EXTRA GROUPS
tup37m.6cbrgvz60biqj3j5   1h        2019-12-１１T21:14:55+09:00   <none>                   Proxy for managing TTL for the kubeadm-certs secret   <none>
xvir4m.dgu8wdowa19pbj6d   23h       2019-12-１２T19:14:55+09:00   authentication,signing   <none>                                                system:bootstrappers:kubeadm:default-node-token
```

`kubadm init --upload-certs` によって、 2つの bootstrapトークンが生成されていることがわかります。上記の１つめは、Secret `kubeadm-certs` に対してTTLを設定するための bootstrap トークンです（TTLはデフォルト２時間）。ちなみに `kubeadm-certs` はマスターノードをjoinする際に必要となる、証明書の情報を暗号化して保持しています。２つ目はノードをjoinする際のtokenとして利用することになります（TTLはデフォルト２４時間）。

なお、この２つのトークンは、実際には kubeadm で作成したクラスターのSecretとして登録されているので、 kubectl で確認することもできます。

```
# kubectl get secret -n kube-system --field-selector=type=bootstrap.kubernetes.io/token
NAME                     TYPE                            DATA   AGE
bootstrap-token-tup37m   bootstrap.kubernetes.io/token   4      50m
bootstrap-token-xvir4m   bootstrap.kubernetes.io/token   6      50m
```

## kubeadm token list -o

さて、ここまでは以前のバージョンでもできたのですが、 v1.17 になってやっと `-o` オプションが対応されました。ヘルプを見てみましょう。

```
# kubeadm token list -h | grep '\-o'
  -o, --experimental-output string    Output format. One of: text|json|yaml|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file. (default "text")
```

`-o` オプションによっていくつかフォーマットが選べることがわかります。 `-o json` をつけて実行してみましょう。

```
# kubeadm token list -o json
{
    "kind": "BootstrapToken",
    "apiVersion": "output.kubeadm.k8s.io/v1alpha1",
    "token": "tup37m.6cbrgvz60biqj3j5",
    "description": "Proxy for managing TTL for the kubeadm-certs secret",
    "expires": "2019-12-11T12:14:55Z"
}
{
    "kind": "BootstrapToken",
    "apiVersion": "output.kubeadm.k8s.io/v1alpha1",
    "token": "xvir4m.dgu8wdowa19pbj6d",
    "expires": "2019-12-12T10:14:55Z",
    "usages": [
        "authentication",
        "signing"
    ],
    "groups": [
        "system:bootstrappers:kubeadm:default-node-token"
    ]
}
```

たしかに json で結果が得られることがわかります。これなら、 なにか別のツールでbootstrapトークンの情報が必要なときに、容易に情報を取得可能になることでしょう。これは便利そう…ですね！

## joinしてみる

さて、token がわかったので joinを実行するためには `kubeadm token list -o xxxx` で得られた値を指定するだけで行けそうにも見えますが、もうひと工夫必要になります。 `kubeadm join` では `--token` の指定の他に、通常は `--discovery-token-ca-cert-hash` の指定が必要となります。 `--discovery-token-ca-cert-hash` は KubernetesクラスタのCA証明書の公開鍵のハッシュです。

`--discovery-token-ca-cert-hash` の値を採取するためにはいくつか方法はありますが、 たとえばKubernetesクラスタにjoin済みのノード内（CA証明書が `/etc/kubernetes/pki/ca.crt` に格納されている前提）で下記を実行すると取得できます。

```
# openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
aa7b901857a20600ad12c84ea746d7f774a139db9fe369a6e3e25a0b7973a695
```

上記で得られたハッシュと、join用のbootstrapトークンを指定することで `kubeadm join` することができます。たとえば、下記のとおりです。

```
# kubeadm join <Kubernetes APIのアドレス>:6443 \
    --token xvir4m.dgu8wdowa19pbj6d \
    --discovery-token-ca-cert-hash sha256:aa7b901857a20600ad12c84ea746d7f774a139db9fe369a6e3e25a0b7973a695
```

ちなみに、 おすすめはしませんが `--discovery-token-ca-cert-hash` の代わりに `--discovery-token-unsafe-skip-ca-verification` を指定しても可能ではあります。

また、マスターノード（２台目以降）の join の場合は上記に追加で `--control-plane` の指定と `--certificate-key` （Secret `kubeadm-certs` 内の証明書データの復号鍵） が必要になります。 `--certificate-key` の値は、`kubeadm init` 時に控えていない場合は、どこにも保存されていないので、再度 `kubeadm init phase upload-certs --upload-certs` を行う必要があります。

```
# kubeadm init phase upload-certs --upload-certs
[upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
[upload-certs] Using certificate key:
2cb2de7f3c08ed89d941da3a3b9c141ca3685c1d18da9dfca6ad4e416afc0f24

# kubeadm join <Kubernetes APIのアドレス>:6443 \
    --token xvir4m.dgu8wdowa19pbj6d \
    --discovery-token-ca-cert-hash sha256:aa7b901857a20600ad12c84ea746d7f774a139db9fe369a6e3e25a0b7973a695 \
    --control-plane \
    --certificate-key 2cb2de7f3c08ed89d941da3a3b9c141ca3685c1d18da9dfca6ad4e416afc0f24
```

ただし、 `kubeadm init` は現状まだ `-o` 指定はできません。そのため、この出力を機械的にパースして `--certificate-key` の値を取得するのは、json等の出力と比べてややハードルが上がります。 代替手段としては、 `kubeadm init` を実施する前に予め別の方法で `--certificate-key` を用意しておく事もできたりはしますが…。


## まとめ

`kubeadm token list -o` によって出力フォーマットを指定できることがわかりました。

ただし、現状 `-o` オプションは kubeadm では `kubeadm token list` と `kubeadm version` でしか対応していません。例えば `kubeadm init` 等でも同様の対応をされれば他のツール等の組み合わせが更に実現しやすくなることでしょう。 [KEP](https://github.com/kubernetes/enhancements/blob/master/keps/sig-cluster-lifecycle/kubeadm/20190506-kubeadm-machine-output.md) を読むと他のコマンドの `-o` 対応も検討はされているようなので、今後の対応が気になるところです。