「Kubernetes1.8のクラスタを構築する。kubeadmで。」で、Dashboardがうまく動かない問題が発生したんだけど、それを解決した話。

問題の現象

kubeadmでKubernetesクラスタを組んで、自前のアプリ(Goslings)のデプロイまではうまくできたんだけど、Dashboardをデプロイしたら動かず、Web UIにkubectl proxy経由でつないでもタイムアウトしてしまった。

対策

なんとなく、クラスタ内部での名前解決にはkube-dnsによるDNSサービスが使われているっぽいので、/etc/hostsに余計な事書いたのがいけなかったと思った。

ので、/etc/hostsからk8s-masterとk8s-nodeのエントリを削除してから、kubeadm initからやり直してみた。

結果

したらちゃんと動いた。

VMのホストでkubectl proxyして、

C:\Users\kaitoy\Desktop>kubectl proxy
Starting to serve on 127.0.0.1:8001

http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/にブラウザでつないだらサインイン画面が表示された。

dashboard


Dashboardのサインイン処理はKubernetes(というかkube-apiserver)のそれに移譲している。 Dashboardはそこで認証されたユーザでクラスタのリソースにアクセスし、情報を取得して表示する。多分。

Dashboardへのサインイン方法はいくつかあるが、それらを理解するにはKubernetesのアクセス制御について学ぶことを推奨とあったのでちょっとKubernetesのドキュメントを読んだ。

Kubernetesのアクセス制御

Kubernetesクラスタのエンドポイントはkube-apiserverであり、クラスタのリソースへのアクセス制御もkube-apiserverがやる。 クライアントとkube-apiserverとのTLSセッションが確立した後、HTTP層のデータを見てアクセス制御をするんだけど、その処理はAuthentication(認証)、Authorization(認可)、Admission(許可)の三段階からなる。

Authentication

第一段階がAuthentication。 ここでは、kube-apiserverに仕込まれたAuthenticatorモジュールがユーザ認証をする。

Kubernetesが認証するユーザには、Kubernetesが管理するService Accountと、クラスタ外部で管理される通常ユーザの二通りがある。 Service AccountはPodがkube-apiserverと話すためのユーザで、通常ユーザは主に人がkubectlとかでkube-apiserverと話すためのユーザ。(匿名で話すこともできる。) 前者はServiceAccountオブジェクトで定義されるけど、後者用のオブジェクトはない。

ServiceAccountはNamespaceと関連付き(つまりnamespace毎にユニーク)、Secretに紐づく。 Secretオブジェクトはクレデンシャルのセットを定義し、Podにマウントされる。 ServiceAccountとSecretは、ふつうは自動で作られ、Podに割り当てられる。

kube-apiserverには一つ以上のAuthenticatorモジュールを設定できて、どれかで認証できれば次の段階に進める。 認証失敗するとHTTPステータスコード401が返る。

Authenticatorモジュールには以下のようなものがある。

  • クライアント証明書: X.509のディジタル証明書を使うモジュール。kube-apiserver起動時に--client-ca-fileオプションで証明書ファイルを渡してやると有効になる。証明書のCommon Nameがユーザ名になり、Organizationがグループになる。クライアント側は、その証明書と対応する秘密鍵をクレデンシャルとして指定する。
  • Bearer Token: 無記名トークンを使うモジュール。kube-apiserver起動時に--token-auth-fileオプションでトークン情報を渡してやると有効になる。トークン情報はCSVで、「token,user,uid,"group1,group2,group3"」という形式で書く。クライアント側は、トークン文字列をクレデンシャルとして指定する。
  • ベーシック認証: ユーザ名とパスワードで認証するモジュール。kube-apiserver起動時に--basic-auth-fileオプションでユーザ名とパスワードのリストを渡してやると有効になる。このリストはCSVで、「password,user,uid,"group1,group2,group3"」という形式で書く。クライアント側は、ユーザ名とパスワードをクレデンシャルとして指定する。HTTPクライアントの時はAuthorizationヘッダが使える。
  • Service Account Token: Service Accountを署名付きBearer Tokenで認証するモジュール。デフォルトで有効になる。

このあたり、Qiitaの「kubernetesがサポートする認証方法の全パターンを動かす」という記事をみると理解が深まる。

Authorization

Authenticationをパスすると、クライアントのユーザ(とグループ)が認証され、第二段階のAuthorizationモジュールの処理に移る。 ここでは、リクエストの内容(操作対象、操作種別(メソッド)等)を見て、それがユーザに許されたものなら認可する。 何を許すかは事前にクラスタにポリシーを定義しておく。

kube-apiserver起動時に--authorization-modeオプションで一つ以上のAuthenticatorモジュールを指定できて、どれかで認可されれば次の段階に進める。 さもなくばHTTPステータスコード403が返る。

Authorizationモジュールには以下のようなものがある。

  • Node: kubeletからのリクエストを認可する。
  • ABAC Mode: Attribute-based Access Control。リクエストに含まれる属性とPolicyオブジェクトを比較して、マッチするものがあれば認可。
  • RBAC Mode: Role-Based Access Control。RoleオブジェクトやClusterRoleオブジェクトでロールを作成し、アクセスできるリソースや許可する操作を定義して、RoleBindingオブジェクトやClusterRoleBindingオブジェクトでユーザ名やグループと紐づける。
  • Webhook Mode: リクエストの内容を示すSubjectAccessReviewオブジェクトをシリアライズしたJSONデータをHTTPでPOSTして、そのレスポンスによって認可可否を決める。

Admission Control

Authorizationをパスすると、第三段階のAdmission Controlモジュールの処理に移る。 ここでは、オブジェクトの作成、削除、更新などのリクエストをインターセプトして、オブジェクトの永続化前にそのオブジェクトを確認して、永続化を許可するかを決める。 リクエストされたオブジェクトやそれに関連するオブジェクトを永続化前にいじって、デフォルト値を設定したりもできる。 読み取りリクエストの場合は実行されない。

kube-apiserver起動時に--admission-controlオプションで複数のAdmission Controlモジュールを指定できて、全てが許可しないとリクエストが却下される。

Admission Controlモジュールは色々あるんだけど、Kubernetes 1.6以降では--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSecondsと指定するのが強く推奨されている。 ここで指定しているServiceAccountモジュールは、kube-controller-managerに含まれるServiceAccountControllerとTokenControllerと協調し、Service Account周りの処理を自動化してくれるもの。

ServiceAccountControllerは、各Namespaceにdefaultという名前のService Accountを作る。

ServiceAccountが作成されるとTokenControllerが動き、対応したSecretとトークンを生成して紐づける。

ServiceAccountモジュールは、Podの作成や更新時に動き、以下の処理をする。

  1. PodにServiceAccountが設定されていなければ、defaultを設定する。
  2. Podに設定されたServiceAccountが存在していることを確認し、存在していなければリクエストを却下する。
  3. PodがImagePullSecretsを含んでいなければ、ServiceAccountのImagePullSecretsをPodに追加する。
  4. トークンを含んだVolumeをPodに追加する。
  5. Pod内の各コンテナの/var/run/secrets/kubernetes.io/serviceaccountにそのVolumeをマウントさせる。

DashboardへBearer Tokenでサインイン

Dashboardの話に戻る。 とりあえずBearer Tokenでのサインインを試す。

クラスタにはデフォルトで色んなService Accountが作られていて、異なる権限を持っている。 そのいずれかのSecretのTokenを使ってDashboardへサインインできるらしい。

以下のコマンドでkube-systemというNamespaceのSecretを一覧できる。

C:\Users\kaitoy>kubectl -n kube-system get secret
NAME                                     TYPE                                  DATA      AGE
attachdetach-controller-token-skzmj      kubernetes.io/service-account-token   3         18m
bootstrap-signer-token-mhqfh             kubernetes.io/service-account-token   3         18m
bootstrap-token-2964e0                   bootstrap.kubernetes.io/token         7         18m
certificate-controller-token-fvrgm       kubernetes.io/service-account-token   3         18m
cronjob-controller-token-hmrdm           kubernetes.io/service-account-token   3         18m
daemon-set-controller-token-vqz85        kubernetes.io/service-account-token   3         18m
default-token-h987g                      kubernetes.io/service-account-token   3         18m
deployment-controller-token-86bp9        kubernetes.io/service-account-token   3         18m
disruption-controller-token-6mskg        kubernetes.io/service-account-token   3         18m
endpoint-controller-token-d4wz6          kubernetes.io/service-account-token   3         18m
generic-garbage-collector-token-smfgq    kubernetes.io/service-account-token   3         18m
horizontal-pod-autoscaler-token-wsbn9    kubernetes.io/service-account-token   3         18m
job-controller-token-fttt2               kubernetes.io/service-account-token   3         18m
kube-dns-token-sn5qq                     kubernetes.io/service-account-token   3         18m
kube-proxy-token-w96xd                   kubernetes.io/service-account-token   3         18m
kubernetes-dashboard-certs               Opaque                                2         7m
kubernetes-dashboard-key-holder          Opaque                                2         6m
kubernetes-dashboard-token-gtppc         kubernetes.io/service-account-token   3         7m
namespace-controller-token-5kksd         kubernetes.io/service-account-token   3         18m
node-controller-token-chpwt              kubernetes.io/service-account-token   3         18m
persistent-volume-binder-token-d5x49     kubernetes.io/service-account-token   3         18m
pod-garbage-collector-token-l8sct        kubernetes.io/service-account-token   3         18m
replicaset-controller-token-njjwr        kubernetes.io/service-account-token   3         18m
replication-controller-token-qrr5h       kubernetes.io/service-account-token   3         18m
resourcequota-controller-token-dznjm     kubernetes.io/service-account-token   3         18m
service-account-controller-token-99nh8   kubernetes.io/service-account-token   3         18m
service-controller-token-9cw7k           kubernetes.io/service-account-token   3         18m
statefulset-controller-token-8z8w9       kubernetes.io/service-account-token   3         18m
token-cleaner-token-cxbkc                kubernetes.io/service-account-token   3         18m
ttl-controller-token-k7gh7               kubernetes.io/service-account-token   3         18m
weave-net-token-lqdgm                    kubernetes.io/service-account-token   3         17m

