実はCDKにを始めてまだ2ヶ月程度なのですが、CDKの世界感がとても気持ちがよく、のめり込んでしまいました。
インフラ界隈では以下のような変遷がありますが、
- __Script__によるインフラ構築
- IaC (Infrastructure as Code)
- CaD (Configuration as Data)
私はTemplateによるインフラ構築が最終形だとばかり思ってました。
しかし、CloudFormationやk8s manifestなどTemplateの管理やメンテにも以下のような課題があり、限界を感じてきています。
- システムが肥大化するにつれTemplate管理が難しくなってきた
- Templateの引き継ぎで設計意図が伝わらない
CDKとは (個人見解)
[AWS Black Belt Online Seminer]AWS Cloud Development Kit (CDK)によると__「あるべき状態の定義がコードで可能+抽象化」__とあります。
今までCloudFormationやk8sのManifestなどの宣言的Templateにより__Reconciliation loop__を実現してきましたが、Codeにより__Reconciliation loop__を実現するというのがCDKのコンセプトだという認識です。
CDKはIaCやCaDとは一線を画すもので、IaCやCaDの上位概念になるものかと理解してます。
ということは、k8s manifestをCaDで書くにはどうしたらいいのかという疑問が湧いてきましたが、ちゃんとありましたね。CDK for Kubernetes (CDK8s)が。このAdvent Calenderでどなたか記事を書いてくれるのを期待してます。
Infrastructure as Boilerplate
「あるべき状態の定義がコードで可能」
CDKがIaCやCaDの上位概念だとすると、TemplateとCodeの違いはなにかが気になります。
Boilerplate と Template は何が違うのでしょうか?
ここを参考に考えると、CDK-constructはCodeというよりBoilplateなのではないかと。
また、CDKではCustom Construct LibraryをnpmやPyPIに公開し共有可能ということからもBoilerplateのほうがしっくり来ます。
つらつらと書いてしまいましたが私の考えをまとめると・・・
CDKは以下のようになります。
- IaC、CaDの上位概念である。
- Reconciliation loopを実現するBoilPlateである。
こうなるとIaCやCaDのような新しい用語が生まれてほしいなあと。
Infrastructure as BoilerplateConfiguration as Boilerplate
いやあ、私の乏しい発想ではいいものが考えられません。
CDKを作った方にこれを表現する用語を作ってもらいたいものですね。
そうすれば、CDKの世界も一気に広がるのではないでしょうか。
CDK construct library - Python
CDK constructはBoilerplateであると言いましたが、私も流れにのってconstructを公開していきたいと思ってます。(いいものができたらPyPIに公開していきたいと思います。)
また、私はpythonianなのでCDKをPythonで書きたいのすが、巷にあるCDKのサンプルはTypeScriptが主流でPythonは少なく寂しい限りです。なのでいくつかPythonのサンプルをGitHubにUpしてます。
苦情や質問、Issueなどありましたら是非お願いいたします。
1. SSM Parameter Store
CDKでSSM Parameter StoreのParameterのサンプルを見つけられなかったのですが、Documentを調べるとちゃんとありました。
aws_cdk.aws_ssm.StringParameter
aws_cdk.aws_ssm.StringListParameter
こちらを参考に作ったものがGitHub: cdk-ssm-parameter-storeです。
SsmStringParameterクラスは単純なものなのですがconstructのレイヤーで作成しました。
この程度のものでしたらあえてconstructを作る必要はないかもしれませんが、将来のためにもStackにベダ書きしたくないのでconstructのレイヤーを作成しています。
Parameterの作成
String parameter
# Stringパラメータの作成
class SsmStringParameter(Construct):
def __init__(self,
scope: Construct,
id: str,
parameter_name: str,
string_value: str,
) -> None:
super().__init__(scope, id)
aws_ssm.StringParameter(
scope=self,
id=f'{id}-construct',
parameter_name=parameter_name,
string_value=string_value,
type=aws_ssm.ParameterType.STRING,
tier=aws_ssm.ParameterTier.STANDARD,
)
Secure String parameter
aws_ssmには安全のためにSecureStringParameterのconstructがありません(パスワードをCodeに書かないよう配慮されています)。そのため、SecureStringParameterは、CLIなどを使って作成する必要があります。
$ aws ssm put-parameter \
--name "/my-site/token" \
--value "xxxxxxxxx" \
--type "SecureString"
String List parameter
# StringListパラメータの作成
class StringListParameter(Construct):
def __init__(self,
scope: Construct,
id: str,
parameter_name: str,
string_list_value: list,
) -> None:
super().__init__(scope, id)
aws_ssm.StringListParameter(
scope=self,
id=f'{id}-construct',
parameter_name=parameter_name,
string_list_value=string_list_value,
tier=aws_ssm.ParameterTier.ADVANCED
)
Parameter取得
String parameter取得
email = aws_ssm.StringParameter.value_for_string_parameter(
scope=self,
parameter_name='/my-site/alerts-email-dev'
)
Secure String parameter取得
token = aws_ssm.StringParameter.value_for_secure_string_parameter(
scope=self,
parameter_name='/my-site/token',
version=1
)
String List parameter取得
String List parameterを取得して、Outputに文字列出力を試みたのですが、単純には行きませんでした。
environments = aws_ssm.StringListParameter.from_string_list_parameter_name(
scope=self,
id='GetEnvironmentFromSsmParameter',
string_list_parameter_name='/my-site/environments',
)
cdk.CfnOutput(
scope=self,
id='parameter_environments',
value=json.dumps(self.resolve(environments.string_list_value))
)
こちらのGitHub issuesにあるようにaws_cdk . resolve()を使い対応しましたが、以下のように表示するのが限界のようです。
{"Fn::Split": [",", "dev,stg,prod"]}
2. Lambda Layer construct
GitHub: cdk_lambda_layers
__docker-lambda__をつかってLambda Layerを作成しました。
以前はローカルでDockerコンテナをマウントして作成していましたがaws_core.BundlingOptionsを使えば簡単につくれますね。
from aws_cdk import BundlingOptions
from constructs import Construct
from aws_cdk import aws_lambda
class LambdaLayerConstruct(Construct):
def __init__(self,
scope: Construct,
id: str,
layer_version_name: str,
) -> None:
super().__init__(scope, id)
self._layer_version_name = layer_version_name
def lambda_layer(
self,
language: str,
layer_code_path: str) -> aws_lambda.LayerVersion:
lang_path = language
commands = [
'/bin/bash',
'-c',
f"""
pip install -r requirements.txt -t /asset-output/{lang_path} &&
cp -au . /asset-output/{lang_path}
"""
]
bundling = BundlingOptions(
image=aws_lambda.Runtime.PYTHON_3_8.bundling_image,
command=commands
)
code = aws_lambda.Code.from_asset(
path=layer_code_path,
bundling=bundling
)
layer = aws_lambda.LayerVersion(
scope=self,
id='LambdaConstructLayerVersion',
layer_version_name=self._layer_version_name,
code=code
)
return layer
3. EKS Container Stack
Github: cdk-eks-vpc
このCDK appはVPC、EKS Cluster、Containerの3つのStackを持ちます。
Container生成する Stackを分離させてるところがポイントかと思います。
以下はコンテナを生成するStackで、ArgoCD、nginxのコンテナ生成constructがあります。
Container Stackを分離することで、非常に見通しの良いものになっているのではないでしょうか。
class ContainerStack(Stack):
def __init__(self,
scope: Construct,
construct_id: str,
cluster: aws_eks.Cluster,
**kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
# ArgoCD
self._container_tool_construct = ContainerToolConstruct(
self, 'ContainerToolConstruct', cluster)
# eginx
self._container_construct = ContainerConstruct(
self, 'ContainerConstruct', cluster)
### おわりに
CDK constructはBoilerplateであることを意識すれば、CDKのコードの見通しが非常に高まると思いました。また、3. EKS Clusterに関しては、現在のコードをベースにPipeline化を進めていきたく、最終的にはGitOpsにしていきたいと考えてます。CDKであればTemplateに比べアジャイルチックに進めていくのもとてもやりやすいので、CDKの「コードで可能」というメリットを教授できます。CDKによりインフラのアジャイル開発が活発化すればいいなと思いました。