我在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在本地运行时,这可以正常工作。
任何提示都将不胜感激。谢谢!
答案 0 :(得分:2)
所以给它一点背景。该应用程序是用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尝试打开连接时,它使用了此5432
调用的默认值os.getenv('DB_PORT', 5432)
,因为我没有明确设置DB_PORT
ENV,没有看到原因这样做。
缺乏想法我已经在AWS ECS任务定义中明确设置了DB_PORT
ENV,并且......出乎意料,它有效!无论出于何种原因(明确设置时,可能会以str
而不是int
传递。)
我通过在任务配置中添加/删除ENV var定义来确认它2次。
答案 1 :(得分:1)
我在Ruby on Rails上遇到了同样的问题。我几乎有相同的数据库配置,我也使用了两个链接的容器分别用于应用程序和数据库(不是直接通过Gitlab CI,而是通过Gitlab CI;它在引擎盖下创建容器并将其链接)。我的环境变量虽然有不同的名称: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库(PostgreSQL的一部分)的包装。让我们看看它如何如何出现此错误。有一个文件fe-connect.c
,其中包含解析连接选项的函数。这是relevant code(来自PostgreSQL 10,这是我使用的版本):
/* Figure out the port number we're going to use. */
if (ch->port == NULL || ch->port[0] == '\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->port
是NULL
或一个空字符串,则表示没有为端口提供连接选项,那么让我们使用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"
。这是因为this commit用自定义的错误检查字符串转换函数替换了上面的atoi
。
好的,此URI代替libpq连接选项中的端口号出现。但是如何到达那里?事实证明,由于Docker。
Docker容器可以具有自动生成的名称,或具有--name
命令的run
选项提供的名称。使用--link
选项链接两个容器时,您可以指定另一个容器的名称以及一个别名(可选)。默认情况下,别名与名称相同。可能您的数据库容器的名称/别名为db
,我的数据库容器的名称为postgres
(在我的情况下,Gitlab默认使用其图像名称命名一个容器:postgres)。
当您链接容器Docker defines a bunch of environment variables时,这些变量是根据容器名称/别名来命名的。变量之一是<alias>_PORT
,它包含容器公开端口的URI。不仅是端口号,还有一个完整的URI(例如,您可以从docker port <alias>
命令获得的URI)。从这里获得"tcp://172.17.0.3:5432"
的地方,它是由Docker写入DB_PORT
变量的,因为您的数据库容器恰好被命名为db
。
毕竟,可能的解决方案是:
DB_PORT
变量,DB_PORT
变量,答案 2 :(得分:0)
#!/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