kubernetes
k8s
ServiceAccount

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

概要

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

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

参考

バージョン

  • 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を作ろうとするとエラーになります。

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

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

% 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が存在することがわかります。

$ 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 というやつですね。

$ 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が入っています。

$ 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 と紐付けられる
  2. Podと紐付いたServiceAccountが存在するか確認し、存在しなければ弾く
  3. Podが ImagePullSecrets の設定を持っていなければ、 ServiceAccountImagePullSecrets が追加される
  4. APIアクセスするためのtokenを入れるための volume が追加される
  5. Podの各コンテナに volumeSource を足す。pathは /var/run/secrets/kubernetes.io/serviceaccount

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

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

$ 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を見ると以下のようになっています。

{
  "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の定義を見ると以下のようになっています(一部省略)。

$ 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なのかなーと思ってますが、微妙なところもあり不明です。