Lambda 函数在使用捆绑程序时不会自动设置 aws-sdk 凭证



我有一个看起来像这样的函数(构建后(:

myFunction/
package.json
node_modules/
myUtils/
index.ts
index.js
index.ts
index.js

myUtils/index.ts内,我有:

import { SecretsManager } from 'aws-sdk' 
...
const secretsManager = new SecretsManager()

如果我压缩myFunction/的内容并将它们部署到 AWS Lambda,它们将按预期工作。但是,存档不必要地臃肿(大约 60 MB 与使用捆绑器时的 3MB 相比(。

所以我安装了 Parcel 并运行了npx parcel build index.ts --no-source-maps,它创建了一个名为dist/的文件夹,其中包含单个文件 (index.js(。如果我压缩并dist/部署到 AWS Lambda,函数调用现在会失败,并显示错误,指示未向SecretsManager构造函数提供区域或凭证。

使用捆绑程序时,是否还需要执行执行角色的凭据配置aws-sdk

要自动配置aws-sdk的凭证,必须从默认 Lambda 运行时环境加载aws-sdk

包裹捆绑器有两种模式:parcel ... --target nodeparcel ... --target node --bundle-node-modules

  • 第一个选项不起作用,因为它省略了所有依赖项,甚至忽略了不在默认 Lambda 运行时环境中的依赖项。
  • 第二个选项不起作用,因为它在捆绑包中包含aws-sdk,覆盖默认的 Lambda 运行时环境。

解决方案是使用动态导入来抑制aws-sdk捆绑,同时捆绑所有其他模块。

所以这个

import { SecretsManager } from 'aws-sdk' 
...
const secretsManager = new SecretsManager()

成为

const awsSdk = import('aws-sdk')
...
const { SecretsManager } = await awsSdk
const secretsManager = new SecretsManager()

CLI 参数是:

parcel build index.ts --no-source-maps --target node --bundle-node-modules

编辑2019年10月:

上述解决方案有效,但我一直是下面描述的基于 Lambda 层的解决方案,我认为它更胜一筹。为了处理 monorepos(我正在使用 Rush(,构建脚本有点笨拙,可能需要修改才能在您的项目中使用它。如果不使用 monorepo,则可以忽略拆分内部和外部依赖项的部分。

可能有更好的方法可以做到这一点,所以欢迎批评。

// @my-scope/my-layer/package.json
{
"name": "@my-scope/my-layer",
"scripts": {
"build": "build-layer"
},
"dependencies": {
"uuid": "^3.3.3",
"axios": "~0.19.0",
"date-fns": "~2.4.1"
},
"devDependencies": {
"@my-scope/build-scripts": "0.0.0"
}
}
// @my-scope/build-scripts/package.json
{
"name": "@meal-planner/tool-build-scripts",
"files": [
"out/**/*"
],
"main": "out/lib/index.js",
"bin": {
"build-layer": "out/bin/build-layer.js",
},
"scripts": {
"build": "tsc"
},
"dependencies": {
"jsonc-parser": "~2.1.1",
"fs-extra": "~8.1.0",
"del": "~5.1.0"
},
"devDependencies": {
"@types/node": "~12.11.1",
"@types/fs-extra": "~8.0.1",
"typescript": "~3.6.4",
}
}
// @my-scope/build-scripts/bin/build-layer.ts
#!/usr/bin/env node
import * as jsonc from 'jsonc-parser'
import * as fs from 'fs'
import * as fse from 'fs-extra'
import * as path from 'path'
import * as util from 'util'
import * as del from 'del'
import { spawnSyncCore } from '../lib'
const access = util.promisify(fs.access)
const readFile = util.promisify(fs.readFile)
const writeFile = util.promisify(fs.writeFile)
const mkdir = util.promisify(fs.mkdir)
const BUILD_DIR = `dist`
const TARGET_PATH = path.join(BUILD_DIR, `nodejs`)
const MANIFEST_NAME = `package.json`
const MANIFEST_LOCK_NAME = `package-lock.json`
const INTERNAL_SCOPE = `@my-scope/`
interface IMinimalPackageManifest {
description: string
repository: unknown
license: string
dependencies: {
[packageName: string]: string | undefined
}
}
async function buildLayer(_argv: readonly string[] = process.argv): Promise<void> {
const {
description,
repository,
license,
dependencies
} = await readManifest(MANIFEST_NAME)
const { internal, external } = splitDependencies(dependencies)
const targetManifestPath = path.join(TARGET_PATH, MANIFEST_NAME)
const targetManifestLockPath = path.join(TARGET_PATH, MANIFEST_LOCK_NAME)
await writeManifest(
targetManifestPath,
{
description,
repository,
license,
dependencies: external
}
)
installExternalDependencies(TARGET_PATH)
await installInternalDependencies(internal, TARGET_PATH)
del.sync(targetManifestPath)
del.sync(targetManifestLockPath)
}
async function readManifest(sourcePath: string): Promise<IMinimalPackageManifest> {
const raw = (await readFile(sourcePath)).toString()
return jsonc.parse(raw)
}
async function writeManifest(targetPath: string, manifest: IMinimalPackageManifest): Promise<void> {
const targetDir = path.dirname(targetPath)
try {
await access(targetDir)
} catch {
await mkdir(targetDir, {
recursive: true
})
}
const raw = JSON.stringify(manifest)
await writeFile(targetPath, raw)
}
interface IDependencyMap {
[key: string]: string | undefined
}
interface IDepedencyGroups {
internal: IDependencyMap
external: IDependencyMap
}
function splitDependencies(dependencies: IDependencyMap): IDepedencyGroups {
return Object.keys(dependencies).reduce<IDepedencyGroups>(
(groups, name) => {
if (name.startsWith(INTERNAL_SCOPE)) {
groups.internal[name] = dependencies[name]
} else {
groups.external[name] = dependencies[name]
}
return groups
},
{
internal: {},
external: {}
}
)
}
function installExternalDependencies(targetDir: string): void {
spawnSyncCore({
command: `npm`,
cwd: targetDir,
env: {
...process.env,
NODE_ENV: `production`
},
args: [
`install`
],
})
}
async function installInternalDependencies(dependencies: IDependencyMap, targetDir: string): Promise<void> {
const sourcePaths = Object.keys(dependencies)
.map(dependency => path.join(`node_modules`, dependency))
for (const sourcePath of sourcePaths) {
const targetPath = path.join(targetDir, sourcePath)
const sourceManifestPath = path.join(sourcePath, MANIFEST_NAME)
const sourceDistPath = path.join(sourcePath, BUILD_DIR)
await fse.copy(sourcePath, targetPath, {
dereference: true,
recursive: true,
filter: (src, _dest) => {
// Only copy package.json and dist/ folder.
return src === sourcePath || src === sourceManifestPath || src.startsWith(sourceDistPath)
}
})
}
}
/* eslint-disable @typescript-eslint/no-floating-promises */
buildLayer()
/* eslint-enable @typescript-eslint/no-floating-promises */
// @my-scope/build-scripts/lib/index.ts
import { spawnSync } from 'child_process'
const TAB_WIDTH = 4
export interface ISpawnSyncCoreOptions {
command: string
cwd?: string
env?: NodeJS.ProcessEnv
args?: readonly string[]
}
export function spawnSyncCore({
command,
cwd,
env,
args
}: ISpawnSyncCoreOptions): void {
const result = spawnSync(command, args, {
shell: true,
cwd,
env
})
if (result.error) {
throw result.error
}
if (result.status !== 0) {
throw new Error(`${command} returned a non-zero exit code: ${JSON.stringify({
stdout: result.stdout.toString(),
stderr: result.stderr.toString(),
status: result.status,
signal: result.signal
}, undefined, TAB_WIDTH)}`)
}
}

最新更新