为什么不要用yahoo等邮箱发送邮件列表

最近postfix-users邮件列表服务进行技术升级,引起欣然大波,众多人参与讨论了数天,回复邮件达到几百封。

它升级的主要地方在于,将发送给邮件列表的sender,改成了列表自身的地址(@postfix.org结尾的)。之前的sender就是独立的发送者本人,现在全部改成了postfix的官方域名,只是回复地址仍然是原始sender。

这是因为,在新的电子邮箱形势下,postfix不得不这样做。因为不这样做的话,很多发送者投递的信件,会被大量的recipients拒收,从而影响邮件列表的投递质量。这种拒收大多数是由DMARC引起的,下面予以描述。

比如常见的yahoo邮箱,它的DMARC设置是这样的(DNS里的txt记录):

v=DMARC1; p=reject; pct=100; rua=mailto:[email protected]; ruf=mailto:[email protected];

问题在于p=reject这个配置,只要yahoo发出去的邮件,在其他接受者那里DMARC校验失败,就会被拒信。这是yahoo自己设置的苛刻的DMARC规则,其他MTA会遵循这个规则。

而邮件列表转发的邮件,大多数会导致DMARC失败。这是因为:

  • 邮件列表在转发时,如果不更改信件头部的from:地址,那么接受方DMARC会校验这个from:地址的SPF记录。SPF与发送时的IP授权有关,如果列表服务器在发送时执行了SRS(地址重写),那么重写的sender地址,对于from:校验没有帮助。如果不执行SRS,那么SPF是天然失败的。
  • 邮件列表在转发时,很多列表服务会在信件主体里,加上自己的签名(比如列表信息、退订方式),这就破坏了原始信件的DKIM校验。

如上两个原因,导致邮件列表转发的信件,SPF和DKIM都失败。而DMARC是依赖于这两个条件的,于是DMARC也失败,导致被接受者MTA拒收。

所以,postfix官方没有办法,只好把发送给邮件列表的信件,不管是smtp session的sender地址,还是信件header里的from:地址,都改成postfix自己的地址,并且加上自己的DKIM签名。这样,这封信等于是postfix服务器自己重新发的,各项标准(SPF, DKIM)都使用自己的技术(不过目前没看到postfix.org配置DMARC),这样就不会被其他MTA拒收。

这个问题的原因还在于电子邮件技术升级带来的生态影响。现代邮件系统大多数配置了SPF, DKIM, DMARC等先进标准,以防止系统被滥用。然而,过于苛刻的DMARC配置,会让传统的邮件列表转发服务失效。

除了yahoo外,存在类似苛刻DMARC的还有mail.ru, AOL, zoho等厂商。而倾向于技术型的企业,比如Gmail, GMX, Fastmail,则不存在这类问题。比如gmail的DMARC设置:

v=DMARC1; p=none; sp=quarantine; rua=mailto:[email protected]

这里p=none的策略设置显然是安全且合理的,它可以放心投递到邮件列表。

综上所述,技术总是升级的,在朝着现代化、标准化发展的同时,也要考虑对周边生态的影响。过于激进的技术更新,在带来好处的同时,也有意想不到的弊端。作为开源邮件系统的主要贡献者,postfix官方升级了自己的系统,让yahoo这些邮箱也能发进来。但还有众多的旧列表系统没有升级,那么yahoo, aol, mail.ru这些系统,发进去的邮件就会遭遇退信,这恐怕是他们自己在设置DMARC时,也没有想到的。

业务生态对编程语言的影响

go语言是google推出的,google在此研发上倾尽全力,光是大牛就有Rob Pike, Ken Thompson等人。不过go在推出来的若干年,都不温不火,在商业生产领域乏人问津。

直到近年,随着云原生的兴起,golang才光彩照人,成为云原生领域的首选语言。这首先得益于docker是用go开发的,而docker成为容器领域的标准实现。随着docker的火热,google趁热打铁推出k8s,这个开源的容器调度系统面世后,给互联网产品开发和部署带来翻天覆地的变化。正是因为有了k8s,微服务才变得如此流行,产品的迭代速度,也就更加快速。

