雲原生時代 RocketMQ 運維管控的利器 - RocketMQ Operator

2021-01-07 阿里云云棲號

作者 | 劉睿、杜恆

導讀:RocketMQ Operator 現已加入 OperatorHub,正式進入 Operator 社區。本文將從實踐出發,結合案例來說明,如何通過 RocketMQ Operator 在 Kubernetes 上快速搭建一個 RocketMQ 集群,並提供一些 RocketMQ 集群管理功能包括 Broker 擴容等。

本文主要分為三個部分:

首先簡單介紹一下 RocketMQ Operator 的相關知識;然後結合案例詳細介紹 RocketMQ Operator 提供的自定義資源及使用方法;最後介紹 Operator 社區目前的情況並展望 RocketMQ Operator 下一步的發展方向。相關背景知識

1. RocketMQ

2012~2013 年期間,阿里巴巴中間件團隊自主研發並對外開源了第三代分布式消息引擎 RocketMQ,其高性能、低延遲、抗堆積的特性穩定支撐了阿里巴巴 雙11 萬億級數據洪峰業務,其雲產品 Aliware MQ 在微服務、流計算、IoT、異步解耦、數據同步等無數工況場景大放異彩。

2016 年,阿里巴巴向 Apache 軟體基金會捐贈了 RocketMQ。次年,RocketMQ 順利從基金會畢業,成為 Apache 頂級開源項目,與 Apache Hadoop,Apache Spark 一起為全球分布式、大數據領域的開發者帶來福音。然而,在雲原生時代的今天,RocketMQ 作為有狀態的分布式服務系統,如何在大規模集群上做到極簡運維,則是一個極具挑戰和價值的問題。

RocketMQ 支持多種部署方式,以基本的雙主雙從架構為例,如下圖所示。

RocketMQ 雙主雙從架構

這裡面包括了一共 7 個 RocketMQ 服務實例:3 個 name server 實例,2 個 master broker 實例,以及 2 個 slave broker 實例。

傳統的部署方式需要手動或編寫腳本在每個節點上進行環境和文件配置。此外,隨著用戶業務的增加,存在對集群進行無縫擴容等需求。傳統方式是運維人員訪問不同節點,依賴操作手冊和腳本按步驟進行操作來完成,耗費人力,且存在誤操作的可能。一些公司可能會使用一些平臺和工具如 Ansible 來幫助自動化運維,此外越來越多的公司開始集成和使用基於 Kubernetes 的雲原生生態。

使用 Kubernetes 提供的 Deployment 和 StatefulSet 等原生資源可以很好地解決無狀態應用的管理問題,但對於資料庫和 RocketMQ 這類有狀態應用,則存在很多局限性。例如對 RocketMQ 來說擴容不僅僅是拉起新的實例 Pod 就完成了,還需要同步複製 Broker 的狀態信息包括 Topic 信息和訂閱關係這些元數據,同時要正確配置新 Broker 的 config 參數,包括 brokerName 和 NameServer IP List 等,才能使得新擴容的 Broker 可用,而這些僅僅靠用戶編寫 StatefulSet,修改 size 或 replicas 然後 apply 是無法做到的。

實際上 Kubernetes 開發人員也發現了這些問題,因此引入了自定義資源和控制器的概念,讓開發人員可以直接用 Go 語言調用 Kubernetes API,編寫自定義資源和對應的控制器邏輯來解決複雜有狀態應用的管理問題,提供特定應用相關的自定義資源的這類代碼組件稱之為 Operator。由具備 RocketMQ 領域知識的專家編寫 Operator,屏蔽了應用領域的專業知識,讓用戶只需要關心和定義希望達到的集群終態,這也是 Kubernetes 聲明式 API 的設計哲學。

2. Kubernetes Operator

Operator 是在 Kubernetes 基礎上通過擴展 Kubernetes API,用來創建、配置和管理複雜的有狀態應用,如分布式資料庫等。Operator 基於 Kubernetes 1.7 版本以來引入的自定義控制器的概念,在自定義資源和控制器之上構建,同時又包含了應用程式特定的領域知識。實現一個 Operator 的關鍵是 CRD(自定義資源)和 Controller(控制器)的設計。

