最近k8sのコードリーディングを始めたのでまずは簡単そうなものから始めます。
in-cluster-client-configurationを参考にします。
main.go
READMEのAuthenticating inside the cluster
の部分を読むとmain.go
の一番最初にあるrest.InClusterConfig()
の説明がある。
リンクは真面目に読まなくてもいい気がする。Service Account Tokens.
rest.InClusterConfig()の実装
KUBERNETES_SERVICE_HOST, KUBERNETES_SERVICE_PORT
の環境変数をみて現在クラスタ内ではない場合エラーを出している。
その後対象のパスからトークンを取って返している。これを使ってclient-set
を作る。
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を叩くことになりそう。
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のCoreV1Client構造体が返される。
Pods("")
でrest clientやnamespaceの情報が保持される。
List()
は以下のような実装。
PodList
に定義されたItems
に実際のPodが入っている。
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からifを書ける。
_, err = clientset.CoreV1().Pods("default").Get(context.TODO(), "example-xxxxx", metav1.GetOptions{})
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から使う最初の一歩。書かれてるような実体と補助という関係らしいがイメージはついていない。
実装を追うと大変だが、最後にmain.go
を見てみるとすごくシンプルでスッキリしていると感じる。