diff --git a/docs/cloudformation/README.md b/docs/cloudformation/README.md new file mode 100644 index 0000000..33849b6 --- /dev/null +++ b/docs/cloudformation/README.md @@ -0,0 +1,108 @@ +# Deployment Documentation for WhyLabs Containerized Application + +This document provides a comprehensive guide for deploying the WhyLabs +containerized application using an AWS CloudFormation template. The template is +designed to facilitate an easy and efficient setup for users who may not be +familiar with cloud infrastructure. + +## Prerequisites + +Before you begin the deployment, ensure you have the following prerequisites: + +1. **AWS Account**: Access to an AWS account with permissions to create and +manage AWS CloudFormation stacks and associated resources such as EC2 instances, +VPCs, subnets, and security groups. + +1. **API Keys and Credentials**: + + - **WhyLabs API Key and Container Password**: These are essential for the + application to function. If you do not have these credentials, please contact + WhyLabs to obtain them. + + - **Docker Registry Credentials**: Username and password for the Docker + registry where the WhyLabs container image is hosted. These credentials are + necessary for your EC2 instances to pull the container image. + + ```bash + docker login registry.gitlab.com -u "${username}" -p "${password}" + ``` + +## Deployment Steps + +### Step 1: Upload the CloudFormation Template + +Log in to your AWS account and navigate to the AWS CloudFormation service. +Select "Create stack" and upload the provided CloudFormation template. AWS +CloudFormation provides a user-friendly interface to input all required +parameters. + +### Step 2: Input Parameters + +You will be prompted to enter values for various parameters. Here's a brief +explanation of key parameters: + +- **API Keys and Credentials**: Enter your WhyLabs API key, container password, +and Docker registry credentials. These fields are secured (NoEcho: true) to +ensure sensitive information is not displayed in the console. + +- **VPC and Subnet Configuration**: By default, the template will create a new +VPC and subnets. However, you can provide an existing VPC and subnet IDs if you +prefer to use existing networking resources. + +- **Ingress and Egress Configuration**: Define CIDR blocks for ingress and +egress to control network traffic. Ingress CIDR determines who can access your +application, while Egress CIDR controls where your instances can send traffic. + +### Step 3: Deploy the Stack + +After filling in the parameters, proceed to deploy the stack. AWS CloudFormation +will handle the resource creation and configuration as defined in the template. + +### Step 4: Accessing the Application + +Once the stack deployment is complete, navigate to the "Outputs" section of your +CloudFormation stack. Here you will find the URL endpoint for your WhyLabs +application. This URL is the entry point to your deployed service. + +## Resource Description and Architecture + +By default, the CloudFormation template creates the following resources: + +- **Elastic Load Balancing (ELB)**: Distributes incoming application traffic +across multiple targets, such as EC2 instances. + +- **Auto Scaling Group**: Ensures that you have the correct number of Amazon EC2 +instances available to handle the load for your application. + +- **VPC, Subnets, and Routing**: Network configuration that provides isolation +and routing for your application instances. + +- **Security Groups**: Acts as a virtual firewall to control inbound and +outbound traffic. + +## Security Considerations + +- **Security Groups**: Configured to allow inbound traffic on specified ports +only from trusted sources (controlled by the Ingress CIDR block). + +- **IAM Role**: Ensure minimal access rights that are necessary for the +resources to operate. + +## Common Scenarios and Overrides + +- **Using an Existing VPC**: If you prefer to use an existing VPC, provide the +VPC ID and IDs for two public subnets and one private subnet. This setup is +critical for integrating the application into your existing AWS environment. + +- **Custom CIDR Blocks**: You can override the default VPC CIDR block for custom +network planning. Adjusting the Ingress CIDR block can restrict access to your +application, ensuring that only traffic from specific networks can access the +load balancer. + +- **Egress CIDR**: Controls the outbound traffic from your instances. Configure +this to restrict outbound access to the internet or other services within your +network. + +This guide aims to equip you with the necessary information to successfully +deploy and manage your WhyLabs application using AWS CloudFormation. For any +additional questions or assistance, please reach out to WhyLabs support. diff --git a/docs/cloudformation/template.yaml b/docs/cloudformation/template.yaml new file mode 100644 index 0000000..ef022a6 --- /dev/null +++ b/docs/cloudformation/template.yaml @@ -0,0 +1,337 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Deployment of Containerized Applications on EC2 Instances + +Parameters: + 001WhyLabsApiKey: + Type: String + Description: "[REQUIRED] API key for WhyLabs" + NoEcho: true + + 002WhyLabsContainerPassword: + Type: String + Description: "[REQUIRED] Password for WhyLabs container" + NoEcho: true + + 003DockerRegistryPassword: + Type: String + Description: "[REQUIRED] Password for Docker registry authentication" + NoEcho: true + + 004DockerRegistryUsername: + Type: String + Description: "[REQUIRED] Username for Docker registry authentication" + NoEcho: true + + DockerRegistryUrl: + Type: String + Default: registry.gitlab.com + Description: URL of the private Docker registry + + InstanceType: + Type: String + Default: c6i.xlarge + Description: EC2 instance type. + + ReplicaCount: + Type: Number + Default: 1 + Description: Number of EC2 instances to launch. + + ExistingVpcId: + Type: String + Default: "" + Description: Optional. Existing VPC ID to use instead of creating a new VPC. Leave empty to create a new VPC. + + VpcCidr: + Type: String + Default: 10.0.0.0/16 + Description: The CIDR block for the VPC. + + PublicSubnetId1: + Type: String + Default: "" + Description: Optional. ID of the first public subnet for the ALB. Required if using an existing VPC. + + PublicSubnetId2: + Type: String + Default: "" + Description: Optional. ID of the second public subnet for the ALB. Required if using an existing VPC. + + PrivateSubnetId: + Type: String + Default: "" + Description: Optional. ID of the private subnet for instances. Required if using an existing VPC. + + IngressCidr: + Type: String + Default: 0.0.0.0/0 + Description: CIDR block for allowing ingress traffic. + + EgressCidr: + Type: String + Default: 0.0.0.0/0 + Description: CIDR block for allowing egress traffic. + + IngressPort: + Type: Number + Default: 80 + Description: The TCP port for ingress traffic. + + EgressPort: + Type: Number + Default: 80 + Description: The TCP port for egress traffic. + + AMIId: + Type: String + Default: ami-0ddda618e961f2270 + Description: The ID of the AMI to be used for the EC2 instances. + +Conditions: + CreateVpc: !Equals [!Ref ExistingVpcId, ""] + +Resources: + ##### Networking + StackVPC: + Type: AWS::EC2::VPC + Condition: CreateVpc + Properties: + CidrBlock: !Ref VpcCidr + EnableDnsSupport: true + EnableDnsHostnames: true + + InternetGateway: + Type: AWS::EC2::InternetGateway + Condition: CreateVpc + + VPCGatewayAttachment: + Type: AWS::EC2::VPCGatewayAttachment + Condition: CreateVpc + Properties: + VpcId: !If [CreateVpc, !Ref StackVPC, !Ref ExistingVpcId] + InternetGatewayId: !Ref InternetGateway + + PrivateSubnet: + Type: AWS::EC2::Subnet + Condition: CreateVpc + Properties: + VpcId: !If [CreateVpc, !Ref StackVPC, !Ref ExistingVpcId] + CidrBlock: 10.0.5.0/24 + MapPublicIpOnLaunch: false + AvailabilityZone: !Select + - 0 + - !GetAZs '' + + PublicSubnet1: + Type: AWS::EC2::Subnet + Condition: CreateVpc + Properties: + VpcId: !If [CreateVpc, !Ref StackVPC, !Ref ExistingVpcId] + CidrBlock: 10.0.1.0/24 + MapPublicIpOnLaunch: true + AvailabilityZone: !Select + - 0 + - !GetAZs '' + + PublicSubnet2: + Type: AWS::EC2::Subnet + Condition: CreateVpc + Properties: + VpcId: !If [CreateVpc, !Ref StackVPC, !Ref ExistingVpcId] + CidrBlock: 10.0.3.0/24 + MapPublicIpOnLaunch: true + AvailabilityZone: !Select + - 1 + - !GetAZs '' + + NatGatewayEIP: + Type: AWS::EC2::EIP + Condition: CreateVpc + Properties: + Domain: VPC + + NatGateway: + Type: AWS::EC2::NatGateway + Condition: CreateVpc + Properties: + AllocationId: !GetAtt NatGatewayEIP.AllocationId + SubnetId: !Ref PublicSubnet1 + + PublicRouteTable: + Type: AWS::EC2::RouteTable + Condition: CreateVpc + Properties: + VpcId: !If [CreateVpc, !Ref StackVPC, !Ref ExistingVpcId] + + PublicRoute: + Type: AWS::EC2::Route + Condition: CreateVpc + Properties: + RouteTableId: !Ref PublicRouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref InternetGateway + + PrivateRouteTable: + Type: AWS::EC2::RouteTable + Condition: CreateVpc + Properties: + VpcId: !If [CreateVpc, !Ref StackVPC, !Ref ExistingVpcId] + + PrivateRoute: + Type: AWS::EC2::Route + Condition: CreateVpc + Properties: + RouteTableId: !Ref PrivateRouteTable + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGateway + + PublicRouteTableAssociation1: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: CreateVpc + Properties: + SubnetId: !Ref PublicSubnet1 + RouteTableId: !Ref PublicRouteTable + + PublicRouteTableAssociation2: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: CreateVpc + Properties: + SubnetId: !Ref PublicSubnet2 + RouteTableId: !Ref PublicRouteTable + + PrivateRouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Condition: CreateVpc + Properties: + SubnetId: !Ref PrivateSubnet + RouteTableId: !Ref PrivateRouteTable + + ##### Security + EC2SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Allow traffic from ALB + VpcId: !If [CreateVpc, !Ref StackVPC, !Ref ExistingVpcId] + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 80 # Assuming container listens on 80 + ToPort: 80 + SourceSecurityGroupId: !Ref ALBSecurityGroup + SecurityGroupEgress: + - IpProtocol: tcp + FromPort: 443 + ToPort: 443 + CidrIp: !Ref EgressCidr + + ALBSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Allow internal and outbound traffic + VpcId: !If [CreateVpc, !Ref StackVPC, !Ref ExistingVpcId] + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: !Ref IngressCidr + - IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: 174.72.69.130/32 + SecurityGroupEgress: + - IpProtocol: "-1" # Allows all outbound traffic + CidrIp: 0.0.0.0/0 + + SSMInstanceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Principal: + Service: "ec2.amazonaws.com" + Action: "sts:AssumeRole" + ManagedPolicyArns: + - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + + ##### Load Balancing + ApplicationLoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Subnets: + - !If [CreateVpc, !Ref PublicSubnet1, !Ref PublicSubnetId1] + - !If [CreateVpc, !Ref PublicSubnet2, !Ref PublicSubnetId2] + SecurityGroups: + - !Ref ALBSecurityGroup + + TargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + VpcId: !If [CreateVpc, !Ref StackVPC, !Ref ExistingVpcId] + Port: 80 + Protocol: HTTP + TargetType: instance + HealthCheckPath: "/health" + + Listener: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + DefaultActions: + - Type: forward + TargetGroupArn: !Ref TargetGroup + LoadBalancerArn: !Ref ApplicationLoadBalancer + Port: 80 + Protocol: HTTP + + ##### Compute + InstanceProfile: + Type: AWS::IAM::InstanceProfile + Properties: + Roles: + - !Ref SSMInstanceRole + + LaunchTemplate: + Type: AWS::EC2::LaunchTemplate + Properties: + LaunchTemplateName: MyLaunchTemplate + LaunchTemplateData: + ImageId: !Ref AMIId + InstanceType: !Ref InstanceType + SecurityGroupIds: + - !Ref EC2SecurityGroup + IamInstanceProfile: + Arn: !GetAtt InstanceProfile.Arn + UserData: !Base64 + 'Fn::Sub': | + #!/bin/bash + yum update -y + yum install -y docker + service docker start + + # Login to Docker registry + echo "${003DockerRegistryPassword}" | docker login --username ${004DockerRegistryUsername} --password-stdin ${DockerRegistryUrl} + + # Pull and run Docker container + docker run -d \ + -p 80:8000 \ + -e WHYLABS_API_KEY=${001WhyLabsApiKey} \ + -e CONTAINER_PASSWORD=${002WhyLabsContainerPassword} \ + ${DockerRegistryUrl}/whylabs/whylogs-container:1.0.14 + + AutoScalingGroup: + Type: AWS::AutoScaling::AutoScalingGroup + Properties: + LaunchTemplate: + LaunchTemplateId: !Ref LaunchTemplate + Version: !GetAtt LaunchTemplate.LatestVersionNumber + MinSize: !Ref ReplicaCount + MaxSize: !Ref ReplicaCount + TargetGroupARNs: + - !Ref TargetGroup + VPCZoneIdentifier: + - !If [CreateVpc, !Ref PrivateSubnet, !Ref PrivateSubnetId] + +Outputs: + LoadBalancerDNS: + Description: DNS of the Load Balancer + Value: !GetAtt ApplicationLoadBalancer.DNSName diff --git a/docs/cloudformation/test-existing-vpc.yaml b/docs/cloudformation/test-existing-vpc.yaml new file mode 100644 index 0000000..12114e3 --- /dev/null +++ b/docs/cloudformation/test-existing-vpc.yaml @@ -0,0 +1,182 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Deployment of Containerized Applications on EC2 Instances + +Parameters: + VpcCidr: + Type: String + Default: 10.199.0.0/16 + Description: The CIDR block for the VPC. + + AMIId: + Type: String + Default: ami-0ddda618e961f2270 + Description: The ID of the AMI to be used for the EC2 instances. + +Resources: + StackVPC: + Type: AWS::EC2::VPC + Properties: + CidrBlock: !Ref VpcCidr + EnableDnsSupport: true + EnableDnsHostnames: true + + InternetGateway: + Type: AWS::EC2::InternetGateway + + VPCGatewayAttachment: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: !Ref StackVPC + InternetGatewayId: !Ref InternetGateway + + PrivateSubnet: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref StackVPC + CidrBlock: 10.199.5.0/24 + MapPublicIpOnLaunch: false + AvailabilityZone: !Select + - 0 + - !GetAZs '' + + PublicSubnet1: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref StackVPC + CidrBlock: 10.199.1.0/24 + MapPublicIpOnLaunch: true + AvailabilityZone: !Select + - 0 + - !GetAZs '' + + PublicSubnet2: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref StackVPC + CidrBlock: 10.199.3.0/24 + MapPublicIpOnLaunch: true + AvailabilityZone: !Select + - 1 + - !GetAZs '' + + NatGatewayEIP: + Type: AWS::EC2::EIP + Properties: + Domain: VPC + + NatGateway: + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt NatGatewayEIP.AllocationId + SubnetId: !Ref PublicSubnet1 + + PublicRouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref StackVPC + + PublicRoute: + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PublicRouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref InternetGateway + + PrivateRouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref StackVPC + + PrivateRoute: + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PrivateRouteTable + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGateway + + PublicRouteTableAssociation1: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnet1 + RouteTableId: !Ref PublicRouteTable + + PublicRouteTableAssociation2: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnet2 + RouteTableId: !Ref PublicRouteTable + + PrivateRouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PrivateSubnet + RouteTableId: !Ref PrivateRouteTable + + EC2SecurityGroup: + Type: AWS::EC2::SecurityGroup + GroupName: !Sub "${AWS::StackName}-EC2-SG" + Properties: + GroupDescription: Allow traffic from ALB + VpcId: !Ref StackVPC + SecurityGroupEgress: + - IpProtocol: tcp + FromPort: 443 + ToPort: 443 + CidrIp: !Ref VpcCidr + + SSMInstanceRole: + Type: AWS::IAM::Role + RoleName: !Sub "${AWS::StackName}-SSM-ROLE" + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Principal: + Service: "ec2.amazonaws.com" + Action: "sts:AssumeRole" + ManagedPolicyArns: + - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + + InstanceProfile: + Type: AWS::IAM::InstanceProfile + InstanceProfileName: !Sub "${AWS::StackName}-EC2-PROFILE" + Properties: + Roles: + - !Ref SSMInstanceRole + + EC2Instance: + Type: AWS::EC2::Instance + Properties: + ImageId: !Ref AMIId + InstanceType: t3.medium + SubnetId: !Ref PrivateSubnet + SecurityGroupIds: + - !Ref EC2SecurityGroup + IamInstanceProfile: + !Ref InstanceProfile + +Outputs: + VpcId: + Description: "The ID of the VPC" + Value: !Ref StackVPC + Export: + Name: !Sub "${AWS::StackName}-VpcId" + + PrivateSubnetId: + Description: "The ID of the private subnet" + Value: !Ref PrivateSubnet + Export: + Name: !Sub "${AWS::StackName}-PrivateSubnetId" + + PublicSubnetId1: + Description: "The ID of the first public subnet" + Value: !Ref PublicSubnet1 + Export: + Name: !Sub "${AWS::StackName}-PublicSubnetId1" + + PublicSubnetId2: + Description: "The ID of the second public subnet" + Value: !Ref PublicSubnet2 + Export: + Name: !Sub "${AWS::StackName}-PublicSubnetId2" diff --git a/docs/compose/README.md b/docs/compose/README.md new file mode 100644 index 0000000..d564e65 --- /dev/null +++ b/docs/compose/README.md @@ -0,0 +1,115 @@ +# Guardrails Application Deployment + +## Prerequisites + +Before you start, make sure you have Docker and Docker Compose installed on your system. You will also need to clone the repository containing the Docker Compose file and related configuration files. + +## Configuration + +### Environment Variables + +Set the necessary environment variables before starting the application: + +- `CONTAINER_PASSWORD`: Password used internally by containers. Set this in your environment or directly in the Docker Compose file. +- `WHYLABS_API_KEY`: API key for WhyLabs integration. + +Example: + +```bash +export CONTAINER_PASSWORD=yourpassword +export WHYLABS_API_KEY=yourapikey +``` + +### Logging into GitLab Container Registry + +Before pulling images from the private GitLab container registry, you must log +in using the credentials provided by WhyLabs. + +1. Open your terminal. +2. Execute the following command: + +```bash +docker login registry.gitlab.com -u "${username}" -p "${password}" +``` + +### Adjusting Replicas + +To change the number of replicas for the guardrails service: + +1. Open the `compose.yaml` file. + +1. Find the guardrails service section. + +1. Modify the replicas value under the deploy key. + +Example: + +```yaml +deploy: + replicas: 5 # Change this number as needed +``` + +### Adjusting Resources + +To modify the CPU and memory limits for the guardrails or nginx services: + +1. Navigate to the resources section under the respective service in the +`compose.yaml` file. + +1. Adjust the cpus and memory under both limits and reservations. + +Example for Guardrails: + +```yaml +resources: + limits: + cpus: '2.0' # From 4.0 to 2.0 for example + memory: 2048M # From 4096M to 2048M for example +``` + +### Updating the Image and Tag + +To update the Docker image used by the guardrails service: + +1. Locate the image key under the guardrails service in the +`compose.yaml` file. + +1. Update the image name and tag. + +Example: + +```yaml +image: registry.gitlab.com/whylabs/whylogs-container:newtag +``` +## Running the Application + +To run the application, use the following command from the directory containing +your `compose.yaml` file. + +> :warning: It is important to execute `docker compose` commands from this +directory, the one containing the `compose.yaml` file. The configuration expects +the `nginx.conf` to be a sibling to the `compose.yaml` file. + +```bash +docker compose up -d +``` + +This will start all services defined in the Docker Compose file. + +### Stopping the Application + +To stop all services, run: + +```bash +docker compose down +``` + +### Monitoring and Logs + +To view the logs for a specific service, use: + +```bash +docker compose logs -f guardrails +``` + +Replace guardrails with nginx to view logs for the NGINX service. diff --git a/docs/compose/compose.yaml b/docs/compose/compose.yaml new file mode 100644 index 0000000..7d6f077 --- /dev/null +++ b/docs/compose/compose.yaml @@ -0,0 +1,54 @@ +name: guardrails +services: + + guardrails: + cap_drop: + - ALL + environment: + CONTAINER_PASSWORD: null + WHYLABS_API_KEY: null + image: registry.gitlab.com/whylabs/langkit-container:1.0.14 + platform: linux/amd64 + ports: + - target: 8000 + read_only: true + security_opt: + - no-new-privileges:true + user: 1000:1000 + volumes: + - type: volume + source: temp-dir + target: /tmp + deploy: + replicas: 4 + restart_policy: + condition: on-failure + resources: + limits: + cpus: '4.0' + memory: 4096M + reservations: + cpus: '4.0' + memory: 4096M + + nginx: + image: nginx:latest + ports: + - "8080:80" + depends_on: + - guardrails + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + - type: tmpfs + target: /etc/nginx/conf.d + deploy: + resources: + limits: + cpus: '1.0' + memory: 1024M + reservations: + cpus: '1.0' + memory: 1024M + +volumes: + temp-dir: {} diff --git a/docs/compose/nginx.conf b/docs/compose/nginx.conf new file mode 100644 index 0000000..829449a --- /dev/null +++ b/docs/compose/nginx.conf @@ -0,0 +1,18 @@ +events { + worker_connections 1024; +} +worker_processes auto; + +http { + server { + listen 80; + + location / { + proxy_pass http://guardrails:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +}