cdk8sやcdk8s+ではじめるKubernetes

みなさん、こんにちは。horsewinです。

AWS CDK Advent Calendar 2022 15日目の記事となります*1

AWS CDK(以降、CDK)でKubernetesk8s)のYAMLが記述できるライブラリである、AWS CDK for Kubernetes/cdk8sに触れていきます。 CDKの開発者体験でKubernetesマニフェストを作成できるため、YAMLマニフェストを書くのに疲れた人は立ち寄ってみてください。

今回触れること、触れないこと

次の内容について、本エントリで述べていきます。 特に2022年に晴れて一般公開(GA)となったcdk8s+は、CDKらしさである抽象化が実現されているためオススメです。

触れること

触れないこと

では、本編に進んでいきましょう。

cdk8sについて

まずは、cdk8sについて触れていきます。cdk8sを一言で述べると、「CDKでKubernetesマニフェストを記述できるOpen Source Software」となります。 2020年2月にGitHub上でTagが切られ、2020年5月に最初のニュースプレスが出ました。

aws.amazon.com

そして、2021年10月に一般公開(GA)しています。

aws.amazon.com

cdk8sの概念図

cdk8sは、次の図のような概念で構成されています。

  • App
    • cdk8sアプリケーションの最上位要素
    • Chartとの依存関係や各Chartのパラメータを定義
  • Chart
    • 自動生成されるYAMLの単位
    • 複数のConstructsの依存関係を定義
    • Chartの入れ子は別YAMLとして生成されるので注意
  • Constructs

cdk8sのしくみ

実装を見たほうが早いため、次のDeploymentを記述するTypeScriptコードを見てみます。

cdk8sで記述したDeploymentのコード

KubeDeploymentというConstructsを利用してDeploymentを生成しています。 Constructsについては通常のCDKと同様のパラメータを取ります。scope/id/propsの3つのパラメータはCDKを書いていると見慣れるものになりますね。 前述したDeploymentのコードからYAMLを生成すると次のようになります。

cdk8sで生成したKubernetesマニフェスト

このようにcdk8sでは、CDKで対応しているプログラミング言語Kubernetesマニフェストの元となるコードを作成し、最終成果物としてYAMLマニフェストを生成します。 そしてkubectl applyコマンド等を用いて、Kubernetesクラスタマニフェスト定義を適用するという仕組みをとっています*3

cdk8sで生成したKubernetesマニフェストクラスタに適用する流れ

では、少しコード部分を見てみます。第3引数であるPropsに与えられているパラメータに着目してみましょう。 specという要素にDeploymentコンポーネントのパラメータであるレプリカ数やselector定義、Podのテンプレート定義などが記載されています。 Propsで与えている内容を見ると、KubernetesYAMLマニフェストの記述とあまり変わらないと感じるでしょう。 それもそのはずです。cdk8sで書かれたコードとYAMLのリソースの定義は1対1対応しています。 通常のCDKを書いていても、CloudFormationのコードをそのまま書くConstructsがいます。

そう。cdk8sはCDKでKubernetesマニフェストを記述する上でL1 Constructsのように振る舞っているのです。ただし、TypeScriptで記述されているためパラメータ名や変数名、三項演算子なども利用できます。もちろん、IDEの補完もバリバリ効きます。ここは大きなメリットと感じる人もいるでしょう。 しかし、cdk8sの真価はこの先になります。それが「cdk8s+」です。

cdk8s+について

cdk8s+を一言で述べると、「CDKでKubernetesマニフェストを記述できて、いい感じにデフォルト値を補完してくれるツール」となります。 2020年2月にGitHub上でTagが切られ、2020年5月に最初のニュースプレスが出ました。

aws.amazon.com

そして、2022年10月に一般公開(GA)しています。

aws.amazon.com

cdk8s+のしくみ

では、cdk8sと同様にDeploymentのTypeScriptコードを見てみます。

cdk8s+で記述したDeploymentのコード

前述したcdk8sと比較して、指定しているパラメータがかなり少なくなりました。 このDeploymentのコードからYAMLを生成すると次のようになります。

cdk8s+で生成したKubernetesマニフェスト

多くのパラメータが補完されて、Kubernetesマニフェストが生成されていることがわかります。 これはまさに、CDKで抽象化されたConstructsである、L2 Constructsの思想と言えます。 例えばcdk8sでは、Deploymentの対象とするPodを選択するselectorを明確に記述していました。一方、cdk8s+ではselectorの定義をせずとも、自動生成されたlabelを元にしてselectorが設定されたDeploymentが生成されます。 このあたりのいい感じにめんどくさい定義を吸収してくれるのがCDKっぽさがありますね。 他にもsecurityContextがしっかり設定されており、このあたりはCIS for Kubernetesのプラクティスを参考に作られているように感じます。

