Als mich ein Java-Entwickler fragte, wie er seine Spring Boot-API auf AWS ECS bereitstellen könne, sah ich darin die perfekte Gelegenheit, in die neuesten Updates des CDKTF-Projekts (Cloud Development Kit for Terraform) einzutauchen.
In einem früheren Artikel habe ich CDKTF vorgestellt, ein Framework, das es Ihnen ermöglicht, Infrastructure as Code (IaC) mit allgemeinen Programmiersprachen wie Python zu schreiben. Seitdem hat CDKTF seine erste GA-Veröffentlichung erreicht, was den perfekten Zeitpunkt für einen erneuten Besuch darstellt. In diesem Artikel gehen wir Schritt für Schritt durch die Bereitstellung einer Spring Boot-API auf AWS ECS mithilfe von CDKTF.
Den Code dieses Artikels finden Sie in meinem Github-Repo.
Bevor wir uns mit der Implementierung befassen, werfen wir einen Blick auf die Architektur, die wir bereitstellen möchten:
Aus diesem Diagramm können wir die Architektur in 03 Schichten unterteilen:
Die von uns bereitgestellte Java-API ist auf GitHub verfügbar.
Es definiert eine einfache REST-API mit drei Endpunkten:
Fügen wir die Docker-Datei hinzu:
FROM maven:3.9-amazoncorretto-21 AS builder WORKDIR /app COPY pom.xml . COPY src src RUN mvn clean package # amazon java distribution FROM amazoncorretto:21-alpine COPY --from=builder /app/target/*.jar /app/java-api.jar EXPOSE 8080 ENTRYPOINT ["java","-jar","/app/java-api.jar"]
Unsere Anwendung ist einsatzbereit!
AWS CDKTF ermöglicht Ihnen die Definition und Verwaltung von AWS-Ressourcen mit Python.
- [**python (3.13)**](https://www.python.org/) - [**pipenv**](https://pipenv.pypa.io/en/latest/) - [**npm**](https://nodejs.org/en/)
Stellen Sie sicher, dass Sie über die erforderlichen Tools verfügen, indem Sie CDKTF und seine Abhängigkeiten installieren:
$ npm install -g cdktf-cli@latest
Dadurch wird die cdktf-CLI installiert, mit der neue Projekte für verschiedene Sprachen erstellt werden können.
Wir können ein neues Python-Projekt erstellen, indem wir Folgendes ausführen:
FROM maven:3.9-amazoncorretto-21 AS builder WORKDIR /app COPY pom.xml . COPY src src RUN mvn clean package # amazon java distribution FROM amazoncorretto:21-alpine COPY --from=builder /app/target/*.jar /app/java-api.jar EXPOSE 8080 ENTRYPOINT ["java","-jar","/app/java-api.jar"]
Es werden standardmäßig viele Dateien erstellt und alle Abhängigkeiten werden installiert.
unten finden Sie die anfängliche main.pyfile:
- [**python (3.13)**](https://www.python.org/) - [**pipenv**](https://pipenv.pypa.io/en/latest/) - [**npm**](https://nodejs.org/en/)
a Stack repräsentiert eine Gruppe von Infrastrukturressourcen, die CDK für Terraform (CDKTF) zu einer unterschiedlichen Terraform -Konfiguration kompiliert. Stapel ermöglichen ein separates staatliches Management für verschiedene Umgebungen innerhalb einer Anwendung. Um Ressourcen über Ebenen hinweg zu teilen, werden wir Cross-Stack-Referenzen verwenden.
Fügen Sie die Datei network_stack.py in Ihrem Projekt hinzu
$ npm install -g cdktf-cli@latest
Fügen Sie den folgenden Code hinzu, um alle Netzwerkressourcen zu erstellen:
# init the project using aws provider $ mkdir samples-fargate $ cd samples-fargate && cdktf init --template=python --providers=aws
Bearbeiten Sie dann die main.py
Datei:
#!/usr/bin/env python from constructs import Construct from cdktf import App, TerraformStack class MyStack(TerraformStack): def __init__(self, scope: Construct, id: str): super().__init__(scope, id) # define resources here app = App() MyStack(app, "aws-cdktf-samples-fargate") app.synth()
Generieren Sie die Terraform -Konfigurationsdateien, indem Sie den folgenden Befehl ausführen:
$ mkdir infra $ cd infra && touch network_stack.py
Bereitstellen Sie den -Netzwerkstapel
bereit:
from constructs import Construct from cdktf import S3Backend, TerraformStack from cdktf_cdktf_provider_aws.provider import AwsProvider from cdktf_cdktf_provider_aws.vpc import Vpc from cdktf_cdktf_provider_aws.subnet import Subnet from cdktf_cdktf_provider_aws.eip import Eip from cdktf_cdktf_provider_aws.nat_gateway import NatGateway from cdktf_cdktf_provider_aws.route import Route from cdktf_cdktf_provider_aws.route_table import RouteTable from cdktf_cdktf_provider_aws.route_table_association import RouteTableAssociation from cdktf_cdktf_provider_aws.internet_gateway import InternetGateway class NetworkStack(TerraformStack): def __init__(self, scope: Construct, ns: str, params: dict): super().__init__(scope, ns) self.region = params["region"] # configure the AWS provider to use the us-east-1 region AwsProvider(self, "AWS", region=self.region) # use S3 as backend S3Backend( self, bucket=params["backend_bucket"], key=params["backend_key_prefix"] + "/network.tfstate", region=self.region, ) # create the vpc vpc_demo = Vpc(self, "vpc-demo", cidr_block="192.168.0.0/16") # create two public subnets public_subnet1 = Subnet( self, "public-subnet-1", vpc_id=vpc_demo.id, availability_zone=f"{self.region}a", cidr_block="192.168.1.0/24", ) public_subnet2 = Subnet( self, "public-subnet-2", vpc_id=vpc_demo.id, availability_zone=f"{self.region}b", cidr_block="192.168.2.0/24", ) # create. the internet gateway igw = InternetGateway(self, "igw", vpc_id=vpc_demo.id) # create the public route table public_rt = Route( self, "public-rt", route_table_id=vpc_demo.main_route_table_id, destination_cidr_block="0.0.0.0/0", gateway_id=igw.id, ) # create the private subnets private_subnet1 = Subnet( self, "private-subnet-1", vpc_id=vpc_demo.id, availability_zone=f"{self.region}a", cidr_block="192.168.10.0/24", ) private_subnet2 = Subnet( self, "private-subnet-2", vpc_id=vpc_demo.id, availability_zone=f"{self.region}b", cidr_block="192.168.20.0/24", ) # create the Elastic IPs eip1 = Eip(self, "nat-eip-1", depends_on=[igw]) eip2 = Eip(self, "nat-eip-2", depends_on=[igw]) # create the NAT Gateways private_nat_gw1 = NatGateway( self, "private-nat-1", subnet_id=public_subnet1.id, allocation_id=eip1.id, ) private_nat_gw2 = NatGateway( self, "private-nat-2", subnet_id=public_subnet2.id, allocation_id=eip2.id, ) # create Route Tables private_rt1 = RouteTable(self, "private-rt1", vpc_id=vpc_demo.id) private_rt2 = RouteTable(self, "private-rt2", vpc_id=vpc_demo.id) # add default routes to tables Route( self, "private-rt1-default-route", route_table_id=private_rt1.id, destination_cidr_block="0.0.0.0/0", nat_gateway_id=private_nat_gw1.id, ) Route( self, "private-rt2-default-route", route_table_id=private_rt2.id, destination_cidr_block="0.0.0.0/0", nat_gateway_id=private_nat_gw2.id, ) # associate routes with subnets RouteTableAssociation( self, "public-rt-association", subnet_id=private_subnet2.id, route_table_id=private_rt2.id, ) RouteTableAssociation( self, "private-rt1-association", subnet_id=private_subnet1.id, route_table_id=private_rt1.id, ) RouteTableAssociation( self, "private-rt2-association", subnet_id=private_subnet2.id, route_table_id=private_rt2.id, ) # terraform outputs self.vpc_id = vpc_demo.id self.public_subnets = [public_subnet1.id, public_subnet2.id] self.private_subnets = [private_subnet1.id, private_subnet2.id]
Unser VPC ist bereit, wie im Bild unten gezeigt:
Fügen Sie die Datei infra_stack.py
Ihrem Projekt
#!/usr/bin/env python from constructs import Construct from cdktf import App, TerraformStack from infra.network_stack import NetworkStack ENV = "dev" AWS_REGION = "us-east-1" BACKEND_S3_BUCKET = "blog.abdelfare.me" BACKEND_S3_KEY = f"{ENV}/cdktf-samples" class MyStack(TerraformStack): def __init__(self, scope: Construct, id: str): super().__init__(scope, id) # define resources here app = App() MyStack(app, "aws-cdktf-samples-fargate") network = NetworkStack( app, "network", { "region": AWS_REGION, "backend_bucket": BACKEND_S3_BUCKET, "backend_key_prefix": BACKEND_S3_KEY, }, ) app.synth()
Fügen Sie den folgenden Code hinzu, um alle Infrastrukturressourcen zu erstellen:
$ cdktf synth
Bearbeiten Sie die main.py
Datei:
$ cdktf deploy network
Bereitstellen Sie die Infra Stack
damit ein:
$ cd infra && touch infra_stack.py
Beachten Sie den DNS -Namen des ALB, wir werden ihn später verwenden.
Fügen Sie die Datei service_stack.py
zu Ihrem Projekt hinzu
from constructs import Construct from cdktf import S3Backend, TerraformStack from cdktf_cdktf_provider_aws.provider import AwsProvider from cdktf_cdktf_provider_aws.ecs_cluster import EcsCluster from cdktf_cdktf_provider_aws.lb import Lb from cdktf_cdktf_provider_aws.lb_listener import ( LbListener, LbListenerDefaultAction, LbListenerDefaultActionFixedResponse, ) from cdktf_cdktf_provider_aws.security_group import ( SecurityGroup, SecurityGroupIngress, SecurityGroupEgress, ) class InfraStack(TerraformStack): def __init__(self, scope: Construct, ns: str, network: dict, params: dict): super().__init__(scope, ns) self.region = params["region"] # Configure the AWS provider to use the us-east-1 region AwsProvider(self, "AWS", region=self.region) # use S3 as backend S3Backend( self, bucket=params["backend_bucket"], key=params["backend_key_prefix"] + "/load_balancer.tfstate", region=self.region, ) # create the ALB security group alb_sg = SecurityGroup( self, "alb-sg", vpc_id=network["vpc_id"], ingress=[ SecurityGroupIngress( protocol="tcp", from_port=80, to_port=80, cidr_blocks=["0.0.0.0/0"] ) ], egress=[ SecurityGroupEgress( protocol="-1", from_port=0, to_port=0, cidr_blocks=["0.0.0.0/0"] ) ], ) # create the ALB alb = Lb( self, "alb", internal=False, load_balancer_type="application", security_groups=[alb_sg.id], subnets=network["public_subnets"], ) # create the LB Listener alb_listener = LbListener( self, "alb-listener", load_balancer_arn=alb.arn, port=80, protocol="HTTP", default_action=[ LbListenerDefaultAction( type="fixed-response", fixed_response=LbListenerDefaultActionFixedResponse( content_type="text/plain", status_code="404", message_body="Could not find the resource you are looking for", ), ) ], ) # create the ECS cluster cluster = EcsCluster(self, "cluster", name=params["cluster_name"]) self.alb_arn = alb.arn self.alb_listener = alb_listener.arn self.alb_sg = alb_sg.id self.cluster_id = cluster.id
Fügen Sie den folgenden Code hinzu, um alle ECS -Service -Ressourcen zu erstellen:
... CLUSTER_NAME = "cdktf-samples" ... infra = InfraStack( app, "infra", { "vpc_id": network.vpc_id, "public_subnets": network.public_subnets, }, { "region": AWS_REGION, "backend_bucket": BACKEND_S3_BUCKET, "backend_key_prefix": BACKEND_S3_KEY, "cluster_name": CLUSTER_NAME, }, ) ...
aktualisieren Sie die main.py (zum letzten Mal?):
$ cdktf deploy network infra
Bereitstellen Sie den Service -Stack
bereit:
$ mkdir apps $ cd apps && touch service_stack.py
Hier gehen wir!
Wir haben alle Ressourcen erfolgreich erstellt, um einen neuen Dienst auf AWS ECS Fargate bereitzustellen.
Führen Sie Folgendes aus, um die Liste Ihrer Stapel zu erhalten
from constructs import Construct import json from cdktf import S3Backend, TerraformStack, Token, TerraformOutput from cdktf_cdktf_provider_aws.provider import AwsProvider from cdktf_cdktf_provider_aws.ecs_service import ( EcsService, EcsServiceLoadBalancer, EcsServiceNetworkConfiguration, ) from cdktf_cdktf_provider_aws.ecr_repository import ( EcrRepository, EcrRepositoryImageScanningConfiguration, ) from cdktf_cdktf_provider_aws.ecr_lifecycle_policy import EcrLifecyclePolicy from cdktf_cdktf_provider_aws.ecs_task_definition import ( EcsTaskDefinition, ) from cdktf_cdktf_provider_aws.lb_listener_rule import ( LbListenerRule, LbListenerRuleAction, LbListenerRuleCondition, LbListenerRuleConditionPathPattern, ) from cdktf_cdktf_provider_aws.lb_target_group import ( LbTargetGroup, LbTargetGroupHealthCheck, ) from cdktf_cdktf_provider_aws.security_group import ( SecurityGroup, SecurityGroupIngress, SecurityGroupEgress, ) from cdktf_cdktf_provider_aws.cloudwatch_log_group import CloudwatchLogGroup from cdktf_cdktf_provider_aws.data_aws_iam_policy_document import ( DataAwsIamPolicyDocument, ) from cdktf_cdktf_provider_aws.iam_role import IamRole from cdktf_cdktf_provider_aws.iam_role_policy_attachment import IamRolePolicyAttachment class ServiceStack(TerraformStack): def __init__( self, scope: Construct, ns: str, network: dict, infra: dict, params: dict ): super().__init__(scope, ns) self.region = params["region"] # Configure the AWS provider to use the us-east-1 region AwsProvider(self, "AWS", region=self.region) # use S3 as backend S3Backend( self, bucket=params["backend_bucket"], key=params["backend_key_prefix"] + "/" + params["app_name"] + ".tfstate", region=self.region, ) # create the service security group svc_sg = SecurityGroup( self, "svc-sg", vpc_id=network["vpc_id"], ingress=[ SecurityGroupIngress( protocol="tcp", from_port=params["app_port"], to_port=params["app_port"], security_groups=[infra["alb_sg"]], ) ], egress=[ SecurityGroupEgress( protocol="-1", from_port=0, to_port=0, cidr_blocks=["0.0.0.0/0"] ) ], ) # create the service target group svc_tg = LbTargetGroup( self, "svc-target-group", name="svc-tg", port=params["app_port"], protocol="HTTP", vpc_id=network["vpc_id"], target_type="ip", health_check=LbTargetGroupHealthCheck(path="/ping", matcher="200"), ) # create the service listener rule LbListenerRule( self, "alb-rule", listener_arn=infra["alb_listener"], action=[LbListenerRuleAction(type="forward", target_group_arn=svc_tg.arn)], condition=[ LbListenerRuleCondition( path_pattern=LbListenerRuleConditionPathPattern(values=["/*"]) ) ], ) # create the ECR repository repo = EcrRepository( self, params["app_name"], image_scanning_configuration=EcrRepositoryImageScanningConfiguration( scan_on_push=True ), image_tag_mutability="MUTABLE", name=params["app_name"], ) EcrLifecyclePolicy( self, "this", repository=repo.name, policy=json.dumps( { "rules": [ { "rulePriority": 1, "description": "Keep last 10 images", "selection": { "tagStatus": "tagged", "tagPrefixList": ["v"], "countType": "imageCountMoreThan", "countNumber": 10, }, "action": {"type": "expire"}, }, { "rulePriority": 2, "description": "Expire images older than 3 days", "selection": { "tagStatus": "untagged", "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 3, }, "action": {"type": "expire"}, }, ] } ), ) # create the service log group service_log_group = CloudwatchLogGroup( self, "svc_log_group", name=params["app_name"], retention_in_days=1, ) ecs_assume_role = DataAwsIamPolicyDocument( self, "assume_role", statement=[ { "actions": ["sts:AssumeRole"], "principals": [ { "identifiers": ["ecs-tasks.amazonaws.com"], "type": "Service", }, ], }, ], ) # create the service execution role service_execution_role = IamRole( self, "service_execution_role", assume_role_policy=ecs_assume_role.json, name=params["app_name"] + "-exec-role", ) IamRolePolicyAttachment( self, "ecs_role_policy", policy_arn="arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", role=service_execution_role.name, ) # create the service task role service_task_role = IamRole( self, "service_task_role", assume_role_policy=ecs_assume_role.json, name=params["app_name"] + "-task-role", ) # create the service task definition task = EcsTaskDefinition( self, "svc-task", family="service", network_mode="awsvpc", requires_compatibilities=["FARGATE"], cpu="256", memory="512", task_role_arn=service_task_role.arn, execution_role_arn=service_execution_role.arn, container_definitions=json.dumps( [ { "name": "svc", "image": f"{repo.repository_url}:latest", "networkMode": "awsvpc", "healthCheck": { "Command": ["CMD-SHELL", "echo hello"], "Interval": 5, "Timeout": 2, "Retries": 3, }, "portMappings": [ { "containerPort": params["app_port"], "hostPort": params["app_port"], } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": service_log_group.name, "awslogs-region": params["region"], "awslogs-stream-prefix": params["app_name"], }, }, } ] ), ) # create the ECS service EcsService( self, "ecs_service", name=params["app_name"] + "-service", cluster=infra["cluster_id"], task_definition=task.arn, desired_count=params["desired_count"], launch_type="FARGATE", force_new_deployment=True, network_configuration=EcsServiceNetworkConfiguration( subnets=network["private_subnets"], security_groups=[svc_sg.id], ), load_balancer=[ EcsServiceLoadBalancer( target_group_arn=svc_tg.id, container_name="svc", container_port=params["app_port"], ) ], ) TerraformOutput( self, "ecr_repository_url", description="url of the ecr repo", value=repo.repository_url, )
Um Bereitstellungen zu automatisieren, integrieren wir einen Workflow von GitHub-Aktionen in unser java-api . Nach dem Aktivieren von GitHub -Aktionen erstellen Sie die Geheimnisse und Variablen für Ihr Repository, erstellen Sie die Datei .github/Workflows/Deploy.yml und fügen Sie den folgenden Inhalt hinzu:
FROM maven:3.9-amazoncorretto-21 AS builder WORKDIR /app COPY pom.xml . COPY src src RUN mvn clean package # amazon java distribution FROM amazoncorretto:21-alpine COPY --from=builder /app/target/*.jar /app/java-api.jar EXPOSE 8080 ENTRYPOINT ["java","-jar","/app/java-api.jar"]
Unser Workflow funktioniert gut:
Der Dienst wurde erfolgreich bereitgestellt, wie im Bild unten gezeigt:
Testen Sie Ihre Bereitstellung mit dem folgenden Skript ( Ersetzen Sie die AlB -URL durch Ihre ):
- [**python (3.13)**](https://www.python.org/) - [**pipenv**](https://pipenv.pypa.io/en/latest/) - [**npm**](https://nodejs.org/en/)
Der ALB ist jetzt bereit, Verkehr zu servieren!
Durch die Nutzung von AWS -CDKTF können wir mit Python sauberen, wartenable IAC -Code schreiben. Dieser Ansatz vereinfacht die Bereitstellung von Containeranwendungen wie eine Spring -Boot -API auf AWS ECS Fargate.
CDKTFs Flexibilität in Kombination mit den robusten Funktionen von Terraform ist eine hervorragende Wahl für moderne Cloud -Bereitstellungen.
Während das CDKTF -Projekt viele interessante Funktionen für das Infrastrukturmanagement bietet, muss ich zugeben, dass ich es manchmal etwas zu ausführlich finde.
Haben Sie Erfahrung mit CDKTF? Haben Sie es in der Produktion verwendet?
Sie können Ihre Erfahrungen mit uns teilen.
Das obige ist der detaillierte Inhalt vonWie stellt man mit CDKTF eine SpringBoot-API auf AWS ECS bereit?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!