---
# System prepended metadata

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

---

## 23. Jobs and CronJobs - Pod Design

- 有時，當我們要啟動服務前，可能會需要先執行一些工作，例如初始化資料庫、執行一些腳本等。這時我們就可以使用 Job 來執行這些工作。
- 而又有時，我們可能需要定期執行一些工作，例如每天備份資料庫、每週執行一次清理工作等。這時我們就可以使用 CronJob 來執行這些工作。

### Job

- 我們先從 Pod 講起，我們寫一個 Pod，而其中要執行的工作是 `3+2`，並且將結果 print 出來。
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: calc
spec:
  containers:
  - name: calc
    image: ubuntu
    command: ["bash", "-c", "echo $((3+2))"]
```
- 當你建立這樣的 pod 時，他會執行完工作，且工作結束後，他又會重啟(你可以使用`k get pods 的 RESTARTS 欄位中看到他重的次數)，因為 pod 的 restartPolicy 預設是 Always。他會一直重啟，直到某個閥值。
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: calc
spec:
  restartPolicy: Always # 如果你 describe 會發現有這行預設是 Always
  containers:
  - name: calc
    image: ubuntu
    command: ["bash", "-c", "echo $((3+2))"]
```
- 所以你可以把 restartPolicy 改為 Never，這樣他就不會重啟了。
- 但現在我們如果有很複雜的事情要處理，不只是 `3+2`，而且還要處理很多事情，需要盡可能多的 Pod 來處理，且處理完就自動結束生命週期，而這時我們需要一個管理者來管理這些 Pod，這時我們就可以使用 Job 來管理這些 Pod。
> ReplicaSet 跟 Job 有相似之處      
> 但 ReolicaSet 是用來管理一組 pod，且 pod 會一直運行，直到你刪除他。       
>而 Job 是用來管理一組 pod，且 pod 會運行一次，直到工作結束後就會結束生命週期。

#### Job 的使用

- 以下是一個 Job 的範例，其實就是把 Pod 中，spec 的部分，移到 Job 的 spec.template 中。
```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: calc
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: calc
        image: ubuntu
        command: ["bash", "-c", "echo $((3+2))"]
```
- 有關 Job 的指令
```bash
# 建立 Job
kubectl apply -f <job.yaml>

# 查看 Job 列表
kubectl get jobs

# 此時，你查看 pod 列表，會發現 Job 建立了 Pod 來執行工作，且在 RESTARTS 欄位中，你會發現他的重啟次數是 0
kubectl get pods

# 如果工作是 print 出 3+2 的結果，那麼你可以查看 pod 的 log 來看結果
kubectl logs <pod-name>

# 刪除 Job
kubectl delete job <job-name>
```

- 如果我需要這個工作被完成 3 次，那麼我們可以使用 completions 來設定
- 在這樣的情況下，他一次會建立一個 pod，且 pod 會執行工作，直到工作結束後就會結束生命週期，才會接著建立第二個 pod。而若其中一個 pod 失敗，那麼他會重新建立一個 pod 來執行工作。直到完成 3 次。
```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: calc
spec:
  completions: 3
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: calc
        image: ubuntu
        command: ["bash", "-c", "echo $((3+2))"]
```
- 那如果我希望可以加快完成三次同樣工作的速度呢? 我們可以使用 parallelism 來設定
- 在這樣的情況下，他會一次建立你指定的 pod 數量，並同時執行工作。
```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: calc
spec:
  completions: 3
  parallelism: 2
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: calc
        image: ubuntu
        command: ["bash", "-c", "echo $((3+2))"]
```

### CronJob(Cron)

- CronJob 基本上就和 Job 一樣，只是 CronJob 可以設定定期執行工作。
- 以下是一個 CronJob 的範例，其實就是把 Job 的 spec 部分，移到 CronJob 的 spec.jobTemplate 中。
- 其中 schedule 是用來設定定期執行工作的時間。cron 表示式的用法如下
    - 第一個 * : 分鐘(0-59)，*/5 表示每 5 分鐘
    - 第二個 * : 小時(0-23)
    - 第三個 * : 日期(1-31)
    - 第四個 * : 月份(1-12)
    - 第五個 * : 星期(0-6)，0 表示星期天
```yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: calc
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
          - name: calc
            image: ubuntu
            command: ["bash", "-c", "echo $((3+2))"]
```

## 24. Services - Services & Networking

- 我們先來用一個情境來描述 Service 的作用，我們部屬了一個pod，且在其上運行一個 web 程式，作為一個外部用戶，我該如何訪問這個 web 程式呢?
- 先來看一下這個情境下的環境
  - 在 kubernetes cluster 的其中一個 worker node 上，我們部屬了一個 pod，且在其上運行一個 web 程式
    - worker node IP : 192.168.1.2
    - 我的電腦 IP : 192.168.1.10
    - internal pod network : 10.244.0.0
    - pod IP : 10.244.0.2
- 很明顯的，我無法直接用我的電腦 ping pod 的 IP 來訪問 web 程式，因為我們的 pod 是在一個 private network 中，我們無法直接訪問。
- 如果我們在 work node 上，可以直接使用 `crul http://10.244.0.2` 來訪問 web 程式，但是我們的目的是要讓外部用戶也可以訪問 web 程式。
- 所以我們需要一個物件，能夠將內部的 pod IP 對外暴露，這時我們就可以使用 Service 來達到這個目的。
  - 這類的 service，我們稱之為 NodePort Service，他會監聽 Node 上的端口，並將其對應到 pod 的端口。

### Service 的種類

| Service Type | Description |
| ------------ | ----------- |
| ClusterIP | 預設的 Service Type，用來將 pod 對外暴露到 Cluster 內部的 IP 上，只能在 Cluster 內部訪問 |
| NodePort | 將 pod 對外暴露到 Node 的 IP 上，可以在 Cluster 外部訪問 |
| LoadBalancer | 在 NodePort 的基礎上，自動創建一個 LoadBalancer，可以在 Cluster 外部訪問 |


### NodePort Service

- 現在的環境如下，總共涉及了 3 個 port。
  - 有一個 Node，Node port 是 30008。(在預設情況下，NodePort 的 port 範圍是 30000-32767)
  - 有一個 Pod，port 是 80，IP 是 10.244.0.2
  - 有一個 NodePort Service，port 是 80
- Pod 上的 port 稱為 targetPort

#### NodePort Service yaml

- 以下是一個 NodePort Service 的 yaml 範例
```yaml
apiVersion: v1
kind: Service
metadata:
  name: myservice

spec:
  type: NodePort
  selector: # 這裡的 selector 用來選擇要對外暴露的 pod，其中的 key-value 必須與 pod 的 labels 一樣
    app: myapp
  ports:
  - targetPort: 80 # Pod 上的 port
    port: 80 # Service 上的 port
    nodePort: 30008 # Node 上的 port
```
- 而若你有多個 pod 在同一個 work node 中，且你希望他們共用同一個 NodePort，我們只要在這些 pod 上加上相同的 label，並在 selector 中指定這個 label 即可。這樣這些 pod 就會共用同一個 NodePort，且有 NodePort Service 所提供的負載平衡功能。
- 即便你的 multiple pod 在不同的 node 上，他們也可以共用同一個 NodePort，因為 NodePort Service 會自動將流量導向到正確的 node 上。(你使用各自不同的 work node ip 來進行 curl，他們都可以訪問到 pod)

### ClusterIP Service

- 一個完整的專案中，可能會有多個不同類型的功能，被放在不同的 pod 中，例如 front-end、back-end、database 等。
- 我們知道這些服務可能會重啟、可能會 crash，所以這些服務的 IP 是不可能固定的，所以我們不能依賴 pod 的 IP 來訪問這些服務，且當我們水平擴展以達到負載平衡'
- 而這些內部的服務，就可以透過 ClusterIP Service 來互相訪問。

#### ClusterIP Service yaml

- 以下是一個 ClusterIP Service 的 yaml 範例
```yaml
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  type: ClusterIP
  selector:
    app: myapp
  ports:
  - targetPort: 80
    port: 80
```

## 25. Ingress - Services & Networking

- Ingress 是一種 API 物件，主要用來管理從外部進入集群內部服務的 HTTP 和 HTTPS 流量。
- Kubernetes 中的服務 (Service) 通常是集群內部可訪問的，Ingress 提供了一個進階的方式來暴露這些內部服務到集群外部，通常應用在以下情況：
    - 負載均衡：    
    Ingress 允許根據不同的請求（例如，URL 路徑或主機名）將流量分配到不同的 Kubernetes 服務，類似於傳統的負載均衡器功能，但它更加靈活。與 LoadBalancer 不同，Ingress 可以處理多個服務的 HTTP/HTTPS 流量並根據規則進行分發。
    - 基於主機名或路徑的路由：    
    Ingress 可以根據請求的 URL 路徑（如 /api 或 /login）或主機名（如 example.com 或 shop.example.com）將流量導向不同的服務。這樣可以在一個入口下管理多個不同應用。
    - SSL/TLS 終止：    
    Ingress 支持 TLS（傳輸層安全性），可以在 Ingress 層終止 HTTPS 連接，並向內部服務傳遞 HTTP 請求。這樣避免了每個內部服務都需要配置 SSL/TLS 證書。
    - 跨命名空間的管理：    
    Ingress 可以用來跨命名空間集中管理多個服務的對外流量，而不需要每個服務都分別設置暴露方式。
    - 外部進入控制：    
    在許多場景中，Ingress 結合防火牆、允許和拒絕規則來限制哪些流量可以訪問內部服務。
- 要對外開放 ingress，我們還是需要搭配一個 service，這個 service 通常是 NodePort 或 LoadBalancer。

- Ingress 分為兩個部分
    - Ingress Controller
        - Ingress Controller 是一個獨立的 pod，用來監聽 Ingress 物件的變化，並根據這些變化來配置反向代理。如 Nginx、Traefik、HAProxy 等。
        - Ingress Controller 通常是由第三方廠商提供，例如 Nginx、Traefik、HAProxy 等。
    - Ingress Resource
        - Ingress Resource 是一個 API 物件，用來定義流量的規則，並指定流量應該如何被轉發。

### Ingress Controller

- 這邊我們使用 Nginx 作為 Ingress Controller
- 而這邊用的 Nginx 跟我們熟悉的 Nginx 不太一樣，這個 Nginx 是專門為 k8s 設計的，他會監聽 Ingress 物件的變化，並根據這些變化來配置反向代理。
- 我們會需要的 Object 有
    - Deployment: 用來部屬 Nginx
    - Service: 用來對外暴露 Ingress Controller
    - ConfigMap: 對應到 Nginx 的設定檔
    - ServiceAccount: 用來授權 Nginx 存取 k8s API

### Ingress Resource

- 以下展示兩種不同的 Ingress Resource 的 yaml 範例
- 按照 URL 路徑來分流量   
  - 這個配置使用了基於路徑的路由。
  - 它沒有指定主機名，而是根據 URL 路徑（/app1 和 /app2）來路由流量。
  - 不同的路徑指向不同的後端服務。
  - 使用情境：    
    - 當您想在同一個域名下提供多個應用程序或服務時。
    - 適用於想要將多個應用整合到一個共同的 URL 結構下的場景。
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: myingress
spec:
  rules:
  - path: /app1
    backend:
      serviceName: app1
      servicePort: 80
  - path: /app2
    backend:
      serviceName: app2
      servicePort: 80