毫无疑问,k8s也是用go开发的。从此后,golang正式进入商业生产领域,提起云原生,以及DevOps(或者SRE),基本离不开go。如果你想找这方面工作,不会go语言,基本是没有前途的。

在云资源编排领域,还有个著名的工具terraform,也是用go写的,我自己就很喜欢它。它通过声明式编程,整合了对AWS, GCP, AZURE等云平台的资源管理。这个工具在国外相当流行,因为资源编排也是一种业务逻辑。比如某个业务,要用到几个web,几个db,多少storage,以及loadbalancer设计,这些本身就是业务逻辑。terraform生成的配置文件,可以很好的维护这些业务关系,并且支持版本迭代(更新、回滚)。在大规模部署里,Infra的声明,跟code一样重要,这也就是k8s, terraform这类工具如此流行的原因。

go还有一个重要开源产品是prometheus,一个基于时序数据库的监控工具,也是属于云原生领域。所以你看,云原生的产品生态,带动了golang这门语言的发展,让它真正步入商业生产领域,成为招聘领域的热门语言。

再看看python,近些年也有着类似经历。python历史很悠久,早些年也就用在系统管理领域,和部分web开发(django之类)。但系统管理领域,更主流的编程语言是perl,而web开发领域更受到一众语言的冲击,比如ruby, node。所以python情形也一度堪忧。

然而,这些年随着科学计算和AI的发展,python又老当益壮,重新焕发生机。这是因为一众的科学计算和AI库,都有python的实现,甚至成为主流的实现。比如Numpy, Scipy, Pandas, Matplot等历史悠久的机器学习和数据处理库。随着google在算法领域的发力,更有了tensorflow和keras这样优秀的机器学习/深度学习框架,它们都提供了python的实现。更多的python机器学习库可以参考这个链接

python成为机器学习的首选语言,倒不是它设计的多么好,实际上python在设计上广受吐槽,它的原始作者在google也混不下去跑路了。python主要受欢迎之处在于简单易用,对新手极度友好,而众多的算法专业人士并不想写c++这样复杂的代码。在此情形下,python就变得很受欢迎。所以,随着机器学习/深度学习的兴起,python焕发第二春,又变得炙手可热,成为职场的重要编程语言。

此外还可以参考的是scala语言,它的历史也很悠久,所谓的函数式编程语言。近些年随着大数据的热门,scala又重新被关注。在并行计算方面著名的框架Apache Spark使用scala编写,分布式消息队列Apache Kafka也使用了scala。我对scala本身也比较熟悉,写过一本关于scala与spark的开源书籍。这门语言的优势还在于函数式编程,保证了数据安全,以及AKKA这种分布式并发actor框架。除此外,scala的设计可谓一团糟,使用上极度不友好。然而,正是大数据的兴起,让scala近些年也重新走入业界的视野,有段时间还很热门。

除了上述语言外,还有wordpress对php的影响也是一个重要体现。没有wordpress,php估计也就over了。要知道,全世界有超过8亿的网站由wordpress驱动,占所有网站比率的43%。这么大基数的wordpress网站还在运行着,注定php开发者还有市场。

综上,可见业务生态对编程语言自身的发展,是极其重要的,它甚至决定了编程语言的生死。要是没有云原生,go不一定能起来。要是没有机器学习,python估计也就死了。要是没有大数据,scala永无前途。要是没有wordpress,php也over了。

要推动一门新语言发展,首先就要找到它的用武之地,能突出它的特别优势的地方。web领域就算了,各类语言前仆后继,神仙打架,php, java, node, ruby, python谁都能站住脚,但是都没有特色,随时被淘汰。而像大数据、云原生、区块链、web3这样的专业领域,一旦站稳脚,反而能形成垄断地位。

从这个角度看,rust也挺有前途的,因为它在blockchain领域,分布非常广,请见这个链接的统计。还是那句话,业务为王,没有业务生态,再优秀的工具也就只能自嗨,没有前途。

从helm看开源生态的包管理系统

