在Docker中连接AWS SAM Local和DynamoDB

49

我使用AWS SAM Local建立了一个API网关/AWS Lambda对,并确认在运行后可以成功调用它。

接下来,我在docker容器中添加了本地的DynamoDB实例,并使用aws cli创建了一个表格。但是,当我将代码添加到Lambda以写入DynamoDB实例时,我收到了以下错误:

2018-02-22T11:13:16.172Z ed9ab38e-fb54-18a4-0852-db7e5b56c8cd error: could not write to table: {"message":"connect ECONNREFUSED 0.0.0.0:8000","code":"NetworkingError","errno":"ECONNREFUSED","syscall":"connect","address":"0.0.0.0","port":8000,"region":"eu-west-2","hostname":"0.0.0.0","retryable":true,"time":"2018-02-22T11:13:16.165Z"} writing event from command: {"name":"test","geolocation":"xyz","type":"createDestination"} END RequestId: ed9ab38e-fb54-18a4-0852-db7e5b56c8cd

我看到网上说可能需要连接相同的docker网络,所以我创建了一个网络 docker network create lambda-local 并更改了启动命令:

sam local start-api --docker-network lambda-local

docker run -v "$PWD":/dynamodb_local_db -p 8000:8000 --network=lambda-local cnadiminti/dynamodb-local:latest

但仍然收到相同的错误信息。sam local正在打印 2018/02/22 11:12:51 Connecting container 98b19370ab92f3378ce380e9c840177905a49fc986597fef9ef589e624b4eac3 to network lambda-local

我使用以下代码创建了dynamodbclient:

const AWS = require('aws-sdk')
const dynamodbURL = process.env.dynamodbURL || 'http://0.0.0.0:8000'
const awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID || '1234567'
const awsAccessKey = process.env.AWS_SECRET_ACCESS_KEY || '7654321'
const awsRegion = process.env.AWS_REGION || 'eu-west-2'

console.log(awsRegion, 'initialising dynamodb in region: ')

let dynamoDbClient
const makeClient = () => {
  dynamoDbClient = new AWS.DynamoDB.DocumentClient({
    endpoint: dynamodbURL,
    accessKeyId: awsAccessKeyId,
    secretAccessKey: awsAccessKey,
    region: awsRegion
  })
  return dynamoDbClient
}

module.exports = {
  connect: () => dynamoDbClient || makeClient()
}

检查我的代码创建的dynamodbclient显示

DocumentClient {
  options:
   { endpoint: 'http://0.0.0.0:8000',
     accessKeyId: 'my-key',
     secretAccessKey: 'my-secret',
     region: 'eu-west-2',
     attrValue: 'S8' },
  service:
   Service {
     config:
      Config {
        credentials: [Object],
        credentialProvider: [Object],
        region: 'eu-west-2',
        logger: null,
        apiVersions: {},
        apiVersion: null,
        endpoint: 'http://0.0.0.0:8000',
        httpOptions: [Object],
        maxRetries: undefined,
        maxRedirects: 10,
        paramValidation: true,
        sslEnabled: true,
        s3ForcePathStyle: false,
        s3BucketEndpoint: false,
        s3DisableBodySigning: true,
        computeChecksums: true,
        convertResponseTypes: true,
        correctClockSkew: false,
        customUserAgent: null,
        dynamoDbCrc32: true,
        systemClockOffset: 0,
        signatureVersion: null,
        signatureCache: true,
        retryDelayOptions: {},
        useAccelerateEndpoint: false,
        accessKeyId: 'my-key',
        secretAccessKey: 'my-secret' },
     endpoint:
      Endpoint {
        protocol: 'http:',
        host: '0.0.0.0:8000',
        port: 8000,
        hostname: '0.0.0.0',
        pathname: '/',
        path: '/',
        href: 'http://0.0.0.0:8000/' },
     _clientId: 1 },
  attrValue: 'S8' }

这个设置能正常工作吗?我应该如何让它们相互通信?

---- 编辑 ----

基于推特上的对话,值得一提的是(也许)我可以在CLI和Web Shell中与DynamoDB进行交互:

使用CLI操作dynamo db

dynamodb web shell

7个回答

44

非常感谢Heitor Lessa在Twitter上回答了我的问题,并提供示例存储库

这指引了我找到了答案...

  • dynamodb的Docker容器从我的机器的角度来看是在127.0.0.1(这就是为什么我可以与之交互)

  • SAM local的Docker容器从我的机器的角度来看也在127.0.0.1

  • 但是它们在彼此的上下文中不是在127.0.0.1上。

因此:https://github.com/heitorlessa/sam-local-python-hot-reloading/blob/master/users/users.py#L14

