[Kubernetes Series] – Bài 9 – StatefulSets: deploying replicated stateful applications

Giới thiệu Chào các bạn tới với series về kubernetes. Đây là bài thứ 9 trong series của mình, ở những bài trước chúng ta đã dùng Pod, ReplicaSet, Deployment để deploy một ứng dụng hoặc nhiều ứng dụng mà chạy chung một template để tăng performance. Ở bài này chúng ta sẽ nói về

Giới thiệu

Chào các bạn tới với series về kubernetes. Đây là bài thứ 9 trong series của mình, ở những bài trước chúng ta đã dùng Pod, ReplicaSet, Deployment để deploy một ứng dụng hoặc nhiều ứng dụng mà chạy chung một template để tăng performance. Ở bài này chúng ta sẽ nói về StatefulSets, một resource sẽ giúp ta deploy một stateful application.

Stateless application là một ứng dụng mà không có lưu trữ trạng thái của chính nó, hoặc là không có lưu trữ dữ liệu mà cần persistent storage. Ví dụ là một web server API mà không có lưu trữ hình ảnh, hoặc session login của user, thì đó là một stateless apps, bởi vì cho dù ta có xóa ứng dụng của ta và tạo lại nó bao nhiêu lần thì cũng không ảnh hưởng tới dữ liệu của người dùng. Bởi vì dữ liệu của ta được lưu trữ thông qua database, web server API chỉ kết nối với database và lưu trữ dữ liệu, chứ nó không có dữ liệu của chính nó. Một ví dụ nữa là command line app, nó không cần phải lưu trữ dữ liệu gì cả, tất cả những gì nó cần là xuất ra kết quả và không cần phải lưu lại kết quả đó. Những đặc tính của stateless app sẽ giúp nó dễ dàng scale hơn.

Stateful application thì yêu cầu có trạng thái (state) của chính nó, và cần lưu lại state đó, hoặc yêu cầu phải có lưu trữ dữ liệu mà cần persistent storage, dữ liệu này sẽ được sử dụng bởi client và các app khác. Ví dụ như là database, nó sẽ có dữ liệu riêng của nó.

Ở trong kubernetes ta có thể deploy một stateful application bằng cách tạo Pod và config volume cho Pod, hoặc dùng PersistentVolumeClaim. Nhưng ta chỉ có thể tạo một single instance của Pod mà kết nối tới PersistentVolumeClaim đó. Vậy thì có thể dùng ReplicaSet để tạo replicated stateful app không? Cái ta muốn là sẽ tạo ra nhiều replicas của Pod, và mỗi Pod ta sẽ dùng một PersistentVolumeClaim riêng, để chạy được một ứng dụng distributed data store.

Hạn chế của việc sử dụng ReplicaSet để tạo replicated stateful app

Vì ReplicaSet tạo nhiều pod replicas từ một Pod template, nên những Pod được replicated đó không khác với những Pod khác ngoại trừ tên và IP. Nếu ta config volume trong Pod template thì tất cả các Pod được replicated đều lưu trữ dữ liệu chung một storage.

Nên ta không thể sử dụng một ReplicaSet rồi set thuộc tính replicas của nó để chạy một ứng dụng distributed data store được. Ta cần phải sử dụng cách khác.

Tạo nhiều ReplicaSet chỉ có một Pod mỗi ReplicaSet

Ta tạo nhiều ReplicaSet và mỗi ReplicaSet đó sẽ có một template Pod khác nhau.

Ta có thể dụng cách này để deploy một ứng dụng distributed data store. Nhưng đây không phải là cách tốt, ví dụ khí ta muốn scale ứng dụng của ta lên thì ta sẽ làm thế nào? Ta chỉ có cách là tạo thêm một ReplicaSet bằng tay, công việc này không tự động chút nào. Ta chọn kubernetes để chạy ứng dụng là vì muốn mọi thứ như scale đều được tự động và dễ dàng nhất.

Cung cấp stable identity cho mỗi Pod

Đối với statefull application, ta cần định danh cho mỗi Pod, vì Pod có thể bị xóa và tạo lại bất cứ lúc nào, khi ReplicaSet thay thế một thằng Pod cũ bằng Pod mới thì Pod mới tạo ra sẽ có tên khác và IP khác. Cho dù dữ liệu ta vẫn còn đó và giống như Pod cũ, nhưng đối với một số ứng dụng, khi ta tạo Pod mới mà nó có một network identity mới (như địa chỉ IP) thì sẽ sinh ra nhiều vấn đề. Nên ta cần phải dùng Service để identity IP cho Pod, ta có bao nhiêu ReplicaSet thì ta sẽ cần tạo bấy nhiêu Service tương ứng.

Khi ta có thêm Service, lúc này ta muốn scale lên, bên cạnh việc phải tạo một ReplicaSet mới, bây giờ ta cần phải tạo thêm một Service mới cho thằng ReplicaSet tương ứng nữa, gấp đôi công việc phải làm bằng tay.

Thì để giải quyết những vấn đề trên, có thể dễ dạng tạo nhiều replicated của Pod, mỗi thằng sẽ có một định danh riêng, và dễ dạng tự động scale mà không cần ta phải làm bằng tay quá nhiều, giúp ta dễ hơn trong việc tạo một ứng dụng distributed data store. Kubernetes cung cấp cho chúng ta một resource tên là StatefulSet.

StatefulSets

Giống như ReplicaSet, StatefulSet là một resource giúp chúng ta chạy nhiều Pod mà cùng một template bằng cách set thuộc tính replicas, nhưng khác với ReplicaSet ở chỗ là Pod của StatefulSet sẽ được định danh chính xác và mỗi thằng sẽ có một stable network identity của riêng nó.

Mỗi Pod được tạo ra bởi StatefulSet sẽ được gán với một index, index này sẽ được sử dụng để định danh cho mỗi Pod. Và tên của Pod sẽ được đặt theo kiểu <statefulset name>-<index>, chứ không phải random như của ReplicaSet.

Cách StatefulSets thay thế một Pod bị mất

Khi một Pod mà được quản lý bởi một StatefulSets bị mất (do một worker node bị failed hoặc bị ai đó xóa đi), thằng StatefulSets sẽ tạo ra một Pod mới để thay thế thằng cũ tương tự như cách làm của ReplicaSet, nhưng thằng Pod được tạo mới này sẽ có tên và hostname giống y như thằng cũ.

Còn thằng ReplicaSet thì sẽ tạo ra thằng Pod mới hoàn toàn khác với thằng cũ.

Cách StatefulSets scale Pod

Khi ta scale up Pod trong StatefulSets, nó sẽ tạo ra một Pod mới được đánh index là số tiếp theo của index hiện tại. Ví dụ StatefulSets đang có replicas bằng 2, sẽ có 2 Pod là <pod-name>-0,<pod-name>-1, khi ta scale up Pod lên bằng 3, Pod mới được tạo ra sẽ có tên là <pod-name>-2.

Tương tự như vậy với scale down, nó sẽ xóa Pod với index lớn nhất. Đối với StatefulSets thì khi ta scale up và scale down thì ta có thể biết chính xác tên của Pod sẽ được tạo ra hoặc xóa đi.

Cung cấp storage riêng biệt cho mỗi Pod

Tới đây thì chúng ta đã biết cách StatefulSets định danh cho mỗi thằng Pod, vậy còn storage thì sao? Mỗi Pod của chúng ta cần có một storage của riêng nó, và khi ta scale down số lượng Pod và scale up lại thì thằng Pod tạo ra mà có index giống với thằng cũ thì vẫn giữ nguyên storage của nó như không phải tạo ra một thằng storage khác.

Thằng StatefulSets làm được việc đó bằng cách tách storage ra khỏi Pod bằng cách sử dụng PersistentVolumeClaims mà ta đã nói ở bài 7. StatefulSets sẽ tạo ra PersistentVolumeClaims cho mỗi Pod và gắn nó vào cho từng Pod tương ứng.

Khi ta scale up Pod trong StatefulSets, thì sẽ có một Pod và một PersistentVolumeClaims mới được tạo ra, nhưng khi ta scale down, thì chỉ có thằng Pod bị xóa đi, thằng PersistentVolumeClaims vẫn giữ ở đó và không bị xóa. Để khi ta scale up lại thì thằng Pod vẫn được gắn đúng với PersistentVolumeClaims trước đó để dữ liệu của nó vẫn được giữ nguyên.

Tạo một StatefulSets

Giờ ta sẽ tạo thử một StatefulSets. Tạo một file tên là kubia-statefulset.yaml với image luksa/kubia-pet, code của image như sau:

const http =require('http');const os =require('os');const fs =require('fs');const dataFile ="/var/data/kubia.txt";functionfileExists(file){try{
    fs.statSync(file);returntrue;}catch(e){returnfalse;}}varhandler=function(request, response){if(request.method =='POST'){var file = fs.createWriteStream(dataFile);
    file.on('open',function(fd){
      request.pipe(file);
      console.log("New data has been received and stored.");
      response.writeHead(200);
      response.end("Data stored on pod "+ os.hostname()+"n");});}else{var data =fileExists(dataFile)? fs.readFileSync(dataFile,'utf8'):"No data posted yet";
    response.writeHead(200);
    response.write("You've hit "+ os.hostname()+"n");
    response.end("Data stored on this pod: "+ data +"n");}};var www = http.createServer(handler);
www.listen(8080);

Config của file kubia-statefulset.yaml:

apiVersion: v1
kind: Service
metadata:name: kubia
spec:clusterIP: None
  selector:app: kubia
  ports:-name: http
      port:80---apiVersion: apps/v1beta1
kind: StatefulSet
metadata:name: kubia
spec:serviceName: kubia # the name of servicereplicas:2template:# pod templatemetadata:labels:app: kubia
    spec:containers:-name: kubia
          image: luksa/kubia-pet
          ports:-name: http
              containerPort:8080volumeMounts:-name: data
          mountPath: /var/data
  volumeClaimTemplates:# pvc template-metadata:name: data
      spec:resources:requests:storage: 1Mi
        accessModes:- ReadWriteOnce

Trong file config này ta sẽ có một Headless Service và một StatefulSet tên là kubia. Trong config của StatefulSet thì ta phải chỉ định service name dùng để định danh network cho Pod, Pod template, và PersistentVolumeClaims template. Cái khác với config của ReplicaSet là ta cần khai báo thêm template cho PersistentVolumeClaims, StatefulSet sẽ dùng nó để tạo ra PVCs riêng cho từng Pod.

Tạo StatefulSet:

$ kubectl create -f kubia-statefulset.yaml
service "kubia" created
statefulset "kubia" created

Giờ ta list Pod xem thử:

$ kubectl get po
NAME     READY  STATUS             RESTARTS  AGE
kubia-0  1/1    Running            0         8s
kubia-1  0/1    ContainerCreating  0         2s

Ta thấy Pod chúng ta được tạo ra sẽ có tên được gán theo index, giờ ta list PVCs xem thử:

$ kubectl get pvc
NAME          STATUS  VOLUME  CAPACITY  ACCESSMODES  AGE
data-kubia-0  Bound   pv-0    0                      37s
data-kubia-1  Bound   pv-1    0                      37s

Tên của PersistentVolumeClaims sẽ được đặt theo tên mà ta chỉ định ở phần volumeClaimTemplate và nối với index. Ở đây thì ta đã thấy là mỗi Pod của chúng ta sẽ có định danh riêng và xài một PVCs riêng của nó. Đúng với thứ ta cần khi xây dựng một hệ thống lưu trữ phân tán.

Tương tác với Pod bằng định danh của nó

Giờ ta sẽ thử tương tác với từng thằng Pod riêng lẻ. Đâu tiên ta dùng câu lệnh proxy:

$ kubectl proxy
Starting to serve on 127.0.0.1:8001

Mở một terminal khác:

$ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
You've hit kubia-0
Data stored on this pod: No data posted yet
$ curl -X POST -d "Hey there! This greeting was submitted to kubia-0." localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
Data stored on pod kubia-0
$ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
You've hit kubia-0
Data stored on this pod: Hey there! This greeting was submitted to kubia-0.

Kết quả trả về in ra là ta đã kết nối với Pod kubia-0.

Bây giờ ta xóa Pod để kiểm tra xem Pod mới được tạo ra có sử dụng đúng PVCs cũ hay không.

$ kubectl delete po kubia-0
pod "kubia-0" deleted

List Pod để xem thử quá trình nó bị xóa và tạo lại.

$ kubectl get po
NAME     READY  STATUS       RESTARTS  AGE
kubia-0  1/1    Terminating  0         3m
kubia-1  1/1    Running      0         3m
$  kubectl get po
NAME     READY  STATUS             RESTARTS  AGE
kubia-0  0/1    ContainerCreating  0         6s
kubia-1  1/1    Running            0         4m
$ kubectl get po
NAME     READY  STATUS   RESTARTS  AGE
kubia-0  1/1    Running  0         9s
kubia-1  1/1    Running  0         4m

Ta thấy rằng ở đây Pod mới của chúng ta tạo ra sẽ có định danh y như Pod cũ, đó là điều mà ta muốn, giờ ta thử xem dữ liệu trước đó của ta có còn ý nguyên trong Pod kubia-0 không.

$ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
You've hit kubia-0
Data stored on this pod: Hey there! This greeting was submitted to kubia-0.

Dữ liệu của ta vẫn còn đây, đúng với thứ mà ta muốn. Như các bạn thấy thì dùng StatefulSet sẽ tạo ra cho các Pod được định danh và có PVCs cũng được định danh. Vậy còn network thì thế nào? Nghĩa là ta sẽ truy cập từng Pod cụ thể ra sao nếu không dùng proxy, ở các bài trước thì ta dùng Service để tương tác với Pod, mà thằng Service thì request của nó sẽ được gửi random tới những Pod phía sau chứ không phải chính xác một thằng Pod, ta muốn ta có thể request tới chính xác một thằng Pod.

Ở đây ta sẽ dùng một kĩ thuật gọi là Headless Service, như các bạn thấy trong file config ở trên ta có tạo một service mà chỉ định thuộc tính clusterIP của nó là None. Đây là cách ta sẽ tạo ra một Headless Service và định danh địa chỉ cho từng thằng Pod.

Headless Service

Đối với thằng Service ClusterIP bình thường, thì khi ta tạo Service đó, nó sẽ tạo ra một thằng Virtual IP cho chính nó và một thằng DNS tương ứng cho VIP đó, và thằng VIP này sẽ mapping với những thằng Pod phía sau Service.

Còn đối với thằng Headless Service, khi khai báo config ta sẽ chỉ định thuộc tính clusterIP: None cho nó, khi ta tạo một thằng Headless Service thì nó sẽ không tạo ra một Virtual IP cho chính nó, mà chỉ tạo ra một DNS. Sau đó, nó sẽ tạo ra DNS cho chính xác từng thằng Pod phía sau, và mapping DNS tới những thằng DNS của Pod phía sau nó. Ví dụ thằng kubia-0 thì sẽ có DNS tương ứng là kubia-0.kubia.default.svc.cluster.local.

Và ta có thể truy cập thẳng tới Pod bên trong cluster bằng cách gọi tới DNS kubia-0.kubiakubia-1.kubia nếu truy cập cùng namespace. Hoặc dùng DNS kubia-0.kubia.default.svc.cluster.localkubia-1.kubia.default.svc.cluster.local nếu truy cập khác namespace.

Headless Service cho phép chúng ta có thể truy cập thẳng tới một Pod nhất định sử dụng DNS, thay vì truy cập qua DNS của Service rồi request của ta sẽ được dẫn tới một Pod random. Ta kết hợp Headless Service với StatefulSet để cung cấp cho Pod một stable network identity, và vì mỗi Pod có định danh bằng index riêng nên ta có thể biết chính xác Pod nào chúng ta cần gọi tới.

Kết luận

Vậy là ta đã tìm hiểu xong về StatefulSet, dùng StatefulSet cho stateful application và database, nên dùng StatefulSet để chạy database thay vì dùng ReplicaSet hoặc Deployment. Nếu có thắc mắc hoặc cần giải thích rõ thêm chỗ nào thì các bạn có thể hỏi dưới phần comment. Tới bài này thì chúng ta đã tìm hiểu hết về các resource cơ bản để deploy một container trong kubernetes. Ở những bài tiếp theo chúng ta sẽ đi sâu về cấu trúc bên trong của Kubernetes Cluster. Đầu tiên ta sẽ nói về cách để container có thể truy cập metadata của chính nó bằng cách dùng Downward API.

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