k8s 學習筆記


常用的kubectl命令
 
kubectl run kubia --image=luksa/kubia --port=8080 --generator=run/v1
 
--image 指定鏡像
--port 是告訴kubernetes 應用監聽8080端口
--generator 通常不會用到,它讓kubernetes創建一個replicationController . 一般不帶這個參數 ,創建的是depoly資源,deploy 在調用replicaset資源,replicaset 和replicationController很像,好像是它的升級版本有更強的selector表達能力,而且這里的--generator=run/v1 並不是創建出的rc名稱是run/v1,創建出的rc就是kubia, run/v1可能是標示rc的版本吧,具體不知道,反正后面不用這個,知道有這么個東西即可
后續學習中,得知--generator=run/v1 就是告訴kubernetes需要創建一個rc來管理pod
如果是:kubectcl run dnsutils --image=tutum/dnsutils --generator=run-pod/v1 --command -- sleep infinity
這里的--generator=run-pod/v1選項就是讓kubectl直接創建pod,而不需要通過replicationController之類的資源來創建。
 
kubectl get pods
kubectl expose rc kubia --type=loadBalancer --name kubia-http
暴露 名稱為kubia的rc 為名稱kubia-http 的service,並且使用loadbalnacer,會將創建kubia時 port端口8080 映射出來
 
kubectl get service
 
kubectl get repliactioncontrollers
 
kubectl scale rc kubia --replicas=3 擴容為3個pod
 
kubectl get pods -o wide 查看pod在哪個node節點上
 
kubectl describe pod pod-id
 
 
查看進群狀態
kubectl cluster-info
 
使用kubectl explain 來發現可能的API對象字段,如:
kubectl explain pods
想查看某個對象下某個具體字段的使用方法
kubectl explain pod.spec
 
使用kubectl create 來創建pod
kubectl create -f kubia-manual.yaml
 
得到運行中pod的完整定義
kubectl get pod kubia-manual -o yaml
kubectl get pod kubia-manual -o json
 
查看應用程序日志
kubectl logs kubia-manual
當一個pod中有多個容器時
kubectl logs kubia-manual -c kubia
 
在不通過service外界相與pod通信,可以使用port-forward命令將短褲轉發到指定pod
以下命令會將機器的本地端口8888轉發到我們的kubia-manual pod的端口8080:
kubectl port-forward kubia-manual 8888:8080
 
 
在kubernetes中 標簽時可以組織kubernetes所有資源。
kubernetes 中創建出來的具體的對象都是資源。某一個資源 屬於某一個資源類如 pod資源類,或者對象。
 
查看所有pod上有什么標簽
kubectl get po --show-labels
 
如果你想將標簽作為顯示列的列頭可以使用-L
kubectl get po -L env,app
 
添加pod標簽
kubectl label po kubia-manual env=test
 
修改pod的現有標簽
kubectl label po kubia-manual env=debug --overwrite
 
通過標簽選擇器列出pod
kubectl get po -l env=debug
 
列出包含evn標簽的pod,不管env是什么值
kubectl get po -l env
 
列出不含evn標簽的pod
kubectl get po -l '!env' (確保是單引號)
 
env !=
env in (test,debug)
env not in (prod,devel)
 
kubernetes中調用pod到哪個節點上是無關緊要的,但由於實際情況,每台node的硬件環境不一致,所以某些情況要求將不同pod調到指定節點上運行。也可以通過label實現。
kubectl label node node-id gpu=true
將pod調用到此節點只需要在yaml中描述到
apiVersion: v1
kind: pod
metadata:
kubia-gpu
spec:
nodeSelector:
gpu: "true"
containers:
- image: luksa/kubia
name: kubia
 
 
探針:對於pod中容器要進行監控,可以用探針。
3種探針方式:
http get 返回2xx,3xx 也就是沒錯誤4xx,5xx
tcp 套接字 能建立連接正常,反之不正常
exec 執行某個命令,成功即0,否則失敗
 
http get的描述方法:
apiVersion: v1
kind: Pod
metadata:
name: kubia-liveneess
spec:
containers:
- image: luksa/kubia-unhealthy
name: kubia
livenessProbe:
httpGet:
path: /
port: 8080
 
luksa/kubia-unhealthy 這個鏡像為node.js,提供web服務,其中應用中添加了讓此web服務只在前5次正常返回,5次后就會返回錯誤。
 
一般錯誤3次后,就會重啟容器,那么你想看容器錯誤日志就要看上次的日志因此要用 --previous參數
kuectl logs mypod --previous
 
使用kubectl describe po pod-id能看到具體的錯誤代碼,以及在底部顯示了容器為什么終止,--kubernetes發現容器不健康,所以終止並重新創建
 
默認在描述了探針,pod在啟動的時候就會在剛剛啟動時進行一次檢測,因此最好的做法是給檢測加一個初始延遲。
apiVersion: v1
kind: Pod
metadata:
name: kubia-liveneess
spec:
containers:
- image: luksa/kubia-unhealthy
name: kubia
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 15
初始延遲為15秒
 
 
replicationcontroller
選擇器
模版
副本數
 
如果更改選擇器,則會創建新的pod
如果更改pod的標簽,那么也會創建新的pod進行替換,但是老的pod不會被刪除
如果更改模版,比如給加入新標簽,那么將在下次替換時用到新模版,這個可以用於升級pod使用
 
kubectl edit rc kubia 這將在你的默認編輯器中打開Replicationcontroller 的YAML配置。
使用export KUBE_EDITOR="/usr/bin/nano"設置默認編輯器
 
kubectl delete rc kubia 這是刪除replicationcontroller命令,刪除rc的同時,pod也會被刪除。我們知道rc只是pod的管理器,rc創建的pod不是rc的組成部分,所以我們也可以只刪除rc而不刪除pod,使用--cascade=false 參數、
kubectl delete rc kubia --cascade=false
 
