Detalhamento Técnico do Pipeline de CI/CD

Lucas Lascasas
12 min readDec 27, 2023

--

Autores

Introdução

Esse artigo é uma continuação técnica de outro trabalho realizado com o intuito de explicar em detalhes o código disponibilizado no GitHub. Assim, esse artigo irá explicar com maior riqueza de detalhes a arquitetura montada na AWS para a esteira de CI/CD (Continuous Integration / Continuous Deployment) que implanta os códigos IaC (Infraestructure as Code) de um sistema de aprendizado automatizado.

A Figura abaixo representa a arquitetura da esteira de implantação de código IaC utilizada no projeto. Assim, um template do CloudFormation cria as roles IAM, bucket do S3 e a esteira pelo CodePipeline, CodeCommit e CodeBuild. Uma vez que o repositório é atualizado, o pipeline é disparado, passando pela etapa de build e deploy via CloudFormation.

Arquitetura do Pipeline de CI/CD via IaC

As seções seguintes vão detalhar o código, além de direcionar o passo a passo de implantação.

Pré-requisitos

Essa seção detalha os requisitos necessários antes da implantação do template CloudFormation.

Conta AWS e IAM User

Para realizar a implantação dessa estrutura, é necessária uma conta AWS criada e um usuário AWS com acesso para criação de roles e estruturas no CloudFormation. Seguem algumas referências para tal:

VPC

A API requer uma VPC criada, com Security Group, Subnets Privadas e um Endpoint do API Gateway para a configuração de segurança de acesso. Seguem as referências para essa criação:

Deploy Pipeline

O arquivo “deploy-pipeline.yaml”, encontrado na raiz do projeto, é o código IaC responsável por realizar a construção da esteira de CI/CD, roles do projeto e o bucket de artefatos. Assim, esse template deve ser criado manualmente no início da implantação para evitar um famoso “loophole” de segurança. Para tal, o usuário deverá definir os seguintes parâmetros:

Parameters:
ApplicationName:
Type: "String"
Description: "The name of the application"
SecurityGroupID:
Type: "AWS::EC2::SecurityGroup::Id"
Description: "The Security Group in the VPC to control access to services"
SubnetIDs:
Type: "List<AWS::EC2::Subnet::Id>"
Description: "List of subnets in the VPC. Recommended 3 subnets."
VPCEndpointAPIID:
Type: "String"
Description: Endpoint ID for API Gateway
  • ApplicationName: nome base da aplicação para configuração de demais serviços;
  • SecurityGroupID: ID do security group da VPC criada para o projeto;
  • SubnetIDs: IDs das subnets escolhidas para o projeto;
  • VCPEndepointAPIID: ID do endpoint de API Gateway na VPC.

Dessa forma, o template cria os seguintes recursos ao ser executado:

  CodeCommitRepo:
Type: "AWS::CodeCommit::Repository"
Properties:
RepositoryName: !Sub "${ApplicationName}-repository"

Repositório no CodeCommit.

  DeployArtifactsBucket:
Type: "AWS::S3::Bucket"
Properties:
BucketName: !Sub "${ApplicationName}-${AWS::AccountId}-${AWS::Region}-da"
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256

Bucket de artefatos no S3 com criptografia padrão do próprio serviço.

  CodeBuildRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "codebuild.amazonaws.com"
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess"
Policies:
- PolicyName: "CodeBuildBasePolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource:
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/*"
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/*:*"
- Effect: "Allow"
Action:
- "s3:PutObject"
- "s3:GetObject"
- "s3:GetObjectVersion"
- "s3:GetBucketAcl"
- "s3:GetBucketLocation"
- "s3:ListBucket"
Resource:
- !GetAtt "DeployArtifactsBucket.Arn"
- !Join ['/', [!GetAtt "DeployArtifactsBucket.Arn", "*"]]
- Effect: "Allow"
Action: "codecommit:GitPull"
Resource: !GetAtt "CodeCommitRepo.Arn"
- Effect: "Allow"
Action:
- "codebuild:CreateReportGroup"
- "codebuild:CreateReport"
- "codebuild:UpdateReport"
- "codebuild:BatchPutTestCases"
- "codebuild:BatchPutCodeCoverages"
Resource: !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:report-group/"

Role do CodeBuild com as seguintes permissões:

  • Logs no CloudWatch;
  • Manipulação do bucket de artefatos e seus objetos;
  • Git Pull no repositório do CodeCommit;
  • Ações de report no CodeBuild para a região e conta sendo utilizada.
  CodeBuildProject:
Type: "AWS::CodeBuild::Project"
Properties:
Name: !Ref "ApplicationName"
Artifacts:
Type: "S3"
Location: !Ref "DeployArtifactsBucket"
Environment:
ComputeType: "BUILD_GENERAL1_SMALL"
Image: "aws/codebuild/standard:4.0"
Type: "LINUX_CONTAINER"
ServiceRole: !GetAtt "CodeBuildRole.Arn"
Source:
Type: "CODECOMMIT"
Location: !Sub "https://git-codecommit.${AWS::Region}.amazonaws.com/v1/repos/${CodeCommitRepo}"

Projeto no CodeBuild, definindo o local de armazenamento dos artefatos, imagem da máquina a ser usada, role de acesso e fonte.

  CodePipelineRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "codepipeline.amazonaws.com"
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: "AWSCodePipelineServiceRole"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action: "iam:PassRole"
Resource: "*"
Condition:
StringEqualsIfExists:
iam:PassedToService:
- "cloudformation.amazonaws.com"
- Effect: "Allow"
Action:
- "codecommit:CancelUploadArchive"
- "codecommit:GetBranch"
- "codecommit:GetCommit"
- "codecommit:GetUploadArchiveStatus"
- "codecommit:UploadArchive"
Resource: "*"
- Effect: "Allow"
Action: "codecommit:GitPull"
Resource: !GetAtt "CodeCommitRepo.Arn"
- Effect: "Allow"
Action:
- "cloudwatch:*"
- "s3:*"
- "lambda:*"
Resource: "*"
- Effect: "Allow"
Action:
- "cloudformation:CreateStack"
- "cloudformation:DeleteStack"
- "cloudformation:DescribeStacks"
- "cloudformation:UpdateStack"
- "cloudformation:CreateChangeSet"
- "cloudformation:DeleteChangeSet"
- "cloudformation:DescribeChangeSet"
- "cloudformation:ExecuteChangeSet"
- "cloudformation:SetStackPolicy"
- "cloudformation:ValidateTemplate"
Resource: "*"
- Effect: "Allow"
Action:
- "codebuild:BatchGetBuilds"
- "codebuild:StartBuild"
- "codebuild:BatchGetBuildBatches"
- "codebuild:StartBuildBatch"
Resource: "*"

Role do CodePipeline com as seguintes permissões:

  • IAM pass role para permitir a criação dos recursos por CloudFormation;
  • Ações do CodeCommit para recuperar artefatos do repositório;
  • Git Pull no repositório do projeto;
  • Permissão ADM para os recursos do projeto (recomendamos refinar esse nível de acesso de acordo com as necessidades e adaptações da arquitetura do projeto);
  • Acessos no CloudFormation para a criação da stack de implantação;
  • Ações do CodeBuild para a execução da etapa.
  CodePipelineDev:
Type: "AWS::CodePipeline::Pipeline"
Properties:
ArtifactStore:
Location: !Ref "DeployArtifactsBucket"
Type: "S3"
RestartExecutionOnUpdate: false
Name: !Sub "${ApplicationName}-pipeline-dev"
RoleArn: !GetAtt "CodePipelineRole.Arn"
Stages:
- Name: "Source"
Actions:
- Name: "Source"
ActionTypeId:
Category: "Source"
Owner: "AWS"
Provider: "CodeCommit"
Version: "1"
Configuration:
BranchName: "dev"
RepositoryName: !GetAtt CodeCommitRepo.Name
OutputArtifacts:
- Name: "SourceArtifact"
- Name: "Build"
Actions:
- Name: "Build"
ActionTypeId:
Category: "Build"
Owner: "AWS"
Provider: "CodeBuild"
Version: "1"
Configuration:
ProjectName: !Ref "CodeBuildProject"
EnvironmentVariables: !Sub
- |
[
{
"name": "STAGE",
"value": "dev"
},
{
"name": "ARTIFACT_BUCKET",
"value": "${DeployArtifactsBucket}"
},
{
"name": "SECURITY_GROUP_ID",
"value": "${SecurityGroupID}"
},
{
"name": "VPC_ENDPOINT_API_ID",
"value": "${VPCEndpointAPIID}"
},
{
"name": "SUBNET_IDS",
"value": "${SUBNET_LIST_STR}"
},
{
"name": "ROLES_LIST",
"value": "${APILambdaRole.Arn},${LambdaRole.Arn},${SageMakerExecutionRole.Arn},${SFNOrchestratorRole.Arn}"
}
]
- SUBNET_LIST_STR: !Join [",", !Ref SubnetIDs]
BatchEnabled: false
InputArtifacts:
- Name: "SourceArtifact"
OutputArtifacts:
- Name: "BuildArtifact"
- Name: "Deploy"
Actions:
- Name: "UpdateChangeSet"
ActionTypeId:
Category: "Deploy"
Owner: "AWS"
Provider: "CloudFormation"
Version: "1"
Configuration:
ActionMode: "CHANGE_SET_REPLACE"
StackName: !Sub "${ApplicationName}-sam-dev"
Capabilities: "CAPABILITY_IAM,CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND"
ChangeSetName: !Sub "${ApplicationName}-changeset-dev"
TemplatePath: "BuildArtifact::cloudformation/serverless.yaml"
TemplateConfiguration: "BuildArtifact::cloudformation/configuration.json"
RoleArn: !GetAtt "CloudFormationRole.Arn"
InputArtifacts:
- Name: "BuildArtifact"
RunOrder: 1
- Name: "ExecuteChangeSet"
ActionTypeId:
Category: "Deploy"
Owner: "AWS"
Provider: "CloudFormation"
Version: "1"
Configuration:
ActionMode: "CHANGE_SET_EXECUTE"
StackName: !Sub "${ApplicationName}-sam-dev"
ChangeSetName: !Sub "${ApplicationName}-changeset-dev"
InputArtifacts:
- Name: "BuildArtifact"
RunOrder: 2

Pipelines em ambiente de desenvolvimento (dev), homologação (hml) e produção (prd), configurando-se o bucket de artefatos, role de execução, os seguintes estágios:

  • Source: configuração para observar o repositório do projeto na branch específica do pipeline (ex: dev observando dev);
  • Build: configuração das variáveis de ambiente para o CloudFormation que cria os recursos de nuvem e referência ao projeto de CodeBuild;
  • Deploy: esse estágio é dividido em duas partes:
  • UpdateChangeSet: etapa que atualiza o changeset do CloudFormation referenciando o código SAM (CloudFormation/serverless.yaml) e as configurações (CloudFormation/configuration.json)
  • ExecuteChageSet: etapa de deploy que executa as atualizações feitas no changeset do CloudFormation, atualizando, criando e deletando estruturas na AWS
  CloudFormationRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "cloudformation.amazonaws.com"
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AWSLambda_FullAccess"
- "arn:aws:iam::aws:policy/AWSCloudFormationFullAccess"
- "arn:aws:iam::aws:policy/AmazonS3FullAccess"
- "arn:aws:iam::aws:policy/AmazonAPIGatewayAdministrator"
- "arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess"
- "arn:aws:iam::aws:policy/AmazonEventBridgeFullAccess"
Policies:
- PolicyName: "PassRoleAccess"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action: "iam:PassRole"
Resource: "*"

Role do CloudFormation, habilitando acesso aos serviços necessários para o projeto, sendo eles:

  • Lambda
  • CloudFormation
  • S3
  • API Gateway
  • Step Functions
  • EventBridge

Observação: por simplicidade da role, utilizamos políticas gerenciadas e o iam:PassRole para que o CloudFormation possa criar as estruturas necessárias na conta. Entretanto, em cenários produtivos em contas com outros recursos, recomendamos a limitação desse acesso.

  APILambdaRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: "APILambdaRole"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "sagemaker:DescribeModelPackage"
- "sagemaker:ListCandidatesForAutoMLJob"
- "sagemaker:ListModelBiasJobDefinitions"
- "sagemaker:ListTransformJobs"
- "sagemaker:ListHumanTaskUis"
- "sagemaker:Search"
- "sagemaker:DescribeModelPackageGroup"
- "sagemaker:GetRecord"
- "sagemaker:DescribeFlowDefinition"
- "sagemaker:ListTrainingJobs"
- "sagemaker:DescribeAlgorithm"
- "sagemaker:ListExperiments"
- "sagemaker:DescribeTransformJob"
- "sagemaker:ListFeatureGroups"
- "sagemaker:DescribeInferenceRecommendationsJob"
- "sagemaker:DescribeHumanLoop"
- "sagemaker:BatchDescribeModelPackage"
- "sagemaker:DescribeDeviceFleet"
- "sagemaker:DescribeHyperParameterTuningJob"
- "sagemaker:ListCompilationJobs"
- "ec2:CreateNetworkInterface"
- "sagemaker:DescribeWorkforce"
- "sagemaker:DescribeProcessingJob"
- "sagemaker:GetDeviceFleetReport"
- "sagemaker:DescribeStudioLifecycleConfig"
- "sagemaker:ListStudioLifecycleConfigs"
- "sagemaker:RenderUiTemplate"
- "sagemaker:ListModelExplainabilityJobDefinitions"
- "sagemaker:DescribeImageVersion"
- "sagemaker:ListPipelineParametersForExecution"
- "sagemaker:ListDomains"
- "sagemaker:ListEdgePackagingJobs"
- "sagemaker:ListModelMetadata"
- "sagemaker:ListUserProfiles"
- "sagemaker:ListWorkteams"
- "sagemaker:DescribeHumanTaskUi"
- "sagemaker:DescribeProject"
- "sagemaker:GetSagemakerServicecatalogPortfolioStatus"
- "sagemaker:ListImageVersions"
- "sagemaker:ListAutoMLJobs"
- "sagemaker:ListMonitoringSchedules"
- "sagemaker:ListInferenceRecommendationsJobSteps"
- "sagemaker:ListProcessingJobs"
- "sagemaker:ListEdgeDeploymentPlans"
- "sagemaker:DescribeModelExplainabilityJobDefinition"
- "sagemaker:DeleteEndpoint"
- "sagemaker:ListDevices"
- "sagemaker:DescribeEndpoint"
- "sagemaker:ListInferenceRecommendationsJobs"
- "sagemaker:DescribeUserProfile"
- "sagemaker:InvokeEndpoint"
- "sagemaker:DescribeFeatureMetadata"
- "sagemaker:DescribeEdgePackagingJob"
- "sagemaker:DescribeFeatureGroup"
- "sagemaker:DescribeModelQualityJobDefinition"
- "sagemaker:DescribeModel"
- "sagemaker:DescribePipeline"
- "sagemaker:DescribeArtifact"
- "sagemaker:ListHyperParameterTuningJobs"
- "sagemaker:ListWorkforces"
- "sagemaker:DescribeImage"
- "sagemaker:ListPipelines"
- "sagemaker:InvokeEndpointAsync"
- "sagemaker:DescribePipelineDefinitionForExecution"
- "sagemaker:DescribeTrialComponent"
- "sagemaker:ListEndpoints"
- "sagemaker:ListApps"
- "sagemaker:DescribeTrainingJob"
- "sagemaker:DescribeLabelingJob"
- "sagemaker:DescribeDataQualityJobDefinition"
- "sagemaker:ListArtifacts"
- "sagemaker:ListDataQualityJobDefinitions"
- "sagemaker:ListMonitoringExecutions"
- "sagemaker:DescribeApp"
- "sagemaker:GetLineageGroupPolicy"
- "logs:CreateLogStream"
- "sagemaker:ListSubscribedWorkteams"
- "sagemaker:ListLabelingJobsForWorkteam"
- "sagemaker:ListLineageGroups"
- "sagemaker:ListPipelineExecutions"
- "sagemaker:DescribeAction"
- "sagemaker:CreateEndpoint"
- "sagemaker:ListAlgorithms"
- "ec2:DeleteNetworkInterface"
- "sagemaker:ListNotebookInstanceLifecycleConfigs"
- "sagemaker:DescribeSubscribedWorkteam"
- "sagemaker:ListTrials"
- "sagemaker:ListDeviceFleets"
- "logs:CreateLogGroup"
- "sagemaker:DescribeAutoMLJob"
- "sagemaker:ListEndpointConfigs"
- "sagemaker:CreateEndpointConfig"
- "sagemaker:ListTrainingJobsForHyperParameterTuningJob"
- "sagemaker:ListActions"
- "sagemaker:DescribeEndpointConfig"
- "sagemaker:ListStageDevices"
- "sagemaker:ListAppImageConfigs"
- "sagemaker:BatchGetRecord"
- "sagemaker:DeleteEndpointConfig"
- "sagemaker:GetDeviceRegistration"
- "sagemaker:DescribeNotebookInstance"
- "sagemaker:DescribeAppImageConfig"
- "sagemaker:ListProjects"
- "sagemaker:ListContexts"
- "sagemaker:DescribeLineageGroup"
- "sagemaker:ListHumanLoops"
- "sagemaker:DescribeNotebookInstanceLifecycleConfig"
- "sagemaker:QueryLineage"
- "sagemaker:DescribeTrial"
- "sagemaker:ListAssociations"
- "sagemaker:DescribeContext"
- "ec2:DescribeNetworkInterfaces"
- "sagemaker:DescribeEdgeDeploymentPlan"
- "sagemaker:ListModelPackageGroups"
- "sagemaker:ListImages"
- "sagemaker:ListModelQualityJobDefinitions"
- "sagemaker:ListNotebookInstances"
- "sagemaker:ListFlowDefinitions"
- "sagemaker:DescribeMonitoringSchedule"
- "sagemaker:ListTags"
- "sagemaker:UpdateModelPackage"
- "sagemaker:GetModelPackageGroupPolicy"
- "sagemaker:ListTrialComponents"
- "logs:PutLogEvents"
- "sagemaker:DescribePipelineExecution"
- "sagemaker:DescribeWorkteam"
- "sagemaker:ListModelPackages"
- "sagemaker:DescribeModelBiasJobDefinition"
- "sagemaker:ListLabelingJobs"
- "sagemaker:BatchGetMetrics"
- "sagemaker:DescribeCompilationJob"
- "sagemaker:GetSearchSuggestions"
- "sagemaker:DescribeExperiment"
- "sagemaker:DescribeDomain"
- "sagemaker:ListModels"
- "sagemaker:DescribeCodeRepository"
- "sagemaker:ListPipelineExecutionSteps"
- "sagemaker:DescribeDevice"
- "sagemaker:ListCodeRepositories"
Resource:
- "*"

Role das funções Lambda das APIs com acessos de leitura no Sagemaker, acesso ao CloudWatch para logs e à EC2 para configurações de VPC.

  LambdaRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: "LambdaRole"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "s3:ListAccessPointsForObjectLambda"
- "sagemaker:DescribeTrainingJob"
- "states:ListStateMachines"
- "sagemaker:DescribeModelPackage"
- "states:DescribeStateMachine"
- "s3:PutStorageLensConfiguration"
- "sagemaker:DescribeModelPackageGroup"
- "sagemaker:CreateModelPackageGroup"
- "ecr:BatchGetRepositoryScanningConfiguration"
- "logs:CreateLogStream"
- "states:GetExecutionHistory"
- "ecr:BatchCheckLayerAvailability"
- "ecr:GetLifecyclePolicy"
- "s3:PutAccountPublicAccessBlock"
- "states:DescribeActivity"
- "ecr:DescribeImageScanFindings"
- "states:ListActivities"
- "s3:ListJobs"
- "ecr:GetDownloadUrlForLayer"
- "ecr:DescribePullThroughCacheRules"
- "sagemaker:CreateModelPackage"
- "ecr:GetAuthorizationToken"
- "states:StartSyncExecution"
- "logs:CreateLogGroup"
- "states:DescribeExecution"
- "s3:PutAccessPointPublicAccessBlock"
- "ecr:BatchGetImage"
- "ecr:DescribeImages"
- "s3:CreateJob"
- "s3:GetAccessPoint"
- "states:ListExecutions"
- "ecr:DescribeImageReplicationStatus"
- "ecr:ListTagsForResource"
- "ecr:ListImages"
- "ecr:GetRegistryScanningConfiguration"
- "sagemaker:ListModelPackageGroups"
- "ecr:DescribeRepositories"
- "ecr:GetRegistryPolicy"
- "s3:ListAccessPoints"
- "ecr:GetLifecyclePolicyPreview"
- "sagemaker:UpdateModelPackage"
- "ecr:DescribeRegistry"
- "s3:ListMultiRegionAccessPoints"
- "logs:PutLogEvents"
- "s3:ListStorageLensConfigurations"
- "states:DescribeStateMachineForExecution"
- "s3:GetAccountPublicAccessBlock"
- "s3:ListAllMyBuckets"
- "states:StartExecution"
- "states:ListTagsForResource"
- "ecr:GetRepositoryPolicy"
Resource:
- "*"
- Effect: "Allow"
Action:
- "s3:*"
Resource:
- "arn:aws:s3:::*belt*"
- Effect: "Allow"
Action:
- "s3:*"
Resource:
- "arn:aws:s3:::*belt*/*"

Role das demais funções Lambda com acessos ao S3, Sagemaker, Step Functions, ECR, Cloudwatch para listagens em todos os recursos e manipulações em recursos com a palavra-chave “belt” no nome. Novamente, recomendamos a restrição nessa etapa de acordo com a necessidade do projeto e conta de implantação.

  SageMakerExecutionRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "sagemaker.amazonaws.com"
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: "SageMakerExecutionRole"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "sagemaker:CreatePresignedDomainUrl"
- "sagemaker:DescribeDomain"
- "sagemaker:ListDomains"
- "sagemaker:DescribeUserProfile"
- "sagemaker:ListUserProfiles"
- "sagemaker:*App"
- "sagemaker:ListApps"
- "sagemaker:CreateHyperParameterTuningJob"
- "sagemaker:DescribeHyperParameterTuningJob"
- "sagemaker:StopHyperParameterTuningJob"
- "sagemaker:ListTags"
- "sagemaker:CreateModel"
- "sagemaker:CreateTransformJob"
Resource:
- "*"
- Effect: "Allow"
Action:
- "sagemaker:*"
Resource:
- "arn:aws:sagemaker:*:*:flow-definition/*"
- Effect: "Allow"
Action:
- "logs:CreateLogDelivery"
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:DeleteLogDelivery"
- "logs:Describe*"
- "logs:GetLogDelivery"
- "logs:GetLogEvents"
- "logs:ListLogDeliveries"
- "logs:PutLogEvents"
- "logs:PutResourcePolicy"
- "logs:UpdateLogDelivery"
Resource:
- "*"
- Effect: "Allow"
Action:
- "s3:PutObject"
- "s3:DeleteObject"
- "s3:AbortMultipartUpload"
- "s3:GetObject"
- "s3:GetBucketLocation"
- "s3:ListBucket"
- "s3:GetBucketAcl"
- "s3:ListAllMyBuckets"
Resource:
- "arn:aws:s3:::*belt*"
- Effect: "Allow"
Action:
- "iam:PassRole"
Resource:
- "arn:aws:iam::*:role/*sage*"

Role de execução do Sagemaker, contendo acesso ao Sagemaker e CloudWatch, além de acessos restritos ao S3 e a permissão de passar a role para outro recurso do sagemaker.

  SFNOrchestratorRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "states.amazonaws.com"
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: "SFNOrchestratorRole"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "states:*"
- "logs:CreateLogStream"
- "cloudwatch:PutMetricData"
- "logs:DescribeLogStreams"
- "ecr:GetDownloadUrlForLayer"
- "sagemaker:*"
- "ecr:BatchGetImage"
- "ecr:GetAuthorizationToken"
- "s3:ListBucket"
- "logs:CreateLogGroup"
- "logs:PutLogEvents"
- "ecr:BatchCheckLayerAvailability"
- "events:PutTargets"
- "events:PutRule"
- "events:DescribeRule"
- "lambda:InvokeFunction"
Resource:
- "*"
- Effect: "Allow"
Action:
- "s3:PutObject"
- "s3:GetObject"
Resource:
- "arn:aws:s3:::*belt*"
- Effect: "Allow"
Action:
- "iam:PassRole"
Resource:
- "*"

Role do orquestrador do sistema no Step Functions, com acesso ao Step Functions, CloudWatch, Sagemaker, ECR, EventBridge e Lambda, além de acesso restrito ao S3 e permissão de passar a role.

