无法从应用容器访问 postgres 容器

Posted

技术标签:

【中文标题】无法从应用容器访问 postgres 容器【英文标题】:Can't reach postgres container from app container 【发布时间】:2021-02-18 14:42:22 【问题描述】:

我有一个 docker-composer.yml 正在设置两个服务:serverdb。 Node.js服务器,即server服务,使用pg连接PostgreSQL数据库;而db 服务是一个 PostgreSQL 镜像。

在服务器启动时,它尝试连接到数据库但超时。


docker-compose.yml

version: '3.8'

services:
  server:
    image: myapi
    build: .
    container_name: server
    env_file: .env
    environment:
      - PORT=80
      - DATABASE_URL=postgres://postgres:postgres@db:15432/mydb
      - REDIS_URL=redis://redis
    ports:
      - 3000:80
    depends_on:
      - db
    command: node script.js
    restart: unless-stopped

  db:
    image: postgres
    container_name: db
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: mydb
    ports:
      - 15432:15432
    volumes:
      - db-data:/var/lib/postgresql/data
    command: -p 15432
    restart: unless-stopped

volumes:
  db-data:

编辑:上面的代码更改为删除 linksexpose


db服务输出:

db        | 
db        | PostgreSQL Database directory appears to contain a database; Skipping initialization
db        | 
db        | 2020-11-05 20:18:15.865 UTC [1] LOG:  starting PostgreSQL 13.0 (Debian 13.0-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
db        | 2020-11-05 20:18:15.865 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 15432
db        | 2020-11-05 20:18:15.865 UTC [1] LOG:  listening on IPv6 address "::", port 15432
db        | 2020-11-05 20:18:15.873 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.15432"
db        | 2020-11-05 20:18:15.880 UTC [25] LOG:  database system was shut down at 2020-11-05 20:18:12 UTC
db        | 2020-11-05 20:18:15.884 UTC [1] LOG:  database system is ready to accept connections

script.js - 由来自server 服务的命令使用。

const pg = require('pg');

console.log(process.env.DATABASE_URL);

const pool = new pg.Pool(
  connectionString: process.env.DATABASE_URL,
  connectionTimeoutMillis: 5000,
);
pool.connect((err, _, done) => 
  if (err) 
    console.error(err);
    done(err);
  
  done();
);
pool.query('SELECT NOW()', (err, res) => 
  console.log(err, res);
  pool.end();
);

const client = new pg.Client(
  connectionString: process.env.DATABASE_URL,
  connectionTimeoutMillis: 5000,
);
client.connect(console.error);
client.query('SELECT NOW()', (err, res) => 
  console.log(err, res);
  client.end();
);

server服务输出:

注意:第一行是来自script.js 的第一个console.log 调用的输出。

注意:由于server 服务是使用restart: unless-stopped 设置的,因此它将永远重复此输出。

server    | postgres://postgres:postgres@db:15432/mydb
server    | Error: Connection terminated due to connection timeout
server    |     at Connection.<anonymous> (/home/node/app/node_modules/pg/lib/client.js:255:9)
server    |     at Object.onceWrapper (events.js:421:28)
server    |     at Connection.emit (events.js:315:20)
server    |     at Socket.<anonymous> (/home/node/app/node_modules/pg/lib/connection.js:78:10)
server    |     at Socket.emit (events.js:315:20)
server    |     at emitCloseNT (net.js:1659:8)
server    |     at processTicksAndRejections (internal/process/task_queues.js:79:21)
server    |     at runNextTicks (internal/process/task_queues.js:62:3)
server    |     at listOnTimeout (internal/timers.js:523:9)
server    |     at processTimers (internal/timers.js:497:7)
server    | Error: Connection terminated due to connection timeout
server    |     at Connection.<anonymous> (/home/node/app/node_modules/pg/lib/client.js:255:9)
server    |     at Object.onceWrapper (events.js:421:28)
server    |     at Connection.emit (events.js:315:20)
server    |     at Socket.<anonymous> (/home/node/app/node_modules/pg/lib/connection.js:78:10)
server    |     at Socket.emit (events.js:315:20)
server    |     at emitCloseNT (net.js:1659:8)
server    |     at processTicksAndRejections (internal/process/task_queues.js:79:21)
server    |     at runNextTicks (internal/process/task_queues.js:62:3)
server    |     at listOnTimeout (internal/timers.js:523:9)
server    |     at processTimers (internal/timers.js:497:7) undefined
server    | Error: timeout expired
server    |     at Timeout._onTimeout (/home/node/app/node_modules/pg/lib/client.js:95:26)
server    |     at listOnTimeout (internal/timers.js:554:17)
server    |     at processTimers (internal/timers.js:497:7)
server    | Error: Connection terminated unexpectedly
server    |     at Connection.<anonymous> (/home/node/app/node_modules/pg/lib/client.js:255:9)
server    |     at Object.onceWrapper (events.js:421:28)
server    |     at Connection.emit (events.js:315:20)
server    |     at Socket.<anonymous> (/home/node/app/node_modules/pg/lib/connection.js:78:10)
server    |     at Socket.emit (events.js:315:20)
server    |     at emitCloseNT (net.js:1659:8)
server    |     at processTicksAndRejections (internal/process/task_queues.js:79:21) undefined
server    | postgres://postgres:postgres@db:15432/mydb
...

从主机,我可以通过db 服务访问PostgreSQL 数据库,连接成功,使用来自server 服务的相同脚本。

从主机运行的脚本的输出:

➜  node script.js
postgres://postgres:postgres@localhost:15432/mydb
null Client  ... 
undefined Result  ... 
null Result  ... 

这个输出表示连接成功。


总结:

我无法从server 容器访问db 容器,连接超时,但我可以从主机访问db 容器,连接成功。


注意事项

首先,感谢您到目前为止的回答。解决提出的一些问题:

缺少网络:

这不是必需的,因为 docker-compose 有一个默认网络。 A 使用自定义网络进行了测试,但它也不起作用。

初始化顺序:

我使用depends_on 来确保首先启动db 容器,但我知道它并不能确保实际上先初始化数据库然后再初始化服务器。这不是问题,因为发生超时时服务器会中断并且它会再次运行,因为它是使用restart: unless-stopped 设置的。因此,如果数据库在第一次或第二次尝试启动服务器时仍在初始化,则没有问题,因为服务器将继续重新启动,直到连接成功(从未发生过)。

更新:

server 容器,我可以使用psql 访问db 服务上的数据库。我仍然无法从那里的 Node.js 应用程序连接。

DATABASE_URL 不是问题,因为我在 psql 命令中使用的 URI 与 script.js 使用的 URI 相同,并由第一个 console.log 调用打印出来。

使用的命令行:

docker exec -it server psql postgres://postgres:postgres@db:15432/mydb

编辑:通过删除对 Sequelize 的依赖来改进代码。现在它只使用pg,直接调用脚本。

【问题讨论】:

好久没用docker了,不妨试试添加:networks: - mynetwork 和server里面的port和db services同级 我未能重现您的错误:我重用了您的 docker-compose 文件,将服务 app 替换为标准 ubuntu 映像,执行了 docker-compose up 并安装了 postgresql-client。来自应用容器的命令psql -h db -p 15432 -U postgres -W 成功!建议你看下Sequelize的配置,可能是连接字符串有错误。 @NelsonG。 @jeeves 我刚刚尝试过,我可以使用psqlserver 容器到达db 容器。现在我需要找出为什么我无法使用 pg 从 node.js 应用程序访问。感谢您的帮助! 你能分享你的Dockerfile吗?我已经使用了您的 docker-compose 和脚本,但无法重现您的问题。我使用node:10-alpine 作为基础映像,除了pg 之外没有安装其他库 答案已更新解决方案:) 【参考方案1】:

