Skip to content

Commit 76aee81

Browse files
committed
master: code used in article
1 parent 2ca0eda commit 76aee81

11 files changed

+225
-0
lines changed

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -1 +1,15 @@
1+
# Puppeteer performance in AWS Lambda Docker containers
2+
This repo contains all code and scripts, used in article https://dev.to/megabotan/aws-lambda-puppeteer-in-docker
3+
## Local run
4+
You need to setup docker, and download aws-lambda-rie binaries `./download_rie.sh`
5+
#### Run custom docker lambda image
6+
`./custom_build.sh && ./custom_run.sh`
7+
#### Run AWS based docker image
8+
`./aws_based_build.sh && ./aws_based_run.sh`
9+
#### Test running image locally
10+
`curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"url": "https://example.com"}'`
11+
## Deploy
12+
First you have to [setup AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html). Don't forget to set credentials and region in AWS cli config files.
13+
After that install [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html).
114

15+
All deploy steps are in `deploy/push_image_and_deploy_lambda.sh`

aws_based_build.sh

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
docker build -f dockerfiles/aws-based.dockerfile -t lambda-aws-based .

aws_based_run.sh

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
docker run -p 9000:8080 lambda-aws-based

custom_build.sh

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
docker build -f dockerfiles/custom.dockerfile -t lambda-custom .

custom_run.sh

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
docker run --rm \
2+
-v ~/.aws-lambda-rie:/aws-lambda \
3+
-p 9000:8080 \
4+
--entrypoint /aws-lambda/aws-lambda-rie \
5+
lambda-custom \
6+
/usr/local/bin/npx aws-lambda-ric app.handler
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
set -ex
2+
3+
IMAGE_NAME="lambda-custom"
4+
#IMAGE_NAME="lambda-aws-based"
5+
echo "Deploying ${IMAGE_NAME}"
6+
REGION=$(aws configure get region)
7+
echo "Using region ${REGION}"
8+
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
9+
echo "Using account id ${ACCOUNT_ID}"
10+
IMAGE_URI="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${IMAGE_NAME}:$(date +%s)"
11+
S3_BUCKET="${IMAGE_NAME}-delete-me"
12+
13+
aws s3api create-bucket --bucket ${S3_BUCKET} --region "${REGION}" --create-bucket-configuration "LocationConstraint=${REGION}" || true
14+
aws ecr create-repository \
15+
--repository-name ${IMAGE_NAME} \
16+
--region "${REGION}" || true
17+
aws ecr get-login-password --region "${REGION}" | docker login --username AWS --password-stdin "${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com"
18+
docker tag ${IMAGE_NAME}:latest "${IMAGE_URI}"
19+
docker push "${IMAGE_URI}"
20+
21+
sam deploy --template-file deploy/template.yaml --stack-name "${IMAGE_NAME}" --region "${REGION}" --s3-bucket "${S3_BUCKET}" \
22+
--parameter-overrides ImageUriParameter="${IMAGE_URI}" FunctionNameParameter="${IMAGE_NAME}" \
23+
--image-repository ${IMAGE_URI} --capabilities CAPABILITY_IAM

deploy/template.yaml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Transform: AWS::Serverless-2016-10-31
3+
Description: lambda_chrome_in_docker_research
4+
5+
Globals:
6+
Function:
7+
Timeout: 60
8+
9+
Resources:
10+
LambdaExecutorFunction:
11+
Type: AWS::Serverless::Function
12+
Properties:
13+
PackageType: Image
14+
FunctionName:
15+
Ref: FunctionNameParameter
16+
ImageUri:
17+
Ref: ImageUriParameter
18+
MemorySize: 2048
19+
Tracing: Active
20+
21+
Parameters:
22+
ImageUriParameter:
23+
Type: String
24+
FunctionNameParameter:
25+
Type: String

