保障安全刻不容缓
保障安全刻不容缓,你不能轻描淡写地说:“今后系统会加强安全性。”
强大的应用程序安全应该被当作开发过程中的首要问题,应该从第1天开始就需要讨论,绝对不能等到第300天。将安全性留到后一分钟实际上会增加开发的时间,因为你不得不返工,通过重构实现安全要求。
更糟糕的是,届时你就没有那么多时间来解决安全问题了,终只能发布易受攻击的代码。雅虎等公司的遭遇就是前车之鉴。
每个应用程序各不相同,且有各自的需求,根据应用程序的需求选择,不要屈服于市场流行度。
每个应用程序各不相同,这点无需多说。世上没有一套适用于所有应用程序的万全之策。在启动新应用程序时,应该由应用程序及其体系结构来规定使用的技术或标准化的平台。
如果在你搞清楚“应用程序需要什么”之前,就决定使用gRPC或Kubernetes系统,那么只会阻碍代码的编写工作。这就是为什么Canonical等公司误入歧途,在物联网设备上运行Kubernetes。
引用Jeff Goldblum的一句话:“科学家们忙于思考他们能够干什么,不能够干什么,以至于他们没有时间停下来思考是否应该这样干。”
你可能并不需要微服务
微服务非常有魅力,我完全理解。即便只是想一想能够单独扩展应用程序中的各个小功能,就足以让人兴奋不已,因为你可以感受到满满的成就感。
但是,说实话你可能不需要微服务。比如“我希望将功能X的代码从功能Y的代码中分离出来”,“防止不良代码渗透到应用程序的其他部分”,或者“小化受影响的应用”等到这些理由不是使用微服务的原因,这些不过是糟糕的开发实践导致的恶果,你需要的是更严格的代码审查标准(不要合并不良代码),以及更为细致的安全控制。
你是否需要单独扩展应用程序的各个部分,而且目前的组件(例如登录流程)是否存在容量的问题?然后再探索那些需要扩展成独立服务的组件。
你是否需要运行基于虚拟服务器的架构并希望降低成本?如果是,那么就不应该探索微服务。即便采用微服务,充其量也不过是功过相抵。恶劣的情况下,终只是多了几个需要单独启动的实例。
假设你的整体式架构包含5个服务,而你却将其分解成了微服务。那么,现在你有5个应用,你所面临的局面是:
a)你需要逐个启动专用实例,而占空的空间是初的5倍;
b)你利用现有的空间,同时承担额外的运营成本。
标准化的开发环境
在与多个开发人员合作时,标准化整个团队使用的开发环境可以让你受益无穷。我并不是说你必须将一些基于容器的虚拟开发环境通过魔法混合在一起。虽然你要这么干我也拦不住,但是你只需使用同一个版本的语言就可以为团队带来奇迹。
如果你的同事用Go 1.11编写代码,而你却在Go 1.12上发现了Bug,那么可真是欲哭无泪。协调何时升级版本可能很困难,但一旦协调成功,诸事都会顺利。
配置的工作不简单,请务必做好相应的计划
虽然有些流行的网站说,配置只不过是“将所有东西都扔进环境变量中”,然而事实远非如此。
我认为配置应用程序的方法至少有四种:代码内的默认值、本地配置文件、命令行的标志、环境变量、远程配置(如使用Hashicorp的Consul)等。我认为远程配置是可选的,而其他四个都是必要的。
对于开发来说,为了在本地运行应用程序而不得不将27个不同的配置值放入环境变量,这会让人万分沮丧。另外,你可能需要更好的自动化和Makefile。你可以利用本地配置源的方法,如application.yaml,并默认设置为“dev”配置。
此外,如果代码中有默认值,则意味着你需要更改它们的默认值。在通过systemd等初始化系统运行应用程序时,命令行的标志非常实用,因为在查看进程的运行情况时,你可以清楚地看到应用程序的配置方式。在容器内运行应用程序时,环境变量非常实用,但也有一些东西不适合放入环境变量,比如Secret。
你永远不能将Secret(密码、身份验证令牌、证书等你不想泄露给公众的数据)放入环境变量,因为它们不安全,而且主机上的任何进程都可以读取环境变量。你应该借助某种Secret管理工具。我个人喜欢使用Vault(来自Hashicorp),但你可以根据应用选择合适的工具。
只在必要时导入软件包
我们都知道left-pad的那个故事吧(https://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/)。
只因为有人从代码仓库中删掉了一个11行的NPM软件包,结果几乎导致整个互联网瘫痪。
请务必小心。只有当你面临正当的需求时,才应该导入软件包,例如某个供应商特定的SDK(比如AWS的SDK),这是一组高度冗长的标准库抽象(这就是人们喜欢使用Python的请求而不是urllib的原因) ,或广泛使用的框架,比如Go的Echo HTTP服务器或Python的Flask WSGI服务器。
一些便捷的库也可以,比如JavaScript的Lodash提供了标准库中没有的一些常用功能和附加功能。这些外部的依赖项可以减轻开发的负担,而且你不需要手动编写样板或集成代码,这些都是包系统的优势。然而,就像left-pad一样,你很容易被坑:“这儿有一个库,我可以使用。”你导入的依赖项每增加一个,随之而来的不稳定性和风险也会剧增,可能这些库根本没人维护。
新依赖项本身导入的每个包也会增加风险,这称之为传递依赖。如果你导入了一个包,而这个包又导入了5个包,那么恭喜你现在继承了这五个依赖关系以及随之而来的所有风险。
我和这个行业的许多其他人都认为软件包不应该引入传递依赖。虽然无法做到万无一失,但至少应该尽可能减少,如果你需要更多功能,那么就为用户提供一种明确的扩展方法。
在导入库时,通常我会遵循一个简单的规则:如果我可以在10-15分钟内自己编写,那么就自己写;否则再考虑使用外部的库。
在开发的时候,牢记这条规则可以避免将不必要的内容导入应用程序,但是你不必每次需要提供API时都考虑从头开始编写新的HTTP服务器。
没必要抽象所有代码
还有一个很大的坑:抽象一切。
有时,你会觉得“稍后我可能会再用到这个功能”,这个想法可能会将你引向一条黑暗又可怕的面向对象之路。
DRY原则(Don’t Repeat Yourself,不要自我重复)彻底征服了我们,尽管这条原则有其充分的理由。
然而,你需要注意不要在抽象上花费太多时间,以至于没有足够的时间编写逻辑。你的工作是写代码!等到你发现你需要实现的某个方法或函数之前已经写过了,那么可以再回过头来抽象,但是切记量力而行。
我个人遵循的原则是,如果抽象之前只是一个只有3行的函数,那么就重复好了。如果真的只有3行代码,也许你该想一想是否值得写成函数。
项目需要像“凤凰”一样,经历浴火重生的洗礼
这个想法令人不寒而栗。经理们会为此感到紧张,产品所有者会为此变得暴躁,而且开发人员也会因此而感到愤怒,但你必须这么做。
每隔一段时间就从头开始其实是一件好事。你可以借机删掉代码中的冗余,而且无需改造现有的半个代码库就可以实现新的想法,同时还可以强制每个人重新评估项目。
你可以把项目想象成一片森林,每一行代码都是一棵参天大松树,绵延数里。随着时间一天天过去,这片森林会布满灌木丛、飘落的松针、松果、枯枝和许多其他杂物。这些都是你的麻烦,你的技术债务。
这些东西越积累越多,直到受到某种外部力量的影响。对于森林而言,这种外部力量就是野火。火焰肆虐过的森林,地表寸草不生,只有树皮足够厚的树木才能存活下来,所有未长成的树木都会被大火烧尽。虽然这对森林来说是灭顶之灾,但其中蕴含着一个惊天的秘密:森林渴望大火。多年来,它一直在耐心地等待,等待火焰来净化一切,因为火焰在树冠下肆虐过后,下一代的参天大树才会从松果中发芽。
当火焰横扫过森林地面时,它会孵化出幼小脆弱的树苗,让它们与被大火烧得漆黑的幸存者并肩而立。你的应用程序也需要这样的洗礼:生命力旺盛、编写良好的代码会从清理中存活下来,而新的想法和代码会从累累白骨中站起来,宛如浴火重生的凤凰。
你不是谷歌
如果你是谷歌的一员,那么请绕道。但如果你真的是谷歌的员工,又何必来读这篇文章呢?关键在于,你是谷歌微软、亚马逊、Twitter或Facebook一员的可能性非常小。所以,你无需在全球17个不同数据中心的10,000台裸金属服务器上协调150,000个容器。通常,你的问题不会影响到世界各地的人民。
那么,为什么我们要谈这个话题?因为你应该根据规模决定你的运营平台。如果你只需要运行几百个容器,那么有必要使用Kubernetes吗?你真的需要运行Kuberetes,还是说只想在简历中多添一项炫耀的资本?
HashiCorp Nomad等系统非常适合中小规模的系统:设置简单,几乎不需要维护,还有良好的文档记录,而且转换应用程序很容易,因为它适用于容器以及系统进程和JVM原生应用程序。如果你真的想使用Kubernetes,那么为什么不使用Rancher等把混乱的东西都抽象化呢?运行Kubernetes这般复杂的系统实在让人感到头疼,而且也心疼钱,因为这些系统都是给谷歌这样的公司设计的,单凭一个团队很难管理。
我甚至会说,创业公司应该不惜一切代价避免这样的系统,除非他们的产品专门针对Kubernetes。但有一个例外,可以使用谷歌、亚马逊和微软通过各自的云产品提供的托管服务,将所有琐事都交给他们管理,可以为你省却很多麻烦。但永远不要让我抓到你在物联网设备上使用Kubernetes。千万不要。
不要听互联网上陌生人的忽悠
你应该自行决定适合自己的应用程序和开发风格的规则。即使本文提到的几件事,你也应该仔细推敲,毕竟我也只是互联网上的一个陌生人。