Skip to content

Commit 5fd08cd

Browse files
committed
Update everything
1 parent 9fc59a3 commit 5fd08cd

9 files changed

+1728
-105
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
/jdk
12
/target
23
/aws-lambda-rie
3-
/jdk
44
/cr
5+
/log.json
6+
/response.json

README.md

Lines changed: 103 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,137 @@
1-
# Blank function (Java)
1+
# AWS Lambda on Java with CRaC
22

3-
![Architecture](/sample-apps/blank-java/images/sample-blank-java.png)
3+
This is an example of a Java AWS Lambda that uses Project CRaC to reduce the cost and time of the first function invocation.
44

5-
The project source includes function code and supporting resources:
5+
Based on https://github.com/awsdocs/aws-lambda-developer-guide/tree/main/sample-apps/blank-java.
66

7-
- `src/main` - A Java function.
8-
- `src/test` - A unit test and helper classes.
9-
- `template.yml` - An AWS CloudFormation template that creates an application.
10-
- `build.gradle` - A Gradle build file.
11-
- `pom.xml` - A Maven build file.
12-
- `1-create-bucket.sh`, `2-build-layer.sh`, etc. - Shell scripts that use the AWS CLI to deploy and manage the application.
7+
In this example the function will be packed as the [container image](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-images.html#gettingstarted-images-package) together with JDK CRaC runtime and the CRaC image.
138

14-
Use the following instructions to deploy the sample application.
9+
The CRaC image with the snapshot of JVM and application states will be preparied with the help of [AWS Runtime Interface Emulator](https://github.com/aws/aws-lambda-runtime-interface-emulator).
1510

16-
# Requirements
17-
- [Java 8 runtime environment (SE JRE)](https://www.oracle.com/java/technologies/javase-downloads.html)
18-
- [Gradle 5](https://gradle.org/releases/) or [Maven 3](https://maven.apache.org/docs/history.html)
19-
- The Bash shell. For Linux and macOS, this is included by default. In Windows 10, you can install the [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10) to get a Windows-integrated version of Ubuntu and Bash.
20-
- [The AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) v1.17 or newer.
11+
The deployment scheme will be:
12+
* Create the container image with the function
13+
* Create the CRaC image:
14+
* Run the function in the local environment
15+
* Optionally, warm-up the function
16+
* Trigger checkpoint to dump the image
17+
* Create the container image with the function and the image
18+
* Deploy the container to AWS
2119

22-
If you use the AWS CLI v2, add the following to your [configuration file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) (`~/.aws/config`):
20+
![Deployment](./deployment.png)
21+
22+
* [Instruction](#instruction)
23+
* [Build](#build)
24+
* [Checkpoint](#checkpoint)
25+
* [Deploy](#deploy)
26+
* [Test locally](#test-locally)
27+
* [Deploy to AWS](#deploy-to-aws)
28+
* [Techical details](#techical-details)
29+
30+
## Instruction
31+
32+
The repo contain a helper script to perform routine operations.
33+
Its intended to be "executable documetnation", so the reader is invited to look to the script
34+
35+
The step zero fetches necessary dependencies:
2336

2437
```
25-
cli_binary_format=raw-in-base64-out
38+
./crac-steps.sh s00_init
2639
```
2740

28-
This setting enables the AWS CLI v2 to load JSON events from a file, matching the v1 behavior.
41+
### Build
2942

30-
# Setup
31-
Download or clone this repository.
43+
The function needs to use a fork of [AWS Lambda Java Libraries](https://github.com/CRaC/aws-lambda-java-libs), `pom.xml` was changed to:
3244

33-
$ git clone https://github.com/awsdocs/aws-lambda-developer-guide.git
34-
$ cd aws-lambda-developer-guide/sample-apps/blank-java
45+
```
46+
<dependency>
47+
<groupId>io.github.crac.com.amazonaws</groupId>
48+
<artifactId>aws-lambda-java-runtime-interface-client</artifactId>
49+
<version>1.0.0</version>
50+
</dependency>
51+
```
3552

36-
To create a new bucket for deployment artifacts, run `1-create-bucket.sh`.
53+
Build the app and create the container image for checkpoint (`crac-lambda-checkpoint`):
54+
```
55+
./crac-steps.sh s01_build
56+
```
3757

38-
blank-java$ ./1-create-bucket.sh
39-
make_bucket: lambda-artifacts-a5e491dbb5b22e0d
58+
### Checkpoint
59+
60+
Run the container for checkpoint on the local machine:
61+
62+
```
63+
./crac-steps.sh s02_start_checkpoint
64+
```
65+
66+
At this point you may provide a workload to warm-up your function, served on port `8080`.
67+
68+
Then trigger checkpoint from another terminal:
69+
```
70+
./crac-steps.sh s03_checkpoint
71+
```
4072

41-
To build a Lambda layer that contains the function's runtime dependencies, run `2-build-layer.sh`. Packaging dependencies in a layer reduces the size of the deployment package that you upload when you modify your code.
73+
The former function process should exit, leaving the CRaC image in the `cr` directory.
4274

43-
blank-java$ ./2-build-layer.sh
75+
Create the final container image with the CRaC image (`crac-lambda-restore`):
4476

45-
# Deploy
77+
```
78+
./crac-steps.sh s04_prepare_restore
79+
```
4680

47-
To deploy the application, run `3-deploy.sh`.
81+
### Deploy
4882

49-
blank-java$ ./3-deploy.sh
50-
BUILD SUCCESSFUL in 1s
51-
Successfully packaged artifacts and wrote output template to file out.yml.
52-
Waiting for changeset to be created..
53-
Successfully created/updated stack - blank-java
83+
Now you can deploy `crac-lambda-restore` as the container function to the AWS.
5484

55-
This script uses AWS CloudFormation to deploy the Lambda functions and an IAM role. If the AWS CloudFormation stack that contains the resources already exists, the script updates it with any changes to the template or function code.
85+
You may also check the function locally with RIE.
5686

57-
You can also build the application with Maven. To use maven, add `mvn` to the command.
87+
#### Test locally
5888

59-
java-basic$ ./3-deploy.sh mvn
60-
[INFO] Scanning for projects...
61-
[INFO] -----------------------< com.example:blank-java >-----------------------
62-
[INFO] Building blank-java-function 1.0-SNAPSHOT
63-
[INFO] --------------------------------[ jar ]---------------------------------
64-
...
89+
You can also test the container locally:
6590

66-
# Test
67-
To invoke the function, run `4-invoke.sh`.
91+
```
92+
./crac-steps.sh s05_local_restore
93+
```
6894

69-
blank-java$ ./4-invoke.sh
70-
{
71-
"StatusCode": 200,
72-
"ExecutedVersion": "$LATEST"
73-
}
95+
From another terminal:
96+
```
97+
./crac-steps.sh "post hi"
98+
```
7499

75-
Let the script invoke the function a few times and then press `CRTL+C` to exit.
100+
To test cold start, drop file system caches by:
101+
```
102+
./crac-steps.sh make_cold_local
103+
```
76104

77-
The application uses AWS X-Ray to trace requests. Open the [X-Ray console](https://console.aws.amazon.com/xray/home#/service-map) to view the service map.
105+
#### Deploy to AWS
78106

79-
![Service Map](/sample-apps/blank-java/images/blank-java-servicemap.png)
107+
The helper script can deploy the container, assuming there is a function named `crac-test` configured to use image `crac-test`.
80108

81-
Choose a node in the main function graph. Then choose **View traces** to see a list of traces. Choose any trace to view a timeline that breaks down the work done by the function.
109+
Initialize the evironment:
110+
```
111+
eval $(./crac-steps.sh s06_init_aws)
112+
```
82113

83-
![Trace](/sample-apps/blank-java/images/blank-java-trace.png)
114+
Push the container and update the function:
115+
```
116+
./crac-steps.sh s07_deploy_aws
117+
```
84118

85-
Finally, view the application in the Lambda console.
119+
To test cold start, reconfigure the function by:
120+
```
121+
./crac-steps.sh make_cold_aws
122+
```
86123

87-
*To view the application*
88-
1. Open the [applications page](https://console.aws.amazon.com/lambda/home#/applications) in the Lambda console.
89-
2. Choose **blank-java**.
124+
## Techical details
90125

91-
![Application](/sample-apps/blank-java/images/blank-java-application.png)
126+
AWS environment restricts features and capabilities for functions in containers:
127+
* the file system is read-only, except `/tmp`
128+
* setting PID/TIDs for being restored process, `ptrace`, and some other syscalls that are usually required by CRIU are not allowed
129+
* storage performance is not great
92130

93-
# Cleanup
94-
To delete the application, run `5-cleanup.sh`.
131+
To overcome storage limits we unpack libjvm.so to /tmp at the start of the container.
132+
Same is done for CRaC image after it's added to the container image.
95133

96-
blank$ ./5-cleanup.sh
134+
CRaC's build of CRIU attempts to set the PID/TIDs on restore first, in case this functionality is presented.
135+
When this fails in AWS, it attempts to align the next PID by creating empty do-nothing processes/threads.
136+
As in the vanilla CRIU, there is strict requirement for PID/TIDs to be available.
137+
To ensure Java PID/TIDs won't intersect with another processes on restore, checkpoint script ensures Java PID/TIDs are created with a reasonable offset, see [the script](./checkpoint.cmd.sh#L8).

checkpoint.cmd.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ exec /aws-lambda-rie /bin/bash -c '\
1212
-XX:CRaCCheckpointTo=/cr \
1313
-cp /function:/function/lib/* \
1414
-Dcom.amazonaws.services.lambda.runtime.api.client.NativeClient.libsBase=/function/lib/ \
15+
--add-opens java.base/java.util=ALL-UNNAMED \
1516
com.amazonaws.services.lambda.runtime.api.client.AWSLambda $0' "$@"

crac-steps.sh

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#!/bin/bash
22

3-
CONTAINERNAME=crac
3+
LAMBDA_NAME=crac-test
4+
LAMBDA_IMAGE=crac-test
5+
6+
CRAC_VERSION=17-crac+2
47

58
IOLIM=60m
69
DEV=/dev/nvme0n1
@@ -10,26 +13,26 @@ CPU=0.88
1013
iolim() { IOLIM=$1; }
1114
cpu() { CPU=$1; }
1215

13-
s00_init() {
14-
15-
if [ -z $JAVA_HOME ]; then
16-
echo "No JAVA_HOME specified"
17-
return 1
18-
fi
19-
16+
dojlink() {
17+
local JDK=$1
2018
rm -rf jdk
21-
cp -r $JAVA_HOME jdk
19+
$JDK/bin/jlink --bind-services --output jdk --module-path $JDK/jmods --add-modules java.base,jdk.unsupported,java.sql
20+
# XXX
21+
cp $JDK/lib/criu jdk/lib/
22+
}
23+
24+
s00_init() {
25+
curl -LO https://github.com/CRaC/openjdk-builds/releases/download/$CRAC_VERSION/jdk$CRAC_VERSION.tar.gz
26+
tar axf jdk$CRAC_VERSION.tar.gz
27+
dojlink jdk$CRAC_VERSION
2228

23-
curl -L -o aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/download/v1.3/aws-lambda-rie-$(uname -m)
29+
curl -L -o aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/download/v1.3/aws-lambda-rie-$(uname -m)
2430
chmod +x aws-lambda-rie
2531
}
2632

27-
dojlink() {
28-
$JAVA_HOME/bin/jlink --bind-services --output jdk --module-path $JAVA_HOME/jmods --add-modules java.base,jdk.unsupported,java.sql
29-
}
3033

3134
s01_build() {
32-
mvn compile dependency:copy-dependencies -DincludeScope=runtime
35+
mvn clean compile dependency:copy-dependencies -DincludeScope=runtime
3336
docker build -t crac-lambda-checkpoint -f Dockerfile.checkpoint .
3437
}
3538

@@ -70,7 +73,7 @@ s04_prepare_restore() {
7073
docker build -t crac-lambda-restore -f Dockerfile.restore .
7174
}
7275

73-
dropcache() {
76+
make_cold_local() {
7477
sync
7578
echo 3 | sudo tee /proc/sys/vm/drop_caches > /dev/null
7679
}
@@ -85,11 +88,13 @@ local_test() {
8588
--device-write-bps $DEV:$IOLIM \
8689
--cpus $CPU \
8790
--entrypoint '' \
91+
--security-opt seccomp=$PWD/seccomp.json \
8892
"$@"
8993
}
9094

9195
s05_local_restore() {
92-
local_test crac-lambda-restore \
96+
local_test \
97+
crac-lambda-restore \
9398
/aws-lambda-rie /bin/bash /restore.cmd.sh
9499
}
95100

@@ -102,43 +107,41 @@ local_baseline() {
102107
example.Handler::handleRequest
103108
}
104109

110+
ltest() {
111+
local_test \
112+
-v /home:/home \
113+
-v $PWD/logdir:/tmp/log \
114+
crac-lambda-restore \
115+
/bin/bash $PWD/restore.cmd.sh
116+
}
117+
105118
s06_init_aws() {
106119
ACCOUNT=$(aws sts get-caller-identity | jq -r '.Account')
107120
echo export ACCOUNT=$ACCOUNT
108121
REGION=$(aws configure get region)
109122
echo export REGION=$REGION
110123
ECR=$ACCOUNT.dkr.ecr.$REGION.amazonaws.com
111124
echo export ECR=$ECR
112-
REMOTEIMG=$ECR/crac-test
125+
REMOTEIMG=$ECR/$LAMBDA_IMAGE
113126
echo export REMOTEIMG=$REMOTEIMG
114127
aws ecr get-login-password | docker login --username AWS --password-stdin $ECR 1>&2
115128
}
116129

117130
s07_deploy_aws() {
118131
docker tag crac-lambda-restore $REMOTEIMG
119132
docker push $REMOTEIMG
120-
}
121-
122-
init_lambda() {
123-
if ! [ $LAMBDANAME ]; then
124-
echo "Provide LAMBDANAME= preconfigured by a container image: \
125-
https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-images.html#gettingstarted-images-function" >&2
126-
exit 1
127-
fi
128-
}
129133

130-
update_lambda() {
131134
local digest=$(docker inspect -f '{{ index .RepoDigests 0 }}' $REMOTEIMG)
132-
aws lambda update-function-code --function-name $LAMBDANAME --image $digest
133-
aws lambda wait function-updated --function-name $LAMBDANAME
135+
aws lambda update-function-code --function-name $LAMBDA_NAME --image $digest
136+
aws lambda wait function-updated --function-name $LAMBDA_NAME
134137
}
135138

136-
s08_invoke_lambda() {
139+
s08_invoke_aws() {
137140
rm -f response.json log.json
138141

139142
aws lambda invoke \
140143
--cli-binary-format raw-in-base64-out \
141-
--function-name $LAMBDANAME \
144+
--function-name $LAMBDA_NAME \
142145
--payload "$(< event.json) " \
143146
--log-type Tail \
144147
response.json \
@@ -148,11 +151,12 @@ s08_invoke_lambda() {
148151
jq -r .LogResult < log.json | base64 -d
149152
}
150153

151-
coldstart_lambda() {
152-
local mem=$(aws lambda get-function-configuration --function-name $LAMBDANAME | jq -r '.MemorySize')
154+
make_cold_aws() {
155+
local mem=$(aws lambda get-function-configuration --function-name $LAMBDA_NAME | jq -r '.MemorySize')
153156
local min=256
154157
local max=512
155-
aws lambda update-function-configuration --function-name $LAMBDANAME --memory-size $(($min + (($mem + 1) % ($max - $min))))
158+
aws lambda update-function-configuration --function-name $LAMBDA_NAME --memory-size $(($min + (($mem + 1) % ($max - $min))))
159+
aws lambda wait function-updated --function-name $LAMBDA_NAME
156160
}
157161

158162
for i; do

deployment.png

43.2 KB
Loading

pom.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@
1212
<maven.compiler.target>1.8</maven.compiler.target>
1313
</properties>
1414
<dependencies>
15-
<dependency>
16-
<groupId>io.github.crac</groupId>
17-
<artifactId>org-crac</artifactId>
18-
<version>0.1.0</version>
19-
</dependency>
2015
<dependency>
2116
<groupId>io.github.crac.com.amazonaws</groupId>
2217
<artifactId>aws-lambda-java-runtime-interface-client</artifactId>

prepare-jdk.cmd.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
[ -d /tmp/sub/jdk ] && exit 0
44

5+
# ensure umask is consistent between environments
6+
umask 0002
7+
58
mkdir -p /tmp/sub/jdk/lib/server/
69
lz4 -d /jdk/lib/server/libjvm.so.lz4 /tmp/sub/jdk/lib/server/libjvm.so
7-
ln -s -t /tmp/sub/jdk/lib /jdk/lib/*
10+
ln -s -t /tmp/sub/jdk/lib /jdk/lib/* 2>/dev/null

restore.cmd.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
/prepare-jdk.cmd.sh
44

5-
lz4 -d /cr.tar.lz4 - | tar x -C /tmp/
5+
lz4 -q -d /cr.tar.lz4 - | tar x -C /tmp/
66

77
/jdk/bin/java -XX:CRaCRestoreFrom=/tmp/cr
8-
# exec /jdk/lib/criu restore -W . --shell-job --action-script /jdk/lib/action-script -D /tmp/cr/ -v1 --exec-cmd -- /jdk/lib/wait

0 commit comments

Comments
 (0)