Operator 站在 Kubernetes 內部視角,為應用的雲原生化打開了新世界的大門。自定義資源可以讓開發人員擴展添加新功能,更新現有的功能,並且可以自動執行一些管理任務,這些自定義的控制器就像 Kubernetes 原生的組件一樣,Operator 可以直接使用 Kubernetes API 進行開發,也就是說他們可以根據這些控制器編寫的自定義規則來創建和更改 Pods / Services、對正在運行的應用進行擴縮容。

快速開始

本文使用 RocketMQ Operator 0.2.1 版本,展示如何使用 RocketMQ Operator 在 Kubernetes 上快速創建部署一個 RocketMQ 服務集群。

準備好 K8s 環境,可以使用 docker desktop 自帶的 K8s,或者 minikube;克隆 rocketmq-operator 倉庫到你的 K8s 節點上;

$ git clone https://github.com/apache/rocketmq-operator.git$cd rocketmq-operator

運行腳本安裝 RocketMQ Operator;

$ ./install-operator.sh

檢查下 RocketMQ Operator 是否安裝成功

$ kubectl get podsNAME READY STATUS RESTARTS AGErocketmq-operator-564b5d75d-jllzk 1/1 Running 0108s

成功安裝時,rocketmq-operator pod 處於類似上面例子的 running 狀態。

應用 Broker 和 NameService 自定義資源,創建 RocketMQ 集群;應用 rocketmq-operator / example 中的 rocketmq_v1alpha1_rocketmq_cluster.yaml 文件,快速部署一個 RocketMQ 集群。rocketmq_v1alpha1_rocketmq_cluster.yaml 文件內容如下:

apiVersion: rocketmq.apache.org/v1alpha1kind: Brokermetadata: # name of broker cluster name: brokerspec: # size is the number of the broker cluster, each broker cluster contains a master broker and [replicaPerGroup] replica brokers. size: 1 # nameServers is the [ip:port] list of name service nameServers: "" # replicationMode is the broker replica sync mode, can be ASYNC or SYNC replicationMode: ASYNC # replicaPerGroup is the number of each broker cluster replicaPerGroup: 1 # brokerImage is the customized docker image repo of the RocketMQ broker brokerImage: apacherocketmq/rocketmq-broker:4.5.0-alpine # imagePullPolicy is the image pull policy imagePullPolicy: Always # resources describes the compute resource requirements and limits resources: requests: memory: "2048Mi" cpu: "250m" limits: memory: "12288Mi" cpu: "500m" # allowRestart defines whether allow pod restart allowRestart: true # storageMode can be EmptyDir, HostPath, StorageClass storageMode: EmptyDir # hostPath is the local path to store data hostPath: /data/rocketmq/broker # scalePodName is broker-[broker group number]-master-0 scalePodName: broker-0-master-0 # volumeClaimTemplates defines the storageClass volumeClaimTemplates: - metadata: name: broker-storage spec: accessModes: - ReadWriteOnce storageClassName: rocketmq-storage resources: requests: storage: 8Gi---apiVersion: rocketmq.apache.org/v1alpha1kind: NameServicemetadata: name: name-servicespec: # size is the the name service instance number of the name service cluster size: 1 # nameServiceImage is the customized docker image repo of the RocketMQ name service nameServiceImage: apacherocketmq/rocketmq-nameserver:4.5.0-alpine # imagePullPolicy is the image pull policy imagePullPolicy: Always # hostNetwork can be trueorfalse hostNetwork: true # Set DNS policy for the pod. # Defaults to"ClusterFirst". # Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default'or'None'. # DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. # To have DNS options set along with hostNetwork, you have to specify DNS policy # explicitly to'ClusterFirstWithHostNet'. dnsPolicy: ClusterFirstWithHostNet # resources describes the compute resource requirements and limits resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1024Mi" cpu: "500m" # storageMode can be EmptyDir, HostPath, StorageClass storageMode: EmptyDir # hostPath is the local path to store data hostPath: /data/rocketmq/nameserver # volumeClaimTemplates defines the storageClass volumeClaimTemplates: - metadata: name: namesrv-storage spec: accessModes: - ReadWriteOnce storageClassName: rocketmq-storage resources: requests: storage: 1Gi

