AWS Developer Tools Blog

Event-driven architecture using Scala, Docker, HAQM Kinesis Firehose, and the AWS SDK for Java (Part 2)

In the first part of this blog post, we used the AWS SDK for Java to create a Scala application to write data in HAQM Kinesis Firehose, Dockerized the application, and then tested and verified the application is working. Now we will roll out our Scala application in HAQM EC2 Container Service (ECS) and use the HAQM EC2 Container Registry (HAQM ECR) as our private Docker registry.

To roll out our application on HAQM ECS, we have to set up a private Docker registry and an HAQM ECS cluster. First, we have to create IAM roles for HAQM ECS. Before we can launch container instances and register them into a cluster, we must generate an IAM role for those container instances to use when they are launched. This requirement applies to container instances launched with the HAQM Machine Image (AMIoptimized for ECS or any other instances where you will run the agent.

aws iam create-role --role-name ecsInstanceRole --assume-role-policy-document file://<path_to_json_file>/ecsInstanceRole.json

ecsInstanceRole.json:

{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

 

aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/service-role/HAQMEC2ContainerServiceforEC2Role --role-name ecsInstanceRole

We have to attach an additional policy so the ecsInstanceRole can pull Docker images from HAQM ECR:

aws iam put-role-policy --role-name ecsInstanceRole --policy-name ecrPullPolicy --policy-document file://<path_to_json_file>/ecrPullPolicy.json

ecrPullPolicy.json:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:BatchGetImage",
                "ecr:GetDownloadUrlForLayer",
                "ecr:GetAuthorizationToken"
            ],
            "Resource": "*"
        }
    ]
}

This role needs permission to write into our HAQM Kinesis Firehose stream, too:

aws iam put-role-policy --role-name ecsInstanceRole --policy-name firehosePolicy --policy-document file://<path_to_json_file>/firehosePolicy.json

firehosePolicy.json:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": [
                "firehose:DescribeDeliveryStream",
                "firehose:ListDeliveryStreams",
                "firehose:PutRecord",
                "firehose:PutRecordBatch"
            ],
            "Resource": [
                "arn:aws:firehose:aws-region:<account-ID>:deliverystream/<delivery-stream-name>"
            ]
        }
    ]
}

The HAQM ECS service scheduler makes calls on our behalf to the HAQM EC2 and Elastic Load Balancing APIs to register and deregister container instances with our load balancers. Before we can attach a load balancer to an HAQM ECS service, we must create an IAM role for our services to use. This requirement applies to any HAQM ECS service that we plan to use with a load balancer.

aws iam create-role --role-name ecsServiceRole --assume-role-policy-document file://<path_to_json_file>/ecsServiceRole.json

ecsServiceRole.json:

{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ecs.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

 

aws iam put-role-policy --role-name ecsServiceRole --policy-name ecsServicePolicy --policy-document file://<path_to_json_file>/ecsServicePolicy.json

ecsServicePolicy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "elasticloadbalancing:Describe*",
        "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
        "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
        "ec2:Describe*",
        "ec2:AuthorizeSecurityGroupIngress"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

Now we have set up the IAM roles and permissions required for a fully functional HAQM ECS cluster. Before setting up the cluster, we will create an ELB load balancer to be used for our akka-firehose service. The load balancer is called “akkaElb.It maps port 80 to port 80, and uses the specified subnets and security groups.

aws elb create-load-balancer --load-balancer-name akkaElb --listeners "Protocol=HTTP,LoadBalancerPort=80,InstanceProtocol=HTTP,InstancePort=80" --subnets subnet-a,subnet-b --security-groups sg-a --region us-east-1

The health check configuration of the load balancer contains information such as the protocol, ping port, ping path, response timeout, and health check interval.

aws elb configure-health-check --load-balancer-name akkaElb --health-check Target=HTTP:80/api/healthcheck,Interval=30,UnhealthyThreshold=5,HealthyThreshold=2,Timeout=3 --region us-east-1

We should enable connection draining for our load balancer to ensure it will stop sending requests to deregistering or unhealthy instances, while keeping existing connections open.

aws elb modify-load-balancer-attributes --load-balancer-name akkaElb --load-balancer-attributes "{"ConnectionDraining":{"Enabled":true,"Timeout":300}}" --region us-east-1

After setting up the load balancer, we can now create the HAQM ECS cluster:

aws ecs create-cluster --cluster-name "AkkaCluster" --region us-east-1

To set up our HAQM ECR repository correctly, we will sign in to HAQM ECR and receive a token that will be stored in /home/ec2-user/.docker/config.jsonThe token is valid for 24 hours.

aws ecr get-login --region us-east-1

To store the Docker image, we will create a repository in HAQM ECR:

aws ecr create-repository --repository-name akka-firehose --region us-east-1

Under most circumstances, the Docker image will be created at the end of a build process triggered by a continuous integration server like Jenkins. Therefore, we have to create a repository policy so that the Jenkins IAM role can push and pull Docker images from our newly created repository:

aws ecr set-repository-policy --repository-name akka-firehose --region us-east-1 --policy-text "{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "jenkins_push_pull",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<account-id>:role/<Jenkins-role>"
            },
            "Action": [
                "ecr:DescribeRepositories",
                "ecr:GetRepositoryPolicy",
                "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer",
                "ecr:ListImages",
                "ecr:BatchGetImage",
                "ecr:PutImage",
                "ecr:InitiateLayerUpload",
                "ecr:UploadLayerPart",
                "ecr:CompleteLayerUpload"
            ]
        }
    ]
}"

