From fe549fd96290993e2702f0bc1a86de0ae23b5812 Mon Sep 17 00:00:00 2001
From: Igor Shmukler <shmukler@Igors-MBP-2.attlocal.net>
Date: Mon, 12 Sep 2022 19:17:28 -0400
Subject: [PATCH 1/3] AWS Fargate gRPC cluster example

---
 aws-fargate/Dockerfile   |  12 ++
 aws-fargate/README.md    |  15 ++
 aws-fargate/fargate.yaml | 287 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 314 insertions(+)
 create mode 100644 aws-fargate/Dockerfile
 create mode 100644 aws-fargate/README.md
 create mode 100644 aws-fargate/fargate.yaml

diff --git a/aws-fargate/Dockerfile b/aws-fargate/Dockerfile
new file mode 100644
index 0000000..a2b99fa
--- /dev/null
+++ b/aws-fargate/Dockerfile
@@ -0,0 +1,12 @@
+FROM authzed/spicedb:v1.12.0
+
+ARG SPICEDB_GRPC_PRESHARED_KEY
+ENV SPICEDB_GRPC_PRESHARED_KEY=${SPICEDB_GRPC_PRESHARED_KEY}
+ARG SPICEDB_DATASTORE_ENGINE
+ENV SPICEDB_DATASTORE_ENGINE=${SPICEDB_DATASTORE_ENGINE}
+ARG SPICEDB_DATASTORE_CONN_URI
+ENV SPICEDB_DATASTORE_CONN_URI=${SPICEDB_DATASTORE_CONN_URI}
+
+ENTRYPOINT ["spicedb", "serve"]
+
+EXPOSE 50051/tcp 8080/tcp 9090/tcp
\ No newline at end of file
diff --git a/aws-fargate/README.md b/aws-fargate/README.md
new file mode 100644
index 0000000..e3fb0b1
--- /dev/null
+++ b/aws-fargate/README.md
@@ -0,0 +1,15 @@
+# SpiceDB AWS Fargate cluster
+This reference config will create a Fargate cluster exposing gRPC port `50051` over an SSL endpoint.
+
+## The following commands need to run:
+1. a network VPC is required for Fargate. Usually, one VPC is enough for multiple services.
+   we will reference SubnetA, SubnetB and a VPC ids of our network VPC in the subsequent steps
+2. create a cluster using your values for SubnetA, SubnetB and VPC
+```
+    aws cloudformation create-stack --stack-name permissions-staging --template-body file://./aws/fargate.yaml --parameters ParameterKey=SubnetA,ParameterValue=subnet-0dd.........d9 ParameterKey=SubnetB,ParameterValue=subnet-062........a5 ParameterKey=VPC,ParameterValue=vpc-09a........57 --capabilities CAPABILITY_NAMED_IAM
+```
+
+TODO:
+1. Parametrize: AWSAccountId, CertificateId, HostedZoneName, ServiceName, etc.
+2. Create ECR repository along with the rest of the stack resources
+3. Route traffic on ports 8080 and 9090 for SpiceDB dashboard and metrics
\ No newline at end of file
diff --git a/aws-fargate/fargate.yaml b/aws-fargate/fargate.yaml
new file mode 100644
index 0000000..4533d86
--- /dev/null
+++ b/aws-fargate/fargate.yaml
@@ -0,0 +1,287 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: CloudFormation template for a SpiceDB cluster.
+Parameters:
+  VPC:
+    Type: AWS::EC2::VPC::Id
+  SubnetA:
+    Type: AWS::EC2::Subnet::Id
+  SubnetB:
+    Type: AWS::EC2::Subnet::Id
+  Certificate:
+    Type: String
+    # Update with the certificate ARN from Certificate Manager, which must exist in the same region.
+    # In our case, it is permissions-staging.domain.com
+    Default: 'arn:aws:acm:us-east-1:5xxxxxxxxx3:certificate/6e603b3c-....-....-....-72b1d8d4711b'
+  Image:
+    Type: String
+    # Update with the Docker image. "You can use images in the Docker Hub registry or specify other repositories (repository-url/image:tag)."
+    Default: 5xxxxxxxxx3.dkr.ecr.us-east-1.amazonaws.com/permissions-staging:latest
+  ServiceName:
+    Type: String
+    # update with the name of the service
+    Default: perms-stg-service
+  ContainerPort:
+    Type: Number
+    Default: 50051
+  LoadBalancerPort:
+    Type: Number
+    Default: 50051
+  HealthCheckPath:
+    Type: String
+    Default: '/grpc.health.v1.Health/Check'
+  HostedZoneName:
+    Type: String
+    Default: domain.com
+  Subdomain:
+    Type: String
+    Default: permissions-staging
+  # for autoscaling
+  MinContainers:
+    Type: Number
+    Default: 1
+  # for autoscaling
+  MaxContainers:
+    Type: Number
+    Default: 2
+  # target CPU utilization (%)
+  AutoScalingTargetValue:
+    Type: Number
+    Default: 50
+Resources:
+  Cluster:
+    Type: AWS::ECS::Cluster
+    Properties:
+      ClusterName: !Join ['', [!Ref ServiceName, Cluster]]
+  TaskDefinition:
+    Type: AWS::ECS::TaskDefinition
+    # Makes sure the log group is created before it is used.
+    DependsOn: LogGroup
+    Properties:
+      # Name of the task definition. Subsequent versions of the task definition are grouped together under this name.
+      Family: !Join ['', [!Ref ServiceName, TaskDefinition]]
+      # awsvpc is required for Fargate
+      NetworkMode: awsvpc
+      RequiresCompatibilities:
+        - FARGATE
+      # 256 (.25 vCPU) - Available memory values: 0.5GB, 1GB, 2GB
+      # 512 (.5 vCPU) - Available memory values: 1GB, 2GB, 3GB, 4GB
+      # 1024 (1 vCPU) - Available memory values: 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB
+      # 2048 (2 vCPU) - Available memory values: Between 4GB and 16GB in 1GB increments
+      # 4096 (4 vCPU) - Available memory values: Between 8GB and 30GB in 1GB increments
+      Cpu: 256
+      # 0.5GB, 1GB, 2GB - Available cpu values: 256 (.25 vCPU)
+      # 1GB, 2GB, 3GB, 4GB - Available cpu values: 512 (.5 vCPU)
+      # 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB - Available cpu values: 1024 (1 vCPU)
+      # Between 4GB and 16GB in 1GB increments - Available cpu values: 2048 (2 vCPU)
+      # Between 8GB and 30GB in 1GB increments - Available cpu values: 4096 (4 vCPU)
+      Memory: 512
+      # A role needed by ECS.
+      # "The ARN of the task execution role that containers in this task can assume. All containers in this task are granted the permissions that are specified in this role."
+      # "There is an optional task execution IAM role that you can specify with Fargate to allow your Fargate tasks to make API calls to Amazon ECR."
+      ExecutionRoleArn: !Ref ExecutionRole
+      # "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that grants containers in the task permission to call AWS APIs on your behalf."
+      TaskRoleArn: !Ref TaskRole
+      ContainerDefinitions:
+        - Name: !Ref ServiceName
+          Image: !Ref Image
+          PortMappings:
+            - ContainerPort: !Ref ContainerPort
+          # Send logs to CloudWatch Logs
+          LogConfiguration:
+            LogDriver: awslogs
+            Options:
+              awslogs-region: !Ref AWS::Region
+              awslogs-group: !Ref LogGroup
+              awslogs-stream-prefix: ecs
+  # A role needed by ECS
+  ExecutionRole:
+    Type: AWS::IAM::Role
+    Properties:
+      RoleName: !Join ['', [!Ref ServiceName, ExecutionRole]]
+      AssumeRolePolicyDocument:
+        Statement:
+          - Effect: Allow
+            Principal:
+              Service: ecs-tasks.amazonaws.com
+            Action: 'sts:AssumeRole'
+      ManagedPolicyArns:
+        - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'
+  # A role for the containers
+  TaskRole:
+    Type: AWS::IAM::Role
+    Properties:
+      RoleName: !Join ['', [!Ref ServiceName, TaskRole]]
+      AssumeRolePolicyDocument:
+        Statement:
+          - Effect: Allow
+            Principal:
+              Service: ecs-tasks.amazonaws.com
+            Action: 'sts:AssumeRole'
+      # ManagedPolicyArns:
+      #   -
+      # Policies:
+      #   -
+  # A role needed for auto scaling
+  AutoScalingRole:
+    Type: AWS::IAM::Role
+    Properties:
+      RoleName: !Join ['', [!Ref ServiceName, AutoScalingRole]]
+      AssumeRolePolicyDocument:
+        Statement:
+          - Effect: Allow
+            Principal:
+              Service: ecs-tasks.amazonaws.com
+            Action: 'sts:AssumeRole'
+      ManagedPolicyArns:
+        - 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole'
+  ContainerSecurityGroup:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      GroupDescription: !Join ['', [!Ref ServiceName, ContainerSecurityGroup]]
+      VpcId: !Ref VPC
+      SecurityGroupIngress:
+        - IpProtocol: tcp
+          FromPort: !Ref ContainerPort
+          ToPort: !Ref ContainerPort
+          SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
+  LoadBalancerSecurityGroup:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      GroupDescription:
+        !Join ['', [!Ref ServiceName, LoadBalancerSecurityGroup]]
+      VpcId: !Ref VPC
+      SecurityGroupIngress:
+        - IpProtocol: tcp
+          FromPort: !Ref LoadBalancerPort
+          ToPort: !Ref LoadBalancerPort
+          CidrIp: 0.0.0.0/0
+        - IpProtocol: tcp
+          FromPort: 80
+          ToPort: 80
+          CidrIp: 0.0.0.0/0
+  Service:
+    Type: AWS::ECS::Service
+    # This dependency is needed so that the load balancer is setup correctly in time
+    DependsOn:
+      - LoadBalancerListener
+    Properties:
+      ServiceName: !Ref ServiceName
+      Cluster: !Ref Cluster
+      TaskDefinition: !Ref TaskDefinition
+      DeploymentConfiguration:
+        MinimumHealthyPercent: 100
+        MaximumPercent: 200
+      DesiredCount: 2
+      # This may need to be adjusted if the container takes a while to start up
+      HealthCheckGracePeriodSeconds: 30
+      LaunchType: FARGATE
+      NetworkConfiguration:
+        AwsvpcConfiguration:
+          # change to DISABLED if you're using private subnets that have access to a NAT gateway
+          AssignPublicIp: ENABLED
+          Subnets:
+            - !Ref SubnetA
+            - !Ref SubnetB
+          SecurityGroups:
+            - !Ref ContainerSecurityGroup
+      LoadBalancers:
+        - ContainerName: !Ref ServiceName
+          ContainerPort: !Ref ContainerPort
+          TargetGroupArn: !Ref TargetGroup
+  TargetGroup:
+    Type: AWS::ElasticLoadBalancingV2::TargetGroup
+    Properties:
+      HealthCheckIntervalSeconds: 300
+      # will look for a 200 status code by default unless specified otherwise
+      HealthCheckPath: !Ref HealthCheckPath
+      HealthCheckTimeoutSeconds: 5
+      UnhealthyThresholdCount: 2
+      HealthyThresholdCount: 2
+      # gRPC -specific configuration not tested
+      HealthCheckEnabled: true
+      HealthCheckPort: 'traffic-port'
+      HealthCheckProtocol: HTTP
+      # end of gRPC -specific configuration
+      Name: !Join ['', [!Ref ServiceName, TargetGroup]]
+      Port: !Ref ContainerPort
+      Protocol: HTTP
+      # gRPC -specific configuration not tested
+      ProtocolVersion: GRPC
+      Matcher:
+        GrpcCode: 0
+      # end of gRPC -specific configuration
+      TargetGroupAttributes:
+        - Key: deregistration_delay.timeout_seconds
+          Value: 300 # default is 300
+      TargetType: ip
+      VpcId: !Ref VPC
+  LoadBalancerListener:
+    Type: AWS::ElasticLoadBalancingV2::Listener
+    Properties:
+      LoadBalancerArn: !Ref LoadBalancer
+      Port: !Ref LoadBalancerPort
+      Protocol: HTTPS
+      SslPolicy: "ELBSecurityPolicy-2016-08"
+      Certificates:
+        - CertificateArn: !Ref Certificate
+      DefaultActions:
+        - Order: 1
+          TargetGroupArn: !Ref TargetGroup
+          Type: "forward"
+  LoadBalancer:
+    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+    Properties:
+      LoadBalancerAttributes:
+        # this is the default, but is specified here in case it needs to be changed
+        - Key: idle_timeout.timeout_seconds
+          Value: 60
+      Name: !Join ['', [!Ref ServiceName, LoadBalancer]]
+      # "internal" is also an option
+      Scheme: internet-facing
+      SecurityGroups:
+        - !Ref LoadBalancerSecurityGroup
+      Subnets:
+        - !Ref SubnetA
+        - !Ref SubnetB
+  DNSRecord:
+    Type: AWS::Route53::RecordSet
+    Properties:
+      HostedZoneName: !Join ['', [!Ref HostedZoneName, .]]
+      Name: !Join ['', [!Ref Subdomain, ., !Ref HostedZoneName, .]]
+      Type: A
+      AliasTarget:
+        DNSName: !GetAtt LoadBalancer.DNSName
+        HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID
+  LogGroup:
+    Type: AWS::Logs::LogGroup
+    Properties:
+      LogGroupName: !Join ['', [/ecs/, !Ref Subdomain]]
+      RetentionInDays: 1
+  AutoScalingTarget:
+    Type: AWS::ApplicationAutoScaling::ScalableTarget
+    Properties:
+      MinCapacity: !Ref MinContainers
+      MaxCapacity: !Ref MaxContainers
+      ResourceId: !Join ['/', [service, !Ref Cluster, !GetAtt Service.Name]]
+      ScalableDimension: ecs:service:DesiredCount
+      ServiceNamespace: ecs
+      # "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that allows Application Auto Scaling to modify your scalable target."
+      RoleARN: !GetAtt AutoScalingRole.Arn
+  AutoScalingPolicy:
+    Type: AWS::ApplicationAutoScaling::ScalingPolicy
+    Properties:
+      PolicyName: !Join ['', [!Ref ServiceName, AutoScalingPolicy]]
+      PolicyType: TargetTrackingScaling
+      ScalingTargetId: !Ref AutoScalingTarget
+      TargetTrackingScalingPolicyConfiguration:
+        PredefinedMetricSpecification:
+          PredefinedMetricType: ECSServiceAverageCPUUtilization
+        ScaleInCooldown: 10
+        ScaleOutCooldown: 10
+        # Keep things at or lower than 50% CPU utilization, for example
+        TargetValue: !Ref AutoScalingTargetValue
+
+Outputs:
+  Endpoint:
+    Description: Endpoint
+    Value: !Join ['', ['https://', !Ref DNSRecord]]
\ No newline at end of file

