K8S的概念:
- Node(節點),節點機器,可以是物理機或虛擬機,比如ECS,運行了各種進程和docker等。
- Pod(容器組),應用的執行單元,創建和部署的單元;包含一個或多個容器,存儲和網絡資源,以及配置項。
- Controller(應用),管理Pods、復制和自愈,例如Node失敗時自動遷移Pods到其他節點。
- Deployment(無狀態),無狀態Controller(應用)。
- Container(容器),一個Pod可以跑N>=1個Container,這些Container可以共享一些東西。
K8S命令
在macOS下可以直接用brew安裝:
brew install kubectl
常用的K8S命令:
kubectl cluster-info
kubectl get cm --all-namespaces
kubectl get cm -n kube-system
kubectl get cm/srs3-config
kubectl get cm/cluster-info -n kube-public -o yaml
kubectl get cm/cluster-info -n kube-public -o json
kubectl get deploy
kubectl get deploy/nginx-deployment -o yaml
kubectl get deploy/nginx-deployment -o json
kubectl get rs
kubectl get rs -w
kubectl get pods
kubectl get pods --show-labels
kubectl edit deployment.v1.apps/nginx-deployment
kubectl api-resources # 可以查詢到縮寫。
kubectl get services
kubectl get svc/nginx-service -o yaml
kubectl exec srs-deployment-f4cd6b6cc-c74bm -c nginx env
kubectl get pods -o jsonpath="{.items[*].spec.containers[*].name}"
kubectl delete po/srs-origin-deployment-577cf4c7b7-zp7bs --force --grace-period=0
kubectl get po --show-labels
kubectl label deploy/srs-origin-deployment app=srs --overwrite
kubectl label po/srs-origin-deployment-6b4fcf6674-qphdz app=srs --overwrite
kubectl exec srs-edge-deploy-58d9999b7c-f4rtr -- ./objs/srs -v
kubectl set image deploy/srs-edge-deploy srs=ossrs/srs:v4.0.6
kubectl set image deploy/srs-edge-deploy srs=ossrs/srs:v4.0.6 --record
kubectl rollout history deploy/srs-edge-deploy
kubectl get rs
kubectl get rs -w # 可以Watch變化。
kubectl scale --replicas=3 deploy/srs-edge-deploy
kubectl logs srs-edge-deploy-d4cfc9d8b-5wnw7
kubectl describe po/srs-edge-deploy-d4cfc9d8b-85nkf |grep -A 3 Events
kubectl get nodes
kubectl describe nodes/cn-beijing.172.17.232.153
kubectl config get-contexts
切換集群
查看集群:
kubectl config get-contexts
設置默認集群:
kubectl config use-context kubernetes-admin-xxx
ConfigMap保存證書
ConfigMap可以配置多個文件,比如證書的私鑰(*.key
)和公鑰(*.pem
),后臺添加配置,或者用yaml:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: https-config
data:
https.key: |-
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA8mEyACKNJcxAfPWvAYr0uy/wVtd16LPEpy7w1l9WD9+SBsOj
n0QZSc2+y/mEpwXA65irqUply42NabB6rgfsfbcLra8SA5CbyHtrIP4fT6jnBA85
Jzmznp/q/1n4nlfaqawFyOB2IzPjPiwzexN/gv2wWC+5wSQ9yq2k
-----END RSA PRIVATE KEY-----
https.pem: |-
-----BEGIN CERTIFICATE-----
MIIFfjCCBGagAwIBAgIQD8reYBHHOc99faMYlJ0lazANBgkqhkiG9w0BAQsFADBu
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
-----END CERTIFICATE-----
EOF
掛到容器作為配置文件:
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: srs
labels:
app: srs
spec:
selector:
matchLabels:
app: srs
template:
metadata:
labels:
app: srs
spec:
volumes:
- name: ssl-volume
configMap:
name: https-config
containers:
- name: srs
image: registry.cn-hangzhou.aliyuncs.com/ossrs/srs:3
imagePullPolicy: IfNotPresent
volumeMounts:
- name: ssl-volume
mountPath: /usr/local/srs/etc
EOF
可以查看etc目錄,就有了證書文件:
$ kubectl get pods -l app=srs
NAME READY STATUS RESTARTS AGE
srs-77b446fbfc-ftz7c 1/1 Running 0 4m50s
$ kubectl exec srs-77b446fbfc-ftz7c -- ls -lh etc
lrwxrwxrwx 1 root root 16 Feb 1 07:06 https.key -> ..data/https.key
lrwxrwxrwx 1 root root 16 Feb 1 07:06 https.pem -> ..data/https.pem
Volume
Volumes,docker有這個概念但比較簡單且無生命周期管理,k8s提供各種復雜的volume,而且volume是隨pod的生命周期(大于container)。
emptyDir,臨時的空目錄,在pod刪除時會情況,container crash后目錄數據還在,可用于crash后的恢復。
比如SRS寫到nginx目錄后,nginx可以分發切片文件。
- nginx默認目錄是:/usr/share/nginx/html
- SRS的默認目錄是:/usr/local/srs/objs/nginx/html
起一個Pod運行這兩個容器,共享這個目錄,這樣就可以實現SRS寫Nginx讀了。
Container Args
使用command和args,command是命令,args是它的參數,一般需要一起設置??梢詂ommand指定為/bin/sh,用shell執行腳本,參數就是:
command: ["/bin/sh"]
args: ["-c", "cp -R ./objs/nginx/html/* /tmp/html/; sleep infinity"]
也可以在里面寫for循環:
command: ["/bin/sh"]
args: ["-c", "while true; do echo hello; sleep 10;done"]
還可以寫成數組的方式,字符串用長字符串換行:
command: ["/bin/sh"]
args:
- "-c"
- >
while true; do
ffmpeg -re -i ./doc/source.200kbps.768x320.flv \
-c copy -f flv rtmp://srs/live/livestream
done
可以用腳本做初始化的事情。也可以只用args,會從args中解析出command:
args:
- /bin/sh
- -c
- echo hello
Resource
管理容器使用的計算資源,Resource。
可以創建Pod時指定需要的CPU和內存,這樣調度時知道調度到哪個節點;還可以指定資源的限額。資源限額定義,Quota。
資源的要求和限制,指定在:
- spec.containers[].resources.limits.cpu
- spec.containers[].resources.limits.memory
- spec.containers[].resources.requests.cpu
- spec.containers[].resources.requests.memory
CPU,比如
cpu: 0.1
就是100m
,m就是千分之,millicpu,millicores。
Memory,比如memory:100Mi
就是100MiB
,還有Ki,Gi,Ti,Pi。
limits是最多使用的限制;requests是需要使用的,也就是要求的資源。
0<=Request<=Limit<=Infinity (如果Limit為0表示不對資源進行限制,這時可以小于Request)
關于調度:
- 調度時會根據request的資源,保障總的request資源小于Node容量。所以request的資源,是比實際運行的要小的。
- 或者可以認為,低峰期時空閑時CPU是1%,高峰時可能request到30%,limit到50%。調度時就根據各個Pod的30%作為上限。
關于limit:
- K8S啟動容器時,會把request cpu傳遞到容器runtime中,docker run的-c參數:
- 默認是1024,至少是2。如果某個容器是空閑的,其他容器可以使用剩下的全部的CPU。某個容器能使用的最多CPU是變化的。
- 例如如果一個是1024,兩個是512,如果容器內的CPU都跑到100%了,那么實際上第一個是50%,剩下兩個是25%。
- 如果再加第四個容器也是1024,那么第一個是33%,第二和三是16.5%,第四是33%。
- 雖然每個容器的CPU份額是小于100%的,但在多核的機器上可能讓多個CPU跑100%。
- 比如有3個CPU,第一個容器是512,另外是1024,那么可能CPU0是100%跑第一個容器,CPU1和CPU2也是100%跑第二個容器。從系統總體看CPU是300%,100%給了第一個容器,200%給了第二個容器。
- 而limit cpu,則會傳遞到docker的cpu-quota參數。
- 而limit memory,則會傳遞到docker的memory參數:
- 如果容器超過內存限制limits.memory,很可能重啟。內存和磁盤是不可壓縮資源。
- 如果容器超過要求的內存requests.memory,在節點超過能力時會被遷移。
- 容器可能會被允許超過CPU的限制,但不會因為CPU過高而被KILL。CPU是可壓縮資源。
- 如果容器因為資源限制被終止:
- 查看容器的事件:kubectl describe po/srs-edge-deploy-d4cfc9d8b-85nkf |grep -A 3 Events
- 查看節點調度的資源情況:kubectl describe nodes/cn-beijing.172.17.232.153
改進:
關于QoS:
- Guranteed,最高保障,request和limit相等,只指定了limit(就默認request等于limit)。
*Burstable,Strong wish,強烈要求,request和limit有設置,但不相等。 - Best-Effort,最好能支持,完全不設置。
OOM時KILL的順序:最先KILL Best-Effort,然后是Burstable,最后是Guranteed。
Config Reload
ConfigMap修改后,文件會變。
注意:fsnotify signal可能因為符號鏈接收不到。
Linux是使用inotify機制,用inotify_init返回fd,watch后如果有變化可以read出來。
可以偵聽所有事件:IN_ALL_EVENTS,但主要是IN_MODIFY和IN_CREATE。
返回的內容轉換成結構體:inotify_event
。
YAML格式
-表示列表,比如ports可以指定多個,所以就以-開頭。
管道命令行apply:
cat <<EOF | kubectl apply -f -
apiVersion: v1
EOF
可以把多個yaml寫在一起,用三個橫杠分割,比如stateful:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
---
apiVersion: apps/v1
kind: StatefulSet
EOF
配置鏡像拉取,如果存在就不拉取images:
spec.containers[*].imagePullPolicy: IfNotPresent
如果總是拉取,則設置為Always
。
Service
Service就是如何讓Pods提供服務,或微服務,涉及服務發現和負載均衡。
一般服務發現使用的是label selector,有時候也需要無selector的Service(必須后端是另外一個K8S或非Pod)。標簽(label)就是分組的一種方式了,或者一種ID。
定義服務時,需要指定selector比如app=nginx,指定服務端口port,以及后端端口targetPort(默認等于port)。
注意targetPort可以是字符串,會解析成對應的端口,這樣后端服務會比較靈活。
下面是一個服務的描述。
cat <<EOF | kubectl apply -f -
kind: Service
apiVersion: v1
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
EOF
查詢該服務:
$ kubectl get svc/nginx-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP 172.21.7.190 <none> 80/TCP 3m13s
服務是由kube-proxy分配的VIP并進行代理,沒有采用DNS因為有緩存等問題:
kube-proxy有幾種不同的代理方式:
- user space proxy mode,用戶空間代理模式,proxy為每個service偵聽一個端口,通過iptables將服務的請求轉發到這個端口然后轉給pod。
- iptables proxy mode,iptables代理,proxy為每個service新增一條轉發規則,負載高應該是內核netfilter轉發,但出錯后不會重試。
- IPVS proxy mode,1.11后支持了IPVS代理,使用的是內核的netlink,比iptables性能更高吞吐率也更高。
如果殺掉nginx的pod,如果只有一個pod會服務不可用,但過一會兒K8S會重新啟動pod,服務就可以了。
Service Type
ServiceTypes服務類型,一般ClusterIP就是內部服務,還有其他的:
- ClusterIP,默認就是這種,集群內部可以訪問。分配的是Service網段IP(172.21.*),有內部端點無外部端點。
- NodePort,綁定到Node上,也就是Node(ECS)網段(172.17.*),會自動創建ClusterIP服務。外部可以通過Node的IP訪問服務。
- LoadBalancer,通過云服務的負載均衡實現,會自動創建NodePort和ClusterIP。外部訪問的是負載均衡。
- ExternalName,綁定到CNAME,比如foo.example.com,不會創建代理,coredns1.7及以上才支持。
默認是ClusterIP,看到內網的IP(ClusterIP),但無外部IP(ExternalIP),ClusterIP 172.21.89.226 <none> 80/TCP,可訪問性如下:
- nginx的realserver是在172.17.232.153
- nginx的POD的IP,可在跳板機訪問:curl http://172.20.0.24
- nginx-service的IP是172.21.89.226,無法在跳板機訪問,但可以在node上訪問:curl http://172.21.89.226
LoadBalancer負載均衡,一般阿里云標準用法是EIP綁定到SLB上,通過SLB對外提供服務。SLB創建時選內部SLB,交換機選k8s-node使用的,比如IP是172.17.232.159。然后再買EIP綁定到SLB。不用創建SLB偵聽,k8s會自動做這事,創建后是這樣:
$ kubectl get svc/nginx-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service LoadBalancer 172.21.113.176 172.17.232.159 80:30589/TCP 6s
可以發現,類型是LoadBalancer,設置了ExternalIP。
Service Discovery
服務發現可以用ENV環境變量,或者DNS。
當注冊Service后,K8S會在POD啟動時,增加服務對應的環境變量,那么POD就可以用這個環境變量來發現服務了。
CoreDNS
組件開啟后,創建了Service就會創建對應ServiceName的DNS記錄,比如nginx-service。
$ kubectl get service/nginx-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP 172.21.7.190 <none> 80/TCP 16m
$ kubectl exec srs3-demo-deploy-55df684cbb-5dzsh -- ping nginx-service
PING nginx-service.default.svc.cluster.local (172.21.7.190) 56(84) bytes of data.
64 bytes from nginx-service.default.svc.cluster.local (172.21.7.190): icmp_seq=1 ttl=64 time=0.033 ms
StatefulSets
時,需要給每個Pod分配可達的地址,這就是HeadlessServices。通過配置:ClusterIP: None 來指定的。
聲明式更新
Declarative updates,聲明式更新:
However, a Deployment is a higher-level concept that manages
ReplicaSets and provides declarative updates to Pods along with
a lot of other useful features.
這文章說declarative updates:
-
kubectl apply
一般是declarative。參考#1和 #2 -
kubectl run
會轉成declaratively Deployment:kubectl translates your imperative command into a declarative Kubernetes Deployment object. A Deployment is a higher-level API that allows rolling updates (see below). - 意味著保持期望狀態和實際狀態一致:The Kubernetes API is fundamentally declarative, which means that the controllers always work to reconcile the observed state with the desired state. Therefore, if we delete a Pod, the ReplicaSet controller will create a new one to replace it, to maintain the desired replica count.
- 配置你想要的,K8S知道怎么實現:However, the power of Kubernetes is in its declarative API and controllers. You can just tell Kubernetes what you want, and it will know what to do.
- apply是
冪等
操作,可以執行多次:The kubectl apply command is idempotent. We can reuse it after modifying the manifests.
官方也有幾個文章說Imperative命令式和Declarative聲明式:
- 命令式,Managing Kubernetes Objects Using Imperative Commands
- 聲明式,Declarative Management of Kubernetes Objects Using Configuration Files
- Kubernetes Object Management,對比了命令式和聲明式的差異。
Aliyun ACK
ACK托管集群,需要的基本資源:
- 至少
1
個worker,2CPU
,2GB
。包括,Replicas:10個,內存:850m,CPU:1030Mi。 - 雖然coredns是兩個replicas,但是也可以運行在一個worker上面。
- 至少
3
個IP,1個是kubectl訪問集群用,1個是SLB對外提供服務用,1個是綁定到worker訪問外網用(比如下載更新docker鏡像)。