istioctl kube-inject コマンドについて

# 概要
`istioctl kube-inject` コマンドで実際の処理はどうなっているのかコードから追ってみます。
基本的に下記のコードを読み進めていきました。

https://github.com/istio/istio/blob/master/istioctl/cmd/istioctl/kubeinject.go

## kube-injectとは
アプリケーションPodを起動する際に、サイドカーコンテナ（envoy）をinjectするためのコマンドです。これにより、ユーザがいちいちサイドカーコンテナ用のyamlを記述する必要が無くなります。

## 使い方
```sh
kubectl apply -f <(istioctl kube-inject -f <resource.yaml>)
```

## ソースコード

まず最初に、コマンドで指定されたyamlファイルを読み込みます。（`-`の場合は、標準入力からyamlを取得）
※そこまで重要ではないので、さらっと読み飛ばしてください。

```golang
if !emitTemplate {
    if inFilename == "-" {
        reader = os.Stdin
    } else {
        var in *os.File
        if in, err = os.Open(inFilename); err != nil {
            return err
        }
        reader = in
        defer func() {
            if errClose := in.Close(); errClose != nil {
                log.Errorf("Error: close file from %s, %s", inFilename, errClose)

                // don't overwrite the previous error
                if err == nil {
                    err = errClose
                }
            }
        }()
    }
}
```

続いて、出力の設定です。outFilenameが無ければ、標準出力に、ファイル名が指定されていればファイルに出力します。
※そこまで重要ではないので、さらっと読み飛ばしてください。

```golang
var writer io.Writer
if outFilename == "" {
    writer = c.OutOrStdout()
} else {
    var out *os.File
    if out, err = os.Create(outFilename); err != nil {
        return err
    }
    writer = out
    defer func() {
        if errClose := out.Close(); errClose != nil {
            log.Errorf("Error: close file from %s, %s", outFilename, errClose)

            // don't overwrite the previous error
            if err == nil {
                err = errClose
            }
        }
    }()
}
```

さて、この辺りから本題meshConfigを取得する処理です。 引数で`--meshConfigFile`を指定した場合は`cmd.ReadMeshConfig(meshConfigFile)` でファイルから読み込みますが、基本的に指定するケースは稀だと思いますので、`getMeshConfigFromConfigMap(kubeconfig)` を詳しく見ていきます。

```golang
var meshConfig *meshconfig.MeshConfig
if meshConfigFile != "" {
    if meshConfig, err = cmd.ReadMeshConfig(meshConfigFile); err != nil {
        return err
    }
} else {
    if meshConfig, err = getMeshConfigFromConfigMap(kubeconfig); err != nil {
        return err
    }
}
```

こちらでは、meshConfigMapの設定を取得しています。そのyamlの設定を構造体`*meshconfig.MeshConfig`に変換してreturnしています。

```golang
func getMeshConfigFromConfigMap(kubeconfig string) (*meshconfig.MeshConfig, error) {
	client, err := createInterface(kubeconfig)
	if err != nil {
		return nil, err
	}

	config, err := client.CoreV1().ConfigMaps(istioNamespace).Get(meshConfigMapName, metav1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("could not read valid configmap %q from namespace  %q: %v - "+
			"Use --meshConfigFile or re-run kube-inject with `-i <istioSystemNamespace> and ensure valid MeshConfig exists",
			meshConfigMapName, istioNamespace, err)
	}
	// values in the data are strings, while proto might use a
	// different data type.  therefore, we have to get a value by a
	// key
	configYaml, exists := config.Data[configMapKey]
	if !exists {
		return nil, fmt.Errorf("missing configuration map key %q", configMapKey)
	}
	cfg, err := model.ApplyMeshConfigDefaults(configYaml)
	if err != nil {
		err = multierr.Append(fmt.Errorf("istioctl version %s cannot parse mesh config.  Install istioctl from the latest Istio release",
			version.Info.Version), err)
	}
	return cfg, err
}
```

ここで、clientとは、kubernetesのリソースを取得するためのclientです。ポイントは下記コードになるのですが、istioNamespace (defaultはistio-system)のmeshConfigMapName（defaultはmesh）のconfigMapを取得しています。
※ go-clientの詳細が知りたければ[こちら](https://github.com/kubernetes/client-go)のreadmeを読むと良いと思います。

```golang
config, err := client.CoreV1().ConfigMaps(istioNamespace).Get(meshConfigMapName, metav1.GetOptions{})
```

要するに、下記と同等の意味があると考えるとイメージがわくと思います。configmap名がistio、namespaceがistio-systemです。

```sh
$ kubectl get configmaps istio -o yaml -n istio-system
```

meshConfigのあとは、いよいよsidecarTemplateを作ります。適用順序についてですが、まず、`injectConfigFile != ""` で引数に`--injectConfigFile`が指定されているかチェックします。もし指定されていればそちらを優先、指定されていなければ`getInjectConfigFromConfigMap(kubeconfig)`で取得します。`getInjectConfigFromConfigMap(kubeconfig)`の中身は、先ほどの`getMeshConfigFromConfigMap(kubeconfig)`とほぼ一緒なので、割愛します。

```golang
else if injectConfigFile != "" {
    injectionConfig, err := ioutil.ReadFile(injectConfigFile) // nolint: vetshadow
    if err != nil {
        return err
    }
    var config inject.Config
    if err := yaml.Unmarshal(injectionConfig, &config); err != nil {
        return err
    }
    sidecarTemplate = config.Template
} else {
    if sidecarTemplate, err = getInjectConfigFromConfigMap(kubeconfig); err != nil {
        return err
    }
}
```

構造体を作り終えたら最後にyamlをアウトプットして終了となります。

```golang
return inject.IntoResourceFile(sidecarTemplate, meshConfig, reader, writer)
```

## まとめ
kube-injectコマンドのソースコードを見て、コマンドがどんな処理をしているか把握することができました。ほんとこの辺りの処理よく実装したなーと思います。ちなみに、今はauto injectionが主流なので、このコマンドを使うことは少なくなりましたが、autoの場合でも、アプリケーションPodの定義をapplyしたタイミングでapplyのリクエストをhookして上記で見てきたことと同等の処理を実施している（と思われます）。ちゃんと見てないので、気になった方は確認してみてください。

## 参考
https://istio.io/docs/reference/commands/istioctl/