Serial blog Nhập môn Kubernetes

DaemonSet

DaemonSet có thể coi là một bản sao đặc biệt của ReplicaSet mà mình đã giới thiệu phần trước. ReplicaSet sẽ bố trí tổng số XXX Pod trên các Node trong Kubernetes phù hợp với tình trạng tài nguyên của các Node đó như thế nào. Chính vì vậy không hẳn là số lượng Pod được phân bổ vào các Node là bằng nhau, và cũng không không hẳn là phân bố cho tất cả các Node.
DaemonSet là loại resource phân bố Pod một cách tuần tự từng Pod một trên tất cả các Node. Chính vì vậy mà chúng ta không thể setting replica, không thể phân bổ từng 2 Pod một vào 1 Node được.
uc?id=1-_XsBmir-97Lh7zgdQ4nmStMBUjRocNU&export=download
Nguồn: https://hackernoon.com/kubernetes-101-daemonsets-5-c5bbfcbb1579

Một trường hợp sử dụng DaemonSet như: khi chúng ta cần một chương trình nào đó nhất định phải chạy trên tất cả các Node

  • Fluentd: Collect log xuất ra từ các Pod
  • Datadog: Monitoring trạng thái hoạt động của các Node hay tình trạng sử dụng tài nguyên của các Pods.

Dưới đây là một user-case áp dụng DaemonSet
uc?id=1ur74a2hjpho9NP2cOpBc-anbOHrJuXqH&export=download
Nguồn: https://topdev.vn/blog/trien-khai-bo-log-tap-trung-centralized-logging-voi-docker-va-kubernettes-cho-server-su-dung-elk-stack/

Create DaemonSet

Chúng ta sẽ tạo một DaemonSet đơn giản, đầu tiên chúng ta cũng chuẩn bị file ds_sample.yml có nội dung dưới đây. Chúng ta sử dụng lại các pod đơn giản đã tạo ở phần trước. Khác với ReplicaSet, số lượng replica không cần setting.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: sample-ds
spec:
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80

Create DaemonSet

$ kubectl apply -f ./ds_sample.yml
daemonset.apps/sample-ds created

Confirm DaemonSet đã được tạo OK chưa, có phải một Pod được phân bổ vào mỗi Node không.

$ kubectl get pods -o wide
NAME                                 READY     STATUS    RESTARTS   AGE       IP           NODE
sample-ds-6bxrx                      1/1       Running   0          5m        10.42.2.23   Node2
sample-ds-8gggj                      1/1       Running   0          5m        10.42.1.15   Node1

Ở đây mình đang chạy ở môi trường Kubernetes được thiết lập bằng Rancher 2.0 như đã trình bày ở phần 3, trong đó 2 Nodes.

StatefulSet

StatefulSet là resource cũng có thể nói là bản sao đặc biệt của ReplicaSet. Giống như tên gọi của nó, nó là loại resource để đối ứng trang thái Workload như là Database.

Khác với ReplicaSet ở những điểm sau:

  • Tên các pod được tạo ra được đánh index bằng số.
    • sample-statefulset-1, sample-statefulset-2, …… sample-statefulset-N
  • Có cơ chế để cố định hoá
    • Trường hợp đang sử dụng PersistentVolume thì nó sẽ Re create trên cùng một ổ đĩa.
    • Tên Pod không thay đổi

Create StatefulSet

Chúng ta sẽ đi tạo một StatefulSet đơn giản. Đầu tiên chúng ta cũng chuẩn bị file statefulset_sample.yml có nội dung như dưới đây. Mình cũng sử dụng lại các Pod đã đã được tạo từ lần trước. So sánh với định nghĩa của ReplicaSet, chúng ta có thể setting thông số cho spec.volumeClaimTemplates. VolumeClaimTemplates sẽ cung cấp bộ nhớ ổn định bằng PersistentVolumes được cung cấp bởi PersistentVolume Provisioner.
Chi tiết tham khảo:
https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: sample-app
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: sample-app
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample-statefulset
spec:
  replicas: 3  # by default is 1
  selector:
    matchLabels:
      app: sample-app  # has to match .spec.template.metadata.labels
  serviceName: nginx
  template:
    metadata:
      labels:
        app: sample-app  # has to match .spec.selector.matchLabels
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80
          volumeMounts:
          - name: www
            mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi

Create StatefulSet

$ kubectl apply -f ./statefulset_sample.yml
statefulset "sample-statefulset" created

Thử kiểm tra xem. Các thông tin hiểu thị cũng giống như ReplicaSet

$ kubectl get statefulset
NAME                 DESIRED   CURRENT   AGE
sample-statefulset   3         3         14h

Kiểm tra các Pod được tạo bởi StatefulSet thì thấy có gắn index theo thứ tự 1 2 3 ... Thêm nữa trường hợp ta thêm hay xoá Pod bằng số lượng replica thì khác với ReplicaSet và DaemonSet, nó sẽ create/delete từng Pod một từng Pod một, việc này tốn rất ít thời gian.

Để các bạn dễ hình dung về cơ chế scale out/in của StatefulSet, mình mô tả bằng hình dưới đây.

uc?id=1nyT-Gsrm53LWXPVzxW8uiTXFP5JiAjl2&export=download

StatefulSet scaling

Giống như với ReplicaSets, chúng ta có thể sử dụng 「kubectl scale」hoặc là 「kubectl apply -f」để scale.

Kiểm tra lưu trữ data khu vực cố định

Bây giờ chúng ta sẽ test xem: chẳng hạn như kiểm tra xem một file đã tòn tại ở khu vực lưu trữ hay chưa, nếu chưa có thì tạo mới.

$ kubectl exec -it sample-statefulset-0 ls /usr/share/nginx/html/sample.html
ls: cannot access '/usr/share/nginx/html/sample.html': No such file or directory
command terminated with exit code 2

# create file sample.html
$ kubectl exec -it sample-statefulset-0 touch /usr/share/nginx/html/sample.html

# confirm
$ kubectl exec -it sample-statefulset-0 ls /usr/share/nginx/html/sample.html
/usr/share/nginx/html/sample.html

Chẳng hạn như trong trường hợp Pod sample-statefulset-0 bị xoá, hoặc là có một exception nào đó xảy ra bên trong container hay là container bị stop đi chăng nữa, thì sau khi phục hồi trở lại, file sample.html đã được tạo trước đó cũng không bị mất.

$ kubectl delete pod sample-statefulset-0
pod "sample-statefulset-0" deleted

#Stop container
$ kubectl exec -it sample-statefulset-0 bash
root@sample-statefulset-0:/# kill 0

# File đã tạo vẫn tồn lại
$ kubectl exec -it sample-statefulset-0 ls /usr/share/nginx/html/sample.html
/usr/share/nginx/html/sample.html

Sau khi confirm trạng thái của StatefuleSet sau khi phục hồi, mặc dù IP Address thay đổi, nhưng chúng ta có thể thấy là tên pod không thay đổi.

$ kubectl get pods -o wide
NAME                   READY     STATUS    RESTARTS   AGE       IP          NODE
sample-statefulset-0   1/1       Running   0          3m        10.8.1.10   gke-cluster-1-default-pool-8c632f08-mrp2
sample-statefulset-1   1/1       Running   0          51m       10.8.2.7    gke-cluster-1-default-pool-8c632f08-sdxm
sample-statefulset-2   1/1       Running   0          50m       10.8.0.7    gke-cluster-1-default-pool-8c632f08-d036

StatefulSet Life cycle

Khác với ReplicaSet, các Pod được tạo bởi StatefulSet không phân bố đồng thời vào các Node, nó sẽ create từng Pod một, đến khi nào Pod đó ở trạng thái Ready thì Pod thứ 2 mới bắt đầu được create. Thêm nữa khi xoá Pod, nó sẽ xoá ở Pod có index lớn nhất (Pod được tạo sau cùng).

Job

Là resource sử dụng container thực hiện xử lý chỉ một lần. Cụ thể hơn, nó là resource thực hiện xử lý một cách song song, đảm bảo thực thi container với số lần được chỉ định.

Khác nhau giữa sử dụng Job và Pod

Điểm khác nhau lớn nhất giữa Job, Pod và ReplicaSet là: "Pod có phải được tạo ra dựa trên tiền đề là việc stop hay không". Pod và ReplicaSet về cơ bản thì Stopped = Unexpected error, nhưng với Job thì Stopped = Nomal Finish. Thêm nữa, Pod và ReplicaSet không hẳn là đếm số lượng nomal finish, Trường hợp những xử lý kiểu như batch, thì mình khuyến khích xử dụng Job.

Create Job

Chúng ta sẽ làm một ví dụ nhỏ về Job, đầu tiên cũng chuẩn bị file YAML như bên dưới. Lần này mình sẽ tạo 1 Job thực hiện câu lệnh là sleep 60 giây. CŨng giống như ReplicaSets, mình có thể chỉ định label, selector, tuy nhiên kubernetes có cơ chế sinh ra một cách tự động để tránh xunng đột uuid. Vì vậy mình không khuyến khích các bạn setting nó ở đây.

#job_sample.yml
apiVersion: batch/v1
kind: Job
metadata:
  name: sample-job
spec:
  completions: 1
  parallelism: 1
  backoffLimit: 10
  template:
    spec:
      containers:
      - name: sleep-container
        image: centos:latest
        command: ["sleep"]
        args: ["60"]
      restartPolicy: Never

Create Job

$ kubectl apply -f job_sample.yml
job.batch/sample-job created

Confirm xem Job đã được khởi tạo chưa, khác với ReplicaSet vv, không hiển thị số lượng cotainer READY mà hiển thị số lượng nomal finish.

$ kubectl get jobs
NAME         DESIRED   SUCCESSFUL   AGE
sample-job   1         1            6m
[operator_user@Kube-Rancher ~]$ 
[operator_user@Kube-Rancher ~]$ kubectl get pods
NAME                                 READY     STATUS      RESTARTS   AGE
sample-job-m4mjx                     1/1       Running   0          8s

Khác nhau của hành vi với restartPolicy

Chúng ta có thể chỉ định cho tham số spec.template.spec.restartPolicy là OnFailure hay Never. Trường hợp là Never thì khi Pod gặp vất đề, sẽ tạo ra Pod mới thay thế. Trường hợp chỉ định là OnFailure, thì tiếp tục sử dụng Pod hiện tại chạy Job một lần nữa.

Thực thi kiểu hàng đợi và song song

Ở ví dụ phía trên, có 2 thông số là completions - số lần thực thi và parallelism - số lượng thực thi song song default là 1. Lần này ta thử thay đổi 2 tham số này như file bên dưới. Tham số backoffLimit là số lần cho phép thất bại.

apiVersion: batch/v1
kind: Job
metadata:
  name: sample-paralleljob
spec:
  completions: 10
  parallelism: 2
  backoffLimit: 10
  template:
    metadata:
      name: sleep-job
    spec:
      containers:
      - name: sleep-container
        image: centos:latest
        command: ["sleep"]
        args: ["30"]
      restartPolicy: Never

Kiểm tra hoạt động.

$ kubectl get jobs
NAME                 DESIRED   SUCCESSFUL   AGE
sample-paralleljob   10        4            1m

$ kubectl get pods
NAME                                 READY     STATUS      RESTARTS   AGE
sample-paralleljob-58sgs             0/1       Completed   0          1m
sample-paralleljob-6j4fg             1/1       Running     0          22s
sample-paralleljob-d6jkm             0/1       Completed   0          1m
sample-paralleljob-pnppt             0/1       Completed   0          56s
sample-paralleljob-qvkd7             0/1       Completed   0          1m
sample-paralleljob-xjc57             1/1       Running     0          31s

