• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            Fork me on GitHub
            隨筆 - 215  文章 - 13  trackbacks - 0
            <2017年4月>
            2627282930311
            2345678
            9101112131415
            16171819202122
            23242526272829
            30123456


            專注即時通訊及網游服務端編程
            ------------------------------------
            Openresty 官方模塊
            Openresty 標準模塊(Opm)
            Openresty 三方模塊
            ------------------------------------
            本博收藏大部分文章為轉載,并在文章開頭給出了原文出處,如有再轉,敬請保留相關信息,這是大家對原創作者勞動成果的自覺尊重!!如為您帶來不便,請于本博下留言,謝謝配合。

            常用鏈接

            留言簿(1)

            隨筆分類

            隨筆檔案

            相冊

            Awesome

            Blog

            Book

            GitHub

            Link

            搜索

            •  

            積分與排名

            • 積分 - 215465
            • 排名 - 118

            最新評論

            閱讀排行榜

            轉自:https://my.oschina.net/caicloud/blog/829365
            著作權歸作者所有,轉載請自覺標明出處,以示尊重!

            大家好,我是徐超,從事 Kubernetes 開發已經兩年多了。 

            今天,我從一個開發者的角度來講一講 client-go repository,以及怎么用 client-go 搭建 Controller。同時,也給大家講一講開發過程中遇到的坑,希望大家在開發的時候可以繞坑而行。

            另外,我還會講一下 Kubernetes 的 API,讓 controller 功能變的更加強大。

            那我們現在先來講,有哪些方法可以跟 APIserver 進行通訊。最常用的,可能就是 kubectl,以及官方支持的 UI,Kube Dashboard,這是 google 最近投入很多的一個項目。

            開發過程中 debug 的時候可以直接去調用 k8s 的 Restful API,通過寫腳本去實現 Controller。

            但是,這些做法無論從效率還是可編程性來說都是不太令人滿意的。

            這也就是為什么我們要創建 client-go,我們其實就是把寫 controller 所需的 clients,utilities 等都放到了 client-go 這個 repository 里面。大家如果需要寫 controller 的話,可以在這里面找到所需要的工具。client-go 是 go 語言的 client,除了 go 語言之外,我們現在還支持 python 的 client,目前是 beta 版本。但是這個 python client 是我們直接從 open API spec 生成的,之后我們會繼續生成 client Java 或者一些其它的語言。

            我們先看一下 client library 的內容。它主要包括各種 clients:clientset、DynamicClient 和 RESTClient。還有幫助你寫 Controller 時用到的 utilities:Workqueue 和 Informer。

            我們先看一下 kube-controller 的大致結構,典型的 controller 一般會有 1 個或者多個 informer,來跟蹤某一個 resource,跟 APIserver 保持通訊,把最新的狀態反映到本地的 cache 中。只要這些資源有變化,informal 會調用 callback。這些 callbacks 只是做一些非常簡單的預處理,把不關心的的變化過濾掉,然后把關心的變更的 Object 放到 workqueue 里面。其實真正的 business logic 都是在 worker 里面, 一般 1 個 Controller 會啟動很多 goroutines 跑 Workers,處理 workqueue 里的 items。它會計算用戶想要達到的狀態和當前的狀態有多大的區別,然后通過 clients 向 APIserver 發送請求,來驅動這個集群向用戶要求的狀態演化。圖里面藍色的是 client-go 的原件,紅色是自己寫 controller 時填的代碼。

            我們來仔細看一下各種 clients。

            先講最常見的 Clientset,它是 k8s 中出鏡率最高的 client,用法比較簡單。先選 group,比如 core,再選具體的 resource,比如 pod 或者 job,最后在把動詞(create、get)填上。

            clientset 的使用分兩種情況:集群內和集群外。

            集群內:將 controller 容器化后以 pod 的形式在集群里跑,只需調用 rest.InClusterConfig(),默認的 service accoutns 就可以訪問 apiserver 的所有資源。

            集群外,比如在本地,可以使用與 kubectl 一樣的 kube-config 來配置 clients。如果是在云上,比如 gke,還需要 import Auth Plugin。

            clientset 是用 client-gen 生成的。如果你打開 pkg/api/v1/tyeps.go,在 pod 的定義上有一行注釋,叫做“+genclient=true”,這句話的意思是,需要為這個 type 生成一個 client,如果之后要做自己的 API type 拓展,也可以通過這樣的方式來生成對應的 clients。

            Clientset 的動詞很多細微的地方比較燒腦,我來幫助大家理解一下。

            我們先來說 Get 的 GetOptions,這是 1.6 的 feature,如果在里面看 client 的 get 的話,有一個 field,叫做 resource version。

            resourece version 是 kubernetes 里面一個 logical clock,用作 optimistic concurrency。如果沒有設置 resourceversion,api-server 收到請求后會從 etcd 讀出最新的值。但設為 0 的話,APIserver 就會從 local 的 cache 里面把值讀取出來,cache 的值可能會有一定的延遲。這樣可以減輕 APIserver 和 etcd 后端的壓力。現在是用得比較多的是 kubelet,經常要 get node status,但是不需要最新的 node status,如果集群很大,就能夠省不少 cpu/memory 的開銷。如果 resource version 設成非常大的值,get request 會在 api-server 掛起,沒有響應的話會 time-out。

            同樣的,在 list 這個操作的時候,你可以提供一個 listOption,這個 listOption 里面也有 resource version,和 get 里的意義一樣。我們在寫 informer 的時候會用到。因為每一個 controller 在啟動的時候,會向 api-server 發送 list 請求,如果每一個 request 都是從 etcd 里讀取過來的話,這個開銷非常大,所以 list 會從 api-server 的 cache 讀取。

            在 watch 里面也有一個 listOption,里面 resrouce version 的意義不一樣。在 watch 的時候,apiserver 會從這個 resuorce version 開始所有的變化。這里的 best practice 設置成:永遠要設置 resource version。因為如果不設置,那么 APIserver 就會從 cache 里隨便的一個時間點開始推送,令 controller 的行為不好預測。

            我們看 informer 是怎么使用 list 進行 watch 的。在 informer 里面,我們一般都是先 list,把 resource version 設為 0,API Server 就可以從 cache 里面給我 list。List 完之后,把 list 的 resource version 取出來,并且設置為 watch 的 listOption,這樣就可以保證 informer 拿到的 events 是連續的。

            另外要注意的是,watch 不是一勞永逸的,apiserver 會 timeout 一個 watchrequest 的,默認值是 5~10 分鐘。這時你需要重新 watch。

            說一下這個 update,client 里面有兩種 update:Update 和 UpdateStatus。

            他們的區別是,如果你 Update 一個 pod,那么你對 status 的修改會被  API server overwrite 掉。UptateStatus 則相反。

            k8s 有 OptimisticConcurrency 機制,如果有兩個 client 都在 update 同一個,會 fail。所以寫代碼時一般會把 update 寫到 loop 里,直到 api-server 返回 200,ok 時才確定 update 成功。

            另外,使用 get+update 有一個 bug:假設 cluster 的 pod 有一個新的 field,如果你使用一個舊的 client,它不知道這個新的 field,那么 get 到的 pod 是沒有這個新的 field 的,再 update 的時候,這個新的 field 會被覆蓋掉。

            可能會在 1.7 的時候把這個 bug 處理掉。

            跟 update 相對應的就是 patch。Update 像拆遷隊,只會把整個 object 推倒重做。Patch 則像手術刀,可以做精細操作,可以精確修改一個 object 的 field。

            patch 如果有 conflicts,會在 apiserver 重試 5 次。除非有用戶 patch 同一個 field,否則一般 client 會一次 patch 成功。當然 patch 有性能問題,因為要在 API serve 做 Json serialiation 和 deserialization。我們估計會在 1.7 的時候優化。如果不關心性能,我們還是推薦用 patch。

            提醒一下:你在做 Patch 的時候,最佳實踐是把 original 的 UID 填在 patch 里。因為 API server 的 key-value store 是以“namespace + name”作為 key 的。在任意一個時間,這個組合都是唯一的。但是如果把時間這條軸加進來的話,比如你有一個 pod,刪除后過了一會兒,又在同一個 namespace 下建了同名的 pod,但是把所有的 spec 都改掉了,那么 controller 舊的 patch 可能會被應用到這個新建的 pod 上,這樣就會有 bug 了。如果在 patch 里加入 uid 的話,一旦發生剛才所說的情況,apiserver 會以為你是要修改 uid,這個是不允許的,所以這個 patch 就會 fail 掉,防止了 bug。

            Delete Option,有一個選項叫 precondition,它有一個 uid 選項。也是為了防止 namespace+name 的組合在時間軸上不唯一。

            當時,我們發現 K8S 的 CI tests 經常會莫名其妙的 fail 掉。最后我發現是因為剛 create 的 pod 跟之前已經被刪除的 pod 重名,但是 kubelet 不知道,就把新的 pod 給誤刪除了。所以我們 delete 的時候,這個 precondition 的 UID 請勿刪除。

             

            Delete 從 1.4 開始有一個 field 叫做 OrphanDependents。如果設為 true 或者 unset 的話,當 delete()返回的時候,這個 object 可能會在繼續存在一會兒,雖然最終還是會被刪掉。另外,這個時候,如果你把 OrphanDependents 設置成 true 或者不設置的話,要刪除的 Dependents 是不會被刪除的。如果設成 false,只要 delete()返回了,這個 object 就肯定已經在 apiserver 上被刪掉了,除非你另外設置了 finalizer。并且 garbage collector 會在背景里面慢慢刪除 dependents。

            現在講一下另外一種 client,叫做 dynamic client。

            dynamic client 用法比較靈活。因為你可以任意設置要操作的 resource。它的 return value,不是一個 structure,而是 map[string]interface{}。如果一個 controller 需要控制所有 API,比如 namespace controller 或者 garbage collector,那就用 dynamic client。使用時可以先通過 discovery,發現有哪些 API,再通過使用 dynamic client access 所有的 api。dynamic client 也支持 third party resources。

            dynamic client 的缺點是它只支持 JSON 一種序列化。而 JSON 的效率遠遠低于 proto buf。

            現在我們講一下 rest client。

            Rest Client 是 client 和 dynamic client 的基礎。屬于比較底層的,跟 dynamic client 一樣,你可以使用它操作各種 resource。支持 Do() 和 DoRaw。

            相比 dynamic client,支持 protobuf 和 json。效率會比較高。

            但是問題就是,如果你要 access third party resource,需要自己寫反序列化,不能直接 decode 到 type。在 demo 里會進行演示。

            現在我們講 informer,它的 input 其實就兩個,一是要 list function 和 watch function,二是你要給 informer 提供一些 callback。informer 跑起來后,它會維護 localstore。你之后就可以直接訪問 localstore,而不用跟 APIserver 通訊。提高一些 performance。

            使用 informer 的好處一個是性能比較好,還有一個是可靠性。如果有 network partition,informer 后會從斷點開始繼續 watch,它不會錯過任何 event 的。

            Informer 也有一些 best practice,第一點,在 controller run 之前,最好等這些 informer 都 sync 了(初始化)。這樣做,一是可以避免 controller 初始化時的 churn:比如 replica set controller 要 watch replica set 和 pod,如果不 sync 就開始 run,controller 會以為現在沒有任何 pod,會創建很多不必要的 pod,之后還要刪除。二來就是會避免很多很詭異的 bug。我在寫 garbage collector 的時候就遇到過不少。

            另外 informer 提供的 localcache 是 read-only 的。如果要修改,先用 DeepCopy 拷貝出來,否則可能有 read-write race。并且你的 cache 可能是和其他 controller 共享的,修改 cache 會影響其他 controller。

            第三個要注意的地方就是,informer 傳遞給 callbacks 的 object 不一定是你所期待的 type。比如 informer 追蹤所有 pod,返回的 Object 可能不是 pod,而是 DeletedFinalStateUnknown。所以在處理 delete 的時候,除了要處理原來跟蹤的 object,還要處理 DeletedFinalStateUnknown。

            最后要講一下的就是,informer 的 resyncoption。它只是周期性地把所有的 local cache 的東西重新放到 FIFO 里。并不是說把 APIserver 上所有的最新狀態都重新 list 一遍。這個 option 大家一般都是不會用到的,可以放心大膽地把這個 resync period 設成 0。

            最后再講一下這個 workqueue。

            其實主要是為了可以 concurrent processing,可以并行地讓 Callbacks 把狀態加到 workqueue 里,然后起一大堆的 worker。

            workqueue 提供的一個保障就是,如果是同一個object,比如同一個 pod,被多次加到 workqueue 里,在 dequeue 時,它只會出現一次。防止會有同一個 object 被多個 worker 同時處理。

            另外 workqueue 還有一些非常有用的 feature。比如說 rate limited:  如果你從 workqueue 里面拿出一個 object,處理時發生了錯誤,重新放回了 workqueue。這時,workqueue 保證這個 object 不會被立刻重新處理,防止 hot loop。

            另外的一個 feature 就是提供 prometheus 監控。你可以實時監控 queue 的長度,延遲等。你可以監控 queue 的處理速度是否跟得上。

            現在我給大家做一個 demo(https://github.com/caesarxuchao/servicelookup)。通過 k8s 的 api 用戶是沒辦法很快通過 pod 的名字找到對應的 service 的。當然你可以找到這個 pod 的 label,然后去跟 selector 進行比較而確定 service,但做這種逆向查詢是非常費時間的。

            所以我這里就是寫了這樣一種  controller,watch 所有的 endpoints 和 pods,來做比對,找到 pod 服務的 service。

            我先啟動兩個 informer,1 個 informer 是追蹤所有 pods 的變化,另一個追蹤所有 endpoints 變化。

            給 informer 注冊 callback,來把 pod 和 endpoint 的變化放到 workqueue。

            然后啟動許多 worker,從 workquue 里拿出 pod 和 endpoint 做比對。

            運行時,先啟動兩個 informer,等它們 sync,最后啟動 worker。

            Demo 的代碼在 github 上,https://github.com/caesarxuchao/servicelookup。
            視頻鏈接:http://v.qq.com/iframe/player.html?vid=c03641vzw2m&width=670&height=502.5&auto=0

            posted on 2017-01-23 17:06 思月行云 閱讀(556) 評論(0)  編輯 收藏 引用 所屬分類: Docker\K8s
            成人妇女免费播放久久久| 午夜精品久久久久成人| 久久亚洲国产最新网站| 狠狠综合久久综合中文88| 国产一级持黄大片99久久| 精品免费久久久久久久| 中文字幕久久波多野结衣av| 久久综合色之久久综合| 无码8090精品久久一区| 中文字幕无码久久人妻| 久久人人爽人人爽人人爽| 久久久久久亚洲精品影院| 欧美亚洲国产精品久久高清| 久久婷婷国产剧情内射白浆| 人妻久久久一区二区三区| 久久99精品久久久久久动态图| 精品无码久久久久久午夜| 久久电影网一区| 精品视频久久久久| 漂亮人妻被中出中文字幕久久| 欧美丰满熟妇BBB久久久| 久久99精品国产麻豆宅宅| 精品久久人人爽天天玩人人妻| 亚洲欧美一区二区三区久久| 久久精品无码一区二区WWW| 久久久精品人妻一区二区三区四 | 久久久久无码国产精品不卡| 精品久久久久久无码不卡| 久久亚洲精品人成综合网| 久久99热这里只有精品国产| 国产美女亚洲精品久久久综合 | 欧美亚洲国产精品久久高清| 亚洲精品乱码久久久久久自慰| 精品久久人妻av中文字幕| 九九热久久免费视频| 一本色道久久88—综合亚洲精品| 久久精品国产91久久麻豆自制| 久久99这里只有精品国产| a级毛片无码兔费真人久久| 久久久无码精品亚洲日韩蜜臀浪潮| 一本久久a久久精品综合夜夜 |