8.2 與Kubernetes API服務器交互
我們已經(jīng)知道,Downward API提供了一種簡單的方式,將pod和容器的元數(shù)據(jù)傳遞給在它們內(nèi)部運行的進程。但這種方式其實僅僅可以暴露一個pod自身的元數(shù)據(jù),而且只可以暴露部分元數(shù)據(jù)。某些情況下,我們的應用需要知道其他pod的信息,甚至是集群中其他資源的信息。這種情況下Downward API方式將無能為力。
正如書中提到的,可以通過服務相關的環(huán)境變量或者DNS來獲取服務和pod的信息,但如果應用需要獲取其他資源的信息或者獲取最新的信息,就需要直接與API服務器進行交互(如圖8.4所示)。
在了解pod中的應用如何與Kubernetes API服務器交互之前,先在自己的本機上研究一下服務器的REST endpoit,這樣我們可以大致了解什么是API服務器。
8.2.1 探究Kubernetes REST API
我們已經(jīng)了解了Kubernetes不同的資源類型。但如果打算開發(fā)一個可以與Kubernetes API交互的應用,要首先了解API。
先嘗試直接訪問API服務器,可以通過運行 kubectl cluster-info 命令來得到服務器的URL。
$ kubectl cluster-info
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
Unable to connect to the server: dial tcp 172.17.0.2:8443: connect: no route to host
因為服務器使用HTTPS協(xié)議并且需要授權(quán),所以與服務器交互并不是一件簡單的事情,可以嘗試通過curl來訪問它,使用curl的 --insecure(或-k)選項來跳過服務器證書檢查環(huán)節(jié),但這也不能讓我們走得更遠。
$ curl https://172.17.0.2:8443 -k
幸運的是,我們可以執(zhí)行 kubectl proxy 命令,通過代理與服務器交互,而不是自行來處理驗證過程。
通過Kubectl proxy訪問API服務器
kubectl proxy 命令啟動了一個代理服務來接收來自你本機的HTTP連接并轉(zhuǎn)發(fā)至API服務器,同時處理身份認證,所以不需要每次請求都上傳認證憑證。它也可以確保我們直接與真實的API服務器交互,而不是一個中間人(通過每次驗證服務器證書的方式)。
運行代理很簡單,所要做的就是運行以下命令:
$ kubectl proxy --address='0.0.0.0' --port=8001 --accept-hosts='.*'
我們也無須傳遞其他任何參數(shù),因為kubectl已經(jīng)知曉所需的所有參數(shù)(API服務器 URL、認證憑證等)。一旦啟動,代理服務器就將在本地端口8001接收連接請求,讓我們看一下它是如何工作的:
$ curl localhost:8001
看,我們發(fā)送請求給代理,代理接著發(fā)送請求給API服務器,然后代理將返回從服務器返回的所有信息,現(xiàn)在讓我們開始研究。
通過Kubectl proxy研究Kubernetes API
我們可以繼續(xù)使用curl,或者打開瀏覽器并且指向 http://localhost:8001 ,看一下當我們訪問這個基礎的URL時,API服務器會返回什么。服務器的應答是一組路徑的清單,如下所示。
代碼清單8.7 API服務器的REST endpoint清單:http://localhost:8001
{
"paths": [
"/.well-known/openid-configuration",
"/api",
"/api/v1",
"/apis",
----------
"/apis/batch", # batchAPI組以及它的兩個版本
"/apis/batch/v1",
"/apis/batch/v1beta1",
]
}
這些路徑對應了我們創(chuàng)建Pod、Service這些資源時定義的API組和版本信息。
你或許已經(jīng)發(fā)現(xiàn)在 /apis/batch/v1 路徑下的 batch/V1 就是在第4章了解的Job資源API組和版本信息。同樣, /api/V1 對應apiVersion:這里所說的V1指的是我們創(chuàng)建的基礎資源(Pod、Service、ReplicationController等)。在Kubernetes最早期版本中提到的最基礎的資源并不屬于任何指定的組,原因是Kubernetes初期并沒有使用API組的概念,這個概念是后期引入的。
注意 這些沒有列入API組的初始資源類型現(xiàn)在一般被認為歸屬于核心的API組。
研究批量API組的REST endpoint
讓我們來研究Job資源API,從路徑 /apis/batch 下的內(nèi)容開始(暫時忽略版本),如下面的代碼清單所示。
代碼清單8.8 在 /apis/batch 目錄下的endpoint清單: curl http://localhost:8001/apis/batch 這個響應消息展示了包括可用版本、客戶推薦使用版本在內(nèi)的批量API組信息。讓我們接著看一下 /apis/batch/V1 路徑下的內(nèi)容,如下面的代碼清單所示。
代碼清單8.9 在 batch/V1 中的資源類型: http://localhost:8001/apis/batch/v1
{
"kind": "APIResourceList",
"apiVersion": "v1",
"groupVersion": "batch/v1", #資源請單
"resources": [ #所有的資源類型
{
"name": "cronjobs",
"singularName": "",
"namespaced": true, #指定namesapce的job資源,
"kind": "CronJob",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"cj"
],
"categories": [
"all"
],
"storageVersionHash": "h/JlFAZkyyY="
}
]
}
像我們看到的一樣,API服務器返回了在 batch/V1 目錄下API組中的資源類型以及 REST endpoint 清單。除了資源的名稱和相關的類型,API服務器也包含了一些其他信息,比如資源是否被指定了命名空間、名稱簡寫(如果有的話,對于Job來說沒有)、資源對應可以使用的動詞列表等。
返回的列表描述了在API服務器中暴露的REST資源。"name":"jobs"行的信息告訴我們API包含了 /apis/batch/V1/jobs 的endpoint,"verbs"數(shù)組告訴我們可以通過endpoint恢復、修改以及刪除Job資源。對于某些特定的資源,API服務器暴露了額外的API endpoint(例如,通過 jobs/status 路徑可以修改Job的狀態(tài))。
列舉集群中所有的Job實例
通過在 /apis/batch/v1/jobs 路徑運行一個GET請求,可以獲取集群中所有Job的清單,如下面的代碼清單所示。
代碼清單8.10 Job清單:http://localhost:8001/apis/batch/v1/jobs
$ curl http://localhost:8001/apis/batch/v1/jobs
如果在集群中沒有部署Job資源,那么items數(shù)組將是空的。可以嘗試在Chapter08/my-job.yaml中部署Job,然后再次訪問RESR endpoint從而得到與代碼清單8.10中相同的輸出信息。
通過名稱恢復一個指定的Job實例
前面的endpoint返回了跨命名空間的所有Job的清單,如果想要返回指定的一個Job,需要在URL中指定它的名稱和所在的命名空間。為了恢復在之前清單中的一個Job(name:my-job ; namespace:dfault ),需要訪問路徑:/apis/batch/v1/namespaces/default/jobs/my-job ,如下面的代碼清單所示。
代碼清單8.11 通過名稱恢復一個指定命名空間下的資源
$ curl http://localhost:8001/apis/batch/v1/namespace/default/jobs/my-job
可以看到,我們得到了my-job這個Job資源的完整的JSON定義信息,和運行 $kubetcl get job my-job -o json 命令得到的信息完全一致。
$ kubectl get job my-job -o json
雖然不使用任何特定的工具,我們也可以訪問Kubernetes REST API服務器,但如果要全面地研究REST API并與之交互,在本章最后會介紹更好的方式。暫時來看,像這樣使用curl進行研究,對我們理解一個應用如何在pod中運行并與Kubernetes交互已經(jīng)足夠。
8.2.2 從pod內(nèi)部與API服務器進行交互
我們已經(jīng)知道如何從本機通過使用kubectl proxy與API服務器進行交互?,F(xiàn)在我們來研究從一個pod內(nèi)部訪問它,這種情況下通常沒有kubectl可用。因此,想要從pod內(nèi)部與API服務器進行交互,需要關注以下三件事情:
- 確定API服務器的位置
- 確保是與API服務器進行交互,而不是一個冒名者
- 通過服務器的認證,否則將不能查看任何內(nèi)容以及進行任何操作
接下來我們看一下交互如何實現(xiàn)。
運行一個pod來嘗試與API服務器進行通信
首先需要一個pod以便從它內(nèi)部發(fā)起與API服務器的交互。運行一個什么也不做的pod(在它僅有的容器內(nèi)部運行一個sleep命令),然后通過 kubectl exec 在容器內(nèi)部運行一個腳本,接下來在腳本中使用curl嘗試訪問API服務器。
因此,需要使用一個包含curl二進制的容器鏡像。如果在Docker Hub中搜索,就會發(fā)現(xiàn)curlimages/curl鏡像,可以使用這個鏡像(也可以使用任何包含curl二進制的已有鏡像或者自己打包的鏡像)。pod的定義如下面的代碼清單所示。
代碼清單8.12 用來嘗試與API服務器通信的pod:curl.yaml
apiVersion: v1kind: Podmetadata: name: curlspec: containers: - name: main image: curlimages/curl #使用curl command: ["sleep", "9999999"] #保持容器處于運行狀態(tài)
在完成pod的創(chuàng)建后,在容器中運行kubectl exec來啟動一個bash shell:
$ kubectl exec curl -it -- sh
我們現(xiàn)在已經(jīng)做好了與API服務器交互的準備。
發(fā)現(xiàn)API服務器地址
首先,需要找到Kubernetes API服務器的IP地址和端口。這一點比較容易做到,因為一個名為kubernetes的服務在默認的命名空間被自動暴露,并被配置為指向API服務器。也許你應該記得,每次使用kubectl get svc命令顯示所有服務清單時,都會看到這個服務。
$ kubectl get svc
在第5章中說過每個服務都被配置了對應的環(huán)境變量,在容器內(nèi)通過查詢 KUBERNETES_SERVICE_HOST 和 KUBERNETES_SERVICE_PORT 這兩個環(huán)境變量就可以獲取API服務器的IP地址和端口。
curl:/$ env | grep KUBERNETES_SERVICEKUBERNETES_SERVICE_PORT=443KUBERNETES_SERVICE_PORT_HTTPS=443KUBERNETES_SERVICE_HOST=10.96.0.1
同樣,我們應該記得每個服務都可以獲得一個DNS入口,所以甚至沒有必要去查詢環(huán)境變量,而只是簡單地將curl指向 https://kubernetes 。公平地講,如果我們不知道服務在哪個端口是可用的,既可以查詢環(huán)境變量,也可以查看DNS SRV記錄來得到實際的端口號。
之前展示的環(huán)境變量說明API服務器監(jiān)聽HTTPS協(xié)議默認的443端口,所以嘗試通過HTTPS協(xié)議來訪問服務器。
curl:/$ curl https://10.96.0.1
雖然最簡單的繞開這一步驟的方式是使用推薦的-k選項(這也是我們在手工操作API服務器時通常會使用的方式),但還是來看一下更長(也是正確)的途徑。我們應該通過使用curl檢查證書的方式驗證API服務器的身份,而不是盲目地相信連接的服務是可信的。
提示 在真實的應用中,永遠不要跳過檢查服務器證書的環(huán)節(jié)。這樣做會導致你的應用驗證憑證暴露給采用中間人攻擊方式的攻擊者。
驗證服務器身份
在之前的章節(jié)中,在討論Secret時,我們看到一個名為defalut-token-xyz的Secret被自動創(chuàng)建,并掛載到每個容器的/var/run/secrets/http://kubernetes.io/serviceaccount目錄下。讓我們查看目錄下的文件,再次看一下Secret的內(nèi)容。
curl:/$ ls /var/run/secrets/kubernetes.io/serviceaccountca.crt namespace token
Secret有三個入口(因此在Secret卷中有三個文件)?,F(xiàn)在,我們關注一下ca.crt文件。該文件中包含了CA的證書,用來對Kubernetes API服務器證書進行簽名。為了驗證正在交互的API服務器,我們需要檢查服務器的證書是否是由CA簽發(fā)。curl允許使用-cacert選項來指定CA證書,我們來嘗試重新訪問API服務器:
curl:/$ curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://10.96.0.1
注意 我們可能看到一個比“Unauthorized”更長的錯誤描述。
到目前為止,我們已經(jīng)取得進展,服務使用了我們信任的CA簽名的證書,所以curl驗證通過了服務器的身份,但Unauthorized這個響應提醒我們需要關注授權(quán)的問題。同時,看一下如何通過設置 CURL_CA_BUNDLE 環(huán)境變量來簡化操作,從而不必在每次運行curl時都指定 --cacert 選項:
curl:/$ export CURL_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
現(xiàn)在,我們可以不使用 --cacert 來訪問API服務器:
curl:/$ curl https://10.96.0.1
這樣操作相對便捷,我們的客戶端(curl)現(xiàn)在信任API服務器,但API服務器并不確認訪問者的身份,所以沒有授權(quán)允許訪問。
獲得API服務器授權(quán)
我們需要獲得API服務器的授權(quán),以便可以讀取并進一步修改或刪除部署在集群中的API對象。為了獲得授權(quán),我們需要認證的憑證,幸運的是,憑證可以使用之前提到的default-token Secret來產(chǎn)生,同時憑證可以被存放在secret卷的token文件中。Secret這個名字就說明了它主要的作用。
可以使用憑證來訪問API服務器,第一步,將憑證掛載到環(huán)境變量中:
curl:/$ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
此時,憑證已經(jīng)被存放在TOKEN環(huán)境變量中,如下面的代碼清單所示,可以在向API服務器發(fā)送請求時使用它。
代碼清單8.13 獲得API服務器的正確響應
curl:/$ curl -N "Authorization: Bearer $TOKEN" https://10.96.0.1
關閉基于角色的訪問控制(RBAC)
如果我們正在使用一個帶有RBAC機制的Kubernetes集群,服務賬戶可能不會被授權(quán)訪問API服務器(或只有部分授權(quán))。我們將在第12章詳細了解服務賬戶和RBAC機制。目前最簡單的方式就是運行下面的命令查詢API服務器,從而繞過RBAC方式。
$ kubectl create clusterrolebinding permissive-binding \ --clusterrole=cluster-admin \ -- group=system:serviceaccount
這個命令賦予了所有服務賬戶(也可以說所有的pod)的集群管理員權(quán)限,允許它們執(zhí)行任何需要的操作,很明顯這是一個危險的操作,永遠都不應該在生產(chǎn)的集群中執(zhí)行,對于測試來說是沒有問題的。
我們通過發(fā)送請求的HTTP頭中的Authorization字段向API服務器傳遞了憑證,API服務器識別確認憑證并返回正確的響應,正如前面幾個章節(jié)我們所做的,現(xiàn)在可以探索集群中所有的資源。
例如,可以列出集群中所有的pod,但前提是我們知道運行curl的pod屬于哪個命名空間。
獲取當前運行pod所在的命名空間
在本章的第一部分,我們了解了如何使用Downward API的方式將命名空間的屬性傳遞到pod。如果你注意觀察的話,或許注意到secret卷中也包含了一個叫作命名空間的文件。這個文件包含了當前運行pod所在的命名空間,所以我們可以讀取這個文件來獲得命名空間信息,而不是通過環(huán)境變量明確地傳遞信息到pod。文件內(nèi)容掛載到NS環(huán)境變量中,然后列出所有的pod,如下面的代碼清單所示。
代碼清單8.14 獲取當前pod所在命名空間中的所有pod清單
curl:/$ NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)curl:/$ curl -N "Authorization: Bearer $TOKEN" --cacert $CURL_BUNDLE https://10.96.0.1/api/v1/namespaces/$NS/pod
這就對了,通過使用掛載在secret卷目錄下的三個文件,可以羅列出與當前pod運行在同一個命名空間下的所有pod的清單。使用同樣的方式不僅可以使用GET請求,還可以使用PUT或者PATCH來檢索和修改其他API對象。
簡要說明pod如何與Kubernetes交互
我們來簡單說明一下在pod中運行的應用如何正確訪問Kubernetes的API:
- 應用應該驗證API服務器的證書是否是證書機構(gòu)所簽發(fā),這個證書是在ca.crt文件中。
- 應用應該將它在token文件中持有的憑證通過Authorization標頭來獲得API服務器的授權(quán)。
當對pod所在命名空間的API對象進行CRUD操作時,應該使用namespace文件來傳遞命名空間信息到API服務器。
定義 CRUD代表創(chuàng)建、讀取、修改和刪除操作,與之對應的HTTP方法分別是POST、GET、PATCH/PUT以及DELETE。
8.2.3 通過ambassador容器簡化與API服務器的交互
使用HTTPS、證書和授權(quán)憑證,對于開發(fā)者來說看上去有點復雜。碰到過開發(fā)者在許多場景下關閉了對服務器證書驗證的功能(當然筆者有時候也會這么做)。幸運的是,我們在保證安全性的前提下有辦法簡化通信的方式。
還記得在8.2.1中提到過的 kubectl proxy 命令嗎?在本機上運行這個命令,從而可以更加方便地訪問API服務器。向代理而不是直接向API服務器發(fā)送請求,通過代理來處理授權(quán)、加密和服務器驗證。同樣,也可以在pod中這么操作。
ambassador容器模式介紹
想象一下,如果一個應用需要查詢API服務器(此外還有其他原因)。除了像之前章節(jié)講到的直接與API服務器交互,可以在主容器運行的同時,啟動一個ambassador容器,并在其中運行 kubecctl proxy 命令,通過它來實現(xiàn)與API服務器的交互。
在這種模式下,運行在主容器中的應用不是直接與API服務器進行交互,而是通過HTTP協(xié)議(不是HTTPS協(xié)議)與ambassador連接,并且由ambassador通過HTTPS協(xié)議來連接API服務器,對應用透明地來處理安全問題(見圖8.6)。這種方式同樣使用了默認憑證Secret卷中的文件。
因為在同一個pod中的所有連接共享同樣的回送網(wǎng)絡接口,所以我們的應用可以使用本地的端口來訪問代理。
運行帶有附加ambassador容器的CURL pod
為了通過操作來理解ambassador容器模式,我們像之前創(chuàng)建curl pod一樣創(chuàng)建一個新的pod,但這次不是僅僅在pod中運行單個容器,而是基于一個多用途的kubectl-proxy容器鏡像來運行一個額外的ambassador容器,這個鏡像是之前創(chuàng)建的并已提交到Docker Hub。如果你想自己來編譯它,可以在代碼存檔中找到Dockerfile鏡像(在 /Chapter08/kubectl-proxy/ 目錄下)。
pod的manifest文件如以下代碼清單所示。(tutum/curl已被廢棄)
代碼清單8.15 帶有ambassador容器的 pod:curl-with-ambassador.yaml
apiVersion: v1kind: Podmetadata: name: curl-with-ambassadorspec: containers: - name: main image: curlimages/curl command: ["sleep", "9999999"] - name: ambassador #ambassador 容器 image: datawire/ambassador
pod的spec與之前非常類似,但pod名稱是不同的,同時增加了一個額外的容器。運行這個pod,并且通過以下命令進入主容器:
$ kubectl exec -it curl-with-ambassador -c main bash
現(xiàn)在pod包含兩個容器,我們希望在main容器中運行bash,所以使用 -c main 選項。如果想在pod的第一個容器中運行該命令,也無須明確地指定容器。但如果想在任何其他的容器中運行這個命令,就需要使用 -c 選項來說明容器的名稱。
通過ambassador來與API服務器進行交互
接下來我們嘗試通過ambassador容器來連接API服務器。默認情況下, kubectl proxy 綁定8001端口,由于pod中的兩個容器共享包括回送地址在內(nèi)的相同的網(wǎng)絡接口,可以如下面的代碼清單所示,將curl指向 localhost:8001 。
代碼清單8.16 通過ambassador容器訪問API服務器
root@curl-with-ambassador:/# curl localhost:8001
成功了!curl的輸出打印結(jié)果與我們之前看到的響應相同,但這次,并不需要處理授權(quán)的憑證和服務器證書。
想要清楚地了解處理的細節(jié),請參考圖8.7。curl向在ambassador容器內(nèi)運行的代理發(fā)送普通的HTTP請求(不包含任何授權(quán)相關的標頭),然后代理向API服務器發(fā)送HTTPS請求,通過發(fā)送憑證來對客戶端授權(quán),同時通過驗證證書來識別服務器的身份。
這是一個很好的例子,它說明了如何使用一個ambassador容器來屏蔽連接外部服務的復雜性,從而簡化在主容器中運行的應用。ambassador容器可以跨多個應用復用,而且與主應用使用的開發(fā)語言無關。負面因素是需要運行額外的進程,并且消耗資源。
8.2.4 使用客戶端庫與API服務器交互
在之前的例子中,我們已經(jīng)體驗到了使用ambassador容器kubectl-proxy的好處,如果我們的應用僅僅需要在API服務器執(zhí)行一些簡單的操作,往往可以使用一個標準的客戶端庫來執(zhí)行簡單的HTTP請求。但對于執(zhí)行更復雜的API請求來說,使用某個已有的Kubernetes API客戶端庫會更好一點。
使用已有的客戶端庫
目前,存在由API Machinery special interest group(SIG)支持的兩個版本的Kuberbetes API客戶端庫。
- Golang client — https://github.com/kubernetes/client-go
- Python — https://github.com/kubernetes-incubator/client-python
注意 Kubernetes社區(qū)有大量的興趣組和工作組,這些小組分別關注著Kubernetes生態(tài)系統(tǒng)中的某個特定部分。可以在https://github.com/kubernetes/community/blob/master/sig-list.md下看到它們的清單。
除了官方支持的兩個庫,這里列出了一些由用戶貢獻的針對不同語言的客戶端庫:
- Fabric8維護的Java客戶端 — https://github.com/fabric8io/kubernetes-client
- Amdatu維護的Java客戶端 — https://bitbucket.org/amdatulabs/amdatu-kubernetes
- tenxcloud維護的Node.js客戶端 — https://github.com/tenxcloud/node-kubernetesclient
- GoDaddy維護的Node.js客戶端 — https://github.com/godaddy/kubernetes-client
- PHP — https://github.com/devstub/kubernetes-api-php-client 另一個PHP客戶端 — https://github.com/maclof/kubernetes-client
- Ruby — https://github.com/Ch00k/kubr
- 另一個Ruby客戶端 — https://github.com/abonas/kubeclient
- Clojure — https://github.com/yanatan16/clj-kubernetes-api
- Scala — https://github.com/doriordan/skuber
- Perl — https://metacpan.org/pod/Net::Kubernetes
這些庫通常支持HTTPS協(xié)議,并且可以處理授權(quán)操作,所以我們不需要使用ambassador容器。
一個使用Fabric8 Java庫與Kubernetes進行交互的例子
為了說明如何通過客戶端庫與API服務器進行交互,以下的代碼清單給出了一個例子說明如何使用Fabric8 Kubernetes客戶端列出一個Java應用中的服務。
使用Swagger和OpenAPI打造你自己的庫
如果我們選擇的開發(fā)語言沒有可用的客戶端,可以使用Swagger API框架生成客戶端庫和文檔。Kubernetes API服務器在/swaggerapi下暴露Swagger API定義,在/swagger.json下暴露OpenAPI定義。
想要了解更多關于Swagger框架的內(nèi)容,請訪問網(wǎng)站 http://swagger.io 。
使用Swagger UI研究API
在本章開頭,已經(jīng)提到了一種更好的研究Rest API的方式,而不是使用curl直接訪問REST endpoint。正如在前面部分所提到的,Swagger不僅是描述API的工具,如果暴露了Swagger API定義,還能夠提供一個用于查看REST API的web UI。
Kubernetes不僅暴露了Swagger API,同時也有與API服務器集成的Swagger UI。Swagger UI默認狀態(tài)沒有激活,可以通過使用 --enable-swagger-ui=true 選項運行API服務器對其進行激活。
提示 如果使用Minikube,可以在啟動集群時,使用 minikube start--extra-config=apiserver.Features.Enable-SwaggerUI=true 選項來激活Swagger UI。
在激活UI后,可以通過以下地址在瀏覽器中打開它:
http(s)://<api server>:<port>/swagger-ui
強烈建議嘗試使用Swagger UI,通過它不僅可以瀏覽Kubernetes API,也可以使用它來進行交互(例如,可以 POST JSON 資源manifest、PATCH資源或者DELETE它們)。