Running your
Dockerized application(s)
on AWS EC2 Container Service
Marco Pas
Philips Lighting
Software geek, hands on
Developer/Architect/DevOps Engineer
@marcopas
Some stuff about me...
● Mostly doing cloud related stuff
○ Java, Groovy, Scala, Spring Boot, IOT, AWS, Terraform, Infrastructure
● Enjoying the good things
● Chef leuke dingen doen == “trying out cool and new stuff”
● Currently involved in a big IOT project
● Wannabe chef, movie & Netflix addict
..Quick Inventory..
From Personal Container Management to ...
Something that runs into production
● Docker
● Security
● Service Discovery
● Logging & Monitoring
● Rolling Deployments
● Networking
● Supervision
● Container hosting
● Docker
Development Production
Learning
cliff
Using Docker Compose != Running production
Agenda
● Containers
● Creating a Container using Spring Boot
● Container Services
● Amazon EC2 Container Service (ECS)
○ Pushing and Pulling containers
○ Deploying containers
○ Scaling your containers
○ Service Discovery
○ Logging
○ Monitoring
Containers
● OS Virtualization
● Process Isolation
● Automation
● Images
What are containers
Portable
Flexible
Fast
Efficient
Creating a Docker Image
# Dockerfile ~ example
FROM alpine:latest
ADD HelloWorld.class HelloWorld.class
RUN apk --update add openjdk8-jre
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "HelloWorld"]
docker build -t <your imagename>:<tag> .
From Docker Image to a Docker Container
Creating a Container
using Spring Boot
// file: DemoApplication.java
package springboot.docker.helloworld;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }
@RequestMapping("/")
String home() {
return "Hello World!"; → say hello :)
}
}
// file: build.gradle ~ some code intentionally removed
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-actuator') → add spring boot actuator
testCompile('org.springframework.boot:spring-boot-starter-test')
}
String dockerImageName = "spring-boot-docker-helloworld" → set the image name
task buildDockerImage(type:Exec) { → task to create an image
group = 'docker'
description = 'Build a docker image'
commandLine 'docker', 'build', '-f', 'build/docker/Dockerfile', '-t', "${dockerImageName}", 'build/docker'
doFirst {
println ">> Creating image: ${dockerImageName}"
// some code intentionally removed
}
}
// file: build.gradle ~ some code intentionally removed
doFirst {
println ">> Creating image: ${dockerImageName}"
copy {
// copy files to build location, Dockerfile - Jar file
}
copy {
// process Dockerfile to replace labels (Dockerfile label: @name@, @version@, @build-date@, …
// copy files to build location, Dockerfile - Jar file
from('src/main/docker/') {
include 'Dockerfile'
filter(ReplaceTokens, tokens: [
'version': version,
'build-date': new Date().format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC")),
'git-branch': gitBranch(),
'git-commit': gitCommitHash()
])
}
file("build/docker/app/${jar.archiveName}").renameTo("build/docker/app/application.jar")
}
// file: Dockerfile example ~ some code intentionally removed
FROM java:8u66-jdk
LABEL com.acme.build-date="@build-date@" → provide data to the Dockerfile
EXPOSE 8080 → expose port 8080, so the host can map this port
# Create app that holds the application.jar file
RUN mkdir -p /app → do some housekeeping, creating directories
WORKDIR /app
COPY /app/application.jar application.jar → copy the application.jar file into the container
COPY /app/docker-entrypoint.sh docker-entrypoint.sh → copy startup script into the container
# Set file permissions
RUN chmod +x docker-entrypoint.sh → make the shel script executable
# Set start script as default command
CMD ["./docker-entrypoint.sh"] → execute the startup script when we start the container
// file: build.gradle
project.ext.dockerRegistry = System.env.DOCKER_REGISTRY → get the docker registry from environment
String dockerImageName = "spring-boot-docker-helloworld" → set the image name
task pushDockerImage(type: Exec) {
group = 'docker'
description = 'Push a docker image'
commandLine 'docker', 'push', "${project.ext.dockerRegistry}/${dockerImageName}"
doFirst {
println ">> Checking dockerRepository"
if (!project.ext.dockerRegistry) {
throw new GradleException("Unable to push image, please provide correct 'dockerRegistry'")
}
println ">> Pushing image: ${dockerImageName}"
}
}
Running the image using Docker Compose
// file: docker-compose.yml
version: '2'
services:
springboot-demo: → name if container
image: spring-boot-docker-helloworld:latest → the image that is going to be used
ports:
- "8080:8080” → port mapping 8080 host -> 8080 container
Demo:
Build & Run Docker Image using
Spring Boot
But wait… we have images now
how do we run our containers?
We need some help!
Container Storage, Scheduling
& Orchestration
Container Services
Container Services
● Most used Container Services
○ Amazon ECS
○ Kubernetes by Google
○ Docker Swarm
○ Hashicorp Nomad
○ Azure Container Service
All have the some focus:
Run your Services / Containers
Container Services
● Storage
● Clustering support
● Control & Monitoring
● Scale up/down
● Scheduling & Orchestration
○ Flexible Container placement
Placement Strategies
● Strategy name
○ node selected
● Spread
○ has the fewest containers,
disregarding their states
● Binpack
○ most packed (i.e. has the minimum
amount of free CPU/RAM)
● Random
○ chosen randomly
Components of AWS ECS
comparable
to Docker
Hub
Amazon EC2 Container Service (ECS)
“ECR → Pushing and Pulling containers”
● Amazon’s version of a Docker Registry
● Registry contains Repositories
○ unique namespace
● Logins generated on demand with
limited session length
● Images:
○ can be shared with AWS accounts
○ at rest are encrypted and stored in S3
○ transmitted over HTTPS
Container Registry
Publishing
to ECR
Demo:
Push/Pull image(s) to/from ECR
Amazon EC2 Container Service (ECS)
“ECS → Deploying containers”
Container
Service
Detail
Docker Container
The result of starting a
Docker Image by the
Scheduler
Container
Service
Detail
EC2 Container Instance
EC2 instance with Docker &
the ECS Agent installed
ECS Agent
Allows EC2 container
instances to connect to a
cluster
Container
Service
Detail
ECS Cluster
A logical grouping of EC2
Container Instances
Container
Service
Detail
Demo Description
● Create the infrastructure
● Deploy “HelloWorld”
container to an
ECS Container Instance
● Make the endpoint
publicly available via
ALB
● Scale the container
instances
How to create the environment?
“Infrastructure as Code”
Terraform
● Provision resources
○ Compute / Storage / Network
● Manage resource lifecycles
● Manage different resource providers (AWS, Google, Azure, …)
● Automate deployments and configurations
Infrastructure as Code
Tip
Considering using TFENV
to manage Terraform versions
// file: main.tf ~ some code intentionally removed
module "vpc" {
source = "github.com/terraform-community-modules/tf_aws_vpc"
name = "my-vpc"
cidr = "10.0.0.0/16"
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
}
module "bastion" { ... }
module "ecs-cluster" {
source = "./ecs-cluster"
cluster_name = "demo"
vpc_id = "${module.vpc.vpc_id}"
subnet_ids = "${module.vpc.private_subnets}"
}
Demo:
Overview of AWS Infrastructure
All fine, we have the infrastructure
Now get some apps deployed :)
Deployment of a Dockerized
app on ECS
Describing your Docker deployment
Describes one or more Docker
Containers that form your
application (blueprint)
Runs and maintains a desired
number of tasks from a
specified task definition
Running container with
the settings defined in
the Task Definition
Example: Task Definition
45
{ "family": "webserver", → family of containers
"containerDefinitions": [{
"name": "web", → name of the container
"image": "nginx", → the image used for the container
"cpu": 99, → cpu + memory credits
"memory": 100,
"portMappings": [{ → port mappings (expose port 80 in container to port 80 on host)
"containerPort": 80,
"hostPort": 80
}],
"environment": [{ → environment variables, used in the container
"name": "MYSQL_ROOT_PASSWORD",
"value": "password"
}]
}]
}
Can you spot the problem?
Host <> Container Port mapping
Host <> Container Port mapping
Run a task/service on ECS Container Service
● AWS Console
○ Use the AWS console and use the UI
● Manual
○ Using the AWS CLI / ECS CLI
● Automated
○ Using Cloudwatch or Terraform
Demo Description
● Create the infrastructure
● Deploy “HelloWorld”
container to an
ECS Container Instance
● Make the endpoint
publicly available via
ALB
● Scale the container
instances
// file: main.tf ~ some code intentionally removed
module "vpc" { ... }
module "bastion" { ... }
module "ecs-cluster" { ... }
module "helloworld-service" {
source = "./helloworld-service"
environment = "test-env"
vpc_id = "${module.vpc.vpc_id}"
ecs_cluster_id = "${module.ecs-cluster.ecs_cluster_id}"
docker_repository = "163079528612.dkr.ecr.us-east-1.amazonaws.com"
public_subnet_ids = "${module.vpc.public_subnets}"
iam_role = "${module.ecs-cluster.ecs_aws_iam_role_name}"
desired_count = 1
}
// file: task-definition.tpl
[
{
"name": "helloworld-service",
"essential": true,
"image": "${docker_repository}/springboot-docker-helloworld:${version}",
"memoryReservation": 256,
"portMappings": [
{ "ContainerPort": 8080 }
]
}
]
Demo:
Deploy Docker Container on ECS
Container Service using Terraform
Service Autoscaling
Autoscaling your containers
● Scaling is based upon metrics → Application Autoscaling
○ Metrics on ECS/Service
■ cpu load, memory usage, io, …
● CloudWatch Alarm
○ cpu > 80% for 1 minute
○ cpu < 50% for 1 minute
● Scaling Policy → “ChangeInCapacity”
○ up +1 instance
○ down -1 instance
Demo Description
● Create the infrastructure
● Deploy “HelloWorld”
container to an
ECS Container Instance
● Make the endpoint
publicly available via
ALB
● Scale the container
instances
Demo:
Autoscaling based on CPU
Service Discovery
Service Discovery
● DNS based Discovery
● Consul Service Discovery
Logging
Logging
Multiple Log Drivers
available
Configuring the Log Driver
62
{ "family": "webserver", → family of containers
"containerDefinitions": [{
"name": "web", → name of the container
"image": "nginx", → the image used for the container
// some intentionally omitted
"logConfiguration": { → log configuration
"logDriver": "awslogs", → to be used logdriver
"options": { → logdriver options
"awslogs-group": "awslogs-nginx",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "awslogs-example"
}
}
}]
}
Monitoring
● Cloudwatch / InfluxDB / Prometheus / SysDig
Monitoring
Prometheus Overview
Demo:
Monitoring using Prometheus
Recap
● Running Docker containers on ECS is not hard
○ Build your Dockerized Spring Boot applications and push them to ECS
○ ECS Cluster with EC2 instances
● Use a “Infrastructure as Code” approach to keep a grasp on what needs to
be deployed
● Do not forget about Logging and Monitoring these steps are important
○ use CloudWatch or other monitoring tools to keep an eye on your infrastructure
● Service Discovery using DNS or Consul
That’s a wrap!
Question?
https://github.com/mpas/running-your-dockerized-application-on-aws-ec2-container-service
Marco Pas
Philips Lighting
Software geek, hands on
Developer/Architect/DevOps Engineer
@marcopas

