AWS Lambda笔记-自动部署-5

本章使用CloudFormation来实现AWS Lambda函数的自动部署。主要包括:

  1. 编译打包
  2. 创建S3存储,打包程序文件上传
  3. 创建函数

工程目录结构

工程目录结构

准备3个java文件

代码同 Serverless-HelloWorld-3可以直接copy即可。

2. 准备Gradle的2个文件

setting.build 文件:gradle指定子目录项目

include 'lambda-test'

build.gradle 文件

task wrapper(type: Wrapper) {
    gradleVersion = '3.0'
}

// 全工程配置,子工程也生效
allprojects {
    // 产品ID
    group 'com.serverlessbook'
    // 项目的版本号
    version '1.0'
    // 添加Gradle java插件
    apply plugin: 'java'
    // java 1.8 
    sourceCompatibility = 1.8
}

// 添加 maven库,jCenter,jitpack
allprojects {
    repositories {
        mavenCentral()
        jcenter()
        maven {
            url "https://jitpack.io"
        }
    }
}

buildscript {
    repositories {
        mavenCentral()
        jcenter()
        maven {
            url "https://jitpack.io"
        }
    }

    //添加各种依赖
    dependencies {
        classpath "com.github.jengelman.gradle.plugins:shadow:1.2.3"
        //classmethod公司的AWS插件,在构建脚本里调用AWS的API
        classpath "jp.classmethod.aws:gradle-aws-plugin:0.37"
    }
}

//制定插件应用于所有子项目。所有的子项目部署到区中
//${aws.region} 取的是 ~/.aws/config 中的region
allprojects {
    apply plugin: "jp.classmethod.aws"
    aws {
        region = "${aws.region}"
    }
}

// 定义Bucket的名字,传递给cloudFormation.template 文件
def bucketRegionName = "${aws.region}"
//定义bucket的名词变量,aws要求所有的bucket名称唯一性
def deploymentBucketName = "serverless-book-${aws.region}"
//定义一个时间变量
def deploymentTime = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(new Date());

//创建一个bucket,如果存在就不创建
//可以运行 ./gradlew createDeploymentBucket 测试下, 运行 aws s3 ls 查看创建的bucket
allprojects {
    apply plugin: "jp.classmethod.aws.s3"
    task createDeploymentBucket(type: jp.classmethod.aws.gradle.s3.CreateBucketTask) {
        bucketName deploymentBucketName
        region bucketRegionName
        ifNotExists true
    }
}

//lambda-开头的子项目,编译lambda工程
configure(subprojects.findAll { it.name.startsWith("lambda-") }) {
    dependencies {
        compile 'com.amazonaws:aws-lambda-java-core:1.1.0'
        compile 'com.fasterxml.jackson.core:jackson-databind:2.6.+'
    }
    //添加插件shadow,编译成fat-jar
    apply plugin: "com.github.johnrengelman.shadow"
    build.finalizedBy shadowJar

    //获取 shadowJar 归档的路径
    def producedJarFilePath = it.tasks.shadowJar.archivePath
    //根据编译的时间命名jar,每次都是一个新jar,要拿过来抱着后续lambda调用的都是最新的jar
    //it 代表闭包的参数,version在最上面已定义为1.0
    def s3Key = "artifacts/${it.name}/${it.version}/${deploymentTime}.jar"

    //上传jar到s3,有两个依赖,build ,createDeploymentBucket 两个task
    //测试:./gradlew uploadArtifactsToS3
    //查看:aws s3 ls s3://serverless-book-us-east-2/artifacts/lambda-test/1.0/
    task uploadArtifactsToS3(type: jp.classmethod.aws.gradle.s3.AmazonS3FileUploadTask,
            dependsOn: [build, createDeploymentBucket]) {
        bucketName deploymentBucketName
        file producedJarFilePath
        key s3Key
    }
}

// 添加cloudformation插件
apply plugin: "jp.classmethod.aws.cloudformation"

cloudFormation {
    //创建IAM资源,由于我们需要创建IAM角色和IAM策略,所以同时必须创建IAM资源,否则cloudformation无法执行。
    capabilityIam true
    //cloudformation 模版文件的位置
    templateFile project.file('cloudformation.template')
    //上传模版文件的bucket名称使用定义的名称:deploymentBucketName = "serverless-book1-${aws.region}" 
    templateBucket deploymentBucketName
    //S3桶名称前缀,可以任意指定,
    templateKeyPrefix "cfn-templates"
    //cloudformation伐的名称
    stackName "serverlessbook"
    //gradle 和 cloudformation模版传参赋值,cloudformation的三个参数
    conventionMapping.stackParams = {
        return [
                DeploymentBucket: deploymentBucketName,
                ProjectVersion  : project.version,
                DeploymentTime  : deploymentTime
        ]
    }
}

//awsCfnMigrateStackAndWaitCompleted:创建或更新CloudFormation伐兵等待创建完成
//awsCfnMigrateStack:同上唯一区别就是不等待操作完成。
//awsCfnUploadTemplate:将本地cloudformation模版上传到S3云存储桶。
awsCfnMigrateStack.dependsOn awsCfnUploadTemplate

//构建和上传程序文件,部署cloudformation模版
task deploy {
    configure(subprojects.findAll { it.name.startsWith("lambda-") }) {
        //依赖 uploadArtifactsToS3 任务(编译打包,创建bucket,上传程序到bucket)
        dependsOn it.uploadArtifactsToS3
    }
    //部署的最后一个操作
    finalizedBy awsCfnMigrateStackAndWaitCompleted
}

cloudformation.template 文件说明

  1. 定义IAM角色,让Lambda函数执行期间的身份,例如函数访问S3资源,是否可读写等权限。
{
    "Resources": {
    "LambdaExecutionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "Path": "/",
        "AssumeRolePolicyDocument": { 
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
        ]
      }
    }
}

属性说明:
AssumeRolePolicyDocument:与此角色关联的信任策略。信任策略定义哪些实体可以代入该角色。只能将一个信任策略与一个角色关联。这里表示关联的服务由lambda函数来执行。
ManagedPolicyArns:附加到用户的 IAM 托管策略的 Amazon 资源名称 (ARN) 的列表。其中内建IAM策略 AWSLambdaVPCAccessExecutionRole,赋予lambda函数在vpc环境中运行以及在cloudWatch中记录日志的权限。几乎所有的函数都需要这两个权限。
在Gradle里deploy的task运行完成后,在IAM控制台可以看到类似 serverlessbook-LambdaExecutionRole-WMME8CT809HQ的角色。角色里面有策略“LambdaCustomPolicy”,策略里面有S3,S3里面有个ListBuckets操作权限。

  1. 创建自定义IAM策略,并给上面创建的IAM角色(LambdaExecutionRole)
"LambdaCustomPolicy": {
  "Type": "AWS::IAM::Policy",
  "Properties": {
    "PolicyName": "LambdaCustomPolicy",
    "PolicyDocument": {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "s3:ListBuckets"
          ],
          "Resource": "*"
        }
      ]
    },
    "Roles": [
      {
        "Ref": "LambdaExecutionRole"
      }
    ]
  }
}

该策略添加一个S3 listBuckets 权限,他使 Lambda函数有获取S3bucket list 的权限。
Roles:关联内建角色时使用{“Ref”:"RESOURCE_NAME"}

  1. cloudformation 参数
"Parameters": {
    "DeploymentBucket": {
      "Type": "String",
      "Description": "S3 bucket name where built artifacts are deployed"
    },
    "ProjectVersion": {
      "Type": "String",
      "Description": "Project Version"
    },
    "DeploymentTime": {
      "Type": "String",
      "Description": "It is a timestamp value which shows the deployment time. Used to rotate sources."
    }
}

定义了三个参数,DeploymentBucket,ProjectVersion,DeploymentTime,这三个参数会在哪儿赋值呢?
这三个参数的值是从 build.gradle传递的参数,对应文件如下:

//gradle 和 cloudformation模版传参赋值,cloudformation的三个参数
    conventionMapping.stackParams = {
        return [
                DeploymentBucket: deploymentBucketName,
                ProjectVersion  : project.version,
                DeploymentTime  : deploymentTime
        ]
    }
  1. 创建Lambda函数