提示我更改连接代码为:

const AWS = require('aws-sdk')
const awsRegion = process.env.AWS_REGION || 'eu-west-2'

let dynamoDbClient
const makeClient = () => {
  const options = {
    region: awsRegion
  }
  if(process.env.AWS_SAM_LOCAL) {
    options.endpoint = 'http://dynamodb:8000'
  }
  dynamoDbClient = new AWS.DynamoDB.DocumentClient(options)
  return dynamoDbClient
}

module.exports = {
  connect: () => dynamoDbClient || makeClient()
}

重要的部分如下:

if(process.env.AWS_SAM_LOCAL) {
  options.endpoint = 'http://dynamodb:8000'
}

从 SAM 本地 Docker 容器的上下文中,可以通过其名称访问 DynamoDB 容器

我的两个启动命令最终为:

docker run -d -v "$PWD":/dynamodb_local_db -p 8000:8000 --network lambda-local --name dynamodb cnadiminti/dynamodb-local

AWS_REGION=eu-west-2 sam local start-api --docker-network lambda-local

唯一的变化是给 DynamoDB 容器命名。


11
对于任何被困惑的人,需要先创建 lambda-local 网络:docker network create lambda-local。请注意,翻译不改变原意,同时力求通俗易懂。 - divillysausages
有人能解释一下为什么sam本地容器无法通过默认桥接与dynamodb容器通信吗? - Matteo
但是...现在你无法与dynamodb shell进行交互。 - maljukan

34

6
这解决了我在Mac上的问题。 - Samuel James
1
我曾经苦恼了很长时间,因为我在本地 DynamoDB 端点上使用的是 "http://localhost:8000" 而不是 "http://docker.for.mac.localhost:8000"。这真是救命稻草。 - Anil_M

15

其他答案对我来说过于复杂/不清晰。以下是我的解决方案。

步骤1:使用docker-compose在自定义网络上运行DynamoDB本地实例

docker-compose.yml

注意网络名称为abp-sam-backend,服务名称为dynamo,并且dynamo服务正在使用backend网络。

version: '3.5'

services:
  dynamo:
    container_name: abp-sam-nestjs-dynamodb
    image: amazon/dynamodb-local
    networks:
      - backend
    ports:
      - '8000:8000'
    volumes:
      - dynamodata:/home/dynamodblocal
    working_dir: /home/dynamodblocal
    command: '-jar DynamoDBLocal.jar -sharedDb -dbPath .'

networks:
  backend:
    name: abp-sam-backend

volumes:
  dynamodata: {}

通过以下方式启动本地DynamoDB容器:

docker-compose up -d dynamo

步骤2:编写处理本地DynamoDB端点的代码

import { DynamoDB, Endpoint } from 'aws-sdk';

const ddb = new DynamoDB({ apiVersion: '2012-08-10' });

if (process.env['AWS_SAM_LOCAL']) {
  ddb.endpoint = new Endpoint('http://dynamo:8000');
} else if ('local' == process.env['APP_STAGE']) {
  // Use this when running code directly via node. Much faster iterations than using sam local
  ddb.endpoint = new Endpoint('http://localhost:8000');
}

注意,我使用的是主机名别名dynamo。这个别名是由Docker在abp-sam-backend网络中自动创建的。别名名称就是服务名称。

步骤3:通过sam local启动代码

sam local start-api -t sam-template.yml --docker-network abp-sam-backend --skip-pull-image --profile default --parameter-overrides 'ParameterKey=StageName,ParameterValue=local ParameterKey=DDBTableName,ParameterValue=local-SingleTable' 
注意,我正在告诉sam local使用已在我的docker-compose.yml中定义的现有网络abp-sam-backend

端到端示例

我制作了一个可工作的示例(以及一堆其他功能),可以在https://github.com/rynop/abp-sam-nestjs找到。


8

SAM 在幕后启动一个 Docker 容器 lambci/lambda,如果你有另一个托管 dynamodb 或其他任何你想连接 lambda 的服务的容器,那么这两个容器应该在同一网络中。

假设 dynamodb(注意 --name,这是现在的端点)

docker run -d -p 8000:8000 --name DynamoDBEndpoint amazon/dynamodb-local

这将导致类似于这样的结果。
0e35b1c90cf0....

为了知道此网络是在哪个网络中创建的:
docker inspect 0e35b1c90cf0