注意到這個例子中 storageMode: EmptyDir,表示存儲使用的是 EmptyDir,數據會隨著 Pod 的刪除而抹去,因此該方式僅供開發測試時使用。一般使用 HostPath 或 StorageClass 來對數據進行持久化存儲。使用 HostPath 時,需要配置 hostPath,聲明宿主機上掛載的目錄。使用 storageClass 時,需要配置 volumeClaimTemplates,聲明 PVC 模版。具體可參考 RocketMQ Operator 文檔。

應用上面的 yaml 文件,輸入命令:

$ kubectl apply -f example/rocketmq_v1alpha1_rocketmq_cluster.yamlbroker.rocketmq.apache.org/broker creatednameservice.rocketmq.apache.org/name-service created

查看集群 Pod 狀態:

$ kubectl get pods -owideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESbroker-0-master-01/1 Running 027s 10.1.2.27 docker-desktop <none> <none>broker-0-replica-1-01/1 Running 027s 10.1.2.28 docker-desktop <none> <none>name-service-01/1 Running 027s 192.168.65.3 docker-desktop <none> <none>rocketmq-operator-76b4b9f4db-x52mz 1/1 Running 03h25m 10.1.2.17 docker-desktop <none> <none>

使用默認的 rocketmq_v1alpha1_rocketmq_cluster.yaml 文件配置,我們看到集群中拉起了 1 個 name server 服務(name-service-0)和 2 個 broker 服務(1 主 1 從)。

好啦!到這裡你已經成功通過 Operator 提供的自定義資源部署了一個 RocketMQ 服務集群。

訪問這個 RocketMQ 集群中的 Pod 來驗證集群是否能正常工作;使用 RocketMQ 的 tools.sh 腳本運行 Producer example:

$ kubectl exec -it broker-0-master-0 bashbash-4.4# sh ./tools.sh org.apache.rocketmq.example.quickstart.ProducerOpenJDK 64-Bit Server VM warning: ignoring option PermSize=128m; support was removed in 8.0OpenJDK 64-Bit Server VM warning: ignoring option MaxPermSize=128m; support was removed in 8.006:56:29.145 [main] DEBUG i.n.u.i.l.InternalLoggerFactory - Using SLF4J as the default logging frameworkSendResult [sendStatus=SEND_OK, msgId=0A0102CF007778308DB1206383920000, offsetMsgId=0A0102CF00002A9F0000000000000000, messageQueue=MessageQueue [topic=TopicTest, brokerName=broker-0, queueId=0], queueOffset=0]...06:56:51.120 [NettyClientSelector_1] INFO RocketmqRemoting - closeChannel: close the connection to remote address[10.1.2.207:10909] result: truebash-4.4#

在另一個節點上運行 Consumer example:

$ kubectl exec -it name-service-0 bashbash-4.4# sh ./tools.sh org.apache.rocketmq.example.quickstart.ConsumerOpenJDK 64-Bit Server VM warning: ignoring option PermSize=128m; support was removed in 8.0OpenJDK 64-Bit Server VM warning: ignoring option MaxPermSize=128m; support was removed in 8.007:01:32.077 [main] DEBUG i.n.u.i.l.InternalLoggerFactory - Using SLF4J as the default logging frameworkConsumer Started.ConsumeMessageThread_1 Receive New Messages: [MessageExt [queueId=0, storeSize=273, queueOffset=19845, sysFlag=0, bornTimestamp=1596768410268, bornHost=/30.4.165.204:53450, storeTimestamp=1596768410282, storeHost=/100.81.180.84:10911, msgId=6451B45400002A9F000014F96A0D6C65, commitLogOffset=23061458676837, bodyCRC=532471758, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{topic='TopicTest', flag=0, properties={MIN_OFFSET=19844, TRACE_ON=true, eagleTraceId=1e04a5cc15967684102641001d0db0, MAX_OFFSET=19848, MSG_REGION=DefaultRegion, CONSUME_START_TIME=1596783715858, UNIQ_KEY=1E04A5CC0DB0135FBAA421365A5F0000, WAIT=true, TAGS=TagA, eagleRpcId=9.1}, body=[72, 101, 108, 108, 111, 32, 77, 101, 116, 97, 81, 32, 48], transactionId='null'}]] ConsumeMessageThread_4 Receive New Messages: [MessageExt [queueId=1, storeSize=273, queueOffset=19637, sysFlag=0, bornTimestamp=1596768410296, bornHost=/30.4.165.204:53450, storeTimestamp=1596768410298, storeHost=/100.81.180.84:10911, msgId=6451B45400002A9F000014F96A0D7141, commitLogOffset=23061458678081, bodyCRC=1757146968, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{topic='TopicTest', flag=0, properties={MIN_OFFSET=19636, TRACE_ON=true, eagleTraceId=1e04a5cc15967684102961002d0db0, MAX_OFFSET=19638, MSG_REGION=DefaultRegion, CONSUME_START_TIME=1596783715858, UNIQ_KEY=1E04A5CC0DB0135FBAA421365AB80001, WAIT=true, TAGS=TagA, eagleRpcId=9.1}, body=[72, 101, 108, 108, 111, 32, 77, 101, 116, 97, 81, 32, 49], transactionId='null'}]]...

