AWS CDK CI/CD 管道 - 部署的 Lambda returns ClassNotFoundException
AWS CDK CI/CD Pipeline - Deployed Lambda returns ClassNotFoundException
我正在尝试通过 AWS CDK 使用 Lambda 构建 CI/CD 管道。我们在这里使用 gradle 项目。此外,我还遵循了示例 documentation。我们定义了两个堆栈,它们是 APIStack 和 ApiStackPipeline,其中 APIStack 由 Lambda_Build
处理,ApiStackPipeline 由 CDK_BUILD
.
处理
我们正在 ApiStack 中初始化 Lambda 函数,例如;
final Function contactFunction = Function.Builder.create(this, "contactFunction").role(roleLambda)
.runtime(Runtime.JAVA_8)
.code(lambdaCode)
.handler("com.buraktas.contact.main.ContactLambda::handleRequest")
.memorySize(512)
.timeout(Duration.minutes(1))
.environment(environment)
.description(Instant.now().toString()).build();
在这种情况下,我们将 lambdaCode
参数设置为 this.lambdaCode = new CfnParametersCode();
与文档中显示的相同(即使我不确定它是如何得到的)。
现在我们将 lambdaCode
传递到 ApiStackPipeline 中,它看起来像;
IRepository repository = Repository.fromRepositoryName(this, repoName, repoName);
IBucket bucket = Bucket.fromBucketName(this, "codepipeline-api", "codepipeline-api");
PipelineProject lambdaBuild = PipelineProject.Builder.create(this, "ApiBuild")
.buildSpec(BuildSpec.fromSourceFilename("lambda-buildspec.yml"))
.environment(BuildEnvironment.builder().buildImage(LinuxBuildImage.STANDARD_4_0).build())
.build();
PipelineProject cdkBuild = PipelineProject.Builder.create(this, "ApiCDKBuild")
.buildSpec(BuildSpec.fromSourceFilename("cdk-buildspec.yml"))
.environment(BuildEnvironment.builder().buildImage(LinuxBuildImage.STANDARD_4_0).build())
.build();
Artifact sourceOutput = new Artifact();
Artifact cdkBuildOutput = new Artifact("CdkBuildOutput");
Artifact lambdaBuildOutput = new Artifact("LambdaBuildOutput");
Pipeline.Builder.create(this, "ApiPipeline")
.stages(Arrays.asList(
StageProps.builder()
.stageName("Source")
.actions(Arrays.asList(
CodeCommitSourceAction.Builder.create()
.actionName("Source")
.repository(repository)
.output(sourceOutput)
.build()))
.build(),
StageProps.builder()
.stageName("Build")
.actions(Arrays.asList(
CodeBuildAction.Builder.create()
.actionName("Lambda_Build")
.project(lambdaBuild)
.input(sourceOutput)
.outputs(Arrays.asList(lambdaBuildOutput)).build(),
CodeBuildAction.Builder.create()
.actionName("CDK_Build")
.project(cdkBuild)
.input(sourceOutput)
.outputs(Arrays.asList(cdkBuildOutput))
.build()))
.build(),
StageProps.builder()
.stageName("Deploy")
.actions(Arrays.asList(
CloudFormationCreateUpdateStackAction.Builder.create()
.actionName("Lambda_CFN_Deploy")
.templatePath(cdkBuildOutput.atPath("ApiStackAlfa.template.json"))
.adminPermissions(true)
.parameterOverrides(lambdaCode.assign(lambdaBuildOutput.getS3Location()))
.extraInputs(Arrays.asList(lambdaBuildOutput))
.stackName("ApiStackAlfaDeployment")
.build()))
.build()))
.artifactBucket(bucket)
.restartExecutionOnUpdate(true)
.build();
这里我也分享了*-buildspec.yml个文件;
lambda-buildspec.yml
version: 0.2
phases:
install:
runtime-versions:
java: corretto8
build:
commands:
- echo current directory `pwd`
- echo building gradle project on `date`
- ./gradlew clean build
artifacts:
files:
- build/distributions/src.zip
discard-paths: yes
cdk-buildspec.yml
version: 0.2
phases:
install:
runtime-versions:
nodejs: 10
java: corretto8
commands:
- echo installing aws-cdk on `date`
- npm install aws-cdk
build:
commands:
- echo current directory `pwd`
- ls -l
- echo building cdk project on `date`
- ./gradlew clean build
- npx cdk synth -o dist
post_build:
commands:
- echo listing files after build under dist
- ls -l dist
artifacts:
files:
- ApiStackAlfa.template.json
base-directory: dist
这是我得到的异常堆栈跟踪
Class not found: com.buraktas.api.main.Lambda: java.lang.ClassNotFoundException
java.lang.ClassNotFoundException: com.buraktas.api.main.Lambda
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
最后在这里我分享了一个简化版本的项目结构,如果有帮助的话
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── buraktas
│ │ │ └── api
│ │ │ ├── main
│ │ │ │ ├── ApiMain.java
│ │ │ │ ├── ApiPipelineStack.java
│ │ │ │ ├── ApiStack.java
│ │ │ │ └── Lambda.java
│ │ │ └── repository
│ │ │ └── Repository.java
│ │ └── resources
│ │ └── log4j.properties
│ └── test
│ ├── java
│ │ ├── DocumentTest.java
│ │ └── JsonWriterSettingsTest.java
│ └── resources
│ └── request.http
看起来一切正常,管道创建成功,源 -> 构建 -> 部署步骤 运行 顺利。但是,当我触发我的 lambda 函数时,我得到了 ClassNotFoundException。我尝试使用 .zip 或 .jar(fat jar)工件,但没有任何改变。
感谢您的帮助。
我发现问题的发生是因为 CodeBuild 从给定的工件创建了一个 zip。这意味着将有一个包含 src.zip
本身的 zip 文件,其中包含正确的项目构建文件。由于此主 zip 文件正在上传到 Lambda,因此无法找到处理程序定义,因此会抛出 ClassNotFoundException。但是,示例文档和构建规范的 AWS CodeBuild 参考文档中均未提及此额外的 zip 过程。我们需要手动解压缩 zip 文件的内容并将其作为工件输出。这是我们 buildspec.yml 的最终版本。此外,如果您不想处理解压缩内容,那么您需要配置您的构建工具(我们在这里使用 gradle)以在 运行 构建命令后不将内容压缩到 zip 文件中。
version: 0.2
phases:
install:
runtime-versions:
java: corretto8
build:
commands:
- echo current directory `pwd`
- echo building gradle project on `date`
- ./gradlew clean build
post_build:
commands:
- mkdir build/distributions/api
- unzip build/distributions/api.zip -d build/distributions/api
artifacts:
files:
- '**/*'
base-directory: build/distributions/api
我正在尝试通过 AWS CDK 使用 Lambda 构建 CI/CD 管道。我们在这里使用 gradle 项目。此外,我还遵循了示例 documentation。我们定义了两个堆栈,它们是 APIStack 和 ApiStackPipeline,其中 APIStack 由 Lambda_Build
处理,ApiStackPipeline 由 CDK_BUILD
.
我们正在 ApiStack 中初始化 Lambda 函数,例如;
final Function contactFunction = Function.Builder.create(this, "contactFunction").role(roleLambda)
.runtime(Runtime.JAVA_8)
.code(lambdaCode)
.handler("com.buraktas.contact.main.ContactLambda::handleRequest")
.memorySize(512)
.timeout(Duration.minutes(1))
.environment(environment)
.description(Instant.now().toString()).build();
在这种情况下,我们将 lambdaCode
参数设置为 this.lambdaCode = new CfnParametersCode();
与文档中显示的相同(即使我不确定它是如何得到的)。
现在我们将 lambdaCode
传递到 ApiStackPipeline 中,它看起来像;
IRepository repository = Repository.fromRepositoryName(this, repoName, repoName);
IBucket bucket = Bucket.fromBucketName(this, "codepipeline-api", "codepipeline-api");
PipelineProject lambdaBuild = PipelineProject.Builder.create(this, "ApiBuild")
.buildSpec(BuildSpec.fromSourceFilename("lambda-buildspec.yml"))
.environment(BuildEnvironment.builder().buildImage(LinuxBuildImage.STANDARD_4_0).build())
.build();
PipelineProject cdkBuild = PipelineProject.Builder.create(this, "ApiCDKBuild")
.buildSpec(BuildSpec.fromSourceFilename("cdk-buildspec.yml"))
.environment(BuildEnvironment.builder().buildImage(LinuxBuildImage.STANDARD_4_0).build())
.build();
Artifact sourceOutput = new Artifact();
Artifact cdkBuildOutput = new Artifact("CdkBuildOutput");
Artifact lambdaBuildOutput = new Artifact("LambdaBuildOutput");
Pipeline.Builder.create(this, "ApiPipeline")
.stages(Arrays.asList(
StageProps.builder()
.stageName("Source")
.actions(Arrays.asList(
CodeCommitSourceAction.Builder.create()
.actionName("Source")
.repository(repository)
.output(sourceOutput)
.build()))
.build(),
StageProps.builder()
.stageName("Build")
.actions(Arrays.asList(
CodeBuildAction.Builder.create()
.actionName("Lambda_Build")
.project(lambdaBuild)
.input(sourceOutput)
.outputs(Arrays.asList(lambdaBuildOutput)).build(),
CodeBuildAction.Builder.create()
.actionName("CDK_Build")
.project(cdkBuild)
.input(sourceOutput)
.outputs(Arrays.asList(cdkBuildOutput))
.build()))
.build(),
StageProps.builder()
.stageName("Deploy")
.actions(Arrays.asList(
CloudFormationCreateUpdateStackAction.Builder.create()
.actionName("Lambda_CFN_Deploy")
.templatePath(cdkBuildOutput.atPath("ApiStackAlfa.template.json"))
.adminPermissions(true)
.parameterOverrides(lambdaCode.assign(lambdaBuildOutput.getS3Location()))
.extraInputs(Arrays.asList(lambdaBuildOutput))
.stackName("ApiStackAlfaDeployment")
.build()))
.build()))
.artifactBucket(bucket)
.restartExecutionOnUpdate(true)
.build();
这里我也分享了*-buildspec.yml个文件;
lambda-buildspec.yml
version: 0.2
phases:
install:
runtime-versions:
java: corretto8
build:
commands:
- echo current directory `pwd`
- echo building gradle project on `date`
- ./gradlew clean build
artifacts:
files:
- build/distributions/src.zip
discard-paths: yes
cdk-buildspec.yml
version: 0.2
phases:
install:
runtime-versions:
nodejs: 10
java: corretto8
commands:
- echo installing aws-cdk on `date`
- npm install aws-cdk
build:
commands:
- echo current directory `pwd`
- ls -l
- echo building cdk project on `date`
- ./gradlew clean build
- npx cdk synth -o dist
post_build:
commands:
- echo listing files after build under dist
- ls -l dist
artifacts:
files:
- ApiStackAlfa.template.json
base-directory: dist
这是我得到的异常堆栈跟踪
Class not found: com.buraktas.api.main.Lambda: java.lang.ClassNotFoundException
java.lang.ClassNotFoundException: com.buraktas.api.main.Lambda
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
最后在这里我分享了一个简化版本的项目结构,如果有帮助的话
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── buraktas
│ │ │ └── api
│ │ │ ├── main
│ │ │ │ ├── ApiMain.java
│ │ │ │ ├── ApiPipelineStack.java
│ │ │ │ ├── ApiStack.java
│ │ │ │ └── Lambda.java
│ │ │ └── repository
│ │ │ └── Repository.java
│ │ └── resources
│ │ └── log4j.properties
│ └── test
│ ├── java
│ │ ├── DocumentTest.java
│ │ └── JsonWriterSettingsTest.java
│ └── resources
│ └── request.http
看起来一切正常,管道创建成功,源 -> 构建 -> 部署步骤 运行 顺利。但是,当我触发我的 lambda 函数时,我得到了 ClassNotFoundException。我尝试使用 .zip 或 .jar(fat jar)工件,但没有任何改变。
感谢您的帮助。
我发现问题的发生是因为 CodeBuild 从给定的工件创建了一个 zip。这意味着将有一个包含 src.zip
本身的 zip 文件,其中包含正确的项目构建文件。由于此主 zip 文件正在上传到 Lambda,因此无法找到处理程序定义,因此会抛出 ClassNotFoundException。但是,示例文档和构建规范的 AWS CodeBuild 参考文档中均未提及此额外的 zip 过程。我们需要手动解压缩 zip 文件的内容并将其作为工件输出。这是我们 buildspec.yml 的最终版本。此外,如果您不想处理解压缩内容,那么您需要配置您的构建工具(我们在这里使用 gradle)以在 运行 构建命令后不将内容压缩到 zip 文件中。
version: 0.2
phases:
install:
runtime-versions:
java: corretto8
build:
commands:
- echo current directory `pwd`
- echo building gradle project on `date`
- ./gradlew clean build
post_build:
commands:
- mkdir build/distributions/api
- unzip build/distributions/api.zip -d build/distributions/api
artifacts:
files:
- '**/*'
base-directory: build/distributions/api