minikube で作成したクラスタ内の TLS が有効になっている etcd へのアクセス方法

Jan 30, 2019 17:56 · 2761 words · 6 minutes read #kubernetes #etcd

TL;DR

TLS が無効になっている etcd へのアクセス方法

$ kubectl exec --namespace kube-system etcd-minikube etcdctl cluster-health

TLS が有効になっている etcd へのアクセス方法

$ kubectl exec --namespace kube-system etcd-minikube -- \
    etcdctl \
        --endpoints https://127.0.0.1:2379 \
        --ca-file /var/lib/minikube/certs/etcd/ca.crt \
        --key-file /var/lib/minikube/certs/etcd/healthcheck-client.key \
        --cert-file /var/lib/minikube/certs/etcd/healthcheck-client.crt \
        cluster-health

etcd v3 に格納されたデータの確認方法

# etcd が稼働している Pod に接続
$ kubectl exec --namespace kube-system -it etcd-minikube ash

# key の一覧を取得
$ ETCDCTL_API=3 etcdctl \
    --endpoints https://127.0.0.1:2379 \
    --cacert /var/lib/minikube/certs/etcd/ca.crt \
    --key /var/lib/minikube/certs/etcd/healthcheck-client.key \
    --cert /var/lib/minikube/certs/etcd/healthcheck-client.crt \
    get / --prefix --keys-only \
    | sed '/^\s*$/d'

# key を元に value を取得
$ ETCDCTL_API=3 etcdctl \
    --endpoints https://127.0.0.1:2379 \
    --cacert /var/lib/minikube/certs/etcd/ca.crt \
    --key /var/lib/minikube/certs/etcd/healthcheck-client.key \
    --cert /var/lib/minikube/certs/etcd/healthcheck-client.crt \
    get -w fields /registry/apiregistration.k8s.io/apiservices/v1

はじめに

minikube を使ってクラスタを起動する際に、指定する Kubernetes のバージョンによって、クラスタ内で稼働する etcd の TLS が有効になるケース(新しいバージョンは基本的に TLS 有効)があります。TLS の 無効/有効 に応じて etcd へのアクセス方法が異なるため、今回は TLS の 無効/有効 に応じた etcd へのアクセス方法を紹介します。

指定する Kubernetes のバージョンによって etcd の TLS 無効/有効 が異なる例

例1: TLS が無効になっているケース( Kubernetes v1.8.0 )

minikube で起動した VM 内に配置されている etcd の manifest を見ると、command で列挙されている etcd コマンドに TLS を有効にするのに必要なパラメータ が渡っておらず、TLS が無効になっていることがわかります。

$ minikube start --kubernetes-version=v1.8.0
$ minikube ssh sudo cat /etc/kubernetes/manifests/etcd.yaml

/etc/kubernetes/manifests/etcd.yaml の内容

apiVersion: v1
kind: Pod
metadata:
  annotations:
    scheduler.alpha.kubernetes.io/critical-pod: ""
  creationTimestamp: null
  labels:
    component: etcd
    tier: control-plane
  name: etcd
  namespace: kube-system
spec:
  containers:
  - command:
    - etcd
    - --listen-client-urls=http://127.0.0.1:2379
    - --advertise-client-urls=http://127.0.0.1:2379
    - --data-dir=/data/minikube
    image: gcr.io/google_containers/etcd-amd64:3.0.17
    livenessProbe:
      failureThreshold: 8
      httpGet:
        host: 127.0.0.1
        path: /health
        port: 2379
        scheme: HTTP
      initialDelaySeconds: 15
      timeoutSeconds: 15
    name: etcd
    resources: {}
    volumeMounts:
    - mountPath: /data/minikube
      name: etcd
  hostNetwork: true
  volumes:
  - hostPath:
      path: /data/minikube
      type: DirectoryOrCreate
    name: etcd
status: {}

TLS が無効になっているケースでの etcd へのアクセス方法はとてもシンプルです。

まずは etcd が稼働している Pod に接続します。

$ kubectl get pods --namespace kube-system --server-print=false
NAME                               READY   STATUS    RESTARTS   AGE
etcd-minikube                      1/1     Running   0          4m38s
kube-addon-manager-minikube        1/1     Running   0          3m47s
kube-apiserver-minikube            1/1     Running   0          3m21s
kube-controller-manager-minikube   1/1     Running   0          4m36s
kube-dns-545bc4bfd4-4fxrp          3/3     Running   0          4m30s
kube-proxy-nd5k8                   1/1     Running   0          4m30s
kube-scheduler-minikube            1/1     Running   0          3m42s
storage-provisioner                1/1     Running   0          4m29s

