让容器中的服务访问主机的数据库服务

Posted on 2023-03-27,4 min read

最近正在将服务器中的各个web应用迁移到为容器中运行(都23年了才...)

一开始跑了仅包括Nginx的镜像访问很顺利,继续测试一个用Nginx启动的PHP应用后发现容器中的应用无法读取到主机的数据库,折腾了几天后才解决,现在将这次的要点记录下来:

  1. localhost在容器中代表的是容器自己的ip,不是宿主机的ip,所以容器中的数据库连接配置文件不是localhost和3306
  2. docker常用的有两种网络类型:
    • bridge
    • host

默认是bridge,开始不清楚bridge的原理然后用这种类型怎么也无法连通DB,于是用--net=host启动,但这样因为没有和宿主机(后文简称host)的网络隔离而容易和host中已运行的app端口冲突,没有较好地利用到容器的隔离性(当然,在某些场景下需要用host)
3. 网上的说容器中的DB地址应该是host的本地ip,然而我查出来host的本地ip不是192.168这种而是本机的公网ip,用公网ip仍然无法访问
4. 划重点:bridge模式下,(Linux)docker网络会分配一个172.17.0.0/16的网段,其网关通常为172.17.0.1,所以容器中的app应该通过这个ip去访问host的服务,但是database默认只监听host的本地请求(即127.0.0.1),所以database的绑定地址要改为0.0.0.0,让数据库服务监听所有的网络连接请求,而不仅仅是本地连接请求。
所以,修改DB的配置文件(我的是mariadb)

vim /etc/mysql/mariadb.conf.d/50-server.cnf

在[mysql]中添加:

5. 再以如下参数启动容器,该参数会在容器中添加一个指向host的ip的域名,另外数据库参数(如果image提供输入)的DB host就用这个域名

--add-host=host.docker.internal:host-gateway

进入容器,可以看到解析出来就是前文所说的172.17.0.1

6. 在容器中安装mysql-client测试一下连通性,我用的php应用的镜像是Alpine构建的,所以

apk add mysql-client

host参数(这里指database所在的target host)直接用172.17.0.1,成功了

7. 在公网也能访问,截图略,因为数据库能连接必然成功

其他要点(假设app的域名是app.mao.com,容器运行在host的6875端口):

  1. PHP应用的App_URL参数(即app的.env配置文件)也要注明所使用的host的端口号,否则CSS和JS资源文件仍然会走80端口,例:https://app.mao.com:6875
  2. 因为要在app中登录,因此必须给站点启用证书。我的host的服务器使用的是Caddy,在配置文件中添加app的域名后会自动创建该域名的证书:
ls /etc/ssl/caddy/acme/acme-v02.api.letsencrypt.org/sites/

然后用docker cp复制crt和key到容器中即可

docker cp /etc/ssl/caddy/acme/acme-v02.api.letsencrypt.org/sites/app.mao.com/xxx.crt mycontainer:/etc/ssl/mykeys/thecert.crt
docker cp /etc/ssl/caddy/acme/acme-v02.api.letsencrypt.org/sites/app.mao.com/xxx.key mycontainer:/etc/ssl/mykeys/thekey.key
  1. 对于容器中的配置文件修改似乎是永久生效的,里面的配置不会随容器的删除而重置,但是复制进去的文件会被清理,关于这一点在学习下容器的原理
  2. 目前离完美还差一步,用https://app.mao.com:6875非标准端口访问https站点不好看,所以要反向代理本地6875端口的服务(即app服务)从而让网址在用户端更美观,但是我的caddy是1.0.4版好像太老了没自带reverse_proxy模块,只能用redir转发https://app.mao.com到https://app.mao.com:6875

下一篇: Dynamics 365 CE 调试插件(plug-in)→