最初replicationcontroller 是用於復制和在異常時重新調度節點的唯一kubernetes組建,后來被replicaSet代替了。現在基本上見不到replicationcontroller,但是有的環境還是有可能會有,所以我們還是知道的好。
replicaset我們通常也不會直接創建,而是在創建最高層級的deployment資源時自動創建。
replicaset 與replicationcontroller的區別
replicaset 標簽選擇器的能力更強。
replicationcontroller只能指定標簽名:標簽值
replicaset 可以指定env=pro,env=devel ,也可以指定只要包含env標簽就行,理解為env=*
雖說不用直接創建,為了學習我們手動創建,
定義replicaset
apiVersion: apps/v1beta2
kind: ReplicaSet
metadata:
name: kubia
spec:
replicas: 3
selector:
matchLables:
app: kubia
template:
metadata:
lables:
app:kubia
spec:
containers:
- name:kubia
image: luska/kubia
 
template部分和replicationController 一樣
selector處不一樣。replicationController 用selector ,replicaSet用 selector.matchLables選擇器 更簡單,更具有表達力
replicaSet 的簡寫 rs
kubectl get rs
kubectl describe rs
 
rs 的matchlables 是精確匹配,說rs支持更強表達能力的選擇器,是因為rs還有matchExpressions選擇器
selector:
matchExpressions:
- key: app
operator: In
values:
- kubia
operator(運算符)有四個
In
NotIn
Exists: 必須包含某個key,值不重要,values不應指定
DoesNotExists: 必須不包含某個key, values不應指定
 
當你的replicaSet 的選擇器同時定義了matchLables和 matchExpressions ,必須兩個同時滿足才能是pod和選擇器匹配
 
kubectl delete rs kubia 刪除rs的同時pod也被刪除,刪除前建議列出pod進行確認
 
rc,rs 都用於在kubernetes集群上運行指定數量的pod,但他們不關心在哪個節點上運行。有種情況是讓每個節點都運行某一個pod比如日志收集,和監控應用。這時候要用到另外一個控制器了DaemonSet.
DaemonSet也使用Pod模版,默認是將模版中的pod,在每個節點中創建pod,但也有特殊情況,只在某特定節點上創建pod,比如不同的硬盤類型。這可以用pod模版的nodeSelector屬性指定
apiVersion: apps/v1beta2
kind: DaemonSet
metadata:
name: ssd-monitor
spec:
selector:
matchLables:
app: ssd-monitor
template:
metadata:
lables:
app: ssd-monitor
spec:
nodeSelector:
disk: ssd
containers:
- name: main
image: luksa/ssd-monitor
 
DaemonSet 的簡寫ds
kubectl get ds
同樣刪除ds,同時也會刪除pod
 
rc,rs,ds都是持續運行pod也就是pod執行結束或者手動刪除pod,這些控制器都會重啟pod,有些時候需要讓pod運行完成退出后不在重啟,並且保證pod如果在運行時異常退出了可以重啟的情況。這就需要用到job了。
job管理的pod,正常完成退出后不重啟,當節點故障時,會按照replicaSet的pod方式,重新安排到一個新節點。也可以配置job,如果進程異常退出返回錯誤碼時,重啟容器。
 
apiVersion: batch/v1
kind: Job
metadata:
name: batch-job
spec:
template:
metadata:
lables:
app: batch-job
spec:
restartPolicy: onFailure Job不能使用Always為默認的重啟策略
containers:
- name: main
image: luksa/batch-job
這里么有定義標簽選擇器,它將根據pod模版中的標簽自動創建標簽選擇器
onFailure 當容器出錯時重啟容器,如果時Never將永遠不重啟
 
kubectl get jobs
kubectl get po -a 查看被刪除的pod
 
可以配置job 按順序運行幾次
apiVersion: batch/v1
kind: Job
metadata:
name: batch-job
spec:
completions: 5
template:
... ...
也可以聲明可以並行的數量
apiVersion: batch/v1
kind: Job
metadata:
name: batch-job
spec:
completions: 5 共計運行5次
parallelism: 2 可以並行2個
template:
... ...
parallelism也可以像replicaSet一樣擴縮容
kubectl scale job batch-job --replicas 3
 
job是一次性任務,萬一運行卡住了,你永遠不知道它的狀態,所以你可以限制它運行的時長。超過時長標記為失敗,這就需要用到pod中activeDeadlineSeconds 屬性來定義運行時長。
同時可以定義job 中spec.backoffLimit配置job被標記為失敗之前可以重試的次數。默認為6.
 
job創建后,立即會運行,但有些時候我們希望定時運行job,這就需要用到kubernetes的CronJob資源
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: batch-job-every-fifteen-minutes
spec:
schedule: "0,15,30,45 * * * *" 每15分鍾執行一次並且在0,15,30,45
JobTemplate:
spec:
template:
matedata:
lables:
app: periodic-batch-job
spec:
restartPolicy: OnFailure
containers:
- name: main
image: luksa/batch-job
假如我們的任務運行時間要求非常准確,不希望原本定在10:30:00運行的任務拖到10:30:16運行,可能超過15秒運行結果就有偏差了。這時可以設置startingDeadlineSeconds。
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: batch-job-every-fifteen-minutes
spec:
schedule: "0,15,30,45 * * * *" 每15分鍾執行一次並且在0,15,30,45
startingDeadlineSeconds: 15
JobTemplate:
 
replicationController,replicaSet,DaemonSet, Job, CronJob 這幾種管理pod的控制器的基本內容就這些了。高級用法碰到在了解。
 
kubernetes service資源
 
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: kubia
 
kubectl get svc
 
