Services 和 Pods
KubernetesPods是有生命周期的。他們可以被創建,而且銷毀不會再啟動。 如果您使用Deployment來運行您的應用程序,則它可以動態創建和銷毀 Pod。
一個Kubernetes的Service
是一種抽象,它定義了一組Pods
的邏輯集合和一個用於訪問它們的策略 - 有的時候被稱之為微服務。一個Service
的目標Pod
集合通常是由Label Selector
來決定的(下面有講一個沒有選擇器的Service
有什么用處)。
舉個例子,想象一個處理圖片的后端運行了三個副本。這些副本都是可以替代的 - 前端不關心它們使用的是哪一個后端。盡管實際組成后端集合的Pod
可能會變化,前端的客戶端卻不需要知道這個變化,也不需要自己有一個列表來記錄這些后端服務。Service
抽象能讓你達到這種解耦。
不像 Pod
的 IP 地址,它實際路由到一個固定的目的地,Service
的 IP 實際上不能通過單個主機來進行應答。 相反,我們使用 iptables
(Linux 中的數據包處理邏輯)來定義一個虛擬IP地址(VIP),它可以根據需要透明地進行重定向。 當客戶端連接到 VIP 時,它們的流量會自動地傳輸到一個合適的 Endpoint。 環境變量和 DNS,實際上會根據 Service
的 VIP 和端口來進行填充。
kube-proxy支持三種代理模式: 用戶空間,iptables和IPVS;它們各自的操作略有不同。
Userspace
作為一個例子,考慮前面提到的圖片處理應用程序。 當創建 backend Service
時,Kubernetes master 會給它指派一個虛擬 IP 地址,比如 10.0.0.1。 假設 Service
的端口是 1234,該 Service
會被集群中所有的 kube-proxy
實例觀察到。 當代理看到一個新的 Service
, 它會打開一個新的端口,建立一個從該 VIP 重定向到新端口的 iptables,並開始接收請求連接。
當一個客戶端連接到一個 VIP,iptables 規則開始起作用,它會重定向該數據包到 Service代理
的端口。 Service代理
選擇一個 backend,並將客戶端的流量代理到 backend 上。
這意味着 Service
的所有者能夠選擇任何他們想使用的端口,而不存在沖突的風險。 客戶端可以簡單地連接到一個 IP 和端口,而不需要知道實際訪問了哪些 Pod
。
iptables
再次考慮前面提到的圖片處理應用程序。 當創建 backend Service
時,Kubernetes 控制面板會給它指派一個虛擬 IP 地址,比如 10.0.0.1。 假設 Service
的端口是 1234,該 Service
會被集群中所有的 kube-proxy
實例觀察到。 當代理看到一個新的 Service
, 它會配置一系列的 iptables 規則,從 VIP 重定向到 per-Service
規則。 該 per-Service
規則連接到 per-Endpoint
規則,該 per-Endpoint
規則會重定向(目標 NAT)到 backend。
當一個客戶端連接到一個 VIP,iptables 規則開始起作用。一個 backend 會被選擇(或者根據會話親和性,或者隨機),數據包被重定向到這個 backend。 不像 userspace 代理,數據包從來不拷貝到用戶空間,kube-proxy 不是必須為該 VIP 工作而運行,並且客戶端 IP 是不可更改的。 當流量打到 Node 的端口上,或通過負載均衡器,會執行相同的基本流程,但是在那些案例中客戶端 IP 是可以更改的。
IPVS
在大規模集群(例如10,000個服務)中,iptables 操作會顯着降低速度。 IPVS 專為負載平衡而設計,並基於內核內哈希表。 因此,您可以通過基於 IPVS 的 kube-proxy 在大量服務中實現性能一致性。 同時,基於 IPVS 的 kube-proxy 具有更復雜的負載平衡算法(最小連接,局部性,加權,持久性)。
下面我們詳細說下k8s支持的4種類型的Service。
ClusterIP
創建ClusterIP的Service yaml如下:
apiVersion: v1
kind: Service
metadata:
name: service-python
spec:
ports:
- port: 3000
protocol: TCP
targetPort: 443
selector:
run: pod-python
type: ClusterIP
使用 kuebctl get svc
:
類型為ClusterIP的service,這個service有一個Cluster-IP,其實就一個VIP。具體實現原理依靠kubeproxy組件,通過iptables或是ipvs實現。
這種類型的service 只能在集群內訪問。
NodePort
我們的場景不全是集群內訪問,也需要集群外業務訪問。那么ClusterIP就滿足不了了。NodePort當然是其中的一種實現方案。
創建NodePort 類型service 如下:
apiVersion: v1
kind: Service
metadata:
name: service-python
spec:
ports:
- port: 3000
protocol: TCP
targetPort: 443
nodePort: 30080
selector:
run: pod-python
type: NodePort
使用 kuebctl get svc
:
此時我們可以通過http://4.4.4.1:30080或http://4.4.4.2:30080
對pod-python訪問。該端口有一定的范圍,比如默認Kubernetes 控制平面將在--service-node-port-range
標志指定的范圍內分配端口(默認值:30000-32767)。
LoadBalancer
LoadBalancer類型的service 是可以實現集群外部訪問服務的另外一種解決方案。不過並不是所有的k8s集群都會支持,大多是在公有雲托管集群中會支持該類型。負載均衡器是異步創建的,關於被提供的負載均衡器的信息將會通過Service
的status.loadBalancer
字段被發布出去。
創建 LoadBalancer service 的yaml 如下:
apiVersion: v1
kind: Service
metadata:
name: service-python
spec:
ports:
- port: 3000
protocol: TCP
targetPort: 443
nodePort: 30080
selector:
run: pod-python
type: LoadBalancer
使用 kuebctl get svc
:
可以看到external-ip。我們就可以通過該ip來訪問了。
當然各家公有雲支持諸多的其他設置。大多是公有雲負載均衡器的設置參數,都可以通過svc的注解來設置,例如下面的aws:
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-access-log-enabled: "true"
# Specifies whether access logs are enabled for the load balancer
service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval: "60"
# The interval for publishing the access logs. You can specify an interval of either 5 or 60 (minutes).
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name: "my-bucket"
# The name of the Amazon S3 bucket where the access logs are stored
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix: "my-bucket-prefix/prod"
# The logical hierarchy you created for your Amazon S3 bucket, for example `my-bucket-prefix/prod`
ExternalName
類型為 ExternalName 的service將服務映射到 DNS 名稱,而不是典型的選擇器,例如my-service
或者cassandra
。 您可以使用spec.externalName
參數指定這些服務。
創建 ExternalName 類型的服務的 yaml 如下:
kind: Service
apiVersion: v1
metadata:
name: service-python
spec:
ports:
- port: 3000
protocol: TCP
targetPort: 443
type: ExternalName
externalName: remote.server.url.com
說明:您需要 CoreDNS 1.7 或更高版本才能使用
ExternalName
類型。
當查找主機 service-python.default.svc.cluster.local
時,集群DNS服務返回CNAME記錄,其值為my.database.example.com
。 訪問service-python
的方式與其他服務的方式相同,但主要區別在於重定向發生在 DNS 級別,而不是通過代理或轉發。
將生產工作負載遷移到Kubernetes集群並不容易。大多數我們不可以停止所有服務並在Kubernetes集群上啟動它們。有時,嘗試遷移輕量且不會破壞你服務的服務是很好的。在此過程中,一個可能不錯的解決方案是使用現有的有狀態服務(例如DB),並首先從無狀態容器開始。
從Pod中訪問外部服務的最簡單正確的方法是創建ExternalName
service。例如,如果您決定保留AWS RDS,但您還希望能夠將MySQL容器用於測試環境。讓我們看一下這個例子:
kind: Service
apiVersion: v1
metadata:
name: test-service
namespace: default
spec:
type: ExternalName
externalName: test.database.example.com
你已將Web應用程序配置為使用URL測試服務訪問數據庫,但是在生產集群上,數據庫位於AWS RDS上,並且具有以下URL test.database.example.com
。創建ExternalName service 並且你的Web Pod嘗試訪問test-service上的數據庫之后,Kubernetes DNS服務器將返回值為test.database.example.com
的CNAME記錄。問題解決了。
ExternalName service 也可以用於從其他名稱空間訪問服務。例如:
kind: Service
apiVersion: v1
metadata:
name: test-service-1
namespace: namespace-a
spec:
type: ExternalName
externalName: test-service-2.namespace-b.svc.cluster.local
ports:
- port: 80
在這里,我可以使用名稱空間a中定義的test-service-1
訪問命名空間b中的服務test-service-2
。
這個意義在哪里?
ExternalName service 也是一種service,那么ingress controller 會支持,那么就可以實現跨namespace的ingress。
1、客戶端訪問節點時通過 iptables實現的
2、iptables規則是通過 kube-proxy寫入的
3、apiserver通過監控 kube-proxy去進行對服務和端點的監控
4、kube-proxy通過 pod的標簽( lables)去判斷這個斷點信息是否寫入到 Endpoints里