如何从 docker 容器中的 python 脚本连接到 localhost 上的 mysql 数据库

Posted

技术标签:

【中文标题】如何从 docker 容器中的 python 脚本连接到 localhost 上的 mysql 数据库【英文标题】:How to connect to a mysql database on localhost from a python script in a docker container 【发布时间】:2019-05-31 13:17:27 【问题描述】:

我有一个在 localhost (ubuntu 16.04) 上运行的 mysql 数据库。在同一台主机上,我有一个运行 python 脚本的 docker 容器。此脚本必须连接到本地主机上的 mysqldb。正如这些帖子(post1,post2)中所述,我为我的本地数据库设置了 bind-address=0.0.0.0 并找到了我的本地主机的 ip-address 并在我的 python 脚本中使用它来连接到数据库,但它没有用。下面我展示了我的设置以及我如何运行 docker 容器。 我的 python 脚本(analysis.py)如下所示:

import pandas as pd
import sqlalchemy as db

def find_max_age():
   cnx = db.create_engine('mysql+mysqlconnector://root:password@172.17.0.1:3306/datasets')
   cnx_res = db.create_engine('mysql+mysqlconnector://root:password@172.17.0.1:3306/results')
   df = pd.read_sql("select * from test_table", cnx)
   idx = df['age'].idxmax() == df.index
   df_res = df[idx]

   df_res.to_sql('max_age4', con=cnx_res, index=False)


if __name__ == '__main__':
   find_max_age()

我的 Dockerfile 如下所示:

FROM python:2.7-slim
EXPOSE 80 3306
WORKDIR /app
COPY requirements.txt /app
RUN pip install -r requirements.txt
COPY analysis.py /app
CMD python analysis.py

最后,requirements.txt 是这样的

mysql-connector-python
sqlalchemy
pandas

我构建docker镜像如下:

docker build -t max_age_app .

然后我使用这个镜像启动容器如下:

docker run -d max_age_app:latest

容器以退出代码 1 退出,当我查看容器的相应日志时,我发现其中出现以下错误:

> Traceback (most recent call last):
  File "analysis.py", line 24, in <module>
    find_max_age()
  File "analysis.py", line 11, in find_max_age
    df = pd.read_sql("select * from test_table", cnx)
  File "/usr/local/lib/python2.7/site-packages/pandas/io/sql.py", line 397, in read_sql
    chunksize=chunksize)
  File "/usr/local/lib/python2.7/site-packages/pandas/io/sql.py", line 1063, in read_query
    result = self.execute(*args)
  File "/usr/local/lib/python2.7/site-packages/pandas/io/sql.py", line 954, in execute
    return self.connectable.execute(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 2074, in execute
    connection = self.contextual_connect(close_with_result=True)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 2123, in contextual_connect
    self._wrap_pool_connect(self.pool.connect, None),
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 2162, in _wrap_pool_connect
    e, dialect, self)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1476, in _handle_dbapi_exception_noconnection
    exc_info
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/util/compat.py", line 265, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 2158, in _wrap_pool_connect
    return fn()
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool.py", line 400, in connect
    return _ConnectionFairy._checkout(self)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool.py", line 788, in _checkout
    fairy = _ConnectionRecord.checkout(pool)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool.py", line 529, in checkout
    rec = pool._do_get()
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool.py", line 1193, in _do_get
    self._dec_overflow()
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py", line 66, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool.py", line 1190, in _do_get
    return self._create_connection()
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool.py", line 347, in _create_connection
    return _ConnectionRecord(self)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool.py", line 474, in __init__
    self.__connect(first_connect_check=True)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/pool.py", line 671, in __connect
    connection = pool._invoke_creator(self)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/strategies.py", line 106, in connect
    return dialect.connect(*cargs, **cparams)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/default.py", line 412, in connect
    return self.dbapi.connect(*cargs, **cparams)
  File "/usr/local/lib/python2.7/site-packages/mysql/connector/__init__.py", line 172, in connect
    return CMySQLConnection(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/mysql/connector/connection_cext.py", line 78, in __init__
    self.connect(**kwargs)
  File "/usr/local/lib/python2.7/site-packages/mysql/connector/abstracts.py", line 731, in connect
    self._open_connection()
  File "/usr/local/lib/python2.7/site-packages/mysql/connector/connection_cext.py", line 179, in _open_connection
    sqlstate=exc.sqlstate)
