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