PostgreSQL on AWS ECS: psycopg2.操作错误无效端口号 5432



我在 AWS ECS 上通过 psycopg2 进行数据库连接时遇到问题。 我有一个应用程序容器和一个数据库容器。容器是链接的。

该应用程序具有一个入口点脚本,用于在启动应用程序服务器之前检查数据库是否已启动。

$ until psql -h "$DB_HOST" -U "$DB_USER" -c '' && >&2 echo "Postgres is up"; do
>&2 echo "Postgres is unavailable - sleeping"
sleep 1
done
> Is the server running on host "db" (172.17.0.3) and accepting
> TCP/IP connections on port 5432?
> Postgres is unavailable - sleeping
> Postgres is up

这部分工作正常,但是一旦应用程序服务器启动并尝试连接到数据库,我就会收到以下错误:

psycopg2.OperationalError: invalid port number: "tcp://172.17.0.3:5432"

我不知道会是什么情况。当使用 Docker 在本地运行时,这工作正常。

任何提示将不胜感激。谢谢!

我在Ruby on Rails上遇到了同样的问题。我有几乎相同的数据库配置,我也为应用程序和数据库使用了两个链接容器(不是直接的,而是通过 Gitlab CI;在后台它创建容器并链接它们)。我的环境变量有不同的名称:POSTGRES_HOSTPOSTGRES_PORT等。然而,你明确定义POSTGRES_PORT的解决方案也对我有用!但我不能就这样离开,我想弄清楚为什么这有帮助,以及首先是什么导致了问题。这就是我的发现。

错误说:invalid port number: "tcp://172.17.0.3:5432"。起初,它看起来像一个有效的端口 5432,但实际上它是整个字符串"tcp://172.17.0.3:5432"不是有效的端口号。有些东西将这个URI而不是端口号传递给PostgreSQL,这就是错误所说的。你通过psycopg连接,我用了pg gem,但它们都是libpq C库的包装器,它是PostgreSQL的一部分。让我们看一下它,看看我们是如何得到这个错误的。有一个文件fe-connect.c其中包含解析连接选项的函数。这是相关的代码(来自PostgreSQL 10,这是我使用的版本):

/* Figure out the port number we're going to use. */
if (ch->port == NULL || ch->port[0] == '')
thisport = DEF_PGPORT;
else
{
thisport = atoi(ch->port);
if (thisport < 1 || thisport > 65535)
{
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid port number: "%s"n"),
ch->port);
goto keep_going;
}
}

它说:如果ch->portNULL或空字符串,则表示没有提供连接选项的端口,那么让我们使用预编译的默认端口DEF_PGPORT,通常是5432;如果存在ch->port,让我们用atoi将其转换为int,并检查它是否在1到65535之间。

如果ch->port"tcp://172.17.0.3:5432"atoi(ch->port)返回 0,它小于 1,所以这就是我们得到这个错误的方式。

顺便说一下,在最近的PostgreSQL版本中,人们会得到一个信息量更大的错误:invalid integer value "tcp://172.17.0.3:5432" for keyword "port"。这是因为此提交将上述atoi替换为自定义错误检查字符串转换函数。

好的,此 URI 代替 libpq 连接选项中的端口号出现。但它是如何到达那里的呢?原来,是因为Docker。

Docker 容器可以具有自动生成的名称,也可以是随run命令--name选项提供的名称。使用--link选项链接两个容器时,可以指定另一个容器的名称和别名(可选)。默认情况下,别名与名称相同。可能,您的数据库容器有一个名称/别名db,我的被命名为postgres(Gitlab 默认使用其映像名称命名容器,在我的例子中:postgres)。

当你链接容器时,Docker 定义了一堆环境变量,这些变量是根据容器名称/别名命名的。其中一个变量是<alias>_PORT,它包含容器公开端口的 URI。不仅仅是端口号,而是完整的 URI(就像您从命令中获得docker port <alias>的 URI)。这是你从哪里得到"tcp://172.17.0.3:5432",它是由Docker写入DB_PORT变量的,因为你的数据库容器恰好被命名为db

毕竟,可能的解决方案是:

  • 在 Docker 链接容器后重新定义DB_PORT变量(就像你所做的那样),
  • 重命名配置中的DB_PORT变量,
  • 为 DB 容器设置另一个别名。

所以给它更多的背景。该应用程序是用 Django 编写的,这里是数据库配置部分:

DATABASES = {
'default': {
# Requests will be wrapped in a transaction automatically
# https://docs.djangoproject.com/en/1.10/topics/db/transactions/#tying-transactions-to-http-requests
'ATOMIC_REQUESTS': True,
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': os.getenv('DB_NAME', 'postgres'),
'USER': os.getenv('DB_USER', 'postgres'),
'PASSWORD': os.getenv('DB_PASSWORD', 'secret'),
'HOST': os.getenv('DB_HOST', 'localhost'),
'PORT': os.getenv('DB_PORT', 5432),
'OPTIONS': {
'client_encoding': 'UTF8'
}
}
}

入口脚本中的psql命令使用默认5432端口连接良好。

现在,当 Django 尝试打开连接时,它使用了此os.getenv('DB_PORT', 5432)调用中的默认值5432,因为我没有明确设置 ENVDB_PORT,没有看到这样做的理由。

出于想法,我已经在 AWS ECS 任务定义中明确设置了DB_PORTENV,并且......惊喜,它奏效了!无论出于何种原因(也许它在显式设置时作为str传递而不是int传递)。

我通过从任务配置中添加/删除 ENV var 定义来确认 2 次。

#!/bin/bash
set -e
cmd="$@"
if [ -z "$POSTGRES_USER" ]; then
export POSTGRES_USER=postgres
fi
export DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres:5432/$POSTGRES_USER

function postgres_ready(){
python << END
import sys
import psycopg2
try:
conn = psycopg2.connect(dbname="$POSTGRES_USER", user="$POSTGRES_USER", password="$POSTGRES_PASSWORD", host="postgres")
except psycopg2.OperationalError:
sys.exit(-1)
sys.exit(0)
END
}
until postgres_ready; do
>&2 echo "Postgres is unavailable - sleeping"
sleep 1
done
>&2 echo "Postgres is up - continuing..."
exec $cmd

最新更新