无法从应用容器访问 postgres 容器
Posted
技术标签:
【中文标题】无法从应用容器访问 postgres 容器【英文标题】:Can't reach postgres container from app container 【发布时间】:2021-02-18 14:42:22 【问题描述】:我有一个 docker-composer.yml 正在设置两个服务:server
和 db
。 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:
编辑:上面的代码更改为删除 links
和 expose
。
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 我刚刚尝试过,我可以使用psql
从server
容器到达db
容器。现在我需要找出为什么我无法使用 pg
从 node.js 应用程序访问。感谢您的帮助!
你能分享你的Dockerfile吗?我已经使用了您的 docker-compose 和脚本,但无法重现您的问题。我使用node:10-alpine
作为基础映像,除了pg
之外没有安装其他库
答案已更新解决方案:)
【参考方案1】:
感谢您提供重现问题的来源。
docker-compose
文件中没有您已经排除的问题。
问题在于您的Dockerfile
和您正在使用的node-pg
版本之间。
您正在使用node:14-alpine
和pg: 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-client
和apt 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
容器,但我得到了相同的输出:超时。
我将删除link
和expose
,谢谢。关于这一点,可以肯定地说该端口是可访问的,因为我可以从主机上完成。以上是关于无法从应用容器访问 postgres 容器的主要内容,如果未能解决你的问题,请参考以下文章
无法从 docker-compose 启动 postgres docker 容器
通过容器端Node.js Web应用程序访问容器端Postgres数据库