Buildspec

O arquivo buildspec.yml, encontrado na raiz do projeto é responsável pelas especificações do CodeBuild.

version: 0.2

phases:
install:
runtime-versions:
python: 3.7

pre_build:
commands:
- bash codebuild/template-validation.sh

build:
commands:
- bash codebuild/build.sh

artifacts:
files:
- cloudformation/serverless.yaml
- cloudformation/configuration.json

Definindo assim as fases:

  • Install: instalação do Python 3.7 no ambiente
  • Pre-build: execução do script de validação do template do CloudFormation
  • Build: execução do script de construção do CloudFormation

Além dos artefatos do projeto, sendo eles o código SAM a ser referenciado e seu arquivo de configurações.

Code Build

Este diretório do projeto conta com 2 arquivos, sendo eles:

#!/bin/bash
set -e # exit when any command fails

echo "##### PACKAGING SAM TEMPLATE"
if ! sam package --s3-bucket "$ARTIFACT_BUCKET" -t "./cloudformation/serverless.yaml" \
--output-template-file "./cloudformation/serverless.yaml"
then
echo | cat tmp
rm tmp
exit 1
fi

echo "##### REPLACING VALUES IN PARAMETERS CONFIGURATIONS"
sed -i -e "s/STAGE_PLACEHOLDER/$STAGE/" cloudformation/configuration.json
sed -i -e "s+SECURITY_GROUP_ID_PLACEHOLDER+$SECURITY_GROUP_ID+" cloudformation/configuration.json
sed -i -e "s+SUBNET_IDS_PLACEHOLDER+$SUBNET_IDS+" cloudformation/configuration.json
sed -i -e "s+VPC_ENDPOINT_API_ID_PLACEHOLDER+$VPC_ENDPOINT_API_ID+" cloudformation/configuration.json
sed -i -e "s+ROLES_LIST_PLACEHOLDER+$ROLES_LIST+" cloudformation/configuration.json