kubectl exec kubia-id -- curl -s http://service_ip
雙橫缸代表着kubectl 命令項的結束,下面的是容器內部執行的命令
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
sessionAffinity: ClientIP
sessionAffinity屬性默認為None,ClientIP 是保證特定客戶端產生的請求每次都指向同一個pod
 
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
ports:
- name: http
port: 80
targetPort: 8080
- name: https
port: 443
target: 8443
selector:
app: kubia
標簽選擇器應用於整個服務,不能對每個端口做單獨的配置
 
上面是采用端口號進行映射,還有一種方式給端口命名,這樣在做映射的時候直接指向名稱。好處是pod的端口隨便改,而不用改service的配置如下:
kind: Pod
metadata:
name: kubia
spec:
containers:
- name: kubia
ports:
- name: http
containerPort: 8080
- name: https
containerPort: 8443
 
apiVersion: v1
kind: Service
spec:
ports:
- name: http
port: 80
targetPort: http
- name: https
port: 443
targetPort: https
selector:
app: kubia
kubectl delete po --all 刪除所有pod ,而不管pod的id
kubectl delete all --all all代表所有資源,--all代表所有資源對象
 
backend-database.default.svc.cluster.local
backend-database 服務名稱
default 命名空間
svc.cluaster.local是在所有集群本地服務名稱中使用的可配置集群域后綴
 
 
kubectl exec -ti kubia-3inly bash 運行bash很像 docker exec -ti id bash
不要ping kubernetes中創建的服務名稱,這是因為服務的ip是一個虛擬的IP,只有在與服務端口結合時才有意義
 
 
endpoint資源
kubernetes service不僅可以暴露pod給外部,同樣也可以把外部服務創建為服務讓內部pod進行訪問。服務並不是和pod直接相連的,有一種資源-endpoint介於兩者之間。
endpoint資源就是暴露一個服務的IP地址和端口的列表,endpoint資源和其他kubernetes資源一樣,所以可以使用kubectl info 來獲取它的基本信息
kubectl describe svc kubia 執行此命令能看到endpoint資源
kubectl get endpoints kubia
我知道在創建service時定義了selector pod選擇器,但在重定向傳入連接時不會直接使用它。
選擇器用於構建IP和端口列表,然后存儲在EndPoint資源中。當客戶端連接到服務時,服務代理選擇這些IP和端口對中的一個,並將傳入連接重定向到改位置監聽的服務器。
 
EndPoint是一個單獨的資源,而不是service的屬性,所以我們可以單獨的創建endpoint資源對象。
我們在創建service時不聲明pod選擇器就不會創建endpoint
apiVersion: v1
kind: Service
metadata:
name: external-service
spec:
ports:
- port: 80
這里並沒有定義selector
下面我們手動創建endpoint
apiVersion: v1
kind: Endpoints
metadata:
name: external-service 這里的名稱一定和service的一致
subsets:
- addresses:
- ip: 1.1.1.1
- ip: 2.2.2.2
ports:
- port: 80 這里的port是endpoint的目標端口,是service中的targetPort
以上就做了一個將外部服務通過service讓內部pod可以訪問。
 
還有一種簡單的方式,給外部服務創建一個別名服務。
apiVersion: v1
kind: Service
matedata:
name: external-service
spec:
type: ExternalName 代碼的type被設置成ExternalName
externalName: someapi.somecompany.com 實際服務的完全限定名
ports:
- port: 80
 
內部就可以使用external-service來訪問服務了
 
 
kubectl get po --all-namespaces 非常好用
 
kubernetes關於pod掛載卷的知識
首先要知道卷是pod資源的屬性,pv,pvc是單獨的資源。pod 資源的volumes屬性有多種type,其中就包含有掛載pvc的類型。這也幫我理清了之間的關系。
pv一般是系統管理員做的
pvc 是一般k8s用戶聲明的,大概意思就是說我需要一個 什么權限的,多少存儲空間的卷,然后k8s api收到這個請求就去找pv資源對象,如果兩者相匹配,那么pv就和pvc綁定了。
從這里我們也想到了,pv如果是手動創建的話,那就麻煩大了。幾個,幾十個還好說,上萬個,管理員該如何創建這么多。所以才有了動態創建pv的需求。這就引出另外一個資源 storageClass ,這個資源聲明后端掛載什么樣的存儲服務,比如nfs,chef等,有了這個一般用戶在定義pvc的時候,在提出存儲空間和讀寫權限的同時聲明用那個storageClass了,如下:
apiVersion: v1
kind: PersistentVolumeClaim
metadata: mysql-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: managed-nfs-storage (注意這里)
resources:
requests:
storage: 5Gi
 
以上便是pvc使用storageClass資源對象創建pv,pvc的綁定了
api收到對storageClass聲明后,就會調用這個storageClass的生產者進行生產。
我們就會想到,在創建storageClass資源對象的時候肯定會指定生產者。
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: example-nfs
provisioner: example.com/nfs 這里指定生產者,其中example.com/nfs只是一個標簽,你在deploymend里定義什么這里就寫什么。
mountOptions:
- vers=4.1
 
那么問題又來了。在k8s中生產者組件不支持nfs,所以才會安裝第三方組件。第三方組件的安裝就需要創建相關的rbac的角色和賬戶。第三方組件是用deploymend的資源托管相關組件pod的。
那么通過deploy部署的pod怎么就是provisioner了?這個我不清楚,后面學習后在總結吧。
 
 
回到正題上。
volumes 的4種
1. emptyDir
2. gitRepo
3. hostpath
4.PersistentVolumeClaim
5.configMap,secret
6. 各種雲平台的存儲磁盤卷如google的gce,aws的ebs,azure的azureDisk
其實4只是一個概括,nfs,chef 這些網絡存儲通通可以單獨來使用。但我覺得實際使用中還是講這些網絡存儲轉化成pv,pvc
 