感谢您提供重现问题的来源。 docker-compose 文件中没有您已经排除的问题。

问题在于您的Dockerfile 和您正在使用的node-pg 版本之间。

您正在使用node:14-alpinepg: 7.18.2。 原来在节点 14 和早期版本的 node-pg 上有一个 bug。

解决方案是降级到节点 v12使用最新版本的 node-pg,目前为 8.4.2(修复已在 v8.0.3 上进行)。

我已经在您提供的分支上验证了这两种解决方案并且它们都有效。

【讨论】:

我在问题描述的末尾添加了一些注意事项。我也按照你说的分别测试了启动它们,但问题仍然存在。 这就是它在主机上工作的原因,那里的节点版本是 12。谢谢!!!【参考方案2】:

问题与docker无关。要对此进行测试,请执行以下操作:

通过使用这个 docker-compose.yml 文件:

version: '3.8'

services:
  app:
    image: ubuntu
    container_name: app
    command: sleep 8h

  db:
    image: postgres
    container_name: db
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: mydb
    expose:
      - '15432'
    ports:
      - 15432:15432
    volumes:
      - db-data:/var/lib/postgresql/data
    command: -p 15432
    restart: unless-stopped

volumes:
  db-data:

执行docker exec -it app bash 进入容器应用程序,然后安装postgresql-clientapt install -y postgresql-client`。

命令psql -h db -p 15432 -U postgres -W 成功!

检查pg配置

你说pg 使用环境变量DATABASE_URL 来访问postgresql。我不确定:

从https://node-postgres.com/features/connecting,我们可以找到这个例子:

$ PGUSER=dbuser \
  PGHOST=database.server.com \
  PGPASSWORD=secretpassword \
  PGDATABASE=mydb \
  PGPORT=3211 \
  node script.js

还有这句话:

node-postgres 使用与 libpq 相同的环境变量来连接 PostgreSQL 服务器。

libpq 文档中,没有DATABASE_URL

要将 pg 文档中提供的示例与您的 docker-compose.yml 文件相适应,请尝试使用以下文件(我只更改了应用服务的环境变量):

version: '3.8'

services:
  server:
    image: myapi
    build: .
    container_name: server
    env_file: .env
    environment:
      - PORT=80
      - PGUSER=postgres
      - PGPASSWORD=postgres
      - PGHOST=db
      - PGDATABASE=mydb
      - PGPORT=15432
      - REDIS_URL=redis://redis
    ports:
      - 3000:80
    depends_on:
      - db
    command: node script.js
    restart: unless-stopped

  db:
    image: postgres
    container_name: db
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: mydb
    ports:
      - 15432:15432
    volumes:
      - db-data:/var/lib/postgresql/data
    command: -p 15432
    restart: unless-stopped

volumes:
  db-data:

【讨论】:

我对我的项目(here)做了一个“最小”的实现,但你的想法作为最小的项目更好。我会试试这个并返回任何反馈。谢谢! 好的,我只使用 pg 创建了真正的最小项目,但我仍然有问题,它是 here at branch minimal。 :'(【参考方案3】:

这不是一个完整的答案;我手头没有你的代码,所以我实际上无法测试撰写文件。但是,我想指出一些问题:

links 指令已弃用。

links 是在 Docker 引入用户定义的网络和自动 DNS 支持之前使用的旧选项。你可以摆脱它。 compose 文件中的容器可以通过名称相互引用,而不需要。

expose 指令什么都不做。它可以在例如Dockerfile 中提供信息,作为一种说法,“这个图像将在这个端口上公开一个服务”,但它实际上并没有使任何事情发生。在撰写文件中几乎完全没用。

depends_on 指令也没有你想象的那么有用。它确实会导致 docker-compose 首先启动数据库容器,但是一旦第一个进程启动,容器就被认为是“启动”的。它不会导致 docker-compose 等待数据库准备好为请求提供服务,这意味着如果您的应用程序在数据库准备好之前尝试连接,您仍然会遇到错误。

最好的解决方案是在您的应用程序中构建数据库重新连接逻辑,以便如果数据库出现故障(例如,您重新启动 postgres 容器以激活新配置或升级 postgres 版本),应用程序将重试连接直到成功。

一个可接受的解决方案是在您的应用程序启动中包含代码,该代码会阻塞直到数据库响应请求。

【讨论】:

我在问题描述的末尾添加了一些关于初始化顺序的注意事项。我做了一个测试:我启动了db 容器,并通过从主机访问它来确保数据库已正确初始化,然后我启动了server 容器,但我得到了相同的输出:超时。 我将删除linkexpose,谢谢。关于这一点,可以肯定地说该端口是可访问的,因为我可以从主机上完成。

以上是关于无法从应用容器访问 postgres 容器的主要内容,如果未能解决你的问题,请参考以下文章

无法从另一个容器连接到 Postgres 容器

如何从容器内部访问在主机上运行的数据库?

无法从 docker-compose 启动 postgres docker 容器

通过容器端Node.js Web应用程序访问容器端Postgres数据库

无法使用 docker 连接到 ipv6 中的 postgres

无法从运行 iOS 10 测试版的 iPad 访问应用程序容器