# 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,讓外部可以透過它來存取相關資源

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

範例:[將 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`,建立長連線
回頭看看我們的需求:

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)

從開發者的回答可以看出,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` 就會啟用(或更新)

可以看到我們的 內網 load balacer 已經建置成功
再來我們要到 `google console` 去做下一段工作:[外部負載平衡器](https://hackmd.io/@rd7-edlo/Hy_psVFhO)
---
設置完成後,我們可以到**網路服務 > 外部 IP 位址**查看結果

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

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