見出し画像

Podの払い出しに kubernetes-client / openshift-client ライブラリを使う

アプリからPodを払い出したいという要求があった。
最初はOpenShiftのAPIを直接呼び出して払い出すのが良いかと思って調べ出していたところ、次のライブラリを教えてもらった。 

開発のオーナーシップを持っているのはあのfabric8なので、信頼感もある。

で、実際に使ってみると、KubernetesやOpenShiftの中にあるコンセプトがクラスになっていて、本当に使いやすいと感じた。
最近使ったライブラリの中で最も洗練されているように感じたし、使い方も割とすぐわかった。

まずライブラリの読み込みは、Gradleの場合はこんな感じ。

implementation 'io.fabric8:openshift-client:4.12.0'

kubernetes-clientも公開されているが、それとは別にopenshift-clientも使われている。
openshift-client側はkubernetes-clientを内側に含んでいるので、もしOpenShiftの操作に使おうとしているならopenshift-clientを使うのが賢明。

* io.fabric8 : openshift-client : 4.12.0 - Official search of Maven Central Repository https://search.maven.org/artifact/io.fabric8/openshift-client/4.12.0/jar

* io.fabric8 : kubernetes-client : 4.12.0 - Official search of Maven Central Repository https://search.maven.org/artifact/io.fabric8/kubernetes-client/4.12.0/jar

肝心の呼び出しについては、一例を示すとこんな感じ。clientは接続が終わったらcloseが必要なのでtry-with-resourcesの形式で呼び出している。

final String masterUrl = "https://YOUR_OPENSHFIT_MASTER_API_ENDPOINT";
final String namespace = "YOUR_OPENSHIFT_NAMESPACE";

OpenShiftConfig config = new OpenShiftConfigBuilder().withMasterUrl(masterUrl).build();

try (OpenShiftClient client = new DefaultOpenShiftClient(config)) {
   ObjectMeta metadata = new ObjectMetaBuilder().withName("MY_POD").withNamespace(namespace).build();

   List<Container> containers = new ArrayList<>();
   final String containerTag = "YOUR_CONTAINER_REPO:VERSION_TAG";

   int containerCounter = 0;
   for (int i = 0; i < 3; i++) {
       final List<String> commands = Arrays.asList("/bin/sh", "-c", "tail -f /dev/null");
       VolumeMount volumeMount = new VolumeMountBuilder().withName("VOLUME_NAME").withMountPath("SOMEWHERE")
               .withSubPath("ANYWHERE" + String.valueOf(i)).build();
       Container container = new ContainerBuilder().withCommand(commands).withImage(containerTag)
               .withImagePullPolicy("IfNotPresent").withName(String.valueOf(containerCounter++))
               .withVolumeMounts(volumeMount).build();
       containers.add(container);
   }

   PersistentVolumeClaimVolumeSource pvcSource = new PersistentVolumeClaimVolumeSourceBuilder()
           .withClaimName("YOUR_PVC").build();
   Volume volume = new VolumeBuilder().withName("VOLUME_NAME").withPersistentVolumeClaim(pvcSource).build();
   PodSpec spec = new PodSpecBuilder().withContainers(containers).withHostname("MY_HOST")
           .withServiceAccount("MYSA").withServiceAccountName("MYSA").withVolumes(volume)
           .withRestartPolicy("OnFailure").build();

   Pod myPod = new PodBuilder().withApiVersion("v1").withKind("Pod").withMetadata(metadata).withSpec(spec)
           .build();

   client.resource(myPod).createOrReplace();
} catch (Exception e) {
   e.printStackTrace();
}

最後のcreateOrReplaceの箇所で実際にOpenShiftに対して指示を出している。
今回払い出したのはワンショットのバッチ処理みたいなものなので、RestartPolicyをOnFailureにしている。
また、サニタイズの関係で一部は実際にはやらないだろう、といった例に置き換えている部分もあるし、これからリファクタリング予定のものをリファクタリング前の状態でここに書いているので少し荒い部分があるのも承知してる。

ただ、要はこれだけの内容でいつでもPodがJava経由で払い出せると言うのが本当にすごいと思う。
これで、このJavaアプリをOpenShiftにデプロイした上で、サービスアカウントに対応するトークンを"KUBERNETES_AUTH_TOKEN"という環境変数として差し込んであげれば動く。(註:事前に対象サービスアカウントはoc policy add-role-to-user admin system:serviceaccount:YOUR_OPENSHIFT_NAMESPACE:MYSAする必要あり)

このライブラリを呼び出すコードを書くときに見ていたのは基本的にはoc get pod xxx -o yamlで出力した内容だけ。
それを見て、書かれている順番通りにオブジェクト化して値を埋めていった。一部は入れなくても良い値やOpenShiftが自動で入れてくれる値もあるようで、そのあたりは自分の勉強不足でよくわかっていない。

次のリンク辺りを見て何が必須で何がオプショナルかどういったところは設定の仕様があるのか、など見ていたけれど正直なかなかわからなかった。
ただ、どんな値が設定できるかは、このライブラリの例えばPodBuilderの持つメソッドやプロパティを見ればわかるので、その点でも助かった。

ちなみに、2つめのリンクがPodで指定できる例としては分かりやすかったのだけど、これと全く同じ内容を持つv4系のドキュメントを簡単に探したところでは見つけることができなかった。

それから、初めに試して途中でやめた方法の一つとして、PodのYAMLファイルを用意して、それを読み込ませてPodを払い出す、という方法がある。
GitHubのREADMEには次のようなサンプルコードが記載されている。

Pod refreshed = client.load('/path/to/a/pod.yml').fromServer().get();
Boolean deleted = client.load('/workspace/pod.yml').delete();
LogWatch handle = client.load('/workspace/pod.yml').watchLog(System.out);

もちろんこれがサンプルなのは分かっているのだけど、client.load()をした結果Podクラスにする、というところに少し隔たりを感じた。
ParameterNamespaceListVisitFromServerGetDeleteRecreateWaitApplicable<HasMetadata, Boolean> というクラスがload()からは返ってくる。
これがどうなればPodクラスとして、自分がJavaで書いていたようなクラスに読み込まれるのか、がすぐにはわからず、そもそもそんなことをしている人も多くない印象だったので、その道を行くのはやめた。

感覚的にはJacksonのJSONからクラスに読み込むときに使うObjectMapperクラスくらいの手軽さを想定していたので、どういった思想でこういった応答になっているのかは深掘りしないとわからない点だと思った。

現状だと、ボイラープレート感があるというか、大体決まっている箇所と変動させたい箇所とが、アプリ開発者からはわかっているはずで、そうなれば、決まっている箇所を静的にファイルから読み、残りの更新が必要な箇所を、メソッドを呼び出して更新してあげるのが自然なように思ったのだけど、どうも違うらしい。

こうしたライブラリについて知るときには、ライブラリ開発者が想定している典型的なユースケースと、ライブラリ利用者の大半が実際に利用しているユースケースについて押さえられるとそこから思想や利便性、課題なんかが透けて見えてくる気がしているので、そこまで取り組めると良いのだけど、優先順位の兼ね合いで今はここまで。

いずれにせよ便利なライブラリなのに違いはなさそう。
ユニットテスト向けにモックのKubernetesサーバーも立てれるみたいなので、Testableにするためにもそこは近いうちに挑戦してみようと思う。

posted on: https://blog.tkhm.dev/2020/10/pod-kubernetes-client-openshift-client.html