```
- 按照 Hostname 來分流量
  - 這個配置使用了基於主機名的路由。    
  - 它定義了兩個不同的主機名規則：app1.example.com 和 app2.example.com。
  - 每個主機名都指向不同的後端服務（app1 和 app2）。
  - 使用情境：    
    - 當您有多個子域名，每個子域名對應不同的應用程序時。
    - 適用於需要完全分離不同應用程序的場景，每個應用都有自己的域名。
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: myingress
spec:
  rules:
  - host: app1.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: app1
          servicePort: 80
  - host: app2.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: app2
          servicePort: 80
```
- 以上都是後端的範例，但事實上，前後端都適用，以下是 AI 給我的範例
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-web-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /api(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: backend-service
            port: 
              number: 8080
      - path: /()(.*)
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port: 
              number: 80
```

### 有關 Ingress 的常用指令

```bash
# 使用 yaml 檔來建立 Ingress
kubectl apply -f <ingress.yaml>

# 查看 Ingress 列表
kubectl get ingress

# 查看 Ingress 的詳細資訊
kubectl describe ingress <ingress-name>

# 創建 
```

### Practice 1

#### 11

##### Q

If the requirement does not match any of the configured paths in the Ingress, to which service are the requests forwarded?

##### A

1. 先使用以下指令看一下 ingress 裡面的設定
```bash
kubectl describe ingress myingress
```
```bash
controlplane ~ ➜  k -n=app-space describe ingress ingress-wear-watch 
Name:             ingress-wear-watch
Labels:           <none>
Namespace:        app-space
Address:          10.103.6.199
Ingress Class:    <none>
Default backend:  <default>
Rules:
  Host        Path  Backends
  ----        ----  --------
  *           
              /wear    wear-service:8080 (10.244.0.4:8080)
              /watch   video-service:8080 (10.244.0.5:8080)
Annotations:  nginx.ingress.kubernetes.io/rewrite-target: /
              nginx.ingress.kubernetes.io/ssl-redirect: false
Events:
  Type    Reason  Age                    From                      Message
  ----    ------  ----                   ----                      -------
  Normal  Sync    6m23s (x2 over 6m23s)  nginx-ingress-controller  Scheduled for sync
  ```
2. 可以看到 Default backend 是 <default>，這個時候，如果 request 不符合任何 path，他會被導向到 default backend。
3. 那這邊沒有寫 default backend 是誰，這東西被定義在 ingress controller 的設定檔中，所以我們要去看 ingress controller 的設定檔。
4. 這邊我們使用以下指令來看 ingress controller 的設定檔
```bash
controlplane ~ ✖ k describe deployments.apps ingress-nginx-controller -n ingress-nginx
Name:                   ingress-nginx-controller
Namespace:              ingress-nginx
CreationTimestamp:      Sat, 12 Oct 2024 12:00:36 +0000
Labels:                 app.kubernetes.io/component=controller
                        app.kubernetes.io/instance=ingress-nginx
                        app.kubernetes.io/managed-by=Helm
                        app.kubernetes.io/name=ingress-nginx
                        app.kubernetes.io/part-of=ingress-nginx
                        app.kubernetes.io/version=1.1.2
                        helm.sh/chart=ingress-nginx-4.0.18
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app.kubernetes.io/component=controller,app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx
Replicas:               1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:           app.kubernetes.io/component=controller
                    app.kubernetes.io/instance=ingress-nginx
                    app.kubernetes.io/name=ingress-nginx
  Service Account:  ingress-nginx
  Containers:
   controller:
    Image:       registry.k8s.io/ingress-nginx/controller:v1.1.2@sha256:28b11ce69e57843de44e3db6413e98d09de0f6688e33d4bd384002a44f78405c
    Ports:       80/TCP, 443/TCP, 8443/TCP
    Host Ports:  0/TCP, 0/TCP, 0/TCP
    Args:
      /nginx-ingress-controller
      --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
      --election-id=ingress-controller-leader
      --watch-ingress-without-class=true
      --default-backend-service=app-space/default-backend-service
      --controller-class=k8s.io/ingress-nginx
      --ingress-class=nginx
      --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
      --validating-webhook=:8443
      --validating-webhook-certificate=/usr/local/certificates/cert
      --validating-webhook-key=/usr/local/certificates/key
    Requests:
      cpu:      100m
      memory:   90Mi
    Liveness:   http-get http://:10254/healthz delay=10s timeout=1s period=10s #success=1 #failure=5
    Readiness:  http-get http://:10254/healthz delay=10s timeout=1s period=10s #success=1 #failure=3
    Environment:
      POD_NAME:        (v1:metadata.name)
      POD_NAMESPACE:   (v1:metadata.namespace)
      LD_PRELOAD:     /usr/local/lib/libmimalloc.so
    Mounts:
      /usr/local/certificates/ from webhook-cert (ro)
  Volumes:
   webhook-cert:
    Type:          Secret (a volume populated by a Secret)
    SecretName:    ingress-nginx-admission
    Optional:      false
  Node-Selectors:  kubernetes.io/os=linux
  Tolerations:     <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   ingress-nginx-controller-597d7b4fbd (1/1 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  16m   deployment-controller  Scaled up replica set ingress-nginx-controller-597d7b4fbd to 1
```
5. 可以看到 --default-backend-service=app-space/default-backend-service，這個就是 default backend 的設定，所以這個時候，如果 request 不符合任何 path，他會被導向到 app-space/default-backend-service。

- 練習中所用到的 ingress.yaml
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
  creationTimestamp: "2024-10-12T12:00:36Z"
  generation: 1
  name: ingress-wear-watch
  namespace: app-space
  resourceVersion: "848"
  uid: 06000112-5543-451e-a2ae-a8820d03ce8a
spec:
  rules:
  - http:
      paths:
      - backend:
          service:
            name: wear-service
            port:
              number: 8080
        path: /wear
        pathType: Prefix
      - backend:
          service:
            name: video-service
            port:
              number: 8080
        path: /watch
        pathType: Prefix
status:
  loadBalancer:
    ingress:
    - ip: 10.103.6.199
```

#### 22

##### Q

You are requested to make the new application available at /pay.


Identify and implement the best approach to making this application available on the ingress controller and test to make sure its working. Look into annotations: rewrite-target as well.

Ingress Created

Path: /pay

Configure correct backend service

Configure correct backend port

##### A

1. 首先先找到新加入的 application 的 service name 和 port
```bash
kubectl get svc -A
```
2. 這時有看到新的 service 是 `pay-service`，port 是 8282，且他所在的 namespace 是 critical-space
3. 因為我們原本 ingress 所在的 namespace 是 app-space，而這次新加入的 application 所在的 namespace。而在正常情況下，ingress 只能對應到同一個 namespace 的 service。
4. 所以我們要在 critical-space 建立一個 ingress 來對應在 critical-space 的 pay-service 及 application。
5. 先使用以下指令產生一個 ingress.yaml
```bash
kubectl -n=critical-space create ingress ingress-pay --rule="/pay=pay-service:8282" --dry-run=client -o yaml > in
gress-test-pay.yaml
```
> 可以從 `kubectl create ingress --help` 中看到 `--rule` 的用法
6. 然後修改 ingress-pay.yaml，加上 annotations 並修改 pathType 為 Prefix
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
  creationTimestamp: null
  name: ingress-pay
spec:
  rules:
  - http:
      paths:
      - backend:
          service:
            name: pay-service
            port:
              number: 8282
        path: /pay
        pathType: Exact
status:
  loadBalancer: {}
```
7. 最後使用以下指令來建立 ingress
```bash
kubectl apply -f ingress-pay.yaml
```
8. 測試是否成功
```bash
curl -H "Host: myapp.example.com" http://...
```

> annotations.ingress.kubernetes.io/rewrite-target: /     
> - 在 Kubernetes 中，NGINX Ingress 控制器提供許多自訂配置選項，其中一個常用的選項是 rewrite-target。這個選項的作用是當請求被轉發到後端服務時，修改 URL 的路徑，讓應用可以正確處理請求。以下是 rewrite-target 的說明及其使用範例。    
> - 這個設定是用來將 /pay 這個 path 重新導向到 /，這樣就可以將 /pay 的 request 導向到 pay-service:8282    
> - 假設我們有兩個應用：    
>   - Watch app 顯示影片串流的網頁，網址為 http://watch-service:port/   
>   - Wear app 顯示服裝網頁，網址為 http://wear-service:port/   
> - 我們需要透過 Ingress 配置，讓當使用者訪問以下網址時：   
>   - http://ingress-service:ingress-port/watch 轉發到 http://watch-service:port/   
>   - http://ingress-service:ingress-port/wear 轉發到 http://wear-service:port/   
> - 在沒有使用 rewrite-target 的情況下，會出現以下問題：    
>   - http://ingress-service:ingress-port/watch 會被轉發到 http://watch-service:port/watch    
>   - http://ingress-service:ingress-port/wear 會被轉發到 http://wear-service:port/wear   
> - 也就是把 /watch 和 /wear 這兩個 path 也一起轉發到後端服務，這樣會導致後端服務無法正確處理請求。
> - 所以 rewrite-target 就是把 /watch 和 /wear 這兩個 path 去掉，只轉發到後端服務的根路徑。

> pathType 種類   
> - Exact: 只有當 request 的 path 完全符合時，才會導向到對應的 service
> - Prefix: 只要 request 的 path 有包含時，就會導向到對應的 service


### Practice 2

- 這篇練習主要在建立 ingress controller，並將兩個服務對應到不同的 path

#### 1

##### Q

create namespace called ingress-ngnix

##### A

```bash
kubectl create namespace ingress-nginx
```

#### 2

##### Q

3 / 8
The NGINX Ingress Controller requires a ConfigMap object. Create a ConfigMap object with name ingress-nginx-controller in the ingress-nginx namespace.

No data needs to be configured in the ConfigMap.

##### A

```bash
kubectl create configmap ingress-nginx-controller --namespace=ingress-nginx
```

#### 3

##### Q

The NGINX Ingress Controller requires two ServiceAccounts. Create both ServiceAccount with name ingress-nginx and ingress-nginx-admission in the ingress-nginx namespace.

Use the spec provided below.

Name: ingress-nginx

Name: ingress-nginx-admission

##### A

```bash
kubectl create serviceaccount ingress-nginx --namespace=ingress-nginx
kubectl create serviceaccount ingress-nginx-admission --namespace=ingress-nginx
```

#### 4

他幫我建立了在 ingress-nginx namespace 的 role, rolebinding, clusterrole, clusterrolebinding，所以我就不用做了。

#### 6

以下是他所提供的 ingress-controller.yaml
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.1.2
    helm.sh/chart: ingress-nginx-4.0.18
  name: ingress-nginx-controller
  namespace: ingress-
spec:
  minReadySeconds: 0
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app.kubernetes.io/component: controller
      app.kubernetes.io/instance: ingress-nginx
      app.kubernetes.io/name: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/component: controller
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/name: ingress-nginx
    spec:
      containers:
      - args:
        - /nginx-ingress-controller
        - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
        - --election-id=ingress-controller-leader
        - --watch-ingress-without-class=true
        - --default-backend-service=app-space/default-http-backend
        - --controller-class=k8s.io/ingress-nginx
        - --ingress-class=nginx
        - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
        - --validating-webhook=:8443
        - --validating-webhook-certificate=/usr/local/certificates/cert
        - --validating-webhook-key=/usr/local/certificates/key
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: LD_PRELOAD
          value: /usr/local/lib/libmimalloc.so
        image: registry.k8s.io/ingress-nginx/controller:v1.1.2@sha256:28b11ce69e57843de44e3db6413e98d09de0f6688e33d4bd384002a44f78405c
        imagePullPolicy: IfNotPresent
        lifecycle:
          preStop:
            exec:
              command:
              - /wait-shutdown
        livenessProbe:
          failureThreshold: 5
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        name: controller
        ports:
        - name: http
            containerPort: 80
          protocol: TCP
        - containerPort: 443
          name: https
          protocol: TCP
        - containerPort: 8443
          name: webhook
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          requests:
            cpu: 100m
            memory: 90Mi
        securityContext:
          allowPrivilegeEscalation: true
          capabilities:
            add:
            - NET_BIND_SERVICE
            drop:
            - ALL
          runAsUser: 101
        volumeMounts:
        - mountPath: /usr/local/certificates/
          name: webhook-cert
          readOnly: true
      dnsPolicy: ClusterFirst
      nodeSelector:
        kubernetes.io/os: linux
      serviceAccountName: ingress-nginx
      terminationGracePeriodSeconds: 300
      volumes:
      - name: webhook-cert
        secret:
          secretName: ingress-nginx-admission

---
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.1.2
    helm.sh/chart: ingress-nginx-4.0.18
  name: ingress-controller
  namespace: ingress-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
    nodeport: 30080
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  type: NodePort
```

- 直接使用以下指令來建立 ingress controller
```bash
kubectl apply -f ingress-controller.yaml
```
- 這時發現出現以下錯誤訊息
```bash
error: error parsing ingress-controller.yaml: error converting YAML to JSON: yaml: line 73: mapping values are not allowed in this context
```
- 使用`vim`打開，並按`esc`再輸入`:73`找到第73行，發現多了兩格空格，刪掉後再重新 apply。
- 又出現以下錯誤訊息
```bash
Error from server (NotFound): error when creating "ingress-controller.yaml": namespaces "ingress-" not found
Error from server (BadRequest): error when creating "ingress-controller.yaml": Service in version "v1" cannot be handled as a Service: strict decoding error: unknown field "spec.ports[0].nodeport"
```
- 這時發現他的 namespace 是 ingress-nginx，而不是 ingress-，所以又要再修改，修改完後再重新 apply。
- 然後又出現以下錯誤訊息
```bash
deployment.apps/ingress-nginx-controller created
Error from server (BadRequest): error when creating "ingress-controller.yaml": Service in version "v1" cannot be handled as a Service: strict decoding error: unknown field "spec.ports[0].nodeport"
```
- 這時發現他的 nodeport 是錯誤的，應該是 nodePort，所以要修改後再重新 apply。

> 後來發現我還少改了一個指定的 Service name: ingress-nginx-controller，他原本是有寫，所以就算 apply 也會成功，只是那不是我們要的名字。    
> 所以我就使用 `replace --force` 來重新 apply 一次。但是這樣會出現以下錯誤訊息    
> `The Service "ingress-nginx-controller" is invalid: spec.ports[0].nodePort: Invalid value: 30080: provided port is already allocated`   
> 這是因為我之前已經 apply 過一次，所以 nodePort 已經被佔用了，所以我要先刪除這個 service，再重新 apply 一次。

#### 7

##### Q

Create the ingress resource to make the applications available at /wear and /watch on the Ingress service.

Also, make use of rewrite-target annotation field: -

nginx.ingress.kubernetes.io/rewrite-target: /

Ingress resource comes under the namespace scoped, so don't forget to create the ingress in the app-space namespace.

Ingress Created
Path: /wear
Path: /watch
Configure correct backend service for /wear
Configure correct backend service for /watch
Configure correct backend port for /wear service
Configure correct backend port for /watch service

##### A

- 這邊我們要建立在 app-space namespace 的 ingress，直接使用以下指令來建立 ingress.yaml。注意，因為 ingress 是 namespace scoped，所以要記得加上 namespace。
```bash
kubectl -n=app-space create ingress ingress-wear-watch --rule="/wear=wear-service:8080" --rule="/watch=video-servi
ce:8080" --dry-run=client -o yaml > ingress-wear-watch.yaml
```
- 然後修改 ingress-wear-watch.yaml，加上 annotations 並修改 pathType 為 Prefix
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
  creationTimestamp: null
  name: ingress-wear-watch
  namespace: app-space
spec:
  rules:
  - http:
      paths:
      - backend:
          service:
            name: wear-service
            port:
              number: 8080
        path: /wear
        pathType: Prefix
      - backend:
          service:
            name: video-service
            port:
              number: 8080
        path: /watch
        pathType: Prefix
status:
  loadBalancer: {}
```

TODO: 可以的話，讀完 role, rolebinding, clusterrole, clusterrolebinding 的部分，再來做這個練習。

## 26. Network Policies - Services & Networking

- Traffic 有兩種
  - Ingress Traffic: 進入 pod 的流量
  - Egress Traffic: 離開 pod 的流量
- 當我們要定義這個流量是哪一種時，請以自神作為主體
  - 進入我的，就是 Ingress Traffic
  - 離開我的，就是 Egress Traffic   
- 假設一個情況
  - 有一個 work node 中有
    - 三個 pod
      - front-end
      - back-end
      - database
    - 一個 service
  - 在正常情況下，request 的流向應該是
    - front-end -> back-end -> database
    - database -> back-end -> front-end
  - 但在 kubernetes 的預設情況底下，這些 pod 之間的通訊都是被允許的(藉由 IP 或 pod name 或 service)
  - 所以這時我們就需要 Network Policies 來限制這些流量
- Network Policies 是一個 Kubernetes 的 API 物件，用來定義 pod 之間的流量規則
- 就以上的情況來說，我們可以定義一個 Network Policy，來限制 back-end 只能接收 front-end 的 request、只能向 database 發送 request

### Network Policy yaml

- 我們用上面的範例，針對 DB 的 pod 來限制他只能接收 back-end 的 request
- 在寫這個給 DB pod 所使用的 Network Policy 時，我們要以 DB pod 作為主體來思考是 Ingress 還是 Egress
- DB pod 只能接收 back-end 的 request，所以這個 Network Policy 是 Ingress。
- 當 DB pod 收到 request 後，他要回應給 back-end，這樣的 Egress 就不需要寫了，因為 Ingress 允許了來自 back-end 的 request，所以同一個 request 的 response 會自動被允許。
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-policy
spec:
  podSelector:
    matchLabels:
      app: database
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: back-end
    ports:
    - protocol: TCP
      port: 3306
```
- 需要注意的是，如上這樣的 Network Policy ，對所有擁有 app: database label 的 pod 都會生效，即使是在不同的 namespace 中。
- 如果我們只想對特定的 namespace 生效，我們可以加上 namespaceSelector
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-policy
spec:
  podSelector: # 選擇要使用這個 networkPolicy 的 pod
    matchLabels:
      app: database
  policyTypes:
  - Ingress
  ingress:
  - from:
    ports:
    - protocol: TCP
      port: 3306
    - podSelector: # 選擇可接收 request 的 pod
        matchLabels:
          app: back-end
      namespaceSelector:
        matchLabels:
          name: app-space
```
- 如果不寫 podSelector，只寫 namespaceSelector，這個 db pod 就只能接收來自 app-space namespace 中所有 pod 的 request。而無法些收來自其他 namespace 的 pod 的 request。
- 而又如果，有一個在我的 kubernetes cluster 之外的 server，我想要讓他可以存取我的 db pod，這時我們可以使用 IPBlock
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-policy 
spec:
  podSelector:
    matchLabels:
      app: database
  policyTypes:
  - Ingress
  ingress:
  - from:
    ports:
    - protocol: TCP
      port: 3306
    - ipBlock:
        cidr: 192.168.5.10/32
    - podSelector:
        matchLabels:
          app: back-end
      namespaceSelector:
        matchLabels:
          name: app-space
```
- 再來我們說一下這些 ingress 底下的 `and` 和 `or`
```yaml
# 這樣寫就是 and
    - podSelector:
        matchLabels:
          app: back-end
      namespaceSelector:
        matchLabels:
          name: app-space
# 要同時滿足 app: back-end 和 name: app-space
# 也就是說，這個 db pod 只能接收來自 app-space namespace 中擁有 app: back-end label 的 pod 的 request
```
```yaml
# 這樣寫就是 or
    - podSelector:
        matchLabels:
          app: back-end
    - namespaceSelector:
        matchLabels:
          name: app-space
# 只要滿足 app: back-end 或 name: app-space
# 也就是說，這個 db pod 可以接收兩種來源的 request
# 1. 可以接收擁有 app: back-end label 的 pod 的 request，無論在哪個 namespace
# 2. 可以接收來自 app-space namespace 中所有 pod 的 request
```

- 當然，我們也可以同時設定 ingress 和 egress
- 以下這個 Network Policy 是設定 db pod 只能接收 back-end1 的 request(同時允許發送 response 給這個 request)，且只能發送 response 給 back-end2
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-policy
spec:
  podSelector:
    matchLabels:
      app: database
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: back-end1
    ports:
    - protocol: TCP
      port: 3306
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: back-end2
    ports:
    - protocol: TCP
      port: 3306
```

### Network Policy 的常用指令

```bash
# 使用 yaml 檔來建立 Network Policy
kubectl apply -f <network-policy.yaml>

# 查看 Network Policy 列表
kubectl get networkpolicy

# 查看 Network Policy 的詳細資訊
kubectl describe networkpolicy <network-policy-name>
```

### Practice

#### 10

##### Q

Create a network policy to allow traffic from the Internal application only to the payroll-service and db-service.


Use the spec given below. You might want to enable ingress traffic to the pod to test your rules in the UI.

Also, ensure that you allow egress traffic to DNS ports TCP and UDP (port 53) to enable DNS resolution from the internal pod.

Policy Name: internal-policy

Policy Type: Egress

Egress Allow: payroll

Payroll Port: 8080

Egress Allow: mysql

MySQL Port: 3306

##### A

1. Pod 間的關係如下圖

![alt text](image-1.png)

2. 首先找到要綁定的 payroll pod 和 db pod 的 label
```bash
controlplane ~ ➜  k get pod --show-labels 
NAME       READY   STATUS    RESTARTS   AGE   LABELS
external   1/1     Running   0          39m   name=external
internal   1/1     Running   0          39m   name=internal
mysql      1/1     Running   0          39m   name=mysql
payroll    1/1     Running   0          39m   name=payroll
```
3. 因為建立 network policy 沒辦法直接使用 imperative 的方式，所以我們可以去[官網](https://kubernetes.io/docs/concepts/services-networking/network-policies/)複製一個範例，然後修改。
4. 最後修改如下
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: internal-policy
  namespace: default
spec:
  podSelector: # 要和這個 network policy 綁定的 pod
    matchLabels:
      name: internal
  policyTypes:
  - Egress # 題目要求的是這個 pod 可以發送 request 到 payroll 和 mysql，所以是 Egress
  egress:
  - to: # 一個資源使用一個 to
    - podSelector:
        matchLabels:
          name: payroll
    ports:
    - protocol: TCP
      port: 8080
  - to:
    - podSelector:
        matchLabels:
          name: mysql
    ports:
    - protocol: TCP
      port: 3306
```

## 27. Volumes - State Persistence

- 在 docker 中，當我們把 container 刪除後，裡面的資料也會一併刪除，所以 docker 有個 volume 的機制，當在 container 運行中產生的資料，會使用 volume 將資料存在 host 中，當 container 刪除後，資料還是會存在。且下一次再建立 container 時，可以指定使用這個 volume，以此來保留資料。
- kubernetes 中也有一樣的機制，也叫做 volume。

### Volume yaml

- 以下是一個 pod 使用 volume 的 yaml
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: alpine # alpine 是一個很小的 linux image，可以用來執行一些簡單的測試指令
    command: 
    - "/bin/sh"
    - "-c"
    args:
      - "shuf -i 0-100 -n 1 >> /opt/number.out && cat /opt/number.out"
    volumeMounts: # 這個 container 要掛載的 volume
    - mountPath: /opt # 掛載到 container 中的路徑
      name: myvolume
  volumes: # 定義 volume
  - name: myvolume
    hostPath: # 指在主機上，這個 volume 的路徑
      path: /data
      type: DirectoryOrCreate # 如果這個路徑不存在，就會自動建立
```

## 28. Persistent Volumes - State Persistence

- 上一章中，我們直接在 pod 的 definition 中定義了 volume，也就是說關於著個 volume 的定義內容是寫在 pod 的 yaml 中的。與 pod 綁訂在一起了。
- 如果我們的專案非常大，有非常大量的 pod，萬一你需要修改 volume 的定義，你就需要修改所有 pod 的 yaml，這樣是非常不方便的。
- 因此，kubernetes 提供了 Persistent Volumes 這個機制，讓我們可以將 volume 的定義獨立出來。
- PV 是 Kubernetes 集群中的一個存儲資源，它是由管理員設定或動態創建的，並且是 cluster-level 的資源。
- 可以把 PV 想像成已經存在的一塊磁碟或網路存儲設備，供 Pod 使用。
- PV 中有兩個重點觀念
  - Access Modes
  - Reclaim Policy
- Access Modes
  - 這個 PV 可以被掛載的模式
  - 有三種
    - ReadWriteOnce: 只能被一個 pod 掛載為讀寫模式
    - ReadOnlyMany: 可以被多個 pod 掛載為唯讀模式
    - ReadWriteMany: 可以被多個 pod 掛載為讀寫模式
- Reclaim Policy
  - 當 PV 被釋放時(PVC 被刪除)，PV 中的資料要怎麼處理
  - 有三種
    - Retain: 保留 PV 中的資料，不自動刪除，且這個 PV 不能被再次綁定
    - Recycle: 刪除 PV 中的資料，但保留 PV 供下次使用
    - Delete: 刪除 PV 中的資料，並刪除 PV

### Persistent Volume yaml

- 以下是一個 Persistent Volume 的 yaml
```yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mypv
sepc:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 1Gi
  hostPath:
    path: /data # 教學中提到這個 option 並不適合 production 環境，原因我還不知道
```

### 常用指令

```bash
# 使用 yaml 檔來建立 Persistent Volume
kubectl apply -f <persistent-volume.yaml>

# 查看 Persistent Volume 列表
kubectl get pv
kubectl get persistentvolume

# 查看 Persistent Volume 的詳細資訊
kubectl describe pv <persistent-volume-name>
```

## 29. Persistent Volume Claims - State Persistence

- Persistent Volume 和 Persistent Volume Claim 是 namespace 中，兩種獨立的物件。
- 管理員(?)負責建立 Persistent Volume，而使用者則負責建立 Persistent Volume Claim 來使用 Storage。
- Persistent Volume Claim 簡稱為 PVC。
- 當 PVC 一被建立，kubernetes 會將 PVC 和 PV 進行配對，並將 PV 的資源分配給 PVC。
- 每一個 PVC 只能對應一個 PV。
- 它們配對的條件有很多
  - Sufficient Capacity
  - Access Modes
  - Volume Mode
  - Storage Class
  - Selector
- 而因為它們之間的關係是一對一的，如果有一個提供大容量(Sufficient Capacity)的 PV，而 PVC 只要求小容量，但又沒有其他更適合的 PV，這個 PVC 就會被配對到這個 PV。這樣的狀況就會造成資源浪費。
- PVC 是 Pod 向 Kubernetes 發出的請求，要求使用一個 PV。
- 可以把 PVC 想像成 Pod 對持久性存儲的需求聲明。Pod 不會直接與 PV 互動，而是透過 PVC 來向 Kubernetes 說明它需要多大的存儲空間、要怎麼存取這個存儲（例如可讀可寫）等。
- 簡單來說就是
  1. POD 需要存儲空間
  2. POD 以 PVC 的方式向 K8s 請求存儲空間
  3. K8s 會將 PVC 和 PV 進行配對，並將 PV 的資源分配給 PVC
  4. POD 從而得到存儲空間
- 換個更 k8s 的說法
  1. 管理員創建或動態分配 PV。
  2. 使用者或應用程式提交一個 PVC，定義所需的存儲大小和存取模式。
  3. Kubernetes 會找到一個匹配 PVC 要求的 PV，並將兩者綁定。
  4. 一旦綁定，PVC 會被 Pod 使用，Pod 內部的資料可以持久化到這個 PV 上。


### Persistent Volume Claim yaml

- 以下是一個 Persistent Volume Claim 的 yaml
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi
```

- 以下提供一個 pv yaml
```yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mypv
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 1Gi
  awsElasticBlockStore:
    volumeID: <volume-id>
    fsType: ext4
```

- 在 Pod 中這樣使用 PVC
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: alpine
    command:
    - "/bin/sh"
    - "-c"
    args:
    - "shuf -i 0-100 -n 1 >> /opt/number.out && cat /opt/number.out"
    volumeMounts:
    - mountPath: /opt
      name: myvolume
  volumes:
  - name: myvolume
    persistentVolumeClaim:
      claimName: mypvc
```

### 常用指令

```bash
# 使用 yaml 檔來建立 Persistent Volume Claim
kubectl apply -f <persistent-volume-claim.yaml>

# 查看 Persistent Volume Claim 列表
kubectl get pvc

# 查看 Persistent Volume Claim 的詳細資訊
kubectl describe pvc <persistent-volume-claim-name>
```

### Practice

#### 2

##### Q

Pod is already created.
The application stores logs at location /log/app.log. View the logs.

##### A

1. 先找到 pod
```bash
k get pod
```
2. 進入 pod
```bash
k exec -it <pod-name> -- /bin/sh
```
或直接查看指定路徑的檔案
```bash
k exec -it <pod-name> -- cat /log/app.log
```

#### 3

##### Q

If the POD was to get deleted now, would you be able to view these logs.

##### A

1. 使用以下指令查看 pod 的 volume
```bash
k describe pod <pod-name>
```
```bash
controlplane ~ ➜  k describe pod webapp 
Name:             webapp
Namespace:        default
Priority:         0
Service Account:  default
Node:             controlplane/192.18.134.6
Start Time:       Sun, 13 Oct 2024 12:20:56 +0000
Labels:           <none>
Annotations:      <none>
Status:           Running
IP:               10.244.0.4
IPs:
  IP:  10.244.0.4
Containers:
  event-simulator:
    Container ID:   containerd://f85c6a7913a64c26960ce76343de5ca18d8b7486bbe8a8c3c874490cc483da11
    Image:          kodekloud/event-simulator
    Image ID:       docker.io/kodekloud/event-simulator@sha256:1e3e9c72136bbc76c96dd98f29c04f298c3ae241c7d44e2bf70bcc209b030bf9
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Sun, 13 Oct 2024 12:21:00 +0000
    Ready:          True
    Restart Count:  0
    Environment:
      LOG_HANDLERS:  file
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-5rgfw (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True 
  Initialized                 True 
  Ready                       True 
  ContainersReady             True 
  PodScheduled                True 
Volumes:
  kube-api-access-5rgfw:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
```
2. 從這個 volumn 的名稱，就可以得知它是 k8s 預設的 volume，裡面只是存一些必要的配置和憑證信息。例如，kube-api-access-* 這類 Volume 是用來存儲服務賬戶憑證和 API 訪問相關的訊息。所以這個 pod 的 volume 是沒有掛載到任何外部存儲的。而這個 volumn 的生命週期是和 pod 一樣的，當 pod 被刪除時，這個 volume 也會被刪除。
```bash
Volumes:
  kube-api-access-5rgfw:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
```

#### 4

##### Q

Configure a volume to store these logs at /var/log/webapp on the host.
Use the spec provided below.
Name: webapp
Image Name: kodekloud/event-simulator
Volume HostPath: /var/log/webapp
Volume Mount: /log

##### A

- 可能我觀念不夠清楚，這題不太會解，直接看答案了
- 下面解釋一下
  - volumeMounts    
    - 定義了容器內部的掛載點，也就是容器內的某個目錄將會掛載到 Kubernetes 定義的某個存儲卷（volume）上。
    - 在這個範例中
      - mountPath: /log 指定容器內的 /log 目錄。
      - name: webapp 指定了要掛載的 volume 的名稱。
      - 這意味著當容器寫入 /log 目錄時，實際上是寫入到與該 volume 對應的存儲設備或主機目錄中。
  - volumes    
    - 這表示這個 Volume 將掛載到主機節點上的 /var/log/webapp 目錄。hostPath 類型的 Volume 允許 Pod 訪問主機節點上的文件系統。
    - 在這個範例中    
      - name: log-volume 為這個 volume 命名，這個名字與 volumeMounts 中的 name 字段對應。
      - hostPath 表示這個 volume 來自主機目錄 /var/log/webapp。
      - type: Directory 是可選的，表示 path 必須是個目錄，且 Kubernetes 不會自動創建這個目錄。如果目錄不存在，Pod 可能會報錯。
  - volumeMounts 依賴於 volumes，因為 volumeMounts 指定的是容器內部的掛載點，而 volumes 則決定這個掛載點要對應的外部存儲。
  - 簡單來說，volumes 定義了<span style="color:red">外部存儲</span>，volumeMounts 定義了<span style="color:red">容器內部的掛載點</span>。
  - 從掛載的角度來說，volumeMounts 指定的是容器內部的掛載點，而 volumes 則決定這個掛載點要對應的外部存儲。
  
1. 這題的最終目的就是要為這個 pod 建立 volume，把 container 內的 /log 掛載到 host 的 /var/log/webapp
2. 因為我們要修改這個 pod 的 definition，所以我們心把它 backup 起來
```bash
k get pod webapp -o yaml > webapp.backup.yaml
```
3. 然後修改這個 pod 的 definition
```bash
k edit pod webapp
```
4. 在 spec.volumes 底下加上以下 yaml
```yaml
volumes:
- name: log-volume
  hostPath:
    path: /var/log/webapp
```
5. 現在 volume 建好了，就來建立 volumeMounts，把它跟 volume 綁定。在 spec.containers.volumeMounts 底下加上以下 yaml
```yaml
volumeMounts:
- mountPath: /log
  name: log-volume # 這個名字要和上面的 volume 的 name 一樣，才能綁定
```
6. 這樣就可以 `:wq` 保存並退出了
7. 這時也是會出現錯誤訊息，告訴你這個 pod 已經在運行中，所以你要先刪除這個 pod，再重新建立，但我們有幫你把你的修改存在一個位置。
8. 所以我們就直接使用它提供的檔案位置，搭配`replace --force`來重新建立 pod
```bash
k replace --force -f <錯誤訊息中的檔案位置>
```
9. 此時我們可以在 pod 中 volumeMounts 的 mountPath 中指定的位置找到我們的 volume
```bash
k exec -it webapp -- cat /log/app.log
```
10. 也可以在我們本機找到我們的 volume，位置就在 volumes.hostPath.path 中所指定的位置
```bash
ls /var/log/webapp
```

#### 5

##### Q

Create a Persistent Volume with the given specification.

Volume Name: pv-log

Storage: 100Mi

Access Modes: ReadWriteMany

Host Path: /pv/log

Reclaim Policy: Retain

##### A

- 文件中的 pv 有很多種，這邊要使用 hostPath，可以在[這邊](https://kubernetes.io/docs/tasks/configure-pod-container/configure-persistent-volume-storage/#create-a-persistentvolume)找到
- 文件關鍵字是 `Configure a Pod to Use a PersistentVolume for Storage`

1. 把 yaml 複製下來，並依照題目修改
```yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-log
spec:
  accessModes:
    - ReadWriteMany
  capacity:
    storage: 100Mi
  hostPath:
    path: /pv/log
  persistentVolumeReclaimPolicy: Retain
```

2. 使用以下指令建立 pv
```bash
k apply -f <pv.yaml>
```

#### 6

##### Q

Let us claim some of that storage for our application. Create a Persistent Volume Claim with the given specification.

Volume Name: claim-log-1

Storage Request: 50Mi

Access Modes: ReadWriteOnce

##### A

- 這個 yaml 一樣要去複製
- 這題的 yaml 如下
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: claim-log-1
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Mi
```

#### 7

##### Q

Why is the claim not bound to the available Persistent Volume?

##### A

- 正常情況下，PV 會根據 PVC 的需求來配對，但如果 PV 的需求不符合 PVC 的需求，就不會配對。
- 沒有配對成功的原因可能有很多，例如
  - Access Modes 不符
  - Storage 不足
  - Storage Class 不符
  - Selector 不符
- Storage 的部分要注意一下，PVC 會 Claim(宣告)它需要的容量，容量可以大於我的需求，但不能小於我的需求。在這題裡面就是 PVC 要求 50Mi，但 PV 有 100Mi，大於我的需求，那也是可以配對的，只是會造成浪費而已。

1. 先把 PV 及 PVC 的 yaml 印出來看看
```bash
controlplane ~ ➜  cat pvc.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: claim-log-1
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Mi
```
```bash
controlplane ~ ➜  cat pv2.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-log
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 100Mi
  accessModes:
    - ReadWriteMany
  hostPath:
    path: "/pv/log"
```
2. 從上面印出來的 yaml definition 中，我們可以看到
  - PV 所提供的 Storage 大於 PVC 所需求的 Storage，那可以配對
  - PV 的 Access Modes 是 ReadWriteMany，而 PVC 的 Access Modes 是 ReadWriteOnce，這個 Access Modes 不符，所以不會配對
  - Storage Class 我指定了 manual，但 PVC 沒有指定，所以也不會配對，題目並沒有要求我們指定 Storage Class，但因為我複製的 yaml 有指定，所以這裡就要注意一下，把它刪掉

#### 12

##### Q

Update the webapp pod to use the persistent volume claim as its storage.

Replace hostPath configured earlier with the newly created PersistentVolumeClaim.

Name: webapp

Image Name: kodekloud/event-simulator

Volume: PersistentVolumeClaim=claim-log-1

Volume Mount: /log

##### A

1. 因為要修改 pod 的 definition，所以我們先把它 backup 起來
```bash
controlplane ~ ➜  k get pod webapp -o yaml > webapp.backup2.yaml 
```
2. 然後修改這個 pod 的 definition
```bash
controlplane ~ ➜  k edit pod webapp
```
3. 在 spec.volumes 底下加上以下 yaml
```yaml
  - name: pv-log
    persistentVolumeClaim:
      claimName: claim-log-1
```

> 如果你想要刪除 PVC，但他被 pod 使用時，它會卡在 Terminating 狀態，這時你可以先刪除 pod，再刪除 PVC

> 當 pv 的 Reclaim Policy 是 Retain 時，當 PVC 被刪除時，pv 會保留，但是 pv 會變成 Released 狀態，這時就算你建立新的 PVC 也不會被綁定   
- 如果你的 PV 是可使用的，它的狀態會是 Available

## 30. Authentication and Admission Control - Security

- 在 kubernetes 中的安全機制，我們需要決定兩個問題
  - 誰可以存取
  - 可以存取的人，可以做什麼
- Authentication 決定誰可以存取，驗證的方式又分為以下幾種
  - File - Username and Password
  - File - Username and Token
  - Certificate
  - External Authentication provider - LDAP, AD...etc
  - Service Account
- Authorization 也決定可存取的人可以做甚麼
  - RBAC
  - ABAC
  - Webhook
  - Webhook

- 接下來的章節會好好介紹以上的機制

## 31 Authentication - Security

- 我們先來假設一下會有哪些腳色
  - Admins: 管理員
  - Developer: 開發者
  - Application End User: 應用程式的使用者。但這個腳色的權限是由應用程式決定的，不是由 k8s 決定的，所以這邊我們不討論它
  - Bots: 機器人是指那些如 CI/CD 工具，自動化測試工具等等，會透過 service account 來存取 k8s 的資源的物件。在這邊我們也不討論它
-  kube-apiserver 是 Kubernetes 集群的核心組件之一，負責處理所有 API 請求。是集群中所有其他組件和客戶端與集群進行交互的主要接口。主要功能包含以下
  - API 端口    
    提供 RESTful API 來管理集群中的所有資源(如 Pod、Service、ReplicationController 等)。
  - 身份驗譸與授權

- Kube-apiserver 會使用一個叫做 Authenticator 的模組來驗證使用者的身份，可以透過以下資訊來進行驗證與授權
  - Static password file
  - Static token file
  - Client certificates
  - Identity service

### Auth Mechanisms - Static Password File

- 這個方法是最簡單的方法，就是在 kube-apiserver 的參數中指定一個檔案，裡面存放使用者的帳號和密碼
- 例如我們建一個檔案叫做 `user-details.csv`
```csv
password123,user1,u0001,admin
password123,user2,u0002,developer
```
- 我們將它作為一個參數傳入 kube-apiserver
```bash
--basic-auth-file=user-details.csv
```
- 把它寫在 kube-apiserver 的 yaml 中，並重新啟動 kube-apiserver，就可以使用這個檔案來驗證使用者的身份了
- 這時我們就可以使用已下指並搭配 `curl` 來驗證使用者的身份
```bash
curl -v -k https://<master-node-ip>:6443/api/v1/pods -u user1:password123
```

### Auth Mechanisms - Static Token File

- 這個方法是使用 token 來驗證使用者的身份
- 這個方法的好處是，token 可以有一個過期時間，而且可以隨時被撤銷
- 這個方法的使用方式和 Static Password File 一樣，只是在 kube-apiserver 的參數中指定一個檔案，裡面存放使用者的 token
- 例如我們建一個檔案叫做 `user-tokens.csv`
```csv
token1,user1,u0001,admin
token2,user2,u0002,developer
```
- 我們將它作為一個參數傳入 kube-apiserver
```bash
--token-auth-file=user-tokens.csv
```
- 把它寫在 kube-apiserver 的 yaml 中，並重新啟動 kube-apiserver，就可以使用這個檔案來驗證使用者的身份了
- 這時我們就可以使用已下指令來驗證使用者的身份
```bash
curl -v -k https://<master-node-ip>:6443/api/v1/pods -H "Authorization: Bearer token1"
```

> 教學中說並不推薦使用以上兩種方法，因為這兩種方法的密碼或 token 都不夠安全。

> 以上兩種 Basitc authentication on kubernetes 已經在 1.19 版本中被移除了，所以如果你的 k8s 版本是 1.19 以上，就不要使用這兩種方法了。反正也不安全。

> 要先說明一下    
> 如何產生 certificates 給不同的 kubernetes 元件和使用者、以及如何在 kubernetes cluster 中使用它們，並不在 CKAD 的範圍內。    
> 注意，只有"產生"與如何"在kubeconfig檔案中使用"這兩個部分不在 CKAD 的範圍內。
> 而是屬於 CKA 的範圍。

## 32. KubeConfig - Security

- 我們在使用指令時，其實後面有一些參數是需要指定的，才能以正確的身份來執行指令
```bash
k get pods \
  --server my-kube-playground:6443 \
  --client-key admin.key \
  --client-certificate admin.crt \
  --certificate-authority ca.crt
```
```bash
curl -v -k https://my-kube-playground:644/api/v1/pods \
  --key admin.key \
  --cert admin.crt \
  --cacert ca.crt
```
- 但這樣每次都需要寫一堆參數，所以 k8s 提供了一個叫做 kubeconfig 的檔案，我們可以把這些參數定義到這個檔案中，就可以使用以下的方法來執行指令
```bash
k get pod --kubeconfig config
```
- 而在預設的狀況下，kubernetes 會直接尋找 `$HOME/.kube/config` 這個檔案來使用，所以我們如果是使用預設的檔案來執行指令，就可以直接使用以下的方法。除非你有客製的 kubeconfig 檔案，那就是使用前一個方法
```bash
k get pod
```

### kubeconfig file

- 這個文件分為三個部分
  - Cluster
  - Context
  - User
- Cluster
  - 這個部分定義不同的 cluster 組織，例如
    -  Development
    -  Production
    -  Staging
- User
  - 這個部分定義使用者的身份，這些使用者在不同的 cluster 中可能有不同的權限，例如
    -  Developer
    -  Admin
    -  CI/CD Bot
- Context
  - 我們可以使用 Context 來指定使用者要使用哪個 cluster，以及使用哪個身份來執行指令
  - 例如
    -  Developer@Development
    -  Admin@Production
    -  CI/CD Bot@Staging 

### kubeconfig file yaml

- 以下是一個 kubeconfig 的 yaml
```yaml
apiVersion: v1
kind: Config
clusters:
- name: development
  cluster:
    server: https://development-server:6443
    certificate-authority: /path/to/ca.crt
- name: production
  cluster:
    server: https://production-server:6443
    certificate-authority-data: <base64-encoded-ca.crt>
users:
- name: developer
  user:
    client-certificate: /path/to/developer.crt
    client-key: /path/to/developer.key
- name: admin
  user:
    client-certificate: /path/to/admin.crt
    client-key: /path/to/admin.key
contexts:
- name: developer@development
  context:
    cluster: development
    user: developer
    namespace: backend # 指當我們切換使用這個 context 時，我們會進入的預設 namespace
- name: admin@production
  context:
    cluster: production
    user: admin
current-context: developer@development
```

- 其中的 `certificate-authority-data` 是 ca.crt 的 base64 編碼，可以使用以下指令來獲得
```bash
cat ca.crt | base64
```
- 而如果你在 config 檔案中看到這個 base64 編碼的資料，你可以使用以下指令來解碼
```bash
echo <base64-encoded-ca.crt> | base64 -d
```

### 常用指令

```bash
# 查看 kubeconfig 的內容
k config view

# 指定 current-context
k config use-context <context-name>

# 查看當前的 context
k config current-context

# 指定 kubeconfig 檔案
kubectl config --kubeconfig=/root/my-kube-config use-context <context-name>

# 查看還有哪些指另可以使用
k config --help
```

### Practice

#### 1

##### Q

Where is the default kubeconfig file located in the current environment?

##### A

- 在 linux 環境中，預設的 kubeconfig 檔案是在 `/root/.kube/config`

#### 12

##### Q

指定我們克制的 kubeconfig 檔案，並指定我們的 context 為 `research`

##### A

```bash
k get pods --kubeconfig /root/my-kube-config --context research
```

#### 13

##### Q

We don't want to have to specify the kubeconfig file option on each command.

##### A

- 這題的目的是要讓我們不用每次都指定 kubeconfig 檔案，所以我們可以直接把我們克制的 kubeconfig 檔案放到 `root/.kube/` 中，取代原本的 kubeconfig 檔案
```bash
cp /root/my-kube-config /root/.kube/config
```

## 33. API groups - Security

開始之前，我們先再複習一下 API server

### API Server

- API server 是 Kubernetes 集群的核心組件之一，負責處理所有 API 請求。是集群中所有其他組件和客戶端與集群進行交互的主要接口。主要功能包含以下
  - 處理請求    
    API server 是 k8s 的門戶，所有對 Clusters 的操作都是通過 API server 進行的。
  - 驗證與授權    
    API server 會使用一個叫做 Authenticator 的模組來驗證使用者的身份，可以透過以下資訊來進行驗證與授權
      - Static password file
      - Static token file
      - Client certificates
      - Identity service
  - 集群狀態的存儲宇同步    
    API server 會從 etcd 中讀取集群的狀態，並在狀態更新時將新的狀態寫回 etcd 中。
  - 與 Controller Manager 和 Scheduler 的溝通    
    API server 會與 Controller Manager 和 Scheduler 進行溝通，以確保集群中的所有資源都處於正確的狀態。
- API server 的工作流程
  - 接收請求
  - 身分驗證
  - 授權
  - 准入控制(Admission Control)
  - 執行請求
  - 更新 etcd
  - 返回結果

### API Groups

- API groups 分為兩種
  - Core API Group(核心 API 組)   
    - 這是最原始的 API 組，也是唯一沒有明確定義 group 的 API。
    - 其資源的路徑是 `/api/v1`
    - 經常使用的資源有包含但不限於
      - Pods
      - Services
      - ReplicationControllers
      - Nodes
      - Namespaces...etc
  - Named API Group(命名空間 API 組)    
    - 這個 API 組更有組織性，所有的新功能都會被放到這個 API 組中。
    - 這個 API 組的資源路徑是 `/apis`
    - 例如
      - `/apps`
      - `/extensions`
      - `/networking.k8s.io`
      - `/storage.k8s.io`
      - `/authentication.k8s.io`
      - `/certificates.k8s.io`...etc
    - 以 `/apps` 為例，在 `/apis/apps` 後加上版本號，如 `/apis/apps/v1`，再接上資源名稱及動詞，就可以最資源進行操作，如 : 
      - `/apis/apps/v1/deployments` + list
      - `/apis/apps/v1/deployments` + create
      - `/apis/apps/v1/deployments` + delete
      - `/apis/apps/v1/deployments` + get
      - `/apis/apps/v1/deployments` + update

## 34. Authorization - Security

- 在 k8s 中，會有很多種腳色需要進入 k8s 來執行指令，例如
  - 管理員
  - 開發者
  - Some application like Jenkins
- 因此我們透過建立 account、password、token、certificate 或 service account 來進行身份驗譑，但這只是驗證身份，還需要進行授權，才能決定這個使用者可以做甚麼事情
- 但我們不希望每種腳色都有相同的權限，所以我們需要進行授權，來決定這個使用者可以做甚麼事情，例如
  - Developer 只能 update deployment
  - Admin 可以 delete deployment

### Authorization Mechanisms 授權機制

- 將會分為以下 6 種
  - Node Authorization
  - ABAC (Attribute-Based Access Control)
  - RBAC (Role-Based Access Control)
  - Webhook
  - AlwaysDeny
  - AlwaysAllow

#### Node Authorization

- Node Authorization 是 Kubernetes 中專門用於節點（Node）的授權機制。
- 這裡的 Node 指的是 worker node。
- 目的    
  - 限制節點只能訪問與其相關的資源。增強集群的安全性。
- 工作原理    
  - 當一個節點發送 API 請求時，API server 會檢查該節點的身分。
  - Node Authorization 檢查請求是否與該節點被允許訪問的資源匹配。
  - 如果匹配，則請求被允許；否則，請求被拒絕。

#### ABAC (Attribute-Based Access Control) 基於屬性的訪問控制

- ABAC 是一種基於屬性的訪問控制機制，它使用策略文件來定義哪些使用者可以訪問哪些資源。
- 所謂屬性包含但不限於
  - username 用戶的身分
  - pod、service、namespace 等請求的資源類型
  - get、list、create、update、delete 等請求的動作
  - namespace、labels、annotations 等資源的屬性
- 工作原理
  - 當用戶發送 API 請求時，API Server 會基於該請求的屬性（如用戶身份、資源類型等）查找相應的 ABAC 策略。    
  - 如果某條策略允許該操作，則請求被授權，否則請求被拒絕。
- ABAC 的靈活性很高，但是管理起來比較困難，因為當集群中的資源增加時，ABAC 策略文件也會變得越來越複雜。例如
```json
{
  "user": "admin",
  "resource": "pods",
  "namespace": "default",
  "readonly": true
}
```
- 以上表示這表示用戶 admin 可以在 default 命名空間內對 pods 執行讀取操作。

#### RBAC (Role-Based Access Control) 基於角色的訪問控制

- RBAC 是 Kubernetes 中最常用的授權模型之一，基於用戶角色來決定他們對資源的訪問權限。RBAC 允許你定義角色（Roles），然後將這些角色分配給用戶或服務帳戶。
基於角色：RBAC 授權決定是基於角色（Role）來實現的，這些角色定義了對某些資源的操作權限。
Role：適用於單個命名空間內的資源。
ClusterRole：適用於集群範圍內的資源。
Binding（綁定）：RoleBinding 和 ClusterRoleBinding 將角色分配給具體的用戶、群組或服務帳戶。
預定義角色：Kubernetes 提供了一些內建的 ClusterRole（如 admin、edit、view）以方便使用。
工作流程：
定義一個角色，指定該角色擁有的資源訪問權限。
創建一個 RoleBinding 或 ClusterRoleBinding，將該角色分配給用戶或服務帳戶。
當用戶發送 API 請求時，RBAC 會檢查該用戶是否有與該請求匹配的角色授權，如果有，則授權該操作。

##### yaml

- 以下是一個 role 的 yaml definition
```YAML
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
- apiGroups: [""]
  resources: ["deployments"]
  verbs: ["get", "watch", "list"]
```

- 我們也可以針對 resource name 來指定權限
- 在同一個 namespace 中，可能會有多個 pod，但我們只想要讓這個 role 可以存取其中兩個 pod，這時我們可以使用 resourceNames 來指定
```YAML
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
  resourceNames: ["my-pod", "my-pod2"]
```

- 以下是一個 rolebinding 的 yaml definition
```YAML
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
- kind: User
  name: admin
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
```

##### 常用指令

```bash
# 建立 role 和 rolebinding
k create -f role.yaml
k create -f rolebinding.yaml

# 查看 role 和 rolebinding
k get role
k get rolebinding

# 查看 role 和 rolebinding 的詳細資訊
k describe role pod-reader
k describe rolebinding read-pods

# 刪除 role 和 rolebinding
k delete role pod-reader
k delete rolebinding read-pods

# 確認目前腳色對某個資源的權限
k auth can-i get pods 

# 確認某個腳色對某個資源的權限
k auth can-i get pods --as admin
```


#### Webhook 


#### AlwaysDeny & AlwaysAllow


#### Practice - RBAC

#### 1

##### Q

Inspect the environment and identify the authorization modes configured on the cluster.

Check the kube-apiserver settings.

##### A

- 這題的目的是要讓我們去查看 kube-apiserver 的設定，來確誫授權模式是如何設定的
- kube-apiserver 的設定檔案是在 `/etc/kubernetes/manifests/kube-apiserver.yaml`
- 這邊我們可以使用 `cat` 來查看這個檔案
```bash
cat /etc/kubernetes/manifests/kube-apiserver.yaml
```
- 或是可以查看在 kube-system namespace 中的 kube-apiserver-controlplane pod
```bash
kubectl describe pod kube-apiserver-controlplane -n kube-system
```
- 這時我們可以看到 `--authorization-mode` 的設定，這個設定就是授權模式的設定

#### 8

##### Q

A user dev-user is created. User's details have been added to the kubeconfig file. Inspect the permissions granted to the user. Check if the user can list pods in the default namespace.

Use the --as dev-user option with kubectl to run commands as the dev-user.

##### A

1. 先來看一下它新增的 user 的資訊，這東西要去 kubeconfig 檔案中找
```bash
cat .kube/config
```
2. 然後我們有兩種方法來看指定的 dev-user 的權限
  - 一種是使用 `k auth can-i` 來查看
  - 一種是使用 `--as` 來切換使用者，然後再查看
```bash
k auth can-i get pods --as dev-user
```
```bash
k get pods --as dev-user
```

#### 9

##### Q

Create the necessary roles and role bindings required for the dev-user to create, list and delete pods in the default namespace.

Use the given spec:
Role: developer
Role Resources: pods
Role Actions: list
Role Actions: create
Role Actions: delete
RoleBinding: dev-user-binding
RoleBinding: Bound to dev-user

##### A

1. 因為我沒有背直接使用指令建立 role 和 rolebinding，所以我們可以使用以下指令來找到範例
```bash
k create role -h
k create rolebinding -h
```
2. 先來建立 role
```bash
k create role developer --resource=pods --verb=list --verb=create --verb=delete
```
3. 再來建立 rolebinding
```bash
k create rolebinding dev-user-binding --role=developer --user=dev-user --namespace=default
```

#### 10

##### Q

A set of new roles and role-bindings are created in the blue namespace for the dev-user. However, the dev-user is unable to get details of the dark-blue-app pod in the blue namespace. Investigate and fix the issue.


We have created the required roles and rolebindings, but something seems to be wrong.

##### A

1. 先來使用 dev-user 來 describe 一下 dark-blue-app pod，看看是不是真的沒有權限
```bash
k describe pod dark-blue-app -n blue --as dev-user
```
2. 來看一下 role 和 rolebinding 的詳細資訊
```bash
k describe role developer -n blue
k describe rolebinding dev-user-binding -n blue
```
3. 我們發現 role 中指定的 resourceName 寫錯了，他指定成 `blue-app`，但實際上是 `dark-blue-app`，所以我們要修改 role 的 resourceName
```bash
k edit role developer -n blue
```
4. 修改 resourceName 後，再來看一下 dev-user 是否有權限
```bash
k describe pod dark-blue-app -n blue --as dev-user
```

#### 11

##### Q

Add a new rule in the existing role developer to grant the dev-user permissions to create deployments in the blue namespace.

Remember to add api group "apps".

##### A

1. 使用 `k edit role` 來修改 role 的 yaml
```bash
k edit role developer -n blue
```
2. 在 rules 底下加上以下 yaml
```yaml
- apiGroups:
  - apps
  resources:
  - deployments
  verbs:
  - create
```

## 35. Cluster Roles - Security

- role 和 rolebinding 是針對 namespace scope 的資源進行授權。例如
  - Pods
  - ReplicaSets
  - Jobs
  - Deployments
  - Services
  - Secrets
  - ConfigMaps
  - PersistentVolumeClaims
- 但有時，有些資源是屬於 cluster scope，例如
  - Nodes
  - PersistentVolumes
  - Namespaces
  - CertificateSigningRequests
- 這時我們就需要使用 cluster role 和 cluster rolebinding 來進行授權
- cluster role 和 role 的差別在於
  - role 是針對 namespace scope 的資源進行授權
  - cluster role 是針對 cluster scope 的資源進行授權
- cluster role 和 rolebinding 的使用方式和 role 和 rolebinding 是一樣的

### Practice - Cluster Roles

#### 7 

##### Q

A new user michelle joined the team. She will be focusing on the nodes in the cluster. Create the required ClusterRoles and ClusterRoleBindings so she gets access to the nodes.

Grant permission to access nodes

##### A

1. 因為我沒有背直接使用指令建立 cluster role 和 cluster rolebinding，所以我們可以使用以下指令來找到範例
```bash
k create clusterrole -h
k create clusterrolebinding -h
```
2. 先來建立 cluster role
```bash
kubectl create clusterrole node-admin --verb=* --resource=nodes
```
3. 再來建立 cluster rolebinding
```bash
kubectl create clusterrolebinding michelle-node-admin --clusterrole=node-admin --user=michelle
```

#### 8

##### Q

michelle's responsibilities are growing and now she will be responsible for storage as well. Create the required ClusterRoles and ClusterRoleBindings to allow her access to Storage.

Get the API groups and resource names from command kubectl api-resources. Use the given spec:

ClusterRole: storage-admin
Resource: persistentvolumes
Resource: storageclasses
ClusterRoleBinding: michelle-storage-admin
ClusterRoleBinding Subject: michelle
ClusterRoleBinding Role: storage-admin

##### A

1. 先來使用 `k api-resources` 來查看 storageClasses 和 persistentVolumes 的 API groups 和 resource names
```bash
k api-resources
```
2. 結果如下
```bash
NAME                                SHORTNAMES   APIVERSION                        NAMESPACED   KIND
storageclasses                      sc           storage.k8s.io/v1                 false        StorageClass
persistentvolumes                   pv           v1                                false        PersistentVolume
```
3. 再來建立 cluster role。f其實你使用以下指令的時候，apiGroup它會自動幫你加上的，所以你不用特別指定
```bash
kubectl create clusterrole storage-admin --verb=* --resource=persistentvolumes --resource=storageclasses 

# 或是你也可以這樣寫，就先產生 yaml 檔案，然後再 apply
kubectl create clusterrole storage-admin --verb=* --resource=persistentvolumes --resource=storageclasses --dry-run=client -o yaml > storage-admin.yaml
```
4. 再來建立 cluster rolebinding
```bash
kubectl create clusterrolebinding michelle-storage-admin --clusterrole=storage-admin --user=michelle
```

## 36. Admission controllers - Security

- 我們知道我們每次發送請求，例如 create pod，請求都會背發送到 API server，然後 API server 會進行驗證、授權、准入控制、執行請求、更新 etcd、返回結果
  1. 認證（Authentication）： 驗證用戶身份。
  2. 授權（Authorization）： 檢查用戶是否有權執行該操作。
  3. 准入控制（Admission Control）： 進行請求的驗證和變異操作。這個步驟決定請求是否被允許進入 Kubernetes 集群。
  4. 持久化（Persistence）： 如果准入控制器允許請求，那麼資源的狀態會被更新，並存儲在 etcd 中。
- 但有時我們希望在請求進入 API server 之前，就可以對請求進行一些檢查，這時我們就可以使用 Admission controllers 來進行檢查
- Admission Controller（准入控制器）是一組攔截 API 請求的插件，用來決定是否允許該請求進行特定操作。這些插件在 Kubernetes API Server 中運行，用於執行額外的驗證、修改或限制操作，以確保集群安全性和資源管理的準確性。
- Admission Controller 可以分為兩類
  - Mutating Admission Controllers（變異型准入控制器）   
    對 API 請求的內容進行修改。典型的變異型控制器有 MutatingWebhook、NamespaceAutoProvision、DefaultStorageClass 等。這些控制器可以為資源設置默認值、添加標籤等。 
  - Validating Admission Controllers（驗證型准入控制器）    
    驗證 API 請求的內容是否符合集群策略，不對內容進行修改。常見的驗證型控制器有 ValidatingWebhook、PodSecurityPolicy、ResourceQuota 等。
- 我們可以使用以下指令來看目前被 Enable 的 Admission controllers
```bash
k get pod kube-apiserver-controlplane -n kube-system -o yaml | grep -i admission
```
- 如果我們想要添加或移除 Admission controllers，我們可以修改 kube-apiserver 的 yaml 檔案，然後重新啟動 kube-apiserver，而這個 kube-api-server 的 yaml 檔案是在 `/etc/kubernetes/manifests/kube-apiserver.yaml`

### Practice - Admission controllers

#### 5 

##### Q

The previous step failed because kubernetes have NamespaceExists admission controller enabled which rejects requests to namespaces that do not exist. So, to create a namespace that does not exist automatically, we could enable the NamespaceAutoProvision admission controller


Enable the NamespaceAutoProvision admission controller

Note: Once you update kube-apiserver yaml file, please wait for a few minutes for the kube-apiserver to restart completely.

##### A

- 這題的目的是要讓我們去啟用 NamespaceAutoProvision Admission controllers
1. 先來查看目前的 Admission controllers
```bash
cat /etc/kubernetes/manifests/kube-apiserver.yaml
```
2. 然後我們可以看到 `--enable-admission-plugins` 的設定，這個設定就是 Admission controllers 的設定
3. 直接使用 `vim` 來編輯這個檔案
```bash
vim /etc/kubernetes/manifests/kube-apiserver.yaml
```
4. 在 `--enable-admission-plugins` 的設定中加上 `NamespaceAutoProvision`
5. 而我們需要等待 kube-apiserver 重新啟動，再重新執行指令

#### 7

Note that the NamespaceExists and NamespaceAutoProvision admission controllers are deprecated and now replaced by NamespaceLifecycle admission controller.

The NamespaceLifecycle admission controller will make sure that requests
to a non-existent namespace is rejected and that the default namespaces such as
default, kube-system and kube-public cannot be deleted.

#### 8

##### Q

Disable DefaultStorageClass admission controller

This admission controller observes creation of PersistentVolumeClaim objects that do not request any specific storage class and automatically adds a default storage class to them. This way, users that do not request any special storage class do not need to care about them at all and they will get the default one.

Note: Once you update kube-apiserver yaml file then please wait few mins for the kube-apiserver to restart completely.

##### A

- 這題的目的是要讓我去停用 DefaultStorageClass Admission controllers
1. 直接使用 `vim` 來編輯這個檔案
```bash
vim /etc/kubernetes/manifests/kube-apiserver.yaml
```
2. 複製 `--enable-admission-plugins` 那行
3. 在 `--enable-admission-plugins` 下一行貼上剛剛複製得內容，然後把 enable 的部分改成 disable
4. 最後在 `=` 後面都移除，改成`DefaultStorageClass`

## 37. Validating and Mutating Admission Controllers - Security

- 如前面提到的 Admission controllers，有兩種
  - Mutating Admission Controllers（變異型准入控制器）   
    對 API 請求的內容進行修改。典型的變異型控制器有 MutatingWebhook、NamespaceAutoProvision、DefaultStorageClass 等。這些控制器可以為資源設置默認值、添加標籤等。 
  - Validating Admission Controllers（驗證型准入控制器）    
    驗證 API 請求的內容是否符合集群策略，不對內容進行修改。常見的驗證型控制器有 ValidatingWebhook、PodSecurityPolicy、ResourceQuota 等。
- 這兩種 Admission Controllers 有先後順序，通常是先進行驗證(Vaildating)再進行變異(Mutating)
- k8s 官方已經提供了一些 Admission Controllers，我們可以直接使用，例如
  - AlwaysPullImages
  - DefaultStorageClass
  - EventRateLimit
  - NamespaceAutoProvision...等等
- 但有些情況下，我們可能想要自訂 mutatingAdmission controllers 和 validatingAdmission controllers，這時我們可以使用 webhook 來進行自訂   
  - MutatingAdmission Webhook
  - ValidatingAdmission Webhook
- 考試的時候，並不會要求我們自己寫一段 code 來自定義 mutating 或 validating 的邏輯
- 但我們需要知道當我們已經有做好的 webhook，使用 deployment 部屬、也使用 service 來暴露這個 webhook 時，我們要配置 webhook configuration 來告訴 k8s 我們要使用這個 webhook
```yaml
apiVersion: admissionregistration.k8s.io/v1
# 這邊的 kind 有兩種
# kind: MutatingWebhookConfiguration
# kind: ValidatingWebhookConfiguration
kind: MutatingWebhookConfiguration
metadata:
  name: pod-mutator.example.com # 通常習慣是使用這樣的命名方式
webhooks:
- name: pod-mutator.example.com
  clientConfig:
    service:
      name: pod-mutator
      namespace: default
    caBundle: <base64-encoded-ca-cert> # 這是 webhook server 的 ca.crt 的 base64 編碼
  rules: # 表示當我執行 kubectl create pod 時，就會觸發這個 webhook
  - operations: ["CREATE"]
    apiGroups: [""]
    apiVersions: ["v1"]
    resources: ["pods"]
    scope: "Namespaced"
```

### Practice

#### 4

##### Q

Create TLS secret webhook-server-tls for secure webhook communication in webhook-demo namespace.


We have already created below cert and key for webhook server which should be used to create secret.

Certificate : /root/keys/webhook-server-tls.crt

Key : /root/keys/webhook-server-tls.key

##### A

1. 先來建立 secret，但我不知道怎麼使用 command 來建立，所以先來查一下 command
```bash
k create secret -h
```
2. 這時他告訴我要先選擇 secret 的 type，那我就先選好如題目中的 tls，再來看有哪些指令可以用
```bash
k create secret tls -h
```
3. 然後我們就可以使用以下指令來建立 secret
```bash
k create secret tls webhook-server-tls --cert=/root/keys/webhook-server-tls.crt --key=/root/keys/webhook-server-tls.key -n webhook-demo
```

#### 5

##### Q

Create webhook deployment now.

We have already added sample deployment definition under /root/webhook-deployment.yaml so just create deployment with that definition.

##### A

```bash
k apply -f webhook-deployment.yaml
```

#### 6

##### Q

Create webhook service now so that admission controller can communicate with webhook.

We have already added sample service definition under /root/webhook-service.yaml so just create service with that definition.

##### A

```bash
k apply -f webhook-service.yaml
```

## 38. API version - Security

- 我們知道在 `/apis` 下有很多的 API groups，如    
  - `/apis/apps`
  - `/apis/extensions`
  - `/apis/networking.k8s.io`
- 而在 API groups 下又有很多的版本，如
  - `/apis/apps/v1`
  - `/apis/apps/v1beta1`
  - `/apis/apps/v1beta2`
- 每種版本，都有他的意思
  - Alpha   
    - Alpha 版本是一個早期的版本，首次成為 k8s 的一部分，通常是在 k8s 的新功能中使用。
    - 可能又會分為 Alpha 1、Alpha 2...等等
    - 這類的 API 在預設情況下是關閉的，需要透過 `--feature-gates` 來開啟
    - 這些 API 可能會有很多的 bug，且不保證向後兼容，有可能會在下一個版本中被移除
  - Beta
    - Beta 版本是 Alpha 版本的下一個階段，通常是在 Alpha 版本經過一段時間的測試後，並且 API 的設計已經穩定下來後，才會進入 Beta 階段。
    - 這些 API 有很高的機率會成為正式版本，但仍然可能會有一些變動
  - GA/stable
    - GA 版本是 Beta 版本的下一個階段，通常是在 Beta 版本經過一段時間的測試，有沒有發現到重大的 bug 後，才會進入 GA 階段。
    - 這些 API 是穩定的，且保證向後兼容，並且不會在短期內被移除
- 同一個 API group 可能會有多個版本，我們可以使用 `explain` 來查看 API 的 prefered version
```bash
k explain pod 
```
- 如果我們想要 enable 或 disable 特定的 API version，我們可以在 kube-apiserver 的 yaml 檔案中加上 `--runtime-config` 來進行設定

## 39. API Deprecation - Security

- kubernetes 版本閱讀方式，如 1.22.2
  - 1 代表主版本 major version
  - 22 代表次版本 minor version
  - 2 代表補丁版本 patch version
- Kubernetes api deprecation policy rules
  - rule 1 
    - API 元素只能透過增加增加 api group 的新版本來進行刪除
  - rule 2(看不懂)
    API 物件必須要能夠在同一釋出版本的不同版本之間進行轉換，而不會丟失資料
  - rule 4a
    - GA 版本可以標記為「已廢棄」（deprecated），但在 Kubernetes 主版本（major version）內不可移除。
    - Beta 版本在發布後最多 9 個月或 3 個次版本（minor release）內標記為廢棄（取較長者），並在廢棄後 9 個月或 3 個次版本後（取較長者）停止支援。
    - Alpha 版本可以在任何釋出版本中被移除，而不需事先的廢棄通知。
  - rule 4b
    - API 群組的「首選」版本與「存儲版本」在釋出之前不應提前
    - 使用者必須能夠升級至 Kubernetes 新版本，並在不需轉換至新 API 版本的情況下回滾至先前版本，且不會出現中斷（除非他們明確使用了僅在較新版本中可用的功能）。這在物件的儲存表示方式中特別重要。
  - rule 3
    - API 軌道中的版本不可被較不穩定的版本取代
    - GA 版本可以取代 Beta 和 Alpha 版本。
    - Beta 版本可以取代先前的 Beta 和 Alpha 版本，但不可取代 GA 版本。
    - Alpha 版本可取代早期的 Alpha 版本，但不可取代 GA 或 Beta 版本。

- rule 1 舉例
  - 當我有一個 api 組 
    - `apis/test.com/v1alpha1/course`
    - `apis/test.com/v1alpha1/webinar`
  - 而現在我想要刪除 `apis/test.com/v1alpha1/webinar`
  - 那我必須新增一個版本 `v1alpha2`
  - 在 `v1alpha2` 中，只有 `course`
  - 而此時，`v1alpha1` 中有 `course` 和 `webinar`，就達到了向後兼容，避免既存的程式碼出錯
  - 不過此時的 storage 及 prefered version 都是 `v1alpha2`
- rule 2
  - 這代表物件可以以 v1 寫入，然後讀回 v2，再轉換回 v1，其結果應與原始的 v1 資源相同
  - 例如有一個資源叫做 Course ，在 v1alpha2 中新增了一個欄位叫做 `duration`，而 v1alpha1 沒有這個欄位
  - 而當 v1alpha2 要轉回 v1alpha1 時，就多了一個欄位，這時就會出錯
  - 所以我們必須在 original v1alpha1 中就要有這個欄位，只是沒有值而已，才不會丟失資料

### kubectl convert

- 這是一個獨立的 plugin，在預設的環境下可能沒有安裝，需要自己手動安裝
- 這是一個用來轉換 API 版本的工具，可以將一個 API 版本的資源轉換成另一個 API 版本的資源

### 一些相關的指令

```bash
# 某一個資源(resource)的 api group 及 version，可以使用以下指令
k explain pod

# 查看某一個 api group 的 prefered version ??? TODO 不懂
# 以 authorization.k8s.io 為例
k api-resources --api-group=authorization.k8s.io
```
    
### Practice

#### 4
    
##### Q
    
What is the preferred version for authorization.k8s.io api group?

##### A

- 這個問題是要找到 authorization.k8s.io 的 prefered version，prefered version 就是 k8s 建議使用的版本
- 我們可以使用以下指令來查看
```bash
# kubectl proxy 會開啟一個代理服務器，& 代表背景執行
k proxy 8001&
# 因為上面的指令，讓我們可以直接在本地端使用 curl 來查看打 k8s 的 api
curl localhost:8001/apis/authorization.k8s.io
```

#### 5

##### Q

Enable the v1alpha1 version for rbac.authorization.k8s.io API group on the controlplane node.

##### A

- 要啟用 v1alpha1 的版本，我們可以在 kube-apiserver 的 yaml 檔案中加上 `--runtime-config` 來進行設定
- 這邊我們可以使用 `vim` 來編輯這個檔案
```bash
vim ../etc/kubernetes/manifests/kube-apiserver.yaml
```
- 在 spec.containers.command 下加上以下 yaml
```yaml
- --runtime-config=rbac.authorization.k8s.io/v1alpha1
```
- `runtime-config` 就是用來控制 API group 的版本的參數

> `k api-versions` 這個指令可以查看目前 k8s 支援的 API 版本   
> `k api-resources` 這個指令可以查看目前 k8s 支援的 API 資源

#### 6

##### Q

Install the kubectl convert plugin on the controlplane node.

##### A

1. 先到官網找到 kubectl convert 安裝指令    
  - 位置在 [這裡](https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/)
  - 或是你可以在左側找 Task > Install Tools > Install and Set Up kubectl on Linux > 使用 ctrl+d 搜尋 convert
2. 安裝完成後，使用 `ls` 查看是否有安裝成功，如果有的話，就會看到 `kubectl-convert` 這個檔案
3. 先對他加上執行權限(x 是代表執行權限、+ 是代表新增權限、chmod 是改變檔案權限的指令，change mode 的縮寫)
```bash
chmod +x kubectl-convert
```
4. 接著把這個檔案移到 `/usr/local/bin` 下
```bash
mv kubectl-convert /usr/local/bin
```
5. 測試儀下是否安裝成功且可以使用
```bash
kubectl-convert -h
```

#### 7

##### Q

Ingress manifest file is already given under the /root/ directory called ingress-old.yaml.

With help of the kubectl convert command, change the deprecated API version to the networking.k8s.io/v1 and create the resource.

##### A

1. 題目已經幫我們準備好舊的 yaml 檔案
2. 接下來用上一題所安裝的 kubectl-covert 來轉換 yaml 檔案
```bash
kubectl-covert -f ingress-old.yaml --output-version networking.k8s.io/v1 > ingress-new.yaml
```
3. 最後再使用 kubectl apply 來部屬
```bash
kubectl apply -f ingress-new.yaml
```

## 40. Custom Resource Definition(CRD) - Security

- 我們先來了解遺下 resources，k8s 中有很多種 resources    
  - ReplicaSet
  - Deployment
  - Job
  - CronJob
  - StatefulSet
  - Namespace...etc
- 以 deployment 來說，我們常常會在 deployment 中設定要有多少 replicas，而當我們寫好了 definition yaml，並使用 `k apply -f ` 時，一路上會發生的事情    
  1. 透過 kubectl client 將 yaml 檔案傳送到 kube-apiserver
  2. kube-apiserver 會將這個 yaml 檔案儲存到 etcd 中
  3. kube-controller-manager 會監控 etcd 中的資源，並根據資源的狀態，使用 controller 來進行相對應的操作
  4. kube-scheduler 會根據資源的狀態，將 pod 分配到適合的 node 上
  5. kubelet 會根據 kube-scheduler 的指示，在 node 上建立 pod
- 但有時，我們可能會需要一些自定義的資源，這時我們就可以使用 Custom Resource Definition(CRD) 來進行自定義

### CRD yaml

- 假設我現在想要建立一個 resource，他在 kind 叫做 FlightTicket，我們預想這個 resource 的 definition yaml 應該是這樣的
```yaml
apiversion: flights.com/v1
kind: FlightTicket
metadata:
  name: my-flight-ticket
spec:
  from: Taipei
  to: Tokyo
  number: 2
```
- 如果我想要使用以上的 FlightTicket definition yaml 來建立這個資源的話，那我們就需要先建立一個 Custom Resource Definition(CRD) 來告訴 k8s 這個資源的定義
- 以下是一個 CRD 的 yaml 檔案
```yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: flighttickets.flights.com
spec:
  scope: Namespaced # 這個資源是屬於 Namespaced 還是 Cluster scope
  group: flights.com # 這個資源的 group，也就是 apiVersion 中的 flights.com
  names: # 這個資源的名稱
    kind: FlightTicket # 也就是上面的 kind
    singular: flightticket # 單數時的名稱
    plural: flighttickets # 複數時的名稱
    shortNames: # 簡稱，可以有多個
    - ft
  versions:
  - name: v1 # 這個資源的版本
    served: true # 決定了該版本的資源是否可以通過 Kubernetes API 進行訪問。例如有新版本取代這個舊版本時，就可以把這個設定為 false
    storage: true # 決定了該版本的資源是否用於存儲在 etcd 中。通常會將最新的穩定版本設置為 storage: true。在升級過程中，你可以將新的版本設置為 storage: true，並將舊版本設置為 storage: false，以便進行數據遷移。
    schema: # 這個資源的 schema
      openAPIV3Schema:
        type: object
        properties: # 以下就是定義這個資源 spec 的部分
          spec:
            type: object
            properties:
              from:
                type: string
              to:
                type: string
              number:
                type: integer
                minimum: 1
                maximum: 10
```  
- 有關 `served` 及 `storage`，在升級或棄用某個版本時，你可以調整 served 和 storage 屬性。例如：   
  - 引入新版本 `v2` 時
  ```yaml
  versions:
  - name: v1
    served: true
    storage: false # 不存在 ETCD 中表示不會被持久化，通常用於過渡期的版本，當 api server 重啟時，這個版本的資源就會遺失
  - name: v2
    served: true
    storage: true # 存在 ETCD 中表示會被持久化，通常用於最新的穩定版本。當 api server 重啟時，這個版本的資源仍會被保留
  ```
  - 棄用 `v1` 時
  ```yaml
  versions:
  - name: v1
    served: false # 無法透過 Kubernetes API 進行訪問，表示這個版本的資源已經被棄用
    storage: false
  - name: v2
    served: true
    storage: true
  ```

### Practice

#### 3

##### A

```yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: internals.datasets.kodekloud.com 
spec:
  group: datasets.kodekloud.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                internalLoad:
                  type: string
                range:
                  type: integer
                percentage:
                  type: string
  scope: Namespaced
  names:
    plural: internals
    singular: internal
    kind: Internal
    shortNames:
    - int
```

## 41. Helm Introduction - Helm Fundamentals

- 試想一下，如果有一個專案，裡面有很多的 yaml 檔案，而這些 yaml 檔案都是用來部屬 k8s 的資源，那麼我們會需要重複很多次建立各種資源的步驟，而且也會很難管理這些 yaml 檔案和版本，這時我們就可以使用 Helm 來進行管理
- 當我們有一堆 k8s 資源，而對 helm 來說就是一個整體、一個專案。
- Helm 是一個 k8s 的套件管理工具，可以用來簡化 k8s 的部屬和管理

### Helm 常用指令

```bash
# 安裝一個 chart(就是一個專案)
helm install wordpress

# 升級一個 chart
helm upgrade wordpress

# 回滾一個 chart
helm rollback wordpress

# 刪除一個 chart
helm delete wordpress
```
- 即使我們在這個專案的各資源有很多修改，也可以只用以上的指令，就達到了一次部屬、升級、回滾、刪除的目的

### Install Helm

1. 第一步是要先確定我們的作業系統，可以使用以下指令來獲得
```bash
cat /etc/*release*
```
2. 接著我們可以到 [Helm 官網](https://helm.sh/docs) > Introduction > Installing Helm > 找到我們的作業系統(我的是 ubuntu) > 安裝指令
3. 接著就直接開始安裝吧
```bash
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt-get install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm
```
4. 安裝完成使用以下指令來確認是否安裝成功
```bash
helm version
```

## 42. Helm Concepts - Helm Fundamentals

- Helm 有以下幾個重要的概念
  - Chart
  - values
  - Released
  - Repository
  - Template
  - Helm Hooks
  - Chart Dependencies
  - Helmfile

### Chart

- Chart 是 Helm 的核心單位，代表 Kubernetes 應用程式的封裝格式。一個 Chart 包含定義應用程式的所有 Kubernetes 資源配置檔案，通常使用 YAML 格式。
- 每個 Chart 可包含多個 Kubernetes 資源，如 Deployment、Service、ConfigMap 等，並且支援參數化。
- Chart 可以打包成 .tgz 格式並上傳至 Helm Repository，方便分發和版本管理。
- 優點    
  - 簡化部署：Helm 提供了打包和模板化的方式，降低手動配置 Kubernetes 資源的複雜性。
  - 版本控制：Helm 支持 Release 的升級、回滾和歷史版本查詢。
  - 靈活的自定義：通過 Values 配置，使用者可以根據環境需求定制應用程式行為。
  - 高效的依賴管理：Helm 支援 Chart 的依賴管理，可以實現應用程式的組件化與資源共享。

#### Chart 目錄結構

```text
mychart/
  ├── Chart.yaml           # Chart 的描述文件
  ├── values.yaml          # 默認配置值
  ├── charts/              # 依賴的子 charts
  ├── templates/           # 模板文件目錄
  │   ├── deployment.yaml
  │   ├── service.yaml
  │   ├── _helpers.tpl    # 模板助手文件
  │   └── NOTES.txt       # 使用說明
  └── .helmignore         # 類似 .gitignore
```

#### Chart.yaml

```yaml
apiVersion: v2
name: mychart
description: A Helm chart for my application
type: application
version: 0.1.0
appVersion: "1.16.0"
dependencies:
  - name: mongodb
    version: 8.0.0
    repository: https://charts.bitnami.com/bitnami
```

### Values

- Values 是 Helm Chart 的設定檔，主要用來覆蓋或自定義 Chart 中的參數。Helm 使用 values.yaml 檔案來存儲預設的配置值，使用者可以在部署 Chart 時提供自定義的Values，這些設定會動態覆蓋預設值。
- Values 允許使用者在不同的環境中自定義同一個 Chart 的行為，例如在測試環境和生產環境中配置不同的資源需求或變數。

#### values.yaml

```yaml
# values.yaml
replicaCount: 3
image:
  repository: nginx
  tag: "1.16.0"
service:
  type: ClusterIP
  port: 80
```

### Release

- Release 是指 Helm 部署的特定 Chart 實例。每次使用 Helm 安裝 Chart 時，會產生一個 Release，這個 Release 包含該 Chart 的所有 Kubernetes 資源以及配置。
- 使用者可以對 Release 進行升級、回滾、查看歷史紀錄或刪除，這讓管理和監控應用變得更加靈活。
- 每個 Release 會被分配一個名稱，方便在 Kubernetes 叢集中管理和識別。

#### 相關指令
  
```bash
# 安裝 Release
helm install myapp ./mychart

# 升級 Release
helm upgrade myapp ./mychart

# 回滾 Release
helm rollback myapp 1

# 查看 Release 歷史
helm history myapp
```

### Repository

- Repository 是 Chart 儲存和分發的集中地，類似於程式庫。Helm Repository 允許使用者上傳和下載不同版本的 Chart。
- Helm 預設提供官方 Repository，例如 stable 和 incubator，但使用者也可以自行設定自定義的 Repository。
- Helm 透過 helm repo add 命令可以將新的 Repository 加入本地清單中，方便搜尋和下載 Chart。

#### 相關指令

```bash
# 添加倉庫
helm repo add bitnami https://charts.bitnami.com/bitnami

# 更新倉庫
helm repo update

# 查看倉庫列表
helm repo list
```

### Template

- Template 是 Helm Chart 中的模板化資源配置檔案，它使用 Go 的模板語法來動態生成 Kubernetes 資源檔案。
- Helm 會根據 Values 以及模板檔案生成符合需求的 YAML，並將其應用至 Kubernetes 叢集中。
- Helm 的模板語法支持變數、流程控制（例如 if、range 等），並可以重複使用和擴展，提高 Chart 的彈性和可維護性。

#### 模板文件

```yaml
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-deployment
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Chart.Name }}
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
```

### Helm Hooks

- Hooks 是 Helm 提供的一種機制，允許在 Chart 部署過程的不同階段執行自定義動作。常見的 Hook 包括 pre-install、post-install、pre-upgrade 和 post-upgrade。
- Hooks 可以幫助執行例如資料庫遷移、初始化設置等複雜的流程，並且能夠與 Helm 的 Release 協同工作。

#### Hook 文件

- pre-install.yaml 範例，就是指在安裝 Chart 之前執行的動作
```yaml
# pre-install hook 示例
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Release.Name }}-pre-install-job
  annotations:
    "helm.sh/hook": pre-install
spec:
  template:
    spec:
      containers:
        - name: pre-install-job
          image: busybox
          command: ['sh', '-c', 'echo pre-install job']
      restartPolicy: Never
```

### Chart Dependencies

- Helm 支援在一個 Chart 中引入其他 Chart 以解決應用程式的依賴問題。通過設定 Chart 依賴（Dependencies），可以方便地管理和安裝多個 Chart。
- 依賴會在 Chart.yaml 中進行聲明，Helm 會自動解析並安裝所需的子 Chart，實現應用程式的組件化。

#### Chart 依賴

```yaml
# Chart.yaml
dependencies:
  - name: mongodb
    version: 8.0.0
    repository: https://charts.bitnami.com/bitnami
```

### Helmfile

- Helmfile 是社群開發的工具，用於管理多個 Helm Release。當應用程式涉及多個 Chart 時，Helmfile 可集中管理並同時部署多個 Chart，從而提高部署效率。

### Udemy 課程中有提到的相關指令，考試中可能會用到

```bash
# 在 Artifact Hub 中搜尋 Chart
helm search hub <chart-name>
helm search hub wordpress

# 將 repository 添加到本地的 Helm 配置中
helm repo add <repository-name> <repository-url>
helm repo add bitnami https://charts.bitnami.com/bitnami

# 列出本地的 repository
helm repo list

# 搜尋本地的某個 repository
helm search repo <repository-name>
helm search repo bitnami

# 更新本地的 repository
helm repo update

# 安裝 Chart，並指定 release name，可以用同一個 chart 安裝多次，每次的 release name 都不一樣，它們都會是獨立的
helm install <release-name> <chart-name>
helm install my-wordpress bitnami/wordpress

# 列出所有的 release
helm list

# 解除安裝 release
helm uninstall <release-name>
helm uninstall my-wordpress

# 下載並解壓縮 Chart
helm pull --untar bitnami/wordpress

# 安裝使用 pull 拉下來的 Chart
helm install <release-name> <chart-directory>
helm install my-wordpress wordpress

# 當下載並解壓縮完成，可以使用以下指令來查看 Chart 的目錄結構
ls wordpress
```

### Practice

#### 12

##### Q

Install the apache from the downloaded helm package.

Release name: mywebapp

Note: Do make changes accordingly so that 2 replicas of the webserver are running and the http is exposed on nodeport 30080.

Make sure that the pods are in the running state.

Release name is correct?
Installed successfully?
Deployment created?
Two pods running?
Correct NodePort?

##### A

1. 前面我已經把 apache 的 helm package 下載下來了
2. 他要我們確認 replicas 是 2，並且 http 是在 nodeport 30080
3. 使用以下指令打開 values.yaml 來修改 replicas 和 nodeport
```bash
vim apache/values.yaml
```
4. 要注意的是，在這邊 replicas 是叫做 replicaCount，而 nodeport 是在 service 下的 nodePort，我們可已使用`／`加上文字來搜尋這兩個參數。
5. 修改完後，使用以下指令來安裝
```bash
# 這裡的 apache 是指目錄名稱，而 mywebapp 是 release name
helm install mywebapp apache
```
    
[part 3](https://hackmd.io/@ohQEG7SsQoeXVwVP2-v06A/SyWgAG-WJg) 很雜，只是拿來記錄一些做題目時忘記的、一直記不起來的整理，不是很重要。


