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

Posted

技术标签:

【中文标题】AWS ECS 上的 PostgreSQL:psycopg2.OperationalError 无效端口号 5432【英文标题】:PostgreSQL on AWS ECS: psycopg2.OperationalError invalid port number 5432 【发布时间】:2017-08-16 00:22:11 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

我在使用 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,其中包含解析连接选项的函数。这是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-&gt;portNULL 或空字符串,则表示没有提供连接选项的端口,那么让我们使用DEF_PGPORT,预编译的默认端口,通常为5432;如果ch-&gt;port 存在,让我们用atoi 将其转换为int 并检查它是否在1 到65535 之间。

如果ch-&gt;port"tcp://172.17.0.3:5432"atoi(ch-&gt;port) 返回 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 选项链接两个容器时,您可以指定另一个容器的名称和可选的别名。默认情况下,别名与名称相同。可能,您的数据库容器有一个名称/别名db,我的名为postgres(Gitlab 默认使用其图像名称命名一个容器,在我的情况下为:postgres)。

当您链接容器时,Docker defines a bunch of environment variables,这些变量是根据容器名称/别名命名的。其中一个变量是&lt;alias&gt;_PORT,它包含容器公开端口的URI。不仅仅是端口号,而是一个完整的 URI(就像你从 docker port &lt;alias&gt; 命令获得的那个)。这里是你得到"tcp://172.17.0.3:5432"的地方,它是由Docker写入DB_PORT变量的,因为你的数据库容器恰好被命名为db

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

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

【讨论】:

你深入这个兔子洞。感人的!非常感谢您的解释。不幸的是,我不能再在这个项目中检查它了。 我刚刚遇到了同样的问题,GitLab CI 服务和重叠的环境变量。这些是一些非常意想不到的副作用。 Docker 真的不应该像那样垃圾环境变量。【参考方案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 尝试打开连接时,它使用来自此 os.getenv('DB_PORT', 5432) 调用的默认值 5432,因为我没有明确设置 DB_PORT ENV,没有看到这样做的理由。

没有想法,我在 AWS ECS 任务定义中明确设置了 DB_PORT ENV 并且......令人惊讶的是,它起作用了!无论出于何种原因(当显式设置时,它可能以str 而不是int 的形式传递)。

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

【讨论】:

【参考方案3】:
#!/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

【讨论】:

以上是关于AWS ECS 上的 PostgreSQL:psycopg2.OperationalError 无效端口号 5432的主要内容,如果未能解决你的问题,请参考以下文章

在 AWS ECS 上的服务 Auto Scaling 中注册可扩展目标时出错

AWS ECS Fargate 上的 Jenkins 工作人员:并行运行几个作业

ECS 与 EFS 或 EBS 上的持久数据与 CloudFormation

ubuntu 上的 Amazon ECS 代理未启动

如何在节点 AWS 开发工具包代码中使用 AWS ECS 任务角色

使用 AWS ECS 容器计算成本