O build.sh que é um script bash responsável por realizar a chamada de CLI do CloudFormation para o SAM (sam package) que empacota o código em “CloudFormation/serverless.yaml” e o salva no S3. Esse script também realiza a substituição de placeholders no arquivo de configurações do SAM (CloudFormation/configuration.json) de acordo com as variáveis de ambiente construídas no deploy-pipeline.yaml.

#!/bin/bash

echo "##### VALIDATING CLOUDFORMATION TEMPLATES"

while IFS= read -r -d '' cfn_template
do
echo "### Validating CloudFormation template file $cfn_template"
if ! aws cloudformation validate-template --template-body "file://$cfn_template" > tmp
then
echo | cat tmp
rm tmp
exit 1
fi
done < <(find ./cloudformation -type f -regex ".*\.\(yaml\|yml\)$" -print0)

echo "##### VALIDATING CLOUDFORMATION PARAMETERS CONFIGURATIONS"

while IFS= read -r -d '' conf
do
echo "### Validating CFN parameters config file $conf"
if ! python3 -m json.tool < "$conf" > tmp
then
echo | cat tmp
rm tmp
exit 1
fi
done < <(find ./cloudformation -type f -regex ".*\.json$" -print0)

rm tmp

O template-validation.sh é um script bash responsável por validar o template SAM CloudFormation via AWS CLI e o arquivo de configurações JSON com a ferramenta de validação em Python.

