├── cloudformation ├── alb.yml ├── ecs-autoscaling-nodes.yml ├── ecs-cluster.yml ├── ecs-service-task-billing.yml ├── ecs-service-task-messages.yml ├── ecs-service-task-users.yml ├── network-internet-access.yml ├── network-nat.yml ├── network-vpc-subnets.yml ├── network.yml └── pipeline.yml ├── ecs-commands.sh ├── kubernetes ├── Dockerfile ├── aws-auth-cm.yaml ├── deployment.yml ├── eksctl.sh ├── install-tools.sh └── kops.sh ├── microservices ├── messages │ ├── Dockerfile │ ├── package.json │ └── server.js └── users │ ├── Dockerfile │ ├── package.json │ └── server.js └── private-docker-registry └── install-docker.sh /cloudformation/alb.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Description: "Application Load Balancer for microservices in ECS" 4 | 5 | Parameters: 6 | NetworkStack: 7 | Type: "String" 8 | Description: "Network stack to apply to." 9 | 10 | ElbName: 11 | Type: String 12 | Description: "Name for this ELB" 13 | 14 | Resources: 15 | 16 | # create an APPLICATION load balancer for use by 17 | # containers in our microservices ECS cluster 18 | Alb: 19 | # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-loadbalancer.html 20 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer 21 | Properties: 22 | Name: !Ref ElbName 23 | Scheme: internet-facing 24 | Subnets: 25 | # add the 3 DMZ (public) subnets for access to the internet 26 | - Fn::ImportValue: 27 | !Sub ${NetworkStack}-SubnetElbA 28 | - Fn::ImportValue: 29 | !Sub ${NetworkStack}-SubnetElbB 30 | - Fn::ImportValue: 31 | !Sub ${NetworkStack}-SubnetElbC 32 | LoadBalancerAttributes: 33 | - Key: idle_timeout.timeout_seconds 34 | Value: '10' 35 | SecurityGroups: 36 | - Ref: AlbSecurityGroup 37 | Tags: 38 | - Key: Purpose 39 | Value: microservices 40 | 41 | # create a Security Group specifically for our ECS cluster 42 | AlbSecurityGroup: 43 | Type: AWS::EC2::SecurityGroup 44 | Properties: 45 | GroupName: !Ref "AWS::StackName" 46 | GroupDescription: !Ref "AWS::StackName" 47 | VpcId: 48 | Fn::ImportValue: 49 | !Sub ${NetworkStack}-VpcId 50 | 51 | # Allow outbound traffic FROM load balancer 52 | AlbSecurityGroupEgressEphemeral: 53 | Type: AWS::EC2::SecurityGroupEgress 54 | Properties: 55 | GroupId: !Ref AlbSecurityGroup 56 | CidrIp: # only allow ALB to INITIATE new connections to hosts in this VPC 57 | Fn::ImportValue: 58 | !Sub ${NetworkStack}-VpcCidr 59 | IpProtocol: "tcp" # allow only tcp 60 | FromPort: 32768 # default ephemeral range for ECS optimized AMI 61 | ToPort: 61000 # these are the ports used with dynamic port mapping 62 | 63 | # Allow outbound traffic FROM load balancer 64 | AlbSecurityGroupEgressHTTP: 65 | Type: AWS::EC2::SecurityGroupEgress 66 | Properties: 67 | GroupId: !Ref AlbSecurityGroup 68 | CidrIp: # only allow ALB to INITIATE new connections to hosts in this VPC 69 | Fn::ImportValue: 70 | !Sub ${NetworkStack}-VpcCidr 71 | IpProtocol: "tcp" # allow only tcp 72 | FromPort: 80 73 | ToPort: 80 74 | 75 | # Allow outbound traffic FROM load balancer 76 | AlbSecurityGroupEgressHTTPS: 77 | Type: AWS::EC2::SecurityGroupEgress 78 | Properties: 79 | GroupId: !Ref AlbSecurityGroup 80 | CidrIp: # only allow ALB to INITIATE new connections to hosts in this VPC 81 | Fn::ImportValue: 82 | !Sub ${NetworkStack}-VpcCidr 83 | IpProtocol: "tcp" # allow only tcp 84 | FromPort: 443 85 | ToPort: 443 86 | 87 | 88 | # allow Load balancer to receive HTTP 89 | AlbSecurityGroupIngressAllowHTTP: 90 | Type: AWS::EC2::SecurityGroupIngress 91 | Properties: 92 | GroupId: !Ref AlbSecurityGroup 93 | CidrIp: 0.0.0.0/0 94 | IpProtocol: "tcp" 95 | FromPort: 80 96 | ToPort: 80 97 | 98 | # allow Load balancer to receive HTTPS 99 | AlbSecurityGroupIngressAllowHTTPS: 100 | Type: AWS::EC2::SecurityGroupIngress 101 | Properties: 102 | GroupId: !Ref AlbSecurityGroup 103 | CidrIp: 0.0.0.0/0 104 | IpProtocol: "tcp" 105 | FromPort: 443 106 | ToPort: 443 107 | 108 | # Load Balancer needs a listener 109 | ListenerForAlb: 110 | Type: AWS::ElasticLoadBalancingV2::Listener 111 | Properties: 112 | LoadBalancerArn: !Ref Alb 113 | Port: 80 114 | Protocol: HTTP 115 | DefaultActions: 116 | - Type: forward 117 | TargetGroupArn: !Ref TargetGroupForServiceUsers 118 | 119 | 120 | # target group for the users service 121 | TargetGroupForServiceUsers: 122 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 123 | Properties: 124 | HealthCheckIntervalSeconds: 30 125 | HealthCheckProtocol: HTTP 126 | HealthCheckPath: "/users/status" 127 | HealthCheckTimeoutSeconds: 2 128 | HealthyThresholdCount: 2 129 | Matcher: 130 | HttpCode: '200' 131 | Name: service-users 132 | Port: 80 133 | Protocol: HTTP 134 | TargetGroupAttributes: 135 | - Key: deregistration_delay.timeout_seconds 136 | Value: '20' 137 | UnhealthyThresholdCount: 3 138 | VpcId: 139 | Fn::ImportValue: 140 | !Sub ${NetworkStack}-VpcId 141 | Tags: 142 | - Key: Purpose 143 | Value: 'microservices:users' 144 | 145 | # create a listener rule that routes /users/* to users service 146 | # will need a rule for each service 147 | ListenerRuleForServiceUsers: 148 | Type: AWS::ElasticLoadBalancingV2::ListenerRule 149 | Properties: 150 | Actions: 151 | - Type: forward 152 | TargetGroupArn: !Ref TargetGroupForServiceUsers 153 | Conditions: 154 | - Field: path-pattern 155 | Values: 156 | - "/users/*" 157 | ListenerArn: !Ref ListenerForAlb 158 | Priority: 1 159 | 160 | # target group for the billing service 161 | TargetGroupForServiceBilling: 162 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 163 | Properties: 164 | HealthCheckIntervalSeconds: 30 165 | HealthCheckProtocol: HTTP 166 | HealthCheckPath: "/billing/status" 167 | HealthCheckTimeoutSeconds: 2 168 | HealthyThresholdCount: 2 169 | Matcher: 170 | HttpCode: '200' 171 | Name: service-billing 172 | Port: 80 173 | Protocol: HTTP 174 | TargetGroupAttributes: 175 | - Key: deregistration_delay.timeout_seconds 176 | Value: '20' 177 | UnhealthyThresholdCount: 3 178 | VpcId: 179 | Fn::ImportValue: 180 | !Sub ${NetworkStack}-VpcId 181 | 182 | # create a listener rule that routes /billing/* to users service 183 | # will need a rule for each service 184 | ListenerRuleForServiceBilling: 185 | Type: AWS::ElasticLoadBalancingV2::ListenerRule 186 | Properties: 187 | Actions: 188 | - Type: forward 189 | TargetGroupArn: !Ref TargetGroupForServiceBilling 190 | Conditions: 191 | - Field: path-pattern 192 | Values: 193 | - "/billing/*" 194 | ListenerArn: !Ref ListenerForAlb 195 | Priority: 3 196 | 197 | # target group for the messages service 198 | TargetGroupForServiceMessages: 199 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 200 | Properties: 201 | HealthCheckIntervalSeconds: 30 202 | HealthCheckProtocol: HTTP 203 | HealthCheckPath: "/messages/status" 204 | HealthCheckTimeoutSeconds: 2 205 | HealthyThresholdCount: 2 206 | Matcher: 207 | HttpCode: '200' 208 | Name: service-fargate-messages 209 | Port: 80 210 | Protocol: HTTP 211 | TargetType: ip 212 | TargetGroupAttributes: 213 | - Key: deregistration_delay.timeout_seconds 214 | Value: '20' 215 | UnhealthyThresholdCount: 3 216 | VpcId: 217 | Fn::ImportValue: 218 | !Sub ${NetworkStack}-VpcId 219 | Tags: 220 | - Key: Purpose 221 | Value: 'microservices:messages' 222 | 223 | # create a listener rule that routes /messages/* to messages service 224 | # will need a rule for each service 225 | ListenerRuleForServiceMessages: 226 | Type: AWS::ElasticLoadBalancingV2::ListenerRule 227 | Properties: 228 | Actions: 229 | - Type: forward 230 | TargetGroupArn: !Ref TargetGroupForServiceMessages 231 | Conditions: 232 | - Field: path-pattern 233 | Values: 234 | - "/messages/*" 235 | ListenerArn: !Ref ListenerForAlb 236 | Priority: 2 237 | 238 | Outputs: 239 | TargetGroupForServiceUsers: 240 | Description : "TargetGroupForServiceUsers" 241 | Value: !Ref TargetGroupForServiceUsers 242 | Export: 243 | Name: !Sub ${AWS::StackName}-TargetGroupForServiceUsers 244 | TargetGroupForServiceBilling: 245 | Description : "TargetGroupForServiceBilling" 246 | Value: !Ref TargetGroupForServiceBilling 247 | Export: 248 | Name: !Sub ${AWS::StackName}-TargetGroupForServiceBilling 249 | TargetGroupForServiceMessages: 250 | Description : "TargetGroupForServiceMessages" 251 | Value: !Ref TargetGroupForServiceMessages 252 | Export: 253 | Name: !Sub ${AWS::StackName}-TargetGroupForServiceMessages 254 | -------------------------------------------------------------------------------- /cloudformation/ecs-autoscaling-nodes.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Description: "ECS Auto Scaling Groups" 4 | 5 | Parameters: 6 | NetworkStack: 7 | Type: "String" 8 | Description: "Network stack to apply to." 9 | 10 | ClusterName: 11 | Type: "String" 12 | Description: "Which cluster will these nodes join?" 13 | 14 | NumNodes: 15 | Type: String 16 | Description: How many nodes for this cluster? 17 | Default: 0 18 | 19 | AMI: 20 | Type: AWS::EC2::Image::Id 21 | 22 | Resources: 23 | 24 | # A role so that our EC2 instances can communicate with ECS 25 | EC2HostRole: 26 | Type: "AWS::IAM::Role" 27 | Properties: 28 | RoleName: !Sub EC2HostRoleFor${ClusterName} 29 | AssumeRolePolicyDocument: 30 | Version: "2012-10-17" 31 | Statement: 32 | - Effect: "Allow" 33 | Principal: 34 | Service: 35 | - "ec2.amazonaws.com" 36 | Action: 37 | - "sts:AssumeRole" 38 | Path: "/" 39 | ManagedPolicyArns: 40 | # attach the EC2 Container Service policy that will allow the instance 41 | # to communicate with ECS, and this policy includes Cloudwatch Logs permissions 42 | - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role 43 | 44 | # A profile bridges roles with instances 45 | EcsNodeProfile: 46 | Type: "AWS::IAM::InstanceProfile" 47 | Properties: 48 | Path: "/" 49 | InstanceProfileName: !Sub EcsNodeProfile${ClusterName} 50 | Roles: 51 | - !Ref EC2HostRole 52 | 53 | # create a role for the ECS service itself 54 | # this particular role only needs to be created ONCE 55 | # it does NOT need to be created per cluster 56 | EcsServiceRole: 57 | Type: "AWS::IAM::Role" 58 | Properties: 59 | RoleName: EcsServiceRole 60 | AssumeRolePolicyDocument: 61 | Version: "2012-10-17" 62 | Statement: 63 | - Effect: "Allow" 64 | Principal: 65 | Service: 66 | - "ecs.amazonaws.com" 67 | Action: 68 | - "sts:AssumeRole" 69 | Path: "/" 70 | ManagedPolicyArns: 71 | # allows ECS to manage resources like ELB for us 72 | - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole 73 | 74 | # Launch Configuration defines WHAT gets launched 75 | LaunchConfig: 76 | Type: AWS::AutoScaling::LaunchConfiguration 77 | Properties: 78 | EbsOptimized: True 79 | IamInstanceProfile: !Ref EcsNodeProfile 80 | ImageId: !Ref AMI 81 | InstanceMonitoring: True 82 | InstanceType: c5.large 83 | SecurityGroups: 84 | - !Ref EcsSecurityGroup 85 | 86 | UserData: 87 | Fn::Base64: 88 | Fn::Join: 89 | - "\n" 90 | - # this line creates the "array within the array" that Join requires as the 2nd arg 91 | - "#!/bin/bash" 92 | # configure the ecs agent to register with the cluster we defined earlier 93 | - !Join ["",["echo \"ECS_CLUSTER=", !Ref ClusterName, "\" >> /etc/ecs/ecs.config"]] 94 | 95 | # Auto Scaling Group defines WHERE it gets launched 96 | ClusterAutoScalingGroup: 97 | Type: AWS::AutoScaling::AutoScalingGroup 98 | UpdatePolicy: 99 | AutoScalingRollingUpdate: 100 | MaxBatchSize: 1 101 | MinInstancesInService: 2 102 | MinSuccessfulInstancesPercent: 100 103 | PauseTime: 180 104 | WaitOnResourceSignals: false 105 | Properties: 106 | DesiredCapacity: !Ref NumNodes 107 | HealthCheckGracePeriod: 420 108 | HealthCheckType: EC2 109 | LaunchConfigurationName: !Ref LaunchConfig 110 | MaxSize: !Ref NumNodes 111 | MinSize: !Ref NumNodes 112 | VPCZoneIdentifier: 113 | - Fn::ImportValue: 114 | !Sub ${NetworkStack}-SubnetNodesA 115 | - Fn::ImportValue: 116 | !Sub ${NetworkStack}-SubnetNodesB 117 | - Fn::ImportValue: 118 | !Sub ${NetworkStack}-SubnetNodesC 119 | Tags: 120 | - Key: Cluster 121 | Value: !Ref ClusterName 122 | PropagateAtLaunch: True 123 | 124 | EcsSecurityGroup: 125 | Type: AWS::EC2::SecurityGroup 126 | Properties: 127 | GroupName: !Ref "AWS::StackName" 128 | GroupDescription: !Ref "AWS::StackName" 129 | VpcId: 130 | Fn::ImportValue: 131 | !Sub ${NetworkStack}-VpcId 132 | Tags: 133 | - Key: Cluster 134 | Value: !Ref ClusterName 135 | 136 | EcsSecurityGroupEgress: 137 | Type: AWS::EC2::SecurityGroupEgress 138 | Properties: 139 | GroupId: !Ref EcsSecurityGroup 140 | CidrIp: 0.0.0.0/0 141 | IpProtocol: "-1" # allow all traffic outbound 142 | 143 | 144 | # allow ECS nodes to receive traffic from the local VPC range 145 | # limit source to respective ELB for added security 146 | EcsSecurityGroupIngressAllowTCP: 147 | Type: AWS::EC2::SecurityGroupIngress 148 | Properties: 149 | GroupId: !Ref EcsSecurityGroup 150 | CidrIp: 151 | Fn::ImportValue: 152 | !Sub ${NetworkStack}-VpcCidr 153 | IpProtocol: "tcp" 154 | FromPort: 32768 # default ephemeral range for ECS optimized AMI 155 | ToPort: 61000 # these are the ports used with dynamic port mapping 156 | -------------------------------------------------------------------------------- /cloudformation/ecs-cluster.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Description: "ECS Auto Scaling Groups" 4 | 5 | Parameters: 6 | 7 | ClusterName: 8 | Type: "String" 9 | Description: "A name for this cluster." 10 | 11 | 12 | Resources: 13 | 14 | # Define a cluster in ECS. This is the *logical grouping* not the actual hosts 15 | # You don't need EC2 instances when running containers in Fargate, but you still need the cluster 16 | EcsCluster: 17 | Type: "AWS::ECS::Cluster" 18 | Properties: 19 | ClusterName: !Ref ClusterName 20 | -------------------------------------------------------------------------------- /cloudformation/ecs-service-task-billing.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Description: "ECS service and task" 4 | 5 | Parameters: 6 | ClusterName: 7 | Type: String 8 | Description: "Specify which ECS cluster the service belongs to" 9 | AlbStack: 10 | Type: String 11 | Description: "Specify name of the CloudFormation stack for ALB" 12 | SecretARN: 13 | Type: String 14 | Description: "Specify the ARN of the Secret in secrets manager" 15 | 16 | 17 | Resources: 18 | 19 | # grant the TASK (collection of containers) the ability to perform its job 20 | TaskExecutionRole: 21 | Type: AWS::IAM::Role 22 | Properties: 23 | Path: / 24 | AssumeRolePolicyDocument: 25 | Version: 2012-10-17 26 | Statement: 27 | - Action: sts:AssumeRole 28 | Effect: Allow 29 | Principal: 30 | Service: ecs-tasks.amazonaws.com 31 | ManagedPolicyArns: 32 | - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy 33 | Policies: 34 | # this inline policy allows this role to access the secrets manager 35 | # in order to retrieve the username and password for the 36 | # private docker registry 37 | - PolicyName: "AccessToSecretStore" 38 | PolicyDocument: 39 | Version: "2012-10-17" 40 | Statement: 41 | - Effect: "Allow" 42 | Action: "secretsmanager:GetSecretValue" 43 | # this resource exists in Richard's account 44 | # REPLACE it with the ARN of YOUR secret 45 | Resource: !Ref SecretARN 46 | 47 | # create a group in CloudWatch logs for the app to write to 48 | LogGroup: 49 | Type: AWS::Logs::LogGroup 50 | Properties: 51 | LogGroupName: /microservices/billing 52 | 53 | # create the service that will maintain our containers for this microservice 54 | Service: 55 | Type: AWS::ECS::Service 56 | Properties: 57 | ServiceName: billing 58 | Cluster: !Ref ClusterName 59 | DesiredCount: 3 60 | TaskDefinition: !Ref TaskDefinition 61 | LaunchType: EC2 62 | LoadBalancers: 63 | - ContainerName: billing 64 | ContainerPort: 80 65 | TargetGroupArn: 66 | Fn::ImportValue: 67 | !Sub ${AlbStack}-TargetGroupForServiceBilling 68 | 69 | # define the collection of containers and cpu/memory/volume requirements 70 | TaskDefinition: 71 | Type: AWS::ECS::TaskDefinition 72 | Properties: 73 | Family: billing 74 | RequiresCompatibilities: 75 | - EC2 76 | Memory: 512 77 | Cpu: 256 78 | NetworkMode: bridge 79 | ExecutionRoleArn: !Ref TaskExecutionRole 80 | ContainerDefinitions: 81 | - Name: billing 82 | Image: docker.cerulean.systems/nginxdemos/hello # start with a ready made image and update once ImageRepo has an image 83 | RepositoryCredentials: 84 | CredentialsParameter: !Ref SecretARN 85 | Environment: 86 | - Name: SERVICE 87 | Value: billing 88 | Essential: true 89 | PortMappings: 90 | - ContainerPort: 80 91 | LogConfiguration: 92 | LogDriver: awslogs 93 | Options: 94 | awslogs-region: !Ref AWS::Region 95 | awslogs-group: !Ref LogGroup 96 | awslogs-stream-prefix: !Ref AWS::StackName 97 | -------------------------------------------------------------------------------- /cloudformation/ecs-service-task-messages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Description: "ECS service and task" 4 | 5 | Parameters: 6 | NetworkStack: 7 | Type: String 8 | Description: "Specify the network stack that created the VPC" 9 | ClusterName: 10 | Type: String 11 | Description: "Specify which ECS cluster the service belongs to" 12 | AlbStack: 13 | Type: String 14 | Description: "Specify name of the CloudFormation stack for ALB" 15 | 16 | Resources: 17 | 18 | # grant the TASK (collection of containers) the ability to perform its job 19 | TaskExecutionRole: 20 | Type: AWS::IAM::Role 21 | Properties: 22 | Path: / 23 | AssumeRolePolicyDocument: 24 | Version: 2012-10-17 25 | Statement: 26 | - Action: sts:AssumeRole 27 | Effect: Allow 28 | Principal: 29 | Service: ecs-tasks.amazonaws.com 30 | ManagedPolicyArns: 31 | - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy 32 | 33 | # create a group in CloudWatch logs for the app to write to 34 | LogGroup: 35 | Type: AWS::Logs::LogGroup 36 | Properties: 37 | LogGroupName: /microservices/messages 38 | 39 | # create a security group for the messages Service 40 | ServiceSecurityGroup: 41 | Type: AWS::EC2::SecurityGroup 42 | Properties: 43 | GroupName: "service-messages" 44 | GroupDescription: !Ref "AWS::StackName" 45 | VpcId: 46 | Fn::ImportValue: 47 | !Sub ${NetworkStack}-VpcId 48 | Tags: 49 | - Key: Cluster 50 | Value: !Ref ClusterName 51 | 52 | ServiceSecurityGroupEgress: 53 | Type: AWS::EC2::SecurityGroupEgress 54 | Properties: 55 | GroupId: !Ref ServiceSecurityGroup 56 | CidrIp: 0.0.0.0/0 57 | IpProtocol: "-1" # allow all traffic outbound 58 | 59 | ServiceSecurityGroupIngressAllowTCP: 60 | Type: AWS::EC2::SecurityGroupIngress 61 | Properties: 62 | GroupId: !Ref ServiceSecurityGroup 63 | CidrIp: 64 | Fn::ImportValue: 65 | !Sub ${NetworkStack}-VpcCidr 66 | IpProtocol: "tcp" 67 | FromPort: 80 68 | ToPort: 80 69 | 70 | 71 | 72 | # create the service that will maintain our containers for this microservice 73 | Service: 74 | Type: AWS::ECS::Service 75 | Properties: 76 | ServiceName: messages 77 | Cluster: !Ref ClusterName 78 | DesiredCount: 3 79 | TaskDefinition: !Ref TaskDefinition 80 | LaunchType: FARGATE 81 | NetworkConfiguration: 82 | AwsvpcConfiguration: 83 | AssignPublicIp: DISABLED 84 | SecurityGroups: 85 | - Ref: ServiceSecurityGroup 86 | Subnets: 87 | - Fn::ImportValue: 88 | !Sub ${NetworkStack}-SubnetNodesA 89 | - Fn::ImportValue: 90 | !Sub ${NetworkStack}-SubnetNodesB 91 | - Fn::ImportValue: 92 | !Sub ${NetworkStack}-SubnetNodesC 93 | 94 | LoadBalancers: 95 | - ContainerName: messages 96 | ContainerPort: 80 97 | TargetGroupArn: 98 | Fn::ImportValue: 99 | !Sub ${AlbStack}-TargetGroupForServiceMessages 100 | 101 | # define the collection of containers and cpu/memory/volume requirements 102 | TaskDefinition: 103 | Type: AWS::ECS::TaskDefinition 104 | Properties: 105 | Family: messages 106 | RequiresCompatibilities: 107 | - FARGATE 108 | Memory: 512 109 | Cpu: 256 110 | NetworkMode: awsvpc 111 | ExecutionRoleArn: !Ref TaskExecutionRole 112 | ContainerDefinitions: 113 | - Name: messages 114 | Image: nginxdemos/hello # start with a ready made image and update once ImageRepo has an image 115 | Environment: 116 | - Name: SERVICE 117 | Value: messages 118 | Essential: true 119 | PortMappings: 120 | - ContainerPort: 80 121 | LogConfiguration: 122 | LogDriver: awslogs 123 | Options: 124 | awslogs-region: !Ref AWS::Region 125 | awslogs-group: !Ref LogGroup 126 | awslogs-stream-prefix: !Ref AWS::StackName 127 | -------------------------------------------------------------------------------- /cloudformation/ecs-service-task-users.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Description: "ECS service and task" 4 | 5 | Parameters: 6 | ClusterName: 7 | Type: String 8 | Description: "Specify which ECS cluster the service belongs to" 9 | AlbStack: 10 | Type: String 11 | Description: "Specify name of the CloudFormation stack for ALB" 12 | 13 | Resources: 14 | 15 | # grant the TASK (collection of containers) the ability to perform its job 16 | TaskExecutionRole: 17 | Type: AWS::IAM::Role 18 | Properties: 19 | Path: / 20 | AssumeRolePolicyDocument: 21 | Version: 2012-10-17 22 | Statement: 23 | - Action: sts:AssumeRole 24 | Effect: Allow 25 | Principal: 26 | Service: ecs-tasks.amazonaws.com 27 | ManagedPolicyArns: 28 | - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy 29 | 30 | # create a group in CloudWatch logs for the app to write to 31 | LogGroup: 32 | Type: AWS::Logs::LogGroup 33 | Properties: 34 | LogGroupName: /microservices/users 35 | 36 | # create the service that will maintain our containers for this microservice 37 | Service: 38 | Type: AWS::ECS::Service 39 | Properties: 40 | ServiceName: users 41 | Cluster: !Ref ClusterName 42 | DesiredCount: 3 43 | TaskDefinition: !Ref TaskDefinition 44 | LaunchType: EC2 45 | LoadBalancers: 46 | - ContainerName: users 47 | ContainerPort: 80 48 | TargetGroupArn: 49 | Fn::ImportValue: 50 | !Sub ${AlbStack}-TargetGroupForServiceUsers 51 | 52 | # define the collection of containers and cpu/memory/volume requirements 53 | TaskDefinition: 54 | Type: AWS::ECS::TaskDefinition 55 | Properties: 56 | Family: users 57 | RequiresCompatibilities: 58 | - EC2 59 | Memory: 512 60 | Cpu: 512 61 | NetworkMode: bridge 62 | ExecutionRoleArn: !Ref TaskExecutionRole 63 | ContainerDefinitions: 64 | - Name: users 65 | Image: nginxdemos/hello # start with a ready made image and update once ImageRepo has an image 66 | Environment: 67 | - Name: SERVICE 68 | Value: users 69 | Essential: true 70 | PortMappings: 71 | - ContainerPort: 80 72 | LogConfiguration: 73 | LogDriver: awslogs 74 | Options: 75 | awslogs-region: !Ref AWS::Region 76 | awslogs-group: !Ref LogGroup 77 | awslogs-stream-prefix: !Ref AWS::StackName 78 | -------------------------------------------------------------------------------- /cloudformation/network-internet-access.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Description: "Internet Gateway and Route Table for public traffic" 4 | 5 | Parameters: 6 | VpcId: 7 | Type: String 8 | Description: "Specify the VPC ID." 9 | 10 | Subnets: 11 | Type: List 12 | Description: "Specify the subnets that should have direct internet access (public)." 13 | 14 | Resources: 15 | 16 | # Provide the VPC with access to the internet 17 | InternetGateway: 18 | Type: AWS::EC2::InternetGateway 19 | Properties: 20 | Tags: 21 | - Key: Name 22 | Value: !Ref AWS::StackName 23 | 24 | # The Internet gateway alone is not enough, it must be attached to a vpc 25 | InternetGatewayAttachment: 26 | Type: AWS::EC2::VPCGatewayAttachment 27 | Properties: 28 | InternetGatewayId: !Ref InternetGateway 29 | VpcId: !Ref VpcId 30 | 31 | # Traffic must be explicitly routed through the internet gateway for bidirectional internet communication 32 | publicRouteTable: 33 | Type: AWS::EC2::RouteTable 34 | Properties: 35 | VpcId: !Ref VpcId 36 | Tags: 37 | - Key: Name 38 | Value: Dmz Routes 39 | - Key: Scope 40 | Value: public 41 | 42 | # add a route to the route table 43 | publicRouteToInternet: 44 | # force Cloudformation to attach the internet gateway before creating the route 45 | DependsOn: InternetGatewayAttachment 46 | Type: AWS::EC2::Route 47 | Properties: 48 | DestinationCidrBlock: 0.0.0.0/0 # all other traffic not destined for the vpc range will be routed through internet gateway 49 | GatewayId: !Ref InternetGateway 50 | RouteTableId: !Ref publicRouteTable 51 | 52 | # Route tables need to be associated with subnets 53 | publicRouteTableAssociation1: 54 | Type: "AWS::EC2::SubnetRouteTableAssociation" 55 | Properties: 56 | RouteTableId: !Ref publicRouteTable 57 | SubnetId: !Select [0, !Ref Subnets] 58 | 59 | publicRouteTableAssociation2: 60 | Type: "AWS::EC2::SubnetRouteTableAssociation" 61 | Properties: 62 | RouteTableId: !Ref publicRouteTable 63 | SubnetId: !Select [1, !Ref Subnets] 64 | 65 | publicRouteTableAssociation3: 66 | Type: "AWS::EC2::SubnetRouteTableAssociation" 67 | Properties: 68 | RouteTableId: !Ref publicRouteTable 69 | SubnetId: !Select [2, !Ref Subnets] 70 | -------------------------------------------------------------------------------- /cloudformation/network-nat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Description: "NAT Gateway and Route Table for egress-only public traffic" 4 | 5 | Parameters: 6 | VpcId: 7 | Type: String 8 | Description: "Specify the VPC ID." 9 | 10 | PublicSubnet: 11 | Type: AWS::EC2::Subnet::Id 12 | 13 | PrivateSubnets: 14 | Type: List 15 | Description: "Specify the subnets that should have protected internet access (hybrid)." 16 | 17 | Resources: 18 | 19 | # Private subnets will need to reach OUT to the internet while remaining PRIVATE 20 | # Network Address Translation solves this, so add a NAT gateway 21 | NatGateway: 22 | Type: AWS::EC2::NatGateway 23 | Properties: 24 | AllocationId: !GetAtt ElasticIP.AllocationId 25 | SubnetId: !Ref PublicSubnet 26 | 27 | # NAT gateway needs a public Elastic IP address 28 | ElasticIP: 29 | Type: AWS::EC2::EIP 30 | Properties: 31 | Domain: vpc 32 | 33 | 34 | # private subnets need a route table 35 | privateRouteTable: 36 | Type: AWS::EC2::RouteTable 37 | Properties: 38 | VpcId: !Ref VpcId 39 | Tags: 40 | - Key: Name 41 | Value: NAT Routes 42 | - Key: Scope 43 | Value: private 44 | 45 | privateRouteToInternet: 46 | Type: AWS::EC2::Route 47 | Properties: 48 | RouteTableId: 49 | Ref: privateRouteTable 50 | DestinationCidrBlock: 0.0.0.0/0 # all traffic not destined for the VPC range 51 | NatGatewayId: !Ref NatGateway # gets routed through the NAT gateway 52 | 53 | # Route tables need to be associated with subnets 54 | privateRouteTableAssociation1: 55 | Type: "AWS::EC2::SubnetRouteTableAssociation" 56 | Properties: 57 | RouteTableId: !Ref privateRouteTable 58 | SubnetId: !Select [0, !Ref PrivateSubnets] 59 | 60 | privateRouteTableAssociation2: 61 | Type: "AWS::EC2::SubnetRouteTableAssociation" 62 | Properties: 63 | RouteTableId: !Ref privateRouteTable 64 | SubnetId: !Select [1, !Ref PrivateSubnets] 65 | 66 | privateRouteTableAssociation3: 67 | Type: "AWS::EC2::SubnetRouteTableAssociation" 68 | Properties: 69 | RouteTableId: !Ref privateRouteTable 70 | SubnetId: !Select [2, !Ref PrivateSubnets] 71 | 72 | 73 | # empty change 74 | -------------------------------------------------------------------------------- /cloudformation/network-vpc-subnets.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Description: "Microservices VPC and Subnets" 4 | 5 | Parameters: 6 | VpcSubnetCidrs: 7 | Type: String 8 | Description: "CIDR block for VPC. Subnets will be derived from this. Minimum size of /21" 9 | Default: "10.100.0.0/16" 10 | 11 | Resources: 12 | Vpc: 13 | Type: "AWS::EC2::VPC" 14 | Properties: 15 | CidrBlock: !Ref VpcSubnetCidrs 16 | EnableDnsSupport: True 17 | EnableDnsHostnames: True 18 | Tags: 19 | - Key: Name 20 | Value: !Ref "AWS::StackName" 21 | 22 | # create three /24 subnets to hold Elastic Load Balancer(s). 251 usable IPs each gives plenty of room to scale 23 | 24 | subnetElbA: 25 | Type: "AWS::EC2::Subnet" 26 | Properties: 27 | AvailabilityZone: !Select [0, !GetAZs ""] 28 | CidrBlock: !Select [0, !Cidr [!GetAtt Vpc.CidrBlock, 6, 8]] 29 | MapPublicIpOnLaunch: true 30 | Tags: 31 | - Key: Name 32 | Value: ELB A 33 | - Key: Scope 34 | Value: public 35 | VpcId: !Ref Vpc 36 | 37 | subnetElbB: 38 | Type: "AWS::EC2::Subnet" 39 | Properties: 40 | AvailabilityZone: !Select [1, !GetAZs ""] 41 | CidrBlock: !Select [1, !Cidr [!GetAtt Vpc.CidrBlock, 6, 8]] 42 | MapPublicIpOnLaunch: true 43 | Tags: 44 | - Key: Name 45 | Value: ELB B 46 | - Key: Scope 47 | Value: public 48 | VpcId: !Ref Vpc 49 | 50 | subnetElbC: 51 | Type: "AWS::EC2::Subnet" 52 | Properties: 53 | AvailabilityZone: !Select [2, !GetAZs ""] 54 | CidrBlock: !Select [2, !Cidr [!GetAtt Vpc.CidrBlock, 6, 8]] 55 | MapPublicIpOnLaunch: true 56 | Tags: 57 | - Key: Name 58 | Value: ELB C 59 | - Key: Scope 60 | Value: public 61 | VpcId: !Ref Vpc 62 | 63 | # create three /24 subnets to hold ec2 instances or fargate ENIs. 251 usable IPs gives plenty of room to scale 64 | 65 | subnetNodesA: 66 | Type: "AWS::EC2::Subnet" 67 | Properties: 68 | AvailabilityZone: !Select [0, !GetAZs ""] 69 | CidrBlock: !Select [3, !Cidr [!GetAtt Vpc.CidrBlock, 6, 8]] 70 | MapPublicIpOnLaunch: false 71 | Tags: 72 | - Key: Name 73 | Value: Cluster Nodes A 74 | - Key: Scope 75 | Value: private 76 | VpcId: !Ref Vpc 77 | 78 | subnetNodesB: 79 | Type: "AWS::EC2::Subnet" 80 | Properties: 81 | AvailabilityZone: !Select [1, !GetAZs ""] 82 | CidrBlock: !Select [4, !Cidr [!GetAtt Vpc.CidrBlock, 6, 8]] 83 | MapPublicIpOnLaunch: false 84 | Tags: 85 | - Key: Name 86 | Value: Cluster Nodes B 87 | - Key: Scope 88 | Value: private 89 | VpcId: !Ref Vpc 90 | 91 | subnetNodesC: 92 | Type: "AWS::EC2::Subnet" 93 | Properties: 94 | AvailabilityZone: !Select [2, !GetAZs ""] 95 | CidrBlock: !Select [5, !Cidr [!GetAtt Vpc.CidrBlock, 6, 8]] 96 | MapPublicIpOnLaunch: false 97 | Tags: 98 | - Key: Name 99 | Value: Cluster Nodes C 100 | - Key: Scope 101 | Value: private 102 | VpcId: !Ref Vpc 103 | 104 | 105 | 106 | Outputs: 107 | VpcId: 108 | Description : "VPC ID" 109 | Value: !Ref Vpc 110 | Export: # export the ID of the Vpc so other stacks can import it 111 | Name: !Sub ${AWS::StackName}-VpcId 112 | 113 | VpcCidr: 114 | Description : "VPC ID" 115 | Value: !GetAtt Vpc.CidrBlock 116 | Export: # export the IP range of the Vpc so other stacks can import it 117 | Name: !Sub ${AWS::StackName}-VpcCidr 118 | 119 | SubnetElbA: 120 | Description : "Public A Subnet ID" 121 | Value: !Ref subnetElbA 122 | Export: 123 | Name: !Sub ${AWS::StackName}-SubnetElbA 124 | 125 | SubnetElbB: 126 | Description : "Public B Subnet ID" 127 | Value: !Ref subnetElbB 128 | Export: 129 | Name: !Sub ${AWS::StackName}-SubnetElbB 130 | 131 | SubnetElbC: 132 | Description : "Public C Subnet ID" 133 | Value: !Ref subnetElbC 134 | Export: 135 | Name: !Sub ${AWS::StackName}-SubnetElbC 136 | 137 | # will be used for ECS cluster auto scaling group later 138 | SubnetNodesA: 139 | Description : "Cluster Nodes Subnet A ID" 140 | Value: !Ref subnetNodesA 141 | Export: 142 | Name: !Sub ${AWS::StackName}-SubnetNodesA 143 | 144 | SubnetNodesB: 145 | Description : "Cluster Nodes Subnet B ID" 146 | Value: !Ref subnetNodesB 147 | Export: 148 | Name: !Sub ${AWS::StackName}-SubnetNodesB 149 | 150 | SubnetNodesC: 151 | Description : "Cluster Nodes Subnet C ID" 152 | Value: !Ref subnetNodesC 153 | Export: 154 | Name: !Sub ${AWS::StackName}-SubnetNodesC 155 | -------------------------------------------------------------------------------- /cloudformation/network.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Description: "Full environment." 4 | 5 | Parameters: 6 | VpcSubnetCidrs: 7 | Type: String 8 | Default: "10.0.0.0/16" 9 | 10 | Bucket: 11 | Type: String 12 | Default: "cerulean-operations" 13 | 14 | Resources: 15 | # Create the VPC and subnets 16 | VpcSubnets: 17 | Type: AWS::CloudFormation::Stack 18 | Properties: 19 | TemplateURL: !Sub https://${Bucket}.s3.amazonaws.com/deploying-containers-aws/network-vpc-subnets.yml 20 | Parameters: 21 | VpcSubnetCidrs: !Ref VpcSubnetCidrs 22 | 23 | 24 | # Create the internet access 25 | InternetAccess: 26 | Type: AWS::CloudFormation::Stack 27 | Properties: 28 | TemplateURL: !Sub https://${Bucket}.s3.amazonaws.com/deploying-containers-aws/network-internet-access.yml 29 | Parameters: 30 | VpcId: !GetAtt VpcSubnets.Outputs.VpcId 31 | Subnets: !Sub 32 | - "${SubnetElbA},${SubnetElbB},${SubnetElbC}" 33 | - {SubnetElbA: !GetAtt VpcSubnets.Outputs.SubnetElbA, SubnetElbB: !GetAtt VpcSubnets.Outputs.SubnetElbB, SubnetElbC: !GetAtt VpcSubnets.Outputs.SubnetElbC} 34 | 35 | 36 | # Create NAT gateway for network address translation 37 | NAT: 38 | Type: AWS::CloudFormation::Stack 39 | DependsOn: InternetAccess 40 | Properties: 41 | TemplateURL: !Sub https://${Bucket}.s3.amazonaws.com/deploying-containers-aws/network-nat.yml 42 | Parameters: 43 | VpcId: !GetAtt VpcSubnets.Outputs.VpcId 44 | PublicSubnet: !GetAtt VpcSubnets.Outputs.SubnetElbA 45 | PrivateSubnets: !Sub 46 | - "${SubnetNodesA},${SubnetNodesB},${SubnetNodesC}" 47 | - {SubnetNodesA: !GetAtt VpcSubnets.Outputs.SubnetNodesA, SubnetNodesB: !GetAtt VpcSubnets.Outputs.SubnetNodesB, SubnetNodesC: !GetAtt VpcSubnets.Outputs.SubnetNodesC} 48 | 49 | Outputs: 50 | ElbSubnets: 51 | Description: "List of subnet IDs for ELB" 52 | Value: !Sub 53 | - "${SubnetElbA},${SubnetElbB},${SubnetElbC}" 54 | - {SubnetElbA: !GetAtt VpcSubnets.Outputs.SubnetElbA, SubnetElbB: !GetAtt VpcSubnets.Outputs.SubnetElbB, SubnetElbC: !GetAtt VpcSubnets.Outputs.SubnetElbC} 55 | Export: 56 | Name: !Sub ${AWS::StackName}-ElbSubnets 57 | 58 | NodeSubnets: 59 | Description: "List subnet IDs for NODES" 60 | Value: !Sub 61 | - "${SubnetNodesA},${SubnetNodesB},${SubnetNodesC}" 62 | - {SubnetNodesA: !GetAtt VpcSubnets.Outputs.SubnetNodesA, SubnetNodesB: !GetAtt VpcSubnets.Outputs.SubnetNodesB, SubnetNodesC: !GetAtt VpcSubnets.Outputs.SubnetNodesC} 63 | Export: 64 | Name: !Sub ${AWS::StackName}-NodeSubnets 65 | -------------------------------------------------------------------------------- /cloudformation/pipeline.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Description: "Microservice CICD pipeline, repo, build, and registry." 4 | Parameters: 5 | ClusterName: 6 | Type: String 7 | Description: "Specify the ECS cluster this deploys to" 8 | Service: 9 | Type: String 10 | Description: "Specify the service this pipeline manages" 11 | ArtifactBucket: 12 | Type: String 13 | Description: "Name of S3 bucket (optional /prefix)" 14 | Default: "cerulean-artifacts-oregon" 15 | 16 | Resources: 17 | 18 | # create a GIT repo for the service's codebase 19 | CodeRepo: 20 | Type: "AWS::CodeCommit::Repository" 21 | Properties: 22 | RepositoryName: !Ref Service 23 | 24 | # create a docker registry for service's container images 25 | ImageRepo: 26 | Type: "AWS::ECR::Repository" 27 | Properties: 28 | RepositoryName: !Ref Service 29 | 30 | # CodeBuild needs a role 31 | CodeBuildServiceRole: 32 | Type: AWS::IAM::Role 33 | Properties: 34 | Path: / 35 | AssumeRolePolicyDocument: 36 | Version: 2012-10-17 37 | Statement: 38 | - Effect: Allow 39 | Principal: 40 | Service: codebuild.amazonaws.com 41 | Action: sts:AssumeRole 42 | Policies: 43 | - PolicyName: root 44 | PolicyDocument: 45 | Version: 2012-10-17 46 | Statement: 47 | # allow CodeBuild to write logs 48 | - Effect: Allow 49 | Action: 50 | - logs:CreateLogGroup 51 | - logs:CreateLogStream 52 | - logs:PutLogEvents 53 | - ecr:GetAuthorizationToken 54 | Resource: "*" 55 | 56 | # allow CodeBuild read/write to S3 57 | - Effect: Allow 58 | Action: 59 | - s3:PutObject 60 | - s3:GetObject 61 | - s3:GetObjectVersion 62 | Resource: !Sub arn:aws:s3:::${ArtifactBucket}/* 63 | 64 | # allow codebuild to publish images to repo 65 | - Effect: Allow 66 | Action: 67 | - ecr:GetDownloadUrlForLayer 68 | - ecr:BatchGetImage 69 | - ecr:BatchCheckLayerAvailability 70 | - ecr:PutImage 71 | - ecr:InitiateLayerUpload 72 | - ecr:UploadLayerPart 73 | - ecr:CompleteLayerUpload 74 | Resource: !Sub arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/${ImageRepo} 75 | 76 | # create a pipeline to manage stages of deployment 77 | CodePipelineServiceRole: 78 | Type: AWS::IAM::Role 79 | Properties: 80 | Path: / 81 | AssumeRolePolicyDocument: 82 | Version: 2012-10-17 83 | Statement: 84 | - Effect: Allow 85 | Principal: 86 | Service: codepipeline.amazonaws.com 87 | Action: sts:AssumeRole 88 | Policies: 89 | - PolicyName: root 90 | PolicyDocument: 91 | Version: 2012-10-17 92 | Statement: 93 | - Effect: Allow 94 | Action: 95 | - codecommit:GetBranch 96 | - codecommit:GetCommit 97 | - codecommit:UploadArchive 98 | - codecommit:GetUploadArchiveStatus 99 | - codecommit:CancelUploadArchive 100 | Resource: !Sub arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${Service} 101 | 102 | - Effect: Allow 103 | Action: 104 | - s3:PutObject 105 | - s3:GetObject 106 | - s3:GetObjectVersion 107 | - s3:GetBucketVersioning 108 | Resource: 109 | - !Sub arn:aws:s3:::${ArtifactBucket} 110 | - !Sub arn:aws:s3:::${ArtifactBucket}/* 111 | 112 | - Effect: Allow 113 | Action: 114 | - ecs:DescribeServices 115 | - ecs:DescribeTaskDefinition 116 | - ecs:DescribeTasks 117 | - ecs:ListTasks 118 | - ecs:RegisterTaskDefinition 119 | - ecs:UpdateService 120 | - codebuild:StartBuild 121 | - codebuild:BatchGetBuilds 122 | - iam:PassRole 123 | Resource: "*" 124 | 125 | # Create a project to build the docker image 126 | CodeBuildProject: 127 | Type: AWS::CodeBuild::Project 128 | Properties: 129 | Artifacts: 130 | Type: CODEPIPELINE 131 | Source: 132 | Type: CODEPIPELINE 133 | BuildSpec: !Sub | 134 | version: 0.2 135 | phases: 136 | pre_build: 137 | commands: 138 | - $(aws ecr get-login --no-include-email) 139 | - TAG="$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)" 140 | - IMAGE_URI="$REPOSITORY_URI:$TAG" 141 | build: 142 | commands: 143 | - docker build --tag "$IMAGE_URI" . 144 | post_build: 145 | commands: 146 | - docker push "$IMAGE_URI" 147 | - printf '[{"name":"${Service}","imageUri":"%s"}]' "$IMAGE_URI" > images.json 148 | artifacts: 149 | files: images.json 150 | Environment: 151 | ComputeType: BUILD_GENERAL1_SMALL 152 | Image: aws/codebuild/docker:17.09.0 153 | Type: LINUX_CONTAINER 154 | EnvironmentVariables: 155 | - Name: AWS_DEFAULT_REGION 156 | Value: !Ref AWS::Region 157 | - Name: REPOSITORY_URI 158 | Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ImageRepo} 159 | Name: !Ref AWS::StackName 160 | ServiceRole: !Ref CodeBuildServiceRole 161 | 162 | Pipeline: 163 | Type: AWS::CodePipeline::Pipeline 164 | Properties: 165 | RoleArn: !GetAtt CodePipelineServiceRole.Arn 166 | ArtifactStore: 167 | Type: S3 168 | Location: !Ref ArtifactBucket 169 | Stages: 170 | - Name: Source 171 | Actions: 172 | - Name: App 173 | ActionTypeId: 174 | Category: Source 175 | Owner: AWS 176 | Version: 1 177 | Provider: CodeCommit 178 | Configuration: 179 | PollForSourceChanges: true 180 | RepositoryName: !Ref Service 181 | BranchName: "master" 182 | OutputArtifacts: 183 | - Name: App 184 | RunOrder: 1 185 | - Name: Build 186 | Actions: 187 | - Name: Build 188 | ActionTypeId: 189 | Category: Build 190 | Owner: AWS 191 | Version: 1 192 | Provider: CodeBuild 193 | Configuration: 194 | ProjectName: !Ref CodeBuildProject 195 | InputArtifacts: 196 | - Name: App 197 | OutputArtifacts: 198 | - Name: BuildOutput 199 | RunOrder: 1 200 | - Name: Deploy 201 | Actions: 202 | - Name: Deploy 203 | ActionTypeId: 204 | Category: Deploy 205 | Owner: AWS 206 | Version: 1 207 | Provider: ECS 208 | Configuration: 209 | ClusterName: !Ref ClusterName 210 | ServiceName: !Ref Service 211 | FileName: images.json 212 | InputArtifacts: 213 | - Name: BuildOutput 214 | RunOrder: 1 215 | -------------------------------------------------------------------------------- /ecs-commands.sh: -------------------------------------------------------------------------------- 1 | 2 | NETWORK_STACK="microservices-network" 3 | REGION="us-east-2" 4 | ARTIFACT_BUCKET="cerulean-operations-us-east-2" # modify this to YOUR OWN value 5 | 6 | # create the base VPC and Subnets (public and private) 7 | aws --region $REGION cloudformation create-stack \ 8 | --stack-name ${NETWORK_STACK} \ 9 | --template-body file://./vpc-subnets.yml \ 10 | --parameters ParameterKey=VpcSubnetCidrs,ParameterValue=10.100.0.0/16 \ 11 | && aws --region $REGION cloudformation wait stack-create-complete --stack-name ${NETWORK_STACK} 12 | 13 | # create the necessary internet access 14 | aws --region $REGION cloudformation create-stack \ 15 | --stack-name ${NETWORK_STACK}-internet \ 16 | --template-body file://./igw-ngw-routes.yml \ 17 | --parameters ParameterKey=NetworkStack,ParameterValue=${NETWORK_STACK} \ 18 | && aws --region $REGION cloudformation wait stack-create-complete --stack-name ${NETWORK_STACK}-internet 19 | 20 | # find latest AMI for ECS 21 | AMI_ID=$(aws --region $REGION ec2 describe-images --owners amazon --filters Name=architecture,Values=x86_64 Name=virtualization-type,Values=hvm Name=root-device-type,Values=ebs Name=name,Values='*amazon-ecs-optimized' --query 'sort_by(Images, &CreationDate)[-1].ImageId' --output text) 22 | 23 | # create autoscaling group and ECS cluster 24 | aws --region $REGION cloudformation create-stack \ 25 | --stack-name microservices-ecs-nodes \ 26 | --template-body file://./ecs-autoscaling-nodes.yml \ 27 | --parameters \ 28 | ParameterKey=ClusterName,ParameterValue=microservices-ecs \ 29 | ParameterKey=NetworkStack,ParameterValue=${NETWORK_STACK} \ 30 | ParameterKey=AMI,ParameterValue=${AMI_ID} \ 31 | ParameterKey=NumNodes,ParameterValue=3 \ 32 | --capabilities "CAPABILITY_NAMED_IAM" \ 33 | && aws --region $REGION cloudformation wait stack-create-complete --stack-name microservices-ecs-cluster 34 | 35 | 36 | # create application load balancer 37 | aws --region $REGION cloudformation create-stack \ 38 | --stack-name microservices-alb-dev-ohio \ 39 | --template-body file://./alb.yml \ 40 | --parameters \ 41 | ParameterKey=NetworkStack,ParameterValue=${NETWORK_STACK} \ 42 | ParameterKey=ElbName,ParameterValue=microservices-dev-ohio \ 43 | && aws --region $REGION cloudformation wait stack-create-complete --stack-name microservices-alb-dev-ohio 44 | 45 | # if creating ECS service for the first time in the account run the following: 46 | # aws --region $REGION iam create-service-linked-role --aws-service-name ecs.amazonaws.com 47 | 48 | # create the ECS service and task for USERS service 49 | aws --region $REGION cloudformation create-stack \ 50 | --stack-name microservices-service-users \ 51 | --template-body file://./ecs-service-task-users.yml \ 52 | --parameters \ 53 | ParameterKey=ClusterName,ParameterValue=microservices-ecs \ 54 | ParameterKey=AlbStack,ParameterValue=microservices-alb-dev-ohio \ 55 | --capabilities "CAPABILITY_NAMED_IAM" \ 56 | && aws --region $REGION cloudformation wait stack-create-complete --stack-name microservices-service-users 57 | 58 | # CodePipeline and CodeBuild require access to a common S3 bucket 59 | aws --region $REGION s3 mb s3://${ARTIFACT_BUCKET} 60 | 61 | # create the CICD pipeline for USERS 62 | aws --region $REGION cloudformation create-stack \ 63 | --stack-name microservices-pipeline-users \ 64 | --template-body file://./pipeline.yml \ 65 | --parameters \ 66 | ParameterKey=ArtifactBucket,ParameterValue=$ARTIFACT_BUCKET \ 67 | ParameterKey=ClusterName,ParameterValue=microservices-ecs \ 68 | ParameterKey=Service,ParameterValue=users \ 69 | --capabilities "CAPABILITY_NAMED_IAM" \ 70 | && aws --region $REGION cloudformation wait stack-create-complete --stack-name microservices-pipeline-users 71 | 72 | # create the ECS service and task for MESSAGES service 73 | # notice that the messages service requires the additional NetworkStack parameter 74 | # this is because the FARGATE launch type will create ENIs (elastic network interface) inside OUR VPC 75 | aws --region $REGION cloudformation create-stack \ 76 | --stack-name microservices-service-messages \ 77 | --template-body file://./ecs-service-task-messages.yml \ 78 | --parameters \ 79 | ParameterKey=NetworkStack,ParameterValue=microservices-network \ 80 | ParameterKey=ClusterName,ParameterValue=microservices-ecs \ 81 | ParameterKey=AlbStack,ParameterValue=microservices-alb-dev-ohio \ 82 | --capabilities "CAPABILITY_NAMED_IAM" \ 83 | && aws --region $REGION cloudformation wait stack-create-complete --stack-name microservices-service-messages 84 | 85 | # create the CICD pipeline for MESSAGES 86 | aws --region $REGION cloudformation create-stack \ 87 | --stack-name microservices-pipeline-messages \ 88 | --template-body file://./pipeline.yml \ 89 | --parameters \ 90 | ParameterKey=ArtifactBucket,ParameterValue=$ARTIFACT_BUCKET \ 91 | ParameterKey=ClusterName,ParameterValue=microservices-ecs \ 92 | ParameterKey=Service,ParameterValue=messages \ 93 | --capabilities "CAPABILITY_NAMED_IAM" \ 94 | && aws --region $REGION cloudformation wait stack-create-complete --stack-name microservices-pipeline-messages 95 | 96 | # create BILLING service, pulling from PRIVATE registry 97 | aws --region $REGION cloudformation create-stack \ 98 | --stack-name microservices-service-billing \ 99 | --template-body file://./ecs-service-task-billing.yml \ 100 | --parameters \ 101 | ParameterKey=ClusterName,ParameterValue=microservices-ecs \ 102 | ParameterKey=AlbStack,ParameterValue=microservices-alb-dev-ohio \ 103 | --capabilities "CAPABILITY_NAMED_IAM" \ 104 | && aws --region $REGION cloudformation wait stack-create-complete --stack-name microservices-service-billing 105 | -------------------------------------------------------------------------------- /kubernetes/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | # update apt 4 | RUN apt-get update 5 | 6 | # install docker 7 | RUN apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common 8 | RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - 9 | RUN add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" 10 | RUN apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io 11 | 12 | # install kubectl 13 | RUN apt-get update && apt-get install -y apt-transport-https 14 | RUN curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - 15 | RUN echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | tee -a /etc/apt/sources.list.d/kubernetes.list 16 | RUN apt-get update && apt-get install -y kubectl 17 | 18 | # install pip3 19 | RUN apt-get install -y python3-pip 20 | 21 | # install aws cli 22 | RUN pip3 install awscli 23 | 24 | # install aws-iam-authenticator 25 | RUN curl -o /usr/bin/aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/1.14.6/2019-08-22/bin/linux/amd64/aws-iam-authenticator 26 | RUN chmod +x /usr/bin/aws-iam-authenticator -------------------------------------------------------------------------------- /kubernetes/aws-auth-cm.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | mapRoles: | 8 | - rolearn: "arn:aws:iam::301228794388:role/microservices-eks-NodeInstanceRole-1Q3ZU0VTIY3OA" 9 | username: system:node:{{EC2PrivateDNSName}} 10 | groups: 11 | - system:bootstrappers 12 | - system:nodes 13 | -------------------------------------------------------------------------------- /kubernetes/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: nginxhello 5 | labels: 6 | app: nginx 7 | spec: 8 | replicas: 3 9 | selector: # allows the deployment to find the pods 10 | matchLabels: 11 | app: nginx 12 | # the following is for the POD 13 | template: 14 | metadata: 15 | labels: 16 | app: nginx 17 | spec: 18 | containers: 19 | - name: nginx 20 | image: nginxdemos/hello 21 | ports: 22 | - containerPort: 80 23 | -------------------------------------------------------------------------------- /kubernetes/eksctl.sh: -------------------------------------------------------------------------------- 1 | eksctl create cluster \ 2 | --name microservices \ 3 | --version 1.13 \ 4 | --nodegroup-name workers \ 5 | --node-type t3.medium \ 6 | --nodes 3 \ 7 | --nodes-min 3 \ 8 | --nodes-max 3 \ 9 | --node-ami auto \ 10 | --vpc-public-subnets=subnet-059e2687d914d6c0c,subnet-064c99608a6f339c6,subnet-01903b5cb71c08418 \ 11 | --vpc-private-subnets=subnet-02a4bfac3b04a977d,subnet-0161e512b30974a02,subnet-03e1b184cf43afecd 12 | 13 | 14 | 15 | # REPLACE the above subnets with YOUR subnets 16 | 17 | kubectl get svc 18 | 19 | kubectl get nodes 20 | 21 | kubectl create -f ./kubernetes/deployment.yml 22 | 23 | kubectl rollout status deployment/nginxhello 24 | 25 | kubectl get pods --show-labels 26 | 27 | kubectl describe deployments 28 | 29 | kubectl get services 30 | # there is not service at this point 31 | kubectl expose deployment nginxhello --type=LoadBalancer --name=helloworld 32 | 33 | kubectl get services 34 | # now we have a service 35 | kubectl get service helloworld -------------------------------------------------------------------------------- /kubernetes/install-tools.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/talentedmrjones/deploying-containers-aws/e37ebad43d6f4e19b7437634f61c2a9aa53cb8e7/kubernetes/install-tools.sh -------------------------------------------------------------------------------- /kubernetes/kops.sh: -------------------------------------------------------------------------------- 1 | export KOPS_STATE_STORE="s3://YOUR_BUCKET_GOES_HERE" 2 | 3 | kops create cluster \ 4 | --name k8s.cerulean.systems \ 5 | --zones us-east-2a,us-east-2b,us-east-2c \ 6 | --master-zones us-east-2a,us-east-2b,us-east-2c \ 7 | --dns-zone cerulean.systems \ 8 | --node-size t2.medium \ 9 | --node-count 3 \ 10 | --master-size t2.medium \ 11 | --master-count 3 \ 12 | 13 | 14 | kops update cluster k8s.cerulean.systems --yes 15 | 16 | kops validate cluster 17 | 18 | kubectl get nodes 19 | 20 | kubectl create -f ./k8s/deployment.yml 21 | 22 | kubectl rollout status deployment/nginxhello 23 | 24 | kubectl get pods --show-labels 25 | 26 | kubectl get services 27 | 28 | kubectl expose deployment nginx-deployment --type=LoadBalancer --name=helloworld 29 | 30 | kubectl get services 31 | 32 | kubectl describe-services helloworld 33 | 34 | kops delete cluster k8s.cerulean.systems --yes 35 | -------------------------------------------------------------------------------- /microservices/messages/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.11.1-alpine 2 | 3 | # Install app dependencies 4 | COPY package.json /src/package.json 5 | RUN cd /src; npm install 6 | 7 | # Bundle app source 8 | COPY . /src 9 | 10 | EXPOSE 80 11 | CMD ["node", "/src/server.js"] 12 | -------------------------------------------------------------------------------- /microservices/messages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "messages", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "dependencies": { 11 | "express": "4.15.3", 12 | "morgan": "1.8.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /microservices/messages/server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const express = require('express') 3 | const morgan = require('morgan') 4 | const app = express() 5 | const pkg = require('./package.json') 6 | 7 | // use morgan to log all request in the Apache combined format to STDOUT 8 | app.use(morgan('combined')) 9 | 10 | // respond to all GET requests 11 | app.get('*', function (req, res) { 12 | res.set('Server', pkg.name + '/v' + pkg.version) 13 | res.json({ message: 'MESSAGES service online', version: pkg.version }); 14 | }) 15 | 16 | app.listen(80, function () { 17 | console.log('service listening on port 80') 18 | }) 19 | -------------------------------------------------------------------------------- /microservices/users/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.11.1-alpine 2 | 3 | # Install app dependencies 4 | COPY package.json /src/package.json 5 | RUN cd /src; npm install 6 | 7 | # Bundle app source 8 | COPY . /src 9 | 10 | EXPOSE 80 11 | CMD ["node", "/src/server.js"] 12 | 13 | 14 | -------------------------------------------------------------------------------- /microservices/users/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "users", 3 | "version": "1.1.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "dependencies": { 11 | "express": "4.15.3", 12 | "morgan": "1.8.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /microservices/users/server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const express = require('express') 3 | const morgan = require('morgan') 4 | const app = express() 5 | const pkg = require('./package.json') 6 | 7 | // use morgan to log all request in the Apache combined format to STDOUT 8 | app.use(morgan('combined')) 9 | 10 | // respond to all GET requests 11 | app.get('*', function (req, res) { 12 | res.set('Server', pkg.name + '/v' + pkg.version) 13 | res.json({ message: 'USERS service online', version: pkg.version }); 14 | }) 15 | 16 | app.listen(80, function () { 17 | console.log('service listening on port 80') 18 | }) 19 | -------------------------------------------------------------------------------- /private-docker-registry/install-docker.sh: -------------------------------------------------------------------------------- 1 | 2 | sudo yum update -y 3 | sudo yum install docker -y 4 | sudo service docker start 5 | sudo usermod -a -G docker ec2-user 6 | docker info 7 | 8 | # install self-signed certificate 9 | # this is OK for EC2 <-> ELB 10 | # this is NOT OK for EC2 <-> Anything else! 11 | openssl req -x509 -newkey rsa:4096 -keyout server-key.pem -out server-cert.pem -days 365 -nodes -sha256 12 | 13 | openssl genrsa -aes256 -out ca-key.pem 4096 14 | openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem 15 | openssl genrsa -out server-key.pem 4096 16 | openssl req -subj "/CN=docker.cerulean.systems" -sha256 -new -key server-key.pem -out server.csr 17 | echo subjectAltName = DNS:docker.cerulean.systems,IP:13.58.175.120 >> extfile.cnf 18 | echo extendedKeyUsage = clientAuth >> extfile.cnf 19 | openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf 20 | 21 | docker run --entrypoint htpasswd registry:2 -Bbn admin password >> ./htpasswd 22 | 23 | docker run -d \ 24 | --restart=always \ 25 | --name registry \ 26 | -v `pwd`:/conf \ 27 | -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \ 28 | -e REGISTRY_HTTP_TLS_CERTIFICATE=/conf/server-cert.pem \ 29 | -e REGISTRY_HTTP_TLS_KEY=/conf/server-key.pem \ 30 | -e REGISTRY_AUTH=htpasswd \ 31 | -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ 32 | -e REGISTRY_AUTH_HTPASSWD_PATH=/conf/htpasswd \ 33 | -p 443:443 \ 34 | registry:2 35 | 36 | # on local 37 | docker pull node:8.11.1-alpine 38 | docker login -u admin -p password docker.cerulean.systems 39 | docker tag node:8.11.1-alpine docker.cerulean.systems/node:8.11.1-alpine 40 | docker push docker.cerulean.systems/node:8.11.1-alpine 41 | curl --user admin:password -X GET https://docker.cerulean.systems/v2/_catalog 42 | --------------------------------------------------------------------------------