ICPでServiceAccount Tokenを使ってkubectlを実行する

IBM Cloud Private v2.1.0.3のCLIでのログイン方法と、ログインしないでServiceAccount Tokenを使ってkubectlを実行する方法のメモ。

ICPでkubectlを実行するための認証情報を取得する方法には以下の2つがある。

- ICPコンソールで認証情報を取得する
- `bx pr login`コマンドで認証情報を取得する

いずれの場合もトークンの有効期限は12時間で切れてしまうので、cron等でスクリプトからkubectlを実行する場合には適さない。

## ICPコンソール

ICPコンソールにログインし、右上の人型のアイコンをクリックして「クライアントの構成」をクリック。表示されたコマンドをペーストする。

```
kubectl config set-cluster cluster.local --server=https://18.179.83.130:8001 --insecure-skip-tls-verify=true
kubectl config set-context cluster.local-context --cluster=cluster.local
kubectl config set-credentials admin --token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiNHl5c2o1Y3B1d3l1MnZmb3M2NXAiLCJyZWFsbU5hbWUiOiJjdXN0b21SZWFsbSIsInVuaXF1ZVNlY3VyaXR5TmFtZSI6ImFkbWluIiwiaXNzIjoiaHR0cHM6Ly9teWNsdXN0ZXIuaWNwOjk0NDMvb2lkYy9lbmRwb2ludC9PUCIsImF1ZCI6ImUxMzQwNWYyMDNkYTE0MmJiYmZhOTIzNGQ1OTFjZDA5IiwiZXhwIjoxNTMyMzQ2MzUxLCJpYXQiOjE1MzIzMTc1NTEsInN1YiI6ImFkbWluIiwidGVhbVJvbGVNYXBwaW5ncyI6W119.bpIp6HuSvccBiOCg6y_4huzuyUWX0Frab3iTnzlN9IrrO2Z4q1pz4fWF9rwLpSeZ6iC6XCPO6SGQnFbE1zFb680iwabZNBj2ideBBS7fYyqmoTCYEg0wb6C7lKLtguHhbr4WW8Kt5l3eyT4dzPUEmPkPMmsyQWdlQ6VqWm-GqWB6Vywbd7pOMVdO6SlKlGbVnJeXTohxLAxmDSwVou8aCLCYhR45g3wLN7kfpY4QqBAEdAGqUjNlz10NXGYu-R2dvUwOmZf1PtY1vQB9BFlySQvrbGR_Bo0fBhtpo8lIqZ9YjzLYMVK4kiJAmnxJAOi7IIHOcmP5d6xHcOvpAStR5A
kubectl config set-context cluster.local-context --user=admin --namespace=default
kubectl config use-context cluster.local-context
```

こちらの方法では、`cluster.local-context`というContextが作成される。

## bx pr login

`bx pr login`コマンドでログインする。

```
bx pr login -a https://mycluster.icp:8443 --skip-ssl-validation
```

```shell-session
root@myicp01:~# bx pr login -a https://mycluster.icp:8443 --skip-ssl-validation
API endpoint: https://mycluster.icp:8443

Username> admin

Password>
Authenticating...
OK

Select an account:
1. mycluster Account (id-mycluster-account)
Enter a number> 1
Targeted account mycluster Account (id-mycluster-account)

Configuring helm and kubectl...
Configuring kubectl: /root/.bluemix/plugins/icp/clusters/mycluster/kube-config
Property "clusters.mycluster" unset.
Property "users.mycluster-user" unset.
Property "contexts.mycluster-context" unset.
Cluster "mycluster" set.
User "mycluster-user" set.
Context "mycluster-context" created.
Switched to context "mycluster-context".

Cluster mycluster configured successfully.

Configuring helm: /root/.helm
Helm configured successfully

OK

root@myicp01:~#
```

このコマンドは`.bluemix/plugins/icp/clusters/mycluster/kube-config`というスクリプトを生成し、このスクリプトがkubectlを構成している。

