Не удается получить доступ к контейнеру postgres из контейнера приложения

#node.js #postgresql #docker #docker-compose

#node.js #postgresql #docker #docker-compose

Вопрос:

У меня есть 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 вывод службы:

NOTE: The first line is the output of the first console.log call from script.js .

NOTE: Since the server service is set up with restart: unless-stopped , it will repeat this output forever.

 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
...
  

From the host computer, I can reach the PostgreSQL database at the db service, connecting successfully, using the same script from the server service.

Вывод сценария, запущенного с главного компьютера:

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

Этот вывод означает, что соединение выполнено успешно.


В итоге:

Я не могу получить доступ к db контейнеру из server контейнера, получая тайм-ауты при подключении, но я могу получить доступ к db контейнеру с главного компьютера, успешно подключившись.


Соображения

Во-первых, спасибо за ответ до сих пор. Устранение некоторых поднятых вопросов:

  • Отсутствует сеть:

    Это не требуется, поскольку docker-compose имеет сеть по умолчанию. A протестировано с пользовательской сетью, но это тоже не сработало.

  • Порядок инициализации:

    Я использую depends_on , чтобы убедиться db , что контейнер запускается первым, но я знаю, что это не гарантирует, что база данных фактически инициализируется первой, а затем сервером. Это не проблема, потому что сервер прерывается, когда происходит тайм-аут, и он запускается снова, потому что он настроен restart: unless-stopped . Поэтому, если база данных все еще инициализируется при первой или второй попытке запуска сервера, проблем нет, потому что сервер будет продолжать перезапускаться до тех пор, пока соединение не завершится успешно (чего никогда не происходило).

  • Обновить:

    Из server контейнера я мог бы получить доступ к базе данных в db службе, используя psql . Я все еще не могу подключиться из Node.js приложение есть.

    Проблема DATABASE_URL не в этом, потому что URI, который я использовал в psql команде, — это тот же URI, который используется script.js и печатается при первом console.log вызове.

    Используется командная строка:

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

Редактировать: улучшен код за счет удаления зависимости для продолжения. Теперь он использует только pg и вызывает скрипт напрямую.

Комментарии:

1. я давно не использовал docker, но, возможно, попробуйте добавить: networks: — mynetwork На том же уровне, что и порт внутри служб сервера и базы данных

2. Мне не удалось воспроизвести вашу ошибку: я повторно использовал ваш файл docker-compose, заменил сервис app стандартным образом Ubuntu, выполнил docker-compose up и установил postgresql-client . Команда psql -h db -p 15432 -U postgres -W из контейнера приложения выполнена успешно! Я рекомендую вам посмотреть конфигурацию Sequelize, возможно, ошибка в строке подключения.

3. @NelsonG. @jeeves Я только что попробовал это, и я смог добраться до db контейнера, используя psql server контейнер. Теперь мне нужно выяснить, почему я не могу получить доступ из node.js использование приложения pg . Спасибо за помощь!

4. Можете ли вы поделиться своим файлом Dockerfile. Я взял ваш docker-compose и скрипт и не могу воспроизвести вашу проблему. Я использую node:10-alpine в качестве базового образа без других установленных библиотек, кроме pg

5. Ответ обновлен решением 🙂

Ответ №1:

Спасибо за предоставление источника для воспроизведения проблемы. Никаких проблем в docker-compose файле, как вы уже исключили.

Проблема заключается между вашей Dockerfile и node-pg используемой вами версией.

Вы используете node:14-alpine и pg: 7.18.2 . Оказывается, есть ошибка на узле 14 и более ранних версиях node-pg .

Решением является либо понижение версии до node v12, либо использование последней версии node-pg , которая в настоящее время является 8.4.2 (исправление было введено в версии 8.0.3).

Я проверил оба этих решения в предоставленной вами ветке, и они работают.

Комментарии:

1. Я добавил некоторые соображения в конце описания вопроса. Я также тестировал их запуск отдельно, как вы сказали, но проблема сохранялась.

2. Вот почему он работал на хост-компьютере, версия узла там 12. Спасибо!!!

Ответ №2:

Это не полный ответ; У меня нет вашего кода под рукой, поэтому я не могу протестировать файл compose. Тем не менее, есть несколько проблем, на которые я хотел бы указать:

  • links Директива устарела.

    Это links устаревшая опция, которая использовалась до того, как Docker представил определяемые пользователем сети и автоматическую поддержку DNS. Вы можете просто избавиться от него. Контейнеры в файле компоновки могут ссылаться друг на друга по имени без.

  • expose Директива ничего не делает. Это может быть информативно, например Dockerfile , как способ сказать: «это изображение предоставит сервис на этом порту», но на самом деле ничего не происходит. Это почти полностью бесполезно в файле компоновки.

  • depends_on Директива также менее полезна, чем вы думаете. Это действительно приведет к тому, что docker-compose сначала откроет контейнер базы данных, но контейнер считается «запущенным», как только запустится первый процесс. Это не заставляет docker-compose ждать, пока база данных будет готова к обслуживанию запросов, что означает, что вы все равно столкнетесь с ошибками, если ваше приложение попытается подключиться до того, как база данных будет готова.

    Лучшее решение для этого — встроить логику повторного подключения к базе данных в ваше приложение, чтобы, если база данных когда-либо выйдет из строя (например, вы перезапускаете контейнер postgres для активации новой конфигурации или обновления версии postgres), приложение будет повторять попытки подключения до тех пор, пока оно не будет успешным.

    Приемлемым решением является включение кода в запуск вашего приложения, который блокируется до тех пор, пока база данных не ответит на запросы.

Комментарии:

1. В конце описания вопроса я добавил некоторые соображения относительно порядка инициализации. Я провел тест: я запустил db контейнер и убедился, что база данных правильно инициализирована, получив доступ к ней с главного компьютера, затем я запустил server контейнер, но получил тот же результат: тайм-аут.

2. Я удалю link и expose , спасибо. Что касается этого, можно с уверенностью сказать, что порт был доступен, потому что я мог сделать это с главного компьютера.

Ответ №3:

Проблема не имеет ничего общего с 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:
  

Комментарии:

1. Я сделал «минимальную» реализацию своего проекта ( здесь ), но ваша идея лучше как минимальный проект. Я попробую это и вернусь с любыми отзывами. Спасибо!

2. Хорошо, я создал настоящий минимальный проект, используя только pg, и у меня все еще есть проблема, она здесь, в ветке minimal . :'(