Kubernetes Sample APIServerの動かし方

# 概要・解説

## Kubernetes Aggregated APIServerとは？

Kubernetesにはシステムコンポーネントとして、kube-apiserverがいますが、それとは別に独自に拡張したAggregated APIServerを配置することができます。

Custom Controllerのサンプルに[Sample Controller](https://github.com/kubernetes/sample-controller.git)があるように、Custom APIServer(Aggregated APIServer)のサンプルとして公式リポジトリに[Sample APIServer](https://github.com/kubernetes/sample-apiserver)があります。

> Sample Controllerの説明は下記で詳しく説明しているので、省略
> https://nextpublishing.jp/book/11389.html

この記事は、そのSample APIServerの動かし方・遊び方についての記事です。

## CRD(+Custom Controller)との違い

CRD + Custom Controllerを使ったOperatorが昨今のKubernetes界隈で流行っていますが、Aggregated APIServerはOperatorと比べると高い自由度を持つことができます。

CRDはある程度決められた枠組みの中で制作・使用することになりますが、APIServerはそれを超えた機能を実現できます。

> Aggregated APIServerを使うことで何ができるのか？ については
> [Programming Kubernetes](https://programming-kubernetes.info/)の第8・9章に詳しくまとまっています

ただ、Aggregated APIServerを使わないと実現できないものはだいぶ限られます。
そのため、Kubernetesを拡張してなにかをしたい！ という場合はほとんどCRD(+Custom Controller)の選択肢が主力になると思います。
実例も少ないため、Aggregated APIServerはマイナーといっていい存在です。

> 少なくとも日本語/英語含めて概要以外の記事が全くないので、この記事を書こうと思いました

# Sample APIServerで遊ぶ

ここから本題です。Sample APIServerを動かす方法は二つあります。

https://github.com/kubernetes/sample-apiserver/blob/master/README.md

 1. KubernetesクラスタにPodとしてSample APIServerをデプロイする
 2. Kubernetesクラスタに対して、etcdとSample APIServerをローカルプロセスとして動かす

この記事は2番目の方法の解説記事です。

## ビルド

前提として、Golangのインストール・設定(Go Module含む)がされているものとします。
筆者の作業環境は下記です。下記のソフトウェアもインストール済みの前提で書いています。

 * macOS 10.14.5
 * Go 1.14.2
 * Minikube v1.11.0
 * etcd 3.3.11

Sample APIServerのソースをビルドします。

```bash
$ mkdir -p ~/go/src/k8s.io
$ git clone https://github.com/kubernetes/sample-apiserver.git
$ CGO_ENABLED=0 go build -a -o artifacts/simple-image/kube-sample-apiserver
```

## 準備する

### Kubernetesクラスタを動かす

Kubernetesの拡張機能なので、当然Kubernetesクラスタが必要です。
今回はMinikubeを使って、ローカルクラスタを作ります。

> [Minikube Get Started!](https://minikube.sigs.k8s.io/docs/start/)

```bash
$ minikube start --driver virtualbox
```

### 証明書を作る

Sample APIServerが利用する自己証明書を作成します。
1. CA証明書

```
$ openssl req -nodes -new -x509 -keyout ca.key -out ca.crt
Generating a 2048 bit RSA private key
...........................................................+++
........................................+++
writing new private key to 'ca.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:JP
State or Province Name (full name) []:Tokyo
Locality Name (eg, city) []:Shibuya
Organization Name (eg, company) []:Kubernetes
Organizational Unit Name (eg, section) []:CA
Common Name (eg, fully qualified host name) []:development
Email Address []:
```

```
$ openssl req -out client.csr -new -newkey rsa:4096 -nodes -keyout client.key -subj "/CN=development/O=system:masters"
Generating a 4096 bit RSA private key
..................++
...........................++
writing new private key to 'client.key'
-----
```

2. Client証明書

```
$ openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
Signature ok
subject=/CN=development/O=system:masters
Getting CA Private Key
```

3. curl用のp12形式

```
$ openssl pkcs12 -export -in ./client.crt -inkey ./client.key -out client.p12 -passout pass:password
```

### Sample APIServerを起動する

etcdもローカルプロセスとして動かすため、インストールが必要です。
次のコマンドでetcdとSample APIServerを起動します。

```
$ etcd & ./artifacts/simple-image/kube-sample-apiserver \
  --secure-port 8443 --etcd-servers http://127.0.0.1:2379 --v=7 \
  --client-ca-file ca.crt --kubeconfig ~/.kube/config \
  --authentication-kubeconfig ~/.kube/config --authorization-kubeconfig ~/.kube/config
```

ログが出てきたら起動完了です。

### APISericeを動かして遊ぶ

このSample APIServerでは`Kind: Flunder`というAPI Objectを扱います。
今回はローカルプロセスとして動かしているので、特に実行はしていませんが、通常は`kind: APIService`を登録してからCustom API Obejectを作成します。

以下は`APIService`の参考です。

```
# https://github.com/kubernetes/sample-apiserver/blob/master/artifacts/example/apiservice.yaml
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1alpha1.wardle.example.com
spec:
  insecureSkipTLSVerify: true
  group: wardle.example.com
  groupPriorityMinimum: 1000
  versionPriority: 15
  service:
    name: api
    namespace: wardle
  version: v1alpha1
```

どんなAPIServiceが他にあるのかを見たい人は、`kubectl get apiservice`で見てみると楽しいかもしれません。

#### httpieインストール

macOSでSample APIServerと疎通するために、brewで`httpie`をインストールします。

```
$ brew install httpie
```

#### httpieでGET

Sample APIServerが動いているか確認するために`httpie`でGETリクエストを投げてみます。
まだ何もAPI Objectを作ってないので、空のリストが返ってきます。

```
$ http --verify=no --cert client.crt --cert-key client.key \
   https://localhost:8443/apis/wardle.example.com/v1alpha1/namespaces/default/flunders
HTTP/1.1 200 OK
Cache-Control: no-cache, private
Content-Length: 187
Content-Type: application/json
Date: Sat, 13 Jun 2020 12:19:43 GMT

{
    "apiVersion": "wardle.example.com/v1alpha1",
    "items": [],
    "kind": "FlunderList",
    "metadata": {
        "resourceVersion": "2",
        "selfLink": "/apis/wardle.example.com/v1alpha1/namespaces/default/flunders"
    }
}
```

Sample APIServerをStandAloneで立てた状態でFlunder Objectを作るには、httpieを使ってPOSTリクエストを送ります。

```
$ echo '{ "apiVersion": "wardle.example.com/v1alpha1", "kind": "Flunder", "metadata": { "name": "my-first-flunder", "labels": { "sample-label": "true" } } }' | http --verify=no --cert client.crt --cert-key client.key https://localhost:8443/apis/wardle.example.com/v1alpha1/namespaces/default/flunders
HTTP/1.1 201 Created
Cache-Control: no-cache, private
Content-Length: 605
Content-Type: application/json
Date: Sat, 13 Jun 2020 12:26:49 GMT

{
    "apiVersion": "wardle.example.com/v1alpha1",
    "kind": "Flunder",
    "metadata": {
        "creationTimestamp": "2020-06-13T12:26:49Z",
        "labels": {
            "sample-label": "true"
        },
        "managedFields": [
            {
                "apiVersion": "wardle.example.com/v1alpha1",
                "fieldsType": "FieldsV1",
                "fieldsV1": {
                    "f:metadata": {
                        "f:labels": {
                            ".": {},
                            "f:sample-label": {}
                        }
                    }
                },
                "manager": "HTTPie",
                "operation": "Update",
                "time": "2020-06-13T12:26:49Z"
            }
        ],
        "name": "my-first-flunder",
        "namespace": "default",
        "resourceVersion": "3",
        "selfLink": "/apis/wardle.example.com/v1alpha1/namespaces/default/flunders/my-first-flunder",
        "uid": "2d323301-1447-4cd4-967d-bd07eac3a49f"
    },
    "spec": {},
    "status": {}
}
```

`my-first-flunder` Objectが作成できました。
あらためてGETすると、上記で作成した`my-first-flunder`が返ってきます。

```
$ http --verify=no --cert client.crt --cert-key client.key \
   https://localhost:8443/apis/wardle.example.com/v1alpha1/namespaces/default/flunders
HTTP/1.1 200 OK
Cache-Control: no-cache, private
Content-Length: 731
Content-Type: application/json
Date: Sat, 13 Jun 2020 12:29:30 GMT

{
    "apiVersion": "wardle.example.com/v1alpha1",
    "items": [
        {
            "metadata": {
                "creationTimestamp": "2020-06-13T12:26:49Z",
                "labels": {
                    "sample-label": "true"
                },
                "managedFields": [
                    {
                        "apiVersion": "wardle.example.com/v1alpha1",
                        "fieldsType": "FieldsV1",
                        "fieldsV1": {
                            "f:metadata": {
                                "f:labels": {
                                    ".": {},
                                    "f:sample-label": {}
                                }
                            }
                        },
                        "manager": "HTTPie",
                        "operation": "Update",
                        "time": "2020-06-13T12:26:49Z"
                    }
                ],
                "name": "my-first-flunder",
                "namespace": "default",
                "resourceVersion": "3",
                "selfLink": "/apis/wardle.example.com/v1alpha1/namespaces/default/flunders/my-first-flunder",
                "uid": "2d323301-1447-4cd4-967d-bd07eac3a49f"
            },
            "spec": {},
            "status": {}
        }
    ],
```

```
$ http --verify=no --cert client.crt --cert-key client.key DELETE https://localhost:8443/apis/wardle.example.com/v1alpha1/namespaces/default/flunders/my-first-flunder
```

このFlunder Obejectが作られるとどうなるの!? と思ったかもしれませんが、特に何も起こりません。
Flunder Objectを作ったり、削除できたりするだけです。

あくまでサンプルということですね。

# 終わりに

以上がSample APIServerを動かすための動かし方・遊び方でした。
さらに詳しく知りたい方は、デバッガーを使ってどういう処理をしているのかを学ぶと良いでしょう(自分もまだやってませんが)。
kube-apiserverの処理をベースに、Aggredated APIServerが実装されているため、システムコンポーネントであるkube-apiserverに対する理解も深まるはずです。

以上