From 82f3be8130c24dabfffe5aeea19f35b4db2cbd28 Mon Sep 17 00:00:00 2001
From: Igor Shmukler <igor.shmukler@gmail.com>
Date: Mon, 12 Sep 2022 21:02:15 -0400
Subject: [PATCH 2/3] updated email address

---
 aws-fargate/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aws-fargate/README.md b/aws-fargate/README.md
index e3fb0b1..c674a06 100644
--- a/aws-fargate/README.md
+++ b/aws-fargate/README.md
@@ -12,4 +12,4 @@ This reference config will create a Fargate cluster exposing gRPC port `50051` o
 TODO:
 1. Parametrize: AWSAccountId, CertificateId, HostedZoneName, ServiceName, etc.
 2. Create ECR repository along with the rest of the stack resources
-3. Route traffic on ports 8080 and 9090 for SpiceDB dashboard and metrics
\ No newline at end of file
+3. Route traffic on ports 8080 and 9090 for SpiceDB dashboard and metrics

From 5235840a8c1859f34bac6ccec8baf0a587a8c605 Mon Sep 17 00:00:00 2001
From: Igor Shmukler <igor.shmukler@gmail.com>
Date: Tue, 13 Sep 2022 10:09:13 -0400
Subject: [PATCH 3/3] removed obsolete comments

---
 aws-fargate/fargate.yaml | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/aws-fargate/fargate.yaml b/aws-fargate/fargate.yaml
index 4533d86..daea3fd 100644
--- a/aws-fargate/fargate.yaml
+++ b/aws-fargate/fargate.yaml
@@ -192,12 +192,10 @@ Resources:
     Type: AWS::ElasticLoadBalancingV2::TargetGroup
     Properties:
       HealthCheckIntervalSeconds: 300
-      # will look for a 200 status code by default unless specified otherwise
       HealthCheckPath: !Ref HealthCheckPath
       HealthCheckTimeoutSeconds: 5
       UnhealthyThresholdCount: 2
       HealthyThresholdCount: 2
-      # gRPC -specific configuration not tested
       HealthCheckEnabled: true
       HealthCheckPort: 'traffic-port'
       HealthCheckProtocol: HTTP
@@ -205,11 +203,9 @@ Resources:
       Name: !Join ['', [!Ref ServiceName, TargetGroup]]
       Port: !Ref ContainerPort
       Protocol: HTTP
-      # gRPC -specific configuration not tested
       ProtocolVersion: GRPC
       Matcher:
         GrpcCode: 0
-      # end of gRPC -specific configuration
       TargetGroupAttributes:
         - Key: deregistration_delay.timeout_seconds
           Value: 300 # default is 300
@@ -284,4 +280,4 @@ Resources:
 Outputs:
   Endpoint:
     Description: Endpoint
-    Value: !Join ['', ['https://', !Ref DNSRecord]]
\ No newline at end of file
+    Value: !Join ['', ['https://', !Ref DNSRecord]]