AWS ECS 上的 PostgreSQL:psycopg2.OperationalError 端口号 5432 无效
PostgreSQL on AWS ECS: psycopg2.OperationalError invalid port number 5432
我在 AWS ECS 上通过 psycopg2 连接数据库时遇到问题。
我有一个 App 容器和一个 DB 容器。容器已链接。
该应用程序有一个入口点脚本,用于在启动应用程序服务器之前检查数据库是否已启动。
$ 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.
时,这很好用
如有任何提示,我们将不胜感激。谢谢!
#!/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
所以给它多一点背景。该应用程序是用 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
,因为我没有明确设置 DB_PORT
ENV,没有看到这样做的理由。
出于想法,我在 AWS ECS 任务定义中明确设置了 DB_PORT
ENV 并且......令人惊讶的是,它起作用了!无论出于何种原因(当明确设置时,可能它被传递为 str
而不是 int
)。
我通过从任务配置中添加/删除 ENV var 定义来确认它 2 次。
我在 Rails 上遇到了与 Ruby 相同的问题。我有几乎相同的数据库配置,我也为应用程序和数据库使用了两个 linked 容器(不是直接,而是通过 Gitlab CI;在引擎盖下它创建容器和 link是他们)。虽然我的环境变量有不同的名称:POSTGRES_HOST
、POSTGRES_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 library, a part of PostgreSQL. Let's take a look at it to see how we're getting this error. There is a file fe-connect.c
which contains functions that parse connection options. And here's the relevant code 的包装器(来自 PostgreSQL 10,这是我使用的版本):
/* Figure out the port number we're going to use. */
if (ch->port == NULL || ch->port[0] == '[=10=]')
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->port
是NULL
或者一个空字符串,这意味着没有端口提供连接选项,那么让我们使用DEF_PGPORT
,预编译的默认端口,通常是5432 ;如果 ch->port
存在,让我们用 atoi
将其转换为 int 并检查它是否在 1 和 65535 之间。
如果ch->port
是"tcp://172.17.0.3:5432"
,atoi(ch->port)
returns 0,它小于1,所以这就是我们得到这个错误的原因。
顺便说一句,在最近的 PostgreSQL 版本中,人们会得到一个信息量更大的错误:invalid integer value "tcp://172.17.0.3:5432" for keyword "port"
。那是因为 this commit 用自定义错误检查字符串转换函数替换了上面的 atoi
好的,此 URI 出现在 libpq 连接选项中的端口号位置。但它是如何到达那里的呢?结果是,因为 Docker.
Docker 容器可以有名称,可以自动生成或通过 run
命令的 --name
选项提供。当您使用 --link
选项来 link 两个容器时,您指定另一个容器的名称和可选的别名。默认情况下,别名与名称相同。可能,你的数据库容器有一个 name/alias db
,我的被命名为 postgres
(Gitlab 默认情况下用它的图像名称命名一个容器,在我的例子中:postgres)。 =43=]
当您 link 容器时,Docker defines a bunch of environment variables,这些变量是基于容器 names/aliases 命名的。其中一个变量是 <alias>_PORT
,它包含容器暴露端口的 URI。不仅仅是端口号,还有完整的 URI(就像您从 docker port <alias>
命令中获得的 URI)。这是你从哪里得到 "tcp://172.17.0.3:5432"
,它是由 Docker 写入到 DB_PORT
变量,因为你的数据库容器恰好被命名为 db
.
毕竟,可能的解决方案是:
- 在容器 Docker link 之后重新定义
DB_PORT
变量(正如您所做的那样),
- 重命名配置中的
DB_PORT
变量,
- 为数据库容器设置另一个别名。
我在 AWS ECS 上通过 psycopg2 连接数据库时遇到问题。 我有一个 App 容器和一个 DB 容器。容器已链接。
该应用程序有一个入口点脚本,用于在启动应用程序服务器之前检查数据库是否已启动。
$ 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.
时,这很好用如有任何提示,我们将不胜感激。谢谢!
#!/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
所以给它多一点背景。该应用程序是用 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
,因为我没有明确设置 DB_PORT
ENV,没有看到这样做的理由。
出于想法,我在 AWS ECS 任务定义中明确设置了 DB_PORT
ENV 并且......令人惊讶的是,它起作用了!无论出于何种原因(当明确设置时,可能它被传递为 str
而不是 int
)。
我通过从任务配置中添加/删除 ENV var 定义来确认它 2 次。
我在 Rails 上遇到了与 Ruby 相同的问题。我有几乎相同的数据库配置,我也为应用程序和数据库使用了两个 linked 容器(不是直接,而是通过 Gitlab CI;在引擎盖下它创建容器和 link是他们)。虽然我的环境变量有不同的名称:POSTGRES_HOST
、POSTGRES_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 library, a part of PostgreSQL. Let's take a look at it to see how we're getting this error. There is a file fe-connect.c
which contains functions that parse connection options. And here's the relevant code 的包装器(来自 PostgreSQL 10,这是我使用的版本):
/* Figure out the port number we're going to use. */
if (ch->port == NULL || ch->port[0] == '[=10=]')
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->port
是NULL
或者一个空字符串,这意味着没有端口提供连接选项,那么让我们使用DEF_PGPORT
,预编译的默认端口,通常是5432 ;如果 ch->port
存在,让我们用 atoi
将其转换为 int 并检查它是否在 1 和 65535 之间。
如果ch->port
是"tcp://172.17.0.3:5432"
,atoi(ch->port)
returns 0,它小于1,所以这就是我们得到这个错误的原因。
顺便说一句,在最近的 PostgreSQL 版本中,人们会得到一个信息量更大的错误:invalid integer value "tcp://172.17.0.3:5432" for keyword "port"
。那是因为 this commit 用自定义错误检查字符串转换函数替换了上面的 atoi
好的,此 URI 出现在 libpq 连接选项中的端口号位置。但它是如何到达那里的呢?结果是,因为 Docker.
Docker 容器可以有名称,可以自动生成或通过 run
命令的 --name
选项提供。当您使用 --link
选项来 link 两个容器时,您指定另一个容器的名称和可选的别名。默认情况下,别名与名称相同。可能,你的数据库容器有一个 name/alias db
,我的被命名为 postgres
(Gitlab 默认情况下用它的图像名称命名一个容器,在我的例子中:postgres)。 =43=]
当您 link 容器时,Docker defines a bunch of environment variables,这些变量是基于容器 names/aliases 命名的。其中一个变量是 <alias>_PORT
,它包含容器暴露端口的 URI。不仅仅是端口号,还有完整的 URI(就像您从 docker port <alias>
命令中获得的 URI)。这是你从哪里得到 "tcp://172.17.0.3:5432"
,它是由 Docker 写入到 DB_PORT
变量,因为你的数据库容器恰好被命名为 db
.
毕竟,可能的解决方案是:
- 在容器 Docker link 之后重新定义
DB_PORT
变量(正如您所做的那样), - 重命名配置中的
DB_PORT
变量, - 为数据库容器设置另一个别名。