We have to add a similar repository policy for HAQM ECS because the HAQM EC2 instances in our HAQM ECS cluster have to be able to pull Docker images from our private Docker registry:

aws ecr set-repository-policy --repository-name akka-firehose --region us-east-1 --policy-text "{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "ecs_instance_pull",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<account-id>:role/ecsInstanceRole"
            },
            "Action": [
                "ecr:DescribeRepositories",
                "ecr:GetRepositoryPolicy",
                "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer",
                "ecr:ListImages",
                "ecr:BatchGetImage"
            ]
        }
    ]
}"

Now we can tag and push the Docker container into our repository in HAQM ECR:

docker tag akka-firehose <account-id>.dkr.ecr.us-east-1.amazonaws.com/akka-firehose


docker push <account-id>.dkr.ecr.us-east-1.amazonaws.com/akka-firehose

To populate our HAQM ECS cluster, we have to launch a few HAQM EC2 instances and register them in our cluster. It is important to choose either the HAQM Machine Image optimized for ECR or one for another operating system (such as CoreOS or Ubuntu) with the HAQM ECS container agent installed. (In this example, we will use the ECS-optimized AMI of HAQM Linux.) In the first step, we will create an instance profile and then attach the ecsInstanceRole to this profile:

aws iam create-instance-profile --instance-profile-name ecsServer


aws iam add-role-to-instance-profile --role-name ecsInstanceRole --instance-profile-name ecsServer

Now we will use the following user data script to launch a few EC2 instances in different subnets:

ecs-userdata.txt:

#!/bin/bash
yum update -y
echo ECS_CLUSTER=AkkaCluster >> /etc/ecs/ecs.config

This user data script updates the Linux packages of the HAQM EC2 instance and registers it in the HAQM ECS cluster. By default, the container instance is launched into your default cluster if you don’t specify another one.

aws ec2 run-instances --image-id ami-840e42ee --count 1 --instance-type t2.medium --key-name <your_ssh_key> --security-group-ids sg-a --subnet-id subnet-a --iam-instance-profile Name=ecsServer --user-data file://<path_to_user_data_file>/ecs-userdata.txt --region us-east-1


aws ec2 run-instances --image-id ami-840e42ee --count 1 --instance-type t2.medium --key-name <your_ssh_key> --security-group-ids sg-a --subnet-id subnet-b --iam-instance-profile Name=ecsServer --user-data file://<path_to_user_data_file>/ecs-userdata.txt --region us-east-1

Now we will register our task definition and service:

aws ecs register-task-definition --cli-input-json file://<path_to_json_file>/akka-firehose.json --region us-east-1

akka-firehose.json:

{
  "containerDefinitions": [
    {
      "name": "akka-firehose",
      "image": "<your_account_id>.dkr.ecr.us-east-1.amazonaws.com/akka-firehose",
      "cpu": 1024,
      "memory": 1024,
      "portMappings": [{
                      "containerPort": 8080,
                      "hostPort": 80
              }],
      "essential": true
    }
  ],
  "family": "akka-demo"
}

The task definition specifies which image you want to use, how many resources (CPU and RAM) are required and the port mappings between Docker container and host.

aws ecs create-service --cluster AkkaCluster --service-name akka-firehose-service --cli-input-json file://<path_to_json_file>/akka-elb.json --region us-east-1

akka-elb.json:

{
    "serviceName": "akka-firehose-service",
    "taskDefinition": "akka-demo:1",
    "loadBalancers": [
        {
            "loadBalancerName": "akkaElb",
            "containerName": "akka-firehose",
            "containerPort": 8080
        }
    ],
    "desiredCount": 2,
    "role": "ecsServiceRole"
}

Our service uses the task definition in version 1 and connects the containers on port 8080 to our previously defined ELB load balancer. The configuration specifies the desired number of services to two, so if we have registered two HAQM EC2 instances in our HAQM ECS cluster, each of them should run a service. After a short amount of time, the service should run successfully on the cluster. We can test the current setup by sending a POST request to our ELB load balancer:

curl -v -H "Content-type: application/json" -X POST -d '{"userId":100, "userName": "This is user data"}' http://<address_of_elb>.us-east-1.elb.amazonaws.com/api/user

After sending data to our application, we can list the files in the S3 bucket we created as a target for HAQM Kinesis Firehose:

aws s3 ls s3://<your_name>-firehose-target --recursive

In this blog post we created the infrastructure to roll out our Scala-based microservice in HAQM ECS and HAQM ECR. We hope we’ve given you ideas for creating your own Dockerized Scala-based applications in AWS. Feel free to share your ideas in the comments below!