Infrastructure as Code (IaC)

LarsTechnik

Infrastructure as Code ist in der Cloud Welt in aller Munde. Sei es bei Azure via ARM Templates oder bei AWS mit CloudFormation. AWS geht nun aber einen Schritt weiter und bietet ein Cloud Development Kit an.

Mit AWS CDK können Cloud Ressourcen mithilfe von TypeScript, Python, Java (Entwicklervorschau) und .NET (Entwicklervorschau) modelliert werden. Dabei können die Entwickler ihre bevorzugten IDEs und Testtools verwenden. Dank Tools wie automatische Vervollständigung und Inline Dokumentation verschwendet man weniger Zeit damit, zwischen Service Dokumentation und dem Code zu wechseln.

Ein super Einstieg in die Thematik bietet der Online Workshop von AWS an. Der Workshop hilft bei der Installation und bietet ein geführtes Beispiel an. Die CDK Reference Dokumentation findet ihr hier. Ich gehe nicht tief in die einzelnen Funktionen von CDK ein. Dies sind sehr gut in den Online Dokumentationen beschrieben.

Das Development Kit ist sehr mächtig, ich möchte dies anhand einem kleinen Beispiel aufzeigen. Vor allem der Unterschied zwischen der Anzahl Codezeilen und der daraus resultierenden CloudFormation Deklaration. Dazu verwende ich Python als Programmiersprache.

Ein kleiner Webserver soll auf einem Container in der AWS Cloud laufen. Für diesen Zweck verwende ich den Fargate Service von AWS. Der Fargate Service sitzt hinter einem Loadbalancer, alle Anfragen werden via Loadbalancer an den Container weiter geleitet. Für das Setup braucht es noch ein VPC mit zwei publik und zwei privaten Subnetzen, denn das ganze soll auch redundant sein.

Der Code in der clxs_fargate_service_stack.py Datei sieht folgendermassen aus:

from aws_cdk import (
core,
aws_ec2 as ec2,
aws_ecs as ecs,
aws_ecs_patterns as ecs_patterns,
aws_logs as logs,
aws_iam as iam,
)

class ClxsFargateServiceStack(core.Stack):
  def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
    super().__init__(scope, id, **kwargs)

    # The code that defines your stack goes here

    #Create Subnet for the VPC
    subnets = []
    subnets.append(ec2.SubnetConfiguration(
                name="euw1-pr-sub-fargate01", 
                subnet_type=ec2.SubnetType.PRIVATE, 
                cidr_mask=24))

    subnets.append(ec2.SubnetConfiguration(
                name="euw1-pr-sub-public01", 
                subnet_type=ec2.SubnetType.PUBLIC, 
                cidr_mask=24))

    #Create the VPC
    vpc = ec2.Vpc(
        self, "Vpc",
        max_azs=2,
        cidr="192.168.0.0/16",
        subnet_configuration=subnets,
    )

    # Create the Fargate Cluster
    cluster = ecs.Cluster(
        self, 'Ec2Cluster',
        vpc=vpc,
        cluster_name='clxs-pr-cluster-fgservice'
    )

    ##Create VPC Flow Log
    # Setup IAM Policy Statement
    flow_log_policy_statement = iam.PolicyStatement(
            effect = iam.Effect.ALLOW,
            resources = ["*"],
            actions = [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:DescribeLogGroups",
                "logs:DescribeLogStreams"
            ],
    )

    #Create Policy with the Statement
    flow_log_policy = iam.ManagedPolicy(self, "MyPolicy",
                                        managed_policy_name="clxs-pr-pol-vpc_flow_log",
                                        statements=[flow_log_policy_statement])

    #Create a IAM Role and attache the Policy
    vpc_flow_role = iam.Role(
        self, 'vpc-flow-log',
        assumed_by=iam.ServicePrincipal('vpc-flow-logs.amazonaws.com'),
        role_name="clxs-pr-role-vpc_flow_log",
        managed_policies=[flow_log_policy]
    )

    # Create CloudWatch log group
    log_group = logs.LogGroup(
        self, 'log-group',
        log_group_name='clxs-pr-log-vpc_flow_log',
        retention=logs.RetentionDays('ONE_YEAR')
    )

    # Setup VPC flow logs and give the just created Role
    vpc_log = ec2.CfnFlowLog(
        self, 'FlowLogs',
        resource_id=vpc.vpc_id,
        resource_type='VPC',
        traffic_type='ALL',
        deliver_logs_permission_arn=vpc_flow_role.role_arn,
        log_destination_type='cloud-watch-logs',
        log_group_name=log_group.log_group_name
    )

    #Create the FargateService with the Docker Image saved in the DockerImage Folder
    fargate_service = ecs_patterns.NetworkLoadBalancedFargateService(
        self, "FargateService",
        cluster=cluster,
        task_image_options={
            'image': ecs.ContainerImage.from_asset("DockerImage")
        },
        service_name="clxs-pr-fgse-website",
        memory_limit_mib=512,
        cpu=256, 
        desired_count=1,
    )

    #Add Port 80 to Security Group
    fargate_sg = fargate_service.service.connections.security_groups[0]
    fargate_sg.add_ingress_rule(ec2.Peer.any_ipv4(), ec2.Port.tcp(80))

    #Give the Loadbalancer URL back to the CLI
    core.CfnOutput(
        self, "LoadBalancerDNS",
        value="http://" + fargate_service.load_balancer.load_balancer_dns_name
    )

In der app.py Datei wird dann die Klasse aus der clxs_fargate_service_stack.py Datei verwendet. In diesem Beispiel werden die AWS Credentials aus der AWS CLI Konfigurationsdatei verwendet. Durch Vererbung und mehrfacher Deklaration könnten wir diesen Stack auch auf andere AWS Regionen anwenden.

from aws_cdk import core

from clxs_fargate_service.clxs_fargate_service_stack import ClxsFargateServiceStack

app = core.App()
ClxsFargateServiceStack(app, "clxs-fargate-service",
          env={'region': 'eu-west-1'
          }
)

app.synth()

Das Dockerfile ist auch sehr einfach gehalten, so sieht das Dockerfile aus. Wir nehmen das nginx:alpine Image, kopieren ein index.html in das nginx Verzeichnis und stellen noch den http Port bereit.

FROM nginx:alpine
COPY index.html /usr/share/nginx/html/
EXPOSE 80

Nun kann mit «cdk diff» geschaut werden was alles CloudFormation im Hintergrund erstellt, jedoch wird der Stack noch nicht ausgeführt. Mit «cdk deploy» wird dann der Stack in CloudFormation ausgeführt. Vor dem ersten Deploy muss noch ein «cdk bootstrap» ausgeführt werden. Das Bootstrap erstellt ein S3 Bucket um das Docker Image hoch zu laden, auch kreiert das Bootstrap einen eigenen Stack in CloudFormation. Mit «cdk synth» kann der CloudFormation Code angeschaut werden der bei einem Deploy ausgeführt wird. Dies sind einige Zeilen mehr als der Python Code. Ich möchte niemand langweilen mit dem CloudFormation Code, der ist 665 Zeilen lang.

[happy c0ding]!