Implantação

Para realizar a implantação, basta seguir os passos descritos nessa seção.

Acesso ao CloudFormation

Acesse o console AWS e busque pelo serviço CloudFormation na barra de busca:

No console, clique na opção “Stacks” ou “Pilhas” na barra lateral da esquerda até chegar a seguinte visualização:

Criação da Stack do Pipeline

No console do CloudFormation, clique no botão “create stack”, que o vai direcionar para a seguinte tela:

Nessa tela, selecione as opções “Template is ready” e “Upload a template file”, em seguida selecione o arquivo “deploy-pipeline.yaml” do repositório local do projeto. Sua tela deve ficar da seguinte forma:

Nesse momento, será necessário definir as seguintes informações:

  • StackName: nome da stack que será criada, recomendamos um nome específico e descritivo, como “cicd-pipeline-aprendizado-automatizado”.
  • ApplicationName: uma base dos nomes para a sua aplicação. Esse nome será utilizado nas nomenclaturas de recursos AWS.
  • SecutiryGourpID: identificador do Security Group da VPC que será utilizada para encapsular a aplicação.
  • SubnetIDs: lista de IDs das subnets da VPC que serão utilizadas para encapsular a aplicação.
  • VPCEndpointAPIID: ID do endpoint do API Gateway na VPC da aplicação.