```sh:.bluemix/plugins/icp/clusters/mycluster/kube-config

if [ "$(uname -s)" = 'Linux' ]; then
	CONFIG_FILEPATH=$(dirname "$(readlink -f "$0" || echo "$(echo "$0" | sed -e 's,\\,/,g')")")
else
	CONFIG_FILEPATH=$(dirname "$(readlink "$0" || echo "$(echo "$0" | sed -e 's,\\,/,g')")")
fi

CLUSTER_NAME=mycluster
mkdir -p "$HOME/.kube/$CLUSTER_NAME"
chmod 700 "$HOME/.kube/$CLUSTER_NAME"
cp "$CONFIG_FILEPATH/ca.pem" "$HOME/.kube/$CLUSTER_NAME/"
cp "$CONFIG_FILEPATH/cert.pem" "$HOME/.kube/$CLUSTER_NAME/"
cp "$CONFIG_FILEPATH/key.pem" "$HOME/.kube/$CLUSTER_NAME/"

chmod 600 $HOME/.kube/$CLUSTER_NAME/*.pem
kubectl config unset clusters.$CLUSTER_NAME
kubectl config unset users.$CLUSTER_NAME-user
kubectl config unset contexts.$CLUSTER_NAME-context
kubectl config set-cluster $CLUSTER_NAME --server=https://18.179.83.130:8001 "--certificate-authority=$HOME/.kube/$CLUSTER_NAME/ca.pem"
kubectl config set-credentials $CLUSTER_NAME-user --token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiMTVmd3h3aTJ1c2w5M2VodDdjZ3MiLCJyZWFsbU5hbWUiOiJjdXN0b21SZWFsbSIsInVuaXF1ZVNlY3VyaXR5TmFtZSI6ImFkbWluIiwiaXNzIjoiaHR0cHM6Ly9teWNsdXN0ZXIuaWNwOjk0NDMvb2lkYy9lbmRwb2ludC9PUCIsImF1ZCI6ImUxMzQwNWYyMDNkYTE0MmJiYmZhOTIzNGQ1OTFjZDA5IiwiZXhwIjoxNTMyMzYwNDc2LCJpYXQiOjE1MzIzMzE2NzYsInN1YiI6ImFkbWluIiwidGVhbVJvbGVNYXBwaW5ncyI6W119.ovdbBxf13UBnfBEK45njE5I3LiuK-FnLPxePEo-2LJulUd2c6YQFkuniBtf4BPYQZQbkmmKuiDmU0n-u3fgCUt7a7wCAS68K226DkyhmEC3HLuxot6i2E6idIdgmTksOi3nleHDKiaOjvxGgkjQgEVqxTf4OAsjXVCCnjOdV-NaV7lWCZeR47SmOMg9dAObxqfNpuNO-vNg1KnhPZedR6zU2aSq-MSWhbW7bjyApbTOirbkiVvD5P5dwrSm-PBTWIDgLrzdT4_z2ZloJ_IczC27fJVC-69H5Dc68eV7i_oQOosWDvYKMJuaT2IbJl6uuehkNDTPBQGpi0m9GIIVjbw
kubectl config set-context $CLUSTER_NAME-context --cluster=$CLUSTER_NAME --user=$CLUSTER_NAME-user --namespace=default
kubectl config use-context $CLUSTER_NAME-context
```

こちらの方法では、`mycluster-context`というcontextを作っている。
トークンはこのスクリプトに含まれており、毎回更新される。

## ServiceAccount Token

Cluster全体を参照だけ可能なServiceAccountを作り、このServiceAccountのトークンでkubectlを実行するまでを試す。

### ServiceAccountの作成

ServiceAccountを作成する。同時にTokenも作成される。ServiceAccountはNamespaceに紐付くのでNamespaceを指定する必要がある。

```
kubectl create sa cluster-viewer -n kube-system
```

作成したServiveAccountを確認する。

```
root@myicp01:~# kubectl get sa cluster-viewer -n kube-system -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: 2018-07-23T08:58:01Z
  name: cluster-viewer
  namespace: kube-system
  resourceVersion: "274410"
  selfLink: /api/v1/namespaces/kube-system/serviceaccounts/cluster-viewer
  uid: 8094e0ef-8e56-11e8-a58a-06f83da66800
secrets:
- name: cluster-viewer-token-hqjjv
root@myicp01:~#
```

### ClusterRoleの作成

今回は全てのNamespaceに対する参照権限を付与したいので、RoleではなくClusterRoleを使用する。RoleはNamespaceに紐付くが、ClusterRoleはNamespaceに紐付かない。

デフォルトでviewという参照だけしかできなそうなClusterRoleがいるので、これをそのまま流用することにする。

