Lo último que discutiremos es el proceso de implementación de cada uno de los componentes en AWS. La canalización de datos, el backend y el frontend están contenidos cada uno dentro de sus propias pilas de CloudFormation (colecciones de recursos de AWS). Permitir que se implementen de forma aislada de esta manera garantiza que toda la aplicación no se vuelva a implementar innecesariamente durante el desarrollo. Utilizo AWS SAM (modelo de aplicación sin servidor) para implementar la infraestructura para cada componente como código, aprovechando la especificación de la plantilla SAM y la CLI:
- La especificación de plantilla SAM: una sintaxis abreviada que sirve como una extensión de AWS CloudFormation para definir y configurar colecciones de recursos de AWS, cómo deben interactuar y los permisos necesarios.
- SAM CLI: una herramienta de línea de comandos que se utiliza, entre otras cosas, para crear e implementar recursos según lo definido en una plantilla SAM. Maneja el empaquetado del código de la aplicación y las dependencias, convirtiendo la plantilla SAM a la sintaxis de CloudFormation e implementando plantillas como pilas individuales en CloudFormation.
En lugar de incluir las plantillas completas (definiciones de recursos) de cada componente, resaltaré áreas de interés específicas para cada servicio que hemos analizado a lo largo de la publicación.
Pasar variables de entorno confidenciales a recursos de AWS:
En toda la aplicación se depende en gran medida de componentes externos como Youtube Data API, OpenAI API y Pinecone API. Aunque es posible codificar estos valores en las plantillas de CloudFormation y pasarlos como ‘parámetros’, un método más seguro es crear secretos para cada uno en AWS SecretsManager y hacer referencia a estos secretos en la plantilla de esta manera:
Parameters:
YoutubeDataAPIKey:
Type: String
Default: '{{resolve:secretsmanager:youtube-data-api-key:SecretString:youtube-data-api-key}}'
PineconeAPIKey:
Type: String
Default: '{{resolve:secretsmanager:pinecone-api-key:SecretString:pinecone-api-key}}'
OpenaiAPIKey:
Type: String
Default: '{{resolve:secretsmanager:openai-api-key:SecretString:openai-api-key}}'
Definición de una función Lambda:
Estas unidades de código sin servidor forman la columna vertebral de la canalización de datos y sirven como punto de entrada al backend de la aplicación web. Para implementarlos usando SAM, es tan simple como definir la ruta al código que la función debe ejecutar cuando se invoca, junto con los permisos y las variables de entorno necesarios. A continuación se muestra un ejemplo de una de las funciones utilizadas en la canalización de datos:
FetchLatestVideoIDsFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ../code_uri/.
Handler: chatytt.youtube_data.lambda_handlers.fetch_latest_video_ids.lambda_handler
Policies:
- AmazonS3FullAccess
Environment:
Variables:
PLAYLIST_NAME:
Ref: PlaylistName
YOUTUBE_DATA_API_KEY:
Ref: YoutubeDataAPIKey
Recuperando la definición de canalización de datos en el idioma de los estados de Amazon:
Para utilizar Step Functions como orquestador para las funciones Lambda individuales en la canalización de datos, debemos definir el orden en el que se debe ejecutar cada una, así como configuraciones como el número máximo de reintentos en el lenguaje de estados de Amazon. Una forma sencilla de hacerlo es utilizando el Estudio de flujo de trabajo en la consola de Step Functions para crear esquemáticamente el flujo de trabajo y luego tomar la definición ASL generada automáticamente del flujo de trabajo como punto de partida que se puede modificar adecuadamente. Luego, esto se puede vincular en la plantilla de CloudFormation en lugar de definirlo en el lugar:
EmbeddingRetrieverStateMachine:
Type: AWS::Serverless::StateMachine
Properties:
DefinitionUri: statemachine/embedding_retriever.asl.json
DefinitionSubstitutions:
FetchLatestVideoIDsFunctionArn: !GetAtt FetchLatestVideoIDsFunction.Arn
FetchLatestVideoTranscriptsArn: !GetAtt FetchLatestVideoTranscripts.Arn
FetchLatestTranscriptEmbeddingsArn: !GetAtt FetchLatestTranscriptEmbeddings.Arn
Events:
WeeklySchedule:
Type: Schedule
Properties:
Description: Schedule to run the workflow once per week on a Monday.
Enabled: true
Schedule: cron(0 3 ? * 1 *)
Policies:
- LambdaInvokePolicy:
FunctionName: !Ref FetchLatestVideoIDsFunction
- LambdaInvokePolicy:
FunctionName: !Ref FetchLatestVideoTranscripts
- LambdaInvokePolicy:
FunctionName: !Ref FetchLatestTranscriptEmbeddings
Ver aquí para la definición de ASL utilizada para la canalización de datos analizada en esta publicación.
Definición del recurso API:
Dado que la API para la aplicación web se alojará por separado del front-end, debemos habilitar la compatibilidad con CORS (intercambio de recursos entre orígenes) al definir el recurso API:
ChatYTTApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Cors:
AllowMethods: "'*'"
AllowHeaders: "'*'"
AllowOrigin: "'*'"
Esto permitirá que los dos recursos se comuniquen libremente entre sí. Los diversos puntos finales a los que se puede acceder a través de una función Lambda se pueden definir de la siguiente manera:
ChatResponseFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.9
Timeout: 120
CodeUri: ../code_uri/.
Handler: server.lambda_handler.lambda_handler
Policies:
- AmazonDynamoDBFullAccess
MemorySize: 512
Architectures:
- x86_64
Environment:
Variables:
PINECONE_API_KEY:
Ref: PineconeAPIKey
OPENAI_API_KEY:
Ref: OpenaiAPIKey
Events:
GetQueryResponse:
Type: Api
Properties:
RestApiId: !Ref ChatYTTApi
Path: /get-query-response/
Method: post
GetChatHistory:
Type: Api
Properties:
RestApiId: !Ref ChatYTTApi
Path: /get-chat-history/
Method: get
UpdateChatHistory:
Type: Api
Properties:
RestApiId: !Ref ChatYTTApi
Path: /save-chat-history/
Method: put
Definición del recurso de la aplicación React:
AWS Amplify puede crear e implementar aplicaciones utilizando una referencia al repositorio de Github relevante y un token de acceso apropiado:
AmplifyApp:
Type: AWS::Amplify::App
Properties:
Name: amplify-chatytt-client
Repository: <https://github.com/suresha97/ChatYTT>
AccessToken: '{{resolve:secretsmanager:github-token:SecretString:github-token}}'
IAMServiceRole: !GetAtt AmplifyRole.Arn
EnvironmentVariables:
- Name: ENDPOINT
Value: !ImportValue 'chatytt-api-ChatYTTAPIURL'
Una vez que se pueda acceder al repositorio, Ampify buscará un archivo de configuración con instrucciones sobre cómo construir e implementar la aplicación:
version: 1
frontend:
phases:
preBuild:
commands:
- cd client
- npm ci
build:
commands:
- echo "VITE_ENDPOINT=$ENDPOINT" >> .env
- npm run build
artifacts:
baseDirectory: ./client/dist
files:
- "**/*"
cache:
paths:
- node_modules/**/*
Como beneficio adicional, también es posible automatizar el proceso de implementación continua definiendo un recurso de sucursal que será monitoreado y utilizado para volver a implementar la aplicación automáticamente en futuras confirmaciones:
AmplifyBranch:
Type: AWS::Amplify::Branch
Properties:
BranchName: main
AppId: !GetAtt AmplifyApp.AppId
EnableAutoBuild: true
Una vez finalizada la implementación de esta manera, cualquiera que tenga el enlace disponible desde la consola de AWS Amplify podrá acceder a ella. Se puede encontrar una demostración grabada de la aplicación a la que se accede de esta manera. aquí: