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

4

我有一个 docker-compose.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:

编辑:上面的代码更改以删除链接和暴露。


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();
});

服务器 服务输出:

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

注意:由于 服务器 服务设置为 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容器。


注意事项:

首先,感谢目前为止的答复。针对一些提出的问题:

  • Missing network:

    It isn't required because docker-compose has a default network. A tested with a custom network but it didn't work either.

  • Order of initialization:

    I'm using depends_on to ensure the db container is started first but I know it isn't ensuring the database is in fact initialized first then the server. It isn't the problem because the server breaks when a timeout happens and it runs again because it is set up with restart: unless-stopped. So if the database is still initializing on the first or second try to start the server, there is no problem because the server will continue to be restarted until it succeeds in the connection (which never happened.)

  • UPDATE:

    From the server container, I could reach the database at the db service using psql. I still can't connect from the Node.js app there.

    The DATABASE_URL isn't the problem because the URI I used in the psql command is the same URI used by the script.js and printed by the first console.log call there.

    Command-line used:

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

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


你的docker-compose文件看起来没问题,可能是缺少网络。你尝试使用默认的postgres端口了吗? - Nelson G.
1
我无法重现你的错误:我重用了你的docker-compose文件,将服务app替换为标准的ubuntu镜像,执行了docker-compose up并安装了postgresql-client。从应用程序容器中运行的命令psql -h db -p 15432 -U postgres -W成功了!我建议你查看Sequelize的配置,也许是连接字符串出错了。 - Nelson G.
1
@NelsonG. @jeeves 我刚试了一下,我可以从server容器中使用psql访问db容器。现在我需要找出为什么我无法使用pg从node.js应用程序访问它。感谢您的帮助! - Mateus Pires
1
你能分享一下你的Dockerfile吗?我已经使用了你的docker-compose和脚本,但是无法重现你的问题。我使用node:10-alpine作为基础镜像,除了pg之外没有安装其他库。 - jeeves
1
答案已更新,附上解决方案 :) - jeeves
显示剩余12条评论
3个回答

3

感谢您提供重现问题的源代码。

docker-compose文件没有问题,因为您已经排除了这部分的错误。

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

您正在使用node:14-alpinepg: 7.18.2。事实证明,在 node 14 和早期版本的 node-pg 中存在一个bug

解决方法要么是降级到 node v12要么使用最新版本的node-pg,目前最新版本是8.4.2(修复在v8.0.3中进行)。

我已经验证了您提供的分支上这两个解决方案,它们都可行。


1
我在问题描述的结尾添加了一些注意事项。我也按照你说的将它们分开测试,但问题仍然存在。 - Mateus Pires
1
这就是为什么它在主机上运行正常,因为那里的 Node 版本是 12。谢谢! - Mateus Pires

1

这不是一个完整的答案;我手头没有你的代码,所以无法测试compose文件。不过,我想指出几个问题:

  • links指令已过时。

    links是一项传统选项,在Docker引入用户定义网络和自动DNS支持之前使用。您可以直接将其删除。Compose文件中的容器可以通过名称相互引用,而无需使用links

  • expose指令不起作用。例如在Dockerfile中,它可以作为一种方式来说明“此镜像将在此端口上公开服务”,但实际上它并没有做任何事情。在Compose文件中几乎完全没有用处。

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

    解决此问题的最佳方法是将数据库重新连接逻辑构建到应用程序中,以便如果数据库关闭(例如,您重新启动postgres容器以激活新配置或升级postgres版本),应用程序将重试连接,直到成功为止。

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


我在问题描述的末尾添加了一些关于初始化顺序的注意事项。我进行了一项测试:我启动了db容器,并通过从主机计算机访问它来确保数据库正确初始化,然后我启动了server容器,但我得到了相同的输出:超时。 - Mateus Pires
我将删除“link”和“expose”,谢谢。关于这一点,可以放心地说端口是可达的,因为我可以从主机计算机上执行此操作。 - Mateus Pires

1
这个问题与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 进入容器 app,然后使用 apt install -y postgresql-client 安装 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文件,请尝试使用以下文件(我只更改了app服务的环境变量):

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:

我制作了一个“极简”版本的项目实现(在此处),但是你的想法更好,作为最小化项目。我会尝试一下,并随时反馈意见。谢谢! - Mateus Pires
好的,我只使用pg创建了真正的最小项目,但我仍然有问题,它在这里的minimal分支。:'( - Mateus Pires

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