利用Docker輕鬆實現雲原生應用-高可用架構設計

易立發表於2016-11-06

14784209934912

本文為利用Docker和容器服務輕鬆實現雲原生應用系列的第一篇

最近對應用遷雲的討論很多,很多使用者對雲環境中的應用架構和運維方式還不瞭解。直接利用雲伺服器替換自有物理機並不是使用雲的正確姿勢。

Cloud Native Application(雲原生應用)是當下一個熱門名詞,簡單而言就是針對雲端計算的特性,來設計應用架構,並優化應用的交付、運維流程。Linux基金會旗下的雲原生計算基金會 CNCF(Cloud Native Computing Foundation)開宗明義地描述了雲原生系統所具有的幾個關鍵特性

  • Container packaged:容器化的交付方式,保證開發、交付和運維的一致性
  • Dynamically managed:自動化的管理,提升系統利用率、降低運維成本
  • Micro-services oriented:鬆耦合應用架構,提升系統的敏捷性和可維護性。

雲原生應用可以滿足我們的幾個關鍵訴求:

  1. 高可用:讓應用“傷得起”。 系統中沒有脆弱的故障單點,另外具有良好的自我恢復能力。
  2. 彈性伸縮:能夠讓應用從容應對峰值流量
  3. 快速迭代:天下武功唯快不破。在網際網路時代,快速迭代、最小化試錯成本是核心競爭力。

下面我們將介紹,如何利用Docker和阿里雲容器服務在雲端實現應用的高可用。

應用架構設計

雲原生應用中一個很重要的理念就是解耦

  • 架構層面:通過微服務架構方法將應用邏輯分解為一組鬆耦合的服務,使得每個服務可以獨立開發、部署、演進和伸縮
  • 實現層面:強調應用層和資料層的分離,只有實現業務邏輯和持久化狀態的解耦,才能讓業務邏輯可以水平擴充套件,並且出現故障的時候可以快速恢復。
  • 交付層面:通過容器來解耦應用和執行時環境,使得應用可以在不同的環境中可以重複、一致地交付、部署、運維,從而更好地支援DevOps和彈性。

微服務架構設計是一個很大的話題,有興趣的同學可以參考微服務設計12 factors應用開發方式。我們今天只是談應用層和資料層的分離。一方面它可以使得應用邏輯變成無狀態的,支援水平擴充套件;另一方面區分無狀態和有狀態服務,可以使我們針對不同工作負載實現效能優化和擴充套件。

以前的兩篇博文中介紹瞭如何將一個Ghost部落格應用,重構成一個符合雲原生應用架構的過程。

最初Ghost應用是利用嵌入式的SQLite來儲存資料,並將所有媒體檔案(影像、視訊)儲存到本地檔案上。這個應用的效能受限於單機能力,無法水平擴充套件,也無法保證高可用。

而簡單的重構之後,我們將應用的資料層剝離出來:把關係型資料儲存在RDS的MySQL例項上,並將所有媒體檔案儲存到OSS物件儲存之中。這樣配合SLB的負載均衡,可以輕鬆地支援Ghost部落格叢集的水平擴容。同時由於所有資料不再儲存到本地,這樣即使特點ECS例項失效,請求也可以交由其他節點應用接管。而資料的可用性是通過RDS服務、OSS儲存服務來保障的。

14782409698005

相應的Ghost的docker-compose模板如下

ghost: 
  image: registry.aliyuncs.com/acs-sample/ghost:0.7
  links: 
    - db:mysql 
  ports: 
    - "2368"
  environment:
    - GHOST_URL=http://blog.${ACS_DEFAULT_DOMAIN}
    - OSS_BUCKET=acs-sample-ghost
    - OSS_BUCKET=acs-sample-ghost
    - OSS_ACCESS_KEY_ID=***********
    - OSS_ACCESS_KEY_SECRET=***********
    - OSS_PREFIX=http://acs-sample-ghost.oss-cn-beijing.aliyuncs.com
    - OSS_ENDPOINT=http://oss-cn-beijing.aliyuncs.com
  labels:
    aliyun.routing.port_2368: `http://blog`
    aliyun.scale: `3`
  restart: always 
db: 
  external: 
    host: rds******.mysql.rds.aliyuncs.com
    ports:
      - 3306
  environment: 
    - MYSQL_DATABASE=blog
    - MYSQL_USER=ghost
    - MYSQL_PASSWORD=***********

阿里雲擴充套件的註釋:

  • 標籤 aliyun.routing.port_xxxx : 定義的虛擬域名訪問方式
  • 標籤 aliyun.scale: 定義了容器複本數量
  • external: 描述了容器對外部雲服務的引用,可以方便地將容器和雲服務組合在一起。

需要注意的是Docker不是銀彈,僅依賴容器技術無法解決所有問題。使用者需要認真思考和判斷什麼樣的應用可以執行在容器中。容器非常適合部署無狀態的應用,因為這類應用可以方便地水平擴充套件和動態排程。然而對於有狀態應用,比如資料庫、訊息佇列,我的建議是可以在開發測試環境和非關鍵應用的生產環境中使用容器;然而對於核心應用,建議採用雲服務提供的高可用中介軟體能力來實現。

部署架構設計

解決了架構層面的水平伸縮能力和高可用,我們下面討論一下應用部署中需要關注的可用性因素。

程式監控

程式監控是最常用的可用性保證機制,傳統上是利用Supervisor, Monit等工具來監控應用程式,並自動拉起失效程式。

Docker內建了程式監控能力,可以利用 docker run --restart=always ... 命令引數,或者在Compose模板中通過 restart: always 指明程式重啟策略。

以上文為例,當Ghost容器執行之後,Docker Daemon會監控容器中PID1的NodeJS程式,如果程式退出會自動重啟容器。可以參考restart policy瞭解更多資訊。

然而僅僅根據程式狀態判斷應用的健康狀態是不夠的:因為有時候由於應用邏輯問題導致應用死鎖或掛起,雖然程式還處於執行狀態,但是已經無法接受新的請求。為此容器服務引入了服務的健康檢查機制,可以通過

目前支援的健康性設定包括:

  • 標籤 aliyun.probe.url: 支援通過HTTP/TCP協議的URL請求對容器進行健康檢查
  • 標籤 aliyun.probe.cmd: 通過 shell 指令碼檢查對容器進行健康檢查

通過上面的方式,容器服務可以更加細粒度地判斷應用健康狀態,並會根據健康資訊實現應用可用性的優化控制,比如:

  1. 應用路由:如果服務容器狀態不健康,該容器會被從負載均衡上摘除,保障後續請求可以路由到健康的容器中。
  2. 容器依賴:Docker Compose一個缺陷是隻能描述容器之間的啟動順序,但是不能保證所依賴的容器已經正常執行,很多同學都為此吃過苦頭。而當容器服務部署應用模板時,如果指明瞭aliyun.depends標籤或者使用Compose V2中的depends_on指令,只有當所依賴的容器進入健康狀態,容器才會被啟動。這樣可以非常優雅地保證應用中容器的啟動順序。
  3. 平滑升級:在升級過程中,為了保證應用訪問不被中斷,系統需要當一個容器更新完畢並健康執行之後再更新下一個容器。這個行為可以通過aliyun.rolling_updates標籤來非常簡單的配置。

可以通過容器服務幫助文件瞭解更多詳細資訊

在Docker 1.12之後,Docker Engine將內建容器的健康檢查,可以通過命令方式支援容器的健康狀態檢測,並自動重啟失效容器。容器服務已經實現對Docker 1.12的支援和相容。

高可用部署架構

一個常見誤區是:使用者認為應用的高可用性可以完全由雲服務商的基礎設施保證,自己無需關注。而真實情況是:雖然雲服務提供了高可靠的基礎設施,還要合理配置組合才能保障應用的可用性。

在雲端和在自有物理機上部署應用有很多不同。在自有資料中心中,有經驗的運維人員會根據應用負載特性選擇合適的物理機。比如,根據應用的可用性要求,把應用部署到不同的物理伺服器或不同機架的物理伺服器上。然而在公共雲和專有云中,由於虛擬化層支援虛擬機器的遷移甚至熱遷移,我們不應也不能再試圖將應用和其部署位置簡單繫結。

正確的姿勢是:在理解雲供應商提供的隔離性、可用性保證機制上,根據應用負載特徵設計部署架構,並讓系統來實現動態管理。

對可用性而言,在阿里雲上可以有多種部署方式:跨多個ECS例項部署應用,防止單例項失效導致的應用無法訪問;跨多個可用區(Availability Zone)的方式可以防止機房整體失效導致的應用中斷;而跨多個地域(Region)的部署方式可以保證,即使一個地域的服務無法訪問時,應用也可正常執行。此外阿里雲上的RDS,SLB等雲服務都提供了跨多個可用區的服務能力。關於雲端高可用架構可以參見 雙11技術攻略:企業雲架構的正確姿勢

隨著微服務架構的引入,應用的部署更加富有挑戰性。首先微服務和傳統應用相比,部署元件的數量可能有10倍以上的區別;而且由於每個服務都支援快速的演進和動態地伸縮,除了需要更好地服務治理能力,也需要系統提供自動化的部署和管理能力保障應用SLA。

阿里雲容器服務為Docker應用提供了內建的高可用支援,可以更好地支援微服務應用部署。

對於容器服務中的叢集,使用者可以選擇新建ECS例項或者手動新增已有ECS例項,並支援手動和彈性擴容。如果使用者應用關注網路效能,則可以選擇自於同一個可用區的ECS例項,這樣例之間的網路延遲會最小化。如果使用者應用關注於高可用性。我們可以為叢集新增來自多個可用區的ECS例項,配合SLB,RDS等多可用區支援能力,我們就會有一個高可用的容器基礎架構。

下面是一個典型的高可用的容器叢集的配置。其中ECS例項和RDS資料庫例項部署在不同可用區之上。哪怕一個機房掉電也不會影響到叢集中應用的可用性。

14782552058492

然而這還不夠,因為在一個叢集上的ECS例項上,通常會執行成百上千個應用。在排程容器高效地利用ECS節點資源的同時,如何避免由ECS節點/可用區失效導致的應用訪問失敗?

阿里雲容器服務完全相容Docker Swarm的叢集管理和資源排程能力。Swarm允許在容器部署時,通過宣告約束條件來指定容器和容器,容器和節點之間的親和性和反親和性。詳細資訊可以參考文件

比如,下面的命令可以讓兩個redis容器部署在不同的節點上

docker run -d --name redis_1 -e `affinity:container!=redis_*` redis
docker run -d --name redis_2 -e `affinity:container!=redis_*` redis

阿里雲容器服務在此基礎之上提供了更加豐富而簡便的的服務級別的親和性約束。在compose模板中,服務定義了一組容器的集合,它們擁有一致的映象和配置。

如果指明瞭 aliyun.scale標籤,相同服務的不同容器複本會盡量分佈到不同節點之上,防止因為ECS例項失效導致該服務所有容器全部無法訪問。

服務之間可以通過 affinity:service標籤來描述服務直接容器的親和性。比如下面的模板,描述了一個MySQL主從結構中,MySQL master和slave容器不會排程到一個ECS例項上

master:
  image: tutum/mysql:5.6
  environment:
    - MYSQL_USER=user
    - MYSQL_PASS=test
    - REPLICATION_MASTER=true
    - REPLICATION_USER=repl
    - REPLICATION_PASS=repl
    - affinity:service!=slave
slave:
  image: tutum/mysql:5.6
  environment:
    - MYSQL_USER=user
    - MYSQL_PASS=test
    - REPLICATION_SLAVE=true
    - affinity:service!=master
  links:
    - master:mysql

還有一個方式通過容器和節點的親和性,可以細粒度的指定如果將容器部署到指定節點之上。可以參見分散式Zookeeper叢集瞭解其中示例。

為了保證服務有更高的可用性,我們可以將服務中的容器排程在不同的可用區(availability zone)裡。

比如,通過 availability:az標籤,我們可以非常簡單地把如下web服務的Nginx容器部署到2個可用區之中。

web:
  image: `nginx:latest`
  environment:
    - availability:az==2
  labels:
    aliyun.scale: `8`
    aliyun.routing.port_80: `web`
  restart: always

一個容器叢集可以保證容器應用在節點或可用區級別的可用性。如果使用者需要達到更高階別的可用性,可以在不同地域建立容器叢集,並分別部署應用。阿里雲容器服務也提供混合雲能力,支援使用者在自有資料中心和雲端分別建立容器叢集,並用統一的方式進行應用部署和管理。其簡單部署架構如下:

14784021578493

自動恢復能力

在分散式架構中,節點失效是不可避免的。高可用部署讓應用可以容忍一定的失效;另一方面我們需要能夠讓應用從故障狀態中自動恢復回來。就像X戰警中的金剛狼,雖然戰鬥力可能不是最強,但是超強恢復力讓他成為決定戰鬥命運的角色。

阿里雲容器服務支援容器自動重新排程策略。也就是當容器所屬宿主機節點失效時,系統可以將該容器自動遷移到其他健康節點上繼續執行,從而保證應用的SLA。

容器服務提供相容 Docker Swarm 的容器重排程策略。 在下面一個示例中,Redis服務開啟了重排程策略

redis:
  image: redis
  labels:
    - com.docker.swarm.reschedule-policies=["on-node-failure"]

什麼樣的容器支援重排程?我們需要分析容器中應用對持久化狀態的依賴。無狀態應用可以簡單地被排程到任何節點上正確執行的;而如果容器應用依賴於宿主機的本地儲存,則是無法被重排程的。對於後者,需要進一步分離應用層和資料層邏輯才能支援遷移。比如在阿里雲容器服務中,容器可以利用檔案卷外掛(volume plugin)提供的網路儲存能力(NAS/NFS和OSSFS)來共享和儲存資料。一旦節點失效,容器被排程到其他節點,容器服務會保證其相應檔案卷的正確掛載。這樣應用可以從網路中獲得已儲存的資料並繼續執行。

總結

雲原生應用在架構、開發、運維等方面和傳統軟體應用有很多不同。利用Docker和阿里雲容器服務可以輕鬆解決高可用部署和故障恢復的複雜性。

阿里雲容器服務在相容Docker原生編排技術的同時,提供了針對阿里雲能力的優化和整合。開發者可以利用宣告式的方式來描述容器應用的部署約束,以及整合阿里雲服務,從而滿足對可用性SLA的要求。

想了解更多容器服務內容,請訪問 https://www.aliyun.com/product/containerservice


相關文章