Введение
В этом руководстве мы собираемся развернуть функцию AWS Lambda с помощью CodePipeline и AWS SAM, чтобы постоянно интегрировать и развертывать изменения нашего кода в облаке, но вместо использования консоли управления AWS мы напишем в CloudFormation шаблон, благодаря чему вы можете легко подготовить инфраструктуру для каждой среды разработки, а также внедрить ее в каждый проект на основе AWS SAM.
Архитектура

Как мы можем видеть на диаграмме выше, мы собираемся написать 2 шаблона кода, первый будет шаблоном CloudFormation и будет отвечать за базовую инфраструктуру, которая в данном случае в основном является конвейером, второй будет шаблон AWS SAM, который будет шаблоном для развертывания нашей сервисной инфраструктуры.
Руки вверх!
Во-первых, давайте создадим пару файлов и каталогов, которые будут находиться в одном месте:

Начнем с написания кода нашей функции Lambda в файле main.js в папке /src:
const AWS = require('aws-sdk')
let dynamodb = new AWS.DynamoDB.DocumentClient({ region: 'us-east-1'})
exports.handler = (event, context, callback) => {
let params = {
TableName: process.env.ddbTable,
Item: {
pk: "[email protected]",
sk: "user",
first_name: "Marco",
last_name: "Mercado"
},
ConditionExpression: "attribute_not_exists(pk)",
ReturnConsumedCapacity: 'TOTAL'
}
dynamodb.put(params, (err, data) => {
if (err) callback(err)
else callback(null, data)
})
}
Функция довольно проста, мы выполняем операцию помещения в таблицу DynamoDB с помощью AWS SDK для NodeJS. Единственное, что я хочу, чтобы вы заметили, это то, что в строке 7 мы вызываем переменную среды для таблицы DynamoDB, эта переменная будет определена в шаблоне SAM.
ПРИМЕЧАНИЕ. В этом примере файл package.json используется по умолчанию. Его можно создать, просто запустив npm init -y.
Теперь давайте создадим файл template.yml, в котором мы будем использовать AWS SAM для развертывания наших бессерверных ресурсов:
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
AWS SAM Lambda function example
Parameters:
Environment:
Type: String
Description: Environment name
Globals:
Function:
Timeout: 3
Resources:
DdbTable:
Type: AWS::DynamoDB::Table
DeletionPolicy: Retain
Properties:
TableName: !Sub tutorial-dynamodb-cicd-${Environment}
AttributeDefinitions:
- AttributeName: pk
AttributeType: S
- AttributeName: sk
AttributeType: S
KeySchema:
- AttributeName: pk
KeyType: HASH
- AttributeName: sk
KeyType: RANGE
BillingMode: PAY_PER_REQUEST
ProvisionedThroughput:
ReadCapacityUnits: 0
WriteCapacityUnits: 0
MainFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub sam-function-example-${Environment}
CodeUri: src/
Handler: main.handler
Runtime: nodejs12.x
Environment:
Variables:
ddbTable: !Ref DdbTable
Policies:
- Statement:
- Sid: DynamoDBPolicy
Effect: Allow
Action:
- dynamodb:PutItem
Resource:
- !GetAtt DdbTable.Arn
Outputs:
MainFunction:
Description: "Main Lambda Function ARN"
Value: !GetAtt MainFunction.Arn
Шаблон будет развертывать таблицу DynamoDB со средой в качестве суффикса для разделения ресурсов по средам и функцию Lambda с ролью IAM, которая позволяет записывать в таблицу DynamoDB.
Хороший! на данный момент мы можем развернуть нашу функцию с помощью интерфейса командной строки AWS SAM, но это будет ручной шаг, мы хотим, чтобы каждый раз, когда разработчик помещает код в репозиторий, эти изменения автоматически создавались и развертывались.
Служба, в которой будет работать шаблон AWS SAM, называется CodeBuild. Зная, что нам нужно написать пошаговые инструкции по сборке и развертыванию нашего проекта с помощью AWS SAM, эти инструкции будут записаны в файле buildspec.yml в разделе папку .awscodepipeline, как следующий код:
version: 0.2
phases:
build:
commands:
- sam build -t template.yml
post_build:
commands:
- |
sam deploy --no-confirm-changeset --no-fail-on-empty-changeset \
--s3-bucket $CODEPIPELINE_BUCKET \
--s3-prefix $STACK_NAME \
--region $AWS_REGION \
--stack-name $STACK_NAME \
--capabilities CAPABILITY_IAM \
--parameter-overrides \
Environment=$ENVIRONMENT
Теперь у нас есть компоненты для настройки нашего конвейера автоматизации, последний шаг — написать наш шаблон CloudFormation, расположенный в файле main.yml в каталоге .cloudformation, это самая длинная часть примера, поэтому я собираюсь ее разбить. по шагам, если вы хотите посмотреть весь файл, перейдите по ссылке на репозиторий GitHub в конце этого поста.
Сначала, как и для каждого шаблона CloudFormation, мы определяем версию и пишем краткое описание шаблона:
AWSTemplateFormatVersion: '2010-09-09' Description: 'CloudFormation template to create an automation pipeline for AWS SAM projects'
Далее определяем наши параметры:
Parameters:
FunctionName:
Type: String
Description: Name of the function to deploy with the pipeline
Environment:
Type: String
Description: Environment name
GitHubRepo:
Type: String
Description: GitHub repository name
GitHubUser:
Type: String
Description: GitHub user name
GitHubOAuthToken:
Type: String
Description: GitHub auth token
Resources:
Теперь мы собираемся написать, какие ресурсы будет создавать этот шаблон. Прежде всего, мы собираемся определить роли IAM, которые будут принимать CodePipeline и CodeBuild, чтобы иметь необходимые разрешения:
ПРИМЕЧАНИЕ. На этом этапе каждый блок кода будет иметь отступ в разделе Ресурсы.
Для кодбилда:
CodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: "SAM"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:CreateTable
- dynamodb:DeleteTable
Resource: '*'
- Effect: Allow
Action:
- cloudformation:CreateChangeSet
- cloudformation:DescribeChangeSet
- cloudformation:ExecuteChangeSet
- cloudformation:DescribeStackEvents
- cloudformation:DescribeStacks
- cloudformation:GetTemplateSummary
- cloudformation:UpdateStack
Resource: '*'
- Effect: Allow
Action:
- lambda:AddPermission
- lambda:CreateFunction
- lambda:DeleteFunction
- lambda:GetFunction
- lambda:GetFunctionConfiguration
- lambda:ListTags
- lambda:RemovePermission
- lambda:TagResource
- lambda:UntagResource
- lambda:UpdateFunctionCode
- lambda:UpdateFunctionConfiguration
Resource: arn:aws:lambda:*:*:function:*
- Effect: Allow
Action:
- iam:PassRole
Resource: "*"
Condition:
StringEquals:
iam:PassedToService: lambda.amazonaws.com
- Effect: Allow
Action:
- iam:CreateRole
- iam:DetachRolePolicy
- iam:PutRolePolicy
- iam:AttachRolePolicy
- iam:DeleteRole
- iam:GetRole
Resource: "arn:aws:iam::*:role/*"
- PolicyName: "logs"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- ssm:GetParameters
Resource: '*'
- PolicyName: "S3"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:GetObjectVersion
Resource: !Sub arn:aws:s3:::${ArtifactBucket}/*
Для CodePipeline:
CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Resource:
- !Sub arn:aws:s3:::${ArtifactBucket}/*
- !Sub arn:aws:s3:::${ArtifactBucket}
Effect: Allow
Action:
- s3:*
- Resource: "*"
Effect: Allow
Action:
- codebuild:StartBuild
- codebuild:BatchGetBuilds
- iam:PassRole
- Resource: "*"
Effect: Allow
Action:
- codecommit:CancelUplodaArchive
- codecommit:GetBranch
- codecommit:GetCommit
- codecommit:GetUploadArchiveStatus
- codecommit:UploadArchive
Далее мы собираемся создать корзину S3 для хранения наших артефактов:
ArtifactBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Delete
Теперь нам нужен проект CodeBuild, помните, что CodeBuild — это вычислительный экземпляр, управляемый , который будет выполнять список шагов для сборки вашего проекта, поэтому нам нужно настроить этот экземпляр:
CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Artifacts:
Type: CODEPIPELINE
Source:
Type: CODEPIPELINE
BuildSpec: .awscodepipeline/buildspec.yml
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/amazonlinux2-x86_64-standard:2.0
Type: LINUX_CONTAINER
EnvironmentVariables:
- Name: CODEPIPELINE_BUCKET
Type: PLAINTEXT
Value: !Ref ArtifactBucket
- Name: STACK_NAME
Type: PLAINTEXT
Value: !Sub ${FunctionName}-${Environment}
- Name: ENVIRONMENT
Type: PLAINTEXT
Value: !Ref Environment
Name: !Ref AWS::StackName
ServiceRole: !Ref CodeBuildServiceRole
Наконец, мы собираем все вместе в конвейер CodePipeline, помните, что CodePipeline — это служба оркестровки, в этом примере координирующая репозиторий GitHub как источник нашего кода с нашим проектом CodeBuild, который собирается развернуть наши бессерверные ресурсы.
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: !Sub ${FunctionName}-${Environment}
RoleArn: !GetAtt CodePipelineServiceRole.Arn
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucket
Stages:
- Name: Source
Actions:
- Name: Function
ActionTypeId:
Category: Source
Owner: ThirdParty
Version: '1'
Provider: GitHub
Configuration:
Owner: !Ref GitHubUser
Repo: !Ref GitHubRepo
Branch: !Ref Environment
OAuthToken: !Ref GitHubOAuthToken
RunOrder: 1
OutputArtifacts:
- Name: SourceArtifact
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Version: '1'
Provider: CodeBuild
Configuration:
ProjectName: !Ref CodeBuildProject
InputArtifacts:
- Name: SourceArtifact
OutputArtifacts:
- Name: BuildArtifact
RunOrder: 2
Прежде чем развертывать проект, убедитесь, что ваш код обновлен в репозитории GitHub в ветке, которая соответствует имени среды, которую вы собираетесь выбрать (т. е. staging).

Теперь мы можем развернуть наш проект с помощью следующей команды с помощью интерфейса командной строки AWS:
ПРИМЕЧАНИЕ. Важно, чтобы на этом этапе у вас был ранее установлен интерфейс командной строки AWS и токен доступа GitHub OAuth, если вы не знаете, как сгенерировать свой токен или как установить интерфейс командной строки AWS. оставлю вам пару полезных ссылок.
Создать токен доступа GitHub OAuth
aws cloudformation create-stack \ --stack-name lambda-cicd-post \ --template-body file://.cloudformation/main.yml \ --parameters \ ParameterKey=FunctionName,ParameterValue=<LAMBDA_FUNCTION_NAME> \ ParameterKey=Environment,ParameterValue=<ENVIRONMENT> \ ParameterKey=GitHubUser,ParameterValue=<GITHUB_USER> \ ParameterKey=GitHubRepo,ParameterValue=<REPO_NAME> \ ParameterKey=GitHubOAuthToken,ParameterValue=<GITHUB_OAUTH_TOKEN> \ --region us-east-1 \ --capabilities CAPABILITY_IAM
Должно произойти следующее:
Стек должен успешно завершиться в CloudFormation:

Следует создать конвейер CodePipeline, который немедленно запускает свой первый запуск:

Этот первый запуск должен создать следующий стек:

И теперь вы сможете проверить свою функцию:



И это все для этого урока, помните, что весь код будет здесь, увидимся в следующий раз!