client-goでDynamic Clientを使用して複数のマニフェストを適用する
client-goで単体のリソースを作成する場合は以下のように明示的に対象のclientを作成することで行うことができます。
func main() {
...
deployment := &appsv1.Deployment{}
deploymentsClient := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)
result, err := deploymentsClient.Create(context.TODO(), deployment, metav1.CreateOptions{})
}
しかし、このやり方だと以下のような複数の種類リソースがまとまったyamlや、どのような種類を扱うか不明なときには利用するのが難しくなります。
apiVersion: v1 kind: Namespace metadata: name: example --- apiVersion: v1 kind: ServiceAccount metadata: name: example
そこでDynamic Clientを使うと、このような状況でもclient-goでリソースの作成をすることができます。
Dynamic Client
Dynamic Clientを使用して上記のyamlを適用していきます。
Dynamic Clientの初期化
通常のClientを作成するときと同じようにDynamic Clientに必要な変数を初期化します。
import ( "k8s.io/apimachinery/pkg/api/meta" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/restmapper" "k8s.io/client-go/tools/clientcmd" ) var ( client kubernetes.Interface discoveryClient discovery.DiscoveryInterface mapper meta.RESTMapper dynamicClient dynamic.Interface config *rest.Config ) type Dynamic struct { Client dynamic.Interface Discovery discovery.DiscoveryInterface Mapper meta.RESTMapper } func InitK8s() error { conf, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile) if err != nil { return err } config = conf clientset, err := kubernetes.NewForConfig(conf) if err != nil { return err } client = clientset discoveryClient = clientset.Discovery() groupResources, err := restmapper.GetAPIGroupResources(discoveryClient) if err != nil { return err } mapper = restmapper.NewDiscoveryRESTMapper(groupResources) dyn, err := dynamic.NewForConfig(config) if err != nil { return err } dynamicClient = dyn return nil }
ポイントとしては RESTMapper
を作成するときにDynamic Clientを構築するために必要な情報をAPI Serverから取得するため、毎回実行してしまうとサーバーに負荷をかけてしまうので予め作成しておく必要があります。
全体の流れ
k8s.io/apimachinery
に付属する関数を使用することでリソース単位のオブジェクトに分割できます。
分割したリソースごとにClientを構築してマニフェストを適用していきます。
import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/yaml" ) func main() { ... dynamic := Dynamic{} manifest := "..." decoder := yaml.NewYAMLOrJSONDecoder(strings.NewReader(manifest), 100) for { var rawObj runtime.RawExtension if err := decoder.Decode(&rawObj); err != nil { break } obj := &unstructured.Unstructured{} client, err := dynamic.NewClient(rawObj.Raw, obj) res, err := dynamic.Apply(client, obj) ... } }
Dynamic Clientの構築
まずは分割したリソースからclientが適切なAPIにアクセスするための情報をパースして、Dynamic Clientを構築します。
func (d Dynamic) NewClient(data []byte, obj *unstructured.Unstructured) (dynamic.ResourceInterface, error) { dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) _, gvk, err := dec.Decode(data, nil, obj) if err != nil { return nil, err } mapping, err := d.Mapper.RESTMapping(gvk.GroupKind(), gvk.Version) if err != nil { return nil, err } if mapping.Scope.Name() == meta.RESTScopeNameNamespace { if obj.GetNamespace() == "" { obj.SetNamespace(metav1.NamespaceDefault) } return d.Client.Resource(mapping.Resource).Namespace(obj.GetNamespace()), nil } else { return d.Client.Resource(mapping.Resource), nil } }
上記コード gvk
の gvk.String()
を出力すると apps/v1, Kind=DaemonSet
のような情報が出力されます。
これがDynamic Clientを構築するために必要な情報です。
この情報をもとに先程作成した RESTMapper
を経由してAPI情報を取得してDynamic Clientを構築します。
Create
Dynamic Clientの構築が完了したので、リソースを作成します。
kubectl create
相当のものはこのようになります。
func (d Dynamic) Create(ctx context.Context, client dynamic.ResourceInterface, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { return client.Create(ctx, obj, metaV1.CreateOptions{ FieldManager: "example", }) }
また、 kubectl apply
相当のものはこのようになります。
func (d Dynamic) Apply(ctx context.Context, client dynamic.ResourceInterface, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { data, err := json.Marshal(obj) if err != nil { return nil, err } return client.Patch(ctx, obj.GetName(), types.ApplyPatchType, data, metaV1.PatchOptions{ FieldManager: "example", }) }
このようにしてDynamic Clientを構築することで、複数のリソースがまとまったマニフェストをclient-goを使用して適用することができます。