cdk8sのみではYAMLマニフェストと1対1対応で書いていたコードがかなり簡略化されてかける魅力、それがcdk8s+です。

Pros&Cons

ここまで簡単ながら、cdk8sとcdk8s+について紹介しました。cdk8s、cdk8s+のいいところ、苦手なところについても見ていきます。

cdk8s, cdk8s+のいいところ

まずはcdk8s, cdk8s+のいいところについて触れていきます。3つポイントを記載しています。

プログラマブルマニフェストの作成が可能

ここはすでに言及した箇所でもあります。CDKのメリットの1つは、自分たちが得意または好きなプログラミング言語*4を選択して、Infrastructure as Code(以降、IaC)を記述できる点にあります。 cdk8s等においても本メリットは享受できます。特に静的型付けがあるTypeScript、GolangJavaを使えばYAMLではできない強固な型チェックが可能となります。

さらに、cdk8s+を使えば、デフォルト値をうまく活用した最小限の設定でKubernetesマニフェストのベースとなるコードの記述が可能です。

環境ごとのマニフェストの切り替えも可能

プログラマブルKubernetesマニフェストを作成できる点について、もう少し踏み込んでみます。 例えば、Iacを使う利点として、コードを記述すれば開発環境やステージング、プロダクション環境といった複数環境をかんたんに生成できる点があります。 複数の環境を構築する際、環境特性に応じて設定値を変更する場合があります。開発環境ではコスト削減のためにECSのタスク数を少なく設定し、プロダクション環境では想定したトラフィック数に合わせて大きく設定する等です。

Kubernetesでは、環境ごとにKubernetesマニフェストを切り替えるためには次のようなツールを使うケースがあります。 - kpt - Kustomize

cdk8sでは、cdk8s synthコマンドでKubernetesマニフェスト生成を実施するため、コマンドにわたすパラメータによって環境ごとのKubernetesマニフェストの生成が可能です。 CDKでも同様のアプローチで環境ごとに設定したい値を切り替えるプラクティスがあります。つぎの動画で詳しく説明されていますのでCDKを実践されている方は参考にするとよいかもしれません。

youtu.be

Helmとの連携

HelmとはKubernetes用に構築されたソフトウェアを検索、共有、使用するための方法を指しています。この記事では、ソフトウェアを使用する文脈で使用します。 HelmはDocker Hubのように、パブリックに公開されたKubernetesコンポーネントと理解すると早いかもしれません。 KubernetesではHelmを利用して外部ライブラリからコンポーネントを取得して、自身のKubernetesクラスタに展開することがあります。

Helmリポジトリに公開されているHelmチャートをcdk8sから直接利用できるため、Kubernetesのエコシステムを有効活用可能です。

CDKと連携したデプロイが可能

こちらは良いところとするか非常に悩みました。 CDKで生成または取り込みをしたEKSに対して、cdk8sで作成したコードを直接利用できるという内容です。コードベースで具体的に見てみます。*5

import * as blueprints from "@aws-quickstart/eks-blueprints";
import { App } from "aws-cdk-lib";
import { MyCdk8sChart } from "../lib/k8s/service";
import * as cdk8s from "cdk8s";

const stack = blueprints.EksBlueprint.builder()
  .account(process.env.AWS_ACCOUNT_ID)
  .region("ap-northeast-1")
  .build(new App(), "eks-blueprint");

const myCdk8sChart = new MyCdk8sChart(new cdk8s.App(), "MyCdk8sChart");
stack.getClusterInfo().cluster.addCdk8sChart("my-cdk8s-chart", myCdk8sChart);

CDKの管理下にあるEKSに対して、addCdk8sChartという処理を追記することでcdk8sで記述したKubernetesマニフェストをEKSに紐付けることができます。 Kubernetesクラスタへの反映についても、kubectl applyを実行せず、cdk deployコマンドによって実施できます。内部的には、cdk8sでKubernetesマニフェストを生成→Lambdaでkubectl applyを実行してKubernetesリソースを更新という動きのようです。

ここの説明のみだと、いいこと尽くしに見えます。一方、cdk8sの思想としてはあくまで「Kubernetesマニフェストを生成すること」にフォーカスを置いています。マニフェストを生成した後、Kubernetesクラスタへのデリバリについては別の責務だということです。そこをCDKと連携してマニフェストの生成とデプロイまでを一貫した責務とするのは私としてはモヤつくところがあります。

しかし、CDKの体験の延長でデプロイまで実施できることはよい点の側面もあるため、良いところとして紹介しました。

cdk8s, cdk8s+の苦手なところ

