【发布时间】:2022-01-07 14:46:52
【问题描述】:
我尝试从 Python 运行时中的 Google Cloud Function 连接到非 Google Cloud 托管的外部 MySQL 服务器数据库。
我的“requirements.txt”:
# Function dependencies, for example:
# package>=version
SQLAlchemy>=1.4.2
PyMySQL==1.0.2
云函数核心代码:
from os import environ
import sqlalchemy
db_user = environ["DB_USER"]
db_pass = environ["DB_PASS"]
db_name = environ["DB_NAME"]
db_host = environ["DB_HOST"]
db_port = environ["DB_PORT"] # if not used, default 3306 anyway
db_address = f"""mysql+pymysql://{db_user}:{db_pass}@{db_host}/{db_name}?charset=utf8&use_unicode=1"""
db_engine = sqlalchemy.create_engine(db_address)
当我在“测试”选项卡中测试云功能时,按Test the function,我收到错误:
for termination reason. Additional troubleshooting documentation can be found at https://cloud.google.com/functions/docs/troubleshooting#logging Details: 500 Internal Server Error: The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application. ```
在日志中:
或作为可搜索文本:
Debug2022-01-07T14:36:29.221892880ZMYCLOUDFUNCTIONdvaw7xewhjqj 函数执行开始 默认2022-01-07T14:36:29.529ZMYCLOUDFUNCTIONdvaw7xewhjqj OpenBLAS 警告 - 无法确定 L2 缓存大小 系统,假设 256k 调试2022-01-07T14:36:30.964665907ZMYCLOUDFUNCTIONdvaw7xewhjqj 函数执行耗时 1743 毫秒,完成状态码:200 调试2022-01-07T14:36:50.088620704ZMYCLOUDFUNCTIONdvawxkmbid1w 功能执行开始 功能执行开始 默认2022-01-07T14:36:50.340ZMYCLOUDFUNCTIONdvawxkmbid1w 2022-01-07 14:36:50,267 [错误]:异常 / [POST] 2022-01-07 14:36:50,267 [错误]:/ [POST] 上的异常 错误 2022-01-07T14:36:50.341ZMYCLOUDFUNCTIONdvawxkmbid1w Traceback(最近一次通话最后一次):文件 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 2447 行,在 wsgi_app response = self.full_dispatch_request() 文件中 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 1952 行,在 full_dispatch_request rv = self.handle_user_exception(e) 文件 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 1821 行,在 handle_user_exception reraise(exc_type, exc_value, tb) 文件 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/_compat.py", 第 39 行,在 reraise raise value 文件中 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 1950 行,在 full_dispatch_request rv = self.dispatch_request() 文件中 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 1936 行,在 dispatch_request 返回 self.view_functionsrule.endpoint 文件 "/layers/google.python.pip/pip/lib/python3.9/site-packages/functions_framework/init.py", 第 99 行,在 view_func 返回函数(request._get_current_object()) 文件“/workspace/main.py”,第 139 行,在 get_csv_in_tmp_and_move_to_gs 引擎 = sqlalchemy.create_engine(db_address) 文件“”,第 2 行, 在 create_engine 文件中 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/util/deprecations.py”, 第 309 行,在警告 Traceback 中(最近一次调用最后一次):文件 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 2447 行,在 wsgi_app response = self.full_dispatch_request() 文件中 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 1952 行,在 full_dispatch_request rv = self.handle_user_exception(e) 文件 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 1821 行,在 handle_user_exception reraise(exc_type, exc_value, tb) 文件 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/_compat.py", 第 39 行,在 reraise raise value 文件中 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 1950 行,在 full_dispatch_request rv = self.dispatch_request() 文件中 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 1936 行,在 dispatch_request 返回 self.view_functionsrule.endpoint 文件 "/layers/google.python.pip/pip/lib/python3.9/site-packages/functions_framework/init.py", 第 99 行,在 view_func 返回函数(request._get_current_object()) 文件“/workspace/main.py”,第 139 行,在 get_csv_in_tmp_and_move_to_gs 引擎 = sqlalchemy.create_engine(db_address) 文件“”,第 2 行, 在 create_engine 文件中 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/util/deprecations.py”, 第 309 行,在警告中 Default2022-01-07T14:36:50.341ZMYCLOUDFUNCTIONdvawxkmbid1w 返回 fn(*args, **kwargs) 返回 fn(*args, **kwargs) Default2022-01-07T14:36:50.341ZMYCLOUDFUNCTIONdvawxkmbid1w 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/engine/create.py”, 第 560 行,在 create_engine 文件中 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/engine/create.py”, 第 560 行,在 create_engine Default2022-01-07T14:36:50.341ZMYCLOUDFUNCTIONdvawxkmbid1w dbapi = dialect_cls.dbapi(**dbapi_args) dbapi = dialect_cls.dbapi(**dbapi_args) Default2022-01-07T14:36:50.341ZMYCLOUDFUNCTIONdvawxkmbid1w 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/dialects/mysql/mysqldb.py”, 第 163 行,在 dbapi 文件中 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/dialects/mysql/mysqldb.py”, 第 163 行,在 dbapi 中 Default2022-01-07T14:36:50.341ZMYCLOUDFUNCTIONdvawxkmbid1w return import("MySQLdb") return import("MySQLdb") 调试2022-01-07T14:36:50.342294068ZMYCLOUDFUNCTIONdvawxkmbid1w 函数执行耗时 254 毫秒,完成状态为:'crash' 函数 执行耗时 254 毫秒,完成状态:'crash' ```
当我对连接生命周期使用 with 语句时,我得到的内容略有不同,但问题是一样的,它无法连接到数据库:
Traceback(最近一次调用最后一次):文件 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 2447 行,在 wsgi_app response = self.full_dispatch_request() 文件中 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 1952 行,在 full_dispatch_request rv = self.handle_user_exception(e) 文件 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 1821 行,在 handle_user_exception reraise(exc_type, exc_value, tb) 文件 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/_compat.py", 第 39 行,在 reraise raise value 文件中 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 1950 行,在 full_dispatch_request rv = self.dispatch_request() 文件中 "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", 第 1936 行,在 dispatch_request 返回 self.view_functionsrule.endpoint 文件 "/layers/google.python.pip/pip/lib/python3.9/site-packages/functions_framework/init.py", 第 99 行,在 view_func 返回函数(request._get_current_object()) 文件“/workspace/main.py”,第 177 行,在 get_csv_in_tmp_and_move_to_gs 连接 = engine.connect() 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/engine/base.py”, 第 3204 行,在连接中返回 self._connection_cls(self, close_with_result=close_with_result) 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/engine/base.py”, 第 96 行,在 init else engine.raw_connection() 文件中 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/engine/base.py”, 第 3283 行,在 raw_connection 返回 self._wrap_pool_connect(self.pool.connect, _connection) 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/engine/base.py”, 第 3253 行,在 _wrap_pool_connect 中 Connection.handle_dbapi_exception_noconnection( 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/engine/base.py”, 第 2100 行,在 handle_dbapi_exception_noconnection util.raise( 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/util/compat.py”, 第 207 行,在引发引发异常文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/engine/base.py”, 第 3250 行,在 _wrap_pool_connect 返回 fn() 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/pool/base.py”, 第 310 行,在连接中返回 _ConnectionFairy._checkout(self) 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/pool/base.py”, 第 868 行,在 _checkout 仙女 = _ConnectionRecord.checkout(pool) 文件中 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/pool/base.py”, 第 476 行,在结帐 rec = pool._do_get() 文件中 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/pool/impl.py”, 第 146 行,在 do_get self.dec_overflow() 文件中 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/util/langhelpers.py”, 第 70 行,在 exit compat.raise( 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/util/compat.py”, 第 207 行,在引发引发异常文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/pool/impl.py”, 第 143 行,在 _do_get 返回 self._create_connection() 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/pool/base.py”, 第 256 行,在 _create_connection 返回 _ConnectionRecord(self) 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/pool/base.py”, 第 371 行,在 init self.__connect() 文件中 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/pool/base.py”, 第 666 行,在 connect pool.logger.debug("Error on connect(): %s", e) 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/util/langhelpers.py”, 第 70 行,在 exit compat.raise( 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/util/compat.py”, 第 207 行,在引发引发异常文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/pool/base.py”, 第 661 行,在 __connect self.dbapi_connection = connection = pool._invoke_creator(self) 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/engine/create.py”, 第 590 行,在连接中返回 dialect.connect(*cargs, **cparams) 文件 “/layers/google.python.pip/pip/lib/python3.9/site-packages/sqlalchemy/engine/default.py”, 第 597 行,在连接中返回 self.dbapi.connect(*cargs, **cparams) 文件 "/layers/google.python.pip/pip/lib/python3.9/site-packages/pymysql/connections.py", 第 353 行,在 init self.connect() 文件中 "/layers/google.python.pip/pip/lib/python3.9/site-packages/pymysql/connections.py", 第 664 行,在连接中引发 exc sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2003, "无法连接到 MySQL 服务器 on 'MY_SERVER_ADDRESS'(超时)")默认 2022-01-09T23:03:25.322Z MYCLOUDFUNCTIONearoz5s8jozu(此错误的背景位于: https://sqlalche.me/e/14/e3q8) (此错误的背景: https://sqlalche.me/e/14/e3q8)
我在本地机器和本地 Docker 容器中测试了相同的代码和设置,两者都可以使用相同的连接字符串 (db_url) 进行连接。
问题
如何获得与非 Google MySQL 服务器的连接?
或许还有一些路要走
想法
这只是权限问题吗,例如,我应该为我的用户角色添加一些权限吗?
想法
或者可能是 Google Cloud Functions 不允许访问 Google 世界之外的服务器?
想法
如果只有 Cloud SQL 服务器被接受为 Cloud Functions 中的源(可能是这种情况,因为我只找到了这个案例的指南,比如来自 Google 的官方指南:Connecting from Cloud Functions to Cloud SQL 或这个Example of how to use MySQL in a Google Cloud Function),我能以某种方式将外部服务器设为 Google 接受的 Cloud SQL 服务器,而无需将其实际上传到那里,或者是否有其他解决方法?
想法
connection example from Cloud Functions to Google Cloud SQL (MySQL) 首先要求您激活 Cloud SQL API,然后在连接字符串中使用套接字:
# Remember - storing secrets in plaintext is potentially unsafe. Consider using
# something like https://cloud.google.com/secret-manager/docs/overview to help keep
# secrets secret.
db_user = os.environ["DB_USER"]
db_pass = os.environ["DB_PASS"]
db_name = os.environ["DB_NAME"]
db_socket_dir = os.environ.get("DB_SOCKET_DIR", "/cloudsql")
instance_connection_name = os.environ["INSTANCE_CONNECTION_NAME"]
pool = sqlalchemy.create_engine(
# Equivalent URL:
# mysql+pymysql://<db_user>:<db_pass>@/<db_name>?unix_socket=<socket_path>/<cloud_sql_instance_name>
sqlalchemy.engine.url.URL.create(
drivername="mysql+pymysql",
username=db_user, # e.g. "my-database-user"
password=db_pass, # e.g. "my-database-password"
database=db_name, # e.g. "my-database-name"
query={
"unix_socket": "{}/{}".format(
db_socket_dir, # e.g. "/cloudsql"
instance_connection_name) # i.e "<PROJECT-NAME>:<INSTANCE-REGION>:<INSTANCE-NAME>"
}
),
**db_config
)
如果没有此类 API 并且没有套接字来保护连接,SQL 查询是否可能无法触发到不是 Google Cloud SQL 服务器的 MySQL 服务器?
想法
或者我是否必须更改为 Google Cloud Run 才能有机会在那里运行我自己的容器?见Python flask app using google cloud functions。在 Cloud Functions 中似乎根本不推荐使用 csv 中的 SQL 查询结果导致的大文件,请参阅 Streaming binary data from Google Cloud Storage to Cloud Function 及其评论:
...我不建议使用 Cloud Functions 处理大文件 尽管。我宁愿推荐其他无服务器选项作为 App 引擎或云运行。大文件处理可能需要一段时间,并且 Cloud Functions 在一定时间后超时。
想法
下一条评论提到了 GCSFS(Python 模块),它可能不提供查询外部数据库:
我确实设法使用 GCFS 使某些东西正常工作,这使我能够 在云存储中打开文件并像在本地一样流式传输二进制文件。
解决方案有效
这只是为了通过添加 VPC 连接器来确认接受的答案是否有效:
然后需要将其附加到应该使用它的 Google Cloud 功能。在函数的“详细信息”选项卡下,您应该看到:
然后对不属于 Google Cloud 的服务器的查询应该可以工作。
【问题讨论】:
-
您的数据库托管在哪里?在 GCP 上?在项目/另一个项目上?别处?数据库有公网IP吗?
-
@guillaumeblaquiere 它不在 GCP 上。数据库位于本地网络中运行的服务器上,我可以从 Docker 环境中访问它。我使用远程连接查询此主机,使用服务器名称而不是其 IP。我不认为它的IP是公开的,因为到目前为止,mysql查询产生的csv文件被上传到一个URL只是被内部网络之外的另一个服务器下载,外部服务器没有办法直接连接到 mysql 数据库主机。这个想法是用 CF+GS 代替 Docker+URL 方法,就像原型一样。
-
如果无法访问数据库,如何解决?
-
@guillaumeblaquiere 好吧,我认为可以从 Cloud Function CF 访问数据库。如果不是,那也是一个答案,我没想到。谷歌似乎将可能的查询减少到只有
Cloud SQL(尽管我不确定)。至于最后需要 csv 的其他外部服务器:该服务器 可以 像之前的 URL 一样访问 Google Storage (GS)。它只是无法访问内部网络。因此,原型的想法,为该过程带来了更多的秩序,避免了本地 Docker 容器的维护。 -
没有魔法!
标签: python sqlalchemy google-cloud-functions database-connection pymysql