Skip to content

Commit

Permalink
Merge pull request #26 from Nelsonlin0321/dev/infra
Browse files Browse the repository at this point in the history
Dev/infra
  • Loading branch information
Nelsonlin0321 authored Oct 2, 2023
2 parents 01391f5 + dcd5c8a commit 4924e77
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 64 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,13 @@ jobs:
python ./src/deploy_ecr_image_to_lambda.py \
--repository_name ${IMAGE_NAME} \
--image_tag latest \
--function_name ${IMAGE_NAME}
--function_name ${FUNC_NAME}
env:
AWS_DEFAULT_REGION: ${{ vars.AWS_DEFAULT_REGION }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
IMAGE_NAME: ${{ vars.IMAGE_NAME }}
FUNC_NAME: ${{ vars.FUNC_NAME }}

- name: Lambda Integration Test
run: |
Expand All @@ -80,4 +81,4 @@ jobs:
AWS_DEFAULT_REGION: ${{ vars.AWS_DEFAULT_REGION }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
IMAGE_NAME: ${{ vars.IMAGE_NAME }}
FUNC_NAME: ${{ vars.FUNC_NAME }}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,9 @@ cython_debug/
notebooks
artifacts
response.json
infra/.terraform.lock.hcl
infra/.terraform/providers/registry.terraform.io/hashicorp/aws/5.19.0/darwin_arm64/terraform-provider-aws_v5.19.0_x5
infra/terraform.tfstate
.gitignore
.gitignore
infra/terraform.tfstate.backup
86 changes: 26 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@
| Experiment tracking and model registry | We track the model training experiment and register models using [Mlflow](https://mlflow.org/) | ✔️ |
| Workflow Orchestration| We use [Prefect](https://www.prefect.io/) orchestract training data pipeline | ✔️ |
| Model deployment| Model with FastAPI Deployed to AWS Lambda With API Gateway | ✔️ |
| Reproducibility | We log all training artifatct to make sure reproducibility| ✔️ |
| Reproducibility | We log all training artifact to make sure reproducibility| ✔️ |
| Best practices (DevOps) | Pylint static code analysis | ✔️ |
| Best practices (DevOps) | Unit tests in CICD to make sure continue integration | ✔️ |
| Best practices (DevOps) | Integration test in CICD to make sure continue delivery | ✔️ |
| Best practices (DevOps) | We impletment CI/CD using Github action workflow | ✔️ |
| Best practices (DevOps) | Implement CI/CD using Github action workflow | ✔️ |
| Best practices (DevOps) | Terraform Infrastructure as Codes | ✔️ |


## Guildance to Deploy
## Guidance to Deploy

### Environmenet Settings
### Environment Settings

.env file

Expand Down Expand Up @@ -94,7 +96,7 @@ fastapi docs swagger for information: the http://0.0.0.0:8000/docs

```sh
curl -X 'POST' \
'http://0.0.0.0:8000/recommend' \
'http://0.0.0.0:5050/recommend' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
Expand Down Expand Up @@ -187,70 +189,34 @@ docker tag ${image_name}:latest ${account_id}.dkr.ecr.${region}.amazonaws.com/${
docker push ${account_id}.dkr.ecr.ap-southeast-1.amazonaws.com/${repo_name}:latest
```

### Create a lambda function using ECR image:
### Deploy To AWS with Infra Codes:

<img src="images/create-lambda.png"></img>

### Config Env Varialble
<img src="images/lambda-env-config.png"></img>

### Congig Permission with AWS Role

#### Create Role with S3 MLflow artifacts read access
<img src="images/s3-mlflow-s3-artifacst-policy.png"></img>

#### Attach policy to lambda role
<img src="images/lambda-role-policy.png"></img>

### Test Lambda

<img src="images/test-lambda-console.png"></img>


### Deploy
```sh
image_name=movielens1m-recommender-lambda
python ./src/deploy_ecr_image_to_lambda.py \
--repository_name ${image_name} \
--image_tag latest \
--function_name ${image_name}
cd ./infra
terraform init
terraform apply
```


### Invoke Lambda
### Get API URL
```sh
image_name=movielens1m-recommender-lambda
payload=$(cat integration_test/test_recommend_payload.json)
aws lambda invoke \
--function-name ${image_name} \
--payload ${payload} \
response.json
terraform output -json > ./output.json
```

### Build a Serverless API With API Gateway
<img src="images/create-api-gateway.png"></img>


#### Create "recommend" endpoint

<img src="images/recommend-api-endpoint.png"></img>

#### Create POST Method Recommendation API


<img src="images/recommend-api-setup.png"></img>

#### Test You API
<img src="images/test-api-gateway.png"></img>


#### Deploy To API Gateway
```json
{
"apigateway_invoke_url": {
"sensitive": false,
"type": "string",
"value": "https://7jufjyexya.execute-api.ap-southeast-1.amazonaws.com/prod"
}
}
```

<img src="images/deploy-to-production.png"></img>
### Test API

```sh
curl -X 'POST' \
'https://j87zs9ftf4.execute-api.ap-southeast-1.amazonaws.com/production/recommend' \
'https://7jufjyexya.execute-api.ap-southeast-1.amazonaws.com/prod/recommend' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
Expand Down Expand Up @@ -307,12 +273,12 @@ Recommendation Response:

```sh
curl -X 'GET' \
'https://j87zs9ftf4.execute-api.ap-southeast-1.amazonaws.com/production/healthcheck' \
'https://7jufjyexya.execute-api.ap-southeast-1.amazonaws.com/prod/healthcheck' \
-H 'accept: application/json' \
-H 'Content-Type: application/json'
```

Heathcheck Response:
heathcheck Response:
```sh
{"message":"The server is up since 2023-08-13 07:52:09","start_uct_time":"2023-08-13 07:52:09"}
```
165 changes: 165 additions & 0 deletions infra/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.18"
}
}

required_version = ">= 1.2.0"
}

data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"

principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}

actions = ["sts:AssumeRole"]
}
}

resource "aws_iam_role" "iam_for_lambda" {
name = "${var.lambda_function_name}-iam-role"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

resource "aws_cloudwatch_log_group" "lambda_log_group" {
name = "/aws/lambda/${var.lambda_function_name}"
retention_in_days = 14
}


data "aws_iam_policy_document" "lambda_logging" {
statement {
effect = "Allow"

actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
]

resources = ["arn:aws:logs:*:*:*"]
}
}

resource "aws_iam_policy" "lambda_logging_policy" {
name = "${var.lambda_function_name}_logging"
path = "/"
description = "IAM policy for logging from a lambda"
policy = data.aws_iam_policy_document.lambda_logging.json
}


resource "aws_iam_role_policy_attachment" "lambda_logs_policy_attachment" {
role = aws_iam_role.iam_for_lambda.name
policy_arn = aws_iam_policy.lambda_logging_policy.arn
}

data "aws_iam_policy_document" "lambda_s3_access" {
statement {
effect = "Allow"

actions = [
"s3:GetObject",
"s3:ListBucket",
]

resources = ["arn:aws:s3:::s3-mlflow-artifacts-storage/*",
"arn:aws:s3:::s3-mlflow-artifacts-storage"]
}
}

resource "aws_iam_policy" "lambda_s3_access_policy" {
name = "${var.lambda_function_name}_lambda_s3_access"
path = "/"
description = "IAM policy of s3 access for lambda ${var.lambda_function_name}"
policy = data.aws_iam_policy_document.lambda_s3_access.json
}

resource "aws_iam_role_policy_attachment" "lambda_s3_access_policy_attachment" {
role = aws_iam_role.iam_for_lambda.name
policy_arn = aws_iam_policy.lambda_s3_access_policy.arn
}


resource "aws_lambda_function" "movielens1m_recommender_lambda" {
function_name = var.lambda_function_name

timeout = 5
memory_size = 720
image_uri = "${var.account_id}.dkr.ecr.${var.region}.amazonaws.com/${var.ecr_repository_name}:latest"
package_type = "Image"
architectures = ["x86_64"]
depends_on = [
aws_cloudwatch_log_group.lambda_log_group,
aws_iam_role_policy_attachment.lambda_logs_policy_attachment,
aws_iam_role_policy_attachment.lambda_s3_access_policy_attachment

]
role = aws_iam_role.iam_for_lambda.arn

ephemeral_storage {
size = 512
}

environment {
variables = {
ARTIFACTS_URL = var.ARTIFACTS_URL
BATCH_SIZE = var.BATCH_SIZE
}
}
}


resource "aws_api_gateway_rest_api" "lambda_rest_gateway" {
name = "${var.lambda_function_name}-api"
}

resource "aws_api_gateway_resource" "proxy" {
rest_api_id = "${aws_api_gateway_rest_api.lambda_rest_gateway.id}"
parent_id = "${aws_api_gateway_rest_api.lambda_rest_gateway.root_resource_id}"
path_part = "{proxy+}"
}

resource "aws_api_gateway_method" "proxy" {
rest_api_id = "${aws_api_gateway_rest_api.lambda_rest_gateway.id}"
resource_id = "${aws_api_gateway_resource.proxy.id}"
http_method = "ANY"
authorization = "NONE"
}

resource "aws_api_gateway_integration" "lambda" {
rest_api_id = "${aws_api_gateway_rest_api.lambda_rest_gateway.id}"
resource_id = "${aws_api_gateway_method.proxy.resource_id}"
http_method = "${aws_api_gateway_method.proxy.http_method}"

integration_http_method = "POST"
type = "AWS_PROXY"
uri = "${aws_lambda_function.movielens1m_recommender_lambda.invoke_arn}"
}

resource "aws_lambda_permission" "apigw" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = var.lambda_function_name
principal = "apigateway.amazonaws.com"

# The /*/* portion grants access from any method on any resource
# within the API Gateway "REST API".
source_arn = "${aws_api_gateway_rest_api.lambda_rest_gateway.execution_arn}/*/*"
}


resource "aws_api_gateway_deployment" "lambda_rest_prod_gateway" {
depends_on = [
aws_api_gateway_integration.lambda,
]

rest_api_id = "${aws_api_gateway_rest_api.lambda_rest_gateway.id}"
stage_name = "prod"
}
7 changes: 7 additions & 0 deletions infra/output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"apigateway_invoke_url": {
"sensitive": false,
"type": "string",
"value": "https://7jufjyexya.execute-api.ap-southeast-1.amazonaws.com/prod"
}
}
3 changes: 3 additions & 0 deletions infra/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "apigateway_invoke_url" {
value = aws_api_gateway_deployment.lambda_rest_prod_gateway.invoke_url
}
26 changes: 26 additions & 0 deletions infra/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
variable "region" {
default = "ap-southeast-1"
}

variable "account_id" {
default = "932682266260"
}

variable "lambda_function_name" {
default = "movielens1m-transformer-based-recommender-lambda"
}


variable "ecr_repository_name" {
default = "movielens1m-recommender-lambda"
}

variable "ARTIFACTS_URL" {
default = "s3://s3-mlflow-artifacts-storage/mlflow/15/7008c7131367497a8dd99e2b2d506f96"
}

variable "BATCH_SIZE" {
default = "12"
}


Loading

0 comments on commit 4924e77

Please sign in to comment.