作为程序员,平时面对太多的包管理系统。比如我在ubuntu下工作,涉及的基本包管理系统就有如下。

  • apt: 系统自带软件的包管理系统
  • snap: 跟apt类似,不过软件版本更为激进
  • pip3: 编程语言python的包管理系统
  • cpanm: 编程语言perl的包管理系统
  • rbenv: 编程语言ruby的包管理系统
  • sdkman: 编程语言scala的包管理系统

其他还有node, go, php等,都有类似的系统,它们无疑增加了使用的复杂度。

不过凡事都有好坏,在熟悉了各自生态的包管理系统后,后续工作的效率更高,也更为规范。

比如,在没使用apt之前,安装一个linux开源程序,都要下载源码,编译,再安装。这不但有一定工作量,还容易出错。每个人装的不一致,还带来不规范。

而有了apt,对于通用的软件,大家都用apt install一键搞定,既快又省事,环境还统一。

类似的,kubernetes也有自己的包管理系统,叫做helm。使用helm在k8s集群上安装软件,也是如此简单,比如安装mysql:

$ helm install repo mysql --generate-name

上述一个命令就搞定了。当然在此之前,要先配置好本地repo:

$ helm repo add bitnami https://charts.bitnami.com/bitnami

安装完后,如何访问mysql?可以启动另一个pod实例,在这个pod里访问mysql:

$ k run mysqlcient --rm -it --restart="Never" --image docker.io/bitnami/mysql:8.0.32-debian-11-r11 -- bash

$ mysql -h 10.244.0.39 -uroot -p

如此一来,使用helm在k8s上安装和使用mysql就变得十分简单。

作为对比,手工在k8s上安装mysql,当然也可以,只是步骤复杂, 要创建YAML文件,还要理解一些概念。安装过程可以参考如下博客。唯一要注意的是本文档使用5.6版本mysql,需要改成5.7,因为docker hub里目前最低就是5.7。

How to Deploy MySQL Statefulset in Kubernetes

除了常用的pod, service, deployment外,还需要理解本文涉及的其他概念,包括:

  • Statefulset: 跟deployment类似的概念,不过它是有状态的,比如mysql有本地存储,这就是状态相关的,在restart时不能丢了存储数据。
  • Headless service:它也是一个service,不过没有clusterIP(声明时设置为None)。如果有clusterIP,这实际上是一个proxy IP,请求会load balance到后台的pod IP。而mysql是个状态服务,显然不能这样做。

k8s集群里有如下IP:

  • pod自身的IP:使用k get pods -o wide可以看到
  • cluster IP:一个proxy IP,可以代理到后台多个pod
  • external IP:集群的外部通讯IP,从宿主机上可以直接访问

按如上文档手工在k8s上安装mysql后,再登陆和访问mysql:

$ k get pods -l app=custo-mysql
NAME            READY   STATUS    RESTARTS   AGE
custo-mysql-0   1/1     Running   0          56m
custo-mysql-1   1/1     Running   0          56m
custo-mysql-2   1/1     Running   0          48m

$ k exec -it custo-mysql-0 -- mysql -uroot -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.7.41 MySQL Community Server (GPL)

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

检查下手工安装的mysql,以及通过helm安装的mysql,它们的statefulset状态:

$ k get sts
NAME               READY   AGE
custo-mysql        3/3     59m
mysql-1677811395   1/1     5h13m

上述helm安装的mysql只有一个pod,可以手工进行扩容:

$ k scale sts/mysql-1677811395 --replicas 2

过一会扩容完再看,现在有两个pod了:

$ k get sts
NAME               READY   AGE
custo-mysql        3/3     64m
mysql-1677811395   2/2     5h18m

通过手工在k8s上安装软件,以及通过包管理器helm安装软件,两者对比看出:

  • 手工安装更复杂,需要理解更多的概念,但有助于加深对k8s的了解。
  • helm使用上更为简单,安装过程更为规范,但高度抽象的同时,隐藏了一些细节。

所有的包管理系统都有上述优劣势。先熟悉生态环境,了解执行细节,再深入接触包管理,在提高效率的同时,又知其所以然。这显然是合格的程序员应该遵循的原则。