---
# System prepended metadata

title: Service & Ingress
tags: [k8s, container]

---

# Service & Ingress

## Service
>An abstract way to expose an application running on a set of Pods as a network service.
With Kubernetes you don't need to modify your application to use an unfamiliar service discovery mechanism. Kubernetes gives Pods their own IP addresses and a single DNS name for a set of Pods, and can load-balance across them.



根據官方定義，`Service` 是 k8s 內定義的抽象化物件(object)，看一下詳細說明：

* [Service(k8s 官網)](https://kubernetes.io/zh/docs/concepts/services-networking/service/)
* [Kubernetes Service 概念詳解](https://tachingchen.com/tw/blog/kubernetes-service/)
* [Kubernetes Service 取路徑差異](https://tachingchen.com/tw/blog/kubernetes-service-in-detail-1/)
* [Kubernetes Service 標籤對於 Service 的影響](https://tachingchen.com/tw/blog/kubernetes-service-in-detail-2/)


在 `hello world, minikube` 中，曾經下過這道指令：
```yaml=
kubectl expose deployment hello-node --type=LoadBalancer --port=8080
```
k8s 會將部署的 pod 對外暴露

```shell=
kubectl get services

NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
hello-node   LoadBalancer   10.108.144.78   <pending>     8080:30369/TCP   21s
kubernetes   ClusterIP      10.96.0.1       <none>        443/TCP          23m
```
換句話說，k8s 幫我們建立了一個 `service` : hello-node，讓外部可以透過它來存取相關資源

![](https://i.imgur.com/h8T04lz.jpg)

>`EXTERNAL-IP`: 指定 service 為 type: LoadBalancer 時，Cloud Provider (如 GCP、AWS) 配發的 ==Public IP==

當然我們也可以用 yaml 來定義我們想要的服務：

範例：`demo-service.yaml`

```yaml=
apiVersion: v1
kind: Service
metadata:
  name: service-example
spec:
  selector:
      app: web         #對應到 deployment 中的 labels: app
  # type: NodePort     #見補充
  ports:
    - name: http
      port: 8080         #service 對外部開放的埠號
      targetPort: 80   #實際 pod 所開放的埠號
      nodePort: 30080   #(optional): 此設定只有在 spec.type 為 NodePort 或 LoadBalancer 才會存在，預設範圍：30000-32767
      protocol: TCP
    - name: https
      port: 443
      targetPort: 443
      protocol: TCP
```
:::info
Type 的取值以及行为如下：

* `ClusterIP`：通过集群的内部 IP 暴露服务，选择该值时服务只能够在集群内部访问。 这也是默认的 ServiceType。

* `NodePort`：通过每个节点上的 IP 和静态端口（NodePort）暴露服务。 NodePort 服务会路由到自动创建的 ClusterIP 服务。 通过请求 <节点 IP>:<节点端口>，你可以从集群的外部访问一个 NodePort 服务。

* `LoadBalancer`：使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的 NodePort 服务和 ClusterIP 服务上。

* `ExternalName`：通过返回 CNAME 和对应值，可以将服务映射到 externalName 字段的内容（例如，foo.bar.example.com）。 无需创建任何类型代理。
:::

## Internal load balancer

>在混合环境中，有时有必要在同一(虚拟)网络地址块内路由来自服务的流量
在水平分割 DNS 环境中，你需要两个服务才能将内部和外部流量都路由到你的端点（Endpoints
如要设置内部负载均衡器，请根据你所使用的云运营商，为服务添加以下注解之一

以 GCP 為例
```yaml=
apiVersion: v1
kind: Service
metadata:
  name: pomelo-block-svc
  annotations:
    networking.gke.io/load-balancer-type: "Internal"
spec:
  selector:
    app: pomelo-block
  type: LoadBalancer
  loadBalancerIP: 10.107.22.111
  ports:
  - name: conn-1
    port: 30001
    targetPort: 30001
    nodePort: 30001
    protocol: TCP
  - name: conn-2
    port: 30002
    targetPort: 30002
    nodePort: 30002
    protocol: TCP  
```

透過上述的 service，同一個網段的外部服務將可以透過 `10.107.22.111:30001(or 30002)` 訪問到 `pomelo-block`


## Ingress

Ingress 是對集群中服務的==外部訪問==進行管理的 API 物件
* [Ingress(k8s 官網)](https://kubernetes.io/zh/docs/concepts/services-networking/ingress/)
* [在 Kubernetes 中實現負載平衡 - Ingress Controller](https://ithelp.ithome.com.tw/articles/10196261)
* [Kubernetes 基礎教學（二）實作範例：Pod、Service、Deployment、Ingress](https://cwhu.medium.com/kubernetes-implement-ingress-deployment-tutorial-7431c5f96c3e)

![](https://i.imgur.com/beIlX4L.jpg)

範例：[將 WebSocket 伺服器公開給 Ingress](https://docs.microsoft.com/zh-tw/azure/application-gateway/ingress-controller-expose-websocket-server)


### Ingress 與 pomelo 的相性

ingress 的特色是，透過不同的 url path mapping，從前端將請求導到正確的後端服務

例如：
```shell=
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: demo-ingress
spec:
  rules:
  - http:
      paths:
      - path: /v1/*
        backend:
          serviceName: web1
          servicePort: 8080
      - path: /v2/*
        backend:
          serviceName: web2
          servicePort: 8080
```
上面的範例，前端收到的請求 `/v1/*` 會將服務引導到 `web1` 這個 service 的 8080 port，
 `/v2/*` 則是對應到 `web2:8080`，這邊最大的特色是，==前端服務對外只會開放 80/443 兩種 port==

這麼做的最大好處是，我們只需要對外暴露一組 ip + 80/443 的 url，就可以讓 client 透過 url path 來做各式的請求

又因為 websocket 是建立在 http 之上的應用，所以也可以透過上述的 ingress 來連接 `ws[wss]://frontend_url`，建立長連線

回頭看看我們的需求：

![](https://i.imgur.com/jXmbqmI.jpg)

pomelo 因為是利用 node js 開發，所以承襲了它的特色，一個 process 對應一個 tcp port，如果我們的 connector 需要開啟 `N` 個 cluster，那就需要開放 `N` 個對應的 port

連接方式會像是：
```shell=
ws://connector:3014
ws://connector:3015
ws://connector:3016
...
```

從這邊可以看得出來，兩種方式的差異


為了將 pomelo 套用到 ingress 上，我嘗試了各種方法，直到看了[這篇](https://github.com/kubernetes/ingress-nginx/issues/1655)


![](https://i.imgur.com/oic4Ffw.png)

從開發者的回答可以看出，ingress 並不支援同一個 domain name 連接多個 port

當然我們也可以改成用這樣的方式來 mapping:

```yaml=
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: pomelo-ingress
spec:
  rules:
  - host: bbcg.prod.com
    http:
      paths:
      - path: /conn1-port1
        backend:
          serviceName: conn-1
          servicePort: 3014
      - path: /conn1-port2
        backend:
          serviceName: conn-1
          servicePort: 3015
```

然後就可以透過 `ws://frontend_url/conn1-port1` 來連接到 `conn-1` 的 cluster-1

但是這麼一來，將和 pomelo 的風格完全不同，包括程式碼與設定檔都需要做大規模都修改

經評估後，決定採用比較單純的 Load Balancer 的方式來對外暴露服務，並手動綁定靜態 IP


### pomelo service load balancer 建置流程

根據需求 我們要滿足可以從內網讓其他 service 可以打進來，同時也要開放 `client port` 讓客端可以透過 websocket 連上

換句話說，我們同時需要 內網/外網 `Load Balancer`

k8s 的 `service api` 可以幫我們輕鬆的建置 load balancer，但是目前看的資料，他只能讓我們二選一 (內網 or 外網)，因此我們需要自行建置其中一個

這邊我採取的方法是，讓 `service api` 幫我們做掉內網，外網我們再透過 `google console`ˋ自行處理

#### service yaml

```yaml=
apiVersion: v1
kind: Service
metadata: 
  name: gate
  labels:
    app: gate
  annotations:
    networking.gke.io/load-balancer-type: "Internal"
    networking.gke.io/internal-load-balancer-subnet: <子網域>
spec:
  selector:
    app: gate
  type: LoadBalancer
  loadBalancerIP: <指定的內網IP>
  ports:
    - protocol: TCP
      name: "client"
      port: 30014
      targetPort: 30014
      nodePort: 30014
    - protocol: TCP
      name: "web"
      port: 30050
      targetPort: 30050
      nodePort: 30050
---
```

首先在 `annotations` 的地方加上
* `load-balancer-type: "Internal"`
* `internal-load-balancer-subnet: <子網域>`

第一個是和 API 註明，我要一個內網 load balancer，第二個則是 cluster 所在的子網域

接下在 `spec` 的地方加上`loadBalancerIP: <指定的內網IP>`，自行指定內網的固定 IP
>要注意此 IP 是否符合 [CIDR](https://zh.wikipedia.org/wiki/%E6%97%A0%E7%B1%BB%E5%88%AB%E5%9F%9F%E9%97%B4%E8%B7%AF%E7%94%B1) 規則，以及是否被佔用

設定完後，直接下 `kubectl apply` 就會啟用(或更新)

![](https://i.imgur.com/R90Nqtd.png)

可以看到我們的 內網 load balacer 已經建置成功

再來我們要到 `google console` 去做下一段工作：[外部負載平衡器](https://hackmd.io/@rd7-edlo/Hy_psVFhO)

---

設置完成後，我們可以到**網路服務 > 外部 IP 位址**查看結果

![](https://i.imgur.com/fST6fJu.png)


:::info
:mag_right: 檢查我們的轉送規則有沒有綁定成功
:::


![](https://i.imgur.com/kQSQN8C.png)


###### tags: `k8s` `container`