"TestLambda": {
  "Type": "AWS::Lambda::Function",
  "Properties": {
    "Handler": "com.serverlessbook.lambda.test.Handler",
    "Runtime": "java8",
    "Timeout": "300",
    "MemorySize": "128",
    "Description": "Test lambda",
    "Role": {
      "Fn::GetAtt": [
        "LambdaExecutionRole",
        "Arn"
      ]
    },
    "Code": {
      "S3Bucket": {
        "Ref": "DeploymentBucket"
      },
      "S3Key": {
        "Fn::Sub": "artifacts/lambda-test/${ProjectVersion}/${DeploymentTime}.jar"
      }
    }
  }
}

Handler:函数的入口方法,注意必须基础LambdaHandle<I,O>
Role:运行Lambda函数的IAM角色,这边引用了LambdaExecutionRole角色,并返回对应的ARN,类似在执行lambda函数创建aws lambda create-function --role arn:aws:iam::083845341320:role/lambada-execution-role
Code:Jar包在S3中和对应的路径(S3Key)

完整的 cloudformation.template 文件
{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Parameters": {
        "DeploymentBucket": {
          "Type": "String",
          "Description": "S3 bucket name where built artifacts are deployed"
        },
        "ProjectVersion": {
          "Type": "String",
          "Description": "Project Version"
        },
        "DeploymentTime": {
          "Type": "String",
          "Description": "It is a timestamp value which shows the deployment time. Used to rotate sources."
        }
    },
    "Resources": {
        "LambdaExecutionRole": {
          "Type": "AWS::IAM::Role",
          "Properties": {
            "Path": "/",
            "AssumeRolePolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Principal": {
                    "Service": [
                      "lambda.amazonaws.com"
                    ]
                  },
                  "Action": [
                    "sts:AssumeRole"
                  ]
                }
              ]
            },
            "ManagedPolicyArns": [
              "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
            ]
          }
        },
        "LambdaCustomPolicy": {
          "Type": "AWS::IAM::Policy",
          "Properties": {
            "PolicyName": "LambdaCustomPolicy",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "s3:ListBuckets"
                  ],
                  "Resource": "*"
                }
              ]
            },
            "Roles": [
              {
                "Ref": "LambdaExecutionRole"
              }
            ]
          }
        },
        "TestLambda": {
          "Type": "AWS::Lambda::Function",
          "Properties": {
            "Handler": "com.serverlessbook.lambda.test.Handler",
            "Runtime": "java8",
            "Timeout": "300",
            "MemorySize": "1024",
            "Description": "Test lambda",
            "Role": {
              "Fn::GetAtt": [
                "LambdaExecutionRole",
                "Arn"
              ]
            },
            "Code": {
              "S3Bucket": {
                "Ref": "DeploymentBucket"
              },
              "S3Key": {
                "Fn::Sub": "artifacts/lambda-test/${ProjectVersion}/${DeploymentTime}.jar"
              }
            }
          }
        }
    }
}

上面的代码转换成三者的关系,通过CloudFormation设计器可以很明显看出。


Role,Policy,Fuction 关系图

一键部署Lambda函数(完成所有工作最爽的一步)

./gradlew deploy
查看整个部署情况:
aws cloudformation describe-stack-resources \
--stack-name serverlessbook \
--region us-east-2

输出结果:

{
    "StackResources": [
        {
            "StackName": "serverlessbook",
            "StackId": "arn:aws:cloudformation:us-east-2:083845954160:stack/serverlessbook/79bc0a10-a573-11ea-8763-0a3e4885fce2",
            "LogicalResourceId": "LambdaCustomPolicy",
            "PhysicalResourceId": "serve-Lamb-17L0RKTV9AKCD",
            "ResourceType": "AWS::IAM::Policy",
            "Timestamp": "2020-06-03T08:24:47.772Z",
            "ResourceStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackResourceDriftStatus": "NOT_CHECKED"
            }
        },
        {
            "StackName": "serverlessbook",
            "StackId": "arn:aws:cloudformation:us-east-2:083845954160:stack/serverlessbook/79bc0a10-a573-11ea-8763-0a3e4885fce2",
            "LogicalResourceId": "LambdaExecutionRole",
            "PhysicalResourceId": "serverlessbook-LambdaExecutionRole-11Y8OL55973LC",
            "ResourceType": "AWS::IAM::Role",
            "Timestamp": "2020-06-03T08:24:31.012Z",
            "ResourceStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackResourceDriftStatus": "NOT_CHECKED"
            }
        },
        {
            "StackName": "serverlessbook",
            "StackId": "arn:aws:cloudformation:us-east-2:083845954160:stack/serverlessbook/79bc0a10-a573-11ea-8763-0a3e4885fce2",
            "LogicalResourceId": "TestLambda",
            "PhysicalResourceId": "serverlessbook-TestLambda-OYCXMPOYMQXE",
            "ResourceType": "AWS::Lambda::Function",
            "Timestamp": "2020-06-03T08:24:33.630Z",
            "ResourceStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackResourceDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}
命令行调用Lambda函数
aws lambda invoke \
--invocation-type RequestResponse \
--region us-east-2 \
--function-name serverlessbook-TestLambda-OYCXMPOYMQXE \
--payload '{"value":"test"}' \
--log-type Tail  \
/tmp/test.txt

查看输出结果:

tail -f /tmp/test.txt


异常一: 创建Bucket 名词有冲突
//当 region "us-east-21" 写错了,会提示:Status Code: 409 ,主要是区的不存在或bucket名词冲突,改下名词即可。
allprojects {
    apply plugin: "jp.classmethod.aws.s3"
    task createDeploymentBucket(type: jp.classmethod.aws.gradle.s3.CreateBucketTask) {
        bucketName "serverless-book-us-east-2"
        region "us-east-2"
        ifNotExists true
    }
}

:createDeploymentBucket FAILED
FAILURE: Build failed with an exception.

  • What went wrong:
    Execution failed for task ':createDeploymentBucket'.
    A conflicting conditional operation is currently in progress against this resource. Please try again. (Service: Amazon S3; Status Code: 409; Error Code: OperationAborted; Request ID: 57D7C696E1B13245; S3 Extended Request ID: eWOVTiGFTjeuXdd/ZPVdJDNId044kU65jSQjaMszpIRrAifFFe81Y7zjXGPiY76KuuyieLUNeJ0=)
  • Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
    BUILD FAILED
    Total time: 2.51 secs
    解决:修改“bucketName”的名词,即可解决冲突问题。
异常二: Region 指定的区域不在 aws-java-sdk-s3-xxx.jar 包中。
  • What went wrong:
    Execution failed for task ':createDeploymentBucket'.
    Cannot create enum from us-east-1 value!
  • Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
    BUILD FAILED
    Total time: 7.015 secs
    我们来看下两个不同版本之间com.amazonaws.services.s3.model.Region的区别,通过命令可以找到本地的jar:
cd ~/.gradle/caches/modules-2/files-2.1/com.amazonaws/aws-java-sdk-s3
aws-java-sdk-s3-1.11.27.jar
aws-java-sdk-s3-1.11.538.jar

那么,这些jar到底是上面Gradel的哪些配置依赖呢?
是 classpath "jp.classmethod.aws:gradle-aws-plugin:0.37" ,版本不一样依赖的aws-java-sdk-s3有区别。

异常二: Region 指定的区域为默认区域us-east-1。
  • What went wrong:
    Execution failed for task ':lambda-test:createDeploymentBucket'.
    The specified location-constraint is not valid (Service: Amazon S3; Status Code: 400; Error Code: >InvalidLocationConstraint; Request ID: A661AAB282AA92BC; S3 Extended Request ID:4GpSQ1bksHt8TcXge2WS/vAh+6weh0gtYYCcM5Nhlf0tHL/MGAPhjXOvwLzDcU7lGI31wEjH7mg=)
  • Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

注释region即可

allprojects {
    apply plugin: "jp.classmethod.aws.s3"
    task createDeploymentBucket(type: jp.classmethod.aws.gradle.s3.CreateBucketTask) {
        bucketName deploymentBucketName
        //region "us-east-1" //默认us-east-1 不需要指定
        ifNotExists true
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343