sqlalchemy.exc.DatabaseError: (mysql.connector.errors.DatabaseError) 2003 (HY000): Can't connect to MySQL server on '172.17.0.1' (111) (Background on this error at: http://sqlalche.me/e/4xp6)

为了确定本地主机的 ip,我使用了ifconfig 命令,结果如下:

docker0   Link encap:Ethernet  HWaddr 02:42:a2:a6:d7:ff  
          inet addr:172.17.0.1 

enp0s3    Link encap:Ethernet  HWaddr 08:00:27:bb:7e:b5  
          inet addr:10.0.2.15 

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1 

所以我尝试了 172.17.0.1 以便从容器内连接到本地数据库,但它不起作用。

启动容器时是否必须通过-p 选项匹配容器和本地主机之间的任何端口?

如果有任何帮助,我将不胜感激。

【问题讨论】:

【参考方案1】:

为了解决这个问题,必须在 localhost 上正确配置 mysql 数据库。除了上面讨论的内容之外,还必须执行以下步骤:

使用 python 应用程序查找 docker 容器的 IP 地址。你 可以使用docker inspect &lt;container_name&gt;。 在本地 mysql 数据库中创建一个新用户(以及相应的 密码)用于该 IP 地址。有关如何操作的信息,您可以在 这个post。描述了如何在mysql中设置密码策略 here。

之后,只需使用命令docker run -d max_age_app 启动容器,python 脚本就足以将数据写入本地主机上的数据库。

【讨论】:

【参考方案2】:

你不应该 EXPOSE 容器上的端口 3306,因为 MySQL 服务器在 localhost 上侦听它之外。我怀疑这是网络问题,因此请尝试查看 localhost 是否具有地址为172.17.0.1 的接口,以及是否可以从容器内部访问此地址(例如,尝试docker exec -ti _your_container_name /bin/sh,然后尝试ping 172.17.0.1)。您还应该检查 mysql 日志,看看那里是否报告了一些错误。

【讨论】:

我从 Dokerfile 中删除了 EXPOSE 3306(我认为这是 mysql 的默认端口),但它并没有帮助。端口 80 是否必须暴露,这可能无关紧要?由于容器在我启动后几乎立即退出,我不得不使用 docker run -ti max_age_app /bin/bash 才能进入它。然后我 ping 了 ip 并且它工作了。为了确定我使用ifconfig的localhost上的ip,实际上还有另一个id-address,我也检查了它。它ping通了,但容器仍然无法建立连接。在 mysql 日志中,我只是旧错误 如果你尝试 netstat -nap |在 localhost 上 grep "3306" 你看到 mysqld 进程绑定了吗? 我看到以下输出(不确定是什么意思):sudo netstat -nap | grep "3306" tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN 19319/mysqld tcp 1 0 127.0.0.1:60474 127.0.0.1:3306 CLOSE_WAIT 22935/python 好像有联系。您是否有任何其他 python 进程连接到您的 mysqld 服务器? 我认为确实有连接,但是docker容器没有被授权访问数据库,所以每次容器中的python脚本尝试连接数据库时都拒绝访问。正如我在回答中描述的那样,问题现在已经解决。无论如何感谢您的提示!

以上是关于如何从 docker 容器中的 python 脚本连接到 localhost 上的 mysql 数据库的主要内容,如果未能解决你的问题,请参考以下文章

将参数传递给 Docker 容器中的 Python argparse

Python 脚本在 docker 容器中找不到使用 CRON 运行的 ENV 变量

无法从 docker 容器内部访问 datadog 代理

如何将 python 库从主机共享到多个 docker 容器?

如何从运行 Fargate ECS 任务中查看 Python 打印语句?

在 nginx docker 容器中运行 python 脚本