本文最初发表于作者个人网站,经原作者AnthonyN.Simon授权,InfoQ中文站翻译并分享。
虽然文章的标题听起来有些夸张,但我想澄清的是,我们正在讨论的是一家压力不大的个人公司,这家公司是我在德国经营的。自己花钱买的,我很喜欢慢慢来。如果我说“科技创业”,那大概和大多数人想象的不一样。
我不能在没有大量的开源软件和管理服务的情况下做到这一点。我觉得自己就是站在巨人的肩膀上,他们在我之前做过那么多艰苦的工作,我非常感谢他们。
从背景角度来说,我经营的是个人SaaS,这是我发表的一篇关于我所使用的技术栈的文章的详细介绍。在听从我建议之前,先考虑一下你的情况。在技术选择上,你的背景很重要,不需要什么圣杯。
我在AWS上使用了Kubernetes,但是不要陷入需要它的误区。经过一直非常耐心的团队的指导,我花了几年的时间学习了这些工具。因为这是我最擅长的,所以我的工作效率非常高,并且我能将集中精力在运输物品上。你们的目标可能不一样。
闲话少叙,言归正题。
1整体架构基础设施可以同时处理多个项目,但是为了演示,我将使用Panelbear,我最近的SaaS,作为此类设置的一个实例。
Panelbear中的浏览器计时图表,这是我将在本教程中使用的一个示例项目。
就技术而言,该SaaS每秒处理来自世界各地的大量请求,并以高效的格式存储数据,实现实时查询。
就业务而言,它仍处于起步阶段(我是半年前推出的),但它的发展比我预期的要快,特别是我最初为自己创建的Django应用,它是在一个小的虚拟专用服务器上使用SQLite。对我当时的目标而言,这是非常有效的,而且我可能已经将这一模式推进了很远。
但是,我变得越来越沮丧,不得不重新使用许多我已经习惯了的工具:零停机部署、自动缩放、健康检查、自动DNS/TLS/ingress规则等等。Kubernetes宠坏了我,让我习惯于在保持控制力和灵活性的情况下处理更高级抽象。
快进六个月,经历了几次迭代,虽然我目前的设置仍然是Django的单体版本,我现在将Postgres用作应用数据库,ClickHouse用作分析数据,Redis用作缓存。同时使用Celery来调度任务,使用自定义事件队列来缓冲写操作。这其中大部分都在托管的Kubernetes集群上运行。
架构概述
听起来似乎很复杂,但它实际上是老式的单体架构,运行在Kubernetes上。如果把Django换成Rails或Laravel,你就知道我在说什么了。令人感兴趣的是,如何将所有的东西粘合在一起并自动执行:自动缩放、入口、TLS证书、故障转移、日志、监控,等等。
值得一提的是,我在多个项目中都使用了这种设置,它帮助我降低了成本,而且很容易进行实验(编写Dockerfile和gitpush)。因为经常有人问我这样一个问题:和你想的相反,我实际上花了很少的时间去管理基础设施,通常每个月要花大概0~2小时。大部分时间都用来开发功能、做客户支持,以及拓展业务。
话又说回来,这些工具我都用了好几年了,都很熟悉。虽然我的设置对它们的能力来说很简单,但我的日常工作却花了很多年才做到这一点。因此,我不会说这就是“阳光和玫瑰”。
不知道谁先说了这句话,但我这样对朋友们说:“Kubernetes让简单的东西变得复杂,但也让复杂的东西变得简单”。
2自动DNS、SSL和负载均衡既然你已经了解了我在AWS上托管的Kubernetes集群,并且在其中运行了各种项目,那么让我们进入本文的第一站:如何将流量引入集群。
我的集群是在一个私有网络中,因此你无法从公共互联网中直接访问。有几个部分可以控制集群的访问和负载均衡流量。
基本上,我让Cloudflare将所有流量代理到NLB(AWSL4NetworkLoadBalancer,网络负载均衡器)。该负载均衡器是公共互联网和我的私有网络之间的桥梁。当收到请求后,它将转发给其中一个Kubernetes集群节点。这些节点分布在AWS中多个可用性区域的私有子网中。所有这些都是顺便处理的,但以后还会有更多。
流量被缓存在边缘,或者转发到我运营的AWS区域中
ingress-nginx就是这样做的:“Kubernetes如何知道该将请求转发到哪个服务?”简单地说,它是一个NGINX集群,由Kubernetes管理,是集群内所有流量的入口。
在将请求发送到相应的应用程序容器之前,NIGIX适用速度限制和其他流量形成规则。就Panelbear而言,应用容器是由Uvicorn服务的Django。
这种方法与传统的nginx/gunicorn/Django的VPS方式没有什么不同,它带来了横向扩展和自动设置CDN的优点。它还可以“一次设置就忘记”,在Terraform/Kubernetes之间主要有一些文件,由所有已部署项目共享。
在部署新项目时,入口配置基本上只有20行代码,就像这样:
apiVersion:networking.k8s.io/v1beta1kind:Ingressmetadata:namespace:examplename:example-apiannotations:kubernetes.io/ingress.class:"nginx"nginx.ingress.kubernetes.io/limit-rpm:""cert-manager.io/cluster-issuer:"letsencrypt-prod"external-dns.alpha.kubernetes.io/cloudflare-proxied:"true"spec:tls:-hosts:-api.example.