dockerfiles/aws-based.dockerfile

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM public.ecr.aws/lambda/nodejs:14
2+
3+
4+
COPY src/* ${LAMBDA_TASK_ROOT}
5+
6+
CMD [ "app.handler" ]

dockerfiles/custom.dockerfile

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
FROM node:14-buster
2+
3+
# Install Google Chrome
4+
RUN apt-get update \
5+
&& apt-get install -y chromium fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
6+
--no-install-recommends \
7+
&& rm -rf /var/lib/apt/lists/*
8+
9+
10+
# Install aws-lambda-cpp build dependencies
11+
RUN apt-get update && \
12+
apt-get install -y \
13+
g++ \
14+
make \
15+
cmake \
16+
unzip \
17+
libcurl4-openssl-dev
18+
19+
WORKDIR /code-and-deps
20+
21+
# Install nodejs dependencies, and create user (to run chromium from non-root user)
22+
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
23+
RUN npm install aws-lambda-ric [email protected] [email protected] \
24+
&& groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
25+
&& mkdir -p /home/pptruser/Downloads \
26+
&& chown -R pptruser:pptruser /home/pptruser \
27+
&& chown -R pptruser:pptruser /code-and-deps
28+
ENV AWS_XRAY_CONTEXT_MISSING LOG_ERROR
29+
30+
COPY src/app.js /code-and-deps/app.js
31+
USER pptruser
32+
ENTRYPOINT ["/usr/local/bin/npx", "aws-lambda-ric"]
33+
CMD ["app.handler"]

download_rie.sh

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
set -ex
2+
3+
mkdir -p ~/.aws-lambda-rie
4+
curl -Lo ~/.aws-lambda-rie/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie
5+
chmod +x ~/.aws-lambda-rie/aws-lambda-rie

src/app.js

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
delete process.env.AWS_XRAY_CONTEXT_MISSING;
2+
const AWSXRay = require('aws-xray-sdk-core');
3+
4+
AWSXRay.setLogger(console);
5+
6+
async function launchBrowser() {
7+
try { // for aws-based image
8+
const chromium = require('chrome-aws-lambda');
9+
console.info('launching chrome-aws-lambda browser');
10+
const browser = await chromium.puppeteer.launch({
11+
args: chromium.args,
12+
defaultViewport: chromium.defaultViewport,
13+
executablePath: await chromium.executablePath,
14+
headless: chromium.headless,
15+
ignoreHTTPSErrors: true,
16+
});
17+
return browser;
18+
} catch (e) { // for custom-built image
19+
console.info('launching puppeteer browser');
20+
const puppeteer = require('puppeteer');
21+
const browser = await puppeteer.launch({
22+
executablePath: '/usr/bin/chromium',
23+
headless: true,
24+
dumpio: true,
25+
args: [
26+
'--autoplay-policy=user-gesture-required',
27+
'--disable-background-networking',
28+
'--disable-background-timer-throttling',
29+
'--disable-backgrounding-occluded-windows',
30+
'--disable-breakpad',
31+
'--disable-client-side-phishing-detection',
32+
'--disable-component-update',
33+
'--disable-default-apps',
34+
'--disable-dev-shm-usage',
35+
'--disable-domain-reliability',
36+
'--disable-extensions',
37+
'--disable-features=AudioServiceOutOfProcess',
38+
'--disable-hang-monitor',
39+
'--disable-ipc-flooding-protection',
40+
'--disable-notifications',
41+
'--disable-offer-store-unmasked-wallet-cards',
42+
'--disable-popup-blocking',
43+
'--disable-print-preview',
44+
'--disable-prompt-on-repost',
45+
'--disable-renderer-backgrounding',
46+
'--disable-setuid-sandbox',
47+
'--disable-speech-api',
48+
'--disable-sync',
49+
'--disk-cache-size=33554432',
50+
'--hide-scrollbars',
51+
'--ignore-gpu-blacklist',
52+
'--metrics-recording-only',
53+
'--mute-audio',
54+
'--no-default-browser-check',
55+
'--no-first-run',
56+
'--no-pings',
57+
'--no-sandbox',
58+
'--no-zygote',
59+
'--password-store=basic',
60+
'--use-gl=swiftshader',
61+
'--use-mock-keychain',
62+
'--single-process',
63+
],
64+
});
65+
return browser;
66+
}
67+
}
68+
69+
function createNewSubsegment(subsegmentName) {
70+
const segment = AWSXRay.getSegment();
71+
if (!segment) return { close() { } };
72+
return segment.addNewSubsegment(subsegmentName);
73+
}
74+
75+
async function lambdaHandler(event, context) {
76+
console.info(`EVENT ${JSON.stringify(event, null, 2)}`);
77+
AWSXRay.setContextMissingStrategy('LOG_ERROR');
78+
const browserLaunchSubsegment = createNewSubsegment('browser launch');
79+
const browser = await launchBrowser();
80+
browserLaunchSubsegment.close();
81+
console.info('browser launched');
82+
const newTabSubsegment = createNewSubsegment('open new tab');
83+
const page = await browser.newPage();
84+
newTabSubsegment.close();
85+
console.info('opened new tab');
86+
let extractedText = '';
87+
try {
88+
const pageGotoSubsegment = createNewSubsegment('opening url');
89+
await page.goto(event.url, {
90+
waitUntil: 'networkidle0',
91+
timeout: 10 * 1000,
92+
});
93+
pageGotoSubsegment.close();
94+
console.info('page opened');
95+
const textExtractSubsegment = createNewSubsegment('extracting text');
96+
extractedText = await page.$eval('*', (el) => el.innerText);
97+
textExtractSubsegment.close();
98+
console.info('text extracted');
99+
// context.succeed(extractedText);
100+
} finally {
101+
console.info('finally');
102+
await page.close();
103+
console.info('page closed');
104+
await browser.close();
105+
console.info('browser closed');
106+
}
107+
return extractedText;
108+
}
109+
110+
module.exports = { handler: lambdaHandler };

0 commit comments

Comments
 (0)