從簡單開始學習
emptyDir 兩種應用場景: 1. 同一個pod中,各個容器間共享文件。 2. 當程序對大數據處理時內存空間不夠時臨時寫入文件(當然也可以使用宿主主機的內存)
例子:
apiVersion: v1
kind: Pod
metadata:
name: fortune
spec:
containers:
- image: luksa/fortune
name: html-generator
volumeMounts:
- name: html
mountPath: /var/htdocs
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPaht: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
emptyDir: {} (為{}表示使用節點服務器的文件系統)
- name: html-2
emptyDir:
medium: Memory (使用節點服務器的內存)
 
以上就是emptyDir的用法。gitRepo其實是依賴於emptyDir的,你可以把它理解成,聲明了一個emptyDir然后在把gitrepo下載填充到emptyDir
例子:
apiVersion: v1
kind: Pod
metadata:
name: gitrepo-volume-pod
spec:
containers:
- image: nginx: alpine
name: web-nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
gitRepo:
revision: master
directory: . (這個.很重要,表示在當前emptyDir目錄下不然就會創建一個kubia-website-example目錄)
 
以上就是gitRepo的用法,其中有個問題就是pod中的gitrepo並不會及時更新。如果想要及時更新需要用到sidecar ,加入到pod中,在Docker Hub搜索 “git ryc”獲取相關鏡像。
還有一個情況不得不使用sidecar container 。gitrepo 不支持連接私有庫,也就是不能是ssh密鑰連接,也不可以有用戶名和密碼。這時候就需要使用支持驗證連接的sidecar container來實現了。具體怎么使用,用到的時候在研究吧.
至此gitRepo卷類型算是簡單介紹了,下面學習hostpath
 
大多數時候pod應該忽略他們的主機節點,因此他們也不需要訪問節點文件系統上的文件。但是某些系統級別的pod(通常由DaemonSet管理)確實需要讀取節點的文件或使用節點文件系統來訪問節設備,這時候就需要用到hostPath
hostPath 算是第一個持久化存儲卷了,但是這個很少用到,因為這個卷只能在某一單一節點上,pod重啟后很可能在另外一個節點上。當然可以使用NodeSelector但這樣看起來也不高明。所以建議使用網絡存儲。hostPath過
 
接下來是網絡存儲和雲平台提供的存儲磁盤卷。這兩種在用到的時候找相關的屬性進行配置即可。也沒什么要注意的,實際應用場景用到最多的持久存儲是pv,pvc,storageClass
 
 
configmap
kubectl create configmap fortune-config --from-literal=sleep-interval=25
 
一般configmap包含多個映射條目所以創建時可以多次使用--from-literal參數
kubectl create conigmap fortune-config --from-literal=foo=bar --from-literal=bar=baz --from-literal=one=two
 
kubectl get configmap fortune-config -o yaml
 
configmap同樣可以存儲粗力度的配置數據,比如完整的配置文件。kubectl create configmap 命令支持從硬盤上讀取文件,並將文件內容單獨存儲為ConfigMap中的條目:
kubectl create configmap my-config --from-file=config-file.conf
運行此命令,kubectl 會在當前目錄下找config-file.conf文件,並將文件內容存儲在configmap中以config-file.conf 為鍵名的條目下。當然也可以手動指定鍵名
kubectl create configmap my-config --from-file=customkey=config-file.conf
 
kubectl create configmap my-config --from-file=/path/to/dir
這時候,kubectl會為文件中每個文件單獨創建條目,僅限文件名可作為合法ConfigMap鍵名的文件。
 
當讓也可以將上面的參數混合使用
 
configmap設置完成了,如何將映射的值傳遞給pod的容器?三種方法
一,設置環境變量,例子如下:
apiVersion: v1
kind: Pod
metadata:
name: fortune-env-from-configmap
spec:
containers:
- image: luksa/fortune:env
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
很好奇當pod 容器中引用不存在的configmap會發生什么?
pod會嘗試運行所有容器,只有這個容器啟動失敗,其他正常(假如有多個容器),當你事后創建處這個configmap,無需重啟pod,容器就會成功。當然也可以引用為可選的,設置configMapKeyRef.optional: true即可,這樣即使ConfigMap不存在,容器也能正常啟動。
 
如果configmap中條目很多,用env屬性列出麻煩且容易出錯。那么有沒有辦法一次導入呢。用envFrom, 例如configmap中有三個條目FOO、BAR和FOO-BAR
 
spec:
containers:
- image: some-image
envFrom:
- prefix: CONFIG_ 所有環境變量均包含前綴CONFIG_ ,不設置就將引用configmap中的鍵名
configMapRef:
name: my-config-map
如此,容器中多出兩個變量 CONFIG_FOO 、CONFIG_BAR
為什么是兩個,因為另外一個FOO-BAR包含破折號,不是一個合法的環境變量名稱。被忽略了,所以我們應該注意創建configmap時 不要使用-
 
上面我們學習了如何將configmap中的條目以變量的形式傳入到容器中
那么如何將configmap中的條目作為容器運行的參數args呢?例子:
apiVersion: v1
kind: Pod
metadata:
name: fortune-env-from-configmap
spec:
containers:
- image: luksa/fortune:env
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
args: ["$(INTERVAL)"]
 
環境變量和參數都適合於變量值較短的場景。configmap是可以包含完整配置文件內容的,當你想要將其暴露給容器時,可以使用上一章中提到的卷的類型。configmap卷會將ConfigMap中的每個條目均暴露成一個文件。運行在容器的進程通過讀取文件內容獲得對應的條目值。
configmap-files為一個目錄
kubectl create configmap fortune-config --from-file=configmap-files
例子
apiVersion: v1
kind: Pod
metadata:
name: fortune-configmap-volume
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
...
- name: config
mountPaht: /etc/nginx/config.d
readOnly: true
....
volumes:
...
- name: config
configMap:
name: fortune-config
...
 
