KubernetesのService Accountについて調べてみた

# 概要
以下のライブラリを使おうと思ったらPod内ならService Account使えば良いと書いてあって、いまいち理解してなかったので調べてみました。
https://github.com/kubernetes/client-go

大体ドキュメントに書いてある通りなのですが、何となく雰囲気掴んでおけばドキュメント読むにも役立つんじゃないかと思うので軽くまとめておきます。
端折ってるので詳しく知りたい方はドキュメントをどうぞ。

# 参考
- https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/
- https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/

# バージョン
- Kubernetes
    - 1.10

多分バージョン変わったら役に立たなくなる気がしたので、調べたときのバージョンを明記しておきます。

# User accounts vs service accounts
Kubernetesではuser accountとservice accountは明確に区別されるようです

- User accountsは人のためのもの、Service accountsはPod内で動くプロセスのためのもの
- User accountsは全てのNamespaceを通して固有である必要があるが、Service accountsはNamespace内で固有であれば良い


そのため、testという名前で2つService accountを作ろうとするとエラーになります。

```console
$ kubectl create serviceaccount test
serviceaccount "test" created
$ kubectl create serviceaccount test
Error from server (AlreadyExists): serviceaccounts "test" already exists
```

しかし、異なるNamespaceなら作成可能です。

```console
% kubectl create serviceaccount test -n name1
serviceaccount "test" created
```

# Service account automation
Service accountsは3つのコンポーネントで成り立っています。

- A Service account admission controller
- A Token controller
- A Service account controller

## Service Account Controller
これは単に全てのNamespaceに `default` というServiceAccountを作成するためのコントローラのようです。

name1というNamespaceを作った直後にServiceAccountを確認すると、既にdefaultが存在することがわかります。

```console
$ kubectl create namespace name1
namespace "name1" created
$ kubectl get serviceaccounts -n name1
NAME      SECRETS   AGE
default   1         24s
```

この `default` というServiceAccountはNamespaceとしての `default` と関連があると思いこんでいたのですが、単に一番最初にあるデフォルトのServiceAccountである、というぐらいの意味でNamespaceとは関係ないようです。
何でもかんでも `default` って付けるのどうなんですかね...

## Token Controller
これはServiceAccountを監視して、新しくServiceAccountが作成されたらAPIアクセスするための `Secret` を作成してくれる存在のようです。この `Secret` にAPIアクセスのためのtokenなどが入っている感じですね。
ServiceAccountが削除されたら `Secret` を消してくれます。

試しにServiceAccountを作ってみると、直後に `Secret` が作成されていることが分かります。 `test-token-wt6nr` というやつですね。

```console
$ kubectl create serviceaccount test
serviceaccount "test" created
$ kubectl get secret
NAME                  TYPE                                  DATA      AGE
default-token-lg725   kubernetes.io/service-account-token   3         7d
test-token-wt6nr      kubernetes.io/service-account-token   3         4s
```

中を見てみるとtokenとca.crtが入っています。

```console
$ kubectl describe secret test-token-wt6nr
Name:         test-token-wt6nr
Namespace:    default
Labels:       <none>
Annotations:  kubernetes.io/service-account.name=test
              kubernetes.io/service-account.uid=XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX

Type:  kubernetes.io/service-account-token

Data
====
namespace:  7 bytes
token:      eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
ca.crt:     1042 bytes
```

Service Account Controllerは他にもServiceAccountに紐づく `Secret` をユーザが自分で作った場合に、ServiceAccountの存在確認やtokenの追加もやってくれるみたいです。
追加で `Secret` 欲しい場合ですかね。

```secret.json
{
    "kind": "Secret",
    "apiVersion": "v1",
    "metadata": {
        "name": "mysecretname",
        "annotations": {
            "kubernetes.io/service-account.name": "myserviceaccount"
        }
    },
    "type": "kubernetes.io/service-account-token"
}
```

こんな感じでJSON書いて `kubectl create -f secret.json` すればいいみたいです。

## Service Account Admission Controller
そして一番知りたかった肝心のコントローラーです。

まずそもそもですが、PodはServiceAccountと紐づけて起動されます。それを知っておかないと混乱するかと思うので最初に補足しました。流れとしては以下です。

1. Pod起動時にServiceAccountが指定されていればそれを使い、指定されていない場合は `default` と紐付けられる
1. Podと紐付いたServiceAccountが存在するか確認し、存在しなければ弾く
1. Podが `ImagePullSecrets` の設定を持っていなければ、 `ServiceAccount` の `ImagePullSecrets` が追加される
1. APIアクセスするためのtokenを入れるための `volume` が追加される
1. Podの各コンテナに `volumeSource` を足す。pathは `/var/run/secrets/kubernetes.io/serviceaccount`

簡単に言えば、ServiceAccountが持っているAPIアクセスするためのtokenが、しれっとコンテナにマウントされます！

見たほうが早いと思うので、適当にnginxのPodをデプロイします。

```console
$ kubectl run nginx --image=nginx
deployment.apps "nginx" created
$ kubectl get pods
NAME                                               READY     STATUS              RESTARTS   AGE
nginx-8586cf59-2ksl7                               0/1       ContainerCreating   0          4s
```

この状態でコンテナに入ってみます。そして上のpathを見に行ってみると、確かにtokenなどがマウントされています。

```
% kubectl exec -it nginx-8586cf59-2ksl7 bash
root@nginx-8586cf59-2ksl7:/# ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt  namespace  token
```

このtokenの中身は単なるJWTなのでデコードしてPayloadを見ると以下のようになっています。

```console
{
  "iss": "kubernetes/serviceaccount",
  "kubernetes.io/serviceaccount/namespace": "default",
  "kubernetes.io/serviceaccount/secret.name": "default-token-lg725",
  "kubernetes.io/serviceaccount/service-account.name": "default",
  "kubernetes.io/serviceaccount/service-account.uid": "68b1d424-6af0-11e8-84f0-067120183a92",
  "sub": "system:serviceaccount:default:default"
}
```

secret.nameが `default-token-lg725` になっていますね。service-account.nameも `default` になっています。
確かにServiceAccountの `default` が持っている `Secret` が勝手にマウントされています。
なるほどな〜という感じ。

でも自分YAMLに何も書いてないのに...と思ってPodの定義を見ると以下のようになっています（一部省略）。

```console
$ kubectl get pods nginx-8586cf59-2ksl7 -o yaml
apiVersion: v1
kind: Pod
metadata:
  ...
  namespace: default
  ...
spec:
  containers:
  - image: nginx
  ...
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-lg725
      readOnly: true
  serviceAccount: default
  serviceAccountName: default
  ...
  volumes:
  - name: default-token-lg725
    secret:
      defaultMode: 420
      secretName: default-token-lg725
      ...
```

確かにマウントされてるし `ServiceAccount: default` になっている...!!

ということで勘の鋭い方はもう分かっていると思いますが、異なるServiceAccountを使いたければYAMLに書いてapplyすれば良いだけですね。

以上から分かったように、PodにはServiceAccountのtokenやca.crtが自動でマウントされるため、Pod内で動かすプログラムは `/var/run/secrets/kubernetes.io/serviceaccount` 以下を見に行くことで自分で `~/.kube/config` 書いたりしなくてもAPIを叩くことが出来ます。

実際に利用可能なAPIはServiceAccountに紐づくRole次第ですが、それはまた別の話なので省略します。


# まとめ
ServiceAccountについてまとめました。
仕組みとしてはAWSでEC2にIAM Roleをアタッチする場合に近い感じですね。

k8sのオブジェクトとして全て表現される世界観で実現されており面白いなーと思いました。
k8s勉強始めたばかりで意味わからずだったので、誰かの役に立てば幸いです。

※ Service accountsだったりService accountだったりServiceAccountだったり混ざってますが、ドキュメントに合わせてます。リソース名を指すときはServiceAccountって書いて一般的なアカウントの説明の場合はService accountsなのかなーと思ってますが、微妙なところもあり不明です。