---
# System prepended metadata

title: K8S lab3(Service)
tags: [k8s]

---

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

