Segment是如何用云計(jì)算技術(shù)重建基礎(chǔ)設(shè)施的
在Segment早期,我們的基礎(chǔ)設(shè)施是拼裝的,我們通過(guò)AWS的用戶(hù)界面提供實(shí)例,有一個(gè)由從未使用的AMI組成的組件,以及通過(guò)三種不同方式實(shí)現(xiàn)的配置。
隨著公司業(yè)務(wù)開(kāi)始騰飛,我們的工程師團(tuán)隊(duì)開(kāi)始擴(kuò)充,架構(gòu)也開(kāi)始日益復(fù)雜,但是生產(chǎn)環(huán)境的相關(guān)工作仍然局限于我們少數(shù)這些知道神秘陷阱的人。我們一直在逐步改善過(guò)程,但我們需要給我們的基礎(chǔ)設(shè)施更深層次的改革以保持快速發(fā)展。
因此,幾個(gè)月前,我們坐下來(lái)問(wèn)自己:“如果我們今天設(shè)計(jì),那么基礎(chǔ)設(shè)施設(shè)置會(huì)是什么樣子?”
在10周的過(guò)程后,我們完全重新設(shè)計(jì)了基礎(chǔ)設(shè)施,我們撤下了幾乎每一個(gè)實(shí)例和舊的配置,將我們的服務(wù)移到Docker容器中運(yùn)行,并且切換為使用全新的AWS賬號(hào)。
我們花了很多時(shí)間考慮如何使生產(chǎn)環(huán)境設(shè)置變得可審計(jì)、簡(jiǎn)單并且易用,同時(shí)仍然不失可擴(kuò)展的彈性。
下面就是我們的解決方案。
獨(dú)立的AWS賬戶(hù)
我們切換到了完全獨(dú)立的AWS賬戶(hù),而不是使用Region或者Tag來(lái)分離不同的預(yù)生產(chǎn)環(huán)境和生產(chǎn)環(huán)境實(shí)例,我們需要確保提供的腳本不會(huì)影響當(dāng)前運(yùn)行的服務(wù),并且使用全新的賬戶(hù)意味著我們將從一張白紙開(kāi)始。
運(yùn)維賬戶(hù)提供了跳轉(zhuǎn)和集中登錄服務(wù),團(tuán)隊(duì)中的每個(gè)人都有一個(gè)AWS IAM賬戶(hù)。
其它環(huán)境擁有一系列IAM角色來(lái)進(jìn)行切換,這意味著只能有一個(gè)登錄點(diǎn)來(lái)管理賬戶(hù),也只有一個(gè)地方限制訪問(wèn)。
舉個(gè)例子來(lái)說(shuō),Alice也許可以訪問(wèn)上圖中的所有三個(gè)環(huán)境,Bob只能訪問(wèn)開(kāi)發(fā)環(huán)境(哪怕他刪除了生產(chǎn)環(huán)境中的負(fù)載均衡器),但是他們都是通過(guò)同一個(gè)運(yùn)維賬戶(hù)登錄的。
作為對(duì)于復(fù)雜的IAM設(shè)置限制訪問(wèn)的替代,我們只是簡(jiǎn)單地通過(guò)環(huán)境來(lái)鎖定用戶(hù)并且通過(guò)角色來(lái)分組,從界面上使用每一個(gè)賬戶(hù)就像切換當(dāng)前的活躍角色一樣方便。
我們可以免費(fèi)獲得真正意義上的隔離,無(wú)需額外配置,而不是擔(dān)心預(yù)生產(chǎn)環(huán)境沙盒會(huì)不安全或者會(huì)改動(dòng)生產(chǎn)環(huán)境數(shù)據(jù)庫(kù)。
分享配置代碼帶來(lái)的額外好處就是我們的預(yù)生產(chǎn)環(huán)境事實(shí)上將會(huì)成為生產(chǎn)環(huán)境的一個(gè)鏡像,配置中僅有的區(qū)別只有實(shí)例大小和日期數(shù)目。
***,我們也可以跨賬戶(hù)合并賬單。我們使用相同的發(fā)票來(lái)支付月付賬單,可以看到一個(gè)按照環(huán)境來(lái)分割的詳細(xì)分解后的費(fèi)用。
Docker和ECS
一旦我們?cè)O(shè)置好了賬戶(hù),接下來(lái)就是輪到如何設(shè)計(jì)服務(wù)真正運(yùn)行了,為此,我們轉(zhuǎn)向了Docker和EC2容器服務(wù)(ECS)。
截止今天為止,我們大部分的服務(wù)都運(yùn)行在Docker容器里,包括我們的API和數(shù)據(jù)流管道。容器每秒鐘都會(huì)接收數(shù)千次請(qǐng)求,每月處理500億條事件。
Docker的***好處就是可以一定程度上授權(quán)團(tuán)隊(duì)從無(wú)到有構(gòu)建服務(wù),我們不再有一組復(fù)雜的配置腳本或AMIs——我們只需交給生產(chǎn)集群一個(gè)鏡像然后運(yùn)行即可。不會(huì)再有有狀態(tài)的實(shí)例了,我們可以保證預(yù)生產(chǎn)環(huán)境和生產(chǎn)環(huán)境運(yùn)行的是一模一樣的代碼。
配置完我們的服務(wù)如何運(yùn)行在容器里后,我們選擇了ECS作為調(diào)度器。
在一個(gè)較高的水平,ECS負(fù)責(zé)在生產(chǎn)環(huán)境中實(shí)際運(yùn)行我們的容器。ECS關(guān)注服務(wù)的調(diào)度,即將服務(wù)放置在單獨(dú)的主機(jī)上運(yùn)行,并且在當(dāng)連接到ELB時(shí)確保零宕機(jī)重載服務(wù)。ECS甚至可以通過(guò)AZs調(diào)度提供更好的可用性,如果某個(gè)容器掛了,ECS會(huì)確保在集群中重新調(diào)度一個(gè)新的實(shí)例。
切換到ECS極大地簡(jiǎn)化了運(yùn)行服務(wù)的工作,從而不需要擔(dān)心Upstart工作或者配置實(shí)例。添加Dockerfile、設(shè)置任務(wù)定義以及將其和集群關(guān)聯(lián)都是非常容易的。
在我們的設(shè)置中,Docker鏡像通過(guò)CI(持續(xù)集成)來(lái)構(gòu)建,然后再推送到Docker Hub。當(dāng)一個(gè)服務(wù)啟動(dòng)時(shí),會(huì)從Dokcer Hub上拉取鏡像,接著ECS就可以跨機(jī)器調(diào)度了。
我們將服務(wù)集群按照它們的關(guān)注領(lǐng)域和負(fù)載profile(比如針對(duì)API、CDN、APP等的不同的集群)分組,擁有分離的集群意味著更好的可見(jiàn)性以及針對(duì)每一個(gè)集群都可以決定如何使用不同的實(shí)例類(lèi)型(因?yàn)镋CS沒(méi)有實(shí)例關(guān)聯(lián)的概念)。
每一個(gè)服務(wù)都有一個(gè)特定的任務(wù)定義,指出了運(yùn)行在哪一個(gè)版本的容器里、運(yùn)行多少實(shí)例以及選擇哪一個(gè)集群。
在運(yùn)行過(guò)程中,服務(wù)通過(guò)ELB注冊(cè)它自身,使用健康檢查來(lái)確認(rèn)容器是否準(zhǔn)備好運(yùn)行。我們?cè)贓LB中指向一個(gè)本地的Route53條目,這樣服務(wù)借助于DNS可以互相通信和簡(jiǎn)單地引用。
設(shè)置非常的棒,因?yàn)槲覀儾恍枰魏畏?wù)發(fā)現(xiàn),本地的DNS就完成了所有的記賬工作。
ECS可以運(yùn)行所有的服務(wù),我們從ELB獲得了免費(fèi)的CloudWatch監(jiān)控指標(biāo),這比起在啟動(dòng)階段就不得不通過(guò)一個(gè)中央認(rèn)證授權(quán)中心注冊(cè)服務(wù)要簡(jiǎn)單多了,并且***的好處還是在于我們不需要親自處理服務(wù)狀態(tài)沖突了。
#p#
Terraform模板化
Docker和ECS描述了如何運(yùn)行我們的每一個(gè)服務(wù),而Terraform就像膠水一樣把它們整合在了一起。在高級(jí)別上,它是一組配置腳本,可以創(chuàng)建和更新我們的基礎(chǔ)設(shè)施。你可以認(rèn)為它像一個(gè)建造中的CloudFormation版本,但不會(huì)讓你想要戳你的眼睛。
Terraform不是運(yùn)行一組服務(wù)來(lái)維護(hù)狀態(tài),而是只通過(guò)一組腳本來(lái)描述集群,配置腳本在本地運(yùn)行(將來(lái)也是這樣,借助于持續(xù)集成)并且提交給Git,所以我們擁有了關(guān)于生產(chǎn)環(huán)境基礎(chǔ)設(shè)施實(shí)際運(yùn)行的持續(xù)記錄。
這里就是一個(gè)我們Terraform模塊設(shè)置Bastion節(jié)點(diǎn)的樣本,該樣本創(chuàng)建了所有的安全組、實(shí)例和AMIs,這樣我們就可以很容易地為將來(lái)的環(huán)境設(shè)置跳躍點(diǎn)。
- // Use the Ubuntu AMI
- module "ami" {
- source = "github.com/terraform-community-modules/tf_aws_ubuntu_ami/ebs"
- region = "us-west-2"
- distribution = "trusty"
- instance_type = "${var.instance_type}"
- }
- // Set up a security group to the bastion
- resource "aws_security_group" "bastion" {
- name = "bastion"
- description = "Allows ssh from the world"
- vpc_id = "${var.vpc_id}"
- ingress {
- from_port = 22
- to_port = 22
- protocol = "tcp"
- cidr_blocks = ["0.0.0.0/0"]
- }
- egress {
- from_port = 0
- to_port = 0
- protocol = "-1"
- cidr_blocks = ["0.0.0.0/0"]
- }
- tags {
- Name = "bastion"
- }
- }
- // Add our instance description
- resource "aws_instance" "bastion" {
- ami = "${module.ami.ami_id}"
- source_dest_check = false
- instance_type = "${var.instance_type}"
- subnet_id = "${var.subnet_id}"
- key_name = "${var.key_name}"
- security_groups = ["${aws_security_group.bastion.id}"]
- tags {
- Name = "bastion-01"
- Environment = "${var.environment}"
- }
- }
- // Setup our elastic ip
- resource "aws_eip" "bastion" {
- instance = "${aws_instance.bastion.id}"
- vpc = true
- }
我們?cè)陬A(yù)生產(chǎn)環(huán)境和生產(chǎn)環(huán)境中都使用相同的模塊來(lái)設(shè)置我們各自的Bastion節(jié)點(diǎn),我們唯一需要換掉的是IAM keys,我們準(zhǔn)備好了。
改變這些也絲毫沒(méi)有痛苦,不需要拆了已有的整個(gè)基礎(chǔ)設(shè)施,Terraform只在需要更新的地方更新。
當(dāng)我們需要修改ELB Draining超時(shí)60秒時(shí),只需要在Terraform apply后跟隨一個(gè)簡(jiǎn)單的find/replace操作,這樣兩分鐘后我們就會(huì)擁有一個(gè)對(duì)于我們所有的ELB都是完全調(diào)整了的生產(chǎn)環(huán)境設(shè)置。
Terraform是可復(fù)制的、可審計(jì)的并且自注釋的,沒(méi)有任何黑箱。
我們將所有的配置都放在一個(gè)中央的基礎(chǔ)設(shè)施Repo,其可以非常容易發(fā)現(xiàn)一個(gè)服務(wù)是如何設(shè)置的。
我們還沒(méi)有完全拿到圣杯。我們想要轉(zhuǎn)換更多的Terraform配置來(lái)利用模塊的優(yōu)勢(shì),這樣可以合并單個(gè)文件,減少共享樣本的數(shù)量。
一路上我們發(fā)現(xiàn)了一些關(guān)于.tfstate的陷阱,Terraform總是一開(kāi)始讀取現(xiàn)有的基礎(chǔ)設(shè)施,然后當(dāng)狀態(tài)變得不同步時(shí)就會(huì)抱怨,我們通過(guò)將.tfstate提交到Repo的方式終止了這種情況,然后在有任何改變之后再推送回去,但是我們正在調(diào)研Atlas,或者通過(guò)持續(xù)集成來(lái)解決這個(gè)問(wèn)題。
移動(dòng)到Datadog
通過(guò)這一點(diǎn),我們有了我們的基礎(chǔ)設(shè)施,我們的供應(yīng)和我們的隔離。剩下的***一件事是度量和監(jiān)控,用以追蹤生產(chǎn)環(huán)境中運(yùn)行的一切東西。
在我們的新的環(huán)境中,我們已經(jīng)將所有的度量和監(jiān)控切換到了Datadog上,這是非常奇妙的。
我們一直非常滿(mǎn)意Datadog的界面、API以及其與AWS的完全集成,但從工具中獲得最多的還是來(lái)自于一些關(guān)鍵的設(shè)置。
我們做的***件事是將AWS與Cloudtrail集成,這個(gè)給出了一個(gè)居高臨下鳥(niǎo)瞰的視角來(lái)觀察我們的每一個(gè)環(huán)境如何演化的,因?yàn)槲覀円c ECS集成,Datadog feed每次都會(huì)在任務(wù)定義更新的時(shí)候更新,所以我們最終得到了免費(fèi)部署的通知。尋找Feed是出奇的快,而且很容易追溯到上一次的服務(wù)部署或重新調(diào)度。
接下來(lái),我們確保添加Datadog-agent作為基礎(chǔ)AMI的容器(Datadog/Docker-dd-agent),它不僅可以從主機(jī)(CPU、內(nèi)存等)收集度量指標(biāo),還可以作為Statsd指標(biāo)庫(kù)。每個(gè)服務(wù)收集自定義的基于查詢(xún)、延遲和錯(cuò)誤的度量指標(biāo),這樣我們可以在 Datadog里探查指標(biāo)和發(fā)送告警。我們的Go工具箱(很快就會(huì)開(kāi)源)自動(dòng)收集Ticker的pprof輸出并且發(fā)送,所以我們可以監(jiān)控內(nèi)存和協(xié)程(Goroutines)。
更酷的是,Datadog-agent可以在環(huán)境中跨主機(jī)將實(shí)例利用率可視化,所以我們可以得到一個(gè)高層次的實(shí)例或集群概述,也許會(huì)有下面的一些議題:
另外,我的隊(duì)友Vince創(chuàng)建了“Terraform provider for Datadog”,所以我們完全可以針對(duì)生產(chǎn)配置編寫(xiě)告警腳本。我們的告警將會(huì)記錄下來(lái)并且與生產(chǎn)環(huán)境運(yùn)行的系統(tǒng)保持同步。
- resource "datadog_monitor_metric" "app.internal_errors" {
- name = "App Internal Errors"
- message = "App Internal Error Alerts"
- metric = "app.5xx"
- time_aggr = "avg"
- time_window = "last_5m"
- space_aggr = "avg"
- operator = ">"
- warning {
- threshold = 10
- notify = "@slack-team-infra"
- }
- critical {
- threshold = 50
- notify = "@slack-team-infra @pagerduty"
- }
- }
按照慣例,我們指定了兩個(gè)告警級(jí)別:Waring和Critical。Waring級(jí)別可以讓任何在線(xiàn)的用戶(hù)知道有什么東西看起來(lái)可疑,并針對(duì)任何潛在的問(wèn)題提前做好預(yù)案。Critical級(jí)別的告警被保留為“喚醒你在深夜的問(wèn)題”亦即一個(gè)嚴(yán)重的系統(tǒng)故障。
更重要的是,一旦我們過(guò)渡到Terraform模塊并為我們的服務(wù)描述添加Datadog供應(yīng)商,接著所有的服務(wù)最終都會(huì)免費(fèi)獲得告警信息,數(shù)據(jù)將直接由我們的內(nèi)部工具箱和Cloudwatch度量指標(biāo)來(lái)驅(qū)動(dòng)處理。
讓Docker運(yùn)行的美好時(shí)光
一旦我們有了上述所有的這些組件,切換的這一天終于來(lái)臨了。
我們首先在新的生產(chǎn)環(huán)境和遺留環(huán)境之間設(shè)置一個(gè)VPC的對(duì)等連接——允許我們?cè)谒鼈冎g集群化數(shù)據(jù)庫(kù)和復(fù)制。
接下來(lái)我們?cè)谛碌纳a(chǎn)環(huán)境中預(yù)熱ELBs,確保它們可以處理負(fù)載。亞馬遜沒(méi)有提供ELBs的自動(dòng)調(diào)整排列功能,所以我們不得不咨詢(xún)亞馬遜來(lái)提前準(zhǔn)備(或者緩慢擴(kuò)展自身)來(lái)處理增加的負(fù)載。
從那里開(kāi)始,這只是一個(gè)使用加權(quán)Route53路由從老環(huán)境到新環(huán)境穩(wěn)步增加流量的問(wèn)題,持續(xù)監(jiān)控,一切都看起來(lái)不錯(cuò)。
今天,我們的API像蜂群一樣辛勤工作,每秒處理成千上萬(wàn)的請(qǐng)求,并且完全運(yùn)行在Docker容器里。
但是我們還沒(méi)有完成,我們?nèi)匀辉谖⒄{(diào)我們的服務(wù)創(chuàng)建方式,并減少樣板,這樣團(tuán)隊(duì)里的任何人都可以非常容易地構(gòu)建具有適當(dāng)?shù)谋O(jiān)控和告警功能的服務(wù),并且我們想改進(jìn)與容器工作相關(guān)的工具,因?yàn)榉?wù)不再與實(shí)例關(guān)聯(lián)了。
我們還計(jì)劃為這個(gè)項(xiàng)目留意有前途的技術(shù)。Convox團(tuán)隊(duì)正在圍繞AWS基礎(chǔ)設(shè)施構(gòu)建很棒的工具。雖然我們喜歡ECS的簡(jiǎn)單以及集成特性,但是Kubernetes、Mesosphere、Nomad和 Fleet看起來(lái)也都是非??岬馁Y源調(diào)度系統(tǒng),看看它們都是如何打開(kāi)市場(chǎng)局面的將是令人興奮的,我們會(huì)一直跟蹤它們,看看有什么可以采用借鑒的。
在所有這些業(yè)務(wù)流程的變化后,我們比以往更強(qiáng)烈的認(rèn)為應(yīng)將我們的基礎(chǔ)設(shè)施外包給AWS。他們已經(jīng)通過(guò)將大量核心服務(wù)產(chǎn)品化改變了游戲規(guī)則,同時(shí)保持一個(gè)非常有競(jìng)爭(zhēng)力的價(jià)格點(diǎn)。這一點(diǎn)帶來(lái)了一種新的類(lèi)型的創(chuàng)業(yè)公司,它們可以高效地構(gòu)建產(chǎn)品,廉價(jià)同時(shí)花費(fèi)更少的時(shí)間進(jìn)行維護(hù),我們看好在AWS的生態(tài)系統(tǒng)上構(gòu)建工具。