Add this test
script into db
so we can easily check our connection from our container.
#!/usr/bin/env python3
import psycopg
import os
import sys
connection_url = os.getenv("CONNECTION_URL")
conn = None
try:
print('attempting connection')
conn = psycopg.connect(connection_url)
print("Connection successful!")
except psycopg.Error as e:
print("Unable to connect to the database:", e)
finally:
conn.close()
We'll add the following endpoint for our flask app:
@app.route('/api/health-check')
def health_check():
return {'success': True}, 200
We'll create a new bin script at bin/flask/health-check
#!/usr/bin/env python3
import urllib.request
response = urllib.request.urlopen('http://localhost:4567/api/health-check')
if response.getcode() == 200:
print("Flask server is running")
else:
print("Flask server is not running")
aws logs create-log-group --log-group-name cruddur
aws logs put-retention-policy --log-group-name cruddur --retention-in-days 1
aws ecs create-cluster \
--cluster-name cruddur \
--service-connect-defaults namespace=cruddur
export CRUD_CLUSTER_SG=$(aws ec2 create-security-group \
--group-name cruddur-ecs-cluster-sg \
--description "Security group for Cruddur ECS ECS cluster" \
--vpc-id $DEFAULT_VPC_ID \
--query "GroupId" --output text)
echo $CRUD_CLUSTER_SG
Get the Group ID (after its created)
export CRUD_CLUSTER_SG=$(aws ec2 describe-security-groups \
--group-names cruddur-ecs-cluster-sg \
--query 'SecurityGroups[0].GroupId' \
--output text)
aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin "$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com"
aws ecr create-repository \
--repository-name cruddur-python \
--image-tag-mutability MUTABLE
export ECR_PYTHON_URL="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/cruddur-python"
echo $ECR_PYTHON_URL
docker pull python:3.10-slim-buster
docker tag python:3.10-slim-buster $ECR_PYTHON_URL:3.10-slim-buster
docker push $ECR_PYTHON_URL:3.10-slim-buster
In your flask dockerfile update the from to instead of using DockerHub's python image you use your own eg.
remember to put the :latest tag on the end
aws ecr create-repository \
--repository-name backend-flask \
--image-tag-mutability MUTABLE
export ECR_BACKEND_FLASK_URL="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/backend-flask"
echo $ECR_BACKEND_FLASK_URL
Go to the backend-flask directory
docker build -t backend-flask .
docker tag backend-flask:latest $ECR_BACKEND_FLASK_URL:latest
docker push $ECR_BACKEND_FLASK_URL:latest
aws ecr create-repository \
--repository-name frontend-react-js \
--image-tag-mutability MUTABLE
export ECR_FRONTEND_REACT_URL="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/frontend-react-js"
echo $ECR_FRONTEND_REACT_URL
docker build \
--build-arg REACT_APP_BACKEND_URL="https://4567-$GITPOD_WORKSPACE_ID.$GITPOD_WORKSPACE_CLUSTER_HOST" \
--build-arg REACT_APP_AWS_PROJECT_REGION="$AWS_DEFAULT_REGION" \
--build-arg REACT_APP_AWS_COGNITO_REGION="$AWS_DEFAULT_REGION" \
--build-arg REACT_APP_AWS_USER_POOLS_ID="ca-central-1_CQ4wDfnwc" \
--build-arg REACT_APP_CLIENT_ID="5b6ro31g97urk767adrbrdj1g5" \
-t frontend-react-js \
-f Dockerfile.prod \
.
docker tag frontend-react-js:latest $ECR_FRONTEND_REACT_URL:latest
docker push $ECR_FRONTEND_REACT_URL:latest
If you want to run and test it
docker run --rm -p 3000:3000 -it frontend-react-js
https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html https://docs.aws.amazon.com/AmazonECS/latest/developerguide/secrets-envvar-ssm-paramstore.html
aws ssm put-parameter --type "SecureString" --name "/cruddur/backend-flask/AWS_ACCESS_KEY_ID" --value $AWS_ACCESS_KEY_ID
aws ssm put-parameter --type "SecureString" --name "/cruddur/backend-flask/AWS_SECRET_ACCESS_KEY" --value $AWS_SECRET_ACCESS_KEY
aws ssm put-parameter --type "SecureString" --name "/cruddur/backend-flask/CONNECTION_URL" --value $PROD_CONNECTION_URL
aws ssm put-parameter --type "SecureString" --name "/cruddur/backend-flask/ROLLBAR_ACCESS_TOKEN" --value $ROLLBAR_ACCESS_TOKEN
aws ssm put-parameter --type "SecureString" --name "/cruddur/backend-flask/OTEL_EXPORTER_OTLP_HEADERS" --value "x-honeycomb-team=$HONEYCOMB_API_KEY"
create this file aws/policies/service-assume-role-execution-policy.json
aws iam create-role \
--role-name CruddurServiceExecutionRole \
--assume-role-policy-document "{
\"Version\":\"2012-10-17\",
\"Statement\":[{
\"Action\":[\"sts:AssumeRole\"],
\"Effect\":\"Allow\",
\"Principal\":{
\"Service\":[\"ecs-tasks.amazonaws.com\"]
}
}]
}"
aws iam create-role \
--role-name CruddurServiceExecutionRole \
--assume-role-policy-document file://aws/policies/service-assume-role-execution-policy.json
next create this file aws/policies/service-execution-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"ssm:GetParameters",
"ssm:GetParameter"
],
"Resource": "arn:aws:ssm:us-east-1:730331264766:parameter/cruddur/backend-flask/*"
}
]
}
aws iam put-role-policy \
--policy-name CruddurServiceExecutionPolicy \
--role-name CruddurServiceExecutionRole \
--policy-document file://aws/policies/service-execution-policy.json
aws iam create-role \
--role-name CruddurTaskRole \
--assume-role-policy-document "{
\"Version\":\"2012-10-17\",
\"Statement\":[{
\"Action\":[\"sts:AssumeRole\"],
\"Effect\":\"Allow\",
\"Principal\":{
\"Service\":[\"ecs-tasks.amazonaws.com\"]
}
}]
}"
aws iam put-role-policy \
--policy-name SSMAccessPolicy \
--role-name CruddurTaskRole \
--policy-document "{
\"Version\":\"2012-10-17\",
\"Statement\":[{
\"Action\":[
\"ssmmessages:CreateControlChannel\",
\"ssmmessages:CreateDataChannel\",
\"ssmmessages:OpenControlChannel\",
\"ssmmessages:OpenDataChannel\"
],
\"Effect\":\"Allow\",
\"Resource\":\"*\"
}]
}
"
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/CloudWatchFullAccess --role-name CruddurTaskRole
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess --role-name CruddurTaskRole
Create a new folder called aws/task-defintions
and place the following files in there:
backend-flask.json
{
"family": "backend-flask",
"executionRoleArn": "arn:aws:iam::AWS_ACCOUNT_ID:role/CruddurServiceExecutionRole",
"taskRoleArn": "arn:aws:iam::AWS_ACCOUNT_ID:role/CruddurTaskRole",
"networkMode": "awsvpc",
"containerDefinitions": [
{
"name": "backend-flask",
"image": "BACKEND_FLASK_IMAGE_URL",
"cpu": 256,
"memory": 512,
"essential": true,
"portMappings": [
{
"name": "backend-flask",
"containerPort": 4567,
"protocol": "tcp",
"appProtocol": "http"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "cruddur",
"awslogs-region": "ca-central-1",
"awslogs-stream-prefix": "backend-flask"
}
},
"environment": [
{"name": "OTEL_SERVICE_NAME", "value": "backend-flask"},
{"name": "OTEL_EXPORTER_OTLP_ENDPOINT", "value": "https://api.honeycomb.io"},
{"name": "AWS_COGNITO_USER_POOL_ID", "value": ""},
{"name": "AWS_COGNITO_USER_POOL_CLIENT_ID", "value": ""},
{"name": "FRONTEND_URL", "value": ""},
{"name": "BACKEND_URL", "value": ""},
{"name": "AWS_DEFAULT_REGION", "value": ""}
],
"secrets": [
{"name": "AWS_ACCESS_KEY_ID" , "valueFrom": "arn:aws:ssm:AWS_REGION:AWS_ACCOUNT_ID:parameter/cruddur/backend-flask/AWS_ACCESS_KEY_ID"},
{"name": "AWS_SECRET_ACCESS_KEY", "valueFrom": "arn:aws:ssm:AWS_REGION:AWS_ACCOUNT_ID:parameter/cruddur/backend-flask/AWS_SECRET_ACCESS_KEY"},
{"name": "CONNECTION_URL" , "valueFrom": "arn:aws:ssm:AWS_REGION:AWS_ACCOUNT_ID:parameter/cruddur/backend-flask/CONNECTION_URL" },
{"name": "ROLLBAR_ACCESS_TOKEN" , "valueFrom": "arn:aws:ssm:AWS_REGION:AWS_ACCOUNT_ID:parameter/cruddur/backend-flask/ROLLBAR_ACCESS_TOKEN" },
{"name": "OTEL_EXPORTER_OTLP_HEADERS" , "valueFrom": "arn:aws:ssm:AWS_REGION:AWS_ACCOUNT_ID:parameter/cruddur/backend-flask/OTEL_EXPORTER_OTLP_HEADERS" }
]
}
]
}
frontend-react.json
{
"family": "frontend-react-js",
"executionRoleArn": "arn:aws:iam::AWS_ACCOUNT_ID:role/CruddurServiceExecutionRole",
"taskRoleArn": "arn:aws:iam::AWS_ACCOUNT_ID:role/CruddurTaskRole",
"networkMode": "awsvpc",
"containerDefinitions": [
{
"name": "frontend-react-js",
"image": "BACKEND_FLASK_IMAGE_URL",
"cpu": 256,
"memory": 256,
"essential": true,
"portMappings": [
{
"name": "frontend-react-js",
"containerPort": 3000,
"protocol": "tcp",
"appProtocol": "http"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "cruddur",
"awslogs-region": "ca-central-1",
"awslogs-stream-prefix": "frontend-react"
}
}
}
]
}
aws ecs register-task-definition --cli-input-json file://aws/task-definitions/backend-flask.json
aws ecs register-task-definition --cli-input-json file://aws/task-definitions/frontend-react-js.json
export DEFAULT_VPC_ID=$(aws ec2 describe-vpcs \
--filters "Name=isDefault, Values=true" \
--query "Vpcs[0].VpcId" \
--output text)
echo $DEFAULT_VPC_ID
export DEFAULT_SUBNET_IDS=$(aws ec2 describe-subnets \
--filters Name=vpc-id,Values=$DEFAULT_VPC_ID \
--query 'Subnets[*].SubnetId' \
--output json | jq -r 'join(",")')
echo $DEFAULT_SUBNET_IDS
export CRUD_SERVICE_SG=$(aws ec2 create-security-group \
--group-name "crud-srv-sg" \
--description "Security group for Cruddur services on ECS" \
--vpc-id $DEFAULT_VPC_ID \
--query "GroupId" --output text)
echo $CRUD_SERVICE_SG
USE THIS SG WHEN CREATING A SERVICE ON ECS
see screenshot
aws ec2 authorize-security-group-ingress \
--group-id $CRUD_SERVICE_SG \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id $CRUD_SERVICE_SG \
--protocol tcp \
--port 4567 \
--cidr 0.0.0.0/0
if we need to get the sg group id again
export CRUD_SERVICE_SG=$(aws ec2 describe-security-groups \
--filters Name=group-name,Values=crud-srv-sg \
--query 'SecurityGroups[*].GroupId' \
--output text)
aws ec2 authorize-security-group-ingress \
--group-id $DB_SG_ID \
--protocol tcp \
--port 5432 \
--source-group $CRUD_SERVICE_SG \
--tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=BACKENDFLASK}]'
create file aws/json/service-backend-flask.json (see week6-7) and update it
create file aws/json/service-frontend-react-js.json (see week6-7) and update it
tips: To optimize cost please delete service after usage
aws ecs create-service --cli-input-json file://aws/json/service-backend-flask.json
aws ecs create-service --cli-input-json file://aws/json/service-frontend-react-js.json
we can test if the check is ok on our bowser -> @ipadd:4567/api/health-check
please see screenshot
Create LB on the dashboard called cruddur-LB create new SG cruddur-alb-sg and allow http and https in Inbound rules copie the sg-ID, to permit omly the others ones sg only to allow access to the service
add this alb-sg to the crud-service inbound rules (port 4567)
add the sg we created on the ALB (refresh view and select the appropriate SG)
after that we have to create a target group named cruddur-backend-flask-tg (we use fargate and we're pointing to a address) use IP addresses target type protocol : HTTP/4567 Health checks: /api/health-check
add the tg we created on the ALB (refresh view and select the appropriate TG). Modify the listener port to 4567
add another listener to port 3000 for the frontend, create also his own tg named cruddur-frontend-react-tg ... ... Create the ALB
modify the alb-sg to allow traffic on ports 4567 and 3000
i use my alb to see if requests work well please see capture
to have access detailed logs of all requests made to your Elastic Load Balancer, we need to ship logs into an existing S3 Goto the alb > attributes > Monitoring and specify the S3
add S3 policy to allow ... https://docs.aws.amazon.com/elasticloadbalancing/latest/application/enable-access-logging.html
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::elb-account-id:root"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::bucket-name/prefix/AWSLogs/your-aws-account-id/*"
}
]
}
Auto Assign is not supported by EC2 launch type for services
This is for when we are uing a NetworkMode of awsvpc
--network-configuration "awsvpcConfiguration={subnets=[$DEFAULT_SUBNET_IDS],securityGroups=[$SERVICE_CRUD_SG],assignPublicIp=ENABLED}"
https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html
Frontend Implementation create files nginx.conf Dockerfile.prod
test that my service works locally before deploying to a container on frontend directory run
npm run build
this command will create a build folder in the frontend directory
Use sessions manager to connect to the EC2 instance.
Shell into the backend flask container and run the ./bin/db/test
script to ensure we have a database connection
./bin/flask/health-check
Check our forwarding ports for the container
docker port <CONTAINER_ID>
docker run --rm --link <container_name_or_id>: curlimages/curl curl :/
docker run --rm --link d71eea0b8e93:flask -it curlimages/curl --get -H "Accept: application/json" -H "Content-Type: application/json" http://flask:4567/api/activities/home
docker run --rm -it curlimages/curl --get -H "Accept: application/json" -H "Content-Type: application/json" http://3.97.113.133/api/activities/home
The instance can hang up for various reasons. You need to reboot and it will force a restart after 5 minutes So you will have to wait 5 minutes or after a timeout.
You have to use the AWS CLI. You can't use the AWS Console. it will not work as expected.
The console will only do a graceful shutdodwn The CLI will do a forceful shutdown after a period of time if graceful shutdown fails.
aws ec2 reboot-instances --instance-ids i-0d15aef0618733b6d
https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html#install-plugin-linux https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html#install-plugin-verify
Install for Ubuntu
curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" -o "session-manager-plugin.deb"
sudo dpkg -i session-manager-plugin.deb
Verify its working
session-manager-plugin
Connect to the container
aws ecs execute-command \
--region $AWS_DEFAULT_REGION \
--cluster cruddur \
--task dceb2ebdc11c49caadd64e6521c6b0c7 \
--container backend-flask \
--command "/bin/bash" \
--interactive
docker run -rm \
-p 4567:4567 \
-e AWS_ENDPOINT_URL="http://dynamodb-local:8000" \
-e CONNECTION_URL="postgresql://postgres:password@db:5432/cruddur" \
-e FRONTEND_URL="https://3000-${GITPOD_WORKSPACE_ID}.${GITPOD_WORKSPACE_CLUSTER_HOST}" \
-e BACKEND_URL="https://4567-${GITPOD_WORKSPACE_ID}.${GITPOD_WORKSPACE_CLUSTER_HOST}" \
-e OTEL_SERVICE_NAME='backend-flask' \
-e OTEL_EXPORTER_OTLP_ENDPOINT="https://api.honeycomb.io" \
-e OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=${HONEYCOMB_API_KEY}" \
-e AWS_XRAY_URL="*4567-${GITPOD_WORKSPACE_ID}.${GITPOD_WORKSPACE_CLUSTER_HOST}*" \
-e AWS_XRAY_DAEMON_ADDRESS="xray-daemon:2000" \
-e AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION}" \
-e AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" \
-e AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" \
-e ROLLBAR_ACCESS_TOKEN="${ROLLBAR_ACCESS_TOKEN}" \
-e AWS_COGNITO_USER_POOL_ID="${AWS_COGNITO_USER_POOL_ID}" \
-e AWS_COGNITO_USER_POOL_CLIENT_ID="5b6ro31g97urk767adrbrdj1g5" \
-it backend-flask-prod