で、適当にそれっぽいSecret、deployment-controller-token-86bp9を選んで、kubectl describeしたらTokenが見れた。 (Dataセクションのtokenのとこ。)

C:\Users\kaitoy>kubectl -n kube-system describe secret deployment-controller-token-86bp9
Name:         deployment-controller-token-86bp9
Namespace:    kube-system
Labels:       <none>
Annotations:  kubernetes.io/service-account.name=deployment-controller
              kubernetes.io/service-account.uid=17fc5207-b627-11e7-9867-000c2938deae

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1025 bytes
namespace:  11 bytes
token:      eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZXBsb3ltZW50LWNvbnRyb2xsZXItdG9rZW4tODZicDkiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVwbG95bWVudC1jb250cm9sbGVyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMTdmYzUyMDctYjYyNy0xMWU3LTk4NjctMDAwYzI5MzhkZWFlIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOmRlcGxveW1lbnQtY29udHJvbGxlciJ9.ZGV9XDd-GQjAwRuLKpdsWL_dTeF0Mr_2gF117OW4BhEuLwPujnsfOuysAQ-DUtNOp1NHKGitlfxjh6fKo4tFsdwLVJWrRK6i4YH1Mm2No7Sheks7IQn1FnwSmr7yCuvjlHD2e4RpZH0wupOFoY7FHntilhOWbXTJzJzi7TozLX02EKbkVGAsvch3LZ6p8jmUH5hr8DdKc4jbmTRp86SOiFS4_-TJ3RtAHCxiioAuKzXm3-rAWdeGLLcKrM2pAFSAGaBNu8MO5BZlAi6h3Xt4x-8-1ZXs4mudtJiECvjB-XIwiwzhpq8wIPZvvQQ-f1khixOyk1RfIXRJhIE5Gqvi8g

サインイン画面でTokenを選択し、 この、eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZXBsb3ltZW50LWNvbnRyb2xsZXItdG9rZW4tODZicDkiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVwbG95bWVudC1jb250cm9sbGVyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMTdmYzUyMDctYjYyNy0xMWU3LTk4NjctMDAwYzI5MzhkZWFlIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOmRlcGxveW1lbnQtY29udHJvbGxlciJ9.ZGV9XDd-GQjAwRuLKpdsWL_dTeF0Mr_2gF117OW4BhEuLwPujnsfOuysAQ-DUtNOp1NHKGitlfxjh6fKo4tFsdwLVJWrRK6i4YH1Mm2No7Sheks7IQn1FnwSmr7yCuvjlHD2e4RpZH0wupOFoY7FHntilhOWbXTJzJzi7TozLX02EKbkVGAsvch3LZ6p8jmUH5hr8DdKc4jbmTRp86SOiFS4_-TJ3RtAHCxiioAuKzXm3-rAWdeGLLcKrM2pAFSAGaBNu8MO5BZlAi6h3Xt4x-8-1ZXs4mudtJiECvjB-XIwiwzhpq8wIPZvvQQ-f1khixOyk1RfIXRJhIE5Gqvi8gを入力したらサインインできて、GoslingsのDeploymentの情報が見れた。

deploy


Podも見れる。

pods


けどServiceは見れない。

service


各画面でオレンジ色のワーニングも出ていて、deployment-controllerユーザで見れる範囲はあまり広くないことが分かる。

DashboardへAdmin権限でサインイン

DashboardのPodのService Accountであるkubernetes-dashboardにAdmin権限を付けてやって、サインイン画面でSKIPを押すとなんでも見れるようになる。セキュリティリスクがあるので本番ではNG設定だけど。

cluster-adminというClusterRoleがあって、これをkubernetes-dashboardにバインドするClusterRoleBindingを作ってやればいい。

ので、以下のようなYAMLファイルを書いて、

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard
  labels:
    k8s-app: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard
  namespace: kube-system

kubectlで投げる。

C:\Users\kaitoy\Desktop>kubectl create -f dashboard-admin.yml
clusterrolebinding "kubernetes-dashboard" created


したらServiceも見えるようになった。

service-admin


ついでにHWリソース情報も見れた。

resources


満足した。