Kubernetesクラスター内のPodからkubectlを実行する

Kubernetesクラスター内のPodから`kubectl`コマンドを実行する方法について調べたのでメモ。
Minikube v0.32.0とIBM Cloud Private v3.1.0で確認。

## 参考リンク

以下に丁寧な回答がある。

- [Kubernetes - How to run kubectl commands inside a container?](https://stackoverflow.com/questions/42642170/kubernetes-how-to-run-kubectl-commands-inside-a-container)

## イメージの作成

`kubectl`のバイナリを配置したイメージを作る。

（参考）[jqだけ含むコンテナの作成](https://qiita.com/sotoiwa/items/c7ec7c25debbe1ca8168)

### FROM ubuntu

ubuntuをベースにイメージを作る。

```dockerfile:Dockerfile
FROM ubuntu:18.04

RUN apt-get update \
  && apt-get install -y wget \
  && rm -rf /var/lib/apt/lists/* \
  && wget https://storage.googleapis.com/kubernetes-release/release/v1.13.0/bin/linux/amd64/kubectl \
  && mv kubectl /usr/local/bin/kubectl \
  && chmod +x /usr/local/bin/kubectl

ENTRYPOINT ["/usr/local/bin/kubectl"]
CMD [""]
```

```shell
docker build -t kubectl:v1.13.0 .
docker tag kubectl:v1.13.0 sotoiwa540/kubectl:v1.13.0
docker push sotoiwa540/kubectl:v1.13.0
```

### FROM alpine

alpineをベースにする場合は以下のようにする。

```dockerfile:Dockerfile
FROM alpine:3.8

RUN apk add --no-cache --virtual=build-deps wget \
  && wget https://storage.googleapis.com/kubernetes-release/release/v1.13.0/bin/linux/amd64/kubectl \
  && mv kubectl /usr/local/bin/kubectl \
  && chmod +x /usr/local/bin/kubectl \
  && apk del build-deps

ENTRYPOINT ["/usr/local/bin/kubectl"]
CMD [""]
```

```shell
docker build -t kubectl:v1.13.0-alpine .
docker tag kubectl:v1.13.0-alpine sotoiwa540/kubectl:v1.13.0-alpine
docker push sotoiwa540/kubectl:v1.13.0-alpine
```

utuntuとaplineでイメージサイズはこれくらい違う。ubuntuのほうはwgetとその依存パッケージを削除していないので、削除するようにしたらもう少しだけ減る。

```shell-session
$ docker images | grep kubectl | grep -v sotoiwa540
kubectl                               v1.13.0                84029f6e1e30        2 minutes ago       133MB
kubectl                               v1.13.0-alpine         93f11846b1be        2 hours ago         43.6MB
$
```

### ibmcom/kubectl

ICPだと`ibmcom/kubectl:v1.11.1.1`というイメージが既にあり、同じようにaplineにkubectlのバイナリを配置したもののようだ。

```shell-session
$ docker images | grep ibmcom/kubectl
mycluster.icp:8500/ibmcom/kubectl           v1.11.1.1              710a64408d49        5 months ago        65.4MB
$
```

```shell-session
$ docker run --rm -it mycluster.icp:8500/ibmcom/kubectl:v1.11.1.1 sh
/ # which kubectl
/usr/local/bin/kubectl
/ # cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.6.2
PRETTY_NAME="Alpine Linux v3.6"
HOME_URL="http://alpinelinux.org"
BUG_REPORT_URL="http://bugs.alpinelinux.org"
/ #
```

## Podの実行

Podとして実行する場合、特に何もしなくてもkubectlが使える（apiserverに接続できる）。なぜなら、kubectlが[このように](https://github.com/kubernetes/client-go/blob/32801e2d7c449af3373a7728b1b3f9b9c19c1bdb/rest/config.go#L399)apiserverへの接続情報を設定しているため。

- 接続するapiserverを環境変数`KUBERNETES_SERVICE_HOST`と`KUBERNETES_SERVICE_PORT`から取得
  - Kubernetesクラスターではapiserverは`kubernetes`というServiceリソースとして定義されていてクラスター内からService経由でアクセスできる
- アクセストークンをコンテナにマウントされている`/var/run/secrets/kubernetes.io/serviceaccount/token`から取得
- apiserverのサーバー証明書をコンテナにマウントされている` /var/run/secrets/kubernetes.io/serviceaccount/ca.crt`から取得

Podとして実行してみる。

```yaml:kubectl-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: kubectl
  name: kubectl-pod
spec:
  containers:
  - name: kubectl
    image: sotoiwa540/kubectl:v1.13.0
    command:
    - tail
    - -f
    - /dev/null
```

```shell
kubectl apply -f kubectl-pod.yaml
```

トークンと証明書はPodが実行されるときにKubernetesによって必ず自動的にマウントされている。

```shell-session
$ kubectl get po
NAME          READY   STATUS    RESTARTS   AGE
kubectl-pod   1/1     Running   0          4s
$ kubectl get po kubectl-pod -o yaml
apiVersion: v1
kind: Pod
metadata:
...
  name: kubectl-pod
  namespace: default
...
spec:
  containers:
...
    image: sotoiwa540/kubectl:v1.13.0
    imagePullPolicy: IfNotPresent
    name: kubectl
...
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-cn7gt
      readOnly: true
...
  serviceAccount: default
  serviceAccountName: default
...
  volumes:
  - name: default-token-cn7gt
    secret:
      defaultMode: 420
      secretName: default-token-cn7gt
...
$
```

ただしこれだけではapiserverに接続できるものの、NamespaceのデフォルトのServiceAccountで実行しているため権限がなくPodをListすることもできない。

```shell-session
$ kubectl exec -it kubectl-pod -- kubectl get po
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:default" cannot list resource "pods" in API group "" in the namespace "default"
command terminated with exit code 1
$
```

新たにServiceAccountを作り、このServiceAccountに強力なClusterRoleを割り当てる。

```shell-session
$ kubectl create sa admin
serviceaccount/admin created
$
```

デフォルトで存在する`cluster-admin`のClusterRoleが強力なので、このClusterRoleを作成したServiceAccountにバインドする。
ClusterRoleはClusterRoleBindingでバインドすることもできるし、RoleBingingでバインドすることもできるが、RoleBidingでバインドした場合は権限がNamespaceに限定される

### ClusterRoleBinding

はじめにClusterRoleBindingを作成する。

```yaml:admin-clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-clusterrolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin
  namespace: default
```

```shell
kubectl apply -f admin-clusterrolebinding.yaml
```

Podのマニフェストに`serviceAccount: admin`の指定を追加する。

```yaml:kubectl-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: kubectl
  name: kubectl-pod
spec:
  serviceAccount: admin
  containers:
  - name: kubectl
    image: sotoiwa540/kubectl:v1.13.0
    command:
    - tail
    - -f
    - /dev/null
```

Podを再作成する。

```shell
kubectl delete po kubectl-pod
kubectl apply -f kubectl-pod.yaml
```

`kubectl`でPodがリストできることを確認。

```shell-session
$ kubectl exec -it kubectl-pod -- kubectl get po
NAME          READY   STATUS    RESTARTS   AGE
kubectl-pod   1/1     Running   0          30s
$ kubectl exec -it kubectl-pod -- kubectl get po -n kube-system
NAME                                        READY   STATUS    RESTARTS   AGE
coredns-576cbf47c7-blfxm                    1/1     Running   1          4d22h
coredns-576cbf47c7-pnnfc                    1/1     Running   1          4d22h
default-http-backend-5957bfbccb-lhr2p       1/1     Running   1          4d22h
etcd-minikube                               1/1     Running   1          4d22h
kube-addon-manager-minikube                 1/1     Running   1          4d22h
kube-apiserver-minikube                     1/1     Running   0          4h35m
kube-controller-manager-minikube            1/1     Running   0          4h35m
kube-proxy-qzh9m                            1/1     Running   0          4h34m
kube-scheduler-minikube                     1/1     Running   1          4d22h
kubernetes-dashboard-5bff5f8fb8-2fbwb       1/1     Running   3          4d22h
nginx-ingress-controller-6958898f8f-7z9dg   1/1     Running   3          4h21m
storage-provisioner                         1/1     Running   3          4d22h
$
```

一度ClusterRoleBindingを削除する。

```shell
kubectl delete clusterrolebinding admin-clusterrolebinding
```

### RoleBinding

今度はRoleBindingを作成してみる。

```yaml:admin-clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: admin-clusterrolebinding
subjects:
- kind: ServiceAccount
  name: admin
  namespace: default
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
```

```shell
kubectl apply -f admin-rolebinding.yaml
```

同じ`cluster-admin`のClusterRoleをバインドしているにもかかわらず、自分以外のNamespaceへの権限がない。

```shell-session
$ kubectl exec -it kubectl-pod -- kubectl get po
NAME          READY   STATUS    RESTARTS   AGE
kubectl-pod   1/1     Running   0          7m37s
$ kubectl exec -it kubectl-pod -- kubectl get po -n kube-system
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:admin" cannot list resource "pods" in API group "" in the namespace "kube-system"
command terminated with exit code 1
$
```