kubernetes REST API に外部から接続する

kubernetes には kubectl というコマンドがあり普段のオペレーションはそちらでやることが多いと思いますが、サーバから kubernetes をコントロールしたいというニーズもあるとも思います。

kubernetes には REST API が用意されているのでそれにアクセスすれば良いんですが、どうやって認証すればいいのか迷ったのでメモを残しておきます。

## 認証

いくつかの[認証方式が用意](https://kubernetes.io/docs/admin/authentication/)されています。

- X509証明書
- BASIC認証
- TOKEN
- Service Account
- OpenID

今回は TOKEN 方式でやってみます。

### トークン取得

トークンや証明書は Pod の中に入って `/var/run/secrets/kubernetes.io/` にあります。手動で取るのはさすがにかったるいので、下記コマンドを叩きます。

```
$(kubectl describe secret $(kubectl get secrets | grep default | cut -f1 -d ' ') \
  | grep -E '^token' | cut -f2 -d':' | tr -d '\t')
```

これでトークンが取得できるはずです。

### APIサーバのIP

次に curl で叩く先のIPアドレスを取得します。以下のコマンドで取得できるんですが、複数のクラスタを設定している場合は複数出てしまうので注意してください。

```
kubectl config view | grep server | cut -f 2- -d ":" | tr -d " "
```

GKE だとコントロールパネルの以下の部分で確認ができます。

![gke.png](https://qiita-image-store.s3.amazonaws.com/0/2152/e8ca47fe-4203-f6c8-9eea-5d643897ae15.png)

## アクセスしてみる

```
TOKEN=$(kubectl describe secret $(kubectl get secrets | \
  grep default | cut -f1 -d ' ') | \
  grep -E '^token' | cut -f2 -d':' | tr -d '\t')

curl https://example.com/api/v1/namespaces/default/pods \
  --header "Authorization: Bearer $TOKEN" --insecure
```

これで Pod の一覧が取得できるはずです。最高ですね。あとは、[REST API のリファレンス](https://kubernetes.io/docs/api-reference/v1.7/)を見て kubernetes と仲良くなってください。なお、バージョンによって使える API と使えない API があるので注意してください。

## kubectl proxy というやり方も

公式ではこちらをオススメしてます。

```
kubectl proxy --port=8080 &
```

```
$ curl http://localhost:8080/api/
{
  "versions": [
    "v1"
  ]
}
```

## クライアントライブラリ

クライアントライブラリも各言語ごとにいくつかあります。

- [Client Libraries](https://kubernetes.io/docs/reference/client-libraries/)

公式では Go と Python ですね。Rubyだと [abonas/kubeclient](https://github.com/abonas/kubeclient) あたりが良さそうです。

### abonas/kubeclient

Rubyのgem `abonas/kubeclient` を使ってAPIにアクセスしてみます。前述したTOKENの取得ができていれば環境変数などにブチ込んで接続するだけです。

```ruby
auth_options = {
  bearer_token: ENV['K8S_API_TOKEN']
}

client = Kubeclient::Client.new(
  "https://#{ENV['K8S_API_HOST']}/api/", 'v1', auth_options: auth_options
)

client.get_pods
```

ただ、これだけだとSSLでエラーが出ると思います。kubernetesは独自の証明書を使っているため、そのままだと接続できないからです。 実際にコマンドを実行しようとすると、 `KubeException: SSL_connect returned=1 errno=0 state=error: certificate verify failed` こんな感じのエラーが出ます。

#### 証明書の設定

`OpenSSL::SSL::VERIFY_NONE` にする手もありますがオススメはしません。

まず、接続用の証明書を作ります。

```sh
openssl genrsa -aes128 2048 > client.key
openssl req -new -key client.key > client.csr
openssl x509 -in client.csr -days 365000 -req -signkey client.key > client.crt
openssl rsa -in client.key -out client.key
```

次にkubernetesの証明書を取得します。Podの中の `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt` にあります。

最後に `abonas/kubeclient` で接続します。

```ruby
auth_options = {
  bearer_token: ENV['K8S_API_TOKEN']
}

ssl_options = {
  client_cert: OpenSSL::X509::Certificate.new(File.read(Rails.root.join("secrets", "k8s_client.crt"))),
  client_key:  OpenSSL::PKey::RSA.new(File.read(Rails.root.join("secrets", "k8s_client.key"))),
  ca_file:     Rails.root.join("secrets", "k8s_ca.crt"),
  verify_ssl:  OpenSSL::SSL::VERIFY_PEER
}

client = Kubeclient::Client.new(
  "https://#{ENV['K8S_API_HOST']}/api/", 'v1',
  auth_options: auth_options,
  ssl_options: ssl_options
)

client.get_pods
```

以上で接続できるようになります。