前面的兩部分介紹基礎(chǔ)設(shè)施即代碼以及部署的方式狗唉。和虛擬機(jī)相比初烘,Docker作為操作系統(tǒng)級別的虛擬化技術(shù),特別適用微服務(wù)的部署場景分俯。
那么我們?nèi)绾问褂肈ocker去部署微服務(wù)呢肾筐,首先應(yīng)該考慮解決下面的問題:
- 對于web app,該使用什么樣好的軟件構(gòu)建原則
- 采用何種0 downtime的部署模式
- 如何和現(xiàn)有的監(jiān)控缸剪、日志解決方案集成
- 如何構(gòu)建安全且高可用的private docker registry
使用Docker部署前的狀況
在使用Docker部署前吗铐,我們已基本實(shí)現(xiàn)新的應(yīng)用微服務(wù)化,架構(gòu)上前后端分離杏节,API通過Cloudformation去做部署唬渗,所有新的服務(wù)都部署在AWS上典阵。一個(gè)API的持續(xù)交付的流程如下:
- 代碼提交,通過PR后進(jìn)入持續(xù)交付流水線
- 通過測試后生成RPM包
- 通過Packer镊逝,在一個(gè)基礎(chǔ)鏡像的啟動(dòng)新的服務(wù)器上安裝RPM包壮啊,并生成新的AMI
- 在CI上進(jìn)行自動(dòng)化部署,配置文件persist到private的git repo中撑蒜,避免了維護(hù)中心化服務(wù)器的成本歹啼,deploy腳本從CI中獲取新生成的AMI ID,更新cloudformation template中l(wèi)aunchConfiguration
- cloudformation觸發(fā)自動(dòng)化的不可變部署,完成更新。
這個(gè)流程中不好的地方在于:
- CI中打包兩次坦冠,并且生成AMI時(shí)間比較長
- AMI會(huì)保存到S3中,有成本
整體來看拓萌,使用AMI去部署稍微有點(diǎn)重,而且一旦要從AWS退出升略,移植會(huì)有一點(diǎn)點(diǎn)成本司志。使用容器來做部署看上去可以解決這些問題,2年前Docker已經(jīng)逐漸成熟降宅,所以"領(lǐng)導(dǎo)"做了決定,咱們來用Docker替換AMI部署的方式囚霸。這樣縮短了持續(xù)交付時(shí)間腰根,同時(shí)基本避免了在不同環(huán)境的差異,可移植性大大增加拓型,更加靈活额嘿。
12 factors app
12factors app是我們在考慮使用Docker在生產(chǎn)環(huán)境部署的時(shí)候參考的原則。12factors是在Heroku上部署軟件時(shí)總結(jié)出來的12條好的實(shí)踐劣挫,其基本的原則是:
- 使用標(biāo)準(zhǔn)化流程自動(dòng)配置册养。
- 系統(tǒng)/平臺(tái)最大的可移植性。
- 適合部署在現(xiàn)代的云計(jì)算平臺(tái)压固,從而在服務(wù)器和系統(tǒng)管理方面節(jié)省資源球拦。
- 降低開發(fā)環(huán)境和生產(chǎn)環(huán)境的差異降至最低,并使用持續(xù)交付實(shí)施敏捷開發(fā)帐我。
- 可以在工具坎炼、架構(gòu)和開發(fā)流程不發(fā)生明顯變化的前提下實(shí)現(xiàn)擴(kuò)展。
具體的12條原則如下:
- 基準(zhǔn)代碼:一份代碼拦键,多份部署谣光。意思就是對于每個(gè)應(yīng)用都有單獨(dú)的版本管理的代碼庫,同時(shí)只針對主干開發(fā)芬为,對于不同的環(huán)境test/staging/production萄金,只針對這一份代碼部署蟀悦。在開發(fā)新feature時(shí),最好不要采用feature branch的方式氧敢,因?yàn)閙erge的成本和測試的成本比較高日戈,通過feature toggle去控制即可。
- 依賴:顯式聲明依賴福稳。比如Ruby項(xiàng)目中的的Gemfile涎拉,Java項(xiàng)目中的Gradle配置中會(huì)聲明第三方的依賴。還有類似RPM的spec文件的圆,會(huì)顯式的聲明對第三方類庫的依賴鼓拧,比如ImageMagic。這樣的文件可以放在應(yīng)用代碼庫中越妈,這樣開發(fā)人員對于應(yīng)用在生產(chǎn)環(huán)境中運(yùn)行所需要的依賴就會(huì)比較清晰季俩。
- 配置:在環(huán)境變量中存儲(chǔ)配置。將代碼和配置分離梅掠,我們是把每個(gè)環(huán)境的配置都放在不同的private git repo酌住,當(dāng)然你也可以用中心化的配置服務(wù)器,就是管理維護(hù)的成本比較高阎抒。
- 后端服務(wù): 把后端服務(wù)(backing services)當(dāng)作附加資源酪我。比如郵件服務(wù),依賴的靜態(tài)文件且叁,或者數(shù)據(jù)庫都哭。這樣做的好處是和依賴的服務(wù)松耦合,其它服務(wù)的部署逞带、scale都不會(huì)影響到應(yīng)用本身欺矫,其它服務(wù)的失敗,對于應(yīng)用只有有限的影響展氓,而且對于那些服務(wù)的修復(fù)穆趴,應(yīng)用本身不需要再做額外的工作,如部署等遇汞。
- 構(gòu)建未妹,發(fā)布,運(yùn)行: 嚴(yán)格分離構(gòu)建和運(yùn)行空入。直接修改處于運(yùn)行狀態(tài)的代碼是非常不可取的做法教寂,因?yàn)檫@些修改很難再同步回構(gòu)建步驟。所以打包和配置分開执庐,只有在運(yùn)行時(shí)才把這兩個(gè)結(jié)合起來酪耕。
- 進(jìn)程: 以一個(gè)或多個(gè)無狀態(tài)進(jìn)程運(yùn)行應(yīng)用。12factors的應(yīng)用的進(jìn)程必須是無狀態(tài)并且是不共享架構(gòu)(Shared Nothing Architecture)轨淌。需要持久化的數(shù)據(jù)保存在數(shù)據(jù)庫或者文件服務(wù)器(如S3)等迂烁。雖然很多服務(wù)器和LB都提供了sticky session的功能看尼,但是最好不要使用,如果應(yīng)用宕機(jī)盟步,那么指向這臺(tái)應(yīng)用服務(wù)器的session都會(huì)失敗藏斩,給用戶不好的使用體驗(yàn)。
- 端口綁定:通過端口綁定提供服務(wù)却盘。一些應(yīng)用狰域,比如Java應(yīng)用,會(huì)使用Tomcat等容器黄橘,這樣會(huì)有額外的依賴兆览,12factors應(yīng)用應(yīng)該自我加載,比如用內(nèi)嵌jetty/netty的方式啟動(dòng)Java/Scala應(yīng)用塞关,它們對外以后端服務(wù)的形式暴露出去抬探,比如Rails啟動(dòng)的URL是
localhost:5000
,最前面的LB可以建立一個(gè)簡單的TCP Port Mapping80:5000
,443:5000
即可帆赢。 - 并發(fā): 通過進(jìn)程模型進(jìn)行擴(kuò)展小压。Apache和Nginx都有類似的工作的模式,以進(jìn)程為單位椰于,將工作分配給不同進(jìn)程類型怠益,比如web進(jìn)程處理http請求,worker中運(yùn)行業(yè)務(wù)代碼處理具體的邏輯瘾婿。除此之外蜻牢,服務(wù)啟動(dòng)時(shí)應(yīng)該將自己交給系統(tǒng)的自啟動(dòng)管理工具,System V Init憋他、Upstart或者Systemd等,這樣不需要你自己去寫守護(hù)進(jìn)程或者保存PID等髓削,用統(tǒng)一的方式去處理啟動(dòng)竹挡,重啟或者關(guān)閉進(jìn)程。
- 易處理:快速啟動(dòng)和優(yōu)雅終止可最大化健壯性立膛。12 factors應(yīng)用的進(jìn)程必須是易處理(disposable)的揪罕,意思是說它們可以瞬間開啟或停止。并且在接受到停止信號(SIGTERM)時(shí)宝泵,不再接受新的請求好啰,并且等當(dāng)前的請求處理完成再停止服務(wù)。
- 開發(fā)環(huán)境與線上環(huán)境等價(jià): 盡可能的保持開發(fā)儿奶,預(yù)發(fā)布框往,線上環(huán)境相同。"It works on my machine"是程序員中間流行的一個(gè)笑話闯捎,如果可以通過類似Docker這樣的容器技術(shù)進(jìn)行打包的話椰弊,可以盡量的避免這個(gè)問題许溅。如此,你可以讓錯(cuò)誤更早的呈現(xiàn)秉版,減少反饋的時(shí)間贤重,讓上線更為順利。
- 日志: 把日志當(dāng)作事件流清焕。日志對于追蹤應(yīng)用運(yùn)行的狀況以及一些業(yè)務(wù)分析非常重要并蝗。有些時(shí)候日志會(huì)被存在服務(wù)器硬盤上,但是對應(yīng)的處理這些日志秸妥,你需要添加額外的配置滚停,比如logrotate之類,同時(shí)你對服務(wù)器的磁盤也產(chǎn)生了依賴筛峭。12 factors提倡將日志當(dāng)做事件流铐刘,應(yīng)用的輸入直接寫入stdout和stderror,然后將它們發(fā)送到諸如ElasticSearch以及Splunk之類的日志服務(wù)器中影晓。還有一個(gè)好處是镰吵,開發(fā)人員可以直接從Splunk查找日志,而不需要運(yùn)維去將日志從服務(wù)器拷貝下來挂签,也不需要運(yùn)維人員給開發(fā)人員授權(quán)訪問生產(chǎn)環(huán)境的服務(wù)器的ssh訪問權(quán)限疤祭。
- 管理進(jìn)程: 后臺(tái)管理任務(wù)當(dāng)作一次性進(jìn)程運(yùn)行。典型的例子就是schema migration或者data migration饵婆。這樣的任務(wù)應(yīng)該和生產(chǎn)環(huán)境同一個(gè)環(huán)境下運(yùn)行勺馆,并且必須和生產(chǎn)代碼保持一致。
如果回頭看下我們使用AMI時(shí)候的狀態(tài)侨核,很多已經(jīng)是符合12 factors原則了草穆,但是我們需要在日志處理等方面使用Docker繼續(xù)改進(jìn)。
使用Docker的一些考量
我們原本在生產(chǎn)環(huán)境下運(yùn)行的服務(wù)搓译,需要集成newrelic對服務(wù)器和應(yīng)用做監(jiān)控(NewrelicSysmon和Newrelic Agent)悲柱,同時(shí)日志需要發(fā)送到Splunk indexer集群⌒┘海基于已有的基礎(chǔ)以及12 factors原則豌鸡,應(yīng)用以容器的方式在服務(wù)器上運(yùn)行的形式如下:
- launchConfiguration中只需要一個(gè)存在Docker運(yùn)行環(huán)境的基礎(chǔ)AMI
- Instance啟動(dòng)后,在user-data順序啟動(dòng)日志收集的容器(基于fluentd)段标,運(yùn)行Nginx容器以及app的容器
- 其中幾個(gè)容器必須都運(yùn)行在同一網(wǎng)絡(luò)下涯冠,確保能連接到日志收集容器,并在輸出日志時(shí)tag自己的身份
- fluentd將接受到的日志逼庞,以及host instance的syslog一起通過splunk plugin發(fā)往splunk的fowarder蛇更,再由其轉(zhuǎn)發(fā)給splunk indexer。
如此則應(yīng)用/服務(wù)本身對于服務(wù)器除了計(jì)算資源外,沒有任何的依賴械荷,同時(shí)部署的方式也沿用基于cloudformation的immutable deployment共耍,還有獲得了高可移植性以及多個(gè)環(huán)境的最大一致性。
高可用且安全的private docker registry
要使用Docker部署吨瞎,就得保證私有的docker registry的可用性痹兜,以及安全。使用用戶名密碼的方式做驗(yàn)證是不太合適的颤诀,因?yàn)檫@樣管理和維護(hù)的成本很高字旭。docker registry v2支持 Bearer Token(JWT Token)的方式去做驗(yàn)證。
如果你曾經(jīng)往dockerhub push過鏡像崖叫,那么當(dāng)你在命令行使用docker login
的時(shí)候遗淳,它做的事情是在~/.docker/config.json
里面生成下面的格式的配置:
{
"auths": {
"https://index.docker.io/v1/": {
"auth": "Base64_encoded"
}
}
}
這里auth
中包含的字符串如果你用base64解碼后,可以驚奇的發(fā)現(xiàn)那里就是自己的用戶名和密碼:)心傀。本地docker去訪問docker hub時(shí)屈暗,這個(gè)auth
就是Authorization: -
header中的部分,然后docker hub的registry再對這個(gè)進(jìn)行驗(yàn)證和授權(quán)脂男,確認(rèn)你可以進(jìn)行的操作养叛。
對于高逼格企業(yè)內(nèi)部的私有docker registry,是不會(huì)用這樣的方式進(jìn)行驗(yàn)證的宰翅。一個(gè)合理的方式是使用JWT token弃甥。 JWT是允許按照聲明的格式去發(fā)布一個(gè)加密/簽名過的token,當(dāng)用戶通過身份驗(yàn)證后汁讼,它會(huì)生成下面的格式:
指明加密算法(支持對稱型以及非對稱型加密)以及token類型的Header:
{
"typ": "JWT",
"alg": "ES256",
}
包含用戶名(sub
)淆攻,以及token過期時(shí)間exp
,權(quán)限act
等的Payload:
{
"exp": 1484102411,
"nbf": 1484012411,
"iat": 1484012411,
"sub": "itsasecret",
"aud": [
"my.awesome.internal.docker.registry"
],
"act": [
"read"
]
}
Signature部分嘿架,根據(jù)算法的不同瓶珊,如果是對稱型加密算法,那么簽名的部分知識對Header耸彪、PayLoad的base64編碼后的簽名伞芹,而使用RSA算法,則是使用私鑰去加密Header/PayLoad部分的內(nèi)容后用base64編碼的結(jié)果搜囱。然后這幾部分用.
連接起來丑瞧,構(gòu)成了整個(gè)的token柑土,如下:
token: eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0.QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w
JWT的工作流程如下:
當(dāng)你使用這個(gè)token向registry發(fā)起請求時(shí)蜀肘,其header中會(huì)包含Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IkJWM0Q6MkFWWjpVQjVaOktJQVA6SU5QTDo1RU42Ok40SjQ6Nk1XTzpEUktFOkJWUUs6M0ZKTDpQT1RMIn0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJCQ0NZOk9VNlo6UUVKNTpXTjJDOjJBVkM6WTdZRDpBM0xZOjQ1VVc6NE9HRDpLQUxMOkNOSjU6NUlVTCIsImF1ZCI6InJlZ2lzdHJ5LmRvY2tlci5jb20iLCJleHAiOjE0MTUzODczMTUsIm5iZiI6MTQxNTM4NzAxNSwiaWF0IjoxNDE1Mzg3MDE1LCJqdGkiOiJ0WUpDTzFjNmNueXk3a0FuMGM3cktQZ2JWMUgxYkZ3cyIsInNjb3BlIjoiamxoYXduOnJlcG9zaXRvcnk6c2FtYWxiYS9teS1hcHA6cHVzaCxwdWxsIGpsaGF3bjpuYW1lc3BhY2U6c2FtYWxiYTpwdWxsIn0.Y3zZSwaZPqy4y9oRBVRImZyv3m_S9XDHF1tWwN7mL52C_IiA73SJkWVNsvNqpJIn5h7A2F8biv_S2ppQ1lgkbw
,registry前的反向代理服務(wù)器如Nginx上稽屏,可以運(yùn)行jwt驗(yàn)證的模塊扮宠,用public key解密token,并驗(yàn)證內(nèi)容完整性、是否過期等坛增,完成后再將內(nèi)容傳遞給registry获雕,registry根據(jù)請求類型以及
"act": [
"read"
]
判斷是否有足夠的授權(quán),之后在允許用戶pull image收捣。
這是從安全的角度考慮使用JWT+RSA去驗(yàn)證用戶身份以及授權(quán)届案,好處是維護(hù)的成本比較低,同時(shí)token在一段時(shí)間內(nèi)就過期罢艾,兼顧了安全型楣颠。
另一方面,從可用性的角度考慮咐蚯,我們使用了S3作為registry的storage backend童漩。最終的private docker registry架構(gòu)如下:
- 當(dāng)其中一個(gè)AWS賬戶下的某個(gè)用戶/role需要訪問registry權(quán)限時(shí),你可以按照固定的格式修改config repo春锋,提交PR矫膨,merge后自動(dòng)部署到一個(gè)config s3 bucket。
- 一個(gè)定時(shí)運(yùn)行的Lambda函數(shù)會(huì)從這個(gè)bucket讀取配置文件期奔,并為具體的用戶/role生成訪問的token以及授權(quán)侧馅,并將token保存在另外一個(gè)token的s3 bucket中,同時(shí)授予這個(gè)用戶/role訪問這個(gè)token文件的權(quán)限能庆。
- 用戶在使用時(shí)施禾,首先用自己的用戶名密碼獲得session,之后從token的bucket中下載token文件搁胆,通過
docker login
覆寫~/.docker/config.json
弥搞,再進(jìn)行pull/push docker image的操作。 - 在服務(wù)器(EC2 instance)上部署時(shí)需要給instance綁定instanceProfile讓它可以從token的bucket拿到這個(gè)token
- 為了加快訪問速度渠旁,可以直接從保存docker image的S3 bucket中拿到docker image的tar包
總結(jié)
目前攀例,我們在生產(chǎn)環(huán)境已經(jīng)有100個(gè)左右的微服務(wù)使用docker去做部署(基于我們定制的一個(gè)類PaaS的部署工具)。如果我們回頭再來看這樣的使用docker的運(yùn)行應(yīng)用的模式顾腊,會(huì)發(fā)現(xiàn)似乎應(yīng)用只需要服務(wù)器有運(yùn)行時(shí)環(huán)境以及計(jì)算資源粤铭,如果我的應(yīng)用邏輯簡單,那么有沒有可能由這樣一種服務(wù):
- 它有一些standby的具有運(yùn)行代碼運(yùn)行時(shí)環(huán)境的容器
- 可以在請求過來時(shí)杂靶,以很短的時(shí)間(ms級別)內(nèi)梆惯,啟動(dòng)包含/部署代碼的容器然后處理請求。
這這樣的服務(wù)就像PaaS一樣吗垮,基礎(chǔ)設(shè)施對我不可見垛吗,服務(wù)器維護(hù)成本為零,同時(shí)只要關(guān)注在業(yè)務(wù)代碼即可烁登。這就是現(xiàn)在經(jīng)常提到的Serverless的概念怯屉,下一節(jié)我們就來介紹Serverless、其目前適用的場景以及目前唯一比較成熟的Serverless服務(wù)AWS Lambda。