```
root@myicp01:~# kubectl get clusterrole view -o yaml
aggregationRule:
  clusterRoleSelectors:
  - matchLabels:
      rbac.authorization.k8s.io/aggregate-to-view: "true"
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  creationTimestamp: 2018-07-04T06:15:14Z
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: view
  resourceVersion: "220"
  selfLink: /apis/rbac.authorization.k8s.io/v1/clusterroles/view
  uid: 9d275f63-7f51-11e8-9310-06f83da66800
rules:
- apiGroups:
  - ""
  resources:
  - configmaps
  - endpoints
  - persistentvolumeclaims
  - pods
  - replicationcontrollers
  - replicationcontrollers/scale
  - serviceaccounts
  - services
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - bindings
  - events
  - limitranges
  - namespaces/status
  - pods/log
  - pods/status
  - replicationcontrollers/status
  - resourcequotas
  - resourcequotas/status
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - namespaces
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - apps
  resources:
  - daemonsets
  - deployments
  - deployments/scale
  - replicasets
  - replicasets/scale
  - statefulsets
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - autoscaling
  resources:
  - horizontalpodautoscalers
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - batch
  resources:
  - cronjobs
  - jobs
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - extensions
  resources:
  - daemonsets
  - deployments
  - deployments/scale
  - ingresses
  - networkpolicies
  - replicasets
  - replicasets/scale
  - replicationcontrollers/scale
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - policy
  resources:
  - poddisruptionbudgets
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - networking.k8s.io
  resources:
  - networkpolicies
  verbs:
  - get
  - list
  - watch
root@myicp01:~#
```

### ClusterRoleBindingの作成

最初に作成したServiceAccountに、viewのClusterRoleをBindingする。

```yaml:cluster-viewer-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-viewer-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: view
subjects:
- kind: ServiceAccount
  name: cluster-viewer
  namespace: kube-system
```

```
kubectl apply -f cluster-viewer-binding.yaml
```

### トークンの取得

ServiceAccountが持つトークンの名前を確認する。

```
kubectl get sa cluster-viewer -n kube-system -o yaml
```

jsonpathを使って名前を直接取得する場合は以下のコマンド。

```
kubectl get sa cluster-viewer -n kube-system -o jsonpath='{.secrets[].name}'
```

Secretを確認する。

```
kubectl get secret -n kube-system cluster-viewer-token-hqjjv -o yaml
```

Secretからトークンを取得してデコードする。

```
kubectl get secret cluster-viewer-token-hqjjv -n kube-system -o jsonpath={.data.token} | base64 -d
```

取得したトークンをcluster-viewerユーザーのcredentialとしてセットする。

```
kubectl config set-credentials cluster-viewer --token=$(kubectl get secret cluster-viewer-token-hqjjv -n kube-system -o jsonpath={.data.token} | base64 -d)
```

コンテキストを作成し、ユーザーを関連づける。

```
kubectl config set-context cluster-viewer-context --user=cluster-viewer
```

コンテキストにclusterを関連づける。ICPコンソールで認証情報を取得した場合に定義されたcluster定義を流用する。

```
kubectl config set-context cluster-viewer-context --cluster=cluster.local 
```

コンテキストを変更する。

```
kubectl config use-context cluster-viewer-context
```

Podを参照できるが、編集できないことを確認。

```
root@myicp01:~# kubectl get po -n ns2
NAME                       READY     STATUS    RESTARTS   AGE
liberty-6ddc494666-bpf9x   1/1       Running   3          6d
root@myicp01:~# kubectl edit po -n ns2
error: pods "liberty-6ddc494666-bpf9x" could not be patched: pods "liberty-6ddc494666-bpf9x" is forbidden: User "system:serviceaccount:kube-system:cluster-viewer" cannot patch pods in the namespace "ns2"
You can run `kubectl replace -f /tmp/kubectl-edit-thij1.yaml` to try this update again.
root@myicp01:~#
```

## 参考リンク

[kubectl CLI を使用した IBM® Cloud Private クラスターへのアクセス](https://www.ibm.com/support/knowledgecenter/ja/SSBS6K_2.1.0.3/manage_cluster/cfc_cli.html)

[IBM® Cloud Private CLI のインストール](https://www.ibm.com/support/knowledgecenter/ja/SSBS6K_2.1.0.3/manage_cluster/install_cli.html)

[Configuring the Kubernetes CLI by using service account tokens](https://www.ibm.com/developerworks/community/blogs/fe25b4ef-ea6a-4d86-a629-6f87ccf4649e/entry/Configuring_the_Kubernetes_CLI_by_using_service_account_tokens1?lang=en_us)

[KubernetesのRBACについて](https://qiita.com/sheepland/items/67a5bb9b19d8686f389d)