completions, parallelism và backoffLimit là 3 parameter rất quan trọng, đối với Job workload chúng ta cần setting phù hợp.

Trường hợp ta muốn task chỉ thực hiện 1 lần duy nhất: Setting completions=1, parallelism=1, backoffLimit=1.

uc?id=1nS-2mJu9Yrez2NmEZtP2Ve4GTlW8rjwv&export=download

Bây giờ nếu chúng ta thay đổi các thông số completions=5, parallelism=3, backoffLimit=5. Đầu tiên sẽ tạo ra 3 Pod, sau khi 3 Pod chạy finish thì còn lại 2pods, 2Pod đó chạy hoàn thành nữa là OK.

uc?id=1FyMZu9FDX8cN6y0BWdOvcHniKwgzXc8i&export=download

Nếu chúng ta không chỉ định param completions thì sẽ sẽ chạy liên tục không dừng lại. Nếu param backoffLimit không được setting thì mặc định của nó là 6.
Chúng ta phải chú ý giá trị mặc định này.

uc?id=1SOCvZLK6y_WxPaU03WJ-KydrF440Diyq&export=download

uc?id=1OIhGtjNXxU3RrIAT3RUTwogICEKKnaYT&export=download

CronJob

CronJob là loại resource giống với Job, cho đến kubernetes version 1.4 thì nó có tên gọi là ScheduledJob, bây giờ thì đổi thành CronJob. CronJob giống như một biến thể của Job, quan hệ giữa Job và CronJob cũng giống như Deployment và ReplicaSet. CronJob giống như Cron, thực hiện việc tạo các Job khi schedule.
uc?id=1OAMkArQjhDkai9Wuwq5XumQzfEbt46Mp&export=download

Create CronJob

Tạo file cronjob_sample.yml có nội dung như bên dưới. Lần này mình tạo 1 CronJob có nhiệm vụ create Job (sleep 30s) sau mỗi 60s. Tham số cho spec.schedule giống như format của Cron vậy.

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: sample-cronjob
spec:
  schedule: "*/1 * * * *"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 30
  successfulJobsHistoryLimit: 5
  failedJobsHistoryLimit: 5
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: sleep-container
            image: centos:latest
            command: ["sleep"]
            args: ["30"]
          restartPolicy: Never

Create CronJob

$ kubectl apply -f cronjob_sample.yml 
cronjob.batch/sample-cronjob created

Chúng ta cũng có thể sừ dụng 「kubectl run --schedule」 để tạo CronJob.
Lúc mới khởi tạo thì trạng thái sẽ là không tồn tại Job nào ACTIVE cả.

$ kubectl get cronjobs
NAME             SCHEDULE      SUSPEND   ACTIVE    LAST SCHEDULE   AGE
sample-cronjob   */1 * * * *   False     0         <none>          10s

$ kubectl get job
No resources found.

Đến thời điểm như trong file setting, thì CronJob sẽ chạy

$ kubectl get cronjobs
NAME             SCHEDULE      SUSPEND   ACTIVE    LAST SCHEDULE   AGE
sample-cronjob   */1 * * * *   False     1         14s             5m

$ kubectl get job
NAME                        DESIRED   SUCCESSFUL   AGE
sample-cronjob-1534666500   1         1            5m
sample-cronjob-1534666560   1         1            4m
sample-cronjob-1534666620   1         1            3m
sample-cronjob-1534666680   1         1            2m
sample-cronjob-1534666740   1         1            1m

Tạm dừng CronJob

