---
# System prepended metadata

title: Core Concept (下)
tags: [CKA]

---

[上集連結 - 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` 直接查詢:
        ![](https://i.imgur.com/qKPmSav.png)
        
        ![](https://i.imgur.com/jhy6vm6.png)


+ 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):
![](https://i.imgur.com/1U8fwEc.png)
    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===，問題來了，怎麼決定誰加入誰呢?

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


:::spoiler 解答
![](https://i.imgur.com/qWKKE3E.png)
+ **每一個 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 名稱加上亂碼的後綴
![](https://i.imgur.com/OJUVevs.png)

## 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>
   ```
   ![](https://i.imgur.com/tANboUG.png)
    + 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 更新 : 

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

+ 在 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 : ![](https://i.imgur.com/hAw3a5M.png)
+ 如果今天 Deployment 更新了，會根據新版 Deployment 的內容去產生一個「新的」ReplicaSet，由「新的」ReplicaSet 去建立對應的 Pod![](https://i.imgur.com/q0MqosG.png)
+ Deployment 更新後，原先的 ReplicaSet 不會被移除，保留這些 ReplicaSet 的目的是為了 "Rollback" 到舊版: 
    + 更新前: ![](https://i.imgur.com/HgvvoCy.png)
    + 更新後: ![](https://i.imgur.com/igLoSto.png)
        從第二張圖可以看到原先的 RS 並不會被移除，只是新版的 RS 被 Active (Desired > 0) 了！

+ Deployment 在更新的過程中，會記錄當前的狀態: 
![](https://i.imgur.com/ukfpYMS.png)


在 **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

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

+ K8S 中的 Pod 並不是永久存在的，會隨著 Deployment (ReplicaSet) 的判斷來增加/刪除 Pod。
+ 服務要與 Pod 進行互動時，無法靠 Pod IP 進行互動，因為 Pod 是可變動的。

因此需要透過 Service 來解耦服務間的相依關係: 

+ Pod to Pod 
+ End User to Pod
+ Pod to External Service

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

Kubernetes 提供了三種不同的 Service Type : 

## 1. NodePort
![](https://i.imgur.com/9KGTp2I.png)

在 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 中
```

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

## 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` : 

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

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

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

從 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 
```
![](https://i.imgur.com/eXbeTcR.png)

也有部分物件是不支援 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