Assim, a tela preenchida deverá ficar similar a essa:

Em seguida, você poderá configurar opções da stack como tags, permissões, rollback e afins. Recomendamos manter o padrão como está e adicionar suas tags preferidas para rastreio de custos. Assim, a seguinte tela pode ser mantida com valores padrão:

Finalmente, na última tela, basta marcar a checkbox no fim da página e clicar em “Submit”, como mostrado abaixo:

Acompanhamento da Criação da Stack

Uma vez que a stack for criada, ela vai demorar alguns minutos para finalizar, onde você pode validar o status:

Na aba “Resources”, você consegue validar todos os recursos criados:

Recursos IAM

Essa stack deverá criar as seguintes roles IAM:

  • *APILambdaRole*: Para a função lambda com sua política de acesso inline;
  • *CodeBuildRole*: Para o CodeBuild com uma política inline e a AWSClouFormationReadOnlyAccess;
  • *CodePipelineRole*: Para o CodePipeline com uma política inline;
  • *LambdaRole*: Para as demais funções lambda com uma política inline;
  • *SageMakerExecutionRole*: Para os Jobs do Sagemaker com uma política inline;
  • *SFNOrchestratorRole*: Para o orquestrador do StepFuncions com uma política inline.

Recursos S3

Navegando ao S3, você conseguirá ver a criação de um bucket com a nomenclatura {ApplicationName}-{AccountId}-{region}-da, utilizado para armazenamento dos artefatos do projeto.

Recursos Code Suíte

Navegando ao CodeCommit, você conseguirá validar a criação de um repositório com a nomenclatura {ApplicationName}-repository.

Na página do CodeBuild, você conseguirá ver um build project com a nomenclatura {ApplicationName}.

Finalmente, no console do CodePipeline, você verá 3 pipelines com a nomenclatura {ApplicationName}-pipeline-{dev|hml|prd}.

Clean Up

Para deletar todos esses recursos, basta esvaziar o bucket S3 no botão “empty” como mostrado abaixo:

Em seguida, basta deletar no botão “Delete” a stack no CloudFormation:

Você poderá acompanhar os eventos de deleção no console:

Conclusão

Esse artigo apresenta um detalhamento técnico da esteira de CI/CD utilizada no sistema de aprendizado contínuo para modelo de ML. Existem pontos de evolução e melhoria dessa esteira, entretanto, ela fornece a base necessária para uma implantação rápida e utilização na AWS. Vale ressaltar que a arquitetura apresentada pode ser adaptada para a criação de outras aplicações na AWS utilizando-se da mesma fundação de CI/CD apresentada aqui.

Referências

Repositório público da arquitetura:

https://github.com/lucaslascasas5/sagemaker-auto-learning-pipeline

Aprendizado Automático de Modelos:

Em breve: Detalhamento do código de aprendizado automático de modelos…

--

--

Lucas Lascasas

Master in Computer Science | Manager at BRLink/INGRAM | 4x AWS Certified | AI/ML Black Belt