Khi đã setting CronJob thì đến thời điểm nó sẽ chạy Job cho chúng ta. Trường hợp hệ thống đang mantain và chung ta muốn dừng CronJob thì cũng ta có thể thực hiện suspend (Tạm dừng). Trong CronJob, nhưng đối tượng được thiết lập spec.suspend là true sẽ nằm ngoài đối tượng được schedule. Chúng ta có thể chỉnh sửa file YAML và chạy lại lệnh 「kubectl apply」 cũng Ok, lần này mình sẽ sử dụng lệnh 「kubectl patch」 để thực hiện việc stop CronJob.

$ kubectl patch cronjob sample-cronjob -p '{"spec":{"suspend":true}}'
cronjob.batch/sample-cronjob patched

Thực hiện kubectl get cronjob để kiểm tra thì thấy giá trị SUSPEND đã hiển thị true, kể từ bây giờ thì Job sẽ không được tạo ra nữa. Nếu muốn thực hiện cronjob trở lại thì setting spec.suspend là false.

kubectl get cronjob
NAME             SCHEDULE      SUSPEND   ACTIVE    LAST SCHEDULE   AGE
sample-cronjob   */1 * * * *   True      0         2m              3m

$ kubectl get job
NAME                        DESIRED   SUCCESSFUL   AGE
sample-cronjob-1534667460   1         1            4m
sample-cronjob-1534667520   1         1            3m

Kiểm soát liên quan đến thực thi đồng thời

CronJob có khả năng thiết lập policy liên quan đến thực thi đồng thời. Như mình đã trình bày, khi một job kết thúc thì Job mới được tạo ra. Tuy nhiên, ngay cả trong trường hợp Job cũ vẫn chưa kết thúc công việc thì vẫn có cơ chế để tạo Job mới chạy đồng thời.
Chúng ta có thể setting ở tham số spec.concurrencyPolicy

  • Allow(default): Không hạn chế đối với việc thực thi đồng thời
  • Forbid: Job trước đó thực hiện chưa xong thì không tạo Job mới.(không thực hiện đồng thời)
  • Replace: Trong trường hợp Job cũ đang chạy, huỷ Job cũ và tạo Job mới

Ở CronJob, khi được setting thời gian chạy Job với Kubernetes Master, thì đến thời điểm đó, Job sẽ được khởi chạy. Chính vì vậy trong trường hợp Kubernetes Master bị down vv, chúng ta có thể chỉ định số giây cho phép có thể trễ bằng tham số spec.startingDeadlineSeconds . Ví dụ như chúng ta có thể setting Job chạy hàng giờ lúc 00 phút bằng cách cho phép nó chạy hàng giờ từ 00 phút đến 05 phút. Như vậy thì tham số chúng ta cần setting là spec.startingDeadlineSeconds: 300sec. Giá trị default của tham số này là cho dũ trễ bao lâu đi nữa nó vẫn khởi chạy Job cho chúng ta.
Ngoài ra ở CronJob có một parameter quan trọng là chỉ định số lượng Job luôn được duy trì, đó là spec.successfulJobsHistoryLimit và spec.failedJobsHistoryLimit:

  • spec.successfulJobsHistoryLimit: Số lượng Job thành công được duy trì
  • spec.failedJobsHistoryLimit: Số lượng Job thất bại duy trì.

Trong ví dụ phía trên thì mình có setting cho tham số spec.successfulJobsHistoryLimit: 5. Sau 6 phút kể từ khi chạy CronJob thì mình chạy lệnh kiểm tra kubectl get jobs, thì thu được kết quả là 5 Jobs gần nhất. Giá trị mặ định của tham số này là 3.

Phần tới mình sẽ giới thiệu về Discovery và Loab Balancing resource. Discovery&LB là loại resource cung cấp Endpoint cho phép truy cập từ bên ngoài đến các Workload resource đang được khởi chạy.

Tài liệu tham khảo

  1. https://kubernetes.io/docs/concepts/workloads/controllers/
  2. https://thinkit.co.jp/article/13611
  3. https://hackernoon.com/kubernetes-101-daemonsets-5-c5bbfcbb1579