AWSのKubernetes(EKS)をJavaで構築する

f:id:hamo2020:20201218092826p:plain

マネックス証券 システム開発部のHです。

はじめに

先日GKE(Google Kubernetes Engine)のトレーニングを受講して、久しぶりにKubernetesに触りました。 忘れないうちにもう少し触っておきたいのと、最近AWSのCDK(AWS クラウド開発キット)に興味があるので、今回はCDKでAWSのKubernetes(EKS)を構築してみようと思います。

キーワードの説明

今回KubernetesやCDKといったキーワードが出てきます。クラウド開発している人であればKubernetes(K8s)は一般的な用語であり、改めて説明する必要はないかもしれませんが簡単に説明を書いておきます。CDKはどれくらいの人が利用しているんですかね・・・。

  • Kubernetes(K8sと略すこともあります)は、デプロイやスケーリングを自動化したり、コンテナ化されたアプリケーションを管理したりするための、オープンソースのシステムです。(公式サイトから引用)
    Amazon EKS(Amazon Elastic Kubernetes Service)は、AWS上で動くKubernetesです。Google Cloud Platform(GCP)であればGKE(Google Kubernetes Engine)、Microsoft AzureであればAzure Kubernetes Service (AKS)となります。
    コンテナ上で動作するある機能の新しいバージョンをリリースする際に、コンテナが多いと個別にリリースするとなると1個1個リリースしたり、もしくはリリース用のスクリプトを作ったりとそれなりに手間がかかりますが、Kubernetesを使用するとその手間を軽減することができます。
    またスケーリングについては、負荷に応じてスケールアウトやスケールインを自動でしてくれますが、ECS(Elastic Container Service)でもできるので、コンテナ数が少なければ無理にEKSを使用しなくてもいいような気はします。

  • AWS Cloud Development Kit (AWS CDK) は、使い慣れたプログラミング言語を使用してクラウドアプリケーションリソースを定義するためのオープンソースのソフトウェア開発フレームワークです。(公式サイトから引用)
    AWSの環境は、CloudformationやTerraformのように定義ファイルにAWSのリソースを定義して環境を構築する方法がありますが、CDKではクラウドのリソースをプログラム内で定義します。対応している言語は、TypeScript、JavaScript、Python、Java、C#です。
    CDKではクラウドのリソース(VPC、EC2、ECS、EKSなど)をオブジェクトとして定義し、関連するリソースや属性をメソッドチェインによって追加していきます。(Javaの場合)

いざやってみる

CDKは上記の通りTypeScript、JavaScript、Python、Java、C#に対応していますが、今回EKSの構築はJavaでやってみます。

事前準備

  • JDKのインストール
    私はJava11でやります。Java8以上の好きなJDKをインストールしましょう。
  • Node.jsのインストール
    いろんなところに書いてあると思うので、割愛します。
  • AWS CLIのインストール
    いろんなところに書いてあると思うので、割愛します。また、"aws configure"でアカウントの設定もしておきます。CDKの実行には不要なのですが、kubectlを実行する際に作成したEKSクラスタにアクセスするために"aws eks update-kubeconfig ~"を実行するので、そこで必要となります。
  • kubectl
    このコマンドが使えると、デプロイメントやサービス、ポッドの情報などが見れるので便利です。createやapplyについては、相当するものをJavaで実装するので、今回は使用しません。
  • CDK
    これも探せばいろんなところに書いてありますが・・
    私はWindowsを使用しているので、DOSプロンプト(古い!)から以下のコマンドをたたきます。
npm install -g aws-cdk

その後

cdk bootstrap

でS3にCloudFormationで使用するバケットを作成します。

プログラム実装

CDKでは

cdk init [Javaプロジェクト名] --language java

と実行するとMavenプロジェクトを作成してくれます。ですが私は最近Gradleばかり使用しているので、今回は普通にEclipseから新規Gradleプロジェクトを作成します。Gradleの実行については、Gradleラッパーを使用します。

build.gradle

CDKを利用するにあたり、以下を追加します。

  • plugin
    "gradlew run"を使用するため、以下を設定します。

    • application
  • dependencies
    以下を追加します。

    • software.amazon.awscdk:core
    • software.amazon.awscdk:ec2
    • software.amazon.awscdk:eks
cdk.json

CDKで使用するファイルです。

{
  "app": "gradlew run"
}
Javaの実装

CDK(Java版)では、AWSのリソースをJavaオブジェクトとして定義します。メソッドチェーンで属性を設定していきます。
今回は以下の構成でEKSを作成します。実装はAPIリファレンスを参考にしながら行いました。

  • EKSの構成

    • AWS Fargateで作成します。
    • VPCを新規に作成し、その下にアベイラビリティゾーン(a、c)ごとにパブリックサブネットとプライベートサブネットを1つずつ作成します。
    • コンテナ上で動作するアプリはnginx(バージョン1.19.5)とします。
    • レプリカ数は3とします。
    • サービスのタイプはロードバランサーで、NLB(インスタンスタイプ)を指定します。
      ※Fargateでは、ロードバランサーはNLB(IPターゲットタイプ)かALB(Application Load Balancer)が対応で、CLB(Classic Load Balancer)やNLB(インスタンスタイプ)は対象外なのですが、この構成で動いちゃったのとNLB IPターゲットやALBで構成するためにはAWS Load Balancer Controllerが必要で、やや難易度が上がるため今回は見送りました。
  • VPCの実装例

