AWS ECS上的PostgreSQL:psycopg2.OperationalError无效端口号5432

时间:2017-03-23 12:33:22

标签: python django postgresql docker amazon-ecs

我在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在本地运行时,这可以正常工作。

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

3 个答案:

答案 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_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,其中包含解析连接选项的函数。这是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->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"。这是因为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

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

  • 在Docker链接容器之后(如您所愿)重新定义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