$ kubectl exec --namespace kube-system -it etcd-minikube ash

クライアント側の kubectl のバージョンとクラスタ側のバージョンに乖離がある(今回のケースだと kubectl が v1.13.2 でクラスタが v1.8.0 だった)と kubectl get pods がエラーになるため --server-print=false を付与していますが、おまじないだと思って頂いて構いません。当記事のスコープ外なので割愛しますが、興味がある方は kubernetes#65575 を参照ください。

Pod に接続したら、etcdctl コマンドを実行するだけです。特別なオプションは必要ありません。

$ etcdctl cluster-health
member 8e9e05c52164694d is healthy: got healthy result from http://127.0.0.1:2379
cluster is healthy

↑ だとデフォルトで etcd v2 api を使用していますが、etcd v3 api を使用してコマンドを実行したい場合には ETCDCTL_API=3 を付与して実行してください。

$ ETCDCTL_API=3 etcdctl endpoint health
127.0.0.1:2379 is healthy: successfully committed proposal: took = 5.651756ms

Pod に接続したことを確認して etcdctl を実行するという、少し冗長な手順を踏みましたが、もちろん以下のようにワンライナーでのアクセスも可能です。

$ kubectl exec --namespace kube-system etcd-minikube etcdctl cluster-health
member 8e9e05c52164694d is healthy: got healthy result from http://127.0.0.1:2379
cluster is healthy

例2: TLS が有効になっているケース( Kubernetes v1.13.2 )

例1と同じように etcd の manifest を見ると、command で列挙されている etcd コマンドに TLS を有効にするのに必要なパラメータ が渡っており、TLS が有効になっていることがわかります。

$ minikube start --kubernetes-version=v1.13.2
$ minikube ssh sudo cat /etc/kubernetes/manifests/etcd.yaml

/etc/kubernetes/manifests/etcd.yaml の内容

apiVersion: v1
kind: Pod
metadata:
  annotations:
    scheduler.alpha.kubernetes.io/critical-pod: ""
  creationTimestamp: null
  labels:
    component: etcd
    tier: control-plane
  name: etcd
  namespace: kube-system
spec:
  containers:
  - command:
    - etcd
    - --advertise-client-urls=https://192.168.99.108:2379
    - --cert-file=/var/lib/minikube/certs/etcd/server.crt
    - --client-cert-auth=true
    - --data-dir=/data/minikube
    - --initial-advertise-peer-urls=https://192.168.99.108:2380
    - --initial-cluster=minikube=https://192.168.99.108:2380
    - --key-file=/var/lib/minikube/certs/etcd/server.key
    - --listen-client-urls=https://127.0.0.1:2379,https://192.168.99.108:2379
    - --listen-peer-urls=https://192.168.99.108:2380
    - --name=minikube
    - --peer-cert-file=/var/lib/minikube/certs/etcd/peer.crt
    - --peer-client-cert-auth=true
    - --peer-key-file=/var/lib/minikube/certs/etcd/peer.key
    - --peer-trusted-ca-file=/var/lib/minikube/certs/etcd/ca.crt
    - --snapshot-count=10000
    - --trusted-ca-file=/var/lib/minikube/certs/etcd/ca.crt
    image: k8s.gcr.io/etcd:3.2.24
    imagePullPolicy: IfNotPresent
    livenessProbe:
      exec:
        command:
        - /bin/sh
        - -ec
        - ETCDCTL_API=3 etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/var/lib/minikube/certs//etcd/ca.crt
          --cert=/var/lib/minikube/certs//etcd/healthcheck-client.crt --key=/var/lib/minikube/certs//etcd/healthcheck-client.key
          get foo
      failureThreshold: 8
      initialDelaySeconds: 15
      timeoutSeconds: 15
    name: etcd
    resources: {}
    volumeMounts:
    - mountPath: /data/minikube
      name: etcd-data
    - mountPath: /var/lib/minikube/certs//etcd
      name: etcd-certs
  hostNetwork: true
  priorityClassName: system-cluster-critical
  volumes:
  - hostPath:
      path: /var/lib/minikube/certs//etcd
      type: DirectoryOrCreate
    name: etcd-certs
  - hostPath:
      path: /data/minikube
      type: DirectoryOrCreate
    name: etcd-data
status: {}

TLS が有効になっているので、例1と同じ方法では etcd にアクセスすることができません。

$ kubectl get pods --namespace kube-system
NAME                               READY   STATUS    RESTARTS   AGE
coredns-86c58d9df4-58g5x           1/1     Running   0          9m12s
coredns-86c58d9df4-8kkvt           1/1     Running   0          9m12s
etcd-minikube                      1/1     Running   0          8m25s
kube-addon-manager-minikube        1/1     Running   0          8m13s
kube-apiserver-minikube            1/1     Running   0          8m32s
kube-controller-manager-minikube   1/1     Running   0          8m30s
kube-proxy-59npq                   1/1     Running   0          9m12s
kube-scheduler-minikube            1/1     Running   0          8m21s
storage-provisioner                1/1     Running   0          9m9s

$ kubectl exec --namespace kube-system -it etcd-minikube ash

$ etcdctl cluster-health
cluster may be unhealthy: failed to list members
Error:  client: etcd cluster is unavailable or misconfigured; error #0: malformed HTTP response "\x15\x03\x01\x00\x02\x02"
; error #1: dial tcp 127.0.0.1:4001: getsockopt: connection refused

error #0: malformed HTTP response "\x15\x03\x01\x00\x02\x02"
error #1: dial tcp 127.0.0.1:4001: getsockopt: connection refused

TLS が有効になっているケースでの etcd へのアクセス方法

やり方は単純で etcdctl を実行する際に、TLS で接続するのに必要な証明書や鍵( manifest に記載されているものを指定 )を渡すことでアクセスが可能となります。

まずは Pod に接続する。

$ kubectl exec --namespace kube-system -it etcd-minikube ash

証明書と鍵のペアは、どれを使っても構いませんが今回は healthcheck-client.* を利用していきます。

$ ls -l /var/lib/minikube/certs/etcd
total 32
-rw-r--r--    1 root     root          1017 Jan 30 10:30 ca.crt
-rw-------    1 root     root          1675 Jan 30 10:30 ca.key
-rw-r--r--    1 root     root          1094 Jan 30 10:30 healthcheck-client.crt
-rw-------    1 root     root          1679 Jan 30 10:30 healthcheck-client.key
-rw-r--r--    1 root     root          1131 Jan 30 10:30 peer.crt
-rw-------    1 root     root          1679 Jan 30 10:30 peer.key
-rw-r--r--    1 root     root          1131 Jan 30 10:30 server.crt
-rw-------    1 root     root          1679 Jan 30 10:30 server.key

etcd v2 api を使用する場合は以下。

$ etcdctl \
    --endpoints https://127.0.0.1:2379 \
    --ca-file /var/lib/minikube/certs/etcd/ca.crt \
    --key-file /var/lib/minikube/certs/etcd/healthcheck-client.key \
    --cert-file /var/lib/minikube/certs/etcd/healthcheck-client.crt \
    cluster-health
member 3d406acc885c8ae6 is healthy: got healthy result from https://192.168.99.108:2379
cluster is healthy

etcd v3 api を使用する場合は以下。
バージョンが異なるので渡すパラメータ名も微妙に異なることに注意してください。

$ ETCDCTL_API=3 etcdctl \
    --endpoints https://127.0.0.1:2379 \
    --cacert /var/lib/minikube/certs/etcd/ca.crt \
    --key /var/lib/minikube/certs/etcd/healthcheck-client.key \
    --cert /var/lib/minikube/certs/etcd/healthcheck-client.crt \
    endpoint health
https://127.0.0.1:2379 is healthy: successfully committed proposal: took = 1.348576ms

ワンライナーで実行したいなら以下となります。

$ kubectl exec --namespace kube-system etcd-minikube -- \
    etcdctl \
        --endpoints https://127.0.0.1:2379 \
        --ca-file /var/lib/minikube/certs/etcd/ca.crt \
        --key-file /var/lib/minikube/certs/etcd/healthcheck-client.key \
        --cert-file /var/lib/minikube/certs/etcd/healthcheck-client.crt \
        cluster-health
member 3d406acc885c8ae6 is healthy: got healthy result from https://192.168.99.108:2379
cluster is healthy

etcd v3 に格納されたデータの確認方法( 2019-02-04 追記 )

当記事を作成後に自分がハマったことを追記という形で書きなぐっておきます。

etcd v3 内のデータを確認するために先述の方法を試みたところ、想定通りの結果を得ることができないことに気付きました。以下のコマンドで何も出力されない事象が発生…orz

$ kubectl exec --namespace kube-system etcd-minikube -- etcdctl --version
etcdctl version: 3.2.24
API version: 2

$ kubectl exec --namespace kube-system etcd-minikube -- \
    etcdctl \
        --endpoints https://127.0.0.1:2379 \
        --ca-file /var/lib/minikube/certs/etcd/ca.crt \
        --key-file /var/lib/minikube/certs/etcd/healthcheck-client.key \
        --cert-file /var/lib/minikube/certs/etcd/healthcheck-client.crt \
        ls --recursive

困ったので調べたところ、以下の Issue/Blog に辿り着きました。

これらの Issue/Blog を確認してみると、どうやら etcd は v2 と v3 でデータ構造が変更になったようで、v3 ではディレクトリ構造を取っていないため etcdctl ls でデータが確認できなかったというオチでした。そもそも etcd v3 に対して etcd v2 api でアクセスするのは避けた方が良かったと反省しました…。

なのでここから etcd v3 api を使う方法をまとめていきます。

まずは Pod に接続します。

$ kubectl exec --namespace kube-system -it etcd-minikube ash

etcd v3 api を使うと格納されたデータを確認することができます。

$ ETCDCTL_API=3 etcdctl \
    --endpoints https://127.0.0.1:2379 \
    --cacert /var/lib/minikube/certs/etcd/ca.crt \
    --key /var/lib/minikube/certs/etcd/healthcheck-client.key \
    --cert /var/lib/minikube/certs/etcd/healthcheck-client.crt \
    get / --prefix --keys-only
/registry/apiregistration.k8s.io/apiservices/v1.

/registry/apiregistration.k8s.io/apiservices/v1.apps

/registry/apiregistration.k8s.io/apiservices/v1.authentication.k8s.io

...略

改行が煩わしければ、sed で改行を削除しましょう。

$ ETCDCTL_API=3 etcdctl \
    --endpoints https://127.0.0.1:2379 \
    --cacert /var/lib/minikube/certs/etcd/ca.crt \
    --key /var/lib/minikube/certs/etcd/healthcheck-client.key \
    --cert /var/lib/minikube/certs/etcd/healthcheck-client.crt \
    get / --prefix --keys-only \
    | sed '/^\s*$/d'
/registry/apiregistration.k8s.io/apiservices/v1.
/registry/apiregistration.k8s.io/apiservices/v1.apps
/registry/apiregistration.k8s.io/apiservices/v1.authentication.k8s.io
...略

折角なので、適当なデータの中身を見てみると謎の文字列が出力されました。

$ ETCDCTL_API=3 etcdctl \
    --endpoints https://127.0.0.1:2379 \
    --cacert /var/lib/minikube/certs/etcd/ca.crt \
    --key /var/lib/minikube/certs/etcd/healthcheck-client.key \
    --cert /var/lib/minikube/certs/etcd/healthcheck-client.crt \
    get /registry/services/specs/kube-system/kube-dns
/registry/services/specs/kube-system/kube-dns
k8s

v1Service�
�
kube-dns
        kube-system"*$05915be6-26b3-11e9-bf52-080027f6af9d2����Z
k8s-apkube-dnsZ%
kubernetes.io/cluster-servicetrueZ
kubernetes.io/nameKubeDNSb
...略

etcdctl get のアウトプットフォーマットを指定できる -w オプションで人間が見やすい形で出力してみます。

$ ETCDCTL_API=3 etcdctl \
    --endpoints https://127.0.0.1:2379 \
    --cacert /var/lib/minikube/certs/etcd/ca.crt \
    --key /var/lib/minikube/certs/etcd/healthcheck-client.key \
    --cert /var/lib/minikube/certs/etcd/healthcheck-client.crt \
    get --help \
    | grep '\-w'

  -w, --write-out="simple"			set the output format (fields, json, protobuf, simple, table)

fields, json, protobuf, simple, table がサポートされているようなので、今回は fields でデータを出力してみると、想定通りデータを確認することができました。

$ ETCDCTL_API=3 etcdctl \
    --endpoints https://127.0.0.1:2379 \
    --cacert /var/lib/minikube/certs/etcd/ca.crt \
    --key /var/lib/minikube/certs/etcd/healthcheck-client.key \
    --cert /var/lib/minikube/certs/etcd/healthcheck-client.crt \
    get -w fields /registry/services/specs/kube-system/kube-dns

"ClusterID" : 8171695754847566219
"MemberID" : 1666036459694192939
"Revision" : 221802
"RaftTerm" : 2
"Key" : "/registry/services/specs/kube-system/kube-dns"
"CreateRevision" : 193
"ModRevision" : 193
...略

地味にハマってたので原因が判明して本当に良かったです。おしまい。

さいごに

プチハマりして備忘録としてまとめたものが誰かの役に立てたら幸いです。

参考資料