Skip to content

[wip] Convert docker support from lambci to official AWS docker images and add support for node 14 #1274

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion examples/events/http/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ provider:
memorySize: 128
name: aws
region: us-east-1 # default
runtime: nodejs12.x
runtime: nodejs14.x
stage: dev
versionFunctions: false

custom:
serverless-offline:
useDocker: true

functions:
hello:
events:
Expand Down
2 changes: 1 addition & 1 deletion src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ export const DEFAULT_WEBSOCKETS_API_ROUTE_SELECTION_EXPRESSION =

export const DEFAULT_WEBSOCKETS_ROUTE = '$default'

export const DEFAULT_DOCKER_CONTAINER_PORT = 9001
export const DEFAULT_DOCKER_CONTAINER_PORT = 8080
13 changes: 0 additions & 13 deletions src/lambda/handler-runner/HandlerRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,6 @@ export default class HandlerRunner {
debugLog(`Loading handler... (${handlerPath})`)

if (useDocker) {
// https://github.com/lambci/docker-lambda/issues/329
if (runtime === 'nodejs14.x') {
logWarning(
'"nodejs14.x" runtime is not supported with docker. See https://github.com/lambci/docker-lambda/issues/329',
)
throw new Error('Unsupported runtime')
}

if (runtime === 'python3.9') {
logWarning('"python3.9" runtime is not supported with docker.')
throw new Error('Unsupported runtime')
}

const dockerOptions = {
host: this.#options.dockerHost,
hostServicePath: this.#options.dockerHostServicePath,
Expand Down
36 changes: 27 additions & 9 deletions src/lambda/handler-runner/docker-runner/DockerContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import crypto from 'crypto'
import DockerImage from './DockerImage.js'
import debugLog from '../../../debugLog.js'
import { logLayers, logWarning } from '../../../serverlessLog.js'
import { DEFAULT_DOCKER_CONTAINER_PORT } from '../../../config/constants.js'

const { stringify } = JSON
const { entries } = Object
Expand Down Expand Up @@ -54,7 +55,17 @@ export default class DockerContainer {
}

_baseImage(runtime) {
return `lambci/lambda:${runtime}`
const runtimeSplitterRgx = /(python|nodejs|ruby|java|go|dotnetcore)(\d*\.?\d)/gi
const match = runtimeSplitterRgx.exec(runtime)
const [, language, version] = match

if (!match) {
throw new Error(`Unsupported runtime ${runtime}`)
}

const baseImage = `amazon/aws-lambda-${language}:${version}`
debugLog(`Using base image ${baseImage}`)
return baseImage
}

async start(codeDir) {
Expand All @@ -72,7 +83,7 @@ export default class DockerContainer {
'-v',
`${codeDir}:/var/task:${permissions},delegated`,
'-p',
9001,
DEFAULT_DOCKER_CONTAINER_PORT,
'-e',
'DOCKER_LAMBDA_STAY_OPEN=1', // API mode
'-e',
Expand Down Expand Up @@ -163,8 +174,7 @@ export default class DockerContainer {
await new Promise((resolve, reject) => {
dockerStart.all.on('data', (data) => {
const str = data.toString()
console.log(str)
if (str.includes('Lambda API listening on port')) {
if (str.includes("exec '/var/runtime/bootstrap'")) {
resolve()
}
})
Expand All @@ -182,13 +192,18 @@ export default class DockerContainer {
])
// NOTE: `docker port` may output multiple lines.
//

// e.g.:
// 9001/tcp -> 0.0.0.0:49153
// 9001/tcp -> :::49153
// DEFAULT_DOCKER_CONTAINER_PORT/tcp -> 0.0.0.0:49153
// DEFAULT_DOCKER_CONTAINER_PORT/tcp -> :::49153
//
// Parse each line until it finds the mapped port.
const portRgx = new RegExp(
`^${DEFAULT_DOCKER_CONTAINER_PORT}\\/tcp -> (.*):(\\d+)$`,
)
for (const line of dockerPortOutput.split('\n')) {
const result = line.match(/^9001\/tcp -> (.*):(\d+)$/)
// const result = line.match(/^8080\/tcp -> (.*):(\d+)$/)
const result = line.match(portRgx)
if (result && result.length > 2) {
;[, , containerPort] = result
break
Expand Down Expand Up @@ -318,7 +333,10 @@ export default class DockerContainer {
}/2018-06-01/ping`
const res = await fetch(url)

if (!res.ok) {
// Official lambda images don't have a /ping endpoint, but will return a 404
// when the runtime API is started, so in my book this 'ping' is 'successful'?
// Need a better way to do this
if (!res.ok && !res.statusText.includes('Not Found')) {
throw new Error(`Failed to fetch from ${url} with ${res.statusText}`)
}

Expand All @@ -328,7 +346,7 @@ export default class DockerContainer {
async request(event) {
const url = `http://${this.#dockerOptions.host}:${
this.#port
}/2015-03-31/functions/${this.#functionKey}/invocations`
}/2015-03-31/functions/function/invocations`

const res = await fetch(url, {
body: stringify(event),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { resolve } from 'path'
import fetch from 'node-fetch'
import { satisfies } from 'semver'
import { joinUrl, setup, teardown } from '../../../_testHelpers/index.js'

jest.setTimeout(120000)

// "Could not find 'Docker', skipping 'Docker' tests."
const _describe = process.env.DOCKER_DETECTED ? describe : describe.skip

_describe('Node.js 14.x with Docker tests', () => {
// init
beforeAll(() =>
setup({
servicePath: resolve(__dirname),
}),
)

// cleanup
afterAll(() => teardown())

//
;[
{
description: 'should work with nodejs14.x in docker container',
expected: {
message: 'Hello Node.js 14.x!',
},
path: '/dev/hello',
},
].forEach(({ description, expected, path }) => {
test(description, async () => {
const url = joinUrl(TEST_BASE_URL, path)
const response = await fetch(url)
const json = await response.json()

expect(json.message).toEqual(expected.message)
expect(satisfies(json.version, '14')).toEqual(true)
})
})
})
15 changes: 15 additions & 0 deletions tests/integration/docker/nodejs/nodejs14.x/handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict'

const { versions } = require('process')

const { stringify } = JSON

module.exports.hello = async () => {
return {
body: stringify({
message: 'Hello Node.js 14.x!',
version: versions.node,
}),
statusCode: 200,
}
}
24 changes: 24 additions & 0 deletions tests/integration/docker/nodejs/nodejs14.x/serverless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
service: docker-nodejs14.x-test

plugins:
- ./../../../../../

provider:
memorySize: 128
name: aws
region: us-east-1 # default
runtime: nodejs14.x
stage: dev
versionFunctions: false

custom:
serverless-offline:
useDocker: true

functions:
hello:
events:
- http:
method: get
path: hello
handler: handler.hello