Services in Kubernetes

概述

kubernetes中pods是平凡的,可建立可銷燬並且不可再生。 ReplicationControllers能夠動態的建立&銷燬pods(如擴容 or 縮容 or 更新)。雖然pods有他們單獨的ip,可是他們的ip並不能獲得穩定的保證,這將會致使一個問題,若是在kubernetes集羣中,有一些pods(backends)爲另外一些pods(frontend)提供一些功能,如何能保證frontend可以找到&連接到backends。node

引入Services。git

kubernetes services是一個抽象的概念,定義瞭如何和一組pods相關聯—— 有時候叫作「micro-service」。一個service經過Label Selector來篩選出一組pods(下文會說明什麼狀況下不須要selector)。github

舉個栗子,設想一個擁有三個節點的圖片處理backend,這三個節點均可以隨時替代——frontend並不關係連接的是哪個。即便組成backend的pods發生了變更,frontend也沒必要關心鏈接到哪一個backend。services將frontend和backend的連接關係解耦。redis

對於kubernetes自己的應用來講,kubernetes提供了一個簡單的endpoint 的api,對於非kubernetes自己的應用,kubernetes爲servicet提供了一個解決方案,經過一個設定vip的bridge來連接pods。docker

定義一個service

在kubernetes中,services和pods同樣都是一個REST對象。同其餘的REST對象同樣,經過POST來建立一個service。好比,有一組pods,每一個pod對外暴露9376端口 他們的label爲「app=MyApp」:json

{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "my-service"
    },
    "spec": {
        "selector": {
            "app": "MyApp"
        },
        "ports": [
            {
                "protocol": "TCP",
                "port": 80,
                "targetPort": 9376
            }
        ]
    }
}

上述的json將會作如下事情:建立一個叫「my-service」的service,它映射了label爲「app=MyApp」的pods端口9376,這個service將會被分配一個ip(cluster ip),service用這個ip做爲代理,service的selector將會一直對pods進行篩選,並將起pods結果放入一個也焦做「my-service」的Endpoints中。後端

注意,一個service可能將流量引入到任何一個targetPost,默認targetPort字段和port字段是相同的。有趣的是targetPort 也能夠是一個string,能夠設定爲是一組pods所映射port的name。在每一個pod中,這個name所對應的真實port均可以不一樣。這爲部署& 升級service帶來了很大的靈活性,好比 能夠在api

kubernetes services支持TCP & UDP協議,默認爲tcp。緩存

Services without selectors

kubernetes service一般是連接pods的一個抽象層,可是service也能夠做用在其餘類型的backend。好比:session

  • 在生產環境中你想使用一個外部的database集羣,在測試環境中使用本身的database;
  • 但願將一個service指向另外一個namespace中的service 或者 指向另一個集羣;
  • 但願將非kubernetes的工做代碼環境遷移到kubernetes中;

在以上任意一個情景中,均可以使用到不指定selector的service:

{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "my-service"
    },
    "spec": {
        "ports": [
            {
                "protocol": "TCP",
                "port": 80,
                "targetPort": 9376
            }
        ]
    }
}

在這個例子中,由於沒有使用到selector,所以沒有一個明確的Endpoint對象被建立。 所以須要手動的將service映射到對應的endpoint:

{
    "kind": "Endpoints",
    "apiVersion": "v1",
    "metadata": {
        "name": "my-service"
    },
    "subsets": [
        {
            "addresses": [
                { "IP": "1.2.3.4" }
            ],
            "ports": [
                { "port": 80 }
            ]
        }
    ]
}

不管有沒有selector都不會影響這個service,其router指向了這個endpoint(在本例中爲1.2.3.4:80)。

虛IP & service代理(Virtual IPs and service proxies)

kubernetes中的每一個node都會運行一個kube-proxy。他爲每一個service都映射一個本地port,任何鏈接這個本地port的請求都會轉到backend後的隨機一個pod,service中的字段SessionAffinity決定了使用backend的哪一個pod,最後在本地創建一些iptables規則,這樣訪問service的cluster ip以及對應的port時,就能將請求映射到後端的pod中。

最終的結果就是,任何對service的請求都能被映射到正確的pod中,而client不須要關心kubernetes、service或pod的其餘信息。
圖片描述
默認狀況下,請求會隨機選擇一個backend。能夠將service.spec.sessionAffinity 設置爲 "ClientIP" (the default is "None"),這樣能夠根據client-ip來維持一個session關係來選擇pod。

在kubernetes中,service是基於三層(TCP/UDP over IP)的架構,目前尚未提供專門做用於七層(http)的services。

Multi-Port Services

在不少狀況下,一個service須要對多個port作映射。下面舉個這樣的例子,注意,使用multi-port時,必須爲每一個port設定name,如:

{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "my-service"
    },
    "spec": {
        "selector": {
            "app": "MyApp"
        },
        "ports": [
            {
                "name": "http",
                "protocol": "TCP",
                "port": 80,
                "targetPort": 9376
            },
            {
                "name": "https",
                "protocol": "TCP",
                "port": 443,
                "targetPort": 9377
            }
        ]
    }
}

Choosing your own IP address

用戶能夠爲service指定本身的cluster ip,經過字段spec.clusterIP來實現。用戶設定的ip必須是一個有效的ip,必須符合service_cluster_ip_range 範圍,若是ip不合符上述規定,apiserver將會返回422。

