如何从 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 数据库的主要内容,如果未能解决你的问题,请参考以下文章