[上集連結 - Core Concept (上)](/U_nYwIz8Q7CsmsR7mHBm4A)
###### tags: `CKA`
Core Concept (下)
===
[TOC]
# 基本架構
## Imperative(命令式) V.S Declarative(宣告式)
+ 兩種使用方式沒有誰好誰壞,可以交替使用
+ Declarative : 將物件的規格以 yaml 的形式設定好,透過 `kubectl create/apply/replace -f <file>` 來建立物件
+ Imperative: 直接透過 `kubectl` 指令來對物件進行操作:
+ 使用 `--dry-run=client` 來驗證指令是否正確,不會真的建立物件
+ 使用 `-o yaml` 來將物件以 yaml 格式輸出,是個很好的範本
```bash=
# 建立 Pod
kubectl run nginx --image=nginx --dry-run=client -o yaml
# 建立 Deployment
kubectl create deployment nginx --image=nginx --replicas=4
# 調整 deployment 的 replica
kubectl scale deployment nginx --replicas=4
# 建立服務 - 1
kubectl expose pod nginx --type=NodePort --port=80 --name=nginx-service --dry-run=client -o yaml
# 建立服務 - 2
kubectl create service clusterip redis --tcp=6379:6379 --dry-run=client -o yaml
# 透過編輯器直接編輯物件
kubectl edit <object> <object_name>
```
+ 課程中建議在考試時使用 Imperative 可以省下一點時間
## YAML 基本架構說明
```yaml=
apiVersion: v1
kind: Pod # 物件名稱,通常大寫駝峰體
metadata:
name: *YOUR_RESOURCE_NAME* # 替這個物件命名
labels: # 遵循 Key Value 格式即可,可以用來編排
key1: value1
key2: value2
annotations: # 遵循 Key Value 格式即可
key3: value3
key4: value4
spec: # 不同種類的資源會有對應的規格
*以下依照資源而不同*
```
+ apiVersion :
+ 定義這個物件背後所使用的 Kubernetes API
+ Kubernetes 的物件是透過 API Group 的概念進行編排
+ 可以在 [K8S 官網](https://kubernetes.io/docs/reference/kubernetes-api/) 查詢每個物件隸屬的 API Group
+ 也可以透過 `kubectl api-resources` 直接查詢:


+ labels 和 annotation 的差別:
+ label 有識別意義,常見範例:
+ "release" : "stable", "release" : "canary"
+ "environment" : "dev", "environment" : "qa", "environment" : "production"
+ "tier" : "frontend", "tier" : "backend", "tier" : "cache"
+ "partition" : "customerA", "partition" : "customerB"
+ "track" : "daily", "track" : "weekly"
+ annotation 沒有識別意義,通常會放:
+ SHA 值
+ 發行時間
+ 發行版本
+ 負責人聯絡方式
+ 使用 kubectl 語法可以以不同的方式針對 label 進行 Query :
+ Equality-based requirement:
```bash
kubectl get pods -l environment=production,tier=frontend
```
+ Set-based requirement:
```bash
kubectl get pods -l 'environment in (production),tier in (frontend)'
```
# Pod
## 冷知識 - Pod 名稱的由來
:::spoiler 點我
:::info
A *pod* of whales!
:::
## Pod 的定義
+ Pod 是 K8S 中最基本的單位
+ 一個 Pod 可以存放 1 ~ N 個 Container (但通常只會有一個 Application)
+ Pod 中的 Container 是共享儲存空間和網路的 (shared storage and network resources)
+ Atomic Operations : 只有當 Pod 裡面的 Container 全部啟動成功,Pod 才會開始運行
## Pod 語法回顧
```yaml=
apiVersion: v1
kind: Pod
metadata: # Dictionary
name: *pod-name*
labels: # labels 中的內容可以自行定義 (遵循 Key Value 格式即可)
app: myapp
type: front-end
spec:
containers: # List/Array
- name: nginx-container # Container 1
image: nginx
command: ["/bin/sh"]
args: ["-c", "echo Hello from the container"]
- name: busybox # Container 2
image: busybox
command: ["sleep", "3600"]
```
## Pod 中到底要放幾個 Container ?
+ 大部分 Pod 中只會放一個 Container (Application)
+ 如果要在 Pod 裡面放多 Container ,通常會發生在Container的生命週期完全相同
+ 有幾種情境適合使用多個 Container :
+ 共同使用 Storage (透過 Shared-Volume):
+ Web Server & Log
+ Git Sync Sidecar
+ 需要這些 Container 被置於同樣的節點 : k8s 在分配任務時,是以 Pod 為單位配發至節點中。
+ Pod 中放多個 Container 通常有幾種 Design Pattern (CKAD):

1. **Sidecar Pattern**:
+ 一個主要的 Container + 輔助用的 Container
+ 就算輔助用的 Container 出問題也不會影響主要服務
+ Logging utilities / Sync services / Watcher / Monitor agents
2. **Adapter Pattern**:
+ 用來對 Application 的 output 進行標準化或正規化
+ 舉例來說,集群內的兩個 Application 分別有不同規格的 Output :
- AP1: `[DATE] - [HOST] - [DURATION]`
- AP2: `[HOST] - [START_DATE] - [END_DATE]`
+ 但是集群的監控工具要使用的格式是: `[AP1|AP2] - [HOST] - [DATE] - [DURATION]`
+ 如果要求 AP 團隊修改格式是可行,但很惹人厭。透過另外的 Container 來修改格式是比較適合的方式。
3. **Ambassador Pattern**:
+ 負責處理其他 Container 的連線,根據需求轉送至不同的環境
+ 範例: 開發連線至DB的功能時,AP 可以連線到 Ambassador (localhost),由 Ambassador Container 來決定要連線至哪個 DB (local/test/prod)
+ 如果今天某座 DB 的連線資訊調整了,其實不需要改 AP Container,只需要調整 Ambassador Container 即可。
## Infra Container
+ 在前面提到 Pod 中的 Container 是共享儲存空間和網路的。
+ 而在 Linux 中是透過 ===NameSpace=== 來實現 Container 的資源隔離,為了要共享網路,勢必是由某個 Container 加入另外一個 Container 的 ===NameSpace===,問題來了,怎麼決定誰加入誰呢?

:::spoiler 解答

+ **每一個 Pod 啟動時都會建立一個 Infra Container**,所有 User 定義的 Container 都會加入這個 Infra Cnotainer 的 ===NameSpace===。
+ Infra Container 通常會使用的 Image 是: `k8s.gcr.io/pause`,這個 Image 是用組合語言撰寫(容量很小),且永遠處在 paused state。
+ 在 kubelet 的設定中 `KUBELET_POD_INFRA_CONTAINER` 可以設定使用的 IMAGE (但通常不會調整)。
+ 這個 Container 所扮演的角色 :
1. 是建立基本的 Network Namespace,其他 Container 再加入 Infra Container 的 Namespace 中
2. Reaping Zombies: 收回已經停止運行但還存在 Process Table 的 Process
+ 實際上 Pod 的生命週期跟使用者定義的Container 無關,只跟 Infra Container 有關。
:::
+ [參考文件 - The Almighty Pause Container](https://www.ianlewis.org/en/almighty-pause-container)
+ [參考文件 - Namespace & cgroup](https://www.nginx.com/blog/what-are-namespaces-cgroups-how-do-they-work/)
# ReplicaSet
## Why Replication?
建立多個 Replication 有什麼好處?
+ **Reliability** : 如果一個 Instance 掛了,其他還能提供服務。
+ **Load balancing** : 可以將流量分別導入至不同的 Instance,避免單點承受太大的流量。
+ **Scaling**: 當流量太大時,可以設定條件來擴充 Instance 數量。
Pod 本身並不存在復原功能(刪掉就刪掉了),需要透過其他物件來監控 Pod 的數量。
在 K8S 的世界中,有兩個物件可以做到這件事情 : **Replication Controller** 和 **ReplicaSet**。 大部分好像都用 ReplicaSet 為主,但兩個都可以了解一下~
## Replication Controller
這是舊版管理 Replication 的工具 :
```yaml=
apiVersion: v1
kind: ReplicationController
metadata:
name: *object_name*
spec:
replicas: *replicas_number*
selector: # Replica 透過 Label 來識別 Pod
app: *your_label*
template:
metadata:
name: *pod_name*
labels:
app: *your_label* # 被識別的標籤
spec:
containers:
- name: nginx-container
image: nginx
ports:
- containerPort: 80
```
由 Replication Controller 來建立對應的 Pod,並且可以看到啟動的 Pod 名稱會是 RC 名稱加上亂碼的後綴

## ReplicaSet
ReplicaSet 相較於 Replication Controller 來說,提供了更精細的 Selector 語法
```yaml=
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
replicas: 3
selector:
matchLabels: # 選項 1 - 跟 RC 幾乎一樣
tier: frontend
matchExpressions: # 選項 2 - 提供較細緻的 Query 語法 (ANDed together)
- {key: app, operator: In, values: [soaktestrs, soaktestrs, soaktest]}
- {key: tier, operator: NotIn, values: [production]}
template:
metadata:
labels:
tier: frontend
spec:
containers:
- name: php-redis
image: gcr.io/google_samples/gb-frontend:v3
```
## 相關指令
1. 取得 ReplicaSet 相關資訊:
```
kubectl describe rs <rs_name>
```

+ desired: 目前這個 RS 預期要產生的 Pod 數量
+ current: 當前存在的 Pod 數量 (當數量不符合時,ReplicaSet 會自動增刪 Pod)
+ ready: 當前已經可以處於 Ready 狀態的 Pod 數量
2. 調整 Replica 數量 :
```bash
kubectl scale --replicas=7 rs <rs_name>
```
# Deployment
+ Pod 無法自我復原,透過 ReplicaSet 可以維持「特定數量」的 Pod 存在。
+ 但是如果 Pod 需要更新怎麼辦? Deployement 其實就是一種負責管理 ReplicaSet 的版本的物件,當 ReplicaSet 被更新,就會自動觸發 Pod 更新 :

+ 在 Deployment 中可以定義 Pod 的內容、更新的方式
```yaml=
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
strategy: # 更新的策略
type: Recreate
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
```
+ 當今天更新了 Deployment 時,會產生一版 (Division) 新的 Deployment : 
+ 如果今天 Deployment 更新了,會根據新版 Deployment 的內容去產生一個「新的」ReplicaSet,由「新的」ReplicaSet 去建立對應的 Pod
+ Deployment 更新後,原先的 ReplicaSet 不會被移除,保留這些 ReplicaSet 的目的是為了 "Rollback" 到舊版:
+ 更新前: 
+ 更新後: 
從第二張圖可以看到原先的 RS 並不會被移除,只是新版的 RS 被 Active (Desired > 0) 了!
+ Deployment 在更新的過程中,會記錄當前的狀態:

在 **Section 5 - Application Lifecycle** 中會講解到 Rolling Upgrade 和 Rollback 的方法。
## Deployment Strategy
在 "Deployment.spec.strategy" 中可以設定部署的策略 :
### 策略 1 - Recreate
```yaml
*前略*
spec:
replicas: 4
strategy:
type: Recreate
```
全部砍掉再全部重建 -> 會有 Downtime
### 策略 2 - RollingUpdate
```yaml
*前略*
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: *一次可以新增的Pod數量/比例(預設25%)*
maxUnavailable: *rollout過程中可允許Unavailble的數量/比例*
```
1. Ramped Slow Rollout:
+ maxSurge: 1
+ maxUnavailable: 0
2. Best-Effort Controlled Rollout:
+ maxSurge: 0
+ maxUnavailable: <特定比例>
+ 當 maxSurge 設定為 0 時,K8S 會盡量用最高效率去替換所有的 Pod
[Ref: 5 Kubernetes Deployment Strategies: Roll Out Like the Pros](https://spot.io/resources/kubernetes-autoscaling/5-kubernetes-deployment-strategies-roll-out-like-the-pros/)
# Services

+ K8S 中的 Pod 並不是永久存在的,會隨著 Deployment (ReplicaSet) 的判斷來增加/刪除 Pod。
+ 服務要與 Pod 進行互動時,無法靠 Pod IP 進行互動,因為 Pod 是可變動的。
因此需要透過 Service 來解耦服務間的相依關係:
+ Pod to Pod
+ End User to Pod
+ Pod to External Service

Kubernetes 提供了三種不同的 Service Type :
## 1. NodePort

在 Node 上開啟一個 Port (30000 ~ 32767),造訪這個 Port 的流量將會藉由 Service 導向 Pod。
使用 NodePort 後,**各節點**的 NodePort 都可以導向此 Service,此 Service 會再將連線導入至 Cluster 中的各 Pod (不管在哪個 Node) → 就算 Pod 長在 AB,但使用 CD 的 NodePort 還是可以連線到 Pod!
```yaml=
apiVersion: v1
kind: Service
metadata:
name: hello-service
spec:
type: NodePort
ports:
- port: 3000 # Service 暴露在 Cluster 上的 port (集群內部進入 Service 的 port)
nodePort: 30008 # 在 Node 上面開啟的 port
protocol: TCP
targetPort: 3000 # 在 Pod 上面開啟的 port
selector:
app: my-deployment # Service 將流量導向符合條件的 Pod 中
```

## 2. ClusterIP
在集群中替這個服務建立一組虛擬 IP,只要在集群內部就可以造訪到。
```yaml=
apiVersion: v1
kind: Service
metadata:
name: hello-service
spec:
type: ClusterIP
selector:
app: my-deployment
ports:
- port: 5000
targetPort: 5000
```
## 3. Load Balancer
Load Balancer type 是另一個讓外部可以直接存取 cluster 內部 service 的方式。
但目前只有雲服務有提供,在這堂課的範圍內沒有提到。
# NameSpaces
將集群內的劃分成不同的空間,同一個空間內的資源名稱具有唯一性。
## 預設的 NameSpace
+ default: 預設的 namespace (當沒有指定 ns 時,預設會帶入這個)
+ kube-system: k8s 系統的物件會儲存在這個空間 (api-server, core-dns...)
+ kube-public: 整個集群可讀取的資訊可以放在這裡(約定俗成)
## 跨 NameSpace 的連線命名原則
```
<obj_name>.<namespace_name>.svc.cluster.local
```
K8S 是怎麼做到的? 透過 `kube-dns` 這個服務來完成
如果我們去查看每個 Pod 裡面的 `/etc/resolv.conf` :

找一下這個 DNS Server 是什麼物件 :

從 IP 位置可以發現是 kube-dns 這個 Service,也就是說 K8S 在建立 Pod 時,就會自動在 Pod 中的 `/etc/resolv.conf` 注入 kube-dns 當作 DNS Server!
參考資料 1 : [[Kubernetes / K8s] Service之間互相溝通?namespace和介紹kube-dns(KIWI 大作)](https://b0444135.medium.com)
參考資料 2: Confluence - D01_DE Talk/01_技術講座/20210324 - Domain Name System
參考資料 3: Confluence - D01_DE Talk/01_技術講座/20220727 Anatomy of Linux DNS lookup
## 有支援 NameSpace 的物件
在 K8S 的物件中,其實不是所有物件都支援 NameSpace 的概念。
透過下列指令查看有支援 NameSpace 的物件 :
```bash
kubectl api-resources --namespaced=true
```

也有部分物件是不支援 NameSpace 的 :
```bash
kubectl api-resources --namespaced=false
```
例如 ClusterRoleBinding 是屬於集群層級的權限管理,就不會支援 NameSpace
## ResourceQuota
可以使用 ResourceQuota 來設定特定 NameSpace 的資源使用 :
```bash
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: dev
spec:
hard:
pods: "10"
requests.cpu: "4"
requests.memory: 5Gi
limits.cpu: "10"
limits.memory: 10Gi
```
+ 當 NameSpace 有設定 Resource Quota 時,會強制要求 Deployment 也要設定 Resource 資訊,不然無法建立。
https://stackoverflow.com/questions/58683871/how-to-add-custom-nameserver-under-etc-resolv-conf-into-pod
https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy