LFS258 [6/15]: Managing State with Deployments
Tags: lfs258, kubernetes, docker, devops
Published: 2020-02-17 10:01:16

# TL;DR

- [Deployment](#deployment): เป็น default controller ของ kubernetes ที่คอยดูและเรื่องการ update version ของ image ใน pod โดยที replicaSet เป็นตัวช่วยในการสร้าง pods และ scale pods ให้มีจำนวนเท่ากับที่เรากำหนด
- [Object Relationship](#object-relationship): แสดงความสัมพันธ์ของ containers, pods, replicaSet และ Deployment ทำงานร่วมกัน ใน kubernetes เพื่อให้ run pods ขึ้นมาทำงานได้อย่างมีประสิทธิภาพ
- [Deployment Details](#deployment-details): แสดง parameter ต่างๆ ที่ใช้ในการสร้าง Deployment พร้อมทั้ง parameter ที่แสดง status ของ Deployment ณ ขณะนั้น
- [Scaling and Rolling Updates](#scaling-and-rolling-updates): แสดงตัวอย่างการทำงานกับ Deployment ในการ scaling และ rollout update 
- [Deployment Rollbacks](#deployment-rollbacks): แสดงบรรทึก revision ของ Deployment และการ rollback ไปยัง revision ต่างๆ ตามความต้องการของเรา 
- [Using DaemonSets](#using-daemonsets): แนะนำการใช้งาน อีก controller หนึ่งที่ช่วย deploy pods ไปยัง ทุกๆ node ใน cluster 
- [Labels](#labels): แสดงวิธ๊การทำงานร่วมกับ label ใน pods ทั้งห แสดง ตั้งต่า และ ลบค่าของ label


<a name="deployment"></a>

---

# Deployment

จากบทที่แล้ว เราได้เรียนรู้เกี่ยวกับ API ในการควบคุม Object ต่างๆ ของ Kubernetes กันไปแล้ว บทนี่เราจะมาดูกันที่ controller ที่ใช้บ่อยสุดๆ ที่ชื่อว่า Deployment กัน แต่ก่อนอื่นเรามาทำความรู้จักกับ บรรพบุรุษ (predecessor) ของ Deployment กันก่อน นั่นก็คือ **ReplicationControllers (RC)**

ReplicationControllers ช่วยงานเรา 2 เรื่อง คือ คอยจัดการให้ pod ที่เรา run ยังอยู่ครบตามที่เรากำหนด และ ทำ rolling update ให้เมื่อเราต้องการ update version ของ image ที่เรา run ใน pod แต่ ReplicationControllers มีปัญหาอยู่หนึ่งอย่างที่สำคัญคือ มันทำงานจาก client side นั่นหมายความว่า ถ้าบังเอิญว่าระหว่างที่เราสั่ง rolling update อยู่นั้น connection ระหว่างเราและ kubernetes cluster ขาดหายไป เราจะไม่รู้ว่าสิ่งที่เราสั่งไปนั้น ถึงไหนแล้ว พังหรือว่าสำเร็จ ด้วยข้อบกพรุ่งนี้ controller ตัวใหม่ที่ชื่อว่า **Deployments** ใน API version `extesions/v1beta1` จึงถือกำเนิดขึ้น (ตอนนี้ API เป็น `apps/v1` ซึง GA เรียบร้อยแล้ว)

Deployments ทำงานแบบ server side ปัญหาดังกล่าวจึงหมดไป นอกจากนั้น Deployment ยังมี feature ในการ ทำ Canary Deployment รวมถึง update strategy อื่นๆ เพิ่มขึ้นมาอีกด้วย

โดย Deployment เรียกใช้ ReplicaSet เพื่อเป็นตัวควบคุมจำนวน Pods ซึ่งข้อดีของการใช้ ReplicaSet คือ เราสามารถ select pods ด้วย label ได้

การสร้าง Deployment ใน Kubernetes สามารถทำได้ดังนี้

``` bash
# kubectl create deployment dev-web --image=nginx:1.13.7-alpine
deployment.apps/dev-web created

# kubectl get deployments
NAME      READY   UP-TO-DATE   AVAILABLE   AGE
dev-web   1/1     1            1           10s

# kubectl get rs
NAME                DESIRED   CURRENT   READY   AGE
dev-web-bf8fcb78f   1         1         1       18s

# kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
dev-web-bf8fcb78f-29wt7   1/1     Running   0          29s
```

<a name="object-relationship"></a>

---

# Object Relationship

เรารู้มาแล้วว่า Deployment หนึ่งใน controller ของ kubernetes ซึ่ง controller ก็คือ thread หนึ่งของ kube-controller-manager ที่ทำหน้าเป็น watch loop ในการ ดึง status ของ object ในความดูแลของมัน จาก kube-apiserver เพื่อเอามาตรวจสอบว่า สถานะของ object นั้นๆ ตรงตามที่ define ไว้ ใน desired state ไหน

ต่อไปเรามาดู ความสัมพันธ์ของ object ต่างๆ เมื่อเราทำการสร้าง deployment ขึ้นมากัน

| <center>Object</center> | <center>Description</center> |
|:------:|:-----------:|
| ![containers](https://dev-to-uploads.s3.amazonaws.com/i/u6q6baj4o2hl14igga8z.png) | **Containers**: เป็นส่วนที่อยู่นอกเหนือ<br/>การควบคุมของ kubernetes แต่ kubernetes <br/>จะควบคุมมันได้อ้อมๆ ผ่านทาง <br/>container runtime และ resource limitation <br/>ในระดับ Pods โดย container จะเป็นคนไปเอา <br/>image มา run ด้วย parameter ที่เรากำหนดไว้ |
| ![Pods](https://dev-to-uploads.s3.amazonaws.com/i/ogyxn5jdpnutyx8vw16h.png) | **Pods**: เป็นหน่วยที่เล็กที่สุด ที่ Kubernetes <br/>สามารถควบคุมได้ตรงๆ โดย pods <br/>จะ watch loop ในการ monitor ให้ <br/>container run อยู่ตามที่กำหนด โดยปกติ<br/> จะมีแค่ 1 container ต่อ 1 pods <br/>แต่ใน 1 pods สามารถมีได้หลาย containers <br/>ซึ่งมีมีแค่ 1 container ที่ทำ main logic <br/>ของ application ส่วน container อื่นๆ <br/>จะทำหน้าที่เป็นส่วนเสริม เช่น logging, <br/>metrics หรือ adapter เป็นต้น |
| ![ReplicaSet](https://dev-to-uploads.s3.amazonaws.com/i/ik3okondgsvjolf6xrp9.png) | **ReplicaSet**: เป็น controller ที่คอยตรวจสอบ<br/>ให้มี pods run อยู่เท่ากับที่กำหนดไว้ <br/>(desired state) เสมอ ถ้ามากกว่าก็จะทำการ<br/> terminate ถ้าน้อยกว่าก็จะ start ขึ้นมา <br/>หรือ ถ้า pods ไหน unhealty <br/>ก็จะ restart pods ให้ |
| ![Deployment](https://dev-to-uploads.s3.amazonaws.com/i/4dr6un3dltspqzvylgd6.png) | **Deployment**: เป็น controller ที่ทำหน้าที่<br/>จัดการ version ของ image ที่ deploy <br/>อยู่ใน pods โดย มันจะ สร้าง <br/>replicaSet ขึ้นมาใหม่ เมื่อทุกอย่าง<br/>เรียบร้อยแล้ว ก็จะสั่งให้ replicaSet เก่า<br/> terminate pods ของตัวเอง<br/> จากนั้นจึง terminate replicaSet เก่า <br/>เพื่อให้มี replicaSet เดียว run อยู่ |

<a name="deployment-details"></a>

---

# Deployment Details

ถ้าเราต้องการดู configure ทั้งหมดของ deployments สามารถ ได้ดังนี้

``` bash
==> YAML format
# kubectl get deployments -o yaml

==> JSON format
# kubectl get deployments -o json
```

แต่ในที่นี้เราจะมาดูด้วย YAML format กัน เพราะส่วนใหญ่ในการทำงานเราจะใช้ format โดย properties สำคัญๆ มีดังนี้

``` yaml
apiVersion: v1
items:
- apiVersion: apps/v1
  kind: Deployment
```

- **apiVersion**: `v1 แสดง version ของ List ว่าเป็น resource ที่ stable แล้ว`
- **items**: `ประกาศชื่อของ list ว่าชื่อ items โดยบรรทัดต่อไป ที่มีย่อหน้าลึกกว่านี้ จะเป็น element หนึ่งของ list นี้ โดย มี "-" แสดงตัวแบ่งแต่ละ element`
  - **apiVersion**: `แสดงเป็นของ version ของ Deployment ซึ่งถูกระบุไว้ในบรรทัดต่อไป`
  - **kind**: `ชนิดของ object ที่จะ create ในที่นี้คือ Deployment`

``` yaml
  metadata:
    annotations:
      deployment.kubernetes.io/revision: "1"
    creationTimestamp: "2020-02-22T04:47:50Z"
    generation: 1
    labels:
      app: dev-web
    name: dev-web
    namespace: default
    resourceVersion: "7790537"
    selfLink: /apis/apps/v1/namespaces/default/deployments/dev-web
    uid: 96c82067-21c1-4869-898f-55d34b1dda12
```

- **metadata**: `เป็นที่แสดง metadata ของ Deployment`
  - **annotations**: `เป็น key-value pair ที่ kubectl ไม่สามารถใช้ในการ เลือก object ได้ แต่มีไว้เพื่อให้ third-party application อ่าน หรือเพื่ออำนวยความสะดวกให้ admin ในการทำงาน`
  - **creationTimestamp**: `เวลาที่ object ถูกสร้างขึ้นมา ค่านี้จะไม่ถูก update หาก object ถูก edit`
  - **generation**: `จำนวนครั้งที่ object ถูก edit`
  - **labels**: `เป็น key-value pair ที่ kubectl และ API ใช้ในการ select หรือ exclude object โดยไม่ต้องสนใจชนิดของ object`
  - **name**: `เป็นชื่อของ object ที่เราระบุตอนสร้างมัน ซึ่งจะต้องไม่ซ้ำใน namespace เดียวกัน`
  - **resourceVersion**: `เป็นเลข version ของ object ใน etcd ถูกใช้ในการ handle concurrency ใน kube-apiserver โดยค่านี้จะเปลี่ยนทุกครั้งที่มีการเปลี่ยนแปลงเกิดขึ้นกับ object`
  - **selfLink**: `เป็น URI path ของ kube-apiserver ในการดึงข้อมูลผ่านทาง kube-api-server`
  - **uid**: `เลขประจำตัวของ object นั้นๆ`

``` yaml
  spec:
    progressDeadlineSeconds: 600
    replicas: 1
    revisionHistoryLimit: 10
    selector:
      matchLabels:
        app: dev-web
    strategy:
      rollingUpdate:
        maxSurge: 25%
        maxUnavailable: 25%
      type: RollingUpdate
```

- **spec**: `ใช้ระบุ specification ในการสร้าง object ซึ่งจะมีทั้งหมด 2 spec โดย spec ตรงนี้จะเป็น spec ของ replicaSet`
  - **progressDeadlineSeconds**: `เวลาเป็นวินาที ที่ยอมรับได้ในการสร้าง object ก่อนที่จะเกิด error`
  - **replicas**: `จำนวน pods ที่ replicaSet จะต้องสร้าง`
  - **revisionHistoryLimit**: `จำนวน revision ของ spec ที่จะเก็บไว้ เพื่อการ rollback`
  - **selector**: `ระบุ label ของ pod ที่ replicaSet จะต้องควบคุม ถ้ามีหลายค่าจะ AND กัน ดังนั้นห้ามสร้าง pod ที่มี label ตรงกับที่ replicaSet ต้องดูแลเอง เพราะอาจเกิดปัญหาได้`
    - **matchLabels**: `เป็น condition ในการเลือก label โดย label ที่มีค่าตรงกับที่ระบุ จะถูกควบคุมโดย replicaSet`
  - **strategy**: `ระบุวิธีการในการ update อาจเป็น rolling update (ค่อยๆ ส้รางใหม่แล้วลบไปทีละ pods) หรือ recreate (ลบทุก pods แล้วจึงสร้างใหม่)`
    - **maxsurge**: `เปอร์เซ็นต์ของจำนวน pod ที่ยอมให้มีค่าเกินกว่าที่ระบุไว้ได้ มักเกิดตอน update (default=25)`
    - **maxUnavailable**: `เปอร์เซ็นต์ของจำนวน pods ที่ยอมให้มีค่าน้อยกว่าที่ระบุไว้ได้ มักเกิดตอน update`
    - **type**: `type ของ object ที่เรากำลังจะสร้าง ในที่นี้คือ RollingUpdate`

``` yaml
    template:
      metadata:
        creationTimestamp: null
        labels:
          app: dev-web
      spec:
        containers:
        - image: nginx:1.13.7-alpine
          imagePullPolicy: IfNotPresent
          name: nginx
          resources: {}
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
        dnsPolicy: ClusterFirst
        restartPolicy: Always
        schedulerName: default-scheduler
        securityContext: {}
        terminationGracePeriodSeconds: 30
```

- **template**: `เป็น template สำหรับ replicaSet ที่ใช้ในการสร้าง pods ในความดูแลของมัน`
  - **Spec**: `เป็นการระบุ specification ของ pod`
    - **containers**: `เป็นชื่อ list ที่บอกว่าหลังจากนี้จะเป็น spec ของแต่ละ container ใน pods นะ`
      - **image**: `ระบุชื่อ image ที่จะถูก run ใน container นั้นๆ ปกติจะเป็น docker`
      - **imagePullPolicy**: `กำหนด policy ในการ download image ของ container runtime ว่าจะให้ใช้ cache หรือไม่`
      - **name**: `prefix ของชื่อ pods`
      - **resources**: `เป็นการกำหนด resource restriction เช่น CPU หรือ memory limit ปกติจะเป็นค่าว่าง`
      - **terminationMessagePath**: `เป็น path ที่จะให้ container write output ไม่ว่าจะเป็น success หรือ fail ลงไป`
      - **terminationMessagePolicy**: `เป็นวิธีการดึงค่า termination message default เป็น "File" คือดึงจาก termination message file เราอาจกำหนดเป็น "FallbackToLogsOnError" ก็ได้ ซึ่งหมายถึง ถ้าไม่มี termination message ให้ใช้ chuck สุดท้ายของ container log แล้ว จบการทำงานของ container ด้วย error`
      - **dnsPolicy**: `กำหนดว่าจะให้ container ใช้ coredns หรือ ถ้ากำหนดเป็น "Default" จะใช้ dns ตามที่ configure ไว้ที่ node`
      - **restartPolicy**: `ถ้า contianer ถูก Kill ไม่ว่าด้วยเหตุใด จะให้ restart ขึ้นมาไหม`
      - **schedulerName**: `กำหนดว่าจะให้ใช้ custome scheduler หรือ ใช้ default`
      - **securityContext**: `ใช้ระบุค่า security เช่น SELinux context, AppArmor values, users และ UIDs สำหรับ ให้ container ใช้`
      - **terminationGracePeriodSeconds**: `เวลาที่รอให้ container ตายด้วย SIGTERM ถ้าเกินกว่านั้น จะถูก SIGKILL เพื่อให้ container ตายไปเลย`

``` yaml
  status:
    availableReplicas: 1
    conditions:
    - lastTransitionTime: "2020-02-22T04:47:58Z"
      lastUpdateTime: "2020-02-22T04:47:58Z"
      message: Deployment has minimum availability.
      reason: MinimumReplicasAvailable
      status: "True"
      type: Available
    - lastTransitionTime: "2020-02-22T04:47:50Z"
      lastUpdateTime: "2020-02-22T04:47:58Z"
      message: ReplicaSet "dev-web-bf8fcb78f" has successfully progressed.
      reason: NewReplicaSetAvailable
      status: "True"
      type: Progressing
    observedGeneration: 1
    readyReplicas: 1
    replicas: 1
    updatedReplicas: 1
```

- **status**: `เป็นที่แสดง status ของ deployment`
  - **availableReplicas**: `แสดงจำนวน replica ที่พร้อมใช้งานเป็นเวลามากกว่า "minReadySeconds"`
  - **observedGeneration**: `จำนวนครั้งที่ deployment นี้ถูก update`
  - **readyReplicas**: `จำนวน Pods ที่สามารถใช้งานได้`
  - **replicas**: `จำนวน replica ที่กำหนดใน spec`
  - **updatedReplicas**: `จำนวน replica ที่ deployment เข้าถึงได้ และ match กับ spec ใน template`

<a name="scaling-and-rolling-updates"></a>

---

# Scaling and Rolling Updates

เราสามารถ update ได้แทบทุกค่าของ kubernetes ผ่านทาง API-server อาจมีบางค่าที่เป็น immutable อยู่แล้ว ซึ่งอาจเปลี่ยนแปลงไปตาม version แต่ที่ แก้ไขได้แน่ๆ และ แก้ไขได้บ่อยด้วย คือ `replicas` 

ถ้าเรา set `replicas` เป็น 0 จะไม่มี container run อยู่เลย แต่ `replicaSet` และ `Deployment` ยังคงอยู่

เราสามารถ scale pods ได้ดังนี้

``` bash
# kubectl scale deployment/dev-web --replicas=4
deployment.apps/dev-web scaled

# kubectl get deployment/dev-web
NAME      READY   UP-TO-DATE   AVAILABLE   AGE
dev-web   4/4     4            4           27h
```

และสามารถ upgrade version ของ image ได้ด้วย `kubectl edit ...` ดังนี้

``` bash
# kubectl edit deployment/dev-web
          :           :
    spec:
      containers:
      - image: nginx:1.17.8-alpine      #<-- edit here
        imagePullPolicy: IfNotPresent
        name: nginx
        resources: {}
          :           :
# kubectl edit deployment/dev-web
          :           :
Pod Template:
  Labels:  app=dev-web
  Containers:
   nginx:
    Image:        nginx:1.17.8-alpine   #<-- this is changed
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
          :           :
```

<a name="deployment-rollbacks"></a>

---

# Deployment Rollbacks

ในการทำการ update ต่างๆ ไปยัง Deployment เราอาจพบว่า บางครั้งอาจเกิดข้อผิดพลาดใน application ทำให้เราต้อง rollback กลับไปใช้ version ก่อนหน้า แต่ถ้าเราจำไม่ได้ล่ะ ว่าก่อนหน้านี้เป็น version อะไร แน่นอน kubernetes มีตัวช่วยให้เรา

kubernetes เก็บ revision ของ Deployment ให้เราด้วย ลองมาค่อยๆ ไล่ดูกัน ว่ามันทำอะไรได้บ้าง

- สร้าง deployment

``` bash
# kubectl create deployment dev-web --image nginx:1.7.9     
deployment.apps/dev-web created

# kubectl rollout history deployment/dev-web
deployment.apps/dev-web 
REVISION  CHANGE-CAUSE
1         <none>
```

- ทำการ update deployment ด้วยการ scale out เป็น 2 replicas จะเห็นว่าไม่มี revision เกิดขึ้นเพราะไม่ได้แก้ไข image

``` bash
# kubectl scale deployment/dev-web --replicas=2
deployment.apps/dev-web scaled

# kubectl rollout history deployment/dev-web   
deployment.apps/dev-web 
REVISION  CHANGE-CAUSE
1         <none>
```

- ทำการแก้ไข image ให้เป็น version ที่ใหม่กว่า

``` bash
# kubectl set image deployment/dev-web nginx=nginx:1.8.1
deployment.apps/dev-web image updated

# kubectl rollout history deployment/dev-web
deployment.apps/dev-web 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
```

- ทำการแก้ไข image และเพิ่ม option `--record` เพื่อให้ kubernetes record command ที่เราทำการแก้ไข

``` bash
# kubectl set image deployment/dev-web nginx=nginx:1.9.1 --record
deployment.apps/dev-web image updated

# kubectl rollout history deployment/dev-web
deployment.apps/dev-web 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         kubectl set image deployment/dev-web nginx=nginx:1.9.1 --record=true
```

- ทำการ rollback กลับไป revision ก่อนหน้า นั่นก็คือเราจะกลับไปที่ `revision=2`

``` bash
# kubectl rollout undo deployment/dev-web 
deployment.apps/dev-web rolled back

# # kubectl rollout history deployment/dev-web
deployment.apps/dev-web 
REVISION  CHANGE-CAUSE
1         <none>
3         kubectl set image deployment/dev-web nginx=nginx:1.9.1 --record=true
4         <none>
```

  จะเห็นว่า revision 2 หายไปและ revision ปัจจุบันเป็น revision 4 ซึ่งจริงๆ แล้วก็คือ revision 2 นั่นเอง

- ทำการ rollback ด้วยการระบุ revision ไปเลย โดยจะกลับไปยัง version=1

``` bash
# kubectl rollout undo deployment/dev-web --to-revision=1
deployment.apps/dev-web rolled back

# kubectl rollout history deployment/dev-web
deployment.apps/dev-web 
REVISION  CHANGE-CAUSE
3         kubectl set image deployment/dev-web nginx=nginx:1.9.1 --record=true
4         <none>
5         <none>
```

  จะเห็นว่า revision 1 หายไปและ revision ปัจจุบันเป็น revision 5 ซึ่งจริงๆ แล้วก็คือ revision 1 นั่นเอง

- **Note:** 
  - เราสามารถดู status ในการ rollback ได้ด้วย 
      - `kubectl rollout status deployment/dev-web`
  - ในระหว่างที่ rollout ถ้าเราพบอะไรผิดปกติ สามารถ pause การ rollout ได้ด้วย command
      - `kubectl rollout pause deployment/dev-web`
  - ถ้าเราตรวจสอบแล้วว่าทุกอย่างปกติ สามารถสั่ง ให้ rollout ต่อได้ด้วย command
      - `kubectl rollout resume deployment/dev-web`
  - ถ้าเราใช้ `ReplicationControllers` สามารถทำการ rolling update ได้ด้วย command
      - `kubectl rolling-update ...`

<a name="using-daemonSets"></a>

---

# Using DaemonSets

นอกจาก Deployment แล้ว controller ที่มักถูกใช้บ่อยๆ คือ **DaemonSet** 

DaemonSet คอยดูแลให้มี pod ที่ เราต้องการ run อยู่ที่ทุก host ใน cluster ถ้าเรา remove host ออกไป มันจะ remove pod ให้เรา เช่นกัน ถ้าเราเพิ่ม host เข้าไปใน cluster มันจะเอา pods ไป run ให้โดยอัตโนมัติ ซึ่งใน DaemonSet เดียวกัน pods ที่ run อยู่ในทุกๆ host จะเป็น image ชุดเดียวกันทั้งหมด

DaemonSet มักถูกใช้ในการ stream log ไปยัง centralized logging system, ส่ง metric ไปยัง service monitoring system และ networking เป็นต้น

ตัวอย่าง YAML file ที่ใช้ในการ create DaemonSet fluentd ที่ใช้ในการ stream log ไปยัง centralized logging system

``` yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
```

<a name="labels"></a>

---

# Labels

Labels เป็นส่วนหนึ่งของ metadata ของ object มันมีประโยชน์มากสำหรับผู้ดูแล cluster ในการใช้เลือก resources ได้ โดยไม่ต้องสนใจว่า จะเป็น resource ชนิดไหน ณ API version "apps/v1" Labels เป็น immutable object

Deployment มี default labels ที่ kubernetes ใส่เข้ามาให้โดยอัตโนมัติ คือ

``` bash
# kubectl describe pods dev-web-6dd54b45cb-8sjnx 
        :               :
Labels:       app=dev-web
              pod-template-hash=6dd54b45cb
        :               :
```

โดย
  - `app=<deployment_name>`
  - `pod-template-hash=<hash_result_of_PodTemplate>`

เราสามารถ แสดง label ของ pods ได้ดังนี้

``` bash
# kubectl get pods --show-labels
NAME                       READY   STATUS    RESTARTS   AGE     APP       LABELS
dev-app-7f98b7696c-6hx9s   1/1     Running   0          3m54s   dev-app   app=dev-app,pod-template-hash=7f98b7696c
dev-web-6dd54b45cb-8sjnx   1/1     Running   0          52m     dev-web   app=dev-web,pod-template-hash=6dd54b45cb
dev-web-6dd54b45cb-p6lgj   1/1     Running   0          52m     dev-web   app=dev-web,pod-template-hash=6dd54b45cb
```

เราสามารถเพิ่ม label ให้กับ pods ได้ดังนี้

``` bash
# kubectl label pods dev-app-7f98b7696c-6hx9s foo=bar
pod/dev-app-7f98b7696c-6hx9s labeled

# kubectl get pods --show-labels 
NAME                       READY   STATUS    RESTARTS   AGE     LABELS
dev-app-7f98b7696c-6hx9s   1/1     Running   0          9m59s   app=dev-app,foo=bar,pod-template-hash=7f98b7696c
dev-web-6dd54b45cb-8sjnx   1/1     Running   0          58m     app=dev-web,pod-template-hash=6dd54b45cb
dev-web-6dd54b45cb-p6lgj   1/1     Running   0          58m     app=dev-web,pod-template-hash=6dd54b45cb
```

เราสามารถ query pods โดยให้ label ที่เราต้องการ แสดงเป็นอีก 1 column ได้ดังนี้

``` bash
# kubectl get pods -L foo          
NAME                       READY   STATUS    RESTARTS   AGE   FOO
dev-app-7f98b7696c-6hx9s   1/1     Running   0          11m   bar
dev-web-6dd54b45cb-8sjnx   1/1     Running   0          59m   
dev-web-6dd54b45cb-p6lgj   1/1     Running   0          59m   

# kubectl get pods -L foo -L app
NAME                       READY   STATUS    RESTARTS   AGE   FOO   APP
dev-app-7f98b7696c-6hx9s   1/1     Running   0          13m   bar   dev-app
dev-web-6dd54b45cb-8sjnx   1/1     Running   0          61m         dev-web
dev-web-6dd54b45cb-p6lgj   1/1     Running   0          61m         dev-web
```

เราสามารถ fiter pods ที่มี label ที่เราต้องการได้โดย

``` bash
# kubectl get pods --show-labels -l app=dev-app 
NAME                       READY   STATUS    RESTARTS   AGE   LABELS
dev-app-7f98b7696c-6hx9s   1/1     Running   0          13m   app=dev-app,foo=bar,pod-template-hash=7f98b7696c
```

และเราสามารถ remove label ได้ดังนี้

``` bash
# kubectl label pods dev-app-7f98b7696c-6hx9s foo-
pod/dev-app-7f98b7696c-6hx9s labeled

# kubectl get pods -L foo  
NAME                       READY   STATUS    RESTARTS   AGE   FOO
dev-app-7f98b7696c-6hx9s   1/1     Running   0          17m   
dev-web-6dd54b45cb-8sjnx   1/1     Running   0          65m   
dev-web-6dd54b45cb-p6lgj   1/1     Running   0          65m 
```