一種特殊情況,當你只想暴露configmap-files目錄下的某一個配置文件,該如何做:
volumes:
- name: config
configmap:
name: fortune-config
items:
- key: my-nginx-config.conf
path: gzip.conf
這樣配置后,掛載fortune-config 卷后,就只有my-nginx-config.conf 並且掛載后的名稱為gzip.conf
 
另一種特殊情況,我們通過上述掛載configmap卷后會發現,被掛載的目錄之前的文件都被隱藏掉了。那么如果你需求不想隱藏之前的文件,該如何做:
spec:
containers:
- image: some/image
volumeMounts:
- name: myvolume
mountPath: /etc/someconfig.conf 掛載到指定的某一個文件,而不是某個文件夾
subPath: myconfig.conf 掛載指定的條目,而不是完整的卷
 
為configMap卷中的文件設置權限
volumes:
- name: config
configmap:
name: fortune-config
defaultMode: "6600"
 
更新應用配置且不重啟應用程序
使用環境變量或命令行參數作為配置源的弊端在於無法在進程運行時更新配置。將ConfigMap暴露為卷可以達到配置熱更新的效果,無需重新創建pod或重啟容器。
ConfigMap被更新后,卷中引用它的文件也會相應更新,進程發現文件被改變后進行重載。kubernetes同樣支持文件更新后手動通知容器。但要注意的是,更新configmap之后對應文件的更新會相當耗時。
我們使用kubectl edit configmap fortune-config 來更改某一個值。
然后執行
kubectl exec fortune-configmap-volume -c web-server -- cat /etc/nginx/config.d/my-nginx-config.conf
查看文件是不是被修改了,如果沒有稍等一會再查看。在確認更改后,我們來手動通知容器進行重載。
kubectl exec fortune-configmap-volume -c web-server -- nginx -s reload
 
你可能會疑惑 Kubernetes更新完configmap卷中的所有文件之前(所有的文件被更新而不是一部分),應用是否會監聽到文件編號並主動進行重載。答案是不會,會在所有的文件更新后,一次性更新到pod容器中。kubernetes通過富豪鏈接做到這一點的。
kubectl exec -ti fortune-configmap-volume -c web-server -- ls -lA /etc/nginx/config.d
..4989_09_04_15_06.028485
..data -> ..4989_09_04_15_06.028485
my-nginx-config.conf -> ..data/my-nginx-config.conf
sleep-interval -> ..data/sleep-interval
可以看到,被掛載configMap卷中的文件是..data文件夾中文件的符號鏈接,而..data又是連接到 ..4989的符號鏈接,每當ConfigMap被更新后,Kubernetes 會創建一個這樣的文件夾,卸任所有文件並重新將符號..data鏈接至新文件夾,通過這種方式可以一次性修改所有文件。
 
如果掛載configmap中的某一個文件,而不是文件夾,configmap更新之后對應的文件不會被更新。如果現在你需要掛載單個文件且在修改Configmap后想自動更新,可以將該卷掛載到其他文件夾,然后做一個軟鏈接。
至於如何實現應用重載配置 ,需要應用自己實現了。
 
configmap都是以名文存儲的,有些信息比較敏感,就需要用秘文存儲了。
這就需要使用kubernetes 的secret資源了。
首先有一個情況我們需要了解
使用kubectl describe pod pod-id 查看任何一個pod你都會發現默認有掛載一個secret卷。
Volumes:
default-token-ps7ff:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-ps7ff
Optional: false
這個卷里的內容我們可以使用kubectl describe secrets查看
# kubectl describe secrets default-token-ps7ff
Name: default-token-ps7ff
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name: default
kubernetes.io/service-account.uid: 6efa7f7c-6a61-11e9-bfdb-0a382f97318e
 
Type: kubernetes.io/service-account-token
 
Data
====
ca.crt: 1359 bytes
namespace: 7 bytes
token: eyJhbGciOiJSUzI1NiIsImtp...
可以看出這個Secret包含是哪個條目ca.crt 、namespace與token
包含了從pod內部安全訪問Kubernetes API服務器所需的全部信息。盡管我們希望做到應用對kubernetes無感知,但是直連Kubernetes沒有其他方法,你只能使用secret卷提供的文件來訪問kubernetes API。
使用kubectl describe pod命令顯示secret卷北掛載的位置:
mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-ps7ff (ro)
 
注意: default-token Secret默認會被掛載至每個容器。可以通過設置pod定義中的automountServiceAccountToken字段為false,或者設置pod使用的服務賬戶中的相同字段為false來關閉這種默認行為。
 
查看容器中掛載了哪些條目
# kubectl exec nginx-dnm9n -- ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt
namespace
token
 
創建Secret
openssl genrsa -out https.key 2048
openssl req -new -x509 -key https.key -out https.cert -days 3650 -subj /CN=www.kubia-example.com
# kubectl create secret generic fortune-https --from-file=https.key --from-file=https.cert --from-file=foo
secret/fortune-https created
也可以使用 --from-file=fortune-https 囊括這個歌文件夾中的文件
# kubectl get secret fortune-https -o yaml
# kubectl describe secret fortune-https
 
對比configmap與secret
secret與configMap有比較大的區別,這也是為何kubernetes開發者們在支持了Secret一段時間之后會選擇創建ConfigMap。穿件的Secret的YAML格式定義顯示
# kubectl get secret fortune-https -o yaml
apiVersion: v1
data:
foo: YmFyCg==
https.cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFekNDQWZ1Z0F3SUJBZ0lKQU96Y00rNzI3RWJHTUEwR0NTcUdTSWIzRFFFQkN3VUFNQ0F4SGpBY0JnTlYKQkF
https.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBeFYvUVJiazJiRm8zRmdZdWpaTWxPVGg3MUxpY3AyUS9pL2pib2E1SExlUlpSTDBi
kind: Secret
. . .
將其與CoinfigMap的Yaml格式定義做對比
apiVersion: v1
data:
bar: baz
foo: bar
one: two
kind: ConfigMap
 