Vpc vpc = Vpc.Builder.create(this, "VpcEks")
        .cidr("10.0.0.0/16")
        .enableDnsHostnames(true)
        .enableDnsSupport(true)
        .maxAzs(2)
        .subnetConfiguration(
            Arrays.asList(
                new SubnetConfiguration.Builder() // パブリックサブネット
                    .name("EksPublic")
                    .subnetType(SubnetType.PUBLIC)
                    .reserved(false)
                    .build(),
                new SubnetConfiguration.Builder() // プライベートサブネット
                    .name("EksPrivate")
                    .subnetType(SubnetType.PRIVATE)
                    .reserved(false)
                    .build()))
        .build();
  • EKSクラスターとノードグループの実装例
    createDeploymentDef()はデプロイメント定義、createServiceDef()はサービス定義で後述します。
// EKSクラスタの作成
FargateCluster cluster = FargateCluster.Builder.create(this, "EksCluster")
        .vpc(vpc)
        .version(KubernetesVersion.V1_18)
        .outputMastersRoleArn(true)
        .build();

// ノードグループ設定
cluster.addNodegroupCapacity("ng-sample", new NodegroupOptions.Builder()
    .minSize(1)
    .maxSize(3)
    .build());
cluster.addManifest("EksMf", createDeploymentDef(), createServiceDef());
  • デプロイメント定義
    デプロイメントやサービスの定義について、DeploymentやServiceみたいなクラスがあるかと思ったら・・・ありませんでした。通常YAMLファイルで定義するものをMap<String, Object>で定義して、上記にある通りクラスタにaddManifest()で追加します。
private Map<String, Object> createDeploymentDef() {
    Map<String, Object> map = Map.of(
        "apiVersion", "apps/v1",
        "kind", "Deployment",
        "metadata", Map.of(
            "name", "nginx-deployment",
            "labels", Map.of(
                "app", "nginx")),
        "spec", Map.of(
            "replicas", 3,
            "selector", Map.of(
                "matchLabels", Map.of(
                    "app", "nginx")),
                "template", Map.of(
                    "metadata", Map.of(
                        "labels", Map.of(
                            "app", "nginx")),
                    "spec", Map.of(
                        "containers", Arrays.asList(Map.of(
                            "name", "nginx",
                            "image", "nginx:1.19.5",
                            "ports", Arrays.asList(Map.of(
                                "containerPort", 80))))))));

    return map;
}
  • サービス定義
    サービス定義もデプロイメント定義と同様Map<String, Object>で定義して、addManifest()で追加します。
private Map<String, Object> createServiceDef() {
    Map<String, Object> map = Map.of(
        "apiVersion", "v1",
        "kind", "Service",
        "metadata", Map.of(
            "name", "nginx-service",
            "annotations", Map.of(
                "service.beta.kubernetes.io/aws-load-balancer-type", "nlb")), // ロードバランサーはNLB(インスタンスタイプ)で定義する
        "spec", Map.of(
            "type", "LoadBalancer",
            "ports", Arrays.asList(Map.of(
                "protocol", "TCP",
                "port", 80,
                "targetPort", 80)),
            "selector", Map.of(
                "app", "nginx")));

    return map;
}

実行(AWSにデプロイ)

cdk synth

とDOSプロンプトで実行するとCloudFormationのテンプレートを表示します。(このコマンドではAWSへはデプロイしません) こんな感じです。個人的にはこれを細かく見るのは苦痛ですね・・・
ちなみに作成されるリソースにはユニークIDなどが付与されます。

f:id:hamo2020:20201215114642p:plain

cdk deploy

を実行すると、AWS CLIで設定されているアカウントへデプロイします。--profileオプションを使えば別のアカウントで実行することもできます。 下記のような画面が表示され、yを入力するとデプロイが開始します。 大体20~30分くらいでデプロイが完了します。

f:id:hamo2020:20201215115313p:plain

f:id:hamo2020:20201215115355p:plain

デプロイ中はこんな表示がDOSプロントに出力されます。また、Cloudformationのスタック上でも確認できます。

f:id:hamo2020:20201215115612p:plain

デプロイが完了すると下記のような表示が出ます。

f:id:hamo2020:20201215120133p:plain

デプロイ完了時に表示される

aws eks update-kubeconfig --name ~

をコピーして実行することで、kubectlでEKSの情報を参照できるようになります。

確認

  • kubectlコマンドを実行すると、デプロイメントやサービス、ポッドの情報が確認できます。

    f:id:hamo2020:20201217120058p:plain

  • AWSコンソール画面
    AWSのコンソール画面でクラスターが作成されていることが確認できます。

    f:id:hamo2020:20201215121235p:plain
    f:id:hamo2020:20201215121357p:plain

  • nginxの表示 サービスの外部IPに対し、ブラウザからアクセスすると、nginxの画面が表示されます。

    f:id:hamo2020:20201215121720p:plain

おわりに

今回AWSのKubernetes(EKS)を、CDKを利用しJavaで構築するということを行いました。
Googleで事例を検索しても、CDKでJavaを使用している例もCDKでデプロイまでやっているEKSの事例も少なく、APIリファレンスとにらめっこしながら実装しましたが、何とかとりあえず動くものは作成できました。今回できなかったロードバランサーをNLB(IPターゲット)にしたり、ALBに変更するところは今後機会があればやってみたいと思います。

f:id:hamo2020:20200830084302p:plain