使用Kubernetes來管理Docker擴展
來自:http://www.infoq.com/cn/articles/scaling-docker-with-kubernetes
Kubernetes是一款開源的項目,管理Linux容器集群,并可將集群作為一個單一的系統來對待。其可跨多主機來管理和運行Docker容器、提供容器的定位、服務發現以及復制控制。它由Google發起,現在得到了如微軟、紅帽、IBM和Docker等眾多廠商的支持。
Google使用容器技術有著超過十年的歷史,每周要啟動超過2億臺容器。通過Kubernetes,Google分享了他們關于容器的專業經驗,即創建大規模運行容器的開放平臺。
一旦用戶開始使用Docker容器,那么問題就來了,一、如何跨多個Docker主機擴展和啟動容器,且能夠在主機間平衡這些容器。二、還需要高度抽象出API來定義如何從邏輯上組織容器、定義容器池、負載均衡以及關聯性。該項目就是為了解決這兩個問題而生的。
Kubernetes仍然處于早期階段,這也就意味著會有很多新的變化進入到該項目,目前還僅有比較簡單的實例,仍需要加入新的功能,但是它正在一步一步地平穩發展,也得到了很多大公司的支持,前途無量。
Kubernetes概念
Kubernetes的架構被定義為由一個master服務器和多個minons服務器組成。命令行工具連接到master服務器的API端點,其可以管理和編排所有的minons服務器,Docker容器接收來自master服務器的指令并運行容器。
- Master:Kubernetes API 服務所在,多Master的配置仍在開發中。
- Minons:每個具有Kubelet服務的Docker主機,Kubelet服務用于接收來自Master的指令,且管理運行容器的主機。
- Pod:定義了一組綁在一起的容器,可以部署在同一Minons中,例如一個數據庫或者是web服務器。
- Replication controller:定義了需要運行多少個Pod或者容器。跨多個minons來調度容器。
- Service:定義了由容器所發布的可被發現的服務/端口,以及外部代理通信。服務會映射端口到外部可訪問的端口,而所映射的端口是跨多個minons的Pod內運行的容器的端口。
- kubecfg:命令行客戶端,連接到master來管理Kubernetes。
(點擊圖片可放大顯示)
Kubernetes由狀態所定義,而不是進程。當你定義了一個pod時,Kubernetes會設法確保它會一直運行。如果其中的某個容器掛掉 了,Kubernetes會設法再啟動一個新的容器。如果一個復制控制器定義了3份復制,Kubernetes會設法一直運行這3份,根據需要來啟動和停 止容器。
本文中所用的例子應用是Jenkins持續集成服務,Jenkins是典型的通過主從服務來建立分布式的工作任務的例子。Jenkins由jenkins swarm插件來配置,運行一個jenkins主服務和多個jenkins從服務,所有的Jenkins服務都以Docker容器的方式跨多個主機運行。swarm從服務在啟動時連接到Jenkins主服務,然后就可以運行Jenkins任務了。例子中使用的配置文件可以從Github上下載到,而該Docker鏡像可以從casnchez/jenkins-swarm獲取,對于Jenkins主服務來說,可以通過swarm插件來擴展官方Jenkins鏡像。對于jenkins從服務來說,可以從csanchez/jenkins-swarm-slave獲取,它只是在JVM容器中運行了jenkins從服務而已。
創建Kubernetes集群
Kubernetes提供了多個操作系統和云/虛擬化提供商下創建集群的腳本,有Vagrant(用于本地測試)、Google Compute Engine、Azure、Rackspace等。
本文所實踐的例子就是運行在Vagrant之上的本地集群,使用Fedora作為操作系統,遵照的是官方入門指南,測試的Kubernetes版本是0.5.4。取代了默認的3個minion(Docker主機),而是使用了2個minion,兩臺主機就足以展示Kubernetes的能力了,三臺有點浪費。
當你下載了Kubernetes,然后將至解壓后,即可在本地的目錄下運行這些示例了。初學者創建集群僅需要一個命令,即./cluster/kube-up.sh。
$ export KUBERNETES_PROVIDER=vagrant $ export KUBERNETES_NUM_MINIONS=2 $ ./cluster/kube-up.sh
獲取示例配置文件:
$ git clone https://github.com/carlossg/kubernetes-jenkins.git
集群創建的快慢取決于機器的性能和內部的帶寬。但是其最終完成不能有任何的錯誤,而且它僅需要運行一次。
命令行工具
和Kubernetes交互的命令行工具叫做kubecfg,此腳本位于cluster/kubecfg.sh。
為了檢驗我們剛才創建的兩個minons已經啟動并運行了,只需運行kubecfg list minions命令即可,它的結果會顯示Vagrant的兩臺虛擬機。
$ ./cluster/kubecfg.sh list minions Minion identifier ---------- 10.245.2.2 10.245.2.3
Pod
在Kubernetes的術語中,Jenkins主服務被定義為一個pod。在一個pod中可以指定多個容器,這些容器均部署在同一Docker主機中,在一個pod中的容器的優勢在于可以共享資源,例如存儲卷,而且使用相同的網絡命名空間和IP地址。默認的卷是空的目錄,類型為emptyDir,它的生存時間就是pod的生命周期,而并非是指定的容器,所以如果一個容器失效了,但是其持久性的存儲仍然在。另外一個卷的類型是hostDir,它是將主機的一個目錄掛載到容器中。
在這個具體的Jenkins示例中,我們所創建的pod是兩個容器,分別是Jenkins主服務和MySQL,前者作為實例,后者作為數據庫。然而我們只需要關心jenkins主服務容器即可。
為了創建一個Jenkins pod,我們運行定義了Jenkins容器pod的kubecfg,使用Docker鏡像csanchez/jenkins-swarm,為了能夠訪問 Jenkins的Web界面和從服務的API,我們將主機的端口8080和50000映射到容器,以及將/var/jenkins_home掛載為卷。讀 者可從Github下載到示例代碼。
Jenkins Web 界面的pod(pod.json)的定義如下:
{ "id": "jenkins", "kind": "Pod", "apiVersion": "v1beta1", "desiredState": { "manifest": { "version": "v1beta1", "id": "jenkins", "containers": [ { "name": "jenkins", "image": "csanchez/jenkins-swarm:1.565.3.3", "ports": [ { "containerPort": 8080, "hostPort": 8080 }, { "containerPort": 50000, "hostPort": 50000 } ], "volumeMounts": [ { "name": "jenkins-data", "mountPath": "/var/jenkins_home" } ] } ], "volumes": [ { "name": "jenkins-data", "source": { "emptyDir": {} } } ] } }, "labels": { "name": "jenkins" } }
然后使用下面命令來創建:
$ ./cluster/kubecfg.sh -c kubernetes-jenkins/pod.json create pods Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- jenkins csanchez/jenkins-swarm:1.565.3.3 name=jenkins Pending
這需要等待一段時間,具體時間的長短要視你的網絡而定,因為它會從Docker Hub上下載Docker鏡像到minion,我們可以查看它的狀態,以及在那個minion中啟動了。
$ ./cluster/kubecfg.sh list pods Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- jenkins csanchez/jenkins-swarm:1.565.3.3 10.0.29.247/10.0.29.247 name=jenkins Running
如果我們使用SSH登錄到minion中,此minion即是pod被分配到的minion-1或minion-2,我們就可以看到Docker按 照所定義的那樣啟動起來了。其中還包括了用于Kubernetes內部管理的容器(kubernetes/pause和google /cadvisor)。
$ vagrant ssh minion-2 -c "docker ps" CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7f6825a80c8a google/cadvisor:0.6.2 "/usr/bin/cadvisor" 3 minutes ago Up 3 minutes k8s_cadvisor.b0dae998_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0.default.file_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0_28df406a 5c02249c0b3c csanchez/jenkins-swarm:1.565.3.3 "/usr/local/bin/jenk 3 minutes ago Up 3 minutes k8s_jenkins.f87be3b0_jenkins.default.etcd_901e8027-759b-11e4-bfd0-0800279696e1_bf8db75a ce51fda15f55 kubernetes/pause:go "/pause" 10 minutes ago Up 10 minutes k8s_net.dbcb7509_0d38f5b2-759c-11e4-bfd0-0800279696e1.default.etcd_0d38fa52-759c-11e4-bfd0-0800279696e1_e4e3a40f e6f00165d7d3 kubernetes/pause:go "/pause" 13 minutes ago Up 13 minutes 0.0.0.0:8080->8080/tcp, 0.0.0.0:50000->50000/tcp k8s_net.9eb4a781_jenkins.default.etcd_901e8027-759b-11e4-bfd0-0800279696e1_7bd4d24e 7129fa5dccab kubernetes/pause:go "/pause" 13 minutes ago Up 13 minutes 0.0.0.0:4194->8080/tcp k8s_net.a0f18f6e_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0.default.file_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0_659a7a52
還有,我們一旦拿到了容器的ID,就可以通過如vagrant ssh minion-1 -c "docker logs cec3eab3f4d3"這樣的命令來查看容器的日志了。
我們也可以訪問Jenkins的Web界面,至于要訪問的URL是http://10.245.2.2:8080/還是http://10.0.29.247:8080/,要看用到了那個minion。
服務發現
Kubernetes支持定義服務,一種為容器使用發現和代理請求到合適的pod的方法。下面示例使用了service-http.json文件來 創建一個服務,其將id為jenkins指向了貼有標簽為name=jenkins的pod。而標簽是在如上面pod的定義中所聲明的。且將端口8888 重定向到容器的8080 。
{ "id": "jenkins", "kind": "Service", "apiVersion": "v1beta1", "port": 8888, "containerPort": 8080, "selector": { "name": "jenkins" } }
使用kubecfg來創建服務:
$ ./cluster/kubecfg.sh -c kubernetes-jenkins/service-http.json create services Name Labels Selector IP Port ---------- ---------- ---------- ---------- ---------- jenkins name=jenkins 10.0.29.247 8888
每個服務都會被分配一個唯一的IP地址,且此IP地址會伴隨服務的整個生命周期。如果我們有多個pod匹配服務的定義,服務就會在所有它們之上提供負載均衡。
服務的另外一個特性是可以為由Kubernetes來運行的容器設置一些環境變量,使其可以連接到該服務容器,這和運行已鏈接的Docker容器有些類似,它可以幫助我們從若干從服務中找到Jenkins主服務。
JENKINS_PORT='tcp://10.0.29.247:8888' JENKINS_PORT_8080_TCP='tcp://10.0.29.247:8888' JENKINS_PORT_8080_TCP_ADDR='10.0.29.247' JENKINS_PORT_8080_TCP_PORT='8888' JENKINS_PORT_8080_TCP_PROTO='tcp' JENKINS_SERVICE_PORT='8888' SERVICE_HOST='10.0.29.247'
在此示例中,我們還需要打開端口50000,Jenkins swarm插件需要用這個端口。我們創建另外一個service-slave.json文件,這樣Kubernetes就可以重定向端口到Jenkins服務容器了。
{ "id": "jenkins-slave", "kind": "Service", "apiVersion": "v1beta1", "port": 50000, "containerPort": 50000, "selector": { "name": "jenkins" } }
再次使用kubecfg來創建:
$ ./cluster/kubecfg.sh -c kubernetes-jenkins/service-slave.json create services Name Labels Selector IP Port ---------- ---------- ---------- ---------- ---------- jenkins-slave name=jenkins 10.0.86.28 50000
列出所有可用的已經定義的服務,包括一些Kubernetes內部的服務:
$ ./cluster/kubecfg.sh list services Name Labels Selector IP Port ---------- ---------- ---------- ---------- ---------- kubernetes-ro component=apiserver,provider=kubernetes 10.0.22.155 80 kubernetes component=apiserver,provider=kubernetes 10.0.72.49 443 jenkins name=jenkins 10.0.29.247 8888 jenkins-slave name=jenkins 10.0.86.28 50000
復制控制器
復制控制器允許在多個minion中運行多個pod。Jenkins的從服務以此方式來運行,可以確保一直有一個運行Jenkins任務的從服務池。
創建replication.json,所定義的內容如下:
{ "id": "jenkins-slave", "apiVersion": "v1beta1", "kind": "ReplicationController", "desiredState": { "replicas": 1, "replicaSelector": { "name": "jenkins-slave" }, "podTemplate": { "desiredState": { "manifest": { "version": "v1beta1", "id": "jenkins-slave", "containers": [ { "name": "jenkins-slave", "image": "csanchez/jenkins-swarm-slave:1.21", "command": [ "sh", "-c", "/usr/local/bin/jenkins-slave.sh -master http://$JENKINS_SERVICE_HOST:$JENKINS_SERVICE_PORT -tunnel $JENKINS_SLAVE_SERVICE_HOST:$JENKINS_SLAVE_SERVICE_PORT -username jenkins -password jenkins -executors 1" ] } ] } }, "labels": { "name": "jenkins-slave" } } }, "labels": { "name": "jenkins-slave" } }
podTemplate一節允許和pod的定義進行一樣的配置。在此示例中,我們打算讓Jenkins從服務自動地連接到Jenkins主服務,而 不是利用Jenkins主服務的多播發現。要實現此想法,我們需要執行jenkins-slave.sh命令,指定參數-master,從而實現在 Kubernetes中讓從服務連接到主服務。注意,上述配置文件中,我們使用了Kubernetes所提供的為Jenkins服務定義的環境變量 (JENKINS_SERVICE_HOST和JENKINS_SERVICE_PORT)。此方法中鏡像的命令覆蓋了容器的配置,為的是利用已經下載好 的鏡像。這也同樣可以在pod的定義中去實現。
使用kubecfg來創建副本:
$ ./cluster/kubecfg.sh -c kubernetes-jenkins/replication.json create replicationControllers Name Image(s) Selector Replicas ---------- ---------- ---------- ---------- jenkins-slave csanchez/jenkins-swarm-slave:1.21 name=jenkins-slave 1
現在執行pod列表,可以看到新的pod已經創建,其數量是由我們剛剛所定義的復制控制器所決定的。
$ ./cluster/kubecfg.sh list pods Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Running 07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Pending
第一次運行的jenkins-swarm-slave鏡像是minion從Docker倉庫下載下來的,但是一段時間之后(這取決于你的互聯網連 接),Jenkins從服務會自動連接到Jenkins主服務。登錄到Jenkins從服務所在的服務器,docker ps會顯示出運行中的容器和Docker的日志,這能夠幫助你來調試容器啟動中出現的問題。
$ vagrant ssh minion-1 -c "docker ps" CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 870665d50f68 csanchez/jenkins-swarm-slave:1.21 "/usr/local/bin/jenk About a minute ago Up About a minute k8s_jenkins-slave.74f1dda1_07651754-4f88-11e4-b01e-0800279696e1.default.etcd_11cac207-759f-11e4-bfd0-0800279696e1_9495d10e cc44aa8743f0 kubernetes/pause:go "/pause" About a minute ago Up About a minute k8s_net.dbcb7509_07651754-4f88-11e4-b01e-0800279696e1.default.etcd_11cac207-759f-11e4-bfd0-0800279696e1_4bf086ee edff0e535a84 google/cadvisor:0.6.2 "/usr/bin/cadvisor" 27 minutes ago Up 27 minutes k8s_cadvisor.b0dae998_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0.default.file_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0_588941b0 b7e23a7b68d0 kubernetes/pause:go "/pause" 27 minutes ago Up 27 minutes 0.0.0.0:4194->8080/tcp k8s_net.a0f18f6e_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0.default.file_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0_57a2b4de
復制控制器可以自動的調整任何想要的副本數量:
$ ./cluster/kubecfg.sh resize jenkins-slave 2
再次列出pod的話,可以看到每個副本是在哪里運行的。
$ ./cluster/kubecfg.sh list pods Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- 07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Running a22e0d59-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.3/10.245.2.3 name=jenkins-slave Pending jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Running
調度
目前默認的調度是隨機的,但是基于資源的調度很快就將實現了。在寫作本文的時候,基于內存和CPU利用率的調度還有一些沒有修復的問題。還有整合Apache Mesos的調度也在緊鑼密鼓的進行中。Apache Mesos是一款分布式系統的框架,提供了資源管理的API,和跨整個數據中心或云環境的調度。
自愈
使用Kubernetes的一大好處就是其能夠自動管理和修復容器。
如果運行Jenkins服務的容器由于某些原因宕機了,比如說進程崩潰了,那么Kubernetes就會得到通知,并會在幾秒鐘之后創建一個新的容器。
$ vagrant ssh minion-2 -c 'docker kill `docker ps | grep csanchez/jenkins-swarm: | sed -e "s/ .*//"`' 51ba3687f4ee $ ./cluster/kubecfg.sh list pods Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Failed 07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Running a22e0d59-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.3/10.245.2.3 name=jenkins-slave Running
稍后再執行list pod命令,一般不會超過1分鐘,會看到如下內容:
Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Running 07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Running a22e0d59-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.3/10.245.2.3 name=jenkins-slave Running
將Jeknkis的數據目錄以卷的形式運行,我們保證了在容器宕機之后仍然能夠保留數據,所以不會丟失任何Jenkins的任務或者是已經創建好的 數據。而且因為Kubernetes在每個minion都代理了服務,Jenkins從服務會自動連接到Jenkins主服務,而根本無須關心它是在哪里 運行的!而且Jenkins從服務容器宕掉,系統也會自動創建新的容器,服務發現會將之自動加入到Jenkins服務池中。
如果發生了更加嚴重的情況,比如minion掛掉了,Kubernetes目前還沒能提供將現有容器重新調度到其它仍在運行的minion中的能力,它僅僅會顯示某個pod失效,如下所示:
$ vagrant halt minion-2 ==> minion-2: Attempting graceful shutdown of VM... $ ./cluster/kubecfg.sh list pods Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Failed 07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Running a22e0d59-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.3/10.245.2.3 name=jenkins-slave Failed
清理
kubecfg還提供了一些命令來停止和刪除復制控制器、pod、以及服務等。
要停止復制控制器,設置復制數為0,所有Jenkins從服務的容器都會被終止:
$ ./cluster/kubecfg.sh stop jenkins-slave
刪除它:
$ ./cluster/kubecfg.sh rm jenkins-slave
刪除Jenkins服務Pod,Jenkins主服務的容器會被終止:
$ ./cluster/kubecfg.sh delete pods/jenkins
刪除服務:
$ ./cluster/kubecfg.sh delete services/jenkins $ ./cluster/kubecfg.sh delete services/jenkins-slave
總結
Kubernetes還是一個尚處于早期的項目,但是極有希望來管理Docker的跨服務器部署以及簡化執行長時間運行和分布式的Docker容 器。通過抽象基礎設施概念,定義狀態而不是進程,它提供了集群的簡單定義,包括脫離管理的自我修復能力。簡而言之,Kubernetes讓Docker的 管理更加的輕松。
關于作者
Carlos Sanchez在 自動化、軟件開發質量、QA以及運維等方面有超過10年的經驗,范圍涉及從構建工具、持續集成到部署自動化,DevOps最佳實踐,再到持續交付。他曾經 為財富500強的公司實施過解決方案,在多家美國的創業公司工作過,最近任職的公司叫做MaestroDev,這也是他一手創建的公司。Carlos曾經 在世界各地的各種技術會議上作演講,包括JavaOne、EclipseCON、ApacheCON、JavaZone、Fosdem和 PuppetConf。他非常積極的參與開源,是Apache軟件基金會的成員,同時也是其它一些開源團體的成員,為很多個開源項目做過貢獻,比如 Apache Maven、Fog 和Puppet。