注意到Secret條目的內容會被以Base64個市編譯,而ConfigMap直接以純文本展示。這種區別導致在處理YAML和JSON格式的Secret時會稍許有些麻煩,需要在設置和讀取相關條目時對內容進行編解碼。
這個具體使用中是這樣的,
比如你現在想把一個配置文件加入到Secret中,那么你首先將配置文件中的內容通過BASE64進行編碼后才能作為條目。
當然你會問難道kubernetes不提供base64編碼?提供,只能對字符串,不能接受文件。如下
kund: Secret
apiVersion: v1
stringData:
foo: plain text
data:
https.cert: lksjkaldjldasjladgjsjl...
https.key: lsiodjsdlfjahcdo...
創建后使用kubectl get secret -o yaml會看到stringData字段中的所有條目會被Base64編碼后展示在data字段下。所以stringData字段是只寫不可讀的。
 
如何在pod中使用Secret
apiVersion: v1
kind: Pod
metadata:
name: fortune-https
spec:
containers:
- image: luksa/fortune:env
name: html-generator
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
volumeMounts:
- name: html
mountPaht: /var/htdocs
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
- name: config
mountPath: /etc/nginx/conf.d
readOnly: true
- name: certs
mountPath: /etc/nginx/certs/
readOnly: true
ports:
- containerPort: 80
- containerPort: 443
volumes:
- name: html
emptyDir: {}
- name: config
configmap:
name: fortune-config
items:
- key: my-nginx-config.conf
path: https.conf
- name: certs
secret:
secretname: fortune-https
簡單點的實例:
apiVersion: v1
kind: Pod
metadata:
name: fortune-https
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
- name: certs
mountPath: /etc/nginx/certs/
readOnly: true
ports:
- containerPort: 80
- containerPort: 443
volumes:
- name: certs
secret:
secretName: fortune-https
 
當然也可以將secret條目暴露為環境變量。但不建議這么做,應用通常會在錯誤報告時轉儲環境變量,或者是啟動時打印在應用日志中,無意中就暴露Secret信息。另外,子進程會繼承父進程的所有環境變量,如果是通過第三方二進制程序啟動應用,你並不知道它使用敏感數據做了什么。所以不建議用環境變量,建議使用secret卷的形式掛載到pod.
env:
- name: FOO_SECRET
valueFrom:
secretKeyRef:
name: fortune-https
key: foo
 
學會了secret,接下來就有一個比較常用的secret實際應用,dockerhub
kubectl create secret docker-registry mydockerhubsecret --docker-username=myusername --docker-password=mypassword --docker-email= my.email@provider.com
 
使用:
apiVersion: v1
kind: Pod
metadata:
name: private-pod
spec:
imagePullSecrets:
- name: mydockerhubsecret
containers:
- image: username/private:tag
name: main
假設系統通常運行大量Pod,你可能會好奇是否需要為每個Pod都添加相同的鏡像拉取Secret.並不是,可以通過添加Secret至ServiceAccount 使所有pod都能自動添加上鏡像拉取的Secret.
 
 
Deployment 聲明式地升級應用
現在你已經知道如何將應用程序組件打包進容器,將他們分組到pod中,並為它們提供臨時或者持久存儲,將密鑰或配置文件注入,並可以使pod之間互相通信。這就是微服務化:如何將一個大規模系統拆分成哥哥獨立運行的組件。之后,你將會需要升級你的應用程序。如何升級再kubernetes集群中運行的程序,以及kubernetes如何幫助你實現真正的零停機升級過程。升級操作可以通過使用replicationController 或者 replicaSet實現,但Kubernetes提供了另一種基於ReplicaSet的資源Deployment,並支持聲明式地更新應用程序。
 
看一個簡單的例子:
現有一個應用 ,版本為V1 ,運行在Kubernetes的pod中,現在應用的鏡像有更新,標記為v2,那么如何將新版本運行的pod替換掉V1版本的pod.
有以下兩種方法更新pod:
1. 直接刪除所有現有的pod,然后創建新pod
2. 新創建一組pod,等待運行后刪除舊pod. 可以一次性創建,一次性刪除,也可以一部分一部分操作。
這兩種各有優缺點:第一種方法將會導致應用程序在一定的時間內不可用。第二種方法會導致新舊版本同時在線,如果對統一個數據的處理方式不一樣,將會給系統帶來數據損壞。
暫且不管優缺點,先來看看如何實現。
 
我們用replicationController來托管pod v1,可以直接通過將pod模版修改為v2,然后再刪除舊pod,這時候rc就會按照新模版創建pod.
如果你可以接受短暫的服務不可用,那這是最簡單更新pod的方法。
 
接下來我們用第二種方法。首先要保持兩個版本同時在線,所以要雙倍的硬件資源。
前提我們是用service來暴露pod的。保持rc v1不變,然后創建 rc v2 ,rc v2的pod模版選擇新鏡像標簽V2. 創建rc v2 ,rc v2將運行pod v2 。等待pod v2運行正常后,我們使用 kubectl set selector 命令來修改service的pod選擇器。
 
這種一次性從v1切到v2的方法即為藍綠部署,主要問題就是硬件資源要兩倍。如果我們不是一次切換,而是一點點切,資源將不需要兩倍。比如你創建rc v2,成功后,縮容 rc v1 ,更改service的pod選擇器。然后再擴容rc v2,在縮容 v1 這就是滾動更新。那么這個就要求你的應用允許存在兩個不同版本,具體要根據實際需求來操作了。
 
以上第二種方法的兩種情況,我們大概知道是怎么回事了。但我們在實際操作中,要創建兩個rc,並且要修改service的pod selector. 這要是用滾動更新,pod副本越多,我們手動操作的次數就越多,手動操作越多越容易出現問題。那么問題來了,難道kubernetes沒有提供自動完成這些操作的方法嗎?答案是提供了。k8s中使用kubectl rolling-update kubia-v1 kubia-v2 --image=luksa/kubia:v2
 
