埃拉託斯特尼篩法
一、Service簡介
1.1 service作用
作用:
-
使叢集內部能訪問pod,或者叢集外訪問pod
-
用於pod的服務發現與負載均衡(TCP/UDP 4層)
-
通過selector指定某一類pod的標籤相關聯pod
-
底層原理是通過iptables和IPVS二種網路模式來實現的服務發現跟負載均衡
為什麼要用到service,因為Pod是不穩定的,隨時可能停止在被控制器拉起,這樣ip就會發生變化,所以需要service提供一個固定的ip來訪問這些pod
pod的服務發現:
通過deployment或者replicas控制器建立的pod,指定了pod的副本個數,當pod的副本個數擴容或裁剪的時候,service是自動發現新增的pod並給他流量,自動發現裁剪的pod,不再給這類下線的pod流量,也就訪問不到下線的pod了。
負載均衡:
使用service關聯多個pod,流量是負載均衡到每個pod上的。
底層原理:
Service工作模式有三種:userspace(k8s 1.1版本之前使用),iptables(k8s 1.10版本之前使用),ipvs(k8s 1.11版本之後一直預設使用)
目前k8s預設使用ipvs模式來轉發流量的,Service也是通過這種模式來找pod的。
參考資料:Service三種工作模式
Iptables實現流量轉發
可使用命令檢視規則
iptables -L -n #檢視
ipvs實現流量轉發
可使用命令檢視規則
ipvsadm -ln
參考資料:iptables跟ipvs區別
1.2 service型別
service型別分為:
- ClusterIP
預設,分配一個只有叢集內部節點可以訪問的虛擬IP,叢集內部節點指的是master跟node節點
- NodePort
在每個叢集節點上分配一個埠作為外部訪問入口,叢集外部節點也能訪問
- LoadBalancer
負載均衡器,工作在特定的Cloud Provider(雲平臺)上,例如Google Cloud,AWS,OpenStack
- ExternalName
表示把叢集外部的服務引入到叢集內部中來,即實現了叢集內部pod和叢集外部的服務進行通訊;也可用於不同名稱空間之間的通訊。
參考資料:
補充:
ClusterIP根據是否生成ClusterIP又可分為普通Service和Headless Service(無頭服務)
-
普通Service:
為Kubernetes的Service分配一個叢集內部可訪問的固定的虛擬IP(Cluster IP), 實現叢集內的訪問。
-
Headless Service: (無頭服務)
該服務不會分配Cluster IP, 也不通過kube-proxy做反向代理和負載均衡。而是通過DNS為每個pod提供一個域名,這樣我們能訪問到每個單獨的pod。
參考資料:Headless Service詳解
二、使用Service
2.1 ClusterIP型別
叢集IP模式,只能在叢集內部進行訪問,如master跟node節點上,將某一類相同標籤的pod映射出來,分配一個service的ip來進行訪問。
如下圖所示,使用Deployment資源建立了3個pod,將這三個pod的80埠對映為8000埠,並提供一個Service的ip來訪問這些pod,這時我們在叢集中的任意節點(即Master或者Node節點上)通過Service的IP加埠就能負載均衡訪問到這3個pod。
下面我們來建立一個該型別的Service服務體驗一下。
1.使用命令建立方式
定義一個名為my-dep的Deployment資源,裡面包含有3個pod副本,每個pod中含有一個nginx容器
kubectl create deployment my-dep --image=nginx --replicas=3
kubectl get deploy
建立Service,預設就是--type=ClusterIP
模式,這裡也可以不寫--type=ClusterIP
kubectl expose deploy my-dep --port=8000 --target-port=80 --type=ClusterIP --protocol=TCP
檢視當前的service
kubectl get services
kubectl get svc
可以看到該Service的叢集IP為10.96.100.203,暴露的埠為8000,我們在任意叢集節點上都可訪問
curl http://10.96.100.203:8000
負載均衡訪問到3個pod中。
什麼,你還不信是負載均衡訪問到後面的pod的?那麼我們分別修改這3個pod中的index.html檔案
#檢視建立的pod名
kubectl get pods
#依次修改每個pod的html頁面,注意進入容器後使用ctrl+d退出容器
kubectl exec -it my-dep-5b7868d854-bntks -- /bin/bash
echo 'this is 1 page' > /usr/share/nginx/html/index.html
#修改第二個pod
...
echo 'this is 2 page' > /usr/share/nginx/html/index.html
#修改第三個pod
...
echo 'this is 3 page' > /usr/share/nginx/html/index.html
#訪問Service服務
curl http://10.96.100.203:8000
2.使用yaml檔案建立方式
1.建立deployment的yaml檔案
[root@k8s-master01 ~]# vim nginx-dep.yml
apiVersion: apps/v1 #版本資訊使用kubectl explain deployment檢視
kind: Deployment
metadata:
name: nginx-dep
spec:
replicas: 2 #定義2個pod副本
selector:
matchLabels:
app: nginx-dep #匹配的pod標籤名
template:
metadata:
name: nginx
labels:
app: nginx-dep # pod的標籤名,要跟matchLabels裡定義的一致
spec:
containers:
- name: nginx
image: nginx:1.15-alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
#應用yaml清單
kubectl apply -f nginx-dep.yml
2.建立Service的yaml檔案
這裡關聯的是標籤名為nginx-dep的pod
[root@k8s-master01 ~]# vim nginx-svc.yml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: default
labels:
app: nginx-svc
spec:
clusterIP: 10.96.139.8 #指定ClusterIP,不寫此行會自動分配一個Service的ip
type: ClusterIP #可以不寫此行,預設為ClusterIP模式
selector:
app: nginx-dep #關聯的pod的標籤名
ports:
- port: 8000
protocol: TCP
targetPort: 80
#應用yaml清單
kubectl apply -f nginx-svc.yml
檢視Pod的標籤名
kubectl get pods --show-labels
kubectl get pod -l app=nginx-dep
3.檢視service,跟deployment,endpoints節點資訊
kubectl get svc,deploy,ep
這裡的endpoints指的是pod,可以檢視pod的IP地址
匯出yaml檔案
kubectl get svc nginx-svc -o yaml> nginx-svc.yaml
編輯修改資源
kubectl edit svc nginx-svc
Service服務發現
關於Service的服務發現,我們來做個實驗。
這裡就著前面搞的那個deloyment資源,原來是3個pod副本,我們改成2個副本
kubectl get deploy
#編輯my-dep資源
kubectl edit deploy my-dep
...
replicas: 2 #修改副本數為2
...
#訪問Service服務
curl http://10.96.100.203:8000
這時發現還是能正常訪問Service服務,這就是Service的服務發現功能,自動感知pod擴容和裁剪,無需我們人工操作
使用Service名訪問
前面我們是通過訪問service的IP發現是可以負載均衡到後面的pod上的,我們也可以在容器內部訪問service的名字達到一樣的效果。
kubectl get svc #得到Service名字my-dep
kubectl get pods #檢視pod
kubectl exec -it my-dep-5b7868d854-crh94 -- /bin/bash #進入容器
curl my-dep.default.svc:8000
curl my-dep:8000
此時的my-dep.default.svc:8000中的my-dep為服務名,default為名稱空間,svc表示service,可簡寫為my-dep:8000,需要進入容器中訪問才能用這種方式,因為有coredns解析,但在叢集節點上無法使用,叢集節點只能用Service_ip:8000的方式訪問,因為沒有dns解析
2.2 NodePort型別
在每臺叢集節點上都對映埠,這樣訪問叢集任意節點的埠就能訪問到pod提供的服務,這就不限於叢集中了,叢集外也能訪問。
下面我們來建立一個該型別的Service服務體驗一下。
1.使用命令建立方式
定義一個名為my-dep的Deployment資源,裡面包含有3個pod副本,每個pod中含有一個nginx容器。
kubectl create deployment my-dep --image=nginx --replicas=3
kubectl get deploy
建立Service,指定型別為NodePort
kubectl expose deploy my-dep --port=8000 --target-port=80 --type=NodePort --protocol=TCP
檢視當前的service
kubectl get services
kubectl get svc
這裡可以看到名為my-dep的Service使用了10.96.27.163的叢集IP,暴露了一個8000埠,用於叢集內部訪問;還有一個38780埠,可用於叢集外部訪問,這個38780埠是在每個叢集節點上都開了
訪問
#在叢集內部訪問
curl 10.96.27.163:8000
#10.154.0.111是我的master的IP地址,其他兩臺是我的node節點
#在叢集外部使用瀏覽器訪問http://10.154.0.111:38780
#在叢集外部使用瀏覽器訪問http://10.154.0.112:38780
#在叢集外部使用瀏覽器訪問http://10.154.0.113:38780
注意,這個38780埠在任何一臺叢集節點上都開放了,該埠是隨機暴露的,範圍在30000-32767之間
如果覺得訪問叢集節點加埠的方式不很方便,可以使用Nginx做負載均衡,或者Ingress控制器做負載均衡。
2.使用yaml檔案建立方式
這裡使用我們之前建立的Deployment關聯的那2個pod,對了,每個pod可以關聯多個Service,名為nginx-dep的pod之前關聯了ClusterIP型別的Service,這裡我們繼續用它關聯NodePort型別的Service。
1.建立NodePort的Service
[root@k8s-master01 ~]# vim nginx-svc2.yml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc2
namespace: default
labels:
app: nginx-svc2
spec:
type: NodePort #這裡改為NodePort模式
selector:
app: nginx-dep #關聯的pod的標籤名
ports:
- port: 8000
protocol: TCP
targetPort: 80
#應用yaml清單
kubectl apply -f nginx-svc2.yml
2.檢視service,跟deployment,endpoints節點資訊
kubectl get svc,deploy,ep
如果不想使用叢集IP加埠的方式訪問,可以部署一臺Nginx作為負載均衡器,這樣能更加規範化。
補充:
我們也可以編輯之前ClusterIP型別建立的名為my-dep的Service,將其改為ClusterIP模式
#編輯名為nginx-svc的Service
kubectl edit service nginx-svc
type: NodePort #修改type為NodePort
#或者
kubectl patch service nginx-svc -p '{"spec": {"type":"NodePort"}}'
2.3 loadbalancer型別
使用雲廠商的提供的負載均衡器,關聯到 loadbalancer型別的Service,這樣我們就能在叢集外訪問雲服務廠商的LB,訪問到我們的pod服務了。
訪問流程:使用者-->域名-->雲服務提供端提供LB-->NodeIP:Port(serviceIP)-->Pod IP:埠
具體方式就是修改Service的type: LoadBalancer
,然後在雲平臺上建立一個LB,關聯到該Service即可,我沒有使用過廠商的雲平臺,這裡可以自行檢視文件,應該很簡單的。
參考資料:騰訊雲LoadBalancer,華為雲LoadBalancer
2.4 ExternalName型別
ExternalName型別的Service,就是將該Service名跟叢集外部服務地址做一個對映,使之訪問Service名稱就是訪問外部服務。
這裡使用兩個案例來介紹,一個是繫結外部域名,也可以繫結其他名稱空間的域名。
1.繫結叢集外部域名
把外部的服務繫結到叢集內部的ExternalName型別的Service上,這樣訪問該Service名就能訪問到外部服務,先來看一個小例子
1.先建立一個帶有curl命令的busybox的pod
[root@k8s-master01 ~]# vim busybox.yml
apiVersion: v1
kind: Pod
metadata:
name: busybox
spec:
containers:
- name: liveness
image: yauritux/busybox-curl
imagePullPolicy: IfNotPresent
args:
- /bin/sh
- -c
- sleep 30000000
2.再建立一個ExternalName型別的Service,繫結的是外部域名www.k8sec.com
[root@k8s-master01 ~]# vim externelname.yml
apiVersion: v1
kind: Service
metadata:
name: k8sec
namespace: default
spec:
type: ExternalName
externalName: www.k8sec.com # 對應的外部域名為www.k8sec.com
#應用YAML檔案
kubectl apply -f externelname.yml
3.檢視Service
kubectl get svc
4.訪問
這時我們就能通過訪問名為k8sec.default.svc
的Service訪問到www.k8sec.com
#進入第一步建立的busybox
kubectl exec -it busybox -- /bin/sh
curl -I k8sec
curl -I k8sec.default.svc
5, 檢視名為k8sec的Service的dns解析
yum install -y bind-utils
dig -t A k8sec.default.svc.cluster.local. @10.96.0.10 #這裡10.96.0.10是叢集dns地址
可以看出其實是做了一個CNAME,CNAME就是域名對映,將名為k8sec.default.svc的Service對映到www.k8sec.com,我們訪問這個Service名,就相當於訪問到了www.k8sec.com
這麼做的好處就是,一旦外部服務發生了地址變更,如www.k8sec.com掛了,只需要修改Service中的externalName欄位即可,對於叢集內部是無感知的。
同樣我們也可以換成別的服務,如mysql服務,externalName欄位改為mysql服務的VIP,這樣叢集內部就能通過Service名訪問到外部的mysql服務了。
2.繫結其他名稱空間的域名
這裡要注意一點的是,不同名稱空間的pod是可以直接通訊的,我們使用ExternalName型別的Service本質上就是建立一個用於訪問的別名。
1.建立test名稱空間
kubectl create ns test
2.在test名稱空間中建立nginx的Service
kubectl create deployment nginx --image=nginx -n test
kubectl expose deploy nginx --port=80 --target-port=80 -n test
3.在預設名稱空間中建立一個busybox
[root@k8s-master01 ~]# vim busybox.yml
apiVersion: v1
kind: Pod
metadata:
name: busybox
spec:
containers:
- name: liveness
image: yauritux/busybox-curl
imagePullPolicy: IfNotPresent
args:
- /bin/sh
- -c
- sleep 30000000
4.建立ExternalName型別的Service繫結test名稱空間的Service
apiVersion: v1
kind: Service
metadata:
name: nginx-external
spec:
type: ExternalName
externalName: nginx.test.svc.cluster.local
ports:
- name: http
port: 80
targetPort: 80
5.驗證
#進入處於預設名稱空間的busybox中,訪問nginx-external
kubectl exec -it busybox -- /bin/sh
curl -I nginx-external
curl -I nginx-external.default.svc
6.檢視dns解析
dig -t A nginx-external.default.svc.cluster.local. @10.96.0.10
還有一點要注意: 叢集內的Pod會繼承Node上的DNS解析規則。所以只要Node可以訪問的服務,Pod中也可以訪問到, 這就實現了叢集內服務訪問叢集外服務
參考資料:ExternalNames說明,ExternalNames案例
三、補充
3.1 sessionAffinity
可以將來自同一個客戶端的請求始終轉發至同一個後端的Pod物件,用於pod的會話保持,就是第一次訪問誰,後面都訪問這個節點。
舉個例子,如果你使用賬號密碼登入,不使用會話保持,那麼每次重新整理頁面就會重新登入一次,因為http是無狀態的,每次請求都沒有聯絡。
使用了會話保持就能基於客戶端IP地址識別客戶端身份,不再每次訪問就登入一次。
跟nginx的ip_hash演算法類似。
1.設定sessionAffinity為Clientip,啟用sessionAffinity
#直接修改service裡的引數
kubectl patch svc my_service -p '{"spec": {"sessionAffinity":"ClientIP"}}'
#或者
kubectl edit svc my-service
...
sessionAffinity: ClientIP
#取消sessionAffinity
kubectl patch svc my_service -p '{"spec": {"sessionAffinity":"None"}}'
3.2 headless service
普通的ClusterIP service是service name解析為cluster ip,然後cluster ip對應到後面的pod ip
而無頭service是指service name 直接解析為後面的pod ip,也就是說沒有ClusterIP了
1, 編寫YAML檔案
[root@k8s-master01 ~]# vim headless-service.yml
apiVersion: v1
kind: Service
metadata:
name: headless-service
namespace: default
spec:
clusterIP: None # None就代表是無頭service
type: ClusterIP # ClusterIP型別,也是預設型別
ports: # 指定service 埠及容器埠
- port: 80 # service ip中的埠
protocol: TCP
targetPort: 80 # pod中的埠
selector: # 指定後端pod標籤
app: nginx # 可通過kubectl get pod -l app=nginx檢視哪些pod在使用此標籤
注意這裡的 clusterIP: None 是沒有IP的
2, 應用YAML檔案建立無頭服務
kubectl apply -f headless-service.yml
3, 驗證
kubectl get svc
可以看到headless-service沒有CLUSTER-IP,用None表示
我們檢視headless-service的DNS解析發現解析到了如下兩個IP地址
dig -t A headless-service.default.svc.cluster.local. @10.96.0.10
接著檢視pod的ip會發現,這兩個IP是一致的,也就是說headless service做dns解析是直接解析到pod的
kubectl get pods -o wide
需要注意的是對這類 Service
並不會分配 Cluster IP,kube-proxy 不會處理它們,而且叢集也不會為它們進行負載均衡和路由。
這種Service主要用於statefulset控制器,用於部署有狀態的pod應用,以後學到了再說吧。
參考資料:Service詳解
3.3 kube-DNS
DNS服務監視Kubernetes API,為每一個Service建立DNS記錄用於域名解析
headless service需要DNS來解決訪問問題
1, 檢視kube-dns服務的IP
kubectl get svc -n kube-system
#檢視到coreDNS的服務地址是10.96.0.10
四、參考資料
黑馬Linux-k8s第三天視訊
今天的學習是為了以後的工作更加的輕鬆!