client-goを使ってkube configを取得してみる

最近k8sのコードリーディングを始めたのでまずは簡単そうなものから始めます。
[in-cluster-client-configuration](https://github.com/kubernetes/client-go/tree/master/examples/in-cluster-client-configuration)を参考にします。

# main.go
READMEの`Authenticating inside the cluster`の部分を読むと`main.go`の一番最初にある`rest.InClusterConfig()`の説明がある。
リンクは真面目に読まなくてもいい気がする。[Service Account Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#service-account-tokens).

## rest.InClusterConfig()の実装
`KUBERNETES_SERVICE_HOST, KUBERNETES_SERVICE_PORT`の環境変数をみて現在クラスタ内ではない場合エラーを出している。
その後対象のパスからトークンを取って返している。これを使って`client-set`を作る。
```go
func InClusterConfig() (*Config, error) {
	const (
		tokenFile  = "/var/run/secrets/kubernetes.io/serviceaccount/token"
		rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
	)
	host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
	if len(host) == 0 || len(port) == 0 {
		return nil, ErrNotInCluster
	}

	token, err := os.ReadFile(tokenFile)
	if err != nil {
		return nil, err
	}
// ~~~~省略
	return &Config{
		// TODO: switch to using cluster DNS.
		Host:            "https://" + net.JoinHostPort(host, port),
		TLSClientConfig: tlsClientConfig,
		BearerToken:     string(token),
		BearerTokenFile: tokenFile,
	}, nil
}
```

## client-setの作成
client-goの`kubernetes.NewForConfig()`と先ほどのトークンを使い作成される。

### NewForConfig()の実装
`NewForConfigAndClient`関数でClientset構造体を作っている(省略)。
Clientset構造体には`corev1`などのよくみるものが定義されていて、`k8s.io/client-go/kubernetes/typed`あたりで定義されている。


このディレクトリのcorev1の下には`pod.go, service.go`などがあり、このClientset構造体から各種APIを叩くことになりそう。
```go
func NewForConfig(c *rest.Config) (*Clientset, error) {
	configShallowCopy := *c

	// share the transport between all clients
	httpClient, err := rest.HTTPClientFor(&configShallowCopy)
	if err != nil {
		return nil, err
	}

	return NewForConfigAndClient(&configShallowCopy, httpClient)
}
```
## forループ
主に全てのnamespaceのPodをリストする例とエラーハンドリングの例がある。

### Podのリスト
`clientset.CoreV1()`が呼ばれると、[core_client.go](https://github.com/kubernetes/client-go/blob/master/kubernetes/typed/core/v1/core_client.go)のCoreV1Client構造体が返される。
`Pods("")`でrest clientやnamespaceの情報が保持される。
`List()`は以下のような実装。
`PodList`に定義された`Items`に実際のPodが入っている。
```go
func (c *pods) List(ctx context.Context, opts metav1.ListOptions) (result *v1.PodList, err error) {
	var timeout time.Duration
	if opts.TimeoutSeconds != nil {
		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
	}
	result = &v1.PodList{}
	err = c.client.Get().
		Namespace(c.ns).
		Resource("pods").
		VersionedParams(&opts, scheme.ParameterCodec).
		Timeout(timeout).
		Do(ctx).
		Into(result)
	return
}
```
### エラーハンドリング
Getで存在しないPodを取得しようとした場合に、apimachineryの[errors.go](https://github.com/kubernetes/apimachinery/blob/master/pkg/api/errors/errors.go)からifを書ける。
```go                 
_, err = clientset.CoreV1().Pods("default").Get(context.TODO(), "example-xxxxx", metav1.GetOptions{})
```
```go
if errors.IsNotFound(err) {
  fmt.Printf("Pod example-xxxxx not found in default namespace\n")
} else if statusError, isStatus := err.(*errors.StatusError); isStatus {
  fmt.Printf("Error getting pod %v\n", statusError.ErrStatus.Message)
} else if err != nil {
  panic(err.Error())
} else {
  fmt.Printf("Found example-xxxxx pod in default namespace\n")
}
```

# 所感
serviceaccountのtoken辺りをふんわり知れた。
clientsetを作らないことには始まらない印象を持った。
apimachinery, api, client-goを行ったり来たりして迷った。
両者の違いは[Kubernetes APIをGoから使う最初の一歩](https://zenn.dev/castaneai/articles/k8s-go-client-first-step)。書かれてるような実体と補助という関係らしいがイメージはついていない。

実装を追うと大変だが、最後に`main.go`を見てみるとすごくシンプルでスッキリしていると感じる。

# 参考
- https://github.com/kubernetes/client-go/tree/master/examples/in-cluster-client-configuration
- https://github.com/kubernetes/kubernetes
- https://github.com/kubernetes/api/tree/master
- https://github.com/kubernetes/apimachinery/tree/master