我們來通過一個例子了解下:
用rc創建一個應用v1 ,並使用service的 loadbalancer將服務對外暴露。
apiVersion: v1
kind: ReplicationController
metadata:
name: kubia-v1
spec:
replicas: 3
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- image: luksa/kubia:v1
name: nodejs
---
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
type: LoadBalancer
selector:
app: kubia
ports:
- port: 80
targetPort: 8080
 
查看loaderbalancer的IP
#kubectl get svc kubia
訪問測試
# while true; do curl http://IP; done
this is v1 running in pod 主機名
接下來用v2版本升級,this is v2 running in pod 主機名
kubectl rolling-update kubia-v1 kubia-v2 --image=luksa/kubia:v2
使用 kubia v2版本應用替換運行着kubia-v1 的replicationController,將新的復制控制器命名為kubia-v2,並使用luksa/kubia:v2作為容器鏡像。
kubectl 通過復制kubia-v1 的replicationController並在其pod模版中改變鏡像版本。如果仔細觀察控制器的標簽選擇器,會阿賢它也被做了修改。它不僅包含一個簡單的app=kubia標簽,而且還包含一個額外的deployment標簽,為了由這個ReplicationController管理,pod必須具備這個標簽。這也是為了避免使用新的和舊的RC來管理同一組Pod.但是即使新創建的pod添加了deployment標簽,pod中還是有app=kubia標簽,所以不僅新的RC要加deployment標簽,舊的RC同樣也要加上deployment標簽,標簽的值為 一個鏡像hash(先這樣理解)。要保證舊的RC添加deployment標簽后依然可以管理之前創建的pod,因此也要修改舊pod,進行添加標簽,而實際上正是在修改舊RC之前,kubectl修改了舊pod的標簽:
kubectl get po --show-labels 進行查看標簽
設置完成后,就開始執行更新操作了,過程就是我們上面描述的滾動更新過程。
 
為什么 kubectl rolling-update已經過時
我們可以在使用rolling-update命令的時候添加 --v 6 來提供日志級別,使得所有kubectl 發起的到API服務器的請求都會被輸出。
你會看到一個put請求:
/api/v1/namespace/default/replicationcontrollers/kubia-v1
它是表示kubia-v1 ReplicationController資源的RESTful URL.這些請求減少了RC的副本數,這表明伸縮的請求是由kubectl 客戶端執行的,而不是kubernetes master執行的。那么當kubectl執行升級時失去了網絡連接,升級過程就會中斷。對於中斷后的結果處理起來將很麻煩。所以我們想盡量把這個過程讓master負責。這就引出了DeployMent資源。
 
Deployment是一個高階資源,replicationController和replicaSet都被認為是底層的概念。當創建一個Deployment時,ReplicaSet資源就會被創建,實際的pod是由Deployment的Replicaset創建和管理的,而不是由Deployment直接創建和管理的。
 
接下來我們創建一個簡單的deployment,將上面的replicationController稍作修改:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: kubia
spec:
replicas: 3
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- image: luksa/kubia:v1
name: nodejs
因為之前RC只維護和管理了一個特定的版本的pod,並需要命名為kubia-v1,而一個deployment資源高於版本本身。可以同時管理多個版本的pod,所以在命名時不需要指定應用的版本號。
 
kubectl create -f kubia-deployment-v1.yaml --record
使用 --record選項會記錄歷史版本號,在之后操作中非常有用。
kubectl get deployment kubia
kubectl describe deployment kubia
還有一個命令,專門用於查看部署狀態:
kubectl rollout status deployment kubia
 
查看pod,會發現使用deployment資源部署的pod名稱有一定的規律,
deployment的名字 + replicaset pod模版的hash + 隨機數, 如:
kubia-15098375-otnsd
kubia-15098375-djc6s
而rc創建出來的名稱是 : rc名稱 + 隨機數
kubia-v1-kgysg
可以通過查看replicaSet來確認pod模版hash
kubectl get replicasets
kubia-15098375 ...
deployment正是根據pod模版的hash值,對給定版本的pod模版創建相同的(或使用已有的)ReplicaSet.
 
Deployment的高明之處
有不同的更新策略
Recreate 策略在刪除舊的Pod之后才開始創建新的Pod。如果不允許多個版本共存,使用這個,但會有短暫的不可用。
RollingUpdate 策略會漸進地刪除舊的pod,於此同時創建新的pod.這是deployment默認使用的策略。如果支持多個版本共存,推薦使用這個。
 
我們來演示下deployment滾動升級的過程。
在演示之前我們先減慢滾動升級的速度,以方便我們觀察
kubectl path deployment kubia -p '{"spec": {"minReadySeconds": 10} }'
使用path對於修改單個或少量資源屬性非常有用,不需要在通過編輯器編輯,使用minReadySeconds 配置正常檢查后多少時間才屬於正常。
注:使用patch命令更改Deployment的自有屬性,並不會導致pod的任何更新,因為pod模版並沒有被修改。更改其他Deployment的屬性,比如所需要的副本數或部署策略,也不會觸發滾動升級,現有運行中的pod也不會受影響。
 
觸發滾動升級
kubectl set image deployment kubia nodejs=luksa/kubia:v2
執行完成后pod模版的鏡像會被更改為luksa/kubia:v2
 
deployment的優點
使用控制器接管整個升級過程,不用擔心網絡中斷。
僅僅更改pod模版即可。
注:如果Deployment中的pod模版引用了一個ConfigMap(或secret),那么更改ConfigMap字眼本身將不會觸發升級操作。如果真的需要修改應用程序的配置並想觸發更新的話,可以通過創建一個新的ConfigMap並修改pod模版引用新的ConfigMap.
Deployment背后完成的升級過程和kubectl rolling-update命令相似。
一個新的ReplicaSet會被創建然后慢慢擴容,同時之前版本的Replicaset會慢慢縮容至0
 