它应该会给你类似这样的东西。
...
Networks: {
     "services_default": {//this is the <<myNetworkName>>

....

如果你熟悉网络并想将Docker容器放入特定的网络中,你可以使用--network选项在启动容器时执行以上步骤并进行一次命令操作。

docker run -d -p 8000:8000 --network myNetworkName --name DynamoDBEndpoint amazon/dynamodb-local

重要提示:您的 Lambda 代码现在应该具有到 Dynamo 的端点,即DynamoDBEndpoint

例如:

if(process.env.AWS_SAM_LOCAL) {
  options.endpoint = 'http://DynamoDBEndpoint:8000'
}

测试所有内容:

使用lambci:lambda

这只应列出其他DynamoDB容器中的所有表格。

docker run -ti --rm --network myNetworkName lambci/lambda:build-go1.x \
   aws configure set aws_access_key_id "xxx" && \
   aws configure set aws_secret_access_key "yyy" &&  \
   aws --endpoint-url=http://DynamoDBEndpoint:4569 --region=us-east-1 dynamodb list-tables

或者调用一个函数:(Go示例,与NodeJS相同)

#Golang
docker run --rm -v "$PWD":/var/task lambci/lambda:go1.x handlerName '{"some": "event"}'
#Same for NodeJS 
docker run --rm -v "$PWD":/var/task lambci/lambda:nodejs10.x index.handler

关于lambci/lambda的更多信息可以在这里找到。

使用SAM(使用相同的容器lambci/lambda):

sam local invoke --event myEventData.json --docker-network myNetworkName MyFuncName

如果您想查看更多详细信息,可以随时使用--debug选项。

或者,您也可以使用http://host.docker.internal:8000,而无需处理docker的麻烦。此URL在内部保留,并使您可以访问主机,但请确保在启动dynamodb容器时公开端口8000。虽然这很容易,但并不适用于所有操作系统。有关此功能的更多详细信息,请参阅docker文档


1
我遇到了一个像host.docker.internal'这样的错误。该服务可能在`us-east-1'不可用。我正在运行Docker Toolbox。这个会工作吗? - S A
http://host.docker.internal:8000 doesn't work, even with docker run -p 8000:8000 .... - Mark A

2

正如@Paul所提到的那样,这涉及到在Docker容器之间配置网络——Lambda和数据库。

另一种对我有用的方法是使用docker-compose。

docker-compose:

version: '2.1'

services:
  db:
    image: ...
    ports:
      - "3306:3306"
    networks:
      - my_network
    environment:
      ...
    volumes:
      ...

networks:
  my_network:

然后,在执行docker-compose up之后,运行docker network ls将会显示:

NETWORK ID          NAME                        DRIVER              SCOPE
7eb440d5c0e6        dev_my_network              bridge              local

我的 Docker 容器名字是 dev_db_1

我的 JS 代码如下:

const connection = mysql.createConnection({
    host: "dev_db_1",
    port: 3306,
    ...
});

接下来,运行sam命令:

sam local invoke --docker-network dev_my_network -e my.json

技术栈:

  • Docker:18.03.1-ce
  • Docker-compose:1.21.1
  • MacOS HighSierra 10.13.6

能否在docker-compose中启动sam local容器(即使用start-api命令)?而不是通过手动命令从CLI分别启动。 - Kwhitejr
如@Kwhitejr在他的博客(https://pauldambra.github.io/2018/02/serverless-3.html)中所提到的,您需要使用docker-network参数运行命令,例如sam local start-api --docker-network lambda-local。 - Software Engineer

1
如果您正在使用LocalStack运行DynamoDB,则我认为在SAM中使用LocalStack网络的正确命令是:
sam local start-api --env-vars env.json --docker-network localstack_default

在您的代码中,LocalStack的主机名应为localstack_localstack_1

const dynamoDbDocumentClient = new AWS.DynamoDB.DocumentClient({
  endpoint: process.env.AWS_SAM_LOCAL ?
    'http://localstack_localstack_1:4569' :
    undefined,
});

然而,我是使用docker-compose up启动LocalStack的。使用pip CLI工具启动LocalStack可能会导致不同的标识符。


0
这可能对仍然遇到相同问题的人有所帮助:
最近我也遇到了同样的问题。我按照rynop提到的所有步骤进行操作(感谢 @rynop)。
我通过将端点(http://localhost:8000)替换为我的(私有)IP地址(即http://192.168.8.101:8000)来解决该问题(在我的Windows上) 在以下代码中:
import { DynamoDB, Endpoint } from 'aws-sdk';

const ddb = new DynamoDB({ apiVersion: '2012-08-10' });

if (process.env['AWS_SAM_LOCAL']) {
  ddb.endpoint = new Endpoint('http://dynamo:8000');
} else if ('local' == process.env['APP_STAGE']) {
  // Use this when running code directly via node. Much faster iterations than using sam local
  ddb.endpoint = new Endpoint('http://localhost:8000');
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接