[上集連結 - 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