我们的CloudFormation模板存储在GitHub中.在CodePipeline内部,我们使用GitHub作为源代码,但是当它们没有存储在S3上时,我们无法引用嵌套的CloudFormation Stacks.
当我们在CodePipeline中使用GitHub作为源代码时,我们如何引用CloudFormation嵌套堆栈?
如果无法做到这一点,我们如何在源阶段(来自GitHub)和CodePipeline中的Deploy Stage之间将CloudFormation模板从GitHub上传到S3?
我可以想到两种方法来引用来自GitHub源的嵌套CloudFormation堆栈以进行CodePipeline部署:
添加在模板上运行的pre-commit
客户端Git挂钩,将aws cloudformation package
生成的模板与对Rit存储库的S3引用一起提交到源模板的更改.
这种方法的好处是您可以利用现有的模板重写逻辑aws cloudformation package
,而不必修改现有的CodePipeline配置.
添加基于Lambda的管道阶段,该阶段从GitHub源工件中提取指定的嵌套堆栈模板文件,并将其上载到父堆栈模板中引用的S3中的指定位置.
这种方法的好处是管道将保持完全独立,不需要提交者进行任何额外的预处理步骤.
我已经发布了一个完整的参考示例实现wjordan/aws-codepipeline-nested-stack
:
AWSTemplateFormatVersion: 2010-09-09 Description: Infrastructure Continuous Delivery with CodePipeline and CloudFormation, with a project containing a nested stack. Parameters: ArtifactBucket: Type: String Description: Name of existing S3 bucket for storing pipeline artifacts StackFilename: Type: String Default: cfn-template.yml Description: CloudFormation stack template filename in the Git repo GitHubOwner: Type: String Description: GitHub repository owner GitHubRepo: Type: String Default: aws-codepipeline-nested-stack Description: GitHub repository name GitHubBranch: Type: String Default: master Description: GitHub repository branch GitHubToken: Type: String Description: GitHub repository OAuth token NestedStackFilename: Type: String Description: GitHub filename (and S3 Object Key) for nested stack template. Default: nested.yml Resources: Pipeline: Type: AWS::CodePipeline::Pipeline Properties: RoleArn: !GetAtt [PipelineRole, Arn] ArtifactStore: Type: S3 Location: !Ref ArtifactBucket Stages: - Name: Source Actions: - Name: Source ActionTypeId: Category: Source Owner: ThirdParty Version: 1 Provider: GitHub Configuration: Owner: !Ref GitHubOwner Repo: !Ref GitHubRepo Branch: !Ref GitHubBranch OAuthToken: !Ref GitHubToken OutputArtifacts: [Name: Template] RunOrder: 1 - Name: Deploy Actions: - Name: S3Upload ActionTypeId: Category: Invoke Owner: AWS Provider: Lambda Version: 1 InputArtifacts: [Name: Template] Configuration: FunctionName: !Ref S3UploadObject UserParameters: !Ref NestedStackFilename RunOrder: 1 - Name: Deploy RunOrder: 2 ActionTypeId: Category: Deploy Owner: AWS Version: 1 Provider: CloudFormation InputArtifacts: [Name: Template] Configuration: ActionMode: REPLACE_ON_FAILURE RoleArn: !GetAtt [CFNRole, Arn] StackName: !Ref AWS::StackName TemplatePath: !Sub "Template::${StackFilename}" Capabilities: CAPABILITY_IAM ParameterOverrides: !Sub | { "ArtifactBucket": "${ArtifactBucket}", "StackFilename": "${StackFilename}", "GitHubOwner": "${GitHubOwner}", "GitHubRepo": "${GitHubRepo}", "GitHubBranch": "${GitHubBranch}", "GitHubToken": "${GitHubToken}", "NestedStackFilename": "${NestedStackFilename}" } CFNRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: ['sts:AssumeRole'] Effect: Allow Principal: {Service: [cloudformation.amazonaws.com]} Version: '2012-10-17' Path: / ManagedPolicyArns: # TODO grant least privilege to only allow managing your CloudFormation stack resources - "arn:aws:iam::aws:policy/AdministratorAccess" PipelineRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: ['sts:AssumeRole'] Effect: Allow Principal: {Service: [codepipeline.amazonaws.com]} Version: '2012-10-17' Path: / Policies: - PolicyName: CodePipelineAccess PolicyDocument: Version: '2012-10-17' Statement: - Action: - 's3:*' - 'cloudformation:*' - 'iam:PassRole' - 'lambda:*' Effect: Allow Resource: '*' Dummy: Type: AWS::CloudFormation::WaitConditionHandle NestedStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: !Sub "https://s3.amazonaws.com/${ArtifactBucket}/${NestedStackFilename}" S3UploadObject: Type: AWS::Lambda::Function Properties: Description: Extracts and uploads the specified InputArtifact file to S3. Handler: index.handler Role: !GetAtt LambdaExecutionRole.Arn Code: ZipFile: !Sub | var exec = require('child_process').exec; var AWS = require('aws-sdk'); var codePipeline = new AWS.CodePipeline(); exports.handler = function(event, context, callback) { var job = event["CodePipeline.job"]; var s3Download = new AWS.S3({ credentials: job.data.artifactCredentials, signatureVersion: 'v4' }); var s3Upload = new AWS.S3({ signatureVersion: 'v4' }); var jobId = job.id; function respond(e) { var params = {jobId: jobId}; if (e) { params['failureDetails'] = { message: JSON.stringify(e), type: 'JobFailed', externalExecutionId: context.invokeid }; codePipeline.putJobFailureResult(params, (err, data) => callback(e)); } else { codePipeline.putJobSuccessResult(params, (err, data) => callback(e)); } } var filename = job.data.actionConfiguration.configuration.UserParameters; var location = job.data.inputArtifacts[0].location.s3Location; var bucket = location.bucketName; var key = location.objectKey; var tmpFile = '/tmp/file.zip'; s3Download.getObject({Bucket: bucket, Key: key}) .createReadStream() .pipe(require('fs').createWriteStream(tmpFile)) .on('finish', ()=>{ exec(`unzip -p ${!tmpFile} ${!filename}`, (err, stdout)=>{ if (err) { respond(err); } s3Upload.putObject({Bucket: bucket, Key: filename, Body: stdout}, (err, data) => respond(err)); }); }); }; Timeout: 30 Runtime: nodejs4.3 LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: {Service: [lambda.amazonaws.com]} Action: ['sts:AssumeRole'] Path: / ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - "arn:aws:iam::aws:policy/AWSCodePipelineCustomActionAccess" Policies: - PolicyName: S3Policy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 's3:PutObject' - 's3:PutObjectAcl' Resource: !Sub "arn:aws:s3:::${ArtifactBucket}/${NestedStackFilename}"
除了具有lambda阶段的解决方案之外,一种简单的方法是使用CodeBuild和AWS SAM.
在主CloudFormation模板中(我们称之为main.yaml),使用'Transform:AWS :: Serverless-2016-10-31'
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Resources: NestedTemplate: AWS::CloudFormation::Stack Properties: TemplateUri: ./nested-template.yaml
请注意,您只需要将相对路径放到子模板而不是绝对s3 uri.
使用以下buildspecification.yaml添加CodeBuild阶段
version: 0.1 phases: build: commands: aws cloudformation package --template-file main.yaml --output-template-file transformed_main.yaml --s3-bucket my_bucket artifacts: type: zip files: - transformed_main.yaml
构建命令'aws cloudformation package'会将nested-template.yaml上传到s3存储桶'my_bucket',并将绝对s3 uri注入转换后的模板.
在CloudFormation部署阶段,使用"创建更改集"和"执行更改集"来创建堆栈.请注意,"创建或更新堆栈"不适用于"转换:AWS :: Serverless-2016-10-31".
以下是您可能会发现有用的文档:
http://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html
http://docs.aws.amazon.com/lambda/latest/dg/automating-deployment.html
第二个文档展示了如何部署lambda函数,但引用嵌套堆栈本质上是相同的.