---
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彼此間也可以互相溝通

## 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的應用場景通常是保護某些端點不被外部存取。

**範例 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。

**範例 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 服務。

**範例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**

```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>
```

```
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請求

#### 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)