LFS258 [5/15]: Kubernetes API Object
Tags: lfs258, kubernetes, docker, devops
Published: 2020-02-10 22:47:29

จากบทที่แล้ว เราได้รู้จักการเรียกใช้ API ของ Kubernetes กันไปแล้ว บทนี้เราจะมาลองลงลึกไปที่ API Object เบื้องต้นของ Kubernetes กันบ้างว่า ในการที่เราจะ run งานของเรานั้น kubernetes มี Object แบบไหน ให้เราเลือกใช้งานกันบ้าง พร้อมแล้ว ? ลุยกันเลย

# Index

- [Overview](#overview)
- [v1 API Group](#v1-api-group)
- [Discovering API Groups](#api-group) 
- [Deploying an Application](#deploying-application)
- [DaemonSets](#daemonSets)
- [StatefulSets](#statefulsets)
- [Autoscaling](#autoscaling)
- [Jobs and Cronjobs](#jobs-cronjobs)
- [RBAC](#rbac)

<a name="overview"></a>

---

# Overview

ถ้าพูดถึง API ของ Kubernetes แน่นอนว่า group และ version (group/version) เป็นสิ่งที่สำคัญมาก เพราะเราต้องระบุมันทุกครั้ง ที่จะสั่งงานผ่าน API ในบทความนี้จะกล่าวถึงเฉพาะ API ที่อยู่ในสถานะ stable แล้วเท่านั้น นั่นคือ v1 โดย API Object ที่พบบ่อยในการ run งานได้แก่

- **app/v1**
  - **[DeamonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/)**: `เป็น object ที่สร้าง pod จำนวนหนึ่งจาก image เดียว แล้วกระจาย pod ไป run ทุก node ใน cluster node ละ 1 pod`
  - **[StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)**: `เป็น object ที่สร้าง pod จำนวนหนึ่งจาก image เดียว และจะมอง pod ที่เกิดขึ้นเป็นคนละ pod กัน ในการ start จะ start ตามลำดับ`
- **batch/v1**
  - **[Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/)**: `เป็น object ที่สร้าง pod ขึ้นมา 1 pod แล้วส่งไป run ใน node ที่เหมาะสม ถ้างานเสร็จแล้วคือจบ จุดมุ่งหมายคืองานต้องสำเร็จ จะมีการกำหนดจำนวนครั้งในการ retry ไว้ถ้า retry ครบ แล้วงานไม่สำเร็จถือว่างานนั้น fail แล้วจบการทำงาน`
  - **[Cronjobs](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/)**: `เป็น jobs ที่ทำตามเวลาที่เรากำหนด เช่น ทุกวันตอน 10 โมงเช้า หรือ ทุก 5 นาที เป็นต้น`
- **rbac.authorization.k8s.io/v1**
  - **[RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)**: `เป็น object ที่ช่วยให้เราสามารถกำหนด authorization ผ่านทาง Roles ของ Kubernetes ได้`

เนื่องจาก Kubernetes เป็น Opensource Project ที่เติบโตเร็วมาก เพื่อติดตาม features ที่สำคัญหรือที่เราสนใจ สามารถดูได้จาก [Kubernetes Enhancement](https://github.com/kubernetes/enhancements) หรือ [Release Note](https://kubernetes.io/docs/setup/release/notes/) ของแต่ละ version ที่ออกมา

> ตั้งแต่ v1.16, ถ้ามีการเรียกใช้งาน Object ที่ยกเลิกการใช้งาน (deprecated) ไปแล้ว kubernetes จะตอบ <mark>ERROR</mark> ไม่เหมือนก่อนหน้านี้ที่จะยังคงรับไปทำงาน

<a name="v1-api-group"></a>

---

# v1 API Group

ใน v1 API จะมีการแบ่ง API ออกเป็นกลุ่มๆ เพื่อให้ง่ายต่อการพัฒนา โดยกลุ่มที่ใช้บ่อยๆ มีดังนี้

- **[Node](https://kubernetes.io/docs/concepts/architecture/nodes/)**: `คือ server อาจเป็น virtual หรือ physical ก็ได้ ซึ่งถูกเอามา join ใน kubernetes cluster`
- **[Service Account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)**: `เป็นตัวจัดการเรื่องสิทธิ์ (authorization) ของ process ที่ run ใน pod ในการเข้าถึง API ของ kubernetes`
- **[Resource Quota](https://kubernetes.io/docs/concepts/policy/resource-quotas/)**: `เป็นตัวจัดการเรื่อง resource quota เช่น CPU, memory และ local ephemeral storage ในแต่ละ namespace`
- **[Endpoint](https://kubernetes.io/docs/concepts/services-networking/connect-applications-service/)**: `โดยปกติเราไม่ได้จัดการ endpoint ตรง แต่ `[`service`](https://kubernetes.io/docs/concepts/services-networking/service/) `เป็นคนทำให้ โดย endpoint ในที่นี้คือ กลุ่มของ IP:Port ของ Pod ที่มี label ตรงกับ label ที่กำหนดไว้ที่ service`

<a name="api-group"></a>

---

# Discovering API Groups

ถ้าเราลอง debug command `kubectl` ด้วย option `--v=9` ดู จะเห็นว่า URL ที่ `kubelet` เรียกไปนั้น มีการระบุ API group ลงไปด้วย เช่น `http://localhost:6443/apis/apiregistrationk8s.io/v1beta1` 

เราสามารถ list api groups ที่ cluster ของเรา support ได้ด้วยวิธีนี้

``` bash
# Get access token
$ TOKEN=$(kubectl get secret $(kubectl get serviceaccount default -o jsonpath='{.secrets[0].name}') -o jsonpath='{.data.token}' | base64 --decode )

# Get Stable API Object
$ curl -s https://localhost:6443/apis --header "Authorization: Bearer $TOKEN" -k | jq -c '.groups[].versions[] | select( .version == "v1") | .groupVersion'
"apiregistration.k8s.io/v1"
"apps/v1"
"authentication.k8s.io/v1"
"authorization.k8s.io/v1"
"autoscaling/v1"
"batch/v1"
"networking.k8s.io/v1"
"rbac.authorization.k8s.io/v1"
"storage.k8s.io/v1"
"admissionregistration.k8s.io/v1"
"apiextensions.k8s.io/v1"
"scheduling.k8s.io/v1"
"coordination.k8s.io/v1"
"crd.projectcalico.org/v1"
```

<a name="deploying-application"></a>

---

# Deploying an Application

การ deploy application บน kubernetes cluster นั้นทำได้ง่ายมาก เพียงแค่ใช้ command `kubectl create ...` เมื่อเรา create แล้วก็จะได้ pod มา 1 pod ข้างใน run container ที่บรรจุ application ของเราอยู่ แต่ถ้าลองเจาะเข้าไปลึกๆ เราจะพบว่ามี object 3 ชนิดเกิดขึ้นได้แก่

- **Deployment**: `เป็น controller คอยจัดการ ReplicaSet และ Pods ของมัน ทำให้การจัดการ Pods ทำได้ง่ายขึ้น ถ้าไม่รู้จะใช้อะไร จงใช้ Deployment`
- **ReplicaSet**: `เป็นตัวที่คอยดูแล Pods ว่ายังมีชีวิตอยู่ปกติดีหรือไม่ ถ้าไม่ก็ restart pods และ update pods หาก admin สั่งมา`
- **Pod**: `ตัวที่ run container ให้เรา`

ตัวอย่าง

``` bash
# Create deployment which have nginx container running inside
$ kubectl create deployment --image nginx myapp 
deployment.apps/myapp created

# Check Deployment status
$ kubectl get deployment
NAME        READY   UP-TO-DATE   AVAILABLE   AGE
myapp       1/1     1            1           14s

# Check ReplicaSet status
$ kubectl get rs
NAME                   DESIRED   CURRENT   READY   AGE
myapp-57c9b8fc4        1         1         1       16s

# Check Pod status
$ kubectl get pods
NAME                         READY   STATUS    RESTARTS   AGE
myapp-57c9b8fc4-wgpj2        1/1     Running   0          20s
```

<a name="daemonsets"></a>

---

# DaemonSets

DaemonSets เป็น controller ช่วยทำให้เรามั่นในว่าทุก node ใน cluster จะมี pod ที่เราระบุไว้ run อยู่เสมอ ถ้ามี node เพิ่ม มันจะคอยเอา pods ที่เราระบุไว้ไป run ให้อัตโนมัติ ในทางกลับกันถ้า node ถูก remove ออกไปจาก cluster มันจะไป down pod ให้เช่นกัน เรามาสามารถ configure ให้ exclude บาง node ออกไปจาก list ที่ต้องเอา pod ไป run ได้

ปกติเราจะใช้ controller ประเภทนี้กับ การ stream log และ metrics ออกจาก cluster หรือแม้กระทั้งดูแลเกี่ยวกับเรื่อง security

ตัวอย่างของ DaemonSets ที่ต้องมีทุก cluster คือ `kube-proxy`

``` bash
# Get all nodes in the cluster
$ kubectl get nodes
NAME                         STATUS   ROLES    AGE   VERSION
ubuntu-s-2vcpu-4gb-nyc1-01   Ready    <none>   20d   v1.16.1
ubuntu-s-4vcpu-8gb-nyc1-01   Ready    master   20d   v1.16.1

# Show pods of DaemonSets which reside in each node
$ kubectl get pods -n kube-system -l k8s-app=kube-proxy -o wide
NAMESPACE     NAME               READY   STATUS    RESTARTS   AGE   IP                NODE                         NOMINATED NODE   READINESS GATES
kube-system   kube-proxy-8ppm2   1/1     Running   1          20d   157.245.241.29    ubuntu-s-2vcpu-4gb-nyc1-01   <none>           <none>
kube-system   kube-proxy-m8xvp   1/1     Running   1          20d   157.245.138.142   ubuntu-s-4vcpu-8gb-nyc1-01   <none>           <none>
```

<a name="statefulsets"></a>

---

# StatefulSets

[StatefulSets](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/) เป็น controller ที่ช่วยให้ stateful application สามารถ run ใน kubernetes cluster ได้ ตัวอย่างของ stateful application เช่น Elasticsearch, Kafka และ Cassandra เป็นต้น

StatefulSets ทำงานเหมือน ReplicaSet แต่ StatefulSet มองว่าแต่ละ pod ไม่เหมือนกัน สังเกตง่ายๆ จาก suffix ของชื่อของ pod จะไม่ใช่ random character แต่เป็นตัวเลขที่เรียงกัน เช่น `app-0`, `app-1` และ `app-2` เป็นต้น ตัวเลขที่เรียงนั้นไม่ใช่เรียงกันเล่นๆ แต่มันแสดงถึง ลำดับในการ start pod ด้วย เช่น `app-0` ต้อง start จน Ready ก่อนถึง `app-1` จะถูก start

Update strategy ของ StatefulSet ทำได้ 2 แบบคือ
- **On Delete**: `ถ้าสั่ง update ไปแล้ว pod จะไม่ update ให้อัตโนมัติ เราต้อง delete pod เอง มันถึงจะ update ให้`
- **Rolling Updates**: `เป็น default strategy โดย pod จะถูก delete และ recreate โดยอัตโนมัติ โดย StatefulSets จากลำดับมากไปหาน้อย`

StatefulSets ถูก promote ให้เป็น Stable ตั้งแต่ v1.9

<a name="autoscaling"></a>

---

# Autoscaling

การที่จะทำ Autoscaling ได้ สิ่งที่ต้องมีใน cluster คือ [metric-server](https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/metrics-server) โดย Autoscaling มีด้วยกัน 3 แบบ คือ

- **Horizontal Pod Autoscalers (HPA)**: `เป็นตัวเดียวที่เป็น stable version ความสามารถของมันคือการเพิ่มหรือลดจำนวน Pod ตาม workload โดย support ทั้ง ReplicationControllers, ReplicaSets และ Deployments โดย default การ scale จะเกิดขึ้นเมื่อมีการใช้งาน CPU ถึง 50% ของที่ limit ไว้เป็นเวลา 3 นาที`
- **Cluster Autoscalers (CA)**: `เป็นการเพิ่มหรือลด node ใน cluster ตาม workload ของ clusters โดย CA เกิดขึ้นได้เมื่อ`
  - `cluster ไม่สามารถ deploy pod เพิ่มได้อีกแล้ว`
  - `node ใด node หนึ่ง มีการใช้งานสูงมาก (low utilization) เป็นเวลาอย่างน้อย 10 นาที`

    `เมื่อถึงเวลาที่จะต้องเพิ่มหรือลด node, CA จะสั่งงานไปยัง cloud provider ให้ทำงานให้ โดย CA จะ ตรวจสอบ utilization ของ node ทุก 10 วินาที`
  - `ในการ scale out (เพิ่ม nodes) จะเกิดขึ้นเมื่อ cluster มี utilization ถึง threshold เป็นเวลา 10 นาที`
  - `ในการ scale in (ลด nodes) จะเกิดขึ้นเมื่อ cluster มี utilization ต่ำกว่า threshold เป็นเวลา 10 นาที ถ้าหาก scale ไม่สำเร็จ CA จะ recheck เป็นเวลา 3 นาที และหลังจาก 5 นาที หากยัง fail อยู่ cluster จะไม่สนใจ node นั้นอีกต่อไป`
- **Vertical Pod Autoscalers (VPA)**: `ยังอยู่ในขั้น development โดย VPA จะเพิ่มหรือลด limit ของ CPU และ memory ให้เหมาะสมกับ workload`

<a name="jobs-cronjobs"></a>

---

# Jobs and Cronjobs

### [Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/)

Jobs จะเป็น pods ที่ run งานใดงานหนึ่งจนเสร็จแล้วก็ตายไป (one-off pods) มันไม่ได้ run ค้างเป็น daemon เหมือน `Deployment`, `DaemonSet` หรือ `StatefulSet` แบบที่ผ่านมา อาจเรียกได้ว่า Jobs คือ การ run batch processing ใน kubernetes cluster

specification ของ jobs มี 2 ค่าที่น่าสนใจคือ

- **parallelism**: จำนวน pods ของงานที่สามมารถ run พร้อมกันได้
- **completion key**: จำนวน pods ขั้นต่ำที่ต้อง run ให้ success จึงจะยอกรับได้ว่างานนั้นจบ

ถ้าหากไม่ได้ระบุ 2 ค่านี้ ค่า default คือ 1 และ ถ้าหาก pods ที่ run งานอยู่ fail มันจะถูก restart จนกว่าจำนวน pod ที่ success เท่ากับ completion key

ตัวอย่าง YAML file ของ Jobs

``` yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4
```

### [Cronjobs](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/)

Cronjobs ของ Kubernetes ทำงานเหมือน Cronjobs ใน Linux เลย โดยจะ run pods ในเวลาที่กำหนด ซึ่งการตั้งเวลาในการ run pods นั้นจะใช้ syntax เดียวกับ Linux ด้วยเหตุนี้ อาจเกิดปัญหาเหมือน linux หาก pod หนึ่งยัง run งานไม่จบแต่ถึงเวลาที่จะต้อง run pod นั้นซ้ำอีกรอบ ดังนั้น application ใน pod ต้องออกแบบให้ run ซ้อนกันและ run ซ้ำกันได้ (idempotent)

ทั้งนี้ Cronjobs สามารถกำหนด `.spec.concurrencyPolicy` เพื่อจัดการกับการ run pod ซ้อนได้ด้วย โดยมี policy ดังต่อไปนี้

- **Allow (default)**: `สามารถ run ซ้อนได้`
- **Forbid**: `ถ้างานเก่ายัง run ไม่เสร็จ งานใหม่จะถูก skip`
- **Replace**: `ยกเลิกงานเก่า แล้ว run งานใหม่แทนทันที`

ตัวอย่าง YAML file ของ Cronjobs

``` yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            args:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure
```

<a name="rbac"></a>

---

# RBAC

[RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) ประกอบด้วย 4 resources ย่อย ได้แก่

- **ClusterRoleBinding**: `เป็นการ grant role ให้กับ user ในระดับ cluster` 
- **ClusterRole**: `Role ในระดับ cluster`
- **RoleBinding**: `เป็นการ grant role ให้กับ user ในระดับ namespace`
- **Role**: `Role ในระดับ namespace`

`RoleBinding` สามารถ bind `ClusterRole` ให้ user ได้ แต่จะมีผลแค่ใน namespace ที่ระบุไว้เท่านั้น