① k8s 基本使用(上)
本文將介紹 k8s 中的一些最基本的命令,並輔以解釋一些基本概念來方便理解,也就是說,本文是一篇偏向實用性而非學術性的文章,如果你想提前了解一下 k8s 相關的知識的話,可以通過以下鏈接進行學習:
k8s 是經典的一對多模型,有一個主要的管理節點 master 和許多的工作節點 slaver 。當然,k8s 也可以配置多個管理節點,擁有兩個以上的管理節點被稱為 高可用 。k8s 包括了許多的組件,每個組件都是單運行在一個 docker 容器中,然後通過自己規劃的虛擬網路相互訪問。你可以通過 kubectl get pod -n kube-system 查看所有節點上的組件容器。
在管理節點中會比工作節點運行更多的 k8s 組件,我們就是靠著這些多出來的組件來對工作節點發號施令。他們都叫什麼這里就不詳細提了。反正對於」基本使用「來說,這些名字並不重要。
要想理解一個東西就要先明白它的內在理念。通俗點就是,k8s 做了什麼?為了提供更加可靠的服務,就要增加伺服器的數量,減少每個伺服器的體量來平攤負載,而越來越多的虛擬機就會帶來越來越高的運維成本。如何讓少量的運維人員就可以管理數量眾多的伺服器及其上的服務呢?這就是 k8s 做的工作。
k8s 把數量眾多的伺服器重新抽象為一個統一的資源池 ,對於運維人員來說,他們面前沒有伺服器1、伺服器2的概念,而是一個統一的資源池,增加新的伺服器對運維人員來說,只是增加自資源池的可用量。不僅如此,k8s 把所有能用的東西都抽象成了資源的概念,從而提供了一套更統一,更簡潔的管理方式。
接下來,我會把每個基本命令當做一節來進行介紹,並輔以介紹一些基本概念。本文介紹的命令涵蓋了增刪改查四方面,可參加下面表格,因為篇幅較長,我們將 create 及之後的不那麼常用的命令放在下一篇文章 k8s 基本使用(下) 里講:
接下來進入正題,首先來了解一下 k8s 中最最最常用的命令 kubectl get ,要記住,k8s 把所有的東西都抽象成了資源,而 kubectl get 就是用來查看這些資源的。最常見的資源就是 pod 。
不僅我們自己的服務是要包裝成 pod 的,就連 k8s 自己也是運行在一堆 pod 上。接下來就讓我們查看一下 k8s 的 pod :
-n 參數指定了要查看哪個命名空間下的 pod 。 k8s 所有的 pod 都被放置在 kube-system 命名空間下。
執行了 kubectl get pod -n kube-system 命令後,你就可以看到如下內容:
其中每一行就是一個資源,這里我們看到的資源是 pod 。你看到的 pod 數量可能和我的不一致,因為這個列表裡包含了 k8s 在所有節點上運行的 pod ,你加入的節點越多,那麼顯示的 pod 也就越多。我們來一列一列的看:
kubectl get 可以列出 k8s 中所有資源
這里只介紹了如何用 kubectl 獲取 pod 的列表。但是不要把 get 和 pod 綁定在一起,pod 只是 k8s 中的一種服務,你不僅可以 get pod ,還可以 get svc ( 查看服務 )、 get rs ( 查看副本控制器 )、 get deploy ( 查看部署 )等等等等,雖然說 kubectl get pod 是最常用的一個,但是如果想查看某個資源而又不知道命令是什麼, kbuectl get <資源名> 就對了。
如果你想看更多的信息,就可以指定 -o wide 參數,如下:
加上這個參數之後就可以看到資源的所在 ip 和所在節點 node 了。
記得加上 -n
-n 可以說是 kubectl get 命令使用最頻繁的參數了,在正式使用中,我們永遠不會把資源發布在默認命名空間。所以,永遠不要忘記在 get 命令後面加上 -n 。
kubectl get 命令可以列出 k8s 中的資源,而 kubectl get pod 是非常常用的查看 pod 的命令。而 -n 參數則可以指定 pod 所在的命名空間。
kubectl describe 命令可以用來查看某一資源的具體信息,他同樣可以查看所有資源的詳情, 不過最常用的還是查看 pod 的詳情 。他也同樣可以使用 -n 參數指定資源所在的命名空間。
舉個例子,我們可以用下面命令來查看剛才 pod 列表中的某個 pod,注意不要忘記把 pod 名稱修改成自己的:
然後你就可以看到很多的信息,咱們分開說,首先是基本屬性,你可以在詳細信息的開頭找到它:
基本屬性
其中幾個比較常用的,例如 Node 、 labels 和 Controlled By 。通過 Node 你可以快速定位到 pod 所處的機器,從而檢查該機器是否出現問題或宕機等。通過 labels 你可以檢索到該 pod 的大致用途及定位。而通過 Controlled By ,你可以知道該 pod 是由那種 k8s 資源創建的,然後就可以使用 kubectl get <資源名> 來繼續查找問題。例如上文 DaemonSet/kube-flannel-ds-amd64 ,就可以通過 kubectl get DaemonSet -n kube-system 來獲取上一節資源的信息。
內部鏡像信息
在中間部分你可以找到像下面一樣的 Containers 段落。該段落詳細的描述了 pod 中每個 docker 容器的信息,常用的比如 Image 欄位,當 pod 出現 ImagePullBackOff 錯誤的時候就可以查看該欄位確認拉取的什麼鏡像。其他的欄位名都很通俗,直接翻譯即可。
事件
在 describe 查看詳情的時候,最常用的信息獲取處就是這個 Event 段落了,你可以在介紹內容的末尾找到它,如下:
是的,如果你看到上面這樣,沒有任何 Events 的話,就說明該 pod 一切正常。當 pod 的狀態不是 Running 時,這里一定會有或多或少的問題,長得像下面一樣,然後你就可以通過其中的信息分析 pod 出現問題的詳細原因了:
kubectl describe <資源名> <實例名> 可以查看一個資源的詳細信息,最常用的還是比如 kubectl describe pod <pod名> -n <命名空間> 來獲取一個 pod 的基本信息。如果出現問題的話,可以在獲取到的信息的末尾看到 Event 段落,其中記錄著導致 pod 故障的原因。
如果你想查看一個 pod 的具體日誌,就可以通過 kubectl logs <pod名> 來查看。注意,這個只能查看 pod 的日誌。通過添加 -f 參數可以持續查看日誌。例如,查看 kube-system 命名空間中某個 flannel pod 的日誌,注意修改 pod 名稱:
然後就可以看到如下輸出:
如果你發現某個 pod 的服務有問題,但是狀態還是顯示 Running ,就可以使用 kubectl logs 來查看其詳細日誌。
在本篇文章里,我們了解了 k8s 的宗旨和一些基本概念,並知道了最為常用的 get 、 descibe 及 logs 命令,知道了這三條命令之後就幾乎可以從 k8s 中獲取所有常用信息了。接下來的 k8s 基本使用(下) 里,我們會更深一步,來了解 k8s 中如何創建、修改及刪除資源。
② 計算機網路-k8s網路
K8S網路模型設計:扁平的可連通的網路
K8S的網路是一個極其復雜的網路,如果想要用兩個簡單的詞來描述K8S網路,那麼我覺得扁平和可連通是K8S網路最大的特點(不懂隔離性)。
何為連通呢?
二層網路的連通:如果能夠直接通過MAC幀直接通信的網路便是二層連通的網路,LAN就是這種網路
比如無限WIFI網路,比如乙太網
三層網路的連通:如果能夠通過IP報直接通信的網路便是三層連通的網路,便是三層連通
三層網路的連通分為兩個部分,第一個部分是三層網路中的每一個LAN都是二層連通的,其次需要存在能夠連通的路由來保證;這里可以簡單回顧下三層網路通信的流程
通過路由表確定目標ip是否在鏈路上
如果在鏈路上,通過arp協議獲取對應主機的mac地址,發送mac幀到鏈路上;
如果不在同一個鏈路上,通過本地路由表發送mac幀給下一跳,然後下一跳解析mac幀,分析ip報,繼續路由直到最終跳到目標網路再次通過mac幀發送到目標主機或者到達ttl消失。
假如其中任何一個步驟不滿足或者出問題,三層網路就無法連通
何為扁平呢?
就是希望可以在pod內直接通過IP進行互相通信而不需要在pod內部使用vpn之類的東西來連接其他pod(基礎架構化),具體的可以看下k8s對網路的設計與要求。
k8s在設計其網路時,就希望網路對運行在其中的pod是透明的,因此提出了以下的一些要求與原則
k8s組網要求
所有的Pods之間可以在不使用 NAT網路地址轉換 的情況下相互通信
所有的Nodes之間可以在不使用NAT網路地址轉換的情況下相互通信
每個Pod自己看到的自己的ip和其他Pod看到的一致
k8s網路模型設計原則
每個Pod都擁有一個獨立的 IP地址,而且 假定所有 Pod 都在一個可以直接連通的、扁平的網路空間中 。
不管它們是否運行在同 一 個 Node (宿主機)中,都要求它們可以直接通過對方的 IP 進行訪問。
設計這個原則的原因 是,用戶不需要額外考慮如何建立 Pod 之間的連接,也不需要考慮將容器埠映射到主機埠等問題。
而要想深入了解K8S的網路,就不得不去了解Linux操作系統中的網路以及計算機網路協議棧和一些網路技術
其中關於計算機網路協議棧道部分上次分享已經分享過了,所以本次的主題更多是Linux操作系統的網路以及一些網路技術
Linux操作系統中的網路
首先,我們來看下基本的linux網路,如下圖所示
一個APP生成socket數據,然後經過網路協議棧包裝IP報文,然後封裝成MAC幀,在經過網路協議棧的過程中,會存在netfilters對數據進行一定的處理,同時也會存在路由的過程,
如果在同一個物理鏈路內,將直接通過ARP協議獲取目標IP地址的MAC地址最終發送出去;
如果不在同一個物理鏈路則通過路由表確定下一跳的MAC地址,封裝成MAC幀發送到目標地址。
在這個過程中,會根據路由表選擇對應的埠,如果是lo埠,則會將幀原封不動的返回計算機網路協議棧,然後回到監聽對應埠的SOCKET里。
如果是乙太網埠則走乙太網埠,如果是藍牙或者無線網埠同理。
iptables與netfilters
iptables是一個用戶空間的應用程序,通過該程序可以修改一些配置文件,這些文件定義了防火牆的一些行為,netfilters是操作系統內核的一部分,netfilters里有5個回調鉤子會觸發iptables里的規則;iptables只是Linux防火牆的管理工具而已,位於/sbin/iptables。真正實現防火牆功能的是
netfilter,它是Linux內核中實現包過濾的內部結構。
這里不具體講述其實現的原理,僅僅列出netfilters的一些功能:
1)filter表——三個鏈:INPUT、FORWARD、OUTPUT
作用:過濾數據包 內核模塊:iptables_filter.
2)Nat表——三個鏈:PREROUTING、POSTROUTING、OUTPUT
作用:用於網路地址轉換(IP、埠) 內核模塊:iptable_nat
3)Mangle表——五個鏈:PREROUTING、POSTROUTING、INPUT、OUTPUT、FORWARD
作用:修改數據包的服務類型、TTL、並且可以配置路由實現QOS內核模塊:iptable_mangle(別看這個表這么麻煩,咱們設置策略時幾乎都不會用到它)
4)Raw表——兩個鏈:OUTPUT、PREROUTING
作用:決定數據包是否被狀態跟蹤機制處理 內核模塊:iptable_raw
虛擬網路設備 tap/tun
TUN 和 TAP 設備是 Linux 內核虛擬網路設備,純軟體實現。TUN(TUNnel)設備模擬網路層設備,處理三層報文如 IP
報文。TAP 設備模擬鏈路層設備,處理二層報文,比如乙太網幀。TUN 用於路由,而 TAP 用於創建網橋。OS 向連接到 TUN/TAP
設備的用戶空間程序發送報文;用戶空間程序可像往物理口發送報文那樣向 TUN/TAP 口發送報文,在這種情況下,TUN/TAP
設備發送(或注入)報文到 OS 協議棧,就像報文是從物理口收到一樣。
虛擬網路設備 veth-pairs
虛擬乙太網電纜。使用雙向有名管道實現。常用於不同 namespace 之間的通信,即 namespace 數據穿越或容器數據穿越。
虛擬網路設備 bridge
bridge是linux自帶的虛擬交換機(網橋),其可以連接多個乙太網設備,擁有智能處理MAC幀的能力,流向交換機的MAC幀將智能的被傳輸到相應的二層鏈路
網路命名空間
在 Linux 中,網路名字空間可以被認為是隔離的擁有單獨網路棧(網卡、路由轉發表、iptables)的環境。網路名字空間經常用來隔離網路設備和服務,只有擁有同樣網路名字空間的設備,才能看到彼此。
從邏輯上說,網路命名空間是網路棧的副本,有自己的網路設備、路由選擇表、鄰接表、Netfilter表、網路套接字、網路procfs條目、網路sysfs條目和其他網路資源。
從系統的角度來看,當通過clone()系統調用創建新進程時,傳遞標志CLONE_NEWNET將在新進程中創建一個全新的網路命名空間。
從用戶的角度來看,我們只需使用工具ip(package is iproute2)來創建一個新的持久網路命名空間。
從系統實現來說,就是原本一個數據結構是static公共的,後來變成進程私有的,在PCB里存在一個命名空間的結構,命名空間里有著網路命名空間,網路命名空間擁有著所有跟網路相關的配置數據
默認空的網路命名空間可能只有一個未啟動的lo回環網卡。
兩個網路命名空間可以通過乙太網攬直接連著兩個網路命名空間的網卡,也可以通過乙太網網橋連接。
通過乙太網網橋或者乙太網攬連接的兩個網路命名空間只能說是在二層連通的,如果希望在三層連通,還需要給每個網路命名空間配置相應的路由表規則以及分配IP地址。
如何使用虛擬網路設備聯通網路命名空間
SingleHost容器網路
none模式
本質上就是創建一個網路命名空間,裡面沒有路由表,也沒有通過veths-pair連接任何鏈路,外部無法訪問這個容器,容器也無法訪問外部
host模式
本質上就是使用宿主機的默認網路命名空間
container模式
本質上就是將當前容器部署在另一個容器所在的網路命名空間,這樣發給本地的報文最終通過回環網卡回到了本機,這是同一個網路命名空間可以互通的原因
bridge模式
橋接模式就是在這些網路命名空間通過veth-pairs連接到同一個虛擬交換機上(二層連通),同時在對應的命名空間配置對應的路由表規則,但是從圖片中可以看到交換機另一端連的上網路協議棧。
也就是那些MAC幀都會被宿主機接收,但是宿主機接收並不一定會處理,比如並沒有開啟ip轉發功能(工作於路由器模式還是主機模式),那麼不是本地ip的報文都會被丟棄;或者說netfilters拒絕處理
這些奇怪的報文。
理論上,這些容器發送給其他容器的mac報文是會被虛擬交換機智能轉發到對應的容器的,這是同一主機不同容器互相連通的原因
假如宿主機配備了相應的路由規則和防火牆規則,那麼容器的報文說能夠通過路由最終轉發出去的,這也是容器訪問互聯網的原理
但是這種模式是沒法運用在多主機的情況下,因為宿主機不知道其他宿主機里的虛擬網路的路由,當相關ip報到達宿主機時,這些ip報將會被宿主機交給默認路由(下一跳:路由器)
最終路由器會把相關的ip報丟失或者到達ttl最終丟失
MultiHost容器網路
路由方案
回顧docker的單機網路模型,我們發現多主機不能通行的原因就在於你只能給當前主機配置路由規則和防火牆規則,而其他主機並不知道這些ip在你的虛擬網路中,假如能夠將這些路由信息同步到其他
宿主機,那麼網路便會打通。比較直接的想法就是給每台宿主機配置路由規則。而路由規則要求下一跳必須在當前網路,所以假如宿主機是二層互聯的,那麼通過給這些宿主機同步這些路由規則便能夠
實現一個扁平的連通的網路。
其中布置在每一台宿主機可以通過k8s的daemonSet實現,而這種數據的管理可以交給etcd來實現。
這類方案便是基於路由,基於這個方案的實現有基於靜態路由的flannel的host-gateway,以及基於動態路由的calico(使用邊際路由協議以及一堆深奧的名詞的實現)。
下面來看看Flannel的host-gateway原理(每一台宿主機都相當於本機容器網路的路由器):
通過路由方案構建的網路,宿主機也能訪問這些虛擬網路里的Pod
詢問基德大佬得知國際化sit環境的k8s網路介面實現就是Flannel的Host-gateway,而我們的辦公網路和集群網路之間的路由是搭建好的,所以我們應該可以直接通過podId訪問pod里的服務
下面是sit環境的兩個服務
跟蹤路由發現符合猜想
其中10.1.9.56和10.1.1.24就是宿主機的ip,這些宿主機在一個LAN里,這些宿主機相當於虛擬網路中的路由器;
猜測我們辦公網和qunhe集群在一個VLAN里(二層可達)
隧道方案
隧道方案比較典型的就是UDP和XVLAN,兩者都是使用Overlay網路(覆蓋網路,所謂的大二層技術);其實用隧道技術最多的是VPN應用
其中UDP是XVLAN的替代品(早期Linux沒有支持XVLAN協議,通過tun/tap技術將流量引到用戶空間然後解包生成包再發,因為發生在用戶空間而且多次導致性能較差,所以一般不推薦,除非你的linux版本比較低沒法用xvlan)
下面就簡單介紹下XVLAN技術的大概原理,下圖是XVLAN的報文格式,可以發現就是在高層協議的報文里塞了二層報文
其中XVLAN頭里有一個關鍵的欄位,VNID這是個24位的欄位,每個虛擬的網路主機都有一個自身的VNID作為標識,理論上支持2的24次方個虛擬網路。
在docker的橋接網路里,是使用docker0網橋,在Flannel的xvlan方案里則是使用cni0作為網橋(和docker0沒啥區別),主要的不同是cni網橋後面連接的是flannel.1這個網路設備,應該是一個虛擬網卡
這個網卡將原始報文包裝成XVLAN報文(linux高版本支持xvlan報文)
這時需要的信息有 源nodeId,目標nodeId,源vnid,源macId,目標macId,源podId,目標podId
其中目標nodeId,目標macId這兩個信息是不存在的;因此需要有個方式根據目標podId獲取目標nodeId以及目標macId
因此需要記錄如何根據目標podId獲取目標macId以及目標nodeId即可
這些數據是可以託管在某個地方的,Flannel就是將這些信息記錄在etcd上
在每個node上的flannel.1網路設備通過etcd來通過對方的podId獲取nodeId和macId
這樣最終報文就變成了一個源ip是源nodeIp,目標ip是目標nodeIp的IP報文了(兩台宿主機三層可達)
原本經過虛擬網橋是直接連接網路協議棧,但在xvlan模式下,則改為連接一個flannel1,在flannel1中將對原始報文封裝成overlay報文轉發
udp模式類似,只是udp轉發報文說通過tap連通到用戶空間,用戶空間對報文進行處理然後發送(因為多次內核態用戶態切換且數據問題,性能較差,僅在不支持xvlan的低版本linux中使用)
當然xvlan是一個技術,上面只是簡單介紹最簡單的形式
參考:
開發內功修煉之網路篇: https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MjM5Njg5NDgwNA==&action=getalbum&album_id=1532487451997454337&scene=173&from_msgid=2247485270&from_itemidx=1&count=3&nolastread=1#wechat_redirect
K8S知識圖譜: https://zhaohuabing.com/post/2020-02-22-k8s-mindmap/
VXLAN協議原理簡介: https://cizixs.com/2017/09/25/vxlan-protocol-introction/
③ k8s網路原理-ipvs
一、背景知識
本文主要介紹k8s網路中service 的兩種模式(clusterIp、nodeport),數據是如何通過ipvs&iptables流轉的。在學習上述知識的同時,還需要了解一下ipset、conntrack的相關知識。 往期回顧文章
1.1、ipset
ipset是什麼?ipset其實是iptables的擴展,可以定義一些列地址的集合。拿黑名單來舉例,我想讓黑名單裡面的ip拒絕訪問網站(黑名單有很多個),按照傳統iptables做法,需要在filter表添加很多規則匹配時一條一條匹配效率很低(嚴重影響性能),而有了ipset,則只用添加一條規則即可,使用hash結構效率很高。
而使用ipset命令如下
當然,ipset還支持 hash:ip,hash:ip,port,ip等多種hash key的組成,具體可以通過 ipset -h 查看。接下來說明一下 -m set 後面 src 和 dst 兩個的含義。src 指來源,dst 指目標,此規則的意思是來自192.178.113.100 ip 訪問本機8410埠的流量給DROP掉。
ipset使用hash結構,比iptables的鏈表遍歷效率要高很多。ipset還有很多更加高級的玩法,本文就不在闡述了。
1.2、ipvs
lvs是什麼?全稱是Linux Virtual Server,是由章文嵩博士主導的開源負載均衡項目,目前已經集成到linux內核中。lvs提供了豐富的負載均衡能力,接收到用戶請求後根據具體的負載均衡演算法在內核態把請求轉發到後端的某個server上,也就是說lvs不需要監聽具體的埠。接下來我們看一下lvs的一些基本概念。
ipvs的原理如下。ipvs工作在iptables 的 input鏈上,VIP一般定義在DS節點上的一個虛擬ip,拿nat模式舉例如下。
① : 當請求數據包到DS上最先經過iptables 的PREROUTING鏈,判斷目標ip (VIP) 是本機的ip,於是把請求轉發到INPUT鏈上。
② : 因為lvs工作在INPUT鏈上,數據到達INPUT鏈上後lvs會將用戶請求和定義的後端服務做對比,如果是請求的後端服務,則使用某種負載均衡演算法找到一個後端RIP,修改數據包的目的ip和埠為某個RIP的(DNAT轉換)。
③ : 此時數據到達POSTROUTING鏈(不會做SNAT),數據包的源ip 為CIP,目的ip為RIP,數據包發往RIP上。
lvs提供了三種包轉發模式,如下所示
由於k8s使用的是NAT模式,接下來看下 NAT模式下的數據包流向 。如下圖所示
①:請求數據包到達DS,數據包經過PREROUTING鏈,此時ip 包 src ip為CIP,dst ip 為VIP
②:由於請求的VIP是DS上的虛擬ip,數據包發往INPUT鏈。
③:數據包到INPUT鏈上後,ipvs發現數據包請求是定義的集群服務,於是使用定義好的負載均衡演算法找到一個具體的RS節點,做DNAT,修改數據包dst ip為RIP,數據包到達POSTROUTING鏈,發送給RS。
④:RS收到數據包後對比dst ip 發現是自己,接收數據包做處理,處理完成後ip 數據包 src ip 為RIP,dst ip 為CIP,把數據包發給DS。
⑤:DS 接收到RS的響應包,修改src ip 為自身的VIP,dst ip 為CIP,把數據包發送給client端。
三種模式對比&優缺點
接下來在簡單聊一下ipvs的負載均衡策略,簡單介紹下面四種。
上面介紹完了ipvs內核態的基本原理,接下來介紹一下如何使用 ipvsadm 用戶態命令來操作ipvs。說明:此次試驗是在四個虛擬機上,ipvs的模式使用的nat模式,RS的網關沒有指向DS的ip(沒辦法做到)在DS節點上手動創建SNAT命令,下文有詳細介紹。創建一個vip,在ip為192.168.113.101上
為vip添加RS
添加完成RS後,查看ipvs規則,如下圖所示
client端的ip地址為192.168.113.102,client端要想直接訪問vip的話,需要在client端添加靜態路由,添加命令如下
添加完命令後,在client端curl 10.10.0.1:8410 發現不通,此時去某個RS上抓包如下
上圖抓包顯示,client 直接訪問的vip,而數據包的目的ip 變為了rs的ip,因此可以看出ipvs做了DNAT轉換。因為做了DNAT,RS發送響應數據直接發給client,client收到RS的數據包。client給vip發的包卻收到了RS的響應包(client 想我從來沒有給RS發過數據),因此client端會把此數據包丟棄。
因為ipvs沒有做SNAT,接下來在DS上添加iptables規則自己實現SNAT的功能,添加完SNAT後, RS就看不到真實的CIP了 。
此時還是不通,查找資料後發現ipvs 的 conntrack 沒有開,手動打開,後續文章介紹conntrack是什麼,設置完成後可以愉快的訪問了。
總結:通過ipvs提供的DNAT功能和負載均衡功能,很容易實現外部用戶訪問內網的需求。但是還要考慮高可用層面,比如主DS宕機VIP要漂移到備DS上,後端RS重啟或宕機,ipvs負載均衡列表中要及時把有問題的RS剔除,這樣才能真正的實現高可用。
1.3、conntrack
大家在家上網時用到的都是192.168.x.x的ip地址,這是私網ip地址。那麼大家是如何能夠成功的訪問外網的呢?答案是路由器幫我們做了SNAT的功能,使我們發出的數據包的src ip變為路由器的公網ip,這樣數據包就能在互聯網上愉快的轉發了。從而實現了對內網的保護。
那麼問題來了,既然做了SNAT轉換,那響應數據包回來以後路由器怎麼知道轉到哪台PC上呢?路由器可能鏈接了很多PC,不可能都給每一個PC轉發吧。。。答案就是conntrack實現的。
接下來我拿上面ipvs的例子舉例,我們手動實現了在DS上SNAT轉換,在client上curl vip:8410,這時候查看DS上和client上的conntrack表如下
先從client上的連接跟蹤分析起:主要看 src、dst、sport、dport這幾個欄位。
client發送數據包
client端發出數據包的src ip 為192.168.113.102,dst ip 為10.10.0.1 (VIP), sport 為35562這個埠,dport為8410(VIP 定義埠)。
client端接收響應數據包
期望src ip 為vip(10.10.0.1),dst ip 為CIP(192.168.113.102),sport為8410,dport為35562
DS接收數據包
DS接收到src ip 為CIP(192.168.113.102),dst ip 為vip(10.10.0.1),sport為35562,dport為8410的數據包
DS接收響應數據包
由於在DS側做了DNAT轉換,根據負載均衡策略找到了一個RS(RIP 192.168.113.99),同時也做了SNAT轉換(判斷是否是VIP和埠),轉換為DS的DIP。所以當DS收到src ip 為192.168.113.99(RIP),dst ip 為192.168.113.101(DIP),sport為8080,dport為35562,會根據連接跟蹤表找到這個包是192.168.113.102這個client發過來的,因此把數據包在轉發給192.168.113.102:35562 上。
conntrack各個欄位的含義
總結:
本文只是簡單的說明了一下conntrack,並沒有具體說明數據流經netfilter時何時創建記錄,數據存儲的數據結構啥樣,底層比較復雜,感興趣的大佬可以自行研究~
二、k8s網路通信
介紹完了ipset、ipvs、conntrack,接下來進入正題,看一下ipvs模式下k8s的網路通信。kube-proxy 的主要作用是watch apiserver,當監聽到pod 或service變化時,修改本地的iptables規則或ipvs規則。
2.1、clusterIp模式
clusterIp模式為一個集群內部可訪問的ip,集群外部沒辦法訪問這個ip,試驗環境如下:
創建完deployment和service後,查看一下service的ip如下。
接下來看下宿主機網卡、ipvs規則、ipset規則有什麼變化
查看iptables 的nat表和filter表,看一下k8s創建了哪些規則以及經過哪些鏈
接下來分析一下curl 10.108.113.237 數據是如何走的,只討論在nat表和filter表的流向,因為在mangle和raw都沒有規則。
1、nat表PREROUTING鏈
①:數據首先進入PREROUTING鏈,所有請求都會進入KUBE-SERVICES鏈。
②:進入KUBE-SERVICES後,查看對應在此鏈上的規則,發現請求的目的ip和port在KUBE-CLUSTER-IP 對應的ipset裡面(上面已有展示),匹配上了則跳往KUBE-MARK-MASQ鏈。
③:數據流向KUBE-MARK-MASQ鏈,主要做了mark 打標記的功能,iptables命令如下
④:之後走向KUBE-NODE-PORT鏈,因為沒有定義nodepode 類型的service,此處先略過。 2、filter表的INPUT鏈
⑤:首先進入INPUT鏈,所有數據轉向KUBE-FIREWALL鏈。
⑥:進入KUBE-FIREWALL鏈,如果發現數據包打了0x8000/0x8000,DROP掉。因為ipvs工作在INPUT鏈上,做完DNAT之後直接轉發到POSTROUTING鏈上。
3、nat表POSTROUTING鏈
⑦:進入POSTROUTING鏈,所有數據轉向KUBE-POSTROUTING鏈
⑧:進入KUBE-POSTROUTING鏈,對有0x4000/0x4000標記的數據包做SNAT轉換,因為ipvs只有DNAT功能。
4、數據轉發給flannel網卡,進行轉發
⑨:flannel 根據具體的backend模式,對數據做封包等操作,然後發出去。flannel的網路模式比較復雜,之後會專門文章進行說明。
2.2、nodeport模式
要想把集群內部的服務可以讓集群外部訪問,可以使用nodeport模式在物理機上開一個埠,這樣外部就能訪問集群內部的服務了。說明:還是使用上面創建的deployment。
查看創建service的信息,發現也創建了集群內部的一個ip。
iptables規則如下
接下來看下ipset規則有什麼變化,發現KUBE-NODE-PORT-TCP下的一個成員是剛才我們指定的那個nodePort的值。
接下來看一下iptables規則,nat表和filter表
1、nat表PREROUTING鏈
①:數據首先進入PREROUTING鏈,所有請求都會進入KUBE-SERVICES鏈。
②:ip和port匹配不上KUBE-CLUSTER-IP 的ipset,判斷是訪問的本地地址,進入KUBE-NODE-PORT鏈。
③:進入KUBE-NODE-PORT鏈後,判斷訪問埠在 KUBE-NODE-PORT-TCP ipset規則中,因此進入KUBE-MARK-MASQ鏈。
④:進入KUBE-MARK-MASQ鏈,對數據做mark標記
後續流程跟clusterIp一樣,此處就不在闡述。
2.3、dns相關
k8s中的dns默認使用的是coredns,通過以下命令查看。k8s中定義的service是有域名的,訪問域名要通過dns解析,此時coredns就發揮它的作用了。
上面的試驗時我們創建了一個my-service 的nodePort的service,此時查看一下此域名對應的ip,如下圖所示,域名解析出來的ip與service對應的ip相同,大功告成。
參考:
以上相關內容介紹了k8s service ipvs的相關實現,如有錯誤歡迎指出~
④ k8s基本使用入門-使用Pod
定義了一個 nginx.yaml 的文件:
其中定義了版本,類型,名稱,鏡像,埠等等。
現在來啟動它:
簡單查看一下狀態:
可以看到命名為 nginx 的 pod 起來了,1/1 表示只有一個
查看詳細狀態:
可以觀察到 pod 的 ip(172.30.64.2),以及所被分配到的節點(kube-node01)
進入到 pod 方式以下方法:
這樣以來就直接進入到 pod 對應的容器里邊來了,如果這個 pod 里邊含有多個容器,那麼默認是進入到第一個容器當中。如果想要進入到其他容器,那麼看下命令幫助信息:
現在看到這個 pod 在集群中的 ip 是172.30.64.2,那麼我們訪問一下
發現在集群當中訪問都是可以的,但是我們這個 nginx 部署之後,是想要讓外部人員能夠訪問的,那麼這個時候就要用的 kubectl 的port-forward了。
先看看介紹:
我們可以通過這個指令將 pod 里邊的埠映射到主機上來,方法如下
將 pod 里邊的 80 埠映射到主機的 8080 埠,那麼現在去訪問一下看看:
發現已經可以了。但是剛剛那個映射的時候,可以看到映射輸出在前台不會退出,而一旦退出之後,則這個映射又消失了,怎樣才能恆久的映射呢,這個後邊再說
⑤ K8s中pod的數據通訊
k8s的網路通訊方式
k8s的網路模型假定是所有Pod都在一個可以直接聯通的扁平的網路空間,我們需要自己實現這個網路假設,將不同節點上的Docker容器之間的互相訪問先打通,然後運行K8s。
同一個Pod內的多個容器: 同一個pod共享一個網路命名空間,共享同一個linux協議棧
同一個節點各pod之間的通訊: 通過docker0網橋直接發送數據,不通過flannel
pod與service之間的通訊: 各節點的iptables規則。
pod到外網: pod向外網發送請求,查找路由表轉發數據包到宿主機的網卡,宿主機網卡完成路由選擇後,iptables執行Masquerade,把源IP更改為著網卡的IP,然後向外網發送請求。
flannel的功能是讓集群中的不同節點主機創建的Docker容器具有集群唯一的虛擬IP地址,能夠在這些Ip地址之間建立
一個覆蓋網路(OverlayNetwork),通過這個覆蓋網路,將數據包原封不動傳遞到目標容器內部。
不同節點的pod之間的訪問:
首先,在node伺服器上會安裝flanneld的守護進程,這個進程會監聽一個埠,這個埠是用於接收和轉發數據包的埠,flanneld進程啟動以後,會開啟一個網橋叫flannel0,flannel0會抓取docker0轉發的數據包,docker0會分配自己的IP到對應的pod上,如果是同一台主機中的不同pod的話,他們之間的通訊走的是docker0網橋。如果是不同節點之間的pod進行通訊,數據包源地址寫自己的ip,目標地址寫目標地址的ip,然後將數據發送到docker0網橋也就是網關,此時flannel0有自己鉤子函數,將數據從docker0抓取,flannel0是flanneld開啟埠,數據到了flanneld之後會將數據報文進行一個封裝,然後將數據轉發到目標節點,然後到了目標節點之後會將數據進行一次解封,到達目標節點的flanneld,然後數據會進行二次解封,flanneld會通過自己的埠將數據發送到docker0網橋,然後在對數據進行轉發,從而發送到相應的目標pod。
這里的etcd與flannel的關系:
存儲管理flannel可分配的IP地址段資源
監控etcd中每個pod的實際地址,並在內存中建立維護pod節點路由表。
flannel啟動以後會向etcd中插入需要分配的網段以及這些網段分別分配在哪個節點上。
⑥ 簡述Kubernetes網路策略
為實現細粒度的容器間網路訪問隔離策略,Kubernetes引入Network Policy。
Network Policy的主要功能是對Pod間的網路通信進行限制和准入控制,設置允許訪問或禁止訪問的客戶端Pod列表。Network Policy定義網路策略,配合策略控制器(Policy Controller)進行策略的實現。我推薦你去看看時速雲,他們是一家全棧雲原生技術服務提供商,提供雲原生應用及數據平台產品,其中涵蓋容器雲PaaS、DevOps、微服務治理、服務網格、API網關等。大家可以去體驗一下。 如果我的回答能夠對您有幫助的話,求給大大的贊。
⑦ K8s創建Pod的不同姿勢,常用策略講解Demo
錢比你想像的重要得多,超過20歲了就別整天活在夢里了,對於平凡的你來講,錢就是你的尊嚴。
Pod 環境:Ansible+K8s集群
Ansible 環境准備
容器集群環境准備
kubectl explain --help
查看pod的語法結構
新建一個命名空間用於創建
kubectl config set-context context1 --namespace=liruilong-pod-create
查看當前集群信息
查看命名空間
設置剛才新建的命名空間為當前命名空間
kubectl run podcommon --image=nginx --image-pull-policy=IfNotPresent --labels="name=liruilong" --env="name=liruilong"
kubectl get pods -o wide
kubectl delete pod pod-demo --force
每個Pod都有一個pause鏡像
kubectl run pod-demo --image=nginx --image-pull-policy=IfNotPresent --dry-run=client -o yaml >pod-demo.yaml
yaml文件的獲取方法:-o yaml
yaml文件創建pod
pod-demo.yaml
yaml文件創建pod
刪除pod:delete pod
創建pod時指定運行命令。替換鏡像中CMD的命令
kubectl delete -f comm-pod.yaml 刪除pod
批量創建pod
通過 sed 更改 pod名字的方式:sed 『s/demo/demo1/』 demo.yaml | kubectl apply -f -
容器共享pod的網路空間的。即使用同一個IP地址:pod IP
一個pod內創建多個容器
comm-pod.yaml 文件編寫
創建 多容器pod
--image-pull-policy
restartPolicy –單個容器正常退出
k8s中每個資源對象都有標簽
查看標簽
指定標簽過濾
⑧ 簡述Kubernetes網路模型
Kubernetes網路模型中每個Pod都擁有一個獨立的IP地址,並假定所有Pod都在一個可以直接連通的、扁平的網路空間中。所以不管它們是否運行在同一個Node(宿主機)中,都要求它們可以直接通過對方的IP進行訪問。設計這個原則的原因是,用戶不需要額外考慮如何建立Pod之間的連接,也不需要考慮如何將容器埠映射到主機埠等問題。
同時為每個Pod都設置一個IP地址的模型使得同一個Pod內的不同容器會共享同一個網路命名空間,也就是同一個Linux網路協議棧。這就意味著同一個Pod內的容器可以通過localhost來連接對方的埠。
在Kubernetes的集群里,IP是以Pod為單位進行分配的。一個Pod內部的所有容器共享一個網路堆棧(相當於一個網路命名空間,它們的IP地址、網路設備、配置等都是共享的)。
我推薦你去看看時速雲,他們是一家全棧雲原生技術服務提供商,提供雲原生應用及數據平台產品,其中涵蓋容器雲PaaS、DevOps、微服務治理、服務網格、API網關等。大家可以去體驗一下。 如果我的回答能夠對您有幫助的話,求給大大的贊。
⑨ Pod的特性
Pod是k8s系統中可以創建和管理的最小單元,是資源對象模型中由用戶創建或部署的最小資源對象模型,也是在k8s上運行容器化應用的資源對象,其他的資源對象都是用來支撐或者擴展Pod對象功能的,比如控制器對象是用來管控Pod對象的,Service或者Ingress資源對象是用來暴露Pod引用對象的,PersistentVolume資源對象是用來為Pod提供存儲等等,k8s不會直接處理容器,而是Pod,Pod是由一個或者多個container組成的。
每個Pod都是運行應用的單個實例,如果需要水平擴展應用(例如,運行多個實例),則應該使用多個Pods,每個實例一個Pod。在Kubernetes中,這樣通常稱為Replication。Replication的Pod通常由Controller創建和管理。
Pod可以被理解成一群可以共享網路、存儲和計算資源的容器化服務的集合。再打個形象的比喻,在同一個Pod里的幾個Docker服務/程序,好像被部署在同一台機器上,可以通過localhost互相訪問,並且可以共用Pod里的存儲資源(這里是指Docker可以掛載Pod內的數據卷,數據卷的概念,後文會詳細講述,暫時理解為「需要手動mount的磁碟」)。筆者總結Pod如下圖,可以看到:同一個Pod之間的Container可以通過localhost互相訪問,並且可以掛載Pod內所有的數據卷;但是不同的Pod之間的Container不能用localhost訪問,也不能掛載其他Pod的數據卷。
1.1、為什麼需要pod
我們先談談為什麼k8s會使用pod這個最小單元,而不是使用docker的容器,k8s既然使用了pod,當然有它的理由。
1、更利於擴展
k8s不僅僅支持Docker容器,也支持rkt甚至用戶自定義容器,為什麼會有這么多不同的容器呢,因為容器並不是真正的虛擬機,docker的一些概念和誤區總結,此外,Kubernetes不依賴於底層某一種具體的規則去實現容器技術,而是通過CRI這個抽象層操作容器,這樣就會需要pod這樣一個東西,pod內部再管理多個業務上緊密相關的用戶業務容器,就會更有利用業務擴展pod而不是擴展容器。
2、更容易定義一組容器的狀態
如果我們沒有使用pod,而是直接使用一組容器去跑一個業務呢,那麼當其中一個或者若干個容器出現問題呢,我們如何去定義這一組容器的狀態呢,通過pod這個概念,這個問題就可以很好的解決,一組業務容器跑在一個k8s的pod中,這個pod中會有一個pause容器,這個容器與其他的業務容器都沒有關系,以這個pause容器的狀態來代表這個pod的狀態.
3、利於容器間文件共享,以及通信。
pod里的多個業務容器共享pause容器的ip和存儲卷Volume,pod中的其他容器共享pause容器的ip地址和存儲,這樣就做到了文件共享和互信。
1.2 Pod 特性:
1 資源共享:IP和Volume
一個Pod里的多個容器可以共享存儲和網路IP,可以看作一個邏輯的主機。共享的如 namespace,cgroups或者其他的隔離資源。
多個容器共享同一個network namespace,由此在一個Pod里的多個容器共享Pod的IP和埠namespace,所以一個Pod內的多個容器之間可以通過localhost來進行通信,所需要注意的是不同容器要注意不要有埠沖突即可。不同的Pod有不同的IP,不同Pod內的多個容器之前通信,不可以使用IPC(如果沒有特殊指定的話)通信,通常情況下使用Pod的IP進行通信。
k8s要求底層網路支持集群內任意兩個pod直接的TCP/IP直接通信,這通常才有虛擬二層網路技術來實現,例如Flannel,Openswitch等。
一個Pod里的多個容器可以共享存儲卷,這個存儲卷會被定義為Pod的一部分,並且可以掛載到該Pod里的所有容器的文件系統上。
2 生命周期短暫
Pod屬於生命周期比較短暫的組件,比如,當Pod所在節點發生故障,那麼該節點上的Pod會被調度到其他節點,但需要注意的是,被重新調度的Pod是一個全新的Pod,跟之前的Pod沒有半毛錢關系。
3 平坦的網路
K8s集群中的所有Pod都在同一個共享網路地址空間中,也就是說每個Pod都可以通過其他Pod的IP地址來實現訪問。
1.3 Pod使用和管理
1、核心原則是:將多個應用分散到多個Pod中 原因:基於資源的合理應用;擴縮容,不同應用應該有不同的擴縮容策略等。
結論:單Pod單容器應用,除非特殊原因。
你很少會直接在kubernetes中創建單個Pod。因為Pod的生命周期是短暫的,用後即焚的實體。當Pod被創建後(不論是由你直接創建還是被其他Controller),都會被Kubernetes調度到集群的Node上。直到Pod的進程終止、被刪掉、因為缺少資源而被驅逐、或者Node故障之前這個Pod都會一直保持在那個Node上。
Pod不會自愈。如果Pod運行的Node故障,或者是調度器本身故障,這個Pod就會被刪除。同樣的,如果Pod所在Node缺少資源或者Pod處於維護狀態,Pod也會被驅逐。Kubernetes使用更高級的稱為Controller的抽象層,來管理Pod實例。雖然可以直接使用Pod,但是在Kubernetes中通常是使用Controller來管理Pod的。
1.4、Pod和Controller
Controller可以創建和管理多個Pod,提供副本管理、滾動升級和集群級別的自愈能力。例如,如果一個Node故障,Controller就能自動將該節點上的Pod調度到其他健康的Node上。
包含一個或者多個Pod的Controller示例:
Deployment
StatefulSet
DaemonSet
通常,Controller會用你提供的Pod Template來創建相應的Pod。
在用戶定義范圍內,如果pod增多,則ReplicationController會終止額外的pod,如果減少,RC會創建新的pod,始終保持在定義范圍。例如,RC會在Pod維護(例如內核升級)後在節點上重新創建新Pod。
對Pod的定義可以通過Yaml或Json格式的配置文件來完成。關於Yaml或Json中都能寫哪些參數,參考官網 http://kubernetes.io/docs/user-guide/pods/multi-container/
Pod的yaml整體文件內容及功能註解如下:
我們來看一個段實際的例子
在使用docker時,我們可以使用docker run命令創建並啟動一個容器,而在Kubernetes系統中對長時間運行的容器要求是:其主程序需要一直在前台運行。如果我們創建的docker鏡像的啟動命令是後台執行程序,例如Linux腳本:
nohup ./startup.sh &
則kubelet創建包含這個容器的pod後運行完該命令,即認為Pod執行結束,之後根據RC中定義的pod的replicas副本數量生產一個新的pod,而一旦創建出新的pod,將在執行完命令後陷入無限循環的過程中,這就是Kubernetes需要我們創建的docker鏡像以一個前台命令作為啟動命令的原因。
對於無法改造為前台執行的應用,也可以使用開源工具supervisor輔助進行前台運行的功能。
Pod可以由一個或多個容器組合而成
場景1:單個應用多個容器
spring boot web:
kubectl create -f springboot-deployment.yml
kubectl get pods -o wide
加入 –o wide參數 查看額外信息:包括node和ip
pod處於pending的原因:通過 kubectl describe pods springbootweb 進一步查找問題。
可以看到pod的鏡像信息寫錯了:
先刪除pod,然後再創建: kubectl delete pod springbootweb
由於創建的埠號是9081,可以直接訪問:curl 10.0.86.2:9081
# curl 10.0.86.2:9081
Hello world
場景2:Pod不同應用多個容器組合而成
例如:兩個容器應用的前端frontend和redis為緊耦合的關系,應該組合成一個整體對外提供服務,則應該將這兩個打包為一個pod.
配置文件frontend-localredis-pod.yaml如下:
屬於一個Pod的多個容器應用之間相互訪問只需要通過localhost就可以通信,這一組容器被綁定在一個環境中。
使用kubectl create創建該Pod後,get Pod信息可以看到如下圖:
#kubectl get gods
可以看到READY信息為2/2,表示Pod中的兩個容器都成功運行了.
2.3 集群外部訪問Pod
上面的例子,在k8s集群的安裝有kube-proxy的node節點上,可以直接通過curl 10.0.86.2:9081 訪問集群的pod。但在集群外的客戶端系統無法通過Pod的IP地址或者Service的虛擬IP地址和虛擬埠號訪問到它們。為了讓外部客戶端可以訪問這些服務,可以將Pod或Service的埠號映射到宿主機,以使得客戶端應用能夠通過物理機訪問容器應用。
1、將容器應用的埠號映射到物理機
(2)通過設置Pod級別的hostNetwork-true,該Pod中所有容器的埠號都將被直接映射到物理機上。設置hostNetwork-true時需要注意,在容器的ports定義部分如果不指定hostPort,則默認hostPort等於containerPort,如果指定了hostPort,則hostPort必須等於containerPort的值。
靜態pod是由kubelet進行管理的僅存在於特定Node的Pod上,他們不能通過API Server進行管理,無法與ReplicationController、Deployment或者DaemonSet進行關聯,並且kubelet無法對他們進行健康檢查。靜態Pod總是由kubelet進行創建,並且總是在kubelet所在的Node上運行。
創建靜態Pod有兩種方式:配置文件或者HTTP方式
1)配置文件方式
首先,需要設置kubelet的啟動參數"--config",指定kubelet需要監控的配置文件所在的目錄,kubelet會定期掃描該目錄,並根據目錄中的 .yaml或 .json文件進行創建操作
假設配置目錄為/etc/kubelet.d/配置啟動參數:--config=/etc/kubelet.d/,然後重啟kubelet服務後,再宿主機受用docker ps或者在Kubernetes Master上都可以看到指定的容器在列表中
由於靜態pod無法通過API Server直接管理,所以在master節點嘗試刪除該pod,會將其變為pending狀態,也不會被刪除
#kubetctl delete pod static-web-node1
要刪除該pod的操作只能在其所在的Node上操作,將其定義的.yaml文件從/etc/kubelet.d/目錄下刪除
#rm -f /etc/kubelet.d/static-web.yaml
#docker ps
Volume類型包括:emtyDir、hostPath、gcePersistentDisk、awsElasticBlockStore、gitRepo、secret、nfs、scsi、glusterfs、persistentVolumeClaim、rbd、flexVolume、cinder、cephfs、flocker、downwardAPI、fc、azureFile、configMap、vsphereVolume等等,可以定義多個Volume,每個Volume的name保持唯一。在同一個pod中的多個容器能夠共享pod級別的存儲卷Volume。Volume可以定義為各種類型,多個容器各自進行掛載操作,講一個Volume掛載為容器內需要的目錄。
如下圖:
如上圖中的Pod中包含兩個容器:tomcat和busybox,在pod級別設置Volume 「app-logs」,用於tomcat想其中寫日誌文件,busybox讀日誌文件。
配置文件如下:
busybox容器可以通過kubectl logs查看輸出內容
#kubectl logs volume-pod -c busybox
tomcat容器生成的日誌文件可以登錄容器查看
#kubectl exec -ti volume-pod -c tomcat -- ls /usr/local/tomcat/logs
應用部署的一個最佳實踐是將應用所需的配置信息於程序進行分離,這樣可以使得應用程序被更好的復用,通過不用配置文件也能實現更靈活的功能。將應用打包為容器鏡像後,可以通過環境變數或外掛文件的方式在創建容器時進行配置注入。ConfigMap是Kubernetes v1.2版本開始提供的一種統一集群配置管理方案。
6.1 ConfigMap:容器應用的配置管理
容器使用ConfigMap的典型用法如下:
ConfigMap以一個或多個key:value的形式保存在Kubernetes系統中共應用使用,既可以用於表示一個變數的值,也可以表示一個完整的配置文件內容。
通過yuaml配置文件或者直接使用kubelet create configmap 命令的方式來創建ConfigMap
6.2 ConfigMap的創建
舉個小例子cm-appvars.yaml來描述將幾個應用所需的變數定義為ConfigMap的用法:
# vim cm-appvars.yaml
執行kubectl create命令創建該ConfigMap
#kubectl create -f cm-appvars.yaml
查看建立好的ConfigMap:
#kubectl get configmap
kubectl describe configmap cm-appvars
kubectl get configmap cm-appvars -o yaml
另:創建一個cm-appconfigfile.yaml描述將兩個配置文件server.xml和logging.properties定義為configmap的用法,設置key為配置文件的別名,value則是配置文件的文本內容:
在pod "cm-test-app"定義中,將configmap "cm-appconfigfile"中的內容以文件形式mount到容器內部configfiles目錄中。
Pod配置文件cm-test-app.yaml內容如下:
創建該Pod:
#kubectl create -f cm-test-app.yaml
Pod "cm-test-app"created
登錄容器查看configfiles目錄下的server.xml和logging.properties文件,他們的內容就是configmap 「cm-appconfigfile」中定義的兩個key的內容
#kubectl exec -ti cm-test-app -- bash
root@cm-rest-app:/# cat /configfiles/server.xml
root@cm-rest-app:/# cat /configfiles/ logging.properties
6.3使用ConfigMap的條件限制
使用configmap的限制條件如下:
Pod在整個生命周期過程中被定義為各種狀態,熟悉Pod的各種狀態有助於理解如何設置Pod的調度策略、重啟策略
Pod的狀態包含以下幾種,如圖:
Pod的重啟策略(RestartPolicy)應用於Pod內所有的容器,並且僅在Pod所處的Node上由kubelet進行判斷和重啟操作。當某哥容器異常退出或者健康檢查石柏師,kubelet將根據RestartPolicy的設置進行相應的操作
Pod的重啟策略包括Always、OnFailure及Nerver,默認值為Always。
kubelet重啟失效容器的時間間隔以sync-frequency乘以2n來計算,例如1、2、4、8倍等,最長延時5分鍾,並且成功重啟後的10分鍾後重置該事件。
Pod的重啟策略和控制方式息息相關,當前可用於管理Pod的控制器寶庫ReplicationController、Job、DaemonSet及直接通過kubelet管理(靜態Pod),每種控制器對Pod的重啟策略要求如下:
RC和DaemonSet:必須設置為Always,需要保證該容器持續運行
Job:OnFailure或Nerver,確保容器執行完成後不再重啟
kubelet:在Pod失效時重啟他,不論RestartPolicy設置什麼值,並且也不會對Pod進行健康檢查
對Pod的健康檢查可以通過兩類探針來檢查:LivenessProbe和ReadinessProbe
LivenessProbe探針:用於判斷容器是否存活(running狀態),如果LivenessProbe探針探測到容器不健康,則kubelet殺掉該容器,並根據容器的重啟策略做響應處理
ReadinessProbe探針:用於判斷容器是否啟動完成(ready狀態),可以接受請求。如果ReadinessProbe探針探測失敗,則Pod的狀態被修改。Endpoint Controller將從service的Endpoint中刪除包含該容器所在的Pod的Endpoint。
kubelet定製執行LivenessProbe探針來診斷容器的健康狀況。LivenessProbe有三種事項方式。
1)ExecAction:在容器內部執行一個命令,如果該命令的返回值為0,則表示容器健康。例:
(2)TCPSocketAction:通過容器ip地址和埠號執行TCP檢查,如果能夠建立tcp連接表明容器健康。例:
3)HTTPGetAction:通過容器Ip地址、埠號及路徑調用http get方法,如果響應的狀態嗎大於200且小於400,則認為容器健康。例:
對於每種探針方式,都需要設置initialDelaySeconds和timeoutSeconds兩個參數,它們含義如下:
initialDelaySeconds:啟動容器後首次監控檢查的等待時間,單位秒
timeouSeconds:健康檢查發送請求後等待響應的超時時間,單位秒。當發生超時就被認為容器無法提供服務無,該容器將被重啟
九.玩轉Pod調度
在Kubernetes系統中,Pod在大部分場景下都只是容器的載體而已,通常需要通過RC、Deployment、DaemonSet、Job等對象來完成Pod的調度和自動控制功能。
9.1 RC、Deployment:全自動調度
RC的主要功能之一就是自動部署容器應用的多份副本,以及持續監控副本的數量,在集群內始終維護用戶指定的副本數量。
在調度策略上,除了使用系統內置的調度演算法選擇合適的Node進行調度,也可以在Pod的定義中使用NodeName、NodeSelector或NodeAffinity來指定滿足條件的Node進行調度。
1)NodeName
Pod.spec.nodeName用於強制約束將Pod調度到指定的Node節點上,這里說是「調度」,但其實指定了nodeName的Pod會直接跳過Scheler的調度邏輯,直接寫入PodList列表,該匹配規則是強制匹配。
2)NodeSelector:定向調度
Kubernetes Master上的scheler服務(kube-Scheler進程)負責實現Pod的調度,整個過程通過一系列復雜的演算法,最終為每個Pod計算出一個最佳的目標節點,通常我們無法知道Pod最終會被調度到哪個節點上。實際情況中,我們需要將Pod調度到我們指定的節點上,可以通過Node的標簽和pod的nodeSelector屬性相匹配來達到目的。
Pod.spec.nodeSelector是通過kubernetes的label-selector機制進行節點選擇,由scheler調度策略MatchNodeSelector進行label匹配,調度pod到目標節點,該匹配規則是強制約束。
啟用節點選擇器的步驟為:
kubectl label nodes <node-name> <label-key>=<label-value>
例: #kubectllabel nodes k8s-node-1 zonenorth
運行kubectl create -f命令創建Pod,scheler就會將該Pod調度到擁有zone=north標簽的Node上。 如果多個Node擁有該標簽,則會根據調度演算法在該組Node上選一個可用的進行Pod調度。
需要注意的是:如果集群中沒有擁有該標簽的Node,則這個Pod也無法被成功調度。
3)NodeAffinity:親和性調度
該調度策略是將來替換NodeSelector的新一代調度策略。由於NodeSelector通過Node的Label進行精確匹配,所有NodeAffinity增加了In、NotIn、Exists、DoesNotexist、Gt、Lt等操作符來選擇Node。調度側露更加靈活。
9.2 DaemonSet:特定場景調度
DaemonSet用於管理集群中每個Node上僅運行一份Pod的副本實例,如圖:
這種用法適合一些有下列需求的應用:
在實際生產環境中,我們經常遇到某個服務需要擴容的場景,也有可能因為資源精確需要縮減資源而需要減少服務實例數量,此時我們可以Kubernetes中RC提供scale機制來完成這些工作。
以redis-slave RC為例,已定義的最初副本數量為2,通過kubectl scale命令可以將Pod副本數量
#kubectl scale rc redis-slave --replicas=3
ReplicationController"redis-slave" scaled
#kubectl get pods
除了可以手工通過kubectl scale命令完成Pod的擴容和縮容操作以外,新版本新增加了Horizontal Podautoscaler(HPA)的控制器,用於實現基於CPU使用路進行啟動Pod擴容縮容的功能。該控制器基於Mastger的kube-controller-manager服務啟動參數 --horizontal-pod-autoscler-sync-period定義的時長(默認30秒),周期性監控目標Pod的Cpu使用率並在滿足條件時對ReplicationController或Deployment中的Pod副本數量進行調整,以符合用戶定義的平均Pod Cpu使用率,Pod Cpu使用率來源於heapster組件,所以需預先安裝好heapster。
當集群中的某個服務需要升級時,我們需要停止目前與該服務相關的所有Pod,然後重新拉取鏡像並啟動。如果集群規模較大,因服務全部停止後升級的方式將導致長時間的服務不可用。由此,Kubernetes提供了rolling-update(滾動升級)功能來解決該問題。
滾動升級通過執行kubectl rolling-update命令一鍵完成,該命令創建一個新的RC,然後自動控制舊版本的Pod數量逐漸減少到0,同時新的RC中的Pod副本數量從0逐步增加到目標值,最終實現Pod的升級。需要注意的是,系統要求新的RC需要與舊的RC在相同的Namespace內,即不能把別人的資產轉到到自家名下。
例:將redis-master從1.0版本升級到2.0:
需要注意的點:
運行kubectl rolling-update來完成Pod的滾動升級:
#kubectl rolling-update redis-master -f redis-master-controller-v2.yaml
另一種方法就是不使用配置文件,直接用kubectl rolling-update加上--image參數指定新版鏡像名來完成Pod的滾動升級
#kubectl rolling-update redis-master --image=redis-master:2.0
與使用配置文件的方式不同的是,執行的結果是舊的RC被刪除,新的RC仍然使用就的RC的名字。
如果在更新過程總發現配置有誤,則用戶可以中斷更新操作,並通過執行kubectl rolling-update-rollback完成Pod版本的回滾。