我們知道Kubernetes是強大的聲明式容器編排工具,而計算、存儲、網絡等功能均是通過以接口的形式暴露、以插件的形式實現,這種靈活開放的設計理念使Kubernetes非常容易集成外部工具擴展和強化功能。而IaaS雲平臺提供的核心功能就是計算、存儲和網絡,這意味著Kubernetes與IaaS雲平臺並不是獨立割裂的,而是天然非常適合結合在一起,功能和職責互補,二者聯合起來既能充分利用底層伺服器、網絡、存儲等基礎設施能力又能提供彈性、敏捷的基礎架構平臺。
目前Kubernetes已經能和OpenStack、AWS、Google雲等IaaS雲平臺很好的集成,比如Volume能和OpenStack的Cinder以及AWS的EBS集成,Pod網絡則能和雲平臺的VPC網絡集成,而Kubernetes Service和Ingress則分別適合與IaaS雲平臺的四層防火牆、七層防火牆集成。
本文接下來主要以Kubernetes融合AWS資源為例,主要通過實驗的方式介紹Kubernetes與IaaS資源的結合方式。
2 基礎環境配置2.1 節點配置首先需要在AWS上創建至少3臺EC2實例,需要注意的是由於後面涉及的AWS NLB以及ALB均需要至少跨2個AZ,因此EC2實例需要至少跨2個AZ的子網。
另外Kubernetes自動創建AWS資源時需要調用AWS API,因此需要向AWS IAM進行身份和權限認證,考慮安全的問題,不建議使用AKSK的方式進行配置,而是採用Assume Role的方式使用STS進行登錄認證,需要的權限如EBS卷的創建、掛載、安全組配置、標籤設置和讀取等,實驗時為了簡化Policy策略配置,直接使用了內置的 AmazonEC2FullAccess Policy策略,創建了EC2 Role後關聯到所有的EC2實例,如圖:
實際生產時需要遵循最小權限授權原則,必須通過自定義Policy的形式僅配置Kubernetes需要的權限,Kubernetes Master節點和Node節點需要的權限不同,需要分別授權,github上有現成的Policy可以用kubernetes/cloud-provider-aws。
另外需要注意的是,Kubernetes的所有Node節點hostname必須使用EC2實例的private DNS名,原因可以參考文章:https://blog.juanwolf.fr/post/programming/why-k8s-nodes-changes-name-when-using-aws/。因此需要在所有的Node節點手動配置:
sudo hostnamectl set-hostname \
$(curl -sSL http://169.254.169.254/latest/meta-data/local-hostname)
2.2 標籤很重要Kubernetes需要知道哪些AWS資源是屬於這個集群的,哪些是可以使用的,比如Kubernetes創建Service時為了暴露網際網路訪問需要修改EC2實例的安全組,但通常一個EC2實例可能會掛多個安全組,有些安全組是基線安全組,供一些公共服務如堡壘機使用,用戶肯定不期望Kubernetes把這些安全組規則也改了。因此Kubernetes要求用戶必須給資源打上標籤,通過標籤的方式告訴Kubernetes哪些資源可以用,其中有兩個標籤是必須的:
KubernetesCluster: 配置Kubernetes cluster名稱,對應部署Kubernetes時kubeadm的 clusterName參數,因為一個Account下可能有多個Kubernetes集群,通過這個參數用於區分是哪個集群。
kubernetes.io/cluster/int32bit-kubernetes: 填寫 shared或者 owned,即是否允許多個集群共享這些資源。
這些標籤需要打到所有要使用的資源上,包括EC2實例、安全組、子網,當然不使用的則不打,比如不需要Kubernetes託管的安全組就不要打上如上標籤。
另外Kubernetes創建負載均衡時需要知道哪些子網是私有子網(掛的是NAT Gateway路由表),哪些是公有子網(掛的是Internet Gateway路由表),因此需要給所有使用的子網打上如下標籤:
參考EKS集群VPC注意事項。
2.3 安裝與配置Kubernetes目前安裝與配置Kubernetes主要使用kubeadm工具,不過現版本kubeadm還不支持直接傳遞 cloud provider參數,因此需要自己寫配置文件,建議先使用kubeadm生成默認的配置文件:
kubeadm config print init-defaults >kubeadm.yaml
修改 kubeadm.yaml配置文件,控制平面需要修改 controllerManager,Node平面需要修改 nodeRegistration,添加 kubeletExtraArgs的 cloud-provider參數:
nodeRegistration:
# ...
kubeletExtraArgs:
cloud-provider: aws
# ...
controllerManager:
extraArgs:
cloud-provider: aws
configure-cloud-routes: "false"
當然為了更適合國內網絡環境,建議使用阿里雲的Kubernetes容器鏡像,修改 imageRepository參數為 registry.aliyuncs.com/google_containers。
另外需要記住配置的 clusterName,默認為 kubernetes,後面打標籤和配置需要使用。
首個節點初始化:
kubeadmin init --config kubeadm.yaml
當首個節點完成init後,其他Node節點需要join,同樣需要修改 nodeRegistration參數指定cloud provider,生成默認配置文件方法如下:
kubeadm config print join-defaults >kubeadm.yaml
# 修改kubeadm.yaml的nodeRegistration以及apiServerEndpoint
kubeadm join --config kubeadm.yaml
3 通過塊存儲實現動態PVC卷Kubernetes通過StorageClass實現動態PVC,目前支持AWS EBS、OpenStack Cinder、Ceph等驅動,利用這些雲平臺提供的底層存儲,Kubernetes可實現按需創建持久化存儲的Volume,以創建AWS EBS StorageClass為例,聲明文件如下:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
reclaimPolicy: Retain
allowVolumeExpansion: true
volumeBindingMode: Immediate
type參數可配置選擇不同的EBS卷類型。
聲明StorageClass後就可以使用PVC了,如下申請30GB的volume。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ebs-standard-30g
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard
resources:
requests:
storage: 30Gi
查看kuberctl資源:
kubectl get storageclasses -o wide
NAME PROVISIONER AGE
standard kubernetes.io/aws-ebs 17h
root@ip-192-168-193-172:~# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
ebs-standard-30gBound pvc-5eb81e14-1328-459d-8751-b7881c554c0a 30Gi RWO standard 17h
在AWS上可以發現對應的volume已經創建出來:
volume會自動打上Kubernetes集群以及PVC的元數據標籤。
創建一個ngix Pod使用這個PVC:
# kubectl apply -f -
kind: Pod
apiVersion: v1
metadata:
name: nginx
spec:
volumes:
- name: test-ebs-standard-30g
persistentVolumeClaim:
claimName: ebs-standard-30g
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: test-ebs-standard-30g
在AWS上查看volume狀態:
我們發現EBS volume自動掛載到了Pod所在Node的EC2實例上,在Node節點上可以確認:
root@ip-192-168-193-226:~# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0 7:0088.4M1 loop /snap/core/7169
loop1 7:1018M1 loop /snap/amazon-ssm-agent/1335
loop2 7:2089.1M1 loop /snap/core/8039
loop3 7:3018M1 loop /snap/amazon-ssm-agent/1480
nvme0n1 259:00100G0 disk /var/lib/docker
nvme1n1 259:108G0 disk
└─nvme1n1p1 259:208G0 part /
nvme2n1 259:3030G0 disk /var/lib/kubelet/pods/422..be/v
\ ` c 55
ol
\umes/kubernetes.io~aws-ebs/pvc-5e..
需要注意的是,由於EBS不支持跨AZ掛載,因此當Pod漂移到其他Node時可能導致Volume無法掛載的情況,不過Scheduler會自動根據Node的AZ元數據規避這種情況。
由上面的實驗可知,Kubernetes通過聲明一個AWS StorageClass指定EBS的卷類型,然後當聲明一個PVC實例時,Kubernetes會自動根據StorageClass配置的元數據創建指定大小的Volume,當Pod聲明使用Volume時,Kubernetes會把Volume掛載到Pod所在的Node EC2實例上,如果沒有文件系統,則會自動創建文件系統,默認的文件系統類型為ext4.
4 Pod網絡與VPC集成在之前的文章聊聊幾種主流Docker網絡的實現原理中介紹了使用Flannel的aws-vpc後端實現容器的跨主機通信,其原理是把路由配置到AWS VPC路由表中。容器使用的網絡IP位址是Flannel分配和維護的,和VPC的網絡地址完全獨立。這就會有一個問題,AWS VPC Peer為了防止IP/APR欺騙會強制檢查目標IP位址,如果不是對端VPC的IP,則會直接丟棄,因此Flannel aws-vpc雖然能實現跨子網通信,但無法實現跨VPC的通信。
amazon-vpc-cni-k8s是AWS自己維護的開源項目,實現了CNI接口與AWS VPC集成,Pod能直接分配到與Node節點相同子網的IP位址,和EC2虛擬機共享VPC資源,使得Pod和EC2實例實現網絡功能上平行,能夠充分復用VPC原有的功能,如安全組、vpc flow logs、ACL等。並且由於Pod IP就是VPC的IP,因此能夠通過VPC Peer隧道實現跨VPC通信。
4.1 amazon-vpc-cni-k8s配置配置使用amazon-vpc-cni-k8s很簡單,需要注意的是由於使用這種方案,Node節點可能會自動添加多張網卡,因此在kubeadm配置中最好選擇主網卡的IP作為Node IP,另外podSubnet需要配置為VPC的CIDR,如下:
# cat kubeadm.yaml
nodeRegistration:
# ...
kubeletExtraArgs:
cloud-provider: aws
node-ip: 192.168.193.172# 填寫主網卡的IP位址
# ...
networking:
dnsDomain: cluster.local
podSubnet: 192.168.192.0/22# 填寫VPC的CIDR
serviceSubnet: 10.96.0.0/12
kubeadm init之後需要部署aws-node daemonset,下載yaml聲明文件如下:
curl -sSL -O https://raw.githubusercontent.com/aws/amazon-vpc-cni-k8s/master/config/v1.5/aws-k8s-cni.yaml
國內網絡環境需要把image從 602401143452.dkr.ecr.us-west-2.amazonaws.com/amazon-k8s-cni:v1.5.3改為 lmh5257/amazon-k8s-cni:v1.5.3。
使用如下命令部署:
kubectl apply -f aws-k8s-cni.yaml
部署完後由於版本的問題,所有的Node節點可能需要修改 /etc/cni/net.d/10-aws.conflist文件,添加 cniVersion版本信息並重啟kubelet服務:
# cat /etc/cni/net.d/10-aws.conflist
{
"name": "aws-cni",
"cniVersion": "0.3.1", # 需要手動添加
"plugins": [
{
"name": "aws-cni",
"type": "aws-cni",
"vethPrefix": "eni"
},
{
"type": "portmap",
"capabilities": {"portMappings": true},
"snat": true
}
]
}
使用Ubuntu AMI的EC2實例建議關閉ufw功能,新版本的Docker會把iptables的FORWARD Policy設置為DROP,參考https://github.com/moby/moby/issues/14041,這將導致Node節點無法轉發包,需要手動調整所有Node節點FORWARD policy為ACCEPT:
iptables -P FORWARD ACCEPT
AWS EKS AMI則是通過iptables restore持久化如上配置:
[Unit]
Description=Restore iptables
# iptables-restore must start after docker because docker will
# reconfigure iptables to drop forwarded packets.
After=docker.service
[Service]
Type=oneshot
ExecStart=/bin/bash -c "/sbin/iptables-restore < /etc/sysconfig/iptables"
[Install]
WantedBy=multi-user.target
4.2 amazon-vpc-cni IP位址分配原理前面提到Pod使用的是AWS VPC的子網IP位址,而Pod是運行在EC2實例上,因此首先需要把IP分配給Pod所運行的EC2實例。如何實現?答案是給EC2實例的eni配置多IP,eni(Elastic Network Interfaces )即虛擬網絡,類似OpenStack Neutron的port,而AWS所謂的給eni分配多個IP位址,其實就是類型於OpenStack Neutron port的allowed address pairs功能。
但是AWS的eni能夠支持分配的IP個數是有限的,如何解決這個問題呢?答案是給EC2實例再綁定一個eni網卡。這樣假設一個EC2實例最多能綁定N個eni網卡,每個eni能給分配M個IP,則一個EC2實例可以最多擁有 N*M個IP,除去EC2實例host的每個eni需要佔用一個IP,則一個EC2實例可以分配的POD IP位址數量為 N*M-N,換句話說,一個Node節點至多同時運行 N*M-N個Pod。不同的EC2實例類型N和M的值都不一樣,參考AvailableIpPerENI。以 c5.large為例,能夠支持綁定的eni數量為3,每個eni最多關聯10個IP位址,則該節點最多能夠運行27個Pod。而 c5.xlarge能夠支持最大的eni數量為4,每個eni最多可關聯15個IP,則該節點最多可以運行56個Pod。生產部署時應充分考慮這種限制,選擇適合的EC2實例類型。
aws-node agent會維護每個節點的IP位址池,當Node的可用Pod IP小於某個閾值時會自動調用AWS API創建一個新的eni分配IP位址並關聯到該EC2實例,而當Node的可用Pod IP大於某個閾值時則會自動釋放eni以及IP位址。
我們可以查看Node節點的EC2實例的信息如下:
可見aws-node已經預先添加了一個eni接口並分配了20個IP。
為了研究網絡原理,我們exec進入容器查看網絡信息:
# kubectl exec -t -i kubernetes-bootcamp-v1-c5ccf9784-6sgkm -- bash
# ip a
3: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default
link/ether 7a:45:bf:f9:f5:44 brd ff:ff:ff:ff:ff:ff
inet 192.168.193.214/32 brd 192.168.193.214 scope global eth0
valid_lft forever preferred_lft forever
# ip r
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
# ip neigh
192.168.193.194 dev eth0 lladdr fe:2c:94:74:24:25 STALE
169.254.1.1 dev eth0 lladdr fe:2c:94:74:24:25 PERMANENT
看過我之前的文章聊聊幾種主流Docker網絡的實現原理可以發現,和Calico非常類似,容器的IP是32位的,網關為一個假IP 169.254.1.1(IP其實是什麼無所謂),而這個假IP的MAC地址為veth對端eni eni7efd263cb92的MAC地址。換句話說,從容器出去的包會把MAC地址修改為veth pair對端eni eni7efd263cb92的MAC地址。
我們查看Pod所在的Node節點主機網絡信息如下:
# ip a | grep -A 10 -B 1 fe:2c:94:74:24:25
10: eni7efd263cb92@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP groupdefault
link/ether fe:2c:94:74:24:25 brd ff:ff:ff:ff:ff:ff link-netnsid 2
inet6 fe80::fc2c:94ff:fe74:2425/64 scope link
valid_lft forever preferred_lft forever
# ip r
default via 192.168.193.129 dev ens5 proto dhcp src 192.168.193.194 metric 100
192.168.193.128/25 dev ens5 proto kernel scope link src 192.168.193.194
192.168.193.129 dev ens5 proto dhcp scope link src 192.168.193.194 metric 100
192.168.193.214 dev eni7efd263cb92 scope link
目標為192.168.193.214,轉發給 eni7efd263cb92,即容器veth,從而實現了容器入訪的流量分發,入訪沒有問題。
問題是每個Node都有可能有多個相同子網的eni,那容器訪問其他Pod容器的出訪該如何走呢?我們可能會想到的辦法是開啟Linux內核rp_filter功能,但更優雅的做法是配置路由策略,根據分配的IP屬於哪個eni決定從哪個eni轉發出去:
# ip rule | grep 192.168.193.214
512: from all to 192.168.193.214 lookup main
1536: from192.168.193.214 to 192.168.192.0/22 lookup 2
# ip route list table 2
default via 192.168.193.129 dev ens6
192.168.193.129 dev ens6 scope link
可見凡是Pod 192.168.193.214訪問其他容器的出訪都使用table 2,而table 2的默認出口為ens6,這個正是aws-node分配的eni。
南北流量我們查看iptables規則如下:
-A POSTROUTING -m comment --comment "AWS SNAT CHAN"-j AWS-SNAT-CHAIN-0
-A POSTROUTING -m comment --comment "AWS SNAT CHAN"-j AWS-SNAT-CHAIN-0
-A AWS-SNAT-CHAIN-0! -d 192.168.192.0/22-m comment --comment "AWS SNAT CHAN"-j AWS-SNAT-CHAIN-1
-A AWS-SNAT-CHAIN-1-m comment --comment "AWS, SNAT"-m addrtype ! --dst-type LOCAL -j SNAT --to-source 192.168.193.194--random
可見POSTROUTING會跳到AWS-SNAT-CHAIN-0子鏈,這個子鏈會判斷如果不是VPC網段的(即不是Pod網段),則跳到AWS-SNAT-CHAIN-1子鏈處理,而這個AWS-SNAT-CHAIN-1子鏈的任務其實就是把源IP改為主網卡的IP。
更多關於aws-vpc-cni的設計文檔可參考Proposal: CNI plugin for Kubernetes networking over AWS VPC。
5 Service與四層負載均衡集成Kubernetes通過Service暴露服務,這個和傳統數據中心暴露服務的方式其實是一樣的,這個Service就是類似四層負載均衡的功能,因此可以認為Service就是一個虛擬四層防火牆,理解這個很重要。因為我們經常誤以為Service是和Pod綁定的,實際上Service與Pod是完全鬆耦合的,Service後端其實綁定的是邏輯的Endpoint,這個Endpoint就是類似於負載均衡的Member。
Endpoint除了可以關聯Pod,甚至支持關聯集群外部已有的服務,Kubernetes做的只是根據Selector自動把Pod IP添加到Endpoint中。prometheus的外部exporter,Kubernetes就顯然無法自動發現,必須通過手動修改Endpoint列表實現。
目前Kubernetes Service除了主要支持的類型為ClusterIP、NodePort以及LoadBalancer,但其實後兩者都是在ClusterIP實現之上的功能疊加。
ClusterIP默認會分配一個虛擬IP,通過IPtables轉發到Pod IP中。NodePort則相當於在ClusterIP的基礎之上做了個Node節點的IP NAT映射,使得外部能夠通過Node的IP與Service通信,與Docker的 -p參數的實現原理類似。
為了研究LoadBalancer實現原理,首先以AWS為例創建一個LoadBalancer Service:
# kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
labels:
app: kubernetes-bootcamp-v1
name: kubernetes-bootcamp-v1
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 8080
selector:
app: kubernetes-bootcamp-v1
type: LoadBalancer
以上需要注意的是要添加 service.beta.kubernetes.io/aws-load-balancer-type註解,否則創建的是AWS Classic Load Balancer,這種負載均衡AWS已經廢棄不推薦使用了,因此需要指定創建的Load Balancer類型為 nlb(Network Load Balancer)。
查看創建的Service:
# kubectl get service kubernetes-bootcamp-v1
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes-bootcamp-v1 LoadBalancer10.105.6.241 a...3.elb.cn-northwest-1.amazonaws.com.cn 8080:31905/TCP 13h
可見Service的 External-IP為 nlb的域名,在 Port(s)中我們發現該Service分配了一個NodePort。
我們在AWS上查看該負載均衡如下:
Listener為Service指定的Port值。我們查看其target group如下:
可見Targets為Node節點的IP以及Node Port埠。
由此可見LoadBalancer是在NodePort的基礎之上,再通過IaaS雲平臺創建一個四層負載均衡,並把Node以及Node埠添加到後端Member列表中。
6 Ingress與七層負載均衡集成Service是基於TCP協議的4層埠分發服務,而Ingress則使用了七層的應用層協議進行服務分發,這裡的應用指基於HTTP或者HTTPS協議的Web服務。Ingress可以認為是對Service的再次包裝轉發,支持基於主機名和URL路徑匹配的規則轉發。
目前支持基於Haproxy、Ngnix、Kong等實現Ingress,但使用比較多的Ingress是基於Nginx的反向代理實現的NginxController。而本文將使用AWS ALB Ingress Controller,從名字上可看出是基於AWS的ALB(Application Load Balancer)實現的。
Kubernetes的所有Ingress都需要手動配置安裝,AWS ALB Ingress Controller也不例外。
首先配置RBAC角色:
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.3/docs/examples/rbac-role.yaml
下載ALB ingress controller聲明文件:
wget https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.3/docs/examples/alb-ingress-controller.yaml
之所以不是直接 kubectl apply而是先要下載下來,是因為我們需要稍微修改下配置。
首先需要去掉 --cluster-name=注釋,並填寫cluster name。其次如果Node節點EC2實例沒有配置IAM Role,則需要在聲明文件中配置AKSK,我們已經關聯了IAM Role,因此會自動從metadata中獲取STS,不需要配置AKSK。
完成配置之後apply創建controller:
kubectl apply -f alb-ingress-controller.yaml
此時會在kube-system namespace下創建一個 alb-ingress-controller Deployment,Deployment會通過ReplicaSet基於 amazon/aws-alb-ingress-controller鏡像啟動Pod。
接下來我們創建一個Ingress實例如下:
# kubectl apply -f -
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: "int32bit-aws-alb-ingress"
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 18080}]'
labels:
app: int32bit-aws-alb-ingress
spec:
rules:
- host: test.int32bit.me
http:
paths:
- path: /v1
backend:
serviceName: "kubernetes-bootcamp-v1"
servicePort: 8080
- path: /v2
backend:
serviceName: "kubernetes-bootcamp-v2"
servicePort: 8080
如上定義的訪問規則為:
if host == "test.int32bit.me":
if path == "/v1":
redirect to service kubernetes-bootcamp-v1
elif path == "/v2":
redirect to service kubernetes-bootcamp-v2
else:
404
else:
404
另外需要注意的是Kubernetes默認創建的是 internalalb,只能VPC內部訪問,如果需要暴露給網際網路,需要通過 alb.ingress.kubernetes.io/scheme註解配置alb類型為 internet-facing。ALB默認監聽的HTTP埠為80,HTTPS埠為443,由於這些埠均需要ICP備案,因此這次測試通過 alb.ingress.kubernetes.io/listen-ports配置監聽器的HTTP埠為 18080。
我們查看創建的Ingress實例:
# kubectl describe ingresses.
Name: int32bit-aws-alb-ingress
Namespace: default
Address: 82e05f75-default-int32bita-5196-1991518503.cn-northwest-1.elb.amazonaws.com.cn
Default backend: default-http-backend:80(<none>)
Rules:
HostPathBackends
---- ---- ---
test.int32bit.me
/v1 kubernetes-bootcamp-v1:8080(10.244.3.241:8080,10.244.4.6:8080,10.244.5.2:8080)
/v2 kubernetes-bootcamp-v2:8080(10.244.3.242:8080,10.244.4.7:8080,10.244.5.3:8080)
Annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/listen-ports: [{"HTTP": 18080}]
其中Address為ALB的DNS域名。為了訪問我們的Ingress服務,通常還需要到域名伺服器上添加一個CNAME記錄test.int32bit.me到這個域名,這裡為了測試直接修改了本地/etc/hosts文件:
# dig +short 82e05f75-default-int32bita-5196-1991518503.cn-northwest-1.elb.amazonaws.com.cn
52.83.70.253
161.189.46.130
# echo "161.189.46.130 test.int32bit.me" >>/etc/hosts
此時我們訪問Ingress服務如下:
# curl test.int32bit.me:18080/v1
HelloKubernetes bootcamp! | Running on: kubernetes-bootcamp-v1-c5ccf9784-cmw2t | v=1
# curl test.int32bit.me:18080/v2
HelloKubernetes bootcamp! | Running on: kubernetes-bootcamp-v2-569df8ddd5-hthwp | v=2
# curl -I test.int32bit.me:18080/v3
HTTP/1.1404NotFound
Server: awselb/2.0
Date: Sat, 23Nov201910:13:54 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 0
Connection: keep-alive
可見Ingress針對我們的訪問路徑進行了Service正確轉發。
我們查看AWS ALB:
其中Listener監聽的埠為我們Ingress配置的HTTP埠。
我們查看規則如下:
和我們Ingress配置的規則一一對應,根據不同的虛擬主機名和路徑轉發到不同的target group。
我們發現target group和前面的LoadBalance配置完全一樣,都是把Node的IP以及NodePort加到targets中。這也意味著添加到Ingress的Service必須是NodePort類型,當然LoadBalancer也是通過NodePort實現,因此也是沒有問題的。
Kubernetes的Cluster Autoscaler功能能夠實現Node節點的彈性伸縮,當Node節點資源不足時能夠自動創建新的Node節點來運行Pod,並且自動遷移Pod到新的Node上。
這個功能依賴於雲平臺的AUTO SCALING功能,目前很多雲平臺都支持這個功能,比如OpenStack就支持通過Heat或者Senlin實現自動伸縮,社區已經實現運行在OpenStack Magnum之上的Kubernetes平臺彈性伸縮,參考Cluster Autoscaler for OpenStack Magnum。
AWS的AutoScaling與CloudWatch、LoadBalancer組合可以實現無狀態服務的自動伸縮,目前也已經實現利用AWS的AutoScaling實現Kubernetes的Node節點彈性伸縮,參考Cluster Autoscaler on AWS。
7.1 AWS AutoScaling配置首先在AWS上創建一個Launch Configuration以及Auto Scaling Group(ASG)
Launch Configuration選擇已經安裝好Docker和kubelet、kubeadm、kubectl的AMI快照,並傳遞如下userdata數據:
#!/bin/bash
MY_HOSTNAME=$(curl -sSL http://169.254.169.254/latest/meta-data/local-hostname)
MY_IP=$(curl -sSL http://169.254.169.254/latest/meta-data/local-ipv4)
hostnamectl set-hostname "${MY_HOSTNAME}"
cat >~/kubeadm.yaml <<EOF
apiVersion: kubeadm.k8s.io/v1beta2
caCertPath: /etc/kubernetes/pki/ca.crt
discovery:
bootstrapToken:
apiServerEndpoint: 192.168.193.172:6443
token: abcdef.0123456789abcdef
unsafeSkipCAVerification: true
timeout: 5m0s
tlsBootstrapToken: abcdef.0123456789abcdef
kind: JoinConfiguration
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: ${MY_HOSTNAME}
taints: null
kubeletExtraArgs:
cloud-provider: aws
node-ip: ${MY_IP}
EOF
kubeadm join --config ~/kubeadm.yaml
ASG打上如下兩個標籤:
7.2 安裝Cluster Autoscaler下載Cluster Autoscaler,修改 --node-group-auto-discovery參數使結果與ASG打的標籤保持一致。
Ubuntu需要修改volume的ssl-certs host-path為/etc/ssl/certs/ca-certificates.crt,另外image建議修改阿里雲源: registry.cn-hangzhou.aliyuncs.com/google_containers/cluster-autoscaler:v1.12.3。
修改完畢後apply。
7.3 驗證AutoScaling功能我們創建如下nginx Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 10
revisionHistoryLimit: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:latest
name: nginx
ports:
- containerPort: 80
resources:
requests:
memory: 2G
此時由於資源不足,Pod狀態為pending:
# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-7cfc94d94-4k4750/1Pending010m
nginx-7cfc94d94-5pgbw1/1Running011m
nginx-7cfc94d94-5rtvj0/1Pending010m
nginx-7cfc94d94-66c450/1Pending010m
nginx-7cfc94d94-fxdh9 0/1Pending010m
nginx-7cfc94d94-g2hsj 0/1Pending010m
nginx-7cfc94d94-hkqrn 0/1Pending010m
nginx-7cfc94d94-j9zz2 1/1Running010m
nginx-7cfc94d94-mzjx4 1/1Running011m
nginx-7cfc94d94-xsp4s 0/1Pending010m
查看cluster-autoscaler日誌如下:
可見日誌報node資源不足,增加節點個數為4。
查看AWS ASG發現新啟動了4個EC2實例:
等了大概5分鐘後發現新的node添加進來了:
# kubectl get node
NAME STATUS ROLES AGE VERSION
ip-192-168-193-172.cn-northwest-1.compute.internalReady master 20h v1.16.3
ip-192-168-193-194.cn-northwest-1.compute.internalReady<none>2m54s v1.16.3
ip-192-168-193-226.cn-northwest-1.compute.internalReady<none>109s v1.16.3
ip-192-168-193-77.cn-northwest-1.compute.internalReady<none>76s v1.16.3
並且nginx Pod也正在創建:
8 總結本文以AWS為例,介紹了Kubernetes的Pod網絡、PVC、Service、Ingress、Cluster Autoscaler與IaaS資源的融合,我們發現Kubernetes和底層IaaS資源不是完全割裂的,而是可以相互配合。