--- title: K8S lab3(Service) tags: k8s --- K8S lab3(Service) === [TOC] ## What is Service Service的概念是將一組 Pods 上的應用程式組合成一個群組並公開其網路的抽象方法。 可以配置各種訪問群組的方式。 默認情況下, Kubernetes 會為 Pos群組 提供一組固定 IP 位址,並為每一個 Pod 提供相同的DNS名稱。客戶端可以使用該IP 位址聯繫 Service 中的 Pod。 Client 向 固定IP地址發送請求,請求load balance到 Service 中的其中一個 Pod。Pod彼此間也可以互相溝通 ![](https://hackmd.io/_uploads/BkCuTCrW9.png) ## Why Service 在 lab2 的時候,我們連線到 Pod 是用 `port-forward` 的指令,當有多個 Pod 想要同時進行連線的時候,就可以透過 Service 元件。 Service 就是 K8s 中用來定義一群 Pod 要如何被連線及存取的元件。 - 將 pod 的端點暴露出來 - 兩個 pod 將使用 service 進行溝通 - 因為 pod 與 pod 之間的網路是隔離的,所以會需要使用 service 將服務暴露出來給 namespace中的其他 pod 使用 ## How Service 要建立一個 Service,如同建立Pos,也可以使用yaml的方式建立 先建立範例`demo-service.yaml` ```yaml= apiVersion: v1 kind: Service metadata: name: demo-service spec: selector: app: demoApp type: NodePort ports: - protocol: TCP port: 3001 targetPort: 3000 nodePort: 30390 ``` * kind: 元件屬性,常見有 `Pod`、`Node`、`Service`、`Namespace`... * name: kind 屬性的名稱,範例為Service就是Service的名稱 * spec.`selector`: 該 Service 的連線規則適用在哪一群 Pods,建立 Pod 的時候,需加上 `label`才能透過 spec.selector.app: demoApp,去找到那群 label 的 app 屬性是 demoApp 的 Pods * type: Service的總類共有5種(下面會詳細說明) * ports: 指定port mapping的方式會根據server type不同 ### 建立Service 有兩種方式: 透過 `kubectl expose` 及 透過 yaml 檔 **1.透過 `kubectl expose`** ```shell= $ kubectl expose pod deposit --port=80 --name=deposit-service ``` :::warning :bulb: **備註**: 待研究....... 請先使用yaml建立service~ ::: **2.使用 yaml** ```shell= kubectl apply -f demo-service.yaml ``` #### 查看Service ```shell= kubectl get services ``` ``` ~/Desktop/Kubernetes $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE demo-service NodePort 10.8.14.9 <none> 3001:30390/TCP 18s kubernetes ClusterIP 10.8.0.1 <none> 443/TCP 3d1h ``` #### 刪除services ``` ~/Desktop/Kubernetes $ kubectl delete services demo-service service "demo-service" deleted ~/Desktop/Kubernetes $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.8.0.1 <none> 443/TCP 3d1h ``` ## Types of Kubernetes Services Service 總共有:four: 種 Type: * **ClusterIP** (Default): Exposes the Service on a cluster-internal IP. Choosing this value makes the Service only reachable from within the cluster. This is the default ServiceType. * **NodePort**: Exposes the Service on each Node's IP at a static port (the NodePort). A ClusterIP Service, to which the NodePort Service routes, is automatically created. You'll be able to contact the NodePort Service, from outside the cluster. * **LoadBalancer**: Exposes the Service externally using a cloud provider's load balancer. NodePort and ClusterIP Services, to which the external load balancer routes, are automatically created. * **ExternalName**(:new: ): Internal clients use the DNS name of a Service as an alias for an external DNS name. ### ClusterIP K8s 預設的 Service,只提供在相同的 Cluster 內的 Pod 可以互相連結虛擬IP,無法讓外部使用。 ClusterIP的應用場景通常是保護某些端點不被外部存取。 ![](https://hackmd.io/_uploads/HkkYu9dWq.png) **範例 yaml:** ```yaml= apiVersion: v1 kind: Service metadata: name: clusterip-service spec: selector: app: MyApp type: ClusterIP ports: - targetPort: 80 port: 80 protocol: TCP ``` - yaml 參數說明 `type`: 決定這個 Service 要以什麼形式,後面接的就是上述那四種 Type。 `ports`: 決定該 Service 要連接哪些 port。 `protocol`: 連線的網路協議,預設是TCP `port`: Service 會以哪個 port 連接 Pod。 `targetPort`: 目標 Pod 的 port,通常與`port`設定一樣。 ### NodePort 將 Pod 暴露到外部。在 Node 上開一個特定 Port,再透過 Service 將該 port mapping 到 Pod 上的 Port。 ![](https://hackmd.io/_uploads/SJFY_cObc.png) **範例 yaml:** ```yaml= apiVersion: v1 kind: Service metadata: name: nodeport-service spec: type: NodePort selector: app: MyApp ports: - port: 80 targetPort: 80 nodePort: 30007 ``` - yaml 參數說明 `nodePort`: 代表 Node 要開哪個 Port 出去,Service 會將此 Port mapping 到 Pod 的 Port 上 :::warning :bulb: 提醒:nodePort的範圍只有30000-32767 ::: ### LoadBalancer 在將 Pod 暴露到外部時,會使用雲端平台提供的 load balancer,像是GKE的環境下,會啟動一個[Network Load Balancer](https://cloud.google.com/load-balancing/docs/network),給一個單獨的ip,轉發所有流量到該 Service 上。 同時也會自動創建外部負載 load balancer route 到的 NodePort 和 ClusterIP 服務。 ![](https://hackmd.io/_uploads/HJd9d5ub9.png) **範例yaml** ```yaml= apiVersion: v1 kind: Service metadata: name: loadbalancer-service spec: selector: app: MyApp type: LoadBalancer ports: - port: 80 targetPort: 8080 ``` ### ExternalName 外部 DNS 名稱提供內部 Alias。內部 Client 使用內部 DNS 名稱發出請求會被重導向到外部 DNS 名稱。 **範例yaml** ![](https://hackmd.io/_uploads/BkDsd5_Zc.png) ```yaml= apiVersion: v1 kind: Service metadata: name: my-service spec: type: ExternalName externalName: example.com ``` ## How Connecting Applications with Services ### 0. 準備能下 net-tool pod > **欲練神功 必先自宮(裝curl、telnet)** > 1. 準備 openjdk:8-jre-alpine 的 container 如果沒有,請參考[Docker lab1](https://hackmd.io/@S_HP7z6qQmC4l2tX34ATug/SkyBm8Qx9/%2FrOqjbnkbTWCtgbWGbcy6oQ#Docker-%E9%80%90%E6%AD%A5%E6%93%8D%E4%BD%9C-%E5%BB%BA%E7%AB%8BDocker%E6%9C%8D%E5%8B%99) 2. 啟動 container ```shell= docker start <container-id> ``` 3. 進到 container 中 ```shell= docker exec -it <container-id> sh ``` 4. 安裝 curl ``` /app # apk add curl fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz (1/4) Installing nghttp2-libs (1.35.1-r2) (2/4) Installing libssh2 (1.9.0-r1) (3/4) Installing libcurl (7.64.0-r5) (4/4) Installing curl (7.64.0-r5) Executing busybox-1.29.3-r10.trigger OK: 85 MiB in 57 packages ``` 5. 安裝 telnet ``` /app # apk add busybox-extras fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz (1/1) Installing busybox-extras (1.29.3-r10) Executing busybox-extras-1.29.3-r10.post-install Executing busybox-1.29.3-r10.trigger OK: 85 MiB in 58 packages ``` 6. local 測試 ``` /app # curl www.google.com /app # telnet www.google.com 80 ``` 測試完畢,退出 container ``` /app # exit ``` 7. 將 container 轉乘 image ```shell= docker commit <container-id> ``` 8. 將 image 加上 tag ``` > docker images REPOSITORY TAG IMAGE ID CREATED SIZE <none> <none> 883307944b66 4 minutes ago 87.5MB deposit v1.0.0 7f80e8e1b430 13 days ago 106MB ``` 將 REPOSITORY:TAG 這個 image 加上 tag ```shell= docker tag <container-id> <dockerhub-id>/net-tool ``` 檢查 image ``` > docker images REPOSITORY TAG IMAGE ID CREATED SIZE <dockerhub-id>/net-tool latest 883307944b66 7 minutes ago 87.5MB ``` 9. 傳到 Repository (Docker Hub) ``` > docker push <dockerhub-id>/net-tool Using default tag: latest The push refers to repository [docker.io/marklab1108/net-tool] bda82d1f65a6: Pushed edd61588d126: Mounted from marklab1108/deposit 9b9b7f3d56a0: Mounted from marklab1108/deposit f1b5933fe4b5: Mounted from marklab1108/deposit latest: digest: sha256:48e0e865a13f6e871669424df003f121f99f468eb34578e5ad8cf8d6412a9461 size: 1158 ``` 10. 建成 pod 建立 net-tool.yml: ```yaml= apiVersion: v1 kind: Pod metadata: name: net-tool labels: app: lab-net-tool spec: containers: - name: net-tool image: marklab1108/net-tool ``` 建成 Pod ```shell= kubectl apply -f net-tool.yml -n <namespace-name> ``` 確認 pod ``` > kubectl get pod -n my-namespace NAME READY STATUS RESTARTS AGE net-tool 1/1 Running 0 29s ``` 11. 測試 進到 Pod 裡面 ```shell= kubectl exec -it net-tool -n <namespace-name> sh ``` 測試 curl 及 telnet ``` /app # curl www.google.com /app # telnet www.google.com 80 ``` 12. 往後要連線到其他 pod,就可以透過 net-tool ### 1. 調整 Pod yaml 將之前建 Deposit pod 的 yaml檔中加入`metadata`.`labels`.`app` ```yaml= apiVersion: v1 kind: Pod metadata: name: my-deposit labels: app: <label-name> spec: containers: - name: my-deposit image: <DockerHub帳號/repository> ports: - containerPort: 8080 volumeMounts: - name: app-config mountPath: /app/config readOnly: true volumes: - name: app-config configMap: name: <config-name> ``` 重新刪除、建立Deposit pod ``` ~/Desktop/Kubernetes $ kubectl apply -f my-deposit-pod.yaml pod/my-deposit created ``` ### 2. 建立 Service #### Type: ClusterIP - 查看 pod ip ``` > kubectl describe pod my-deposit -n my-namespace Name: my-deposit Namespace: my-namespace Priority: 0 Node: gke-cluster-1-default-pool-4c7db623-ph4l/10.128.0.10 Start Time: Thu, 10 Mar 2022 11:59:22 +0800 Labels: app=lab-my-deposit Annotations: <none> Status: Running IP: 10.4.1.13 IPs: IP: 10.4.1.13 ... 以下略 ``` - 測試 API 透過net-tool Pod 進行測試 ``` # curl --location --request GET "http://10.4.1.13:8080/api/version" {"version":"v1.0.3"} ``` 現在來建立ClusterIp Type Service * 建立 clusterip-service.yaml ```yaml= apiVersion: v1 kind: Service metadata: name: clusterip-service spec: selector: app: <label-name> type: ClusterIP ports: - targetPort: 8080 port: 80 protocol: TCP ``` ```shell= kubectl apply -f clusterip-service.yaml ``` * 確認ClusterIP Service 的 spec.clusterIP ``` ~/Desktop/Kubernetes $ kubectl get service clusterip-service --output yaml apiVersion: v1 kind: Service metadata: annotations: cloud.google.com/neg: '{"ingress":true}' kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"clusterip-service","namespace":"default"},"spec":{"ports":[{"port":80,"protocol":"TCP","targetPort":8080}],"selector":{"app":"my-deposit"},"type":"ClusterIP"}} creationTimestamp: "2022-03-10T09:13:24Z" name: clusterip-service namespace: default resourceVersion: "1741212" uid: dc5aa9d5-88cf-41bd-b058-0c3295809bfb spec: clusterIP: 10.8.3.218 clusterIPs: - 10.8.3.218 ...省略 ``` * 進入Pod ``` kubectl exec -it my-deposit -- sh ``` * 安裝curl指令 > **沒有自宮 也可練功(關掉重啟Deposit Pod時再裝就好~想不到吧~)** ```shell= apk add --no-cache curl ``` ``` /app # apk add --no-cache curl fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz (1/4) Installing nghttp2-libs (1.35.1-r2) (2/4) Installing libssh2 (1.9.0-r1) (3/4) Installing libcurl (7.64.0-r5) (4/4) Installing curl (7.64.0-r5) Executing busybox-1.29.3-r10.trigger OK: 85 MiB in 57 packages ``` * Cluster **內部** 透過 ClusterIP Service 測試API ``` /app # curl --location --request GET "http://10.8.3.218:80/api/version" {"version":"v1.0.9"} ``` #### Type: NodePort - 建立 service.yml: ```yaml= apiVersion: v1 kind: Service metadata: name: <service-name> spec: type: NodePort selector: app: <label-name> ports: - port: 8080 targetPort: 8080 nodePort: 30007 ``` - 建立 Service ```shell= kubectl apply -f service.yml -n <namespace-name> ``` - 查看 Service ``` > kubectl get service -n my-namespace NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE my-deposit-nodeport NodePort 10.8.8.228 <none> 8080:30007/TCP 16s ``` - 比對 pod 與 service ``` > kubectl describe pod my-deposit -n my-namespace Name: my-deposit Namespace: my-namespace Priority: 0 Node: gke-cluster-1-default-pool-4c7db623-ph4l/10.128.0.10 Start Time: Thu, 10 Mar 2022 11:59:22 +0800 Labels: app=lab-my-deposit Annotations: <none> Status: Running IP: 10.4.1.13 IPs: IP: 10.4.1.13 ... 以下略 ``` ``` > kubectl describe service my-deposit-nodeport -n my-namespace Name: my-deposit-nodeport Namespace: my-namespace Labels: <none> Annotations: cloud.google.com/neg: {"ingress":true} Selector: app=lab-my-deposit Type: NodePort IP Family Policy: SingleStack IP Families: IPv4 IP: 10.8.8.228 IPs: 10.8.8.228 Port: <unset> 8080/TCP TargetPort: 8080/TCP NodePort: <unset> 30007/TCP Endpoints: 10.4.1.13:8080 Session Affinity: None External Traffic Policy: Cluster Events: <none> ``` 可以發現這個 service 的 8080 port 有連到 pod 的8080 port 上。 - 測試 API 建立NodePort Service後還不能直接從外部連接Pod 取得node 外部 IP 地址(EXTERNAL-IP ) ```shell= kubectl get nodes --output wide ``` ``` ~/Desktop/Kubernetes $ kubectl get nodes --output wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME gke-cluster-1-default-pool-f1e22c17-b91t Ready <none> 4d v1.21.6-gke.1503 10.128.0.39 35.226.47.33 Container-Optimized OS from Google 5.4.144+ containerd://1.4.8 gke-cluster-1-default-pool-f1e22c17-mdll Ready <none> 4d v1.21.6-gke.1503 10.128.0.40 35.239.219.147 Container-Optimized OS from Google 5.4.144+ containerd://1.4.8 gke-cluster-1-default-pool-f1e22c17-sbvw Ready <none> 4d v1.21.6-gke.1503 10.128.0.38 35.232.65.134 Container-Optimized OS from Google 5.4.144+ containerd://1.4.8 ``` 開啟GCloud Shell建立防火墙規則允許 TCP 端口 ```shell= gcloud compute firewall-rules create test-node-port --allow tcp:<nodePort> ``` ![](https://hackmd.io/_uploads/HJNqzHObc.png) ``` mickey_taozhu@cloudshell:~ (rational-being-343002)$ gcloud compute firewall-rules create test-node-port --allow tcp:30007 Creating firewall...working..Created [https://www.googleapis.com/compute/v1/projects/rational-being-343002/global/firewalls/test-node-port]. Creating firewall...done. NAME: test-node-port NETWORK: default DIRECTION: INGRESS PRIORITY: 1000 ALLOW: tcp:30007 DENY: DISABLED: False mickey_taozhu@cloudshell:~ (rational-being-343002)$ ``` 外部從<EXTERNAL-IP>:<nodePort>發API請求 ![](https://hackmd.io/_uploads/S130fB_-c.png) #### Type: LoadBalancer - 建立 service.yml: ```yaml= apiVersion: v1 kind: Service metadata: name: <service-name> spec: type: LoadBalancer selector: app: <label-name> ports: - port: 8080 targetPort: 8080 ``` - 建立 Service ```shell= kubectl apply -f service.yml -n <namespace-name> ``` - 查看 Service ``` > kubectl get service -n my-namespace NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE my-deposit-loadbalancer LoadBalancer 10.8.13.151 130.211.127.49 8080:32369/TCP 69s my-deposit-nodeport NodePort 10.8.8.228 <none> 8080:30007/TCP 64m ``` - 比對 pod 與 service ``` > kubectl describe pod my-deposit -n my-namespace Name: my-deposit Namespace: my-namespace Priority: 0 Node: gke-cluster-1-default-pool-4c7db623-ph4l/10.128.0.10 Start Time: Thu, 10 Mar 2022 11:59:22 +0800 Labels: app=lab-my-deposit Annotations: <none> Status: Running IP: 10.4.1.13 IPs: IP: 10.4.1.13 ... 以下略 ``` ``` > kubectl describe service my-deposit-loadbalance -n my-namespace Name: my-deposit-loadbalancer Namespace: my-namespace Labels: <none> Annotations: cloud.google.com/neg: {"ingress":true} Selector: app=lab-my-deposit Type: LoadBalancer IP Family Policy: SingleStack IP Families: IPv4 IP: 10.8.13.151 IPs: 10.8.13.151 LoadBalancer Ingress: 130.211.127.49 Port: <unset> 8080/TCP TargetPort: 8080/TCP NodePort: <unset> 32369/TCP Endpoints: 10.4.1.13:8080 Session Affinity: None External Traffic Policy: Cluster Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal EnsuringLoadBalancer 3m4s service-controller Ensuring load balancer Normal EnsuredLoadBalancer 2m12s service-controller Ensured load balancer ``` 可以發現這個 service 的 8080 port 有連到 pod 的8080 port 上。 - 測試 API 透過外部測試 ``` # curl --location --request GET "http://130.211.127.49:8080/api/version" {"version":"v1.0.3"} ``` :::success :bulb: LoadBalancer 這個 Type 本身就會有對外的能力,不需要透過[Ingress]()。 ::: :::danger :bulb: 提醒: LoadBalancer Service 會獲得一個外部 IP 地址和一個負載均衡器。**會有額外的費用。** ::: #### Type ExternalName (:new: ) :::info :bulb: 備註: ExternalName:單純回應給 client 外部的 DNS Server (並非由 k8s cluster 內部的 pod 服務) ::: - 建立 yaml: ```yaml= apiVersion: v1 kind: Service metadata: name: my-external-service spec: type: ExternalName externalName: www.webcomm.com.tw ``` - 建 Service - 測試 在與 service 相同的 net-tool pod 中,下 ping 指令 ``` /app # ping my-external-service PING my-external-service (59.124.124.8): 56 data bytes 64 bytes from 59.124.124.8: seq=0 ttl=58 time=162.158 ms 64 bytes from 59.124.124.8: seq=1 ttl=58 time=161.950 ms ``` 驗證 www.webcomm.com.tw,證明 ExternalName Service 有正確轉導 ```shell= /app # ping www.webcomm.com.tw PING www.webcomm.com.tw (59.124.124.8): 56 data bytes 64 bytes from 59.124.124.8: seq=0 ttl=58 time=161.487 ms 64 bytes from 59.124.124.8: seq=1 ttl=58 time=161.693 ms ``` 搞定? ## 參考 - [kubernets-service](https://kubernetes.io/docs/concepts/services-networking/service/) - [Labels and Selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) - [GKE-Service](https://cloud.google.com/kubernetes-engine/docs/concepts/service) - [GKE-Exposing applications using services](https://cloud.google.com/kubernetes-engine/docs/how-to/exposing-apps) - [淺顯易懂的解釋三種 type](https://dockone.io/article/4884) - [來說說 Label](https://fufu.gitbook.io/kk8s/label-note)