刪除集群,清理環境;清除 RocketMQ 服務集群實例:

$ kubectl delete -f example/rocketmq_v1alpha1_rocketmq_cluster.yaml

清除 RocketMQ Operator:

$ ./purge-operator.sh

按照 OperatorHub 官網指導安裝 RocketMQ Operator

在 OperatorHub.io 網頁搜索 RocketMQ Operator;選擇 Streaming & Messaging 類別,點擊 RocketMQ Operator:

進入 RocketMQ Operator 頁面,點擊 Install 按鈕;

按照說明安裝 OLM 和 RocketMQ Operator;

本地安裝 OLM 來使用 RocketMQ Operator

本地安裝和動 OLM(Operator Lifecycle Manager) console;參考:OLM 安裝文檔。

本地啟動 UI 界面控制臺;

$ makerun-console-local

訪問 http://localhost:9000 查看控制臺;

OperatorHub

搜索 RocketMQ 或點擊 All Items 分類中的 Streaming & Messaging,找到 RocketMQ Operator 並進行安裝;安裝完 RocketMQ Operator 後可以在 Installed Operators 中找到 RocketMQ Operator;

已安裝的 Operators 界面

RocketMQ Operator 介紹界面

通過 UI 界面創建 NameService 自定義資源

可以在 UI 中創建指定 Namespace 下的 NameService 和 Broker 實例,並對已創建的實例進行瀏覽和管理。我們也可以通過命令查看當前 K8s 集群中的 Pod 狀態,例如:

$ kubectl get pods -ANAMESPACE NAME READY STATUS RESTARTS AGEdocker compose-78f95d4f8c-8fr5z 1/1 Running 032hdocker compose-api-6ffb89dc58-nv9rh 1/1 Running 032hkube-system coredns-5644d7b6d9-hv6r5 1/1 Running 032hkube-system coredns-5644d7b6d9-mkqb6 1/1 Running 032hkube-system etcd-docker-desktop 1/1 Running 032hkube-system kube-apiserver-docker-desktop 1/1 Running 032hkube-system kube-controller-manager-docker-desktop 1/1 Running 132hkube-system kube-proxy-snmxh 1/1 Running 032hkube-system kube-scheduler-docker-desktop 1/1 Running 132hkube-system storage-provisioner 1/1 Running 132hkube-system vpnkit-controller 1/1 Running 032hmarketplace broker-0-master-01/1 Running 05h3mmarketplace broker-0-replica-1-01/1 Running 05h3mmarketplace name-service-01/1 Running 05h3mmarketplace marketplace-operator-69756457d8-42chk 1/1 Running 032hmarketplace rocketmq-operator-0.2.1-c9fffb5f-cztcl 1/1 Running 032hmarketplace rocketmq-operator-84c7bb4ddc-7rvqr 1/1 Running 032hmarketplace upstream-community-operators-5b79db455f-7t47w 1/1 Running 132holm catalog-operator-7b788c597d-gjz55 1/1 Running 032holm olm-operator-946bd977f-dhszg 1/1 Running 032holm operatorhubio-catalog-fvxp9 1/1 Running 032holm packageserver-789c7b448b-7ss7m 1/1 Running 032holm packageserver-789c7b448b-lfxrw 1/1 Running 032h

