docker 误朕,Joomla 发送邮件超时失败,原因竟是 docker 默认不支持 IPv6

更新日期: 2024-07-03 阅读次数: 1475 字数: 1567 分类: docker

前几天迁移 Joomla 服务器到半夜 11 点多,最后发现发送邮件失败,总是报超时。太困实在扛不住了,而且第二天要上线公司内部的 CRM 微信小程序,老板要体验,不敢怠慢,于是决定速速睡觉,缓缓再排查邮件发送失败的问题。今天,CRM 小程序终于上线了,赶紧定位了一下邮件问题。

Joomla 是何物

Joomla 是一个开源的内容管理系统(CMS),用于构建网站和在线应用程序。 适用于构建各种类型的网站,包括企业网站、电子商务平台、小型企业网站、社区门户网站、学校网站,以及个人主页。类似于 Wordpress,也是由世界上最好的语言 PHP 开发。

问题现象

提交数据后,默认会将提交信息发送到指定邮箱,但是在提交时,出现 504 超时错误。导致提交失败。

报错信息

即便在 configuration.php 文件中启用了 debug 模式,也看不到具体的错误日志。可能是前面套了层 cloudflare 的原因。只在 Nginx error 日志文件中找到了简陋的错误信息。

upstream timed out (110: Connection timed out) while reading response header from upstream, client: x.x.x.x, server: www.xxx.com, request: "POST /administrator/index.php?option=com_config&task=application.sendtestmail&format=json HTTP/1.1", upstream: "fastcgi://unix:/var/xxx/services/fpm_sock_data/phpfpm.sock", host: "www.xxx.com", referrer: "https://www.xxx.com/administrator/index.php?option=com_config"

基本上没有什么帮助。

Joomla 错误日志在哪里

administrator/logs

但是没有有价值的日志。

相关配置

唯一的线索在 config 文件中,可以看到使用的是 sendmail 客户端。

# grep mail configuration.php
public $feed_email = 'none';                                               public $mailer = 'smtp';
public $mailfrom = 'test@xxx.com';
public $mailonline = true;
public $massmailoff = false;
public $sendmail = '/usr/sbin/sendmail';
public $smtphost = 'test.xxx.com';

# grep smtp configuration.php
public $mailer = 'smtp';
public $smtpauth = true;
public $smtphost = 'test.xxx.com';
public $smtppass = 'xxx';
public $smtpport = 465;
public $smtpsecure = 'ssl';
public $smtpuser = 'test@xxx.com';

排查思路

  • sendmail 自己的错误日志 (排除)
  • 通过 sendmail 命令行来查询问题(排除)
  • PHP docker 的 SMTP 配置是否有问题(排除)
  • sendmail service 是否起来(排除)
  • sendmail requires editing the /etc/hosts file(排除)
  • 目前定位到的问题是,在 docker 中无法 telnet smtp 服务的 465 端口,而宿主机中可以

sendmail 是什么

Sendmail 是一个负责处理邮件传输的软件包,而 SMTP 是用于在邮件服务器之间传输邮件的协议。Sendmail 使用 SMTP 协议与其他邮件服务器进行通信,实现邮件的接收和发送。

由于 PHP 是安装在一个独立的 docker 容器中(PHP docker 镜像,默认是一个 Debian 12 的系统),所以进入容器看看,sendmail 是不是有配置问题。

docker compose exec phpfpm /bin/bash

where is sendmail:

# which sendmail
/usr/sbin/sendmail

执行一下看看:

# sendmail
Exim is a Mail Transfer Agent. It is normally called by Mail User Agents,
not directly from a shell command line. Options and/or arguments control
what it does when called. For a list of options, see the Exim documentation.

Exim 与 sendmail 的关系

# ls -lah /usr/sbin/sendmail
/usr/sbin/sendmail -> exim4

可以看到,sendmail 指向了 exim4。而 Exim 是作为 Sendmail 的替代品。 相对 Sendmail, Exim 配置简单,且更加安全。

telnet 寻到蛛丝马迹

做了一下对比测试,实际上折腾了半天,多亏 solos 大师的指点:

在宿主机上测试:

  • telnet smtp 服务器 465 端口,正常
  • telnet google.com 80, 正常
  • telnet smtp.gmail.com 465,正常

但是在 docker 容器内:

  • telnet smtp 服务器 465 端口,超时错误
  • telnet google.com 80, 正常
  • telnet smtp.gmail.com 465,正常。说明并不是 docker 防火墙的问题,而且 docker 也没开防火墙。

但是,有一点奇怪的地方,被我这名侦探犀利的眼睛瞥见:

docker 容器内:

# telnet test.xxx.com 465

Trying x.x.x.x ...
Connection failed: Connection timed out
Trying 240e:f7:4019:5f:0:10:13:237...
telnet: Unable to connect to remote host: Network is unreachable

而在宿主机上:

# telnet test.xxx.com 465
Trying 240e:f7:4019:5f:0:10:13:237...
Connected to test.xxx.com.
Escape character is '^]'.

对比可见,虽然 docker 容器内 telnet 失败了,但是其先尝试了 IPv4 的地址,超时失败后,又尝试了一次 IPv6 的地址,又失败了。而宿主机则是直接请求 IPv6 地址,一次成功。

于是疑问来了,在宿主机上直接 telnet IPv4 465 会怎样???Holy shit,同样超时。。。

也就是说,只要是 IPv4,目标 smtp 服务器就拒绝连接,这个 smtp 服务器只支持 IPv6 访问。

让 docker 支持 IPv6

Google 了一下,原来 Docker 安装完毕后默认是不支持 IPv6 方式监听和访问的。

  • https://docs.docker.com/config/daemon/ipv6/ docker 官方的狗屎文档,光看这个啥问题也解决不了
  • https://fariszr.com/docker-ipv6-setup-with-propagation/#migrating-existing-containers--docker-compose-projects 相对官方文档更详细一点,关键是说明了 docker compose 文件如何设置 IPv6,以及如何操作
  • https://plusnan.me/2024/02/01/docker-ipv6/ 这个文章说明了如何查看本机 IPv6, 及如何设置 docker IPv6 的 IP 段

结合这三个文档,才终于配置好了 docker 的 IPv6。

failed to create network xxx_default: Error response from daemon: could not find an available, non-overlapping IPv6 address pool among the defaults to assign to the network

如果不设置 daemon.json 中的 default-address-pools 会报上面错误。

使 docker IPv6 配置生效

If you are using Compose, first delete all containers and their networks (this should’t delete volumes):

> docker compose down
> docker compose up -d

最后

我对 docker 其实并没有什么好感,除了部署 python,php 这些最好的脚本语言确实方便,但是后续维护起来,坑非常得多。有非常多的反直觉,反经验的使用方法,一不留神就会踩坑。而且 docker 官方文档,确实不太行,很多细节没有说明白。网上的资料也不全,能把解决方法说明白的少之又少。而 docker 的学习成本并不低,简单的使用确实能轻松上手,但是遇到生产环境的复杂问题,对我这种服务器业余运维人员来说,如临大敌。我觉得投入 docker 的学习时间完全不值得,有这个时间,多写写代码是多么快乐的事情。

微信关注我哦 👍

大象工具微信公众号

我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊, 查看更多联系方式

tags: joomla