---
# System prepended metadata

title: ckad war 2 part 1
tags: [k8s]

---

上次我已經花了 1W 台幣，結果兩次考試都差1題的分數就可以拿到認證了...。

這次我一定要拿到認證，所以我這篇要用中文寫，並且好好記錄通過考試所需要的細節。
    
## 各位觀眾!!!

寫完這些文章加上兩周不認真的做題目
    
## 通過啦~~
    
![image](https://hackmd.io/_uploads/BkxvTsK4kl.png)

希望大家也都順利通過考試!

## 1. alias

因為考試的時間很短，題目又很多，又有部分的題目比較複雜，
所以第一步是把所有複雜的指令先使用 alias 簡化。

```bash
alias k=kubectl
```

常會用到、可以幫忙的指令
```bash
# 查看目前 k8s 各資源的 group、version 還有縮寫
k api-resources

# 建立某資源時，可使用的參數以及使用指令建立資源的範例
k create <resource> -h

# 查看某一個資源的詳細資訊，少用，裡面比較有用的資訊是 version
k explain pod

# 不要真的執行，只是檢查是否正確，可以使用 dry-run
k run mypod --image=nginx --dry-run=client

# dry run 搭配 -o yaml 可以直接產生 yaml 檔
k run mypod --image=nginx --dry-run=client -o yaml > mypod.yaml

# 計算有多少個某個資源
k get pods --all-namespaces | wc -l
k get pod -A | wc -l

# 計算有多少個某個資源，扣掉 header 的行數
k get pods --all-namespaces --no-headers | wc -l

# 在 Pod 中執行指令
k exec -it mypod -- /bin/bash
```

## 2. 綜觀 k8s 結構

- Master Node 是 k8s 的大腦，負責管理整個集群。
    - API Server : 提供 k8s 的 API 服務。       
        通常 yaml 文件位置是在 `/etc/kubernetes/manifests/kube-apiserver.yaml`。或是可以直接使用 `kubectl get pods -n kube-system` 查看。
    - Scheduler : 負責將 Pod 分配到 Node 上。
    - Controller Manager : 負責監控各種資源，包含 Pod、Node、Service 等。
    - etcd : 負責儲存 k8s 的資料。
- Worker Node 是 k8s 的手腳，負責執行工作。
    - Kubelet : 負責與 Master Node 通訊，接收和執行分配的 pod，並監控 pod 的狀態。
    - Kube Proxy : 負責網路代理與負載均衡，確保 pod 之間的網路通訊。
    - Container Runtime : 負責執行容器。
- Pod 是 k8s 的最小單位，是一個或多個容器的集合。而我們想要部屬的應用程式就是部屬在 Pod 上。而 Pod 的位置是由 Master Node 的 Scheduler 決定要部屬在哪個 Worker Node 上。
- Container 是一個獨立的運行單位，是一個獨立的虛擬機器。我們會將應用程式打包成容器，然後部屬到 Pod 上。

- 大致的圖如下，但不是很完整，只是為了有個概念。
```plantuml
@startuml

package "Master Node" {
    [API Server]
    [Scheduler]
    [Controller Manager]
    [etcd]
}

package "Worker Node" {
    [Kubelet]
    [Kube Proxy]
    [Container Runtime]
}

package "Pod" {
    [Container 1]
    [Container 2]
    [Container 3]
}

[API Server] --> [Scheduler] : Schedule Pods
[API Server] --> [Controller Manager] : Manage Cluster State
[API Server] --> [etcd] : Store Data
[Scheduler] --> [Kubelet] : Assign Pods
[Controller Manager] --> [Kubelet] : Monitor Pods
[Kubelet] --> [Container Runtime] : Manage Containers
[Kube Proxy] --> [Pod] : Network Proxy
[Pod] --> [Container 1] : Contains
[Pod] --> [Container 2] : Contains
[Pod] --> [Container 3] : Contains

@enduml
```

## 3. Pod - Core Concepts

Pod 是 k8s 的最小單位，是一個或多個容器的集合。而我們想要部屬的應用程式就是部屬在 Pod 上。

- 通常我們在一個 Pod 中只會放一個主要的應用程式(Container)。
- 如果因為流量增加，需要水平擴充，通常我們不會在同一個 Pod 中放多個 Container，而是增加 Pod 的數量。
- 但某些情況下我們可能會需要在同一個 Pod 中放多個 Container，這種情況通常是因為我們需要另外一個輔助的應用程式(Container)來幫助主要的應用程式(Container)，我們稱之為 Helper container。所以它們不會是同一個應用程式，而是兩個不同的應用程式。
- Pod 所使用的 apiVersion 是 v1。

### pod 常用的指令
```bash
# 創建 pod
kubectl run mypod --image=nginx

# 銃建 pod 並指定 port、env、label
kubectl run mypod --image=nginx --port 80 --env="key=value" --labels="app=myapp,type=frontend"

# 把 pod 藉由 ClusterIP service 暴露出去，並將這個 service 暴露在 8080 port、將這個 service 的名稱設定為 myservice
kubectl expose pod mypod --port=8080 --name=myservice

# 如果是使用 yaml 檔創建 pod。-f 是指定檔案的意思。
kubectl apply -f mypod.yaml

# 查看 pod 列表
kubectl get pods

# 查看某一個 pod 的詳細資訊
kubectl describe pod mypod

# 刪除 pod
kubectl delete pod mypod
```

- pod yaml 檔
```yaml
apiVersion: v1 # 看你要創建甚麼資源，決定要用哪個 apiVersion。要創建 Pod 就用 v1。
kind: Pod
metadata:
  name: mypod
  labels: # 這是一個 key-value 的對應陣列，用來標記 Pod。是 Optional 的。
    app: myapp
    type: frontend
spec:
  containers: # 這是一個陣列，裡面放的是要執行的容器。
  - name: mycontainer
    image: nginx
```

## 4. deployment - Core Concepts

Deployment 是用來管理 Pod 的控制器，它可以幫助我們管理 Pod 的數量，並且可以幫助我們做滾動更新。

- Deployment 支持滾動更新，讓我們在更新的時候並不會造成服務中斷。
- Ddeployment 支持回滾，如果更新失敗，可以回滾到之前的版本。
- Deployment 也可以更好的進行擴展或縮減 Pod 的數量。
- Deployment 還可以動修復，如果 Pod 異常，Deployment 會自動重啟 Pod。
- deployment 所使用的 apiVersion 是 apps/v1。

### deployment 常用的指令
```bash
# 創建 deployment
kubectl create deployment mydeploy --image=nginx --replicas=3 

# 銃建 deployment 並指定 port、env、label
kubectl create deployment mydeploy --image=nginx --port 80 --env="key=value" --labels="app=myapp,type=frontend" 

# 如果是使用 yaml 檔創建 deployment。-f 是指定檔案的意思。
kubectl apply -f mydeploy.yaml

# 查看 deployment 列表
kubectl get deployments

# 查看某一個 deployment 的詳細資訊
kubectl describe deployment mydeploy

# 刪除 deployment
kubectl delete deployment mydeploy

# 滾動更新，更新容器映像
kubectl set image deployment/mydeploy mycontainer=nginx:1.15.0

# 回滾 Deployment，是指回到上一個版本
kubectl rollout undo deployment/mydeploy

# 擴展 Deployment，是指增加 Pod 的數量
kubectl scale deployment mydeploy --replicas=5

# 縮減 Deployment，是指減少 Pod 的數量
kubectl scale deployment mydeploy --replicas=3
```

- deployment yaml 檔
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
  labels:
    app: myapp
    type: frontend
spec:
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: mycontainer
        image: nginx:1.14.2 # 定義容器映像
        ports:
        - containerPort: 80
  replicas: 3 # 定義期望的 Pod 副本數量
  selector:
    matchLabels:
      app: myapp
      type: frontend
```

> -o 是指定輸出的格式，json 或 yaml。例如：`kubectl get pods -o json` 或 `kubectl get pods -o yaml`。     
> -o wide 是指定輸出的格式，並且顯示更多的資訊。例如：`kubectl get pods -o wide`。      
> --dry-run=client 是指定不要真的執行，只是檢查是否正確。例如：`kubectl run mypod --image=nginx --dry-run=client`。

## 5. namespace - Core Concepts

Namespace 是用來區分不同的環境，讓不同的環境可以共存。

我們用個簡單的方式來解釋 namespace:

1. 現在有兩個人，兩個人的名字都叫小明。但這樣我們無法分辨誰是誰。
2. 但它們的姓氏不同，一個姓王，一個姓李。這兩個小明來自不同的家庭。
3. 在王小明的的家庭中，爸爸媽媽叫他們的小孩就直接叫小明。
4. 但在王家的爸爸媽媽如果要叫李家的小明，就要說李小明。
5. 而當不在王家，也不在李家的人要叫某一個小明，都要使用全名，例如：王小明、李小明。
6. 而這個家庭的概念，就是 namespace。

- 在 k8s 的 namespace 中，可能會有不同的資源，例如 : Pod、Service、Deployment 等。
- 當我們創立 k8s 的資源時，就會有一個預設的 namespace，這個 namespace 就是 default。
- 而當我們沒有指定 namespace 時，k8s 就會使用 default 這個 namespace。

### namespace 的 DNS

- 在 k8s 中，每個 namespace 都有自己的 DNS。
- 假設我現在有兩個 namespace，一個是 default，一個是 dev。
    - default 中有以下資源：
        - Pod : mypod
        - Service : db-service
    - dev 中有以下資源：
        - Pod : mypod
        - Service : db-service
- 當我在 default 的 Pod 中要連接 default 的 Service 時，可以直接使用 myservice 這個名稱。
    - 例如 : `mysql.connect("db-service")`
- 而當我在 default 的 Pod 中要連接 dev 的 Service 時，我就要使用以下的名稱。
    - 例如 : `mysql.connect("db-service.dev.svc.cluster.local")`
- 其中的 `cluster.local` 是 k8s 的預設 domain name，而 `svc` 是 subdomain name。再來就是 `dev` namespace 的名稱。

### 在指令中使用 namespace

- 當我們再使用 `kubectl get pods` 時，就會看到 default namespace 中的 Pod。完整的指令如下
```bash
kubectl get pods
kubectl get pods --namespace=default
```
- 而當我們想要查看 dev namespace 中的 Pod 時，就要使用以下的指令。
```bash
kubectl get pods --namespace=dev
```
- 而當我建立一個資源時，如果我沒有指定 namespace，k8s 就會使用 default 這個 namespace。
```bash
kubectl run mypod --image=nginx
```
- 而當我想要建立一個資源在 dev namespace 中時，就要使用以下的指令。
```bash
kubectl run mypod --image=nginx --namespace=dev
```
- 如果要改成使用 yaml 檔，就要在 yaml 檔中加上 namespace 的設定。
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
  namespace: dev # 指定 namespace
spec:
  containers:
  - name: mycontainer
    image: nginx
```

### namespace 常用的指令
```bash
# 查看 namespace 列表
kubectl get namespaces

# 查看某一個 namespace 的詳細資訊
kubectl describe namespace dev

# 創建 namespace
kubectl create namespace dev

# 刪除 namespace
kubectl delete namespace dev

# 永久修改預設的 namespace，讓每次都使用 dev 這個 namespace
kubectl config set-context --current --namespace=dev

# 取得所有 namespace 的資源
kubectl get all --all-namespaces
k get all -A

# 取得所有 namespace 的 Pod
kubectl get pods --all-namespaces

# 可以使用 namespace 的縮寫 ns
kubectl get ns
```

- 使用 yaml 檔創建 namespace
```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: dev
```

> 如果想要計算有多少個某個資源，可以使用 word count 的指令 `wc`，配合 line 的參數 `-l`。        
> 例如：`kubectl get pods --all-namespaces | wc -l`，就可以知道有多少個 Pod。       
但這樣的計算方式不太準確，因為會有一些 header 的資訊，所以要扣掉 header 的行數。例如：`kubectl get pods --all-namespaces | wc -l` - 1。     
> 如果有很多個 pod，我想要找到某一個叫做 mypod 的 pod，可以使用 grep 指令。grep 的全名是 global regular expression print，是一個強大的文本搜索工具。例如：`kubectl get pods --all-namespaces | grep mypod`。

> 這裡我們介紹一個方便的指令，讓我們測試或產生 yaml 檔。這個指令是 `kubectl run`。  
> 建立一個 pod 的指令如下：`kubectl run mypod --image=nginx`。
> 而當我們想要建立一個用來建立 pod 的 yaml 檔時，可以使用以下的指令：`kubectl run mypod --image=nginx --dry-run=client -o yaml > mypod.yaml`。

## 6. Configuration - image

在 k8s 中，我們使用 image 來建立容器。而 image 是一個包含應用程式的環境的輕量級、可移植的可執行軟體包。

那我們先來看一下 image 的結構。

- image 的名稱通常是由三個部分組成，分別是 : `registry`、`repository`、`tag`。
    - registry : 通常是一個網址，用來存放 image 的地方。例如 : `docker.io`。
    - repository : 通常是一個名稱，用來標記 image 的名稱。例如 : `nginx`。
    - tag : 通常是一個版本號，用來標記 image 的版本。例如 : `1.14.2`。
- image 的名稱通常是由這三個部分組成，並用冒號 `:` 分隔。例如 : `docker.io/nginx:1.14.2`。

### Dockerfile

- Dockerfile 是一個用來建立 image 的檔案，它是一個文本檔，裡面包含了一系列的指令，這些指令用來告訴 Docker 如何建立 image。

- 以下是一個簡單的 Dockerfile 範例。
```Dockerfile
# 使用哪一個 image 作為基礎
FROM Ubuntu:18.04

# 安裝一些套件
# apt-get update 是更新套件庫
RUN apt-get update
# 安裝 python
RUN apt-get install python
# 安裝 flask，這是一個 python 的 web 框架
RUN pip install flask
# 安裝 flask-mysql，這是一個 python 的 mysql 庫
RUN pip install flask-mysql

# 將本地端的檔案複製到 image 中，. 是源路徑，表示構建上下文的當前目錄。構建上下文是指在執行 docker build 命令時指定的目錄，通常是 Dockerfile 所在的目錄。
COPY . /opt/source-code

# 執行指令，這是告訴 Docker 在啟動容器時要執行的指令。
ENTRYPOINT FLASK_APP=/opt/source-code/app.py flask run
```

- 接著我們可以使用以下的指令來建立 image。
```bash
docker build Dockerfile -t mmumshad/my-custom-app:1.0
```
- 將 image 推送到 Docker Hub。
```bash
docker push mmumshad/my-custom-app:1.0
```
- 如 Dockerfile 中的指令，每一個指令都會建立一個新的 layer，每一層 layer 都會基於上一層 layer 進行疊加，最後形成一個 image。
- 而對 Docker 來說，每一層都是緩存的，當我們修改了 Dockerfile 中的某一個指令，Docker 就會重新建立這一層 layer，而不是重新建立整個 image。

### image 常用的指令
```bash
# 拉取 image
docker pull <image-name>:<tag>

# 建立 image，-t 是指定 image 的名稱和版本
# 如果 Dockerfile 就在當前路徑，可以直接使用 . 來指定 Dockerfile 的路徑
docker build Dockerfile -t <image-name>:<tag> <path_to_Dockerfile>

# 標記 image
docker tag <image-id> <image-name>:<tag>

# 推送 image 到 Docker Hub
docker push <image-name>:<tag> 

# 查看 image 列表
docker images

# 查看 image 是基於哪一個 operating system
docker run python:3.6 cat /etc/*release*

# 起動 image 並指定 port 及指定 container 的名稱
docker run -p 8080:80 --name mycontainer 

# 啟動 image 並讓她在背景執行
docker run -d <image-name>:<tag>

# 刪除 image
docker rmi <image-id>

# 查看 container 列表，其中只有運行中的 container
docker ps

# 查看 container 列表，包含停止的 container
docker ps -a

# 啟動 container
docker start <container-id>

# 停止 container
docker stop <container-id>

# 重啟 container
docker restart <container-id>

# 進入運行中的 container
docker exec -it <container-id> /bin/bash

# 刪除 container
docker rm <container-id>

# 查看 container 的 log
docker logs <container-id>

# 清理未使用的 image
docker image prune

# 清理未使用的 container
docker container prune

# 清理未使用的 volume
docker volume prune

# 清理未使用的 network
docker network prune

# 清理所有未使用的資源
docker system prune

```

## 7. ConfigMap - Configuration

- ConfigMap 是用來存放配置資訊的地方，例如 : 環境變數、設定檔等。
- ConfigMap 是一個 key-value 的對應陣列。
- ConfigMap 存在 etcd 中。
- 我們可以在 Pod 中指定使用某一個 ConfigMap，這樣一來，在 Pod 中的 Container 就可以使用這些配置資訊。
- 所有有關 ConfigMap 的操作有兩個步驟
    1. 創建 ConfigMap
    2. 在 Pod 中 inject ConfigMap

### 使用檔案建立 ConfigMap

- 假設我們有一個檔案 configMap.yaml，內容如下
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: myconfigmap
data:
  key1: value1
  key2: value2
```
- 接著我們可以使用以下的指令來創建 ConfigMap
```bash
kubectl apply -f configMap.yaml
```

### ConfigMap 常用的指令
```bash
# 創建 ConfigMap
kubectl create configmap myconfigmap --from-literal=key1=value1 --from-literal=key2=value2

# 查看 ConfigMap 列表
kubectl get configmaps

# 查看某一個 ConfigMap 的詳細資訊
kubectl describe configmap myconfigmap
```

### 在 Pod 中使用 ConfigMap

1. 假設我有一個 myconfigmap ConfigMap yaml 如下:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: myconfigmap
data:
  APP_COLOR: blue
  APP_MODE: prod
```
2. 使用以下指令建立這個 Configmap
```bash
kubectl create -f myconfigmap.yaml
```
2. 或直接使用 指令來產生以上內如的 ConfigMap
```bash
kubectl create configmap myconfigmap --from-literal=APP_COLOR=blue --drom-literal=APP_MODE=prod
```
3. 假設我有一個 mypod Pod yaml 如下:
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - image: nginx
    name: mycontainer
    ports:
    - containerPort: 80
    envFrom:
    - configMapRef:
        name: myconfigmap
```
4. 使用以下指令建立這個 Pod
```bash
kubectl create -f mypod.yaml
```
如此一來，當 Pod 啟動時，Container 就可以使用這些配置資訊。

如果我們想要使用 ConfigMap 中的某一個 key-value，可以使用以下的方式。
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - image: nginx
    name: mycontainer
    ports:
    - containerPort: 80
    env:
    - name: APP_COLOR
    valueFrom:
        configMapKeyRef:
        name: myconfigmap
        key: APP_COLOR
```
      
### practice

#### 5
##### Question
Update the environment variable on the POD to display a green background.


Note: Delete and recreate the POD. Only make the necessary changes. Do not modify the name of the Pod.

Pod Name: webapp-color

Label Name: webapp-color

Env: APP_COLOR=green

##### Answer

- 首先，因為你要修改一個既存的 pod，如果你改錯了，他就回直接回復不了，所以我們先把這個既存的 pod 輸出成 backup.yaml。
```bash
k get pod webapp-color -o yaml > webapp-color-backup.yaml
```
- 接著我們直接修改運行中的 pod。
```bash
k edit pod webapp-color
```
- 使用以上指令後，會直接在編輯器中開啟這個 pod 的 yaml 檔，輸入`i`進入編輯模式，然後找到 env 的部分，將 APP_COLOR 的值改成 green，然後按下`esc`，輸入`:wq`儲存並離開。
- 這時他會說以下的錯誤，告入你不能修改，但我們幫你把擬修改的檔案存在 `/tmp/kubectl-edit-2653598944.yaml`。
```bash
error: pods "webapp-color" is invalid
A copy of your changes has been stored to "/tmp/kubectl-edit-2653598944.yaml"
error: Edit cancelled, no valid changes were saved.
```
- 接著我們就使用 `replace` 指令，將這個 pod 的 yaml 檔替換成我們剛剛修改的 yaml 檔。
```bash
k replace --force -f /tmp/kubectl-edit-2653598944.yaml
```
- 以上這個指令會先刪除，然後再創建一個新的 pod。而這個過程可能會很慢，我們可以使用以下指令讓刪除及建立變快。
```bash
k delete pod webapp-color --grace-period=0 --force
k create -f /tmp/kubectl-edit-2653598944.yaml
```

## 8. Secret - Configuration

- Secret 是用來存放敏感資訊的地方，例如 : 密碼、金鑰等。
- 跟 ConfigMap 一樣，Secret 也是一個 key-value 的對應陣列。
- 而跟 ConfigMap 不同的是，Secret 的資料是經過加密的。
- 我們可以在 Pod 中指定使用某一個 Secret，這樣一來，在 Pod 中的 Container 就可以使用這些敏感資訊。
- 所有有關 Secret 的操作有兩個步驟
    1. 創建 Secret
    2. 在 Pod 中 inject Secret

### 使用檔案建立 Secret

- 我們要注意一個點，是如果我們使用檔案建立 Secret，那麼我們要先將檔案中的敏感資訊加密，然後再建立 Secret。
- 如果們的 username 是 admin，password 是 1f2d1e2e67df，我們可以使用以下的指令來加密。
```bash
echo -n 'admin' | base64
echo -n '1f2d1e2e67df' | base64
```
- 而當我們想要 decrypt 時，可以使用以下的指令。
```bash
echo -n 'YWRtaW4=' | base64 --decode
echo -n 'MWYyZDFlMmU2N2Rm' | base64 --decode
```
- 假設我們有一個檔案 secret.yaml，內容如下
```yaml
apiVersion: v1  
kind: Secret
metadata:
  name: mysecret
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm
```
- 接著我們可以使用以下的指令來創建 Secret
```bash
kubectl apply -f secret.yaml
```

### Secret 常用的指令
```bash
# 創建 Secret
kubectl create secret generic mysecret --from-literal=username=admin --from-literal=password=1f2d1e2e67df

# 查看 Secret 列表
kubectl get secrets

# 查看某一個 Secret 的詳細資訊
kubectl describe secret mysecret
```

### 在 Pod 中使用 Secret

1. 假設我有一個 mysecret Secret yaml 如下:
```yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm
```
2. 使用以下指令建立這個 Secret
```bash
k apply -f mysecret.yaml
```
3. 假設我有一個 mypod Pod yaml 如下:
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
 containers:
  - image: nginx
    name: mycontainer
    ports:
    - containerPort: 80
    envFrom:
    - secretRef:
        name: mysecret
```
4. 使用以下指令建立這個 Pod
```bash
k apply -f mypod.yaml
```
如此一來，當 Pod 啟動時，Container 就可以使用這些敏感資訊。

如果當我們指想要引用 secret 中的某一個 key-value，可以使用以下的方式。
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - image: nginx
    name: mycontainer
    env:
    - name: DB_USERNAME
    valueFrom:
        secretRef:
        name: mysecret
        key: username
```

> 要特別注意懺生 secret 時，要加 `generic`，這是用來指定 secret 的類型。例如：`kubectl create secret generic mysecret --from-literal=username=admin --from-literal=password=1f2d1e2e67df`。


> k8s 的 yaml 規則      
> 1. 使用空格進行縮進，通常是 2 個空格或 4 個空格。不要使用 Tab 鍵。
> 2. 縮進用於表示層級結構，子屬性應該比父屬性多縮進一層。
> 3. 每行表示一個鍵值對，格式為 key: value。冒號後面應
> 4. 列表項使用 - 開頭，並且 - 後面應該有一個空格。列表項應該與列表的父屬性對齊。
> 5. 字串值可以不加引號，但如果字串中包含特殊字符或空格，應該使用雙引號或單引號。
> 6. 使用 | 或 > 來表示多行字串。| 保留換行符，> 將換行符轉換為空格。

## 9. Security Context - Configuration

- Security Context 是用來設定 Pod 或 Container 的安全性設定。
- K8s 中的 Security Context 與 docker 中的 Security Context 其實該念差不多的。
- 如果在 pod 層級有設定 Security Context，那麼 Container 就會繼承這個設定。
- 如果 pod 層級和 Container 層級都有設定 Security Context，那麼 Container 層級的設定會覆蓋 pod 層級的設定。
- 我們可以設定以下的安全性設定
    - runAsUser : 設定 Container 的使用者。
    - runAsGroup : 設定 Container 的群組。
    - capabilities : 設定 Container 的權限。
    - readOnlyRootFilesystem : 設定 Container 的檔案系統是否唯讀。
    - allowPrivilegeEscalation : 設定 Container 是否允許提升權限。
    - privileged : 設定 Container 是否有 root 權限。
    - seLinuxOptions : 設定 Container 的 SELinux 選項。
    - fsGroup : 設定 Container 的檔案系統群組。
    - sysctls : 設定 Container 的 sysctl 設定。

### 在 Pod 中設定 Security Context

1. 假設我有一個 mypod Pod yaml 如下:
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: multi-pod
spec:
  securityContext: # 設定 Pod 的安全性設定
    runAsUser: 1001
  containers:
  - image: ubuntu
    name: web
    command: ["sleep", "5000"]
    securityContext: # 設定 Container 的安全性設定，會覆蓋 Pod 的安全性設定
     runAsUser: 1002
     capabilities: # 這個配置只能在 Container 層級設定
       add: ["MAC_ADMIN", "MAC_RAW"]
  - image: ubuntu
    name: sidecar
    command: ["sleep", "5000"]
```
- 因為是 list，所以也可以寫成以下這樣
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: multi-pod
spec:
  securityContext: # 設定 Pod 的安全性設定
    runAsUser: 1001
  containers:
  - image: ubuntu
    name: web
    command: 
    - "sleep"
    - "5000"
    securityContext: # 設定 Container 的安全性設定，會覆蓋 Pod 的安全性設定
      runAsUser: 1002
      capabilities: # 這個配置只能在 Container 層級設定
        add: 
        - "MAC_ADMIN"
        - "MAC_RAW"
  - image: ubuntu
    name: sidecar
    command: 
    - "sleep"
    - "5000"
```

### Practice

#### 1

##### Question

What is the user used to execute the sleep process within the ubuntu-sleeper pod?

In the current(default) namespace.

##### Answer

- 可以使用 `kubectl exec` 指令來進入 pod 中，然後使用 `whoami` 指令來查看目前使用者。
```bash
kubectl exec -it ubuntu-sleeper -- /bin/bash
whoami
# or
kubectl exec -it ubuntu-sleeper -- whoami
```

#### 5

##### Question

Update pod ubuntu-sleeper to run as Root user and with the SYS_TIME capability.


Note: Only make the necessary changes. Do not modify the name of the pod.

Pod Name: ubuntu-sleeper

Image Name: ubuntu

SecurityContext: Capability SYS_TIME

Is run as a root user?

##### Answer

securityContext 底下的 capabilities 是用來設定 Container 的權限，只能寫在 container 層級，不能寫在 pod 層級。

## 10. Service Account - Configuration(有點新舊版的問題沒有搞清楚)

在 k8s 中，有兩種 Account，分別是 User Account 和 Service Account。
- User Account 是用來管理使用者(人類)的帳號，例如 : admin、developer 等。讓這些使用者可以存取 k8s 的 API。
- Service Account 是用來管理服務(應用程式)的帳號，例如 : default、system 等。讓這些服務可以存取 k8s 的 API。
- 當我們建立一個 service account 時，k8s 會自動建立一個 secret，並把這個 secret 授權給這個 service account。而當我們要透過這個 service account 存取 k8s 的 API 時，就會驗證這個 secret。
- 如果有外部的服務要存取 k8s 的 API，我們可以使用 service account 來授權這個服務。甚至直接帶上這個 service account 的 token 來存取 k8s 的 API。
- 那麼當有 k8s 內部的服務要存取 k8s 的 API 時，我們就可以使用 service account 來授權這個服務。也可以直接使用 volume mount 的方式來存取這個 service account 的 token。(這個後面會講到)
- 當我們初始化一個 k8s 的 cluster 時，k8s 會自動建立一個 service account，這個 service account 的名稱是 default，而這個 service account 會被自動授權給所有的 pod。
- 當我們創建一個 pod 時，如果我們沒有指定 service account，k8s 就會使用 default 這個 service account 給這個 pod。
- 不可以修改既存 pod 中的 service account，只能在創建 pod 時指定 service account。所以當我們要修改 pod 中的 service account 時，只能刪除 pod，然後再創建一個新的 pod。

### 1.24 版本之前的 service account

- 在 1.24 版本之前，k8s 建立 service account 後，就會自動產生對應 secret，內含一個沒有過期時間的 token。
- 用圖來看的話就是 service account 中定義連接的 secret，而 secret 中包含 token。

### 1.24 版本之後的 service account

- 在 1.24 版本之後，k8s建立 service account 後，並不會自動產生對應的 secret。
- 我們需要使用以下指令直接產生 token。但這個 token 是有過期時間的。而且只有一小時的有效期。
```bash
kubectl create token <service-account-name>
```
- <span style="color:red">但這個 token 跟 service account 是如何連接的呢？定義在哪裡?</span>
- 如果你需要像 1.24 版本之前一樣，產生一個沒有時效性的 token 提供給某一個 service account，你可以這樣做。       
    1. 建立一個 service account
    ```bash
    k create sa myservicaccountwithunlimitedtoken
    ```
    2. 建立 secret <span style="color:red">token哩?</span>
```yaml
apiVersion: v1
kind: Secret
metadata:
  name: secretname
  annotations:
    kubernetes.io/service-account.name: myservicaccountwithunlimitedtoken
```

### Service Account 常用的指令
- service account 簡寫是 sa
```bash
# 建立 Service Account
kubectl create serviceaccount myserviceaccount

# 查看 Service Account 列表
kubectl get serviceaccounts

# 查看某一個 Service Account 的詳細資訊
kubectl describe serviceaccount myserviceaccount

# 產生一個對應到這個 Service Account 的 secret
kubectl create token <service-account-name>
```

### Practice

#### 13

##### Question
You shouldn't have to copy and paste the token each time. The Dashboard application is programmed to read token from the secret mount location. However currently, the default service account is mounted. Update the deployment to use the newly created ServiceAccount

Edit the deployment to change ServiceAccount from default to dashboard-sa.

Deployment name: web-dashboard

Service Account: dashboard-sa

Deployment Ready

##### Answer

- 首先，我們要先將這個 deployment 輸出成 yaml 檔。
```bash
k get deployment web-dashboard -o yaml > web-dashboard.yaml
```
- 接著我們直接修改運行中的 deployment。
```bash
k edit deployment web-dashboard
```
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-dashboard
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      name: web-dashboard
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        name: web-dashboard
    spec:
      serviceAccountName: dashboard-sa # 加上這行，沒有這行的時候是使用 default 這個 service account
      containers:
      - image: gcr.io/kodekloud/customimage/my-kubernetes-dashboard
        imagePullPolicy: Always
        name: web-dashboard
        ports:
        - containerPort: 8080
          protocol: TCP
```
- 接著使用以下指令強制取代這個 deployment。
```bash
k replace --force --grace-period=0 -f web-dashboard.yaml
```

## 11. Resource Requirements - Configuration

- 在 pod 中，我們可以設定資源的需求和限制。
- 資源的需求是指這個 pod 運行時所需要的資源，例如 : CPU、Memory 等。
- 資源的需求指的是當這個 pod 在執行時，所需要的最低資源量。
- 資源的限制是指這個 pod 運行時所能使用的最大資源量。
- 我們舉一些例子

|   | No request | Has Request |
|---| --- | --- |
| No Limit | - 沒有最低需求、沒有最高限制<br>- 可能會有某一個 pod 把所有資源都搶去用的狀況，<br>- 造成別的 pod 無法正常運作 | - 有最低需求，但不限制最高使用量<br>- 這是最理想的設定 |
| Has Limit | - 沒有最低需求，但有最高限制<br>- 在這個情況下，k8s 會自動將 request 設定和 limit 一樣 | - 有最低需求，也有最高使用量<br>- 這是最保險的設定 |

### 在 Pod 中設定 Resource Requirements - LimitRange

- 設定某一個 pod 中的 container 的資源需求和限制，可以使用以下的方式。
- doc 要搜尋 pod resources
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
  labels:
    app: myapp
spec:
  containers:
  - name: mycontainer
    image: nginx
    ports:
    - containerPort: 80
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
```
- 每個 pod 都需要被設定，那萬一我有非常多 pod，那不是很麻煩嗎?
- LimitRange 可以協助我們設定這些資源需求和限制。他的層級是 cod 或 container 級別的控制。g只要在指定的 namespace 中建立一個 LimitRange，這個 namespace 中的所有 pod 都會受到這個 LimitRange 的限制。
- doc 要搜尋 limitrange，但官方文件都只有 cpu，但其實也可以 limit memory
```yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: cpu-resource-constraint
spec:
  limits:
  - default: # limit
      memory: 512Mi
      cpu: 500m
    defaultRequest: # request
      memory: 256Mi
      cpu: 250m
    max: # limit
      memory: 1Gi
      cpu: 1
    min: # request
      memory: 64Mi
      cpu: 100m
    type: Container
```
```yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: mem-resource-constraint
spec:
    limits:
    - default: # limit
        memory: 1Gi
      defaultRequest: # request
        memory: 256Mi
      max: # limit
        memory: 1Gi
      min: # request
        memory: 64Mi
      type: Container
```

> limitrange 無法直接使用 command 來建立，必須使用 yaml 檔來建立。
> 當資源不足而導致無法啟動時，pod 的 state reason 會顯示為 OOMKilled，就是  ran out of memory

### 限制整個 namespace 的資源需求和限制 - ResourceQuota

- ResourceQuota 可以用來限制整個 namespace 的資源總和需求和限制。
- 他的層級跟 LimitRange 是不一樣的，LimitRange 控制 namespace 中的某個 pod 或 container，而 ResourceQuota 控制整個 namespace 中所有的 pod 或 container 加起來的總資源需求和限制。
- 只要在指定的 namespace 中建立一個 ResourceQuota，這個 namespace 中的所有 pod 使用的資源總合都不能超過這個 ResourceQuota 的設定。
```yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: mem-cpu-demo
spec:
  hard:
    requests.cpu: "1"
    requests.memory: 1Gi
    limits.cpu: "2"
    limits.memory: 2Gi
```

> 使用 command line 來建立 resourcequota
```bash
kubectl create resourcequota myresourcequota --hard=requests.cpu=1,requests.memory=1Gi,limits.cpu=2,limits.memory=2Gi
```

## 12. Taints and Tolerations - Configuration

- Cluster:      
    集群是 k8s 中最高層級的單位，包含了多個 node。
- Namespace:      
    命名空間是 k8s 中在 cluster 中的分割機制，在同一個 cluster 中，將不同的資源放在不同的 namespace 中，可以達到隔離的效果。
- Node:     
    節點是 k8s 中的一個物理或虛擬機器，是 k8s 中運行 pod 的地方。主要有以下兩種節點
    - Master Node : 主節點，負責管理整個 cluster。會透過以下的組件來管理 cluster
        - API Server : 提供 k8s 的 API。
        - Scheduler : 負責將 pod 分配到 node 上。
        - Controller Manager : 負責管理控制器。
        - etcd : 負責存放 k8s 的資料。
    - Worker Node : 工作節點，負責運行 pod。
- Pod:
    pod 是 k8s 中最小的單位，是一個或多個 container 的集合。pod 是 k8s 中最小的調度單位，由 Master Node 的 Scheduler 負責將 pod 分配到 node 上。
- Container:
    container 是 pod 中的一個單位，pod 可以包含一個或多個 container。
- Controller:
    控制器是 k8s 中的一個組件，負責管理 pod 的生命週期。包含但不只以下幾種控制器        
    - ReplicaSet : pod 的副本數量。        
    - Deployment : pod 的部署。           
    - Job : 確保 pod 的工作完成。           
    - CronJob : 確保 pod 在指定的時間執行。

這章節主要是講解 Taints and Tolerations，這是 k8s 中的一個機制，用來控制 pod 能否被分配到 node 上。也就是讓 master node 的 scheduler 能夠更好的調度 pod。

### Taint

- Taint 的中文是汙點，與其對應的設定是 toleration，所以我們可以用一個概念來解釋這個機制
    - 當 node 1 被 taint A 汙染，那就只有擁有 toleration A 的 pod 可以在 node 1 中生存
- Taint 是 node 上的一個標記，用來表示這個 node 的特性。
- 我們舉個例子      
    - 現在我們有 3 個 node，分別是 node1、node2、node3。        
        - node1 加上 taint A
        - node2 加上 taint B
        - node3 加上 taint C
    - 有三個 pod，分別是 pod1、pod2、pod3。     
        - pod1 加上 toleration A，可以忍受被 taint A 污染的 node
        - pod2 加上 toleration B，可以忍受被 taint B 污染的 node
        - pod3 加上 toleration C，可以忍受被 taint C 污染的 node
    - 當 master node 的 scheduler 要將 pod 分配到 node 上時，會先檢查 pod 的 toleration 是否符合 node 的 taint。        
        - 如果 pod 上的 toleration 與 node 上的 taint 不符合，那麼這個 pod 就不會被分配到這個 node 上，因為他無法容忍(toleration)該 node 上的污漬(taint)
        - 如果 pod 上的 toleration 與 node 上的 taint 符合，但表該 pod 可以容忍(toleration)該 node 的污漬(taint)，那麼這個 pod 就會被分配到這個 node 上。
    - 而若 node 跟 pod 都沒有被標註 taint 或 toleration，那麼就由 master node 的 scheduler 來自由分配。
- Taint 有三個屬性
    - key
    - value
    - effect : 用來標記 node 的特性。
        - NoSchedule : 不允許新的 Pod 被排程到打上此污點的節點上。但是已經在節點上的 Pod 不會受到影響。
        - PreferNoSchedule : 盡量避免將新的 Pod 排程到打上此污點的節點上，但不強制。優先分配到其他 node。除非其他 node 上更好的資源，才會分配到這個 node。
        - NoExecute : 不僅禁止新的 Pod 被排程到該節點，還會驅逐目前已經在此節點上的不相容 Pod。並將該 pod kill 掉。

#### taint 常用的指令

```bash
# 如果不記得 taint 的指令可以使用
k taint node -h

# 添加 taint 到 node 上
kubectl taint nodes <node-name> key=value:effect
kubectl taint nodes node1 app=blue:NoSchedule

# 查看 node 上的 taint
kubectl describe node <node-name>

# 移除 node 上的 taint
kubectl taint nodes <node-name> key:effect-
kubectl taint nodes node1 app:NoSchedule-
```


### Tolerations

- Tolerations 是 pod 的一個屬性，用來表示這個 pod 能夠容忍的 taint。
- Tolerations 有四個屬性
    - key
    - value
    - effect
    - operator : 用來表示 key 和 value 的關係。
        - Exists : 表示 key 存在即可。
        - Equal : 表示 key 和 value 要完全相等。
- 我們可以使用以下的方式來設定 toleration
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
    containers:
    - name: mycontainer
        image: nginx
    tolerations:
    - key: "app"
      value: "blue"
      effect: "NoSchedule"
      operator: "Equal"
```

> 要注意的是 tolerations 中的四個屬性都必須要使用引號包起來。 其實我試過了，不用引號也可以。      

> taint 和 toleration 並不是告訴 pod 要去哪個 node，而是告訴 pod 哪些 node 是不適合的。

> 可以使用 `--help` 來查看你用的指令有甚麼參數可以使用。        
> 例如建立 pod 的時候，可以使用 `kubectl run --help` 來查看有哪些參數可以使用。

## 13. Node Selector - Configuration

- Node Selector 是 k8s 中用來將 pod 分配到指定 node 上的一個機制。
- 首先我們要先在 node 上設定 label，然後在 pod 中設定 nodeSelector。讓兩者符合，而 master node 的 scheduler 就會將 pod 分配到這個 node 上。

### 相關及常用指令

```bash
# 在 node 上加上 label
kubectl label nodes <node-name> <label-key>=<label-value>

# 查看 node 上的 label
kubectl describe node <node-name>
```

- 在 pod 中設定 nodeSelector
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  nodeSelector:
    disktype: ssd
  containers:
  - name: mycontainer
    image: nginx
```

- 然而， node selector 無法完成反面邏輯，例如我想要將 pod 分配到除了 node1 以外的 node 上，這時就需要使用 node affinity。

## 14. Node Affinity - Configuration

- Node Affinity 是 k8s 中用來將 pod 分配到指定 node 上的一個機制。
- Node Affinity 可以完成 node selector 做不到的事情，例如反面邏輯。

### Node Affinity 的兩種類型

- requiredDuringSchedulingIgnoredDuringExecution （強制調度，忽略執行期變更）       
這種類型定義了 強制條件，Pod 只會被調度到符合條件的節點上。若沒有節點符合條件，Pod 將無法被調度，也不會啟動。這個條件只在 Pod 調度期間 生效，調度完成後，節點的變化（如標籤被修改或移除）將不會影響 Pod 的運行，這就是「忽略執行期變更」的意思。
    - 範例
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/e2e-az-name
            operator: In
            values:
            - e2e-az1
            - e2e-az2
```
- preferredDuringSchedulingIgnoredDuringExecution （偏好調度，忽略執行期變更）      
這種類型定義了 偏好條件，Kubernetes 調度器將「嘗試」將 Pod 調度到符合條件的節點上。如果找不到符合條件的節點，Pod 仍會被調度到其他節點上。因此，這是一個「軟性」的約束，Pod 不必一定滿足這些條件。
    - 範例
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1 # 表示權重，權重的意思是如果有多個 node 符合這個條件，那麼會選擇權重最高的 node。
        preference:
          matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
```

### yaml 範例

- 假設現在我們有 3 個 node，分別是 node1、node2、node3。而他們分別有以下的 label
    - node1 : disksize=large
    - node2 : disksize=medium
    - node3 : disksize=small
- 現在我們有一個 pod，我們希望這個 pod 能夠被分配到 disksize=medium 的 node 上，這時我們可以使用以下的 yaml 檔。
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disksize
            operator: In
            values:
            - medium
  containers:
  - name: mycontainer
    image: nginx
```

- operator 有以下幾種
  - In      
    - 意思：Pod 調度器會將 Pod 調度到那些節點標籤值位於指定值列表中的節點上。
    - 用法：指定的 key 必須有一個值，且該值必須存在於 values 列表中。
    - 範例：
    ```yaml
    matchExpressions:
    - key: disksize
      operator: In
      values:
      - medium
      - large
    ```     
    - 解釋：只有那些 disksize 標籤值是 medium 或 large 的節點才會符合條件，Pod 才會被調度到這些節點。
  - NotIn
    - 意思：Pod 調度器會避免將 Pod 調度到節點標籤值位於指定值列表中的節點上。
    - 用法：指定的 key 必須有一個值，且該值不得存在於 values 列表中。
    - 範例：
    ```yaml
    matchExpressions:
    - key: disksize
      operator: NotIn
      values:
      - small
    ``` 
    - 解釋：disksize 標籤值為 small 的節點將被排除，不符合條件，Pod 不會被調度到這些節點。

  - Exists
    - 意思：Pod 調度器會將 Pod 調度到那些擁有指定標籤 key 的節點上，無論 key 的值為何。
    - 用法：只需指定 key，不需要指定 values。
    - 範例：
    ```yaml
    matchExpressions:
    - key: disktype
      operator: Exists
    ```
    - 解釋：任何具有 disktype 標籤的節點都符合條件，不論標籤的值是什麼，Pod 都可以被調度到這些節點上。

  - DoesNotExist

    - 意思：Pod 調度器會將 Pod 調度到那些不包含指定標籤 key 的節點上。
    - 用法：只需指定 key，不需要指定 values。
    - 範例：
    ```yaml
    matchExpressions:
    - key: disktype
      operator: DoesNotExist
    ```
    - 解釋：不具有 disktype 標籤的節點才會符合條件，Pod 會被調度到這些節點。

  - Gt（Greater than）

    - 意思：Pod 調度器會將 Pod 調度到那些標籤值大於指定數值的節點上。
    - 用法：key 的值必須是可比較的數值類型，並且該值要大於指定的數值。
    - 範例：
    ```yaml
    matchExpressions:
    - key: cpucores
      operator: Gt
      values:
      - "4"
    ```
    - 解釋：只有 cpucores 標籤值大於 4 的節點才會符合條件。

  - Lt（Less than）

    - 意思：Pod 調度器會將 Pod 調度到那些標籤值小於指定數值的節點上。
    - 用法：key 的值必須是可比較的數值類型，並且該值要小於指定的數值。
    - 範例：
    ```yaml
    matchExpressions:
    - key: memory
      operator: Lt
      values:
      - "16Gi"
    ```
    - 解釋：只有 memory 標籤值小於 16Gi 的節點才會符合條件。

- 如果有一個 pod，我們希望這個 pod 能夠被分配到除了 disksize=medium 的 node 以外的 node 上，這時我們可以使用以下的 yaml 檔。
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disksize
            operator: NotIn
            values:
            - medium
  containers:
  - name: mycontainer
    image: nginx
```

### Practice

- 練習時所使用到的 yaml 檔
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: blue
  name: blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: blue
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: blue
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: color
                operator: In
                values:
                - blue
      containers:
      - image: nginx
        imagePullPolicy: Always
        name: nginx
        resources: {}
status: {}
```
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: red
  name: red
spec:
  replicas: 2
  selector:
    matchLabels:
      app: red
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: red
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: node-role.kubernetes.io/control-plane
                operator: Exists
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}
```

> 考試省時小技巧        
> `export ns=default` - 指定 namespace 為 default
> `alias k=kubectl -n $ns` - 這個指令可以幫助你快速的指定 namespace
> `alias kdr='kubectl -n $ns -o yaml --dry-run=client'` - 這個指令可以幫助你快速的輸出 yaml 檔，並且不會真的創建物件。

## 15. Multi-container Pods - Multi-Container Pods

- 在 pod 中，我們可以放入多個 container，這樣的 pod 就叫做 multi-container pod。
- 在一般的情況下，一個 pod 我們只會放入一個 container，但是有時候我們會需要多個 container 來協同工作。
- 而這些多放入的 container 通常是為了完成某一個任務。同常會有以下三種
    - sidecar container : 輔助完成一些功能，例如 log、monitoring 等。
    - adapter container : 將主要 container 的輸出轉成適合其他系統的輸出。
    - ambassador container : ambassador 的意思是大使，這種輔助 container 是幫助主要 container 與外部不同的系統溝通。例如在不同環境需要連接不同的資料庫，就可以將連接資料的工作外包給 ambassador container。

### 範例 yaml 檔

```yaml
apiVersion: v1
kind: Pod
metadate:
  name: mypod
spec:
  containers:
  - name: mycontainer1
    image: nginx
  - name: mycontainer2
    image: busybox
```

## 16. Init Containers - Multi-Container Pods

- Init Containers 是 k8s 中的一個機制，用來在 pod 中執行初始化工作。
- Init Containers 的配置是直接寫在 pod 的 spec.initContainers 底下。

### 範例 yaml 檔

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mycontainer
    image: nginx
  initContainers:
  - name: init-myservice
    image: busybox
    command:
    - echo
    - "init container is running"
    - sleep
    - -c
    - "3600"
```

## 17. Readiness and Liveness Probes - Observability

- [Official doc](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)
- 我們先來了解一下 Pod 的生命週期 states
    - Pending : Pod 已經被建立，container 尚未建立，master node 的 scheduler 正在尋找適合的 node。此時可以使用 `deacribe` 指令來查看為什麼 pending。
    - ContainerCreating : Pod 已經被建立，且 master node 的 scheduler 已經找到適合的 node，而 container 正在建立中。
    - Running : 當前述的工作都完成了，且 Pod 內所有的 containers 都啟動了。
- Pod 還有 Conditions，他是 boolean 值，用來表示 Pod 的狀態。
    - PodScheduled : Pod 是否已經被調度到 node 上。
    - Initialized : Pod 是否已經初始化。
    - ContainersReady : Pod 中的 container 是否已經準備好接收流量。
    - Ready : Pod 是否已經準備好接收流量。

### 為什麼我們需要 Readiness 和 Liveness Probes?

- 我們來假設一個情況
    - 當我們有一個 ready 的 pod。
    - 我們使用一個 service 來將這個 pod 公開給外部使用。
    - 當你創建了這個 service，這個 service 會立即將流量導向這個 pod。
    - 在 k8s 的 defualt 狀況下，只要 pod 中的 container 一準備好，就可以接收流量並提供服務。所以他會將每個 container 的 ready condition 設定為 true。(而實際上， container 的服務可能還在啟動中)
    - 但如果這個 pod 中的 container 需要一段時間才能完全啟動，而 service 卻立即將流量導向這個 pod，那麼這個 pod 就會提供不了服務。
    - 這時我們就需要使用 readiness probe 來告訴 k8s 這個 pod 中的 container 是否已經準備好接收流量。
- 再來舉一個情況
    - 當我們已經有一個 deployment，其中的 replica 是 2。
    - 與其對應的 service 也已經持續的將流量導向這兩個 pod。目前一切工作正常
    - 而因為流量稱增加，我們想要增加 replica 的數量，這時我們就會增加 replica 的數量成為 3。
    - 若是這個新的 pod 需要一段時間才能完全啟動，而我們沒有設定 readiness probe，流量會被直接倒向這個新的 pod，而這個新的 pod 還正在 warm up，這樣就會造成部分的流量無法提供服務。

### Readiness Probes

- 開發者可能更清楚我們 container 所包的 app 在怎樣的情況下是 ready 的、可以接收流量，所以我們可以使用 readiness probe 來告訴 k8s 這個 container 是否 ready。
- 當我們設定的 Readiness Probe 在 Pod 中，k8s 不會立即將 container 的 ready condition 設定為 true，而是等到 readiness probe 的條件符合時才會將 ready condition 設定為 true。
- readiness probe 有以下三種
    - httpGet : 用來檢查 container 是否 ready 的方式是透過 http request。
    - exec : 用來檢查 container 是否 ready 的方式是透過執行一個 command。
    - tcpSocket : 用來檢查 container 是否 ready 的方式是透過 tcp request。

#### Readiness Probes 的設定

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mycontainer
    image: nginx
    readinessProbe:
      httpGet:
        path: /healthz
        port: 80
      initialDelaySeconds: 5 # 5 秒後開始檢查
      periodSeconds: 5 # 每 5 秒檢查一次
      failureThreshold: 3 # 如果連續 3 次檢查失敗，則將 ready condition 設定為 false，就不會接收流量
```

- 三種 readiness probe 的設定
    - httpGet
    ```yaml
    readinessProbe:
      httpGet:
        path: /healthz
        port: 80
    ```
    - exec
    ```yaml
    readinessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
    ```
    - tcpSocket
    ```yaml
    readinessProbe:
      tcpSocket:
        port: 80
    ```

### Liveness Probes

- 當初於某些錯誤或當機時，我們的 container 會自動重啟，這是因為 k8s 會定期檢查 container 是否 alive，如果 container 不 alive，那麼 k8s 就會將 container 重啟。
- 然而，有時候 container 會因為某些原因而無法提供服務，但是 k8s 卻認為他是 alive，這時我們就需要使用 liveness probe 來告訴 k8s 這個 container 是否 alive。
- Liveness probe 可以定期測試 container 是否 alive，如果 container 不 alive，那麼 k8s 就會將 container 重啟。

#### Liveness Probes 的設定

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mycontainer
    image: nginx
    livenessProbe:
      httpGet:
        path: /healthz
        port: 80
      initialDelaySeconds: 5 # 啟動後，等待五秒再開始進行探測
      periodSeconds: 5 # 每五秒探測一次
      failureThreshold: 3 # 如果連續探測失敗三次，則代表認定這個 container 失敗
```

- 三種 liveness probe 的設定
    - httpGet
    ```yaml
    livenessProbe:
      httpGet:
        path: /healthz
        port: 80
    ```
    - exec
    ```yaml
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
    ```
    - tcpSocket
    ```yaml
    livenessProbe:
      tcpSocket:
        port: 80
    ```

## 18. Container logging - Observability

- 在 docker 中，我們可以使用 `docker logs` 來查看 container 的 log。
    - `docker logs <container-id>`
    - `-f` : 跟蹤最新的 log `docker logs -f <container-id>`
- 而在 k8s 中，我們可以使用 `kubectl logs` 來查看 pod 中 container 的 log。
    - `kubectl logs <pod-name>`
    - `-f` : 跟蹤最新的 log `kubectl logs -f <pod-name>`
    - `-c` : 指定 container `kubectl logs -c <container-name> <pod-name>`
    - `--tail` : 顯示最後幾行的 log `kubectl logs --tail=10 <pod-name>`
    - `--since` : 顯示自從多久以前的 log `kubectl logs --since=1h <pod-name>`
- 如果在 pod 中有多個 container，那麼我們就需要在 `<pod-name>` 後面加上 `<container-name>` 或 `-c <container-name>` 來指定要查看的 container。      
    ```bash
    kubectl logs <pod-name> -c <container-name>
    ```

##  19. Monitoring and Debug Applications - Observability

- 有多種工具可以達到監控及除錯的目的
    - Metrics Server
    - Prometheus
    - Elasti Stack
    - Datadog
    - Dynatrace
- 本章主要講解 Metrics Server
- Heapster 是 k8s 中的一個組件，用來監控 k8s 中的資源使用情況。但是 Heapster 已經被淘汰，取而代之的是精簡版的 Metrics Server。
- 每一個 Cluster 都會有一個 Metrics Server，用來監控 Cluster 中的資源使用情況。
- Metrics Server 會監視 Cluster 中的 nodes、pods，並將資源使用情況儲存在 memory 中，並提供 API 供 k8s 的其他組件使用。
- 要注意 Metrics Server 指會將資料存在 memory 中，並不會存在 disk 中，所以當 Metrics Server 重啟時，資料就會消失。

### Metrics Server 如何運作

- Kubernetes 在每個節點上都有一個 kubelet 作為 agent，主要任務是根據 Pod 規範（PodSpec）來確保 Pod 的容器在節點上正確執行。如果 Pod 的狀態與期望的規範不符，Kubelet 會採取相應措施來修復，例如重新啟動容器。
    - 與 kubernetes API server 通信，接收 PodSpec，並匯報 Pod 狀態給 API server。
    - 管理 Pod 和容器，當 PodSpec 與實際狀態不符時，Kubelet 會採取相應措施。如重啟容器。
    - 管理資源，監控 container 對資源的使用情況(如 CPU、memory)，確保 Pod 不會超出資源限制。
    - 監控與健康檢查
        - 如 kubelet 會定期檢查 pod 的 liveness probe、readiness probe。
    - 負責運行時的抽象層，使 kubernetes 支援多種容器運行。
    - 負責拉 image，並將 image 運行為 container。
    - 負責 Volume 的處理。
    - 負責 log 的收集及監控。
    - 配和 CNI(container network interface) 實現容器的網路，使得 container 能夠互相通信。
- kubelet 的工作流程大致如下
    1. 獲取 Pod 規範：Kubelet 通過 API 伺服器獲取需要在該節點上運行的 Pod 配置。
    2. 確保資源可用：Kubelet 檢查該節點的資源狀況（CPU、內存等），以確保該節點可以運行所需的 Pod。
    3. 啟動容器：Kubelet 使用容器運行時（如 Docker 或 containerd）來啟動 Pod 中的容器，並將它們配置為正確的狀態。
    4. 監控容器運行狀態：Kubelet 定期檢查 Pod 和容器的狀態，並進行健康檢查。如果容器出現問題，Kubelet 會按照規範的策略重新啟動它們。
    5. 匯報狀態：Kubelet 定期將節點和 Pod 的狀態匯報給 Kubernetes API 伺服器，以便集群中的其他組件做出調度或資源管理決策。

### Metrics Server 安裝

- 如果你是使用 minikube，那麼你可以使用以下的指令來安裝 Metrics Server
```bash
minikube addons enable metrics-server
```
- 如果是其他的，可以直接從 github 上拉取
```bash
git clone https://github.com/kubernetes-incubator/metrics-server.git

kubectl apply -f metrics-server/deploy/1.8+/
```

### Metrics Server 使用

- 使用以下的指令來查看 Cluster 中的資源使用情況
```bash
# 查看 Cluster 中的資源使用情況
kubectl top nodes

# 查看 Cluster 中的 pod 的資源使用情況
kubectl top pods
```

### Practice

#### Question 2

1. 先把 Metrics Server git clone 下來
```bash
git clone https://github.com/kodekloudhub/kubernetes-metrics-server.git
```

2. 此時 `ls` 會看到一個 `kubernetes-metrics-server` 的資料夾，進入這個資料夾
```bash
cd kubernetes-metrics-server
```
3. 你會看到以下的檔案
```bash
controlplane kubernetes-metrics-server on  master ➜  ls -l
total 32
-rw-r--r-- 1 root root  384 Oct  6 13:55 aggregated-metrics-reader.yaml
-rw-r--r-- 1 root root  303 Oct  6 13:55 auth-delegator.yaml
-rw-r--r-- 1 root root  324 Oct  6 13:55 auth-reader.yaml
-rw-r--r-- 1 root root  293 Oct  6 13:55 metrics-apiservice.yaml
-rw-r--r-- 1 root root 1007 Oct  6 13:55 metrics-server-deployment.yaml
-rw-r--r-- 1 root root  249 Oct  6 13:55 metrics-server-service.yaml
-rw-r--r-- 1 root root  219 Oct  6 13:55 README.md
-rw-r--r-- 1 root root  612 Oct  6 13:55 resource-reader.yaml
```
4. 此時的 `pwd` 應該是 `/root/kubernetes-metrics-server`，這時我們可以使用以下的指令來安裝 Metrics Server
```bash
kubectl apply -f .
```

> `cd ~` 可以回到 root 目錄

## 20. Labels, Selectors and Annotations - Pod Design

- Labels 是 k8s 中的一個機制，用來將物件分類。
- Labels 是 key-value 的形式，可以是任何的 key-value。
- Labels 可以用來標記物件，並且可以用來搜尋物件。
- 例如現在有 5 種動物，它們分別是不同的 pod，而可能有多種不同的分組方式，例如動物的棲地、動物的顏色等。藉由 labels，我們就可以以不同的方式來分組並方便搜尋出我們想要的 pod。
    - 貓
        - 棲地 : 陸地
        - 顏色 : 黑
    - 狗
        - 棲地 : 陸地
        - 顏色 : 白
    - 鳥
        - 棲地 : 空中
        - 顏色 : 灰
    - 豬
        - 棲地 : 陸地
        - 顏色 : 粉
    - 魚
        - 棲地 : 水中
        - 顏色 : 藍

### Labels 的使用

- 在物件的 metadata 底下，我們可以使用 labels 來標記物件，以下以 pod 為例
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
  labels:
    app: myapp
    env: dev
spec:
  containers:
  - name: mycontainer
    image: nginx
```

### Labels 的搜尋 - Label Selectors

- 我們可以使用 label selectors 來搜尋物件，以下以 pod 為例
```bash
# 使用 label selectors 來搜尋 pod
kubectl get pods --selector app=myapp
```

### selector.matchLabels 和 selector.matchExpressions

- 在 pod 的 spec.selector 中，我們可以使用 matchLabels 和 matchExpressions 來搜尋物件。
- matchLabels 是一個 map，用來搜尋 label 的 key-value。
- matchExpressions 是一個 list，用來搜尋 label 的 key-value。
- 以下是 matchLabels 的使用
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mydeploy
spec:
  selector:
    matchLabels: # 使用 matchLabels 來搜尋 label，用來配對 deployment 所管理的 pod，也就是說這裡的 labels 必須與底下 template.metadata.labels 一樣。寫一個就可以配對到了，但如果有其他的 pod 也有這個 label，那麼這個 pod 也會被配對到。所以寫兩個更精確。
    app: myapp
    env: dev
  template:
    metadata:
    labels:
      app: myapp
      env: dev
    spec:
    containers:
    - name: mycontainer
      image: nginx
```
- 以下是 matchExpressions 的使用
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mydeploy
spec:
  selector:
    matchExpressions: # 使用 matchExpressions 功能其實與 matchLabels 差不多，指是 matchExpressions 更回彈性，可以使用 In、NotIn、Exists、DoesNotExist、Gt、Lt 這些 operator 來搜尋 label。
    - key: app
      operator: In
      values:
      - myapp
    - key: env
      operator: In
      values:
      - dev
  template:
    metadata:
    labels:
      app: myapp
      env: dev
    spec:
    containers:
    - name: mycontainer
      image: nginx
```

### 有關 label 和 selector 的指令

```bash
# 在 pod 上加上 label
kubectl label pods <pod-name> <label-key>=<label-value>

# 查看 pod 上的 label
kubectl describe pods <pod-name>

# 查看 pod 的 label
kubectl get pods --show-labels

# 使用 label selectors 來搜尋 pod
kubectl get pods --selector app=myapp

# 使用 label selectors 來搜尋 pod
kubectl get pods --selector app=myapp,env=dev

# 使用 label selectors 來搜尋 pod
kubectl get pods --selector app=myapp,env!=dev
```
### Annotations

- Annotations 是用來記錄一些 metadata 的資訊，例如建立時間、作者等。就是純紀錄而已

#### Annotations 的使用

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
  labels:
    app: myapp
    env: dev
  annotations:
    author: john
    created: "2021-10-06"
spec:
  containers:
  - name: mycontainer
    image: nginx
```

## 21. Rolling Updates and Rollbacks - Pod Design

- 在 k8s 中，有多種 Deployment Strategy
    - Recreate      
        就是把舊的 pods 全部刪除，然後再創建新的 pods。但這樣會造成服務真空期。
    - RollingUpdate
        假設 deployment 有 3 個 replicas，那麼他會一次更新一個 pod，直到更新完所有的 pod。這樣就不會造成服務真空期。        
        如果沒有指定 Deployment Strategy，預設就是 RollingUpdate。
    
> Replicas 是指 deployment 中的 pod 的數量      
> ReplicaSet 是指當 Replicas=3 時，就會建立一個 ReplicaSet 來管理這組(3個) pod。

- RollBack
    - 如果更新後發現有問題，可以使用指令來回滾，回到之前的版本

### Rolling Update 及 Rollback 的常用指令

```bash
# 更新 deployment 可以使用先前的 yaml，然後使用 apply 指令
kubectl apply -f <deployment.yaml>

# 或用指令更新 deployment 中的 image
# 這個指令 即使我沒寫錯，按 teb 也不會幫我補全，所以要記得
# 這邊的 container-name 後的等號，不可以被忽略
kubectl set image deployment <deployment-name> <container-name>=<new-image>

# 更新時，可以使用 --record 來記錄更新的紀錄
# --record 好像要被棄用了，所以不用加也可以
kubectl set image deployment <deployment-name> <container-name>=<new-image> --record true 

# 查看 rollout 的狀態
kubectl rollout status deployment <deployment-name>

# 查看 rollout 的歷史
kubectl rollout history deployment <deployment-name>

# 回滾到之前的版本
kubectl rollout undo deployment <deployment-name> --to-revision=<revision-number>
```

### deployment strategy 範例

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
 name: nginx-deployment
 labels:
   app: nginx
spec:
 replicas: 3
 strategy:
   type: RollingUpdate
   rollingUpdate:
     maxSurge: 25% # 允許一個時間內可更新的最大 pod 數
     maxUnavailable: 25% # 更新過程中允許不可用的最大 Pod 數
 selector:
   matchLabels:
     app: nginx
 template:
   metadata:
     labels:
       app: nginx
   spec:
     containers:
     - name: nginx
       image: nginx:1.14.2
       ports:
       - containerPort: 80

```
- strategy type 也可以是 Recreate，更新時全部停止、全部更新
```yaml
 strategy:
   type: Recreate
```

## 22. Deploy Strategy : Blue Green and Canary Deployments - Pod Design

### Blue Green Deploy Strategy(藍綠部署策略)  
    
- 後面會提到的 service 是用來控制流量流量，這邊先簡單提一下
1. 舊的 deployment 有 5 個 replicas，label 是 version: blue。
2. service 的 selector 也是 version: blue，所以 service 會將流量導向舊的 deployment。
3. 建立新的 deployment，label 我們使用 version: green，replicas 也是 5 個。但因為 service 的 selector 是 version: blue，所以 service 不會將流量導向新的 deployment。
4. 當新的 deployment 建立完成、通過測試後
5. 我們只要將 service 的 selector 改為 green 的 label
6. 這樣 service 就會將流量全部導向新的 deployment。

#### Blue Green Deploy Strategy 範例

- 舊的 deployment 有 3 個 replicas，label 是 app: blue。
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: blue
  labels:
    deploy: blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: blue
  template:
    metadata:
      labels:
        app: blue
    spec:
      containers:
      - name: mybluecontainer
        image: nginx
```
- 建立 service，將流量導向舊的 deployment
```yaml
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  type: NodePort
  selector:
    app: blue
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
```
- 建立新的 deployment，label 我們使用 app: green，replicas 也是 3 個。
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: green
  labels:
    deploy: green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: green
  template:
    metadata:
      labels:
        app: green
    spec:
      containers:
      - name: mygreencontainer
        image: nginx
```
- 當新的 deployment 建立完成、通過測試後，只要將 service 的 selector 改為 green 的 label
```bash
k edit svc myservice
```
```yaml
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  type: NodePort
  selector:
    app: green
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
```
- 如次一來，service 就會將流量全部導向新的 deployment。

### Canary Deploy Strategy

- 先說一個小故事，以前在礦坑中，礦工會帶一隻金絲雀進礦坑，如果金絲雀死了，礦工就知道礦坑中有毒氣，礦工就會趕快逃離礦坑。
- 也就是說我們先以較小的測試群體來測試新的 deployment，如果測試沒問題，我們再將所有的流量導向新的 deployment。
1. 舊的 deployment 有 5 個 replicas，label 是 app: myapp。
2. service 的 selector 也是 app: myapp，所以 service 會將流量導向舊的 deployment。
3. 建立新的 deployment，label 我們也用 app: myapp，但是他的 replicas 只有 1 個。
4. 所以這時 service 會將 1/6 的流量導向新的 deployment，而 5/6 的流量導向舊的 deployment。
5. 經過後續的測試，沒問題後，再慢慢增加新的 deployment 的 replicas，降低舊的 deployment 的 replicas。直到所有的流量都導向新的 deployment。

> 修改已經存在的 deployment 的 replicas，可以使用以下的指令
```bash
kubectl scale deployment <deployment-name> --replicas=5
```

#### Canary Deploy Strategy 範例

- 舊的 deployment 有 5 個 replicas，label 是 app: myapp。
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myolddeploy
  labels:
    deploy: old
spec:
  replicas: 5
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myoldcontainer
        image: nginx
```
- 建立 service，將流量導向舊的 deployment
```yaml
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  type: NodePort
  selector:
    app: myapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
```
- 建立新的 deployment，label 我們使用 app: myapp，replicas 也是 1 個。
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mynewdeploy
  labels:
    deploy: new
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    apec:
      containers:
      - name: mynewcontainer
        image: nginx
```
- 因為舊的和欣的 deployment 的 label 都是 app: myapp，所以 NodePort Service 會將流量的 1/6 導向新的 deployment，5/6 導向舊的 deployment。
- 待運行一段時間後，測試都沒有問題，就可以使用以下指令逐漸增加新的 deployment 的 replicas，降低舊的 deployment 的 replicas。
```bash
kubectl scale deployment mynewdeploy --replicas=2
kubectl scale deployment myolddeploy --replicas=4
```
- 直到所有的流量都導向新的 deployment。


太長了，開個 [part 2](https://hackmd.io/@ohQEG7SsQoeXVwVP2-v06A/HJ5F3anekx)