deployment的另外一個好處是可以回滾
kubectl rollout undo deployment kubia
deployment會被回滾到上一個版本
undo命令也可以在滾動升級過程中運行,並直接停止滾動升級。在升級過程中一創建的pod會被刪除並被老版本的pod替代。
顯示deployment的滾動升級歷史
kubectl rollout history deployment kubia
reversion change-cause
2 kubectl set image deployment kubia nodejs=luksa/kubia:v2
3 kubectl set image deployment kubia nodejs=luksa/kubia:v3
還記得創建Deployment的時候--record 參數嗎?如果不給這個參數,版本歷史中的Change-cause這一欄會空。這也會使用戶很難辨別每次的版本做了哪些修改。
回滾到一個特定的Deployment版本
kubectl rollout undo deployment kubia --to-reversion=1
這些歷史版本的replicaset都用特定的版本號保存Deployment的完整的信息,所以不應該手動刪除ReplicaSet。如果刪除會丟失Deployment的歷史版本記錄而導致無法回滾。
ReplicaSet默認保留最近的兩個版本,可以通過制定Deployment的reversionHistoryLimit屬性來限制歷史版本數量。apps/v1beta2 版本的Deployment默認值為10.
 
控制滾動更新的速率
maxSurge 最多超過期望副本數多少個pod,默認為25%,可以設置為整數
maxUanavailable 最多允許期望副本數內多少個pod為不可用狀態。
看圖比較容易理解
 
暫停滾動更新:
kubectl rollout pause deployment kubia
 
恢復滾動更新:
kubectl rollout resume deployment kubia
 
阻止出錯版本的滾動升級
使用minReadySeconds屬性指定至少要成功運行多久之后,才能將其視為可用。在pod可用之前,滾動升級的過程不會繼續。是由maxSurge屬性和maxUanavailable屬性決定。
例子:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: kubia
spec:
replicas:3
minReadySeconds: 10
strategy:
rollingUpdate:
maxSurge: 1
maxUnavilable: 0
type: RollingUpdate
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- image: luksa/kubia:v3
name: nodejs
readinessProbe:
periodSeconds: 1
httpGet:
path: /
port: 8080
kubectl apply -f kubia-deployment-v3-with-readinesscheck.yaml
kubectl rollout status deployment kubia
就緒探針是如何阻止出錯版本的滾動升級的
首先luksa/kubia:v3鏡像應用前5個應用是正常反饋,后面會500狀態返回。因此pod會從service的endpoint中移除。當執行curl時,pod已經被標記為未就緒。這就解釋了為什么請求不會到新的pod上了。
使用kubectl rollout status deployment kubia查看狀體,發現只顯示一個新副本啟動,之后滾動升級便沒有進行下去,因為新的pod一直處於不可用狀態。即使變為就緒狀態后,也至少需要保持10秒,才是真正可用。在這之前滾動升級過程將不再創建任何新的pod,因為當前maxUnavailable屬性設置為0,所以不會刪除任何原始的pod。如果沒有定義minReadySeconds,一旦有一次就緒探針調用成功,便會認為新的pod已經處於可用狀態。因此最好適當的設置minReadySeconds.另外就緒探針默認間隔為1秒。
deployment資源介紹完結。
 
復制有狀態的Pod
replicaSet通過一個pod模版創建多個pod副本。這些副本除了它們的名字和IP地址不同外,沒有被的差異。如果pod模版里描述了一個關聯到特定持久卷聲明的數據卷,那么ReplicaSet的所有副本都將共享這個持久卷聲明,也就是綁定到同一個持久卷聲明。
因為是在pod模版里關聯持久卷聲明的,又會依據pod模版創建多個副本,則不能對每個副本都指定獨立的持久卷聲明。所以也不能通過一個ReplicaSet來運行一個每個實例都需要獨立存儲的分布式數據存儲服務,至少通過單個ReplicaSet是做不到的。老實說,之前你學習到的所有API對象都不能提供這樣的數據存儲服務,還需要一個其他的對象--StatefulSet
 
我們先看不使用StatefulSet的情況下有沒有方法實現多個副本有自己的持久卷聲明。
三種取巧的方法。
第一種方法,不使用ReplicaSet,使用Pod創建多個pod,每個pod都有獨立的持久卷聲明。需要手動創建它們,當有的pod消失后(節點故障),需要手動創建它們。因此不是一個好方法。
第二種方法,多個replicaSet ,每個rs只有一個pod副本。但這看起來很笨重,而且沒辦法擴縮容。
第三種方法,使用同一個ReplicaSet,大家也都掛載同一個持久卷聲明,應用內部做好互斥,創建多個data數據目錄,每一個pod用一個標記為在用,后面應用不能選被標記為在用的目錄。這樣做很難保證協調的一點沒問題,同時大家用同一個持久卷,讀寫io將成為整個應用的瓶頸。
 
除了上面的存儲需求,集群應用也會要求每一個實例擁有生命周期內唯一標識。pod可以隨時被刪掉,然后被新的pod替代。當一個ReplicaSet中的pod被替換時,盡管新的pod也可能使用被刪除pod數據卷中的數據,但它卻是擁有全新主機名和IP的嶄新pod.在一些應用中,當啟動的實例擁有完全新的網絡標識,但還使用舊實例的數據時,很可能引起問題,比如etcd存儲服務。
當然也可以創建多個service ,每一個replicaset對應一個service,那么一樣很笨重,且顯得很低級。辛運的是,Kubernetes為我們提供了這類需求的完美解決方案--StatefulSet.
 
了解StatefulSet
可以創建一個StatefulSet資源代替ReplicaSet來運行這類pod.它們是專門定制的一類應用,這類應用中每一個實例都是不可替代的。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM