diff --git a/.github/workflows/push_develop.yaml b/.github/workflows/push_develop.yaml new file mode 100644 index 0000000..0423780 --- /dev/null +++ b/.github/workflows/push_develop.yaml @@ -0,0 +1,25 @@ + +name: Push Develop Release + +on: + push: + branches-ignore: + - master + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v1 + - name: Ship to S3 + uses: jakejarvis/s3-sync-action@master + with: + args: --follow-symlinks --delete + env: + SOURCE_DIR: cloudformation + AWS_REGION: "us-east-1" + DEST_DIR: grafana/develop/cloudformation + AWS_S3_BUCKET: sumu-stacks + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} \ No newline at end of file diff --git a/.github/workflows/push_production.yaml b/.github/workflows/push_production.yaml new file mode 100644 index 0000000..6bfb313 --- /dev/null +++ b/.github/workflows/push_production.yaml @@ -0,0 +1,25 @@ + +name: Push Production Release + +on: + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v1 + - name: Ship to S3 + uses: jakejarvis/s3-sync-action@master + with: + args: --follow-symlinks --delete + env: + SOURCE_DIR: cloudformation + AWS_REGION: "us-east-1" + DEST_DIR: grafana/production/cloudformation + AWS_S3_BUCKET: sumu-stacks + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30fa1ce --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config \ No newline at end of file diff --git a/README.md b/README.md index fc8e403..82636be 100644 --- a/README.md +++ b/README.md @@ -1 +1,27 @@ -# aws-ecs-grafana \ No newline at end of file +# Grafana on ECS + +CloudFormation stack for creating an ECS service for Grafana. + +## Launching Stack + +Make sure you are logged into the AWS Console and have permissions then click: + +[![Launch Stack](https://cdn.rawgit.com/buildkite/cloudformation-launch-stack-button-svg/master/launch-stack.svg)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/create/template?stackName=Nakama&templateURL=https://sumu-stacks.s3.amazonaws.com/grafana/production/cloudformation/grafana/top.yaml) + +Fill out the parameters and launch! + +--- + +Creates: + +- ALB +- DNS Records (optional) +- ECS Service +- ECS Task + +Requires: + +- ECS Cluster +- VPC Id +- ACM Certificate +- Subnet Ids \ No newline at end of file diff --git a/cloudformation/grafana/dns.yaml b/cloudformation/grafana/dns.yaml new file mode 100644 index 0000000..7622e4e --- /dev/null +++ b/cloudformation/grafana/dns.yaml @@ -0,0 +1,44 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: Grafana DNS stack +Parameters: + #------------------------ + # Deployment Information + #------------------------ + environment: + Type: String + Description: Name of the environment + Default: production + + #----------------------- + # Route53 Configuration + #----------------------- + Domain: + Type: String + Description: The HostedZoneName to create the endpoint on + SubDomain: + Type: String + Description: The subdomain to be used by grafana + + #----------- + # Resources + #----------- + GrafanaDns: + Type: String + Description: Load balancer dns endpoint for Grafana + +Resources: + GrafanaEndpoint: + Type: AWS::Route53::RecordSet + Properties: + HostedZoneName: !Sub "${Domain}." + Comment: 'DNS name for nakama' + Name: !Sub "${SubDomain}.${Domain}." + Type: CNAME + TTL: '300' + ResourceRecords: + - !Ref GrafanaDns + +Outputs: + GrafanaEndpoint: + Description: 'DNS name for Grafana' + Value: !Sub "${SubDomain}.${Domain}." \ No newline at end of file diff --git a/cloudformation/grafana/load_balancing.yaml b/cloudformation/grafana/load_balancing.yaml new file mode 100644 index 0000000..923e6a0 --- /dev/null +++ b/cloudformation/grafana/load_balancing.yaml @@ -0,0 +1,136 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: Grafana load balancing stack +Parameters: + environment: + Type: String + Description: Name of the environment + Default: production + release: + Type: String + Description: Name of the release name of the stack version to use. + Default: production + AllowedValues: ['develop', 'production'] + ConstraintDescription: "Must be a possible release version." + PublicSubnets: + Description: The public subnets for the ALB to run in. + Type: String + PortalCertificate: + Description: Arn of AWS Certificate + Type: String + VpcId: + Description: ID of the VPC + Type: AWS::EC2::VPC::Id + +Resources: + + #-- Application Load Balancer --# + PublicALB: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Type: application + LoadBalancerAttributes: + - Key: deletion_protection.enabled + Value: false + - Key: idle_timeout.timeout_seconds + Value: 60 + Scheme: internet-facing + SecurityGroups: + - !Ref AlbSecurityGroup + Subnets: !Split [",", !Ref PublicSubnets] + Tags: + - Key: Name + Value: !Sub "Grafana-${environment}-ALB" + - Key: environment + Value: !Ref environment + + AlbSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: ECS Allowed Ports + VpcId: !Ref VpcId + SecurityGroupIngress: + - IpProtocol: icmp + FromPort: "-1" + ToPort: "-1" + CidrIp: 0.0.0.0/0 + - IpProtocol: tcp + FromPort: "443" + ToPort: "443" + CidrIp: 0.0.0.0/0 + - IpProtocol: tcp + FromPort: "80" + ToPort: "80" + CidrIp: 0.0.0.0/0 + SecurityGroupEgress: + - IpProtocol: icmp + FromPort: "-1" + ToPort: "-1" + CidrIp: 0.0.0.0/0 + - IpProtocol: tcp + FromPort: "0" + ToPort: "65535" + CidrIp: 0.0.0.0/0 + - IpProtocol: udp + FromPort: "0" + ToPort: "65535" + CidrIp: 0.0.0.0/0 + + # Target group for admin portal port + AdminPortalTargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + HealthCheckIntervalSeconds: 30 + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: 15 + HealthyThresholdCount: 2 + UnhealthyThresholdCount: 2 + Matcher: + HttpCode: '200' + HealthCheckPath: '/api/health' + Port: 3000 + Protocol: HTTP + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: '20' + VpcId: !Ref 'VpcId' + Tags: + - Key: Name + Value: !Sub 'grafana-${release}' + + # HTTPS for Admin Portal + AdminPortalAlbListener: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + Certificates: + - CertificateArn: !Ref PortalCertificate + DefaultActions: + - Type: forward + TargetGroupArn: !Ref AdminPortalTargetGroup + LoadBalancerArn: !Ref PublicALB + Port: 443 + Protocol: HTTPS + + # Redirect HTTP -> HTTPS + AdminPortalRedirectAlbListener: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + DefaultActions: + - Type: redirect + RedirectConfig: + Protocol: HTTPS + Port: 443 + Host: '#{host}' + Path: '/#{path}' + Query: '#{query}' + StatusCode: HTTP_301 + LoadBalancerArn: !Ref PublicALB + Port: 80 + Protocol: HTTP + +Outputs: + AdminPortalTargetGroup: + Description: "" + Value: !Ref AdminPortalTargetGroup + PublicAlbDnsName: + Description: "" + Value: !GetAtt PublicALB.DNSName \ No newline at end of file diff --git a/cloudformation/grafana/task.yaml b/cloudformation/grafana/task.yaml new file mode 100644 index 0000000..2324183 --- /dev/null +++ b/cloudformation/grafana/task.yaml @@ -0,0 +1,39 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Grafana ECS Task +Parameters: + LogGroupName: + Type: String + Description: The AWS CloudWatch log group to output logs to. + Default: "/ecs/grafana" + +Resources: + + + LogGroup: + Type: AWS::Logs::LogGroup + Properties: + RetentionInDays: 7 + LogGroupName: !Ref LogGroupName + + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + ContainerDefinitions: + - Name: grafana + Essential: 'true' + Image: "grafana/grafana:6.7.3" + MemoryReservation: 800 + PortMappings: + - HostPort: 0 + ContainerPort: 3000 + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-region: + Ref: AWS::Region + awslogs-group: + Ref: LogGroup +Outputs: + TaskArn: + Description: ARN of the TaskDefinition + Value: !Ref TaskDefinition \ No newline at end of file diff --git a/cloudformation/grafana/top.yaml b/cloudformation/grafana/top.yaml new file mode 100644 index 0000000..a9c5319 --- /dev/null +++ b/cloudformation/grafana/top.yaml @@ -0,0 +1,112 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Grafana ECS Service +Parameters: + #------------------------ + # Deployment Information + #------------------------ + environment: + Type: String + Description: Name of the environment to use in naming. + Default: production + release: + Type: String + Description: Name of the release name of the stack version to use. + Default: production + AllowedValues: ['develop', 'production'] + ConstraintDescription: "Must be a possible release version." + VpcId: + Description: ID of the VPC + Type: AWS::EC2::VPC::Id + + #------------------- + # ECS Configuration + #------------------- + EcsCluster: + Type: String + Description: The cluster to run the Grafana service on. + + #----------------- + # Load Balancing + #----------------- + PublicSubnets: + Description: The public subnets for the ALB to run in. + Type: String + PortalCertificate: + Description: Arn of AWS Certificate + Type: String + + #----- + # DNS + #----- + Domain: + Type: String + Description: The domain to create the endpoint on (Must have an existing hosted zone ex. `example.com`) Leave blank to skip DNS. + Default: "" + SubDomain: + Type: String + Description: The subdomain to be used by grafana. (ex. `grafana.example.com`) + Default: grafana + + #------------ + # CloudWatch + #------------ + LogGroup: + Type: String + Description: The AWS CloudWatch log group to output logs to. + Default: "/ecs/grafana" + +Conditions: + CreateDns: !Not [!Equals [!Ref Domain, ""]] + +Resources: + + #----- + # DNS + #----- + DnsRecords: + Condition: CreateDns + Type: AWS::CloudFormation::Stack + Properties: + TemplateURL: !Sub 'https://s3.${AWS::Region}.amazonaws.com/sumu-stacks/grafana/${release}/cloudformation/grafana/dns.yaml' + Parameters: + environment: !Ref environment + Domain: !Ref Domain + SubDomain: !Ref SubDomain + GrafanaDns: !GetAtt LoadBalancing.Outputs.PublicAlbDnsName + + #----------------- + # Load Balancing + #----------------- + LoadBalancing: + Type: AWS::CloudFormation::Stack + Properties: + TemplateURL: !Sub 'https://s3.${AWS::Region}.amazonaws.com/sumu-stacks/grafana/${release}/cloudformation/grafana/load_balancing.yaml' + Parameters: + environment: !Ref environment + release: !Ref release + VpcId: !Ref VpcId + PublicSubnets: !Ref PublicSubnets + PortalCertificate: !Ref PortalCertificate + + #------------------- + # ECS Task & Service + #------------------- + TaskDefinition: + Type: AWS::CloudFormation::Stack + Properties: + TemplateURL: !Sub 'https://s3.${AWS::Region}.amazonaws.com/sumu-stacks/grafana/${release}/cloudformation/grafana/task.yaml' + Parameters: + LogGroupName: !Ref LogGroup + + + EcsService: + DependsOn: LoadBalancing + Type: AWS::ECS::Service + Properties: + Cluster: !Ref EcsCluster + DesiredCount: 1 + TaskDefinition: !GetAtt TaskDefinition.Outputs.TaskArn + LoadBalancers: + - ContainerName: "grafana" + ContainerPort: 3000 + TargetGroupArn: !GetAtt LoadBalancing.Outputs.AdminPortalTargetGroup \ No newline at end of file