Why not use round-robin DNS?

有一個問題會時不時的出現,爲何不用一個DNS輪詢來替換vip?有以下幾個理由:

  • 已經擁有很長曆史的DNS庫不會太注意DNS TTL 而且會緩存name lookup的結果;
  • 許多應用只作一次name lookup而且將結果緩存;
  • 即便app和dns庫作了很好的解決,client對dns作一遍又一遍的輪詢將會增長管理的複雜度;

咱們作這些避免用戶作哪些做死的行爲,可是,若是真有那麼多用戶要求,咱們會提供這樣的選擇。

Discovering services

對於每一個運行的pod,kubelet將爲其添加現有service的全局變量,支持Docker links compatible變量 以及 簡單的{SVCNAME}_SERVICE_HOST and {SVCNAME}_SERVICE_PORT變量。

好比,叫作」redis-master「的service,對外映射6379端口,已經被分配一個ip,10.0.0.11,那麼將會產生以下的全局變量:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

這意味着一個順序依賴——service要想被pod使用,必須比pod先創建,不然這些service環境變量不會構建在pod中。DNS沒有這些限制。

DNS

一個可選的擴展(強烈建議)是DNS server。DNS server經過kubernetes api server來觀測是否有新service創建,併爲其創建對應的dns記錄。若是集羣已經enable DNS,那麼pod能夠自動對service作name解析。

舉個栗子,有個叫作」my-service「的service,他對應的kubernetes namespace爲」my-ns「,那麼會有他對應的dns記錄,叫作」my-service.my-ns「。那麼在my-ns的namespace中的pod均可以對my-service作name解析來輕鬆找到這個service。在其餘namespace中的pod解析」my-service.my-ns「來找到他。解析出來的結果是這個service對應的cluster ip。

Headless services

有時候你不想作負載均衡 或者 在乎只有一個cluster ip。這時,你能夠建立一個」headless「類型的service,將spec.clusterIP字段設置爲」None「。對於這樣的service,不會爲他們分配一個ip,也不會在pod中建立其對應的全局變量。DNS則會爲service 的name添加一系列的A記錄,直接指向後端映射的pod。此外,kube proxy也不會處理這類service
,沒有負載均衡也沒有請求映射。endpoint controller則會依然建立對應的endpoint。

這個操做目的是爲了用戶想減小對kubernetes系統的依賴,好比想本身實現自動發現機制等等。Application能夠經過api輕鬆的結合其餘自動發現系統。

External services

對於你應用的某些部分(好比frontend),你可能但願將service開放到公網ip,kubernetes提供兩種方式來實現,NodePort and LoadBalancer。

每一個service都有個type字段,值能夠有如下幾種:

  • ClusterIP: 使用集羣內的私有ip —— 這是默認值。
  • NodePort: 除了使用cluster ip外,也將service的port映射到每一個node的一個指定內部port上,映射的每一個node的內部port都同樣。
  • LoadBalancer: 使用一個ClusterIP & NodePort,可是會向cloud provider申請映射到service自己的負載均衡。

注意:NodePort支持TCP/UDP,LoadBalancer只支持TCP。

Type = NodePort

若是將type字段設置爲NodePort,kubernetes master將會爲service的每一個對外映射的port分配一個」本地port「,這個本地port做用在每一個node上,且必須符合定義在配置文件中的port範圍(爲--service-node-port-range)。這個被分配的」本地port「定義在service配置中的spec.ports[*].nodePort字段,若是爲這個字段設定了一個值,系統將會使用這個值做爲分配的本地port 或者 提示你port不符合規範。

這樣就方便了開發者使用本身的負載均衡方案。

Type = LoadBalancer

若是在一個cloud provider中部署使用service,將type地段設置爲LoadBalancer將會使service使用人家提供的負載均衡。這樣會異步的來建立service的負載均衡,在service配置的status.loadBalancer字段中,描述了所使用被提供負載均衡的詳細信息,如:

{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "my-service"
    },
    "spec": {
        "selector": {
            "app": "MyApp"
        },
        "ports": [
            {
                "protocol": "TCP",
                "port": 80,
                "targetPort": 9376,
                "nodePort": 30061
            }
        ],
        "clusterIP": "10.0.171.239",
        "type": "LoadBalancer"
    },
    "status": {
        "loadBalancer": {
            "ingress": [
                {
                    "ip": "146.148.47.155"
                }
            ]
        }
    }
}

這樣外部的負載均衡方案將會直接做用在後端的pod上。

Shortcomings

經過iptables和用戶控件映射能夠很好的爲中小型規模服務,可是並不適用於擁有數千個service的集羣。詳情請看」 the original design proposal for portals「。

使用kube-proxy不太可能看到訪問的源ip,這樣使得某些類型防火牆實效。

LoadBalancers 只支持TCP.

type字段被設計成嵌套的結構,每一層都被增長到了前一層。不少雲方案提供商支持的並非很好(如,gce沒有必要分配一個NodePort來使LoadBalancer正常工做,可是AWS須要),可是當前的API須要。

Future work

The gory details of virtual IPs

以上的信息應該足夠用戶來使用service。可是仍是有許多東西值得你們來深刻理解。
(懶得翻了,你們本身看吧,最後貼上最後一個圖)

Avoiding collisions

IPs and VIPs

圖片描述

相關文章
相關標籤/搜索