owned this note
owned this note
Published
Linked with GitHub
- Book mode https://hackmd.io/@ncnu-opensource/book
[toc]
# Container 管理
## Monolithic to Microservice
### Monolithic 的缺點
1. 隨時間而變肥大且複雜,軟體難以重構。在除錯時也難以尋找。
2. scaling 時一大包,造成各種資源大量的損耗。
### Microservice 的優點
1. 開發上的 decouple
2. Scaling
3. 不同的微服務可以獨立維護部署


## 困難點
- 😂 重新抽象軟體架構
- 😂 **API 要變成 stateless** (*註解ㄧ)
- 😍 由於系統變得龐大且複雜,所以在除錯上會更加困難 [Zipkin](https://zipkin.io/)
- 😍😎 當有大量的微服務 **相互依賴** 時會讓你要去決定誰去要一起被部署
- 😎 微服務造成環境的多樣性更高,在開發和部署環境上更不容易維護
- 😎 環境需求的不同,程式想要不同版本的 shared libs
:::info
註解一:
1. 當你的服務不需要在內部維持請求之間的狀態時就是 `stateless service` ,反之就是 `statefule service` (**需要開 sticky session**)。
2. 微服務應該將狀態資訊儲存在外部而非內部,這才有利於服務的擴展。
[stateful vs stateless](https://stackoverflow.com/questions/58696684/stateless-vs-stateful-microservices)
:::
## 解決
- 😂 依賴軟體團隊的經驗設計 (練家軍) ...
- 😍 服務的[顆粒度(Granularity)](https://www.fiorano.com/blogs/microservices)應該以獨立的業務邏輯來解決之間因 **相互依賴** 導致 **除錯困難** 的問題
- 😎 **We ease the pain with the help of docker and kubernetes.**
# 介紹
**因應大量容器部署管理的困難,所以才需要有一個容器管理工具來解決** (e.g. kktix 測試用 8000 個 container)
## Container management solutions
- `docker-compose`
- 適用在 **單節點** 的環境上,可以快速部署多個容器。
- `openshift`
- **now base on kubernetes**
- red hat cloud 較不有名
- <img src="https://i.imgur.com/JegIerL.png" width="450" height="450" />
- `kubernetes`
- 在 public cloud 得到最完整的支援
- 儼然成為 Container management 的標準
- `minikube`
- local 的單節點 k8s。**適合拿來練習**
- `k3s` (閹割)
- 沒有 add-ons ,不夠彈性
- `microk8s`
- 有 add-ons ,可彈性抽換不同 component
## What's the Kubernetes?
### 歷史
1. Kubernetes(又稱為 k8s)是一個開放原始碼的 **容器編排管理系統 container orchestrator**,用於自動化應用程序的部署,擴展和管理。
2. Google 於 2014 年中首次宣布 Kubernetes ,現由 [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/) 負責維護。
3. 原以 C++ 編寫,後以 Golang 重構。
4. 發音 koo ~ ber ~ natis,又稱作 **k8s**。
### 叢集架構

1. `Pod` 最基本的部署單位。
2. `Service` 代表一組功能相同的 Pods 群體。訪問 Service 時會自動 load balance 到該群體底下任意的 Pod 上。
3. `Label` 可以將任意物件 **貼上標籤** ,方便將物件進行分組還有分配任務
4. `Ingress` 用於管理從外部對叢集中的 `Service` 進行訪問(HTTP, HTTPS)。必須部署到 master node 上面。
### 系統元件
<!-- > FIXME: ingress service 在哪兒? -->

- `master node` : 負責管理和控制所有 `worker node` 的主機。
- `Worker node` : 運行微服務的主機。
- `API server` : 提供 Kubernetes API ,讓使用者可以透過它管理和設定整個系統。
- `etcd` : 儲存整個 k8s 叢集中物件的規格。
- `Scheduler` : 負責決定 Pod 要部署到哪一個節點上。
- `Kubelets` : **視為每個節點的大腦**。
- 作為 master node 和 worker node 溝通的媒介。
- 負責管理 Pod,包括在 Pod 發生故障時創建新的 Pod。
- `cAdvisor` : 搜集該節點上所有資源的資訊,透過 kubelet 隨時回報給 master node。
- `Kube-Proxy` : 維護節點上的網絡規則。負責網路流量的傳送。
### 特性
- `Velocity 速度`
- 可達成短時間多次的不停機更新。
- <img src="https://i.imgur.com/v6vg4AW.jpg" width="550" height="325" /></br>
- `Immutability 不變性`
- 當軟體版本更新時
- ✅ (**immutable**) 重新 `build` 一個更新的 `image`
- ❌ (**mutable**) 進入 `container` 更新
- `Declarative configuration 宣告式設定`
- `命令式命令 (imperative commands)` 定義了 **行為** 。「執行A,執行B,然後再執行C...」
- `宣告式設定 (declarartive configurations)` 定義了 **狀態** 。「我需要三個A程式」
- `Self-healing 自我修復`
- 隨時維護當前所需的狀態設定。
- `Scalability 擴展性`
- 通過新增或刪除 Pod 輕鬆地上下擴展服務的工作負載。
# kubernetes Component
## k8s 物件的概念
k8s 常見的物件像是 `Pod`、`Service`、`deployment`、`ingress` 等等
**使用 Kubernetes API 時,是以 JSON 或 YAML 檔案來定義物件。**
<!-- - 這些檔案不是由伺服器因應 query 的請求而返回,又或是作為請求 api 的一部分傳到伺服器上。 -->
```yaml=
# This's a yaml file to describe the spec and state of Pod objects.
apiVersion: v1
kind: Pod
metadata:
name: kubia-manual
labels:
app: kubia
spec:
containers:
- image: oliwave/kubia
name: kubia
ports:
- containerPort: 8080
protocol: TCP
```
## k8s 物件和 RESTful API 的關係
- 任何資源在 k8s 當中都可以用 **RESTful 的 [URI](https://dev.to/flippedcoding/what-is-the-difference-between-a-uri-and-a-url-4455) 來表示**。
```
https://your-k8s.com/api/v1/namespaces/default/pods/my-pod
```
- 這些資源在 k8s 當中都叫做 Kubernetes objects 。
- 每一個 kubernetes objects 都存在 **唯一的 HTTP/HTTPS 路徑**。
取得 Kubernetes objects 最基本的 kubectl 指令是 **get**
> kubectl get <resource-name\>
列出在目前 namespaces 底下的所有資源,然而若想要取得特定資源
> kubectl get <resource-name\> <obj-name\>
想要知道該物件的詳細資訊
> kubectl describe <resource-name\> <obj-name\>
:::info
### -o 參數的意義
在預設的情況下, kubectl 的返回結果是 human-readable ,但這會導致許多詳細的資訊被省略掉。如果想要得到完整的 **物件** 可以加上 -o 參數並指定想要的格式
> -o json/yaml
更進階的做法還可以從返回的物件中指定想要看到的 field
> kubectl get pods my-pod -o jsonpath --template={.status.podIP}
:::
### Creating, Updating 物件
#### 範例
假設有一個簡單的物件在 `obj.yaml` 中定義好了。可以透過 kubectl 在 Kubernetes 中 **創建和更新** 該物件
> kubectl apply -f obj.yaml
**每個物件會有 metadata 這個欄位,用以定義物件唯一的名字**
```yaml=
#...
kind: Pod
metadata:
name: inventory
#...
```
# Namespaces
Kubernetes 支持由同一 physical cluster 支持的多個 virtual clusters。這些 virtual clusters 稱為 **Namespaces**。

namespaces 提供了名稱範圍。資源名稱在名稱空間內必須唯一,但在各個名稱空間之間是不用的。namespaces 彼此是平行的關係,
**物件的名字在一個 namespace 底下是唯一的**
:::success
- `namespaces` 用來組織在叢集中的 **物件**
- 可以把每一個 *`namespaces`* 都想像成一個 **資料夾** 管理自己的 **物件**
- `kubectl` 預設是和 `default namespaces` 互動
- 如果想列出叢集中所有的 `pods` ,可以用 `--all-namespaces` 作為參數
- 不同的使用者應該有不同的 `namespaces` ,也需要有權限的管理
:::
#### 創建 Namespace
```yaml=
apiVersion: v1
kind: Namespace # 資源的類型
metadata:
name: custom-namespace # Namespace 的名字
```
在不同的 `namespace` 創建資源
> kubectl create -f kubia-manual.yaml -n custom-namespace
# Pods
## Pod 基本概念
1. K8s 將多個 container 變成一組單位 `Pod` (Pod 有一群鯨魚的意思)
<!-- 2. Pod 代表一個在相同環境底下執行的 application containers 和 volume 的集合 **// rephrase** -->
2. Pod 是 K8s 中可部署的最小單位,也就是所有在 Pod 裡的 containers 都運行在同一個 worker node 上面。
3. **每一個在 Pod 裡面的 container 都擁有自己的 cgroup ,但卻共用一定數量的 namespaces**
4. 應用程式在同個 Pod 中共享同個 `Network` 、 `UTS` (hostname)、 `IPC` namespaces ,因此可以用 `http://localhost:\<port_num>` 來彼此溝通 (IPC)。相反的應用程式在不同的 Pod 是隔離的,所以有不同的 IP 、 hostname 等等。
:::info
## Container 的概念
使用 Linux 的兩個技術
- **Namespaces** 確保每一個 process 都是由自己的觀點去看整個系統 (files, processes, network interfaces, hostname, and so on) **軟體**
- **Cgroups** 限制每一個 process 可以使用的資源 (CPU, memory, network bandwidth, and so on) **硬體**
有許多種 Namespaces 而每一種都是在隔離不同種的資源
:::
## Pod IP
對 K8s 來說,不管 Pod 在哪一個 Node 上都可以把它們想像成在同一個 LAN 底下。因此彼此是可以訪問對方的,這是透過 **軟體定義網路 SDN** 來實現的 (*由 kube-proxy 完成的*)。

<!-- **在同一個 Pod 底下的 containers 就像一台實體主機或虛擬機上跑一些 processes 一樣** -->
## 如何創建一個 Pod
事實上可以透過 kubectl 用以下幾種 K8s 提供的方式來創造物件
- `Imperative command` 立即指令(**不建議使用**)
- `kubectl run kuard --generator=run-pod/v1 --image=gcr.io/kuar-demo/kuard-amd64:blue`
- `Declarative configuation` => **撰寫 Pod manifest**
- `kubectl apply -f pod.yaml`
1. 撰寫簡易的 Pod Manifest
```yaml=
apiVersion: v1
kind: Pod
metadata:
name: kuard
spec:
containers:
- image: gcr.io/kuar-demo/kuard-amd64:blue
name: kuard
ports:
- containerPort: 8080
name: http
protocol: TCP
```
:::info
### 什麼是 Pod Manifest
- Pod Manifest 就只是一個用 json 或 yaml 格式來描述 **Pod 物件** 的文字檔案
- 該文件描述 Pod 的 **ideal state** ,因此**這是一種 declarative configuration 而不是 imperative configuration**
- 事實上撰寫 Pod Manifest (或其他 K8s 物件) 是比較推薦的
- 當 source code 在維護
- 更容易閱讀
:::
2. 把撰寫好的 Pod Manifest 透過 kubectl 丟到 K8s API server
- > kubectl apply -f kuard-pod.yaml
3. K8s API server 會做兩件事:
1. 透過 **Kube Scheduler** 會決定要放在哪個 worker node 上。
2. **Kubelet** 屆時會負責把相對應寫在 Pod manifest 裡的 container 都運行起來,並且監控。
以上都步驟都完成後,可以查看所有 Pod 的狀況
`kubectl get pods`

或是檢查單一 Pod 的詳細資訊
`kubectl describe pods kuard`
<!-- > FIEXME: 調整 header -->
## 終止 pod
`kubectl delete pods kuard`
:::info
### 還有許多可以對 Pod 做的設定...
#### Grace Period
事實上當我們刪除一個 Pod 時 K8s 會做以下事情
1. 讓 Pod 拒絕接收新的 request,並消化當前還有的 request
2. 在預設時間 30 秒過後才把 Pod 刪除掉
以上的 30 秒就是所謂的 **Grace period** ,這麼做是為了確保服務的**可靠性**。我們不希望有任何請求是因為要刪除 Pod 而無法回應到的
#### Health Check
最簡易的 health check 就是 **process health check** 。也就是直接判斷該程序是否還繼續執行著。但這會有一個很大的問題就是萬一程序進入 dead lock ,事實上該程序仍然運行的但是卻無法再提供服務了。
因此在 K8s 引進了幾種方法來解決
##### Liveness Probe
也**就是執行一個任務,如果通過了他就還健在**。常見的就是發 http request 檢查在 Pod 裡的 container 是否健康~
<br>
```yaml=
apiVersion: v1
kind: Pod
metadata:
name: kuard
spec:
containers:
- image: gcr.io/kuar-demo/kuard-amd64:blue
name: kuard
livenessProbe:
httpGet:
path: /healthy # 測試該容器是否健康的請求
port: 8080
initialDelaySeconds: 5 # 在 Pod 被初始化後的 5 秒才開始測試
timeoutSeconds: 1 # 限時 1 秒回應
periodSeconds: 10 # 每 10 秒一次
failureThreshold: 3 # 超過三次失敗就重起該 container
ports:
- containerPort: 8080
name: http
protocol: TCP
```
##### Readiness Probe
readiness probe 和 liveness probe 最大的不一樣就是 liveness probe 確認應用程式是否正確執行,而它則是 **檢查該應用程式是不是已經準備好可以開始服務了**。
##### 其他的 K8s 所提供的 Health Check
除了 HTTP 以外,還有 **tcpSocket health checks** 或是讓容器執行一段 script 來檢查。這麼做是因為不是每一個服務都是 http-base appliction.
:::
:::danger
##### MISC
**以下都是不建議直接使用 kubectl 去操作的** (違反 declarative configuration 精神)
有更適當的工具或方法去完成這些任務。
1. 取得 Pod 的 Logs (fluentd and elasticsearch)
2. 在 container 裡面執行指令
3. 從 container 裡面複製檔案到外面,反之亦然
:::
#### Resource Management
Pod 在資源(cpu, mem, gpu)的請求上在 K8s 有兩種方式
- **minimum** 該容器最少需要多少該種類型的資源 (情況允許,可以多給)
- **limit** 該容器最多可以擁有該種類型的資源
```yaml=
apiVersion: v1
kind: Pod
metadata:
name: kuard
spec:
containers:
- image: gcr.io/kuar-demo/kuard-amd64:blue
name: kuard
resources:
requests:
cpu: "500m"
memory: "128Mi"
limits:
cpu: "1000m"
memory: "256Mi"
ports:
- containerPort: 8080
name: http
protocol: TCP
```
::: info
1. **記住資源的請求是以 container 為單位,因為不同服務需要的資源種類和數量都不一樣**
2. **一個 Pod 所請求的資源就是其所有 containers 資源請求的總和**
3. **記憶體與 CPU 資源的分配不一樣,因為記憶體一旦分配給一個程序就不能再收回。因此當系統消耗完記憶體的資源時,就會把取得過多的 contaienr 重啟以達重新分配記憶體資源**
:::
#### 在 Pod 中不同類型的 volume
- 生命週期與 Pod 相同的 volume (**emptyDir**)
- For Communication/synchronization
- 容器間可以共用一些資料
- For Cache
- 有時容器需要或被重啟,那就可以緩衝一些運算昂貴的資料
- 長期維持的 volume
- Persistent data
- 不管是 private 或 public 的 provider , k8s 都有提供 api
- e.g. NFS, iSCSI, Amazon’s Elastic Block Store, Azure’s Files and Disk Storage, Google’s Persistent Disk
- ```yaml=
volumes:
- name: "kuard-data"
nfs:
server: my.nfs.server.local
path: "/exports"
```
- Mounting the host filesystem (hostPath)
- 掛載在 Node 上面
- ```yaml=
apiVersion: v1
kind: Pod
metadata:
name: kuard
spec:
volumes:
- name: "kuard-data"
hostPath:
path: "/var/lib/kuard"
containers:
- image: gcr.io/kuar-demo/kuard-amd64:blue
name: kuard
volumeMounts:
- mountPath: "/data"
name: "kuard-data"
ports:
- containerPort: 8080
name: http
protocol: TCP
```
*不像 docker ,沒有簡單的方法~*
# Labels
- Labels 是 **鍵值對**
- Labels 提供了為 **物件分組** 的基礎(該物件可以是 pod, replicaSet ...)
## 概念
**Labels 提供一群物件可被識別的標記**,這為之後分組、操作、查看等動作帶來方便。
在 K8s 中物件要尋找或參考到其他物件的時候就需要用到 selector 。
```yaml=
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: kubia
spec:
replicas: 3
selector: # ReplicaSet 用 label 來尋找它管理的物件
matchLabels:
app: test
template: # PodTemplate
metadata:
labels: # 將 pod 貼上 `app: test` 這個 label
app: test
spec:
containers:
- name: kubia
image: luksa/kubia
```
<img src="https://i.imgur.com/mSLyrq7.png" width="500" height="500" /></br>
:::info
`Selector` 底下分兩種方式來參考到含有該 label 的物件:
1. `matchLabels` 和 `matchExpressions`
- **只有 `Job`、`Deployment`、`ReplicaSet` 還有 `DaemonSet` 有支援**
- ```yaml=
selector:
matchLabels:
app: nginx
tier: frontend
# or
#matchExpressions:
#- key: app # 鍵 必須為 'app'
# operator: In # 值 必須為 'kubia'
# values:
# - kubia
```
3. 什麼都不加,直接放 label
- ```yaml=
selector:
app: nginx
tier: frontend
```
[(matchLabels, labels, and selectors 差別)](https://medium.com/@zwhitchcox/matchlabels-labels-and-selectors-explained-in-detail-for-beginners-d421bdd05362)
:::
# ReplicaSet & DaemonSet

- **RS** 定義一個 Pod 數量讓他們隨機散佈在 K8s cluster 中
- **DS** **特殊需求** ,要讓 cluster 中的每個 worker node 都有一個該服務的 Pod 。
- 例如,像是 log collector 或是 memory monitor 的程式需要在每個節點上。
- 在 K8s 系統中 kube-proxy Pod 也是運行在每一個節點上。
## ReplicaSet
1. 通常不會直接使用 ReplicaSet 而是用更高層次的 Deployment
<!-- 2. 基本上兩者功能都相同,但是在 label selector 上 ReplicaSet **可以同時比較兩個 labels 以上,還有直接判斷是否有該 key 而不管它的 value (e.g. env=\*)** -->
```yaml=
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: kubia
spec:
replicas: 3
selector:
matchLabels:
app: kubia
#matchExpressions:
#- key: app # 鍵 必須為 'app'
# operator: In # 值 必須為 'kubia'
# values:
# - kubia
template:
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: luksa/kubia
```
詳情可見 [label selector](https://hackmd.io/exfgC8MzSfqKsJgkWIpkJA?both#Labels-Selectors)
## DaemonSet
**DS 只會在每一個 Node 上執行一個 Pod**
因此 DaemonSet 不需要透過 K8s scheduler 來安排 Pod 在哪個節點上
### 將 Pod 部署到特定的節點上

```yaml=
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ssd-monitor
spec:
selector:
matchLabels:
app: ssd-monitor # 管理 label 為 `app:ssd-monitor` 的 Pod
template:
metadata:
labels:
app: ssd-monitor
spec:
nodeSelector: # PodSpec 定義的 field
disk: ssd # 把 Pod 部署到有 ssd 的節點上
containers:
- name: main
image: luksa/ssd-monitor
```
:::info
- *如果 nodeSelector 沒有成功找到符合的節點,就不會創建 Pod*
- *刪除 RS DS 都會連帶刪除旗下的 Pods*
daemonset 也可以用 label selector 部署 pod 到特定的 node 上
:::
# Service
## Service Discovery
Pod 重啟或重新部署都會產生新的 IP ,透過 Service 當作媒介,管理與監控這些 Pods 並對外暴露自己的 IP 供別人訪問存取。
<!--  -->

## Serivce 物件
```yaml=
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
# sessionAffinity: ClientIP # 根據需要來設定
ports:
- port: 80 # service 的 port
targetPort: 8080 # service 會將封包轉送的 port
selector:
app: kubia
```

:::success
1. 在 cluster 中若需要 **[`pod-to-pod`](https://kubernetes.io/docs/tasks/access-application-cluster/connecting-frontend-backend/)** 請求中,**請使用 Service 的 name 作為 hostname 而非 Pod ip 。**
- 因為你非但不能在 Pod 被建立以前知道他的 IP ,即使可以 Pod 隨時都有可能重啟或重新部署而改變 IP。
- > http://kubia.<namespace\>
2. `sessionAffinity` 只有 ClientIP 和 None 兩種模式,**並沒有 cookie-based** 。因為 K8s Service 是在 L4 而非 L7 所以只有 TCP 和 UDP
:::
### 多埠 Service 物件
```yaml=
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
ports:
- name: http # 外部 80 轉送到內部 8080
port: 80
targetPort: 8080
- name: https # 443 轉送到 8443
port: 443
targetPort: 8443
selector:
app: kubia
```
### 使用命名埠
*好處是萬一不是一個常見的 port 號,可以透過命名的方式讓結構更清晰*
*可以隨時更改底層 (Pod) 的 port 號而不會影響到上層的服務 (Service)*
首先,先為 Pod 建立好 port 的名字
```yaml=
apiVersion: v1
kind: Pod
spec:
containers:
- name: kubia
ports:
- name: http # 容器的 port 號 8080 命名為 http
containerPort: 8080
- name: https # 容器的 port 號 8443 命名為 https
containerPort: 8443
```
再來是為 Pod 建立好 Service
```yaml=
apiVersion: v1
kind: Service
spec:
ports:
- name: http
port: 80
targetPort: http
- name: https
port: 443
targetPort: https
```
:::info
#### 為什麼 ping 不到 Service 的 IP ?
- <img src="https://i.imgur.com/Vkv0jQB.png" width="500" height="500" /></br>
1. `kube-proxy` 負責監控 Endpoints 的改變
2. `kube-proxy` 如有服務訪問這組 IP 就會被 iptables 修改並且 forward 掉
:::
## 讓外部的用戶連線到 cluster 的 Service

讓外部來連線 Service 有以下幾種方式
1. 將 Service 的類型設為 `NodePort`
- 每一個在 cluster 裡面的 Node 都會開一個 Port ,然後把流量轉送到底層的 Service
- 該 Service 不僅可以透過 ClusterIP 和 Port 來訪問,也可以透過每個 Node 上的 Port
2. 將 Service 的類型設為 `LoadBalancer`
- 是 NodePort 的擴充版
- 是根據不同 cloud infrastructure 而定
- 用戶通過 load balancer 的 IP 來訪問 service
3. 建立 `Ingress` 資源,一種根本不同的機制,用於通過唯一 IP 公開多個服務
- 它是在 Layer 7 上運作的
### NodePort
```yaml=
apiVersion: v1
kind: Service
metadata:
name: kubia-nodeport
spec:
type: NodePort
ports:
- port: 80 # 這是內部 cluster IP 的 port
targetPort: 8080 # 這是 Pod 的 port
nodePort: 30123 # Service 可以從 Node 的 port 來被存取
selector:
app: kubia
```
可以使用以下指令來檢查 NodePort Service
> kubectl get svc kubia-nodeport

可以看到 `EXTERNAL-IP` 是 `<nodes>` ,也就是說該 Service 可以在每一個 node 上透過 30123 port 被存取。

### LoadBalancer
- **用在公有雲上**, cloud providers 通常會支援該模式。
- 與 NodePort 不同,它是透過唯一的 Public IP 來做存取。
<img src="https://i.imgur.com/rjN3ViF.png" width="600" height="500" /></br>
```yaml=
#...
spec:
type: LoadBalancer
ports:
- port: 80 # 這是內部 cluster IP 的 port
targetPort: 8080 # 這是 Pod 的 port
#...
```
### Ingress
單純定義一個 Service 並讓 Ingress 指向它
```yaml=
#...
kind: Service
metadata:
name: kubia
spec:
ports:
- port: 80 # service 的 port
targetPort: 8080 # service 會將封包轉送的 port
selector:
app: kubia
```
## 連接 cluster 外部的 Service
### Service 與 Endpoints 的關係
- Service 和 Pod 並不是直接有所連結的,而是有一個叫做 **Endpoints** 的資源在中間。
- **Endpoints 資源(複數)是一組一組可供 Service 做 loadbalance 的 Pod socket 清單**
> kubectl describe svc kubia

> kubectl get endpoints kubia

:::warning
1. 如果 Service 有設定 Pod 的 label selector 就會自動產生 Endpoints 物件,**並且幫你自動維護**。
2. 反之,必須自己建立 Endpoints 物件,**並且必須與 Service 同名**。
:::
### 自己設定 Service Endpoints 的時機:連線到外部網路的服務
1. 創建一個沒有 selector 的 Service
- ```yaml=
apiVersion: v1
kind: Service
metadata:
name: google-map # Service 的名字必須和 Endpoints 一樣
spec: # 無需定義 selector
ports:
- port: 80
```
2. 為沒有 selector 的 Service 創建 Endpoints
- ```yaml=
apiVersion: v1
kind: Endpoints
metadata:
name: google-map # Endpoints 的名字必須和 Service 的一樣
subsets:
- addresses:
- ip: 11.11.11.11 # Service 將連接轉發到的端點的 IP
- ip: 22.22.22.22
ports:
- port: 80
```

Service 和 Endpoints 建立起來後,就可以讓 Pods 透過 Service 存取具有兩個外部端點。
:::info
### 連線到外部網路服務的另一種方式:ExternalName Service
```yaml=
apiVersion: v1
kind: Service
metadata:
name: google-map
spec:
type: ExternalName # Service 的類型為 ExternalName
externalName: maps.googleapis.com # 真實服務的 FQDN
ports:
- port: 80
```
- Pod 呼叫外部的 API 就使用 `http://google-map`
- 不影響 Pod 的情況下,在 ExternalName Service 上改變存取的外部服務的網址
:::
## Ingress
### 為什麼一定需要 Ingress ?
每一個 LoadBalancer Service 都需要有自己的 IP,而 Ingress 只需要一個 IP。

**Ingress 是在 Layer 7 上面運作,所以可以提供比 Service 更多的功能。**
### Ingress Controller
必須記得 K8s cluster 中必須要有 Ingress controller 才可以讓 Ingress 正常運作。不同的 K8s 環境在 controller 實作上也不同。
### 創建 Ingress
```yaml=
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: kubia
spec:
rules:
- host: kubia.example.com
http:
paths:
- path: /
backend:
serviceName: kubia-nodeport
servicePort: 80
# - path: /foo
# backend:
# serviceName: bar
# servicePort: 80
# - host: bar.example.com
# http:
# paths:
# - path: /
# backend:
# serviceName: bar
# servicePort: 80
```
<!-- :::warning
> FIXEME: 1.幹嘛還需要 ingress 如果有 nodeport 了 2.GKE 還需要用 ingress 嗎
在 GKE 中,要求 service 必須為 NodePort ,但在 k8s 系統中是沒有這個要求的
::: -->
一個 client 向 K8s cluster 請求的基本流程

# Depolyment
**讓服務可以 zero downtime 升級的組建** (不停機)

1. 自動產生和維護 `ReplicaSet`
- 
2. 可隨時 rollback 到先前的版本 (預設是保留兩個版本)
- 
3. Version-based Rollout ([Canary deployment](https://dev.to/mostlyjason/intro-to-deployment-strategies-blue-green-canary-and-more-3a3))
- <img src="https://i.imgur.com/GXjBSMV.png" width="350" height="450" />
```yaml=
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubia-dp
labels:
app: kubia
spec:
replicas: 3
selector:
matchLabels:
app: kubia
template:
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: oliwave/kubia
ports:
- containerPort: 8080
```
# LAB : 在 k8s 上實踐不同版本服務的 rollout
[Play with k8s](https://labs.play-with-k8s.com/)
1. 複製以下檔案
- `kubia-dp-v1.yaml`
- ```yaml=
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubia-dp
labels:
app: kubia
spec:
replicas: 3
selector:
matchLabels:
app: kubia
template:
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: oliwave/kubia:v1
ports:
- containerPort: 8080
```
- `kubia-dp-v2.yaml`
- ```yaml=
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubia-dp
labels:
app: kubia
spec:
replicas: 3
selector:
matchLabels:
app: kubia
template:
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: oliwave/kubia:v2
ports:
- containerPort: 8080
```
- `kubia-svc.yaml`
- ```yaml=
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
#sessionAffinity: ClientIP # 根據需要來設定
ports:
- port: 80 # service 的 port
targetPort: 8080 # service 會將封包轉送的 port
selector:
app: kubia
```
- `ingress.yaml`
- ```yaml=
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: kubia
spec:
rules:
- host: kubia.example.com
http:
paths:
- path: /
backend:
serviceName: kubia
servicePort: 80
```
2. `kubectl apply -f kubia-dp-v1.yaml`
- `kubectl get po -o wide` 檢查 pod 是否部署在從集中
- `kubectl get rs` 檢查 replicasets 是否有被 deployment 自動的產生
4. `kubectl apply -f kubia-svc.yaml`
- `kubectl get svc` 取得 Service 的 IP
6. `kubectl apply -f ingress.yaml`
7. `while true; do curl <service IP>; done`
- 不停得向服務發送請求
9. `kubectl apply -f kubia-dp-v2.yaml`
# Service Mesh
## 傳統 Microservice 不同服務間的交互
<!-- <img src="https://i.imgur.com/BHXiC2F.png" width="600" height="300" /></br> -->
每個微服務必須個別實現 `Security (公有雲)`、`Tracing`、`Logging` 等機制。

1. 導致 **不易開發和維護**
2. 違反微服務 **Do One Thing, Do it Best** 的原則
## What is Service mesh ?
1. 在不影響既有 Application container 的情況下,植入 Sidecar container
2. Sidecar container 負責管理和設定不同服務實體之間的網路通訊

輕鬆簡單地為現有已經部署的微服務進行 `load-balancing`, `service-to-service authentication`, `monitoring` 等等,而 **無需改動現有的服務**
# Istio
<!-- > FIXME: 簡述 Istio 和 Service mesh 的關係 -->
Istio 在架構上大量的使用 Service Mesh 模式
## 實現 Sidecar Injection 的方式
<img src="https://banzaicloud.com/img/blog/istio/sidecar-lifecycle-1.gif" width="450" height="500" />
- `istio-init` ([init container](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/)) 負責修改 iptables 的規則,讓所有進來或出去 Pod 的流量都經過 sidecar proxy
- `istio-proxy` 就是 sidecar proxy (based on [Envoy](https://www.envoyproxy.io/))
## 架構
<!-- > FIXME: pod 要連到其他的 service 時需要經由自己的 service 嗎? -->
<img src="https://i.imgur.com/7iwg60B.png" width="500" height="500" /></br>
Istio service mesh 在邏輯上可以分為 `Control plane` 和 `Data plane`
- `Data plane` 是由一組 proxies (**envoy**) 所組成,並以 **Sidecar** 的方式來部署
- `Control plane` 專門用來管理和設定這些 *路由流量* 的 proxies
#### Pilot
**為 Envoy Sidecar 提供 Service Discovery 的服務**
<!--  -->
<img src="https://i.imgur.com/0cNCHJi.png" width="500" height="500" />
#### Citadel
<!-- > FIXME: 怎麼認證? -->
Enables strong service-to-service and end-user authentication with built-in identity and credential management.
:::info
#### Sidecar deployment 的效果
1. 使 Istio 可以將大量有關流量的訊號抽取出來成為不同的 *屬性*。Istio 可以通過這些屬性來執行不同的 *政策決定 (policy decisions)* ,並將他們送到監控系統來提供當前 service mesh 的行為資訊。
2. 可以將不同的功能加入到現有 Istio 而無需重構架構或是程式碼。
:::
:::warning
#### Traffic
流量在 Istio 的定義中可以分為 `Data plane traffic` 和 `Control plane traffic` 。 Envoy proxies 是 Istio 架構中唯一與 Data plane traffic 有關的組件。
:::
## 流量管理
Istio 的流量管理模組是使用 **envoy proxies** ,隨著你的服務一起部署上去。**所有的流量不論是由 service mesh 接收或送出都會由 envoy 來 proxy**。

:::info
`Envoy` 是一種 L7 proxy 和 communication bus (**API gateway**) 被設計用來在大型 **`SOA` Service-oriented Architectures** 上。
:::
透過簡單規則配置和流量路由控制服務之間的流量和API調用的流量。簡化了 Service 一些屬性上的配置。
1. [Circiut breaker](https://ithelp.ithome.com.tw/articles/10194912)
- 服務自我檢查的機制
- 避免單一服務崩潰,造成整體服務連鎖崩壞
2. A/B Testing
- 
3. Canary rollouts
4. Failure recovery
### Kubernetes 就有做了為什麼還需要 Istio ?
#### 舉例: Version-based Rollout (Canary deployment)
- k8s 使用 **pod 的數量** 來調整不同版本的比例
- <img src="https://i.imgur.com/GXjBSMV.png" width="350" height="450" />
- Istio 透過 `Virtual service` 直接控制流量進入不同流量的比例
- <img src="https://i.imgur.com/FCI30dN.png" width="350" height="450" />
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>