diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 897e357e..16959fb8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ default_language_version: python: python3.11 repos: - repo: https://github.com/codespell-project/codespell - rev: v2.3.0 + rev: v2.4.1 hooks: - id: codespell args: ["--ignore-words=codespell.txt"] @@ -29,7 +29,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/PyCQA/isort - rev: 5.13.2 + rev: 6.0.0 hooks: - id: isort args: ["--settings-path=pyproject.toml"] @@ -41,7 +41,7 @@ repos: language: script types: [python] - repo: https://github.com/PyCQA/bandit - rev: 1.7.10 + rev: 1.8.2 hooks: - id: bandit args: ["-ll"] @@ -69,14 +69,14 @@ repos: - id: check-merge-conflict - id: debug-statements - repo: https://github.com/gruntwork-io/pre-commit - rev: v0.1.24 # Get the latest from: https://github.com/gruntwork-io/pre-commit/releases + rev: v0.1.25 # Get the latest from: https://github.com/gruntwork-io/pre-commit/releases hooks: - id: terraform-fmt - id: helmlint - id: terraform-validate - id: tflint - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook - rev: v9.18.0 + rev: v9.20.0 hooks: - id: commitlint stages: [commit-msg] diff --git a/api/kubernetes/ecr.tf b/api/kubernetes/ecr.tf new file mode 100644 index 00000000..9aec294a --- /dev/null +++ b/api/kubernetes/ecr.tf @@ -0,0 +1,49 @@ +#------------------------------------------------------------------------------ +# written by: Lawrence McDaniel +# https://lawrencemcdaniel.com/ +# +# date: feb-2024 +# +# usage: build and upload a Docker image to AWS Elastic Container Registry (ECR) +#------------------------------------------------------------------------------ +locals { + ecr_repo = "chat_api" + ecr_source_directory = "${path.module}../python/openai_api/" + + ecr_build_path = "${path.module}/ecr_build" + ecr_build_script = "${local.ecr_build_path}/build.sh" +} + +resource "aws_ecr_repository" "chat_api" { + name = local.ecr_repo + image_tag_mutability = "IMMUTABLE" + + image_scanning_configuration { + scan_on_push = true + } + tags = var.tags + +} + +############################################################################### +# Python package +############################################################################### +resource "null_resource" "chat_api" { + triggers = { + always_redeploy = timestamp() + } + + provisioner "local-exec" { + interpreter = ["/bin/bash"] + command = local.ecr_build_script + + environment = { + BUILD_PATH = local.ecr_build_path + CONTAINER_NAME = local.ecr_repo + AWS_REGION = var.aws_region + AWS_ACCOUNT_ID = var.aws_account_id + } + } + + depends_on = [aws_ecr_repository.chat_api] +} diff --git a/api/kubernetes/ecr_build/.gitignore b/api/kubernetes/ecr_build/.gitignore new file mode 100644 index 00000000..5e3397e7 --- /dev/null +++ b/api/kubernetes/ecr_build/.gitignore @@ -0,0 +1,3 @@ +*.zip +venv +archive diff --git a/api/kubernetes/ecr_build/Dockerfile b/api/kubernetes/ecr_build/Dockerfile new file mode 100644 index 00000000..aa53b36d --- /dev/null +++ b/api/kubernetes/ecr_build/Dockerfile @@ -0,0 +1,22 @@ +# Use an AWS Lambda Python runtime as the base image +# https://hub.docker.com/r/amazon/aws-lambda-python +# ------------------------------------------------------ +FROM --platform=linux/amd64 python:3.11-buster + +WORKDIR /app + +COPY openai_api . +COPY requirements.txt . + +RUN apt-get update && apt-get install -y zip +RUN pip install -r requirements.txt + +CMD ["python", "service_controller.py"] + +EXPOSE 8000 + +ENV DEBUG_MODE=False +ARG OPENAI_API_KEY +ARG PINECONE_API_KEY +ARG PINECONE_ENVIRONMENT +ARG GOOGLE_MAPS_API_KEY diff --git a/api/kubernetes/ecr_build/README.md b/api/kubernetes/ecr_build/README.md new file mode 100644 index 00000000..fc077018 --- /dev/null +++ b/api/kubernetes/ecr_build/README.md @@ -0,0 +1,3 @@ +# AWS Lambda Layer for OpenAI/Langchain Lambdas + +This layer contains the combined pip requirements for both lambda_langchain and lambda_openai_v2. diff --git a/api/kubernetes/ecr_build/build.sh b/api/kubernetes/ecr_build/build.sh new file mode 100755 index 00000000..04541be5 --- /dev/null +++ b/api/kubernetes/ecr_build/build.sh @@ -0,0 +1,31 @@ +#!/bin/bash +#------------------------------------------------------------------------------ +# written by: Lawrence McDaniel +# https://lawrencemcdaniel.com/ +# +# date: nov-2023 +# +# usage: Lambda Python packaging tool. +# Called by Terraform "null_resource". Copies python +# module(s) plus any requirements to a dedicated folder so that +# it can be archived to a zip file for upload to +# AWS Lambda by Terraform. +#------------------------------------------------------------------------------ +cd $BUILD_PATH + +pwd +cp -R ../../python/openai_api/ ./openai_api + +# Step 1: Build your Docker image +docker build -t $CONTAINER_NAME . + +# Step 2: Authenticate Docker to your Amazon ECR registry +aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com + +# Step 3: Tag your Docker image +docker tag chat_api:latest $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/chat_api:latest + +# Step 4: Push your Docker image +docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/chat_api:latest + +rm -r ./openai_api diff --git a/api/kubernetes/ecr_build/requirements.txt b/api/kubernetes/ecr_build/requirements.txt new file mode 100644 index 00000000..e6f83f6a --- /dev/null +++ b/api/kubernetes/ecr_build/requirements.txt @@ -0,0 +1,44 @@ +# ----------------------------------------------------------------------------- +# written by: Lawrence McDaniel +# https://lawrencemcdaniel.com +# +# usage: Python requirements for AWS Lambda functions. Create a virtual +# environment in the root of this repository named `venv`. Terraform +# modules will look for and include these requirements in the zip +# packages for each Python-based Lambda function. +# ----------------------------------------------------------------------------- + +# misc +# ------------ +boto3==1.34.25 +botocore==1.34.29 +requests==2.31.0 + +# Lambda layer: openai +# ------------ +openai==1.10.0 +pyyaml==6.0.1 + +# Lambda layer: common +# ------------ +python-dotenv==1.0.1 +pydantic==2.5.3 +pydantic-settings==2.1.0 +python-hcl2==4.3.2 + +# Lambda layer: langchain +# ------------ +langchain==0.1.1 +langchain-openai==0.0.5 + +# Lambda layer: nlp +# ------------ +python-Levenshtein==0.23.0 +nltk==3.8.1 +textblob==0.17.1 + +# weather function +googlemaps==4.10.0 +openmeteo-requests==1.1.0 +requests-cache==1.1.1 +retry-requests==2.0.0 diff --git a/api/kubernetes/k8s.tf b/api/kubernetes/k8s.tf new file mode 100644 index 00000000..fc952297 --- /dev/null +++ b/api/kubernetes/k8s.tf @@ -0,0 +1,19 @@ +#-------------------------------------------------------------- +# Deploy containerized application to an existing Kubernetes cluster +#-------------------------------------------------------------- + +provider "kubernetes" { + config_path = "~/.kube/config" +} + +# resource "kubernetes_manifest" "deployment" { +# manifest = yamldecode(data.template_file.deployment.rendered) +# } + +# 1. namespace +# 2. service +# 3. horizontal scaling policy +# 4. vertical scaling policy +# 5. certificate +# 6. ingress +# 7. route53 dns record diff --git a/api/kubernetes/terraform.tf b/api/kubernetes/terraform.tf new file mode 100644 index 00000000..ffc69644 --- /dev/null +++ b/api/kubernetes/terraform.tf @@ -0,0 +1,35 @@ +#------------------------------------------------------------------------------ +# written by: Lawrence McDaniel +# https://lawrencemcdaniel.com/ +# +# date: July-2023 +# +# usage: Terraform configuration +#------------------------------------------------------------------------------ + +terraform { + required_version = "~> 1.5" + backend "s3" { + bucket = "090511222473-tfstate-openai" + key = "chat_api/terraform.tfstate" + region = "us-east-1" + dynamodb_table = "090511222473-tfstate-lock-openai" + profile = "lawrence" + encrypt = false + } + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.35" + } + null = { + source = "hashicorp/null" + version = "~> 3.2" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.25" + } + } +} diff --git a/api/kubernetes/terraform.tfvars b/api/kubernetes/terraform.tfvars new file mode 100755 index 00000000..f623d702 --- /dev/null +++ b/api/kubernetes/terraform.tfvars @@ -0,0 +1,40 @@ +#------------------------------------------------------------------------------ +# written by: Lawrence McDaniel +# https://lawrencemcdaniel.com/ +# +# date: sep-2023 +# +# usage: override default variable values +#------------------------------------------------------------------------------ + +############################################################################### +# AWS CLI parameters +############################################################################### +aws_account_id = "090511222473" +tags = { + "terraform" = "true", + "project" = "chatGPT microservice" + "contact" = "Lawrence McDaniel - https://lawrencemcdaniel.com/" +} +aws_region = "us-east-2" +aws_profile = "lawrence" + +############################################################################### +# OpenAI API parameters +############################################################################### +openai_endpoint_image_n = 4 +openai_endpoint_image_size = "1024x768" + + +############################################################################### +# CloudWatch logging parameters +############################################################################### +logging_level = "INFO" + + +############################################################################### +# APIGateway parameters +############################################################################### +root_domain = "lawrencemcdaniel.com" +shared_resource_identifier = "openai" +stage = "v1" diff --git a/api/kubernetes/variables.tf b/api/kubernetes/variables.tf new file mode 100644 index 00000000..a0badde5 --- /dev/null +++ b/api/kubernetes/variables.tf @@ -0,0 +1,70 @@ +#------------------------------------------------------------------------------ +# written by: Lawrence McDaniel +# https://lawrencemcdaniel.com/ +# +# date: sep-2023 +# +# usage: all Terraform variable declarations +#------------------------------------------------------------------------------ +variable "shared_resource_identifier" { + description = "A common identifier/prefix for resources created for this demo" + type = string + default = "openai" +} + +variable "aws_account_id" { + description = "12-digit AWS account number" + type = string +} +variable "aws_region" { + description = "A valid AWS data center region code" + type = string + default = "us-east-1" +} +variable "aws_profile" { + description = "a valid AWS CLI profile located in $HOME/.aws/credentials" + type = string + default = "default" +} + +variable "debug_mode" { + type = bool + default = false +} +variable "tags" { + description = "A map of tags to add to all resources. Tags added to launch configuration or templates override these values." + type = map(string) + default = {} +} + +############################################################################### +# OpenAI API parameters +############################################################################### +variable "openai_endpoint_image_n" { + description = "FIX NOTE: what is this?" + type = number + default = 4 +} +variable "openai_endpoint_image_size" { + description = "Image output dimensions in pixels" + type = string + default = "1024x768" +} + + +variable "root_domain" { + description = "a valid Internet domain name which you directly control using AWS Route53 in this account" + type = string + default = "" +} + +variable "stage" { + description = "Examples: dev, staging, prod, v0, v1, etc." + type = string + default = "v1" +} + +variable "logging_level" { + type = string + default = "INFO" +} diff --git a/api/kubernetes/yaml/certificate-manager.yaml b/api/kubernetes/yaml/certificate-manager.yaml new file mode 100644 index 00000000..e69de29b diff --git a/api/kubernetes/yaml/deployment.yaml b/api/kubernetes/yaml/deployment.yaml new file mode 100644 index 00000000..d1038183 --- /dev/null +++ b/api/kubernetes/yaml/deployment.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: openai-api + labels: + app: openai-api +spec: + replicas: 1 + selector: + matchLabels: + app: openai-api + template: + metadata: + labels: + app: openai-api + spec: + containers: + - name: openai-api + image: "${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/${var.ecr_repo_name}:${var.ecr_repo_tag}" + env: + - name: ENV_VAR1 + value: "value1" + - name: ENV_VAR2 + value: "value2" + ports: + - containerPort: 8080 diff --git a/api/kubernetes/yaml/horizontal-pod-autoscaling-policy.yaml b/api/kubernetes/yaml/horizontal-pod-autoscaling-policy.yaml new file mode 100644 index 00000000..e69de29b diff --git a/api/kubernetes/yaml/ingress.yaml b/api/kubernetes/yaml/ingress.yaml new file mode 100644 index 00000000..f9a60f55 --- /dev/null +++ b/api/kubernetes/yaml/ingress.yaml @@ -0,0 +1,44 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + cert-manager.io/cluster-issuer: api.lawrencemcdaniel.com + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"annotations":{"cert-manager.io/cluster-issuer":"api.lawrencemcdaniel.com","kubernetes.io/ingress.class":"nginx","nginx.ingress.kubernetes.io/affinity":"cookie","nginx.ingress.kubernetes.io/backend-protocol":"HTTP","nginx.ingress.kubernetes.io/force-ssl-redirect":"true","nginx.ingress.kubernetes.io/proxy-body-size":"0","nginx.ingress.kubernetes.io/proxy-buffer-size":"256k","nginx.ingress.kubernetes.io/proxy-buffers":"4 512k","nginx.ingress.kubernetes.io/proxy-busy-buffers-size":"512k","nginx.ingress.kubernetes.io/session-cookie-expires":"172800","nginx.ingress.kubernetes.io/session-cookie-max-age":"172800","nginx.ingress.kubernetes.io/session-cookie-name":"wordpress_sticky_session"},"name":"api.lawrencemcdaniel.com","namespace":"lawrencemcdaniel-api"},"spec":{"rules":[{"host":"api.lawrencemcdaniel.com","http":{"paths":[{"backend":{"service":{"name":"wordpress","port":{"number":80}}},"path":"/","pathType":"Prefix"}]}}],"tls":[{"hosts":["api.lawrencemcdaniel.com"],"secretName":"api.lawrencemcdaniel.com-tls"}]}} + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/affinity: cookie + nginx.ingress.kubernetes.io/backend-protocol: HTTP + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "0" + nginx.ingress.kubernetes.io/proxy-buffer-size: 256k + nginx.ingress.kubernetes.io/proxy-buffers: 4 512k + nginx.ingress.kubernetes.io/proxy-busy-buffers-size: 512k + nginx.ingress.kubernetes.io/session-cookie-expires: "172800" + nginx.ingress.kubernetes.io/session-cookie-max-age: "172800" + nginx.ingress.kubernetes.io/session-cookie-name: wordpress_sticky_session + creationTimestamp: "2023-08-22T03:08:08Z" + generation: 1 + name: api.lawrencemcdaniel.com + namespace: lawrencemcdaniel-api + resourceVersion: "79637258" + uid: 0170d971-3b48-46d5-9308-ba4b4a678634 +spec: + rules: + - host: api.lawrencemcdaniel.com + http: + paths: + - backend: + service: + name: wordpress + port: + number: 80 + path: / + pathType: Prefix + tls: + - hosts: + - api.lawrencemcdaniel.com + secretName: api.lawrencemcdaniel.com-tls +status: + loadBalancer: + ingress: + - hostname: a1db5dfcf202b4a63bdcd0f3c03e769f-769707598.us-east-2.elb.amazonaws.com diff --git a/api/kubernetes/yaml/service.yaml b/api/kubernetes/yaml/service.yaml new file mode 100644 index 00000000..5904c303 --- /dev/null +++ b/api/kubernetes/yaml/service.yaml @@ -0,0 +1,40 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + meta.helm.sh/release-name: wordpress + meta.helm.sh/release-namespace: lawrencemcdaniel-api + creationTimestamp: "2023-08-22T03:07:54Z" + labels: + app.kubernetes.io/instance: wordpress + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: wordpress + helm.sh/chart: wordpress-15.2.61 + name: wordpress + namespace: lawrencemcdaniel-api + resourceVersion: "79636663" + uid: f6492a38-d0b8-47c9-b488-aa9b7402b561 +spec: + clusterIP: 10.100.140.218 + clusterIPs: + - 10.100.140.218 + internalTrafficPolicy: Cluster + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - name: http + port: 80 + protocol: TCP + targetPort: http + - name: https + port: 443 + protocol: TCP + targetPort: https + selector: + app.kubernetes.io/instance: wordpress + app.kubernetes.io/name: wordpress + sessionAffinity: None + type: ClusterIP +status: + loadBalancer: {} diff --git a/api/kubernetes/yaml/vertical-pod-autoscaling-policy.yaml b/api/kubernetes/yaml/vertical-pod-autoscaling-policy.yaml new file mode 100644 index 00000000..cf28b60b --- /dev/null +++ b/api/kubernetes/yaml/vertical-pod-autoscaling-policy.yaml @@ -0,0 +1,55 @@ +apiVersion: autoscaling.k8s.io/v1 +kind: VerticalPodAutoscaler +metadata: + creationTimestamp: "2023-08-22T03:08:10Z" + generation: 117309 + name: vpa-recommender-wordpress + namespace: lawrencemcdaniel-api + resourceVersion: "171470577" + uid: 08c46fcd-c2a4-4c32-9393-60ee7d35bf5b +spec: + resourcePolicy: + containerPolicies: + - containerName: wordpress + maxAllowed: + cpu: 1000m + memory: 1000Mi + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: wordpress + updatePolicy: + updateMode: Auto +status: + conditions: + - lastTransitionTime: "2023-08-22T03:10:22Z" + status: "True" + type: RecommendationProvided + recommendation: + containerRecommendations: + - containerName: metrics + lowerBound: + cpu: 12m + memory: 131072k + target: + cpu: 12m + memory: 131072k + uncappedTarget: + cpu: 12m + memory: 131072k + upperBound: + cpu: 15m + memory: 131072k + - containerName: wordpress + lowerBound: + cpu: 22m + memory: "865925832" + target: + cpu: 23m + memory: 1000Mi + uncappedTarget: + cpu: 23m + memory: "1389197403" + upperBound: + cpu: 23m + memory: 1000Mi diff --git a/api/terraform/python/layer_nlp/requirements.txt b/api/terraform/python/layer_nlp/requirements.txt index e1ee2aa5..1243c0c9 100644 --- a/api/terraform/python/layer_nlp/requirements.txt +++ b/api/terraform/python/layer_nlp/requirements.txt @@ -8,13 +8,16 @@ # requirements in the zip package for this layer. # ----------------------------------------------------------------------------- +pyyaml==6.0.1 + # NLP requirements # -------------------------- python-Levenshtein==0.26.1 pyyaml # weather function -googlemaps -openmeteo-requests -requests-cache -retry-requests +# -------------------------- +googlemaps==4.10.0 +openmeteo-requests==1.1.0 +requests-cache==1.1.1 +retry-requests==2.0.0 diff --git a/api/terraform/python/layer_pandas/requirements.txt b/api/terraform/python/layer_pandas/requirements.txt index f5e4ce66..a37af469 100644 --- a/api/terraform/python/layer_pandas/requirements.txt +++ b/api/terraform/python/layer_pandas/requirements.txt @@ -5,4 +5,4 @@ # usage: Shared Python requirements for anything requiring Pandas or NumPy # ----------------------------------------------------------------------------- -pandas +pandas==0.0.1 diff --git a/requirements.txt b/requirements.txt index 65bf2ced..8caed9e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -54,9 +54,7 @@ python-Levenshtein==0.26.0 pyyaml # weather function -googlemaps -openmeteo-requests -requests-cache -retry-requests -numpy -pandas +googlemaps==4.10.0 +openmeteo-requests==1.1.0 +requests-cache==1.1.1 +retry-requests==2.0.0