AWS CDK(Python)でコンテナをサクッとたてる
はじめに
タイトルの通りです。
コンテナをサクッとたてるためのテンプレが欲しくて作りました。
コンテナイメージにはOWASP Juice Shopを指定していますが、コンテナイメージとポートを変更すればおそらく動くはずです。
コード
以下に置いてあります。
構成
主なリソースはECS/Fargate、ALB、WAFです。
特にECSとALBに代表されるようなよくあるパターンであれば、ECS patternsを利用すると少ない記述量で必要なリソースを作成してくれます。
今回はApplicationLoadBalancedFargateServiceを利用しています。これだけでALBとECS、Fargateのセットを構築します。例えばvpc
はOptionalですが、記載しなければCDKが必要なVPCやサブネット、ルートテーブルを作ってくれます。(私がVPCを設定しているのではNAT GWの数をコントロールしたいからです。)
追加でAWS WAFで指定したIPアドレス以外からのアクセスをブロックしています。
セキュリティグループでブロックしてもいいですが、AWS WAFのログを見たかったのでこちらで設定しています。
簡単な説明
VPC
subnet_type
でそのサブネットのタイプを決定します。
ここではec2.SubnetType.PUBLIC
とec2.SubnetType.PRIVATE_WITH_EGRESS
を指定してます。
前者はPublicサブネットとして扱う、後者はPrivateサブネットとして扱いたい場合に指定します。
他にはインターネットに接続しないec2.SubnetType.PRIVATE_ISOLATED
、現在は非推奨であるec2.SubnetType.PRIVATE_WITH_NAT
があります。
以下の設定では、以下のリソースが作成されます。感覚としてはマネジメントコンソールから作成するのと似ていると思います。
- VPC x1
- 2AZ(max_azsは利用する最大のAZ数を指定するもの)
- Publicサブネット x2
- Privateサブネット x2
- NAT GW x1
- ルートテーブル x4
明記していないもの(例えばCIDRや各リソースの名前)は自動で設定・生成されます。
vpc = ec2.Vpc(
self,
"vpc",
max_azs=2,
nat_gateways=1,
subnet_configuration=[
ec2.SubnetConfiguration(
name="public", subnet_type=ec2.SubnetType.PUBLIC
),
ec2.SubnetConfiguration(
name="private", subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS
),
],
)
ECS
今回はECSに指定のVPCを利用したいためCluster
を作成しています。
task_image_options
でコンテナイメージやポートを指定してますが、from_registry
はDockerHubからイメージを取得する場合に使うもので、ECRから取得する場合にはfrom_ecr_repository
という別の関数があるので注意してください。
# ECS
cluster = ecs.Cluster(self, "ecs-cluster", cluster_name="ecs-cluster", vpc=vpc)
# ECS pattern
ecsp_service = ecsp.ApplicationLoadBalancedFargateService(
self,
"ecs_service",
cluster=cluster,
task_image_options=ecsp.ApplicationLoadBalancedTaskImageOptions(
image=ecs.ContainerImage.from_registry("bkimminich/juice-shop"),
container_port=3000
),
public_load_balancer=True
)
WAF
IPSet
AWS WAFのルールで利用するためのIPSetです。単純にIPアドレスのリストです。
addressesのIPアドレスに変更・追加すると、そのIPアドレスからアクセスできるようになります。
# WAF IPSet
ipset = wafv2.CfnIPSet(
self,
"ipset",
addresses=["198.51.100.0/24"],
ip_address_version="IPV4",
scope="REGIONAL",
name="myIP",
)
rules
AWS WAFに設定するルールです。
wafv2.CfnWebACL.RuleProperty
で一つのルールを表しています。ルールは複数のステートメントから構成されることもあるのでもっと長くなったり、マネージドルールの場合は記載が変わったりします。
statement
がルールのマッチ条件になります。wafv2.CfnWebACL.NotStatementProperty
を間にいれることで、IPSetにマッチしなかったらという条件を作ることができます。次のaction
でブロックに設定しているので、IPSetにマッチしない送信元はブロックされます。
rules=[
wafv2.CfnWebACL.RuleProperty(
name="IPblock",
priority=10,
visibility_config=wafv2.CfnWebACL.VisibilityConfigProperty(
cloud_watch_metrics_enabled=True,
metric_name="owasp-waf-ipbloack",
sampled_requests_enabled=True,
),
statement=wafv2.CfnWebACL.StatementProperty(
not_statement=wafv2.CfnWebACL.NotStatementProperty(
statement=wafv2.CfnWebACL.StatementProperty(
ip_set_reference_statement={"arn": ipset.attr_arn}
)
)
),
action=wafv2.CfnWebACL.RuleActionProperty(
block=wafv2.CfnWebACL.BlockActionProperty(
custom_response=wafv2.CfnWebACL.CustomResponseProperty(
response_code=403
)
)
),
)
],
Output
ALBのarnを出力しているだけです。特に何か使っているわけではないです。
なお、ALBのDNS名とスキーマがついたURLはECS patternsによって自動的に出力されるようになっています。
CfnOutput(self, "albarn", value=ecsp_service.load_balancer.load_balancer_arn)
おわりに
試しにWebサーバがたてたい時にはECS patternsが役に立つのではないでしょうか?
ついでにPythonでCDKの情報とAWS WAFをCDKで書いてるサンプルとしても役に立てばうれしいです。
FargateのSpotインスタンス指定は時間ができたら追加しようと思います。
記事中でも述べていますが、AWS WAFはCDK以外の方法で管理するか、aws-samplesを利用するのがよさそうです。
Discussion