最後にcdk8s, cdk8s+の苦手なところや注意点について2つポイントを述べます。

  • cdk8s単体ではデプロイはできない
  • デフォルト値とコントロールプレーンの設定の衝突

cdk8s単体ではデプロイはできない

ここまで何度か述べていきましたが、cdk8sの思想としてはあくまで「Kubernetesマニフェストを生成すること」にフォーカスしているため、必然とも言えます。 しかし、CDKの体験の良さとしてはIDEサポート以外にもシンプルにデプロイできる、ホットリロードなどもあると考えています。 デプロイの責務がコーディングと明確に分離されていることはメリットである反面、CDKを利用してきた人から見たときはデメリットとなりえることがあると考えました。

しかし、Kubernetesでよく実践されているGitOpsのようなアプローチにはcdk8sの思想は非常にマッチしています。 責務の割り切りや、デプロイ以降はKubernetesの世界観や開発者体験に寄せているのは共感を持てました。

デフォルト値とコントロールプレーンの設定の衝突

こちらは抽象化コンポーネントである、cdk8s+の難しい点です。抽象化されているが故に予想外のオプションが設定されることがあるということです。 すでに本記事で見た通り、cdk8s+は短いコードでリッチなKubernetesマニフェストを生成していました。 L2 Constructsのように、Recommendedなデフォルト値が設定されてKubernetesマニフェストの生成処理がなされたためです。 この挙動についてはcdk8s+の魅力であり、非常によい点です。一方、設定されたデフォルト値がうまくワークしないケースもあります。

Kubernetesには、Admission Controllerという機構があります。 Kubernetesコンポーネントをデプロイするためにはkube-apiserverを介したAPIコールが必要となります。APIコールをする際に3つのフェーズを通過します。

  1. 認証処理
  2. 認可処理
  3. Admission Control(リクエストのインターセプトやリクエストの改変、チェック)

3が今回フォーカスしている点です。3のAdmission Controlのフェーズでは、リクエストをチェックして、特定の設定がされていない場合はAPIを通過させないというガード機構を実現しています。 Kubernetesコントロールプレーン側に備わるAdmission Controllerというガード機構がデフォルト値とうまくマッチしないケースがまれにあります。

具体例について述べます。cdk8s+でPod定義を作成すると、Pod Security Contextの設定(YAMLsecurityContextの部分)が入ります。Pod Security Contextの設定の1つにrunAsNonRootというものがあります。これは、root権限以外でコンテナを実行するという内容です(root権限でコンテナを起動されてしまうと権限奪取による危殆化などのリスクが有るためですね)。しかし、runAsNonRootを指定した場合、明示的なUIDを指定しなければAdmission Controllerのガード機構に防御されることがあります。この場合、runAsUserを指定して、Podを生成することでAdmission Controllerのチェックを通過することができます。

このように、cdk8s+が作成してくれたデフォルト値とKubernetesマニフェストを適用するKubernetesクラスタの設定が衝突する可能性について覚えておくとよいでしょう。

まとめ

以上、cdk8sやcdk8s+について本記事では触れていきました。かんたんにまとめます。

cdk8sはCDKでKubernetesマニフェストを生成可能としたソフトウェアです。CDKのコーディング体験でKubernetesマニフェストの生成が可能となります。 また、Kubernetesマニフェストの生成にフォーカスしているため、AWSのみならずオンプレミスやほかクラウドで動作するKubernetesにも適用ができます。 cdk8sでは、いわゆるL1 ConstructsとしてKubernetesコンポーネントを記述できます。しかし、よりCDKらしくコードをかくためにはcdk8s+を使うと良いでしょう。

cdk8s+はcdk8sが提供しているライブラリです。L2 Constructsとして直感的にKubernetesコンポーネントを記述可能となります。

また、cdk8sとCDKを組み合わせて、CDKでインフラとKubernetes(EKSのみに限る)の集約した管理、デプロイも可能となります。 ただし責務分離を意識して、デプロイはKubernetesのプラクティスを適用することをオススメします。

*1:本記事は、JAWS-UG CDK支部 #4の発表内容を元に作成しています

*2:当方、KubernetesについてはPoCで複数のプロジェクトに携わったレベルとなっております。Kubernetes上で動くワークロードを運用している経験はありません。そのため、本番運用ではこんな悩みがあるので難しいと思う!などのコメントは歓迎です。

*3:実際にKubernetesを運用すると本番環境に直接applyを叩くことはなく、GitOpsのような仕組みをとって、ArgoCD等を利用してクラスタに反映するのが良いと筆者は考えています

*4:2022年12月現在、利用できる言語はTypeScript/Golang/Java/Python

*5:EKS Blueprintを使って生成したEKSとは相性が悪いのであくまでサンプル程度にとどめてください