可以看到在 marketplace 這個 namespace 中也成功創建了對應的 name server 和 broker 實例。

以上是基於 OperatorHub 和 OLM 安裝使用 RocketMQ Operator 的案例,我們將持續推送和維護新版本的 RocketMQ Operator 至該平臺,方便用戶獲取最新更新或選擇合適的 Operator 版本。

相關焦點

  • 基於 RocketMQ Prometheus Exporter 打造定製化 DevOps 平臺
    收錄於話題 #RocketMQ 雲原生系列文章所以 RocketMQ-Exporter 的基本邏輯是內部啟動多個定時任務周期性的從 MQ 集群拉取數據,然後將數據規範化後通過端點暴露給 Prometheus 即可。其中主要包含如下主要的三個功能部分:MQAdminExt 模塊通過封裝 MQ 系統客戶端提供的接口來獲取 MQ 集群內部的統計信息。
  • 在CentOS7上安裝RocketMQ 4.7.1
    -4.7.1-bin-release.zip  # 解壓  unzip rocketmq-all-4.7.1-bin-release.zip  # 安裝到/usr/local/rocketmq  mv rocketmq-all-4.7.1-bin-release /usr/local  ln -s /usr/local/rocketmq-all-4.7.1-bin-release /usr/local/rocketmq
  • docker-4:mac使用docker部署開發用rocketmq
    目錄:(1).mac本地docker化rocketmq(2).mac本地docker化rocketmq-console(3).測試(1).mac本地docker化rocketmq現在官方rocketmq-docker:git clone https://github.com/apache/rocketmq-docker
  • 想了解Kafka,RabbitMQ,ZeroMQ,RocketMQ,ActiveMQ之間的差異?這一篇文章就夠了!
    rocketmq:少。沒有專門寫rocketmq的書,網上的資料良莠不齊,官方文檔很簡潔,但是對技術細節沒有過多的描述。activemq:多。沒有專門寫activemq的書,網上資料多。Kafka:Scalarabbitmq:Erlangzeromq:crocketmq:javaactivemq:javaKafka:自己定義的一套...
  • 想了解 Kafka, RabbitMQ, ZeroMQ, RocketMQ, ActiveMQ 之間的差異?這一篇文章就夠了!
    rocketmq:少。沒有專門寫rocketmq的書,網上的資料良莠不齊,官方文檔很簡潔,但是對技術細節沒有過多的描述。activemq:多。沒有專門寫activemq的書,網上資料多。Kafka:Scalarabbitmq:Erlangzeromq:crocketmq:javaactivemq:javaKafka:自己定義的一套...
  • 深度剖析Kafka/RocketMQ順序消息的一些坑
    RocketMQRocketMQ 不像 Kafka 那麼「原生」,RocketMQ 早已為你準備好了你的需求,它本身的消費模型就是單 consumer 實例 + 多 worker 線程模型,有興趣的小夥伴可以從以下方法觀摩 RocketMQ 的消費邏輯:org.apache.rocketmq.client.impl.consumer.PullMessageService
  • RocketMQ消息軌跡-設計篇
    自定義Topic在創建消息生產者或消息消費者時,可以通過參數自定義用於記錄消息軌跡的Topic名稱,不過要注意的是,rokcetmq控制臺(rocketmq-console)中只支持配置一個消息軌跡Topic,故自定義Topic,在目前這個階段或許還不是一個最佳實踐,建議使用系統默認的Topic即可。
  • RocketMQ CPP客戶端更新至1.2.0,兼容多語言、簡化項目編譯流程
    修復了一個關於唯一密鑰的問題,即當發送消息時,生產者不能獲得唯一密鑰; 項目地址:https://github.com/apache/rocketmq-client-cpp
  • Apache RocketMQ 4.4.0 發布 - OSCHINA - 中文開源技術交流社區
    發布說明:http://rocketmq.apache.org/release_notes/release-notes-4.4.0/
  • RocketMQ消息發送常見錯誤與解決方案
    通常情況下 No route info of this topic 這個錯誤一般是在剛搭建RocketMQ,剛入門 RocketMQ遇到的比較多,通常的排查思路如下:可以通過rocketmq-console查詢路由信息是否存在,或使用如下命令查詢路由信息:cd ${ROCKETMQ_HOME}/binsh .
  • 騫雲:雲原生時代下的IT管理之痛
    在雲原生環境下,開發人員、用戶關注的是如何創建和部署應用程式,而不需要關心基礎設施的實現。隨著各類雲平臺技術的成熟,在可持續集成、持續交付等開發理念的帶動下,雲計算的發展已步入新的階段,應用上雲已是不可逆轉的事實,雲原生時代已經到來,而五花八門的雲原生服務則給企業IT管理帶來了更高的要求。
  • 雲原生背景下的運維價值思考與實踐
    切雲的服務大量採用了雲原生的應用與技術架構,作為公司第一批面臨雲原生環境的業務運維,深切感受到雲原生給運維工作帶來的機遇與挑戰,運維模式的轉型已經迫在眉睫,此篇文章最大的價值在於將我們的轉型思路、方法與實踐,提供給後面更多面臨同樣挑戰的團隊借鑑與參考。下面我將從業務場景、運維轉型之道、雲端收益等幾個方面來跟大家一起來探討。
  • Kubernetes API 編程利器:Operator 和 Operator Framework
    按照處理類型的不同,一般可以將其分為兩類:一類可能會修改傳入對象,稱為 mutating webhook;一類則會只讀傳入對象,稱為 validating webhook。工作隊列:controller 的核心組件。
  • 雲原生廣受歡迎,KubeSphere開箱即用、運維友好
    (原標題:雲原生廣受歡迎,KubeSphere開箱即用、運維友好  )
  • 雲原生時代的流量入口:Envoy Gateway
    當雲原生時代大浪襲來,Envoy 這一 CNCF 畢業數據面組件為更多人所知。那麼,優秀「畢業生」Envoy 能否成為雲原生時代下流量入口標準組件?背景 —— 流量入口的眾多選型與場景在網際網路體系下,凡是需要對外暴露的系統幾乎都需要網絡代理:較早出現的 HAProxy、Nginx 至今仍在流行;進入微服務時代後,功能更豐富、管控能力更強的 API 網關又成為流量入口必備組件;在進入容器時代後,Kubernetes Ingress 作為容器集群的入口,是容器時代微服務的流量入口代理標準。
  • 為什麼雲原生+分布式是資料庫的未來?
    結合可靠的自動化手段,雲原生技術使工程師能夠輕鬆地對系統作出頻繁和可預測的重大變更。 李飛飛表示,雲原生的本質就是發揮雲計算資源池化、平臺規模化等技術紅利帶來的業務價值,利用容器化部署、微服務、存計分離、Serverless、多租戶、智能化調度與運維管控等多種技術手段來充分的發揮雲計算帶來的彈性、高可用、靈活部署、簡化運維、易拓展等這些核心業務價值。
  • KubeSphere容器平臺:面向雲原生時代的趁手工具
    在所有企業都在加快數位化步伐,邁入雲原生時代的當下,青雲推出一款面向社區的「劃時代產品」,這樣做的路徑是怎樣的呢?雲原生時代的基礎設施如果要理解KubeSphere是怎樣的一款產品,那麼首先要理解雲原生時代對於企業的必然性。
  • 乘風破浪的雲原生
    為了更加從容地應對十倍擴容,洋蔥學院還進一步優化了整體的 ECS 伺服器配置,將大量的小規格 ECS 伺服器更換成 30 至 50 核大規格 ECS,改造後運維管控也更加便捷。使用雲容器之後,系統在資源利用率上提升了約60%,出現問題後可快速隔離,當面對急劇增長的業務量,也可以在短時間內擴容進行業務支撐。
  • Apache RocketMQ 4.8.0 發布,升級 DLedger 模式
    詳情查看 http://rocketmq.apache.org/release_notes/release-notes-4.8.0/。
  • 雲原生,為何而生?雲計算時代命題之終極解決方
    雲原生是當前雲計算架構的自然進化,業務系統本身的不斷膨脹,導致結構更加錯綜複雜,業務洪峰頻繁遭遇挑戰,運維的壓力已經遠遠不能靠砌人牆來解決問題,以容器為基石,通過微服務化的改造,融合DevOps理念,能夠幫助企業快速構建更加適合雲的敏捷應用服務。