Running your dockerized application(s) on AWS Elastic Container Service

  • 1.
    Running your Dockerized application(s) onAWS EC2 Container Service Marco Pas Philips Lighting Software geek, hands on Developer/Architect/DevOps Engineer @marcopas
  • 2.
    Some stuff aboutme... ● Mostly doing cloud related stuff ○ Java, Groovy, Scala, Spring Boot, IOT, AWS, Terraform, Infrastructure ● Enjoying the good things ● Chef leuke dingen doen == “trying out cool and new stuff” ● Currently involved in a big IOT project ● Wannabe chef, movie & Netflix addict
  • 3.
  • 5.
    From Personal ContainerManagement to ...
  • 6.
    Something that runsinto production ● Docker ● Security ● Service Discovery ● Logging & Monitoring ● Rolling Deployments ● Networking ● Supervision ● Container hosting ● Docker Development Production Learning cliff
  • 7.
    Using Docker Compose!= Running production
  • 8.
    Agenda ● Containers ● Creatinga Container using Spring Boot ● Container Services ● Amazon EC2 Container Service (ECS) ○ Pushing and Pulling containers ○ Deploying containers ○ Scaling your containers ○ Service Discovery ○ Logging ○ Monitoring
  • 9.
  • 10.
    ● OS Virtualization ●Process Isolation ● Automation ● Images What are containers Portable Flexible Fast Efficient
  • 11.
    Creating a DockerImage # Dockerfile ~ example FROM alpine:latest ADD HelloWorld.class HelloWorld.class RUN apk --update add openjdk8-jre ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "HelloWorld"] docker build -t <your imagename>:<tag> .
  • 12.
    From Docker Imageto a Docker Container
  • 13.
  • 14.
    // file: DemoApplication.java packagespringboot.docker.helloworld; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @RequestMapping("/") String home() { return "Hello World!"; → say hello :) } }
  • 15.
    // file: build.gradle~ some code intentionally removed dependencies { compile('org.springframework.boot:spring-boot-starter-web') compile('org.springframework.boot:spring-boot-starter-actuator') → add spring boot actuator testCompile('org.springframework.boot:spring-boot-starter-test') } String dockerImageName = "spring-boot-docker-helloworld" → set the image name task buildDockerImage(type:Exec) { → task to create an image group = 'docker' description = 'Build a docker image' commandLine 'docker', 'build', '-f', 'build/docker/Dockerfile', '-t', "${dockerImageName}", 'build/docker' doFirst { println ">> Creating image: ${dockerImageName}" // some code intentionally removed } }
  • 16.
    // file: build.gradle~ some code intentionally removed doFirst { println ">> Creating image: ${dockerImageName}" copy { // copy files to build location, Dockerfile - Jar file } copy { // process Dockerfile to replace labels (Dockerfile label: @name@, @version@, @build-date@, … // copy files to build location, Dockerfile - Jar file from('src/main/docker/') { include 'Dockerfile' filter(ReplaceTokens, tokens: [ 'version': version, 'build-date': new Date().format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC")), 'git-branch': gitBranch(), 'git-commit': gitCommitHash() ]) } file("build/docker/app/${jar.archiveName}").renameTo("build/docker/app/application.jar") }
  • 17.
    // file: Dockerfileexample ~ some code intentionally removed FROM java:8u66-jdk LABEL com.acme.build-date="@build-date@" → provide data to the Dockerfile EXPOSE 8080 → expose port 8080, so the host can map this port # Create app that holds the application.jar file RUN mkdir -p /app → do some housekeeping, creating directories WORKDIR /app COPY /app/application.jar application.jar → copy the application.jar file into the container COPY /app/docker-entrypoint.sh docker-entrypoint.sh → copy startup script into the container # Set file permissions RUN chmod +x docker-entrypoint.sh → make the shel script executable # Set start script as default command CMD ["./docker-entrypoint.sh"] → execute the startup script when we start the container
  • 18.
    // file: build.gradle project.ext.dockerRegistry= System.env.DOCKER_REGISTRY → get the docker registry from environment String dockerImageName = "spring-boot-docker-helloworld" → set the image name task pushDockerImage(type: Exec) { group = 'docker' description = 'Push a docker image' commandLine 'docker', 'push', "${project.ext.dockerRegistry}/${dockerImageName}" doFirst { println ">> Checking dockerRepository" if (!project.ext.dockerRegistry) { throw new GradleException("Unable to push image, please provide correct 'dockerRegistry'") } println ">> Pushing image: ${dockerImageName}" } }
  • 19.
    Running the imageusing Docker Compose // file: docker-compose.yml version: '2' services: springboot-demo: → name if container image: spring-boot-docker-helloworld:latest → the image that is going to be used ports: - "8080:8080” → port mapping 8080 host -> 8080 container
  • 20.
    Demo: Build & RunDocker Image using Spring Boot
  • 21.
    But wait… wehave images now how do we run our containers? We need some help! Container Storage, Scheduling & Orchestration
  • 22.
  • 23.
    Container Services ● Mostused Container Services ○ Amazon ECS ○ Kubernetes by Google ○ Docker Swarm ○ Hashicorp Nomad ○ Azure Container Service All have the some focus: Run your Services / Containers
  • 24.
    Container Services ● Storage ●Clustering support ● Control & Monitoring ● Scale up/down ● Scheduling & Orchestration ○ Flexible Container placement
  • 25.
    Placement Strategies ● Strategyname ○ node selected ● Spread ○ has the fewest containers, disregarding their states ● Binpack ○ most packed (i.e. has the minimum amount of free CPU/RAM) ● Random ○ chosen randomly
  • 27.
    Components of AWSECS comparable to Docker Hub
  • 28.
    Amazon EC2 ContainerService (ECS) “ECR → Pushing and Pulling containers”
  • 29.
    ● Amazon’s versionof a Docker Registry ● Registry contains Repositories ○ unique namespace ● Logins generated on demand with limited session length ● Images: ○ can be shared with AWS accounts ○ at rest are encrypted and stored in S3 ○ transmitted over HTTPS Container Registry
  • 30.
  • 31.
  • 32.
    Amazon EC2 ContainerService (ECS) “ECS → Deploying containers”
  • 33.
    Container Service Detail Docker Container The resultof starting a Docker Image by the Scheduler
  • 34.
    Container Service Detail EC2 Container Instance EC2instance with Docker & the ECS Agent installed ECS Agent Allows EC2 container instances to connect to a cluster
  • 35.
    Container Service Detail ECS Cluster A logicalgrouping of EC2 Container Instances
  • 36.
  • 37.
    Demo Description ● Createthe infrastructure ● Deploy “HelloWorld” container to an ECS Container Instance ● Make the endpoint publicly available via ALB ● Scale the container instances
  • 38.
    How to createthe environment? “Infrastructure as Code”
  • 39.
    Terraform ● Provision resources ○Compute / Storage / Network ● Manage resource lifecycles ● Manage different resource providers (AWS, Google, Azure, …) ● Automate deployments and configurations Infrastructure as Code
  • 40.
    Tip Considering using TFENV tomanage Terraform versions
  • 41.
    // file: main.tf~ some code intentionally removed module "vpc" { source = "github.com/terraform-community-modules/tf_aws_vpc" name = "my-vpc" cidr = "10.0.0.0/16" public_subnets = ["10.0.1.0/24", "10.0.2.0/24"] private_subnets = ["10.0.101.0/24", "10.0.102.0/24"] } module "bastion" { ... } module "ecs-cluster" { source = "./ecs-cluster" cluster_name = "demo" vpc_id = "${module.vpc.vpc_id}" subnet_ids = "${module.vpc.private_subnets}" }
  • 42.
    Demo: Overview of AWSInfrastructure
  • 43.
    All fine, wehave the infrastructure Now get some apps deployed :) Deployment of a Dockerized app on ECS
  • 44.
    Describing your Dockerdeployment Describes one or more Docker Containers that form your application (blueprint) Runs and maintains a desired number of tasks from a specified task definition Running container with the settings defined in the Task Definition
  • 45.
    Example: Task Definition 45 {"family": "webserver", → family of containers "containerDefinitions": [{ "name": "web", → name of the container "image": "nginx", → the image used for the container "cpu": 99, → cpu + memory credits "memory": 100, "portMappings": [{ → port mappings (expose port 80 in container to port 80 on host) "containerPort": 80, "hostPort": 80 }], "environment": [{ → environment variables, used in the container "name": "MYSQL_ROOT_PASSWORD", "value": "password" }] }] } Can you spot the problem?
  • 46.
    Host <> ContainerPort mapping
  • 47.
    Host <> ContainerPort mapping
  • 48.
    Run a task/serviceon ECS Container Service ● AWS Console ○ Use the AWS console and use the UI ● Manual ○ Using the AWS CLI / ECS CLI ● Automated ○ Using Cloudwatch or Terraform
  • 49.
    Demo Description ● Createthe infrastructure ● Deploy “HelloWorld” container to an ECS Container Instance ● Make the endpoint publicly available via ALB ● Scale the container instances
  • 50.
    // file: main.tf~ some code intentionally removed module "vpc" { ... } module "bastion" { ... } module "ecs-cluster" { ... } module "helloworld-service" { source = "./helloworld-service" environment = "test-env" vpc_id = "${module.vpc.vpc_id}" ecs_cluster_id = "${module.ecs-cluster.ecs_cluster_id}" docker_repository = "163079528612.dkr.ecr.us-east-1.amazonaws.com" public_subnet_ids = "${module.vpc.public_subnets}" iam_role = "${module.ecs-cluster.ecs_aws_iam_role_name}" desired_count = 1 }
  • 51.
    // file: task-definition.tpl [ { "name":"helloworld-service", "essential": true, "image": "${docker_repository}/springboot-docker-helloworld:${version}", "memoryReservation": 256, "portMappings": [ { "ContainerPort": 8080 } ] } ]
  • 52.
    Demo: Deploy Docker Containeron ECS Container Service using Terraform
  • 53.
  • 54.
    Autoscaling your containers ●Scaling is based upon metrics → Application Autoscaling ○ Metrics on ECS/Service ■ cpu load, memory usage, io, … ● CloudWatch Alarm ○ cpu > 80% for 1 minute ○ cpu < 50% for 1 minute ● Scaling Policy → “ChangeInCapacity” ○ up +1 instance ○ down -1 instance
  • 55.
    Demo Description ● Createthe infrastructure ● Deploy “HelloWorld” container to an ECS Container Instance ● Make the endpoint publicly available via ALB ● Scale the container instances
  • 56.
  • 57.
  • 58.
    Service Discovery ● DNSbased Discovery ● Consul Service Discovery
  • 59.
  • 61.
  • 62.
    Configuring the LogDriver 62 { "family": "webserver", → family of containers "containerDefinitions": [{ "name": "web", → name of the container "image": "nginx", → the image used for the container // some intentionally omitted "logConfiguration": { → log configuration "logDriver": "awslogs", → to be used logdriver "options": { → logdriver options "awslogs-group": "awslogs-nginx", "awslogs-region": "ap-northeast-1", "awslogs-stream-prefix": "awslogs-example" } } }] }
  • 63.
  • 64.
    ● Cloudwatch /InfluxDB / Prometheus / SysDig Monitoring
  • 65.
  • 66.
  • 67.
    Recap ● Running Dockercontainers on ECS is not hard ○ Build your Dockerized Spring Boot applications and push them to ECS ○ ECS Cluster with EC2 instances ● Use a “Infrastructure as Code” approach to keep a grasp on what needs to be deployed ● Do not forget about Logging and Monitoring these steps are important ○ use CloudWatch or other monitoring tools to keep an eye on your infrastructure ● Service Discovery using DNS or Consul
  • 68.
    That’s a wrap! Question? https://github.com/mpas/running-your-dockerized-application-on-aws-ec2-container-service MarcoPas Philips Lighting Software geek, hands on Developer/Architect/DevOps Engineer @marcopas