<b>摘要:</b>在雲栖techday34期:dockercon2017最新的技術解讀中,阿裡巴巴技術專家譚林華為大家介紹了docker的最新特性以及與傳統場景相比,這些新特性所具有的優勢和所能夠解決的問題。
<b>以下内容根據演講嘉賓現場視訊以及速記整理而成。</b>
<b>演講嘉賓介紹:</b>
譚林華(花名:霖華),阿裡雲容器服務團隊技術專家,具有多年paas産品研發經驗,在平台設計、基礎架構等方面具有深厚的功底,docker技術實踐者,目前負責阿裡雲容器服務鏡像建構等功能。
本次将分享關于docker新特性如下的幾個點:
多階段建構
資源管理
docker secrets
swarm mode
健康檢查
首先介紹docker新推出的多階段建構;其次介紹一下docker在資源管理方面新加的一些指令,這些指令可以友善開發者來做資源管理;還有就是介紹一下docker secret跟之前那些密碼管理的方式有什麼不同;還有就是swarm mode裡面的一些新特性;最後再介紹一下swarm mode下面的健康檢查。
首先看一下多階段建構,建構鏡像了解docker的同學應該比較熟悉。現在想象一個在java語言下建構鏡像的場景,這個場景下開發者送出代碼,然後一個構模組化塊,之後就去拉取github或者其他源代碼倉庫的代碼,最後執行建構。像對這種靜态語言的建構而言,其實過程會稍微麻煩一點,比如說要有編譯器對它進行編譯,然後跑單元測試,然後打包,打包到最後就生成了war包或者jar包,最後推到registry中去。這個過程有一個問題就是如果把所有的内容都放在一個dockerfile裡面,源代碼就會包含在鏡像裡面,是以這裡有源代碼洩露的風險。還有就是具體的生産環境其實是不需要那些編譯器源代碼以及測試架構或者打包架構的,最終可能隻需要一個簡單的運作環境就可以了,這樣會導緻鏡像變得特别大,是以這個方案不是特别好。

再來看一下docker在5月份出來的一個最新的17.05版本,這個版本中引入了multi-stage build,也就是多階段建構。它的思路就是把剛剛提到的在建構鏡像的過程拆成兩個階段,這兩個階段都會産生鏡像,第一個階段就是去執行編譯、測試然後打包,得到一個鏡像,對于第一階段得到的鏡像,并不會使用這個鏡像的全部内容;在第二階段,可以把第一個階段的jar包拷貝到第二個階段,這樣的好處就是沒有源代碼洩露的風險,因為最終打包到生産環境的隻有最終編譯的位元組碼檔案。同時整個鏡像也變得很小,因為它裡面沒有包含源代碼也沒有包含一些編譯器的軟體、測試架構和打包工具,是以這個方案是比較完美的。
然後看一下它最終的實作,上圖展現的主要是設計思路,最終實作是通過加入一些簡單的文法來支援這個功能的。下圖顯示的就是一個dockerfile,它分為兩個建構階段,兩個建構階段都會生成鏡像,然後第二個建構鏡像是通過引用第一個建構鏡像的方式,把第一個建構鏡像裡面的jar包打到第二個鏡像裡面。主要使用了copy指令,這裡面有個from參數,from參數引用了第一個建構鏡像的名稱,dockerfile增加了一個新的文法就是as指令,上面這個紅框中from指令就相當于給這個建構階段取了一個名字叫build-env,然後在第二個建構階段裡面就引用它,相當于把第一個建構鏡像裡面target目錄下面的app.jar拷貝到目前鏡像的工作目錄中,這樣的好處就是最終隻包含了運作環境和部署到生産環境的jar包,是以整個鏡像也變小了,也沒有代碼洩露的風險。
下圖展現了在docker 1.13中引入的一些資源管理指令,比如docker system df,它可以檢視整個容器叢集裡面資源的使用情況,包括鏡像的使用情況以及資料卷的使用情況。還有就是以前要删除鏡像或者删除容器可能都比較麻煩,需要指定鏡像id或容器id,現在它提供了docker system prune的指令,可以很友善地一鍵清理所有沒有被使用或者沒有被引用的資源,這些資源包括所有停止的容器,還有就是所有沒有被引用的鏡像,以及資料卷和網絡資源對象。沒有被引用的鏡像是什麼意思呢?這裡用了dangling這個英文單詞,稍後會具體介紹。還有就是在一鍵清理這些資源的同時,可以針對某個特定的資源進行清理,也就是第三條指令,可以清理所有沒有被使用的容器鏡像、網絡或者資料卷。
我們來了解一下dangling的含義,在17.05的時候,其實dangling的含義發生了一點點變化。之前dangling的含義指的是同時沒有版本和名稱的鏡像,這個鏡像版本指的是就是鏡像tag,像這種情況它會認為是dangling的鏡像,是以在使用prune的時候會把它删掉。現在dangling的含義也包括有鏡像名稱,但是沒有鏡像版本的鏡像。這種情況最常見于要建構相同tag的鏡像時,新建構的鏡像會把老的覆寫掉,最後它的鏡像tag會變為none,是以這種情況在prune中删掉也是合理的。
接下來介紹一下docker secrets,secrets這個特性的引進還算比較早,在1.13版本就引入了。大家可以了解一下在引入docker secrets之前是怎麼樣傳入一些密碼這樣的敏感資訊的,比如下圖這裡就是一個簡單的compose檔案,這個檔案裡面就是通過環境變量的方式把密碼傳進去,而這樣的方式有一個問題就是不是很安全,還有就是也不适合權限管理,因為有些程式員可能不需要知道密碼裡面具體的内容,隻需要去部署這個compose檔案,是以這裡有這樣的兩個缺點。
docker secrets試圖解決這兩個問題,首先它的整個密碼傳輸的過程都是通過tls加密的,就是使用者如果要把它密文通過docker secrets的方式儲存的話,它最後會儲存到中心化的存儲上,然後當某個服務要引用docker secrets的時候,它會通過tls的方式傳輸到各個節點上,并且已經是以記憶體檔案系統的形式挂載到容器上。
下圖展示的是具體的compose模闆的例子。在這個compose模闆裡邊其實并沒有secrets密碼的明文儲存,例如這個地方定義了secrets,聲明secrets的引用是來自于外部,包括這個wp_db_password和root_db_password,并且聲明了它的使用,還有就是它具體的使用方法,最後所有的docker secrets對象都會挂載到容器裡面的run/secrets目錄下面,這個目錄是固定的,後邊還要加上secrets的名稱。
docker 17.05還加入了docker service logs。之前在檢視容器的日志或者任務的日志的時候,比如在排查問題時,除非用了别的工具,要不然經常是需要挨個任務或者挨個容器進行檢視,docker service logs就試圖解決這個問題。這個 service logs通過服務次元把所有任務的日志列出來,例如下圖中就表明了redis服務的很多任務。然後将它整個日志會按時間排序聚合起來,這樣的好處就是聚合該服務下所有日志後,當進行調試時就可以友善地查找這些日志。還有就是當需要把這些日志儲存到一個集中的地方的時候,通過這個接口也可以直接導到别的地方,而不用把每個容器的日志再收集一遍。
再來看一下swarm mode下service的另外一個特性,就是service create和service update這兩個指令。之前使用的docker create和docker update都是異步的,就是建立或者更新完資源之後,這個指令調用完之後會立馬傳回。隻有通過service ls或者service ps的方式來察看這個任務是處于運作狀态還是失敗狀态。但是現在它加了一個參數叫detach,detach等于false就表明這個指令是在同步的狀态下執行的,是以它會同步地傳回,這指的是它建立的所有任務會顯示狀态,然後一直等到它們處于running狀态或者運作失敗狀态時,才會最終傳回停止。下圖是兩個示例,就是跑了一個redis服務,它會等所有任務傳回之後再停止。
17.05版本的另一個比較有亮點的特性就是拓撲結構感覺的排程。關于排程這塊,先來看一下swarm mode之前的排程方式。假設在兩個可用區下面一共有三個節點,可用區1有兩個節點,可用區2有一個節點,如果使用service create指令建立一個web app的時候,指定下面有六個任務,是以service create預設使用的是spread的排程政策,它會在每個節點上平均配置設定容器,是以最終導緻的結果就是可用區1有四個容器,可用區2有兩個容器。但是事實上為保證可用性,通常會傾向于每個可用區都有一半的容器,也就是可用區1和可用區2各有三個,這樣當可用區1壞掉之後,可用區2能有三個容器來提供服務和支援,特别是在一些流量比較高的場景下,三個容器比兩個容器會有更高的可用性支援。是以在這種情況下用傳統的swarm排程方式是比較麻煩的,如果預設用spread排程方式就隻能夠每個節點兩個容器配置設定,還有一種方式就是用constrain,constrain存在一個問題就是必須辨別每個節點,比如說指定把三個容器部署到node 3,然後再把剩下的三個容器部署到node 1和node 2,這種方式其實跟按照可用區排程是不符合的。
那麼引入的新特性是怎麼做這件事情的呢?在service create的時候增加了一個placement-pref參數,這個參數有spread等于後面的label,這裡的意思是什麼呢?就是說采用的排程政策還是跟之前一樣用spread,由于均分的排程政策,但是具體排程是按照某個label表示的可用區來排程。這個叢集會對于下面所有具有labels.az的節點,檢視它們的label有幾個值,比如說現在有兩個值az1和az2,會按照這兩個值來均分整個的排程政策,最終的結果就會在az1上排程三個容器,然後az2也排程三個容器,就達到了按照可用區域來均分容器的目的。
最後來看一下docker在健康檢查這塊引入的改動,健康檢查其實是已經存在比較久了,在1.12中就引入了。先來了解一下健康檢查使用的指令,健康檢查有兩種設定的方式,可以在dockerfile中就是建構鏡像的過程中設定預設的健康檢查,然後通過健康檢查的指令來執行,是以通過shell指令支援的方式,就可以通過各種方式來檢測應用程式和容器的健康性。還有就是在compose file裡面可以對一些檢查的參數進行細粒度的調整,例如健康檢查的間隔時間以及逾時時間,還有就是連續出現多少次失敗才認為是不健康的。在17.05加了一個參數start period,舉個例子解釋這個啟動參數的意義所在,就是比如配置好mysql資料庫啟動起來大概要個二、三十秒,它的健康檢查間隔時間可能需要5秒或3秒,這種場景就比較适合用start period,這樣的健康檢查比較符合剛剛說的mysql啟動的特性,當然還有一些其他類似的程式。
接下來看一個具體的例子,下圖展示的是對redis做健康檢查,就是有一個檔案叫docker-healthcheck,是用這個檔案來檢查redis。redis的用戶端發一個ping的指令,當伺服器端傳回pong時健康檢查就通過了,否則健康檢查失敗。下面是健康檢查涉及的dockerfile,就是最下面這一行。
健康檢查有什麼用呢,之前提到可以通過docker ps來檢視任務或者容器是否處于健康狀态,但是現在比如這裡的redis它後面會有一個狀态告訴你這個容器是否健康。同時這個健康檢查還會跟swarm mode排程結合在一起,這是什麼意思呢?就是說如果swarm根據你設定的健康檢查指令發現任務失敗了,它就會認為這個任務健康檢查不通過,但是這個任務還在運作中,它會主動地把運作中的這個任務退出,然後再新起一個任務,比如你的任務指定了一定要始終保持三個拷貝,然後它就始終會有三個拷貝在運作狀态,還有就是它在啟動時會保證去檢查健康狀況。