[Note] Khóa học Kubernetes cơ bản (tiếng Việt) – Phần 3 – Chap 9 -> 12

Chapter 9. Authentication, Authorization, Admission Control

Mỗi yêu cầu API đến máy chủ API phải trải qua một số bước kiểm soát trước khi được máy chủ chấp nhận và xử lý. Trong chương này, chúng ta sẽ tìm hiểu về các giai đoạn Xác thực, Ủy quyền và Kiểm soát Truy cập (Admission Control) trong cơ chế kiểm soát truy cập API của Kubernetes.

Đến cuối chương này, bạn sẽ có thể:

  • Thảo luận về các giai đoạn xác thực, ủy quyền và kiểm soát truy cập trong cơ chế kiểm soát truy cập API của Kubernetes.

  • Hiểu các loại người dùng Kubernetes khác nhau.

  • Khám phá các mô-đun khác nhau phục vụ cho quá trình xác thực và ủy quyền.

Authentication, Authorization, and Admission Control – Overview

Để truy cập và quản lý các tài nguyên hoặc đối tượng Kubernetes trong cụm, chúng ta cần truy cập vào một điểm cuối API (API endpoint) cụ thể trên máy chủ API. Mỗi yêu cầu truy cập phải trải qua các giai đoạn kiểm soát truy cập sau:

  • Xác thực (Authentication): Xác thực người dùng dựa trên thông tin đăng nhập được cung cấp trong các yêu cầu API.

  • Ủy quyền (Authorization): Cấp phép cho các yêu cầu API được gửi bởi người dùng đã xác thực.

  • Kiểm soát truy cập (Admission Control): Các mô-đun phần mềm xác thực và/hoặc sửa đổi yêu cầu của người dùng.

Hình ảnh sau mô tả các giai đoạn trên:

Authentication

Kubernetes không lưu trữ trực tiếp đối tượng “người dùng” (user) hay các thông tin chi tiết về họ. Tuy nhiên, Kubernetes vẫn có thể sử dụng tên người dùng (username) trong giai đoạn Xác thực (Authentication) khi kiểm soát truy cập API và cũng cho mục đích ghi nhật ký (logging).

Kubernetes hỗ trợ hai loại người dùng:

  • Người dùng thông thường (Normal Users): Được quản lý bên ngoài cụm Kubernetes thông qua các dịch vụ độc lập như Chứng chỉ Người dùng/ Máy khách (User/Client Certificates), một file lưu trữ danh sách tên người dùng/mật khẩu, tài khoản Google, v.v.

  • Tài khoản dịch vụ (Service Account): Cho phép các tiến trình trong cụm (in-cluster) giao tiếp với máy chủ API nhằm thực hiện các hoạt động khác nhau. Hầu hết Tài khoản dịch vụ được tạo tự động thông qua máy chủ API, nhưng cũng có thể được tạo thủ công. Tài khoản dịch vụ được gắn với một không gian tên (Namespace) cụ thể và chúng sử dụng thông tin xác thực (credentials) dạng Secrets để giao tiếp với máy chủ API.

Nếu được cấu hình đúng cách, Kubernetes cũng có thể hỗ trợ các yêu cầu ẩn danh (anonymous requests), cùng với yêu cầu từ Người dùng thông thường và Tài khoản dịch vụ. Ngoài ra, tính năng giả mạo người dùng (User impersonation) cho phép một người dùng hành động như một người dùng khác – rất hữu ích cho quản trị viên khi khắc phục sự cố các chính sách ủy quyền.

Để xác thực, Kubernetes sử dụng một loạt các mô-đun xác thực:

  • Chứng chỉ máy khách X509: Để sử dụng, ta cần cung cấp đường dẫn đến một file chứa các cơ quan cấp chứng chỉ (certificate authorities) bằng cách truyền tham số -client-ca-file=SOMEFILE tới máy chủ API. Các cơ quan cấp chứng chỉ này sẽ xác thực chứng chỉ máy khách được người dùng xuất trình cho máy chủ API.

  • File Token tĩnh: Ta truyền đường dẫn đến một file chứa các bearer token xác định trước bằng tham số -token-auth-file=SOMEFILE cho máy chủ API. Lưu ý, các token này hiện tại có thời hạn vô tận và không thể thay đổi mà không cần khởi động lại máy chủ API.

  • Bootstrap Token: Token sử dụng để khởi tạo các cụm Kubernetes mới.

  • Service Account Token: Trình xác thực (authenticator) được tự động kích hoạt, sử dụng các bearer token đã ký để xác minh các yêu cầu. Các token này được gắn với các Pod thông qua Bộ điều khiển truy cập tài khoản dịch vụ (Service Account Admission Controller), cho phép các tiến trình trong cụm giao tiếp với máy chủ API.

  • OpenID Connect Token: Hỗ trợ kết nối với các nhà cung cấp OAuth2 (như Azure Active Directory, Salesforce, Google) để chuyển việc xác thực ra dịch vụ bên ngoài.

  • Xác thực Webhook Token: Cho phép chuyển việc xác thực các bearer token ra một dịch vụ từ xa.

  • Xác thực qua Proxy (Authenticating Proxy): Cho phép lập trình thêm các logic xác thực bổ sung.

Chúng ta có thể kích hoạt nhiều trình xác thực và mô-đun đầu tiên xác thực thành công yêu cầu sẽ kết thúc quá trình đánh giá. Để đảm bảo xác thực người dùng thành công, chúng ta nên kích hoạt ít nhất hai phương thức: trình xác thực Service Account Token và một trong các trình xác thực người dùng.

Authorization (1)

Sau bước xác thực thành công, người dùng có thể gửi các yêu cầu API để thực hiện các thao tác khác nhau. Tại đây, các yêu cầu API này được Kubernetes ủy quyền (authorized) bằng cách sử dụng các mô-đun ủy quyền cho phép hoặc từ chối các yêu cầu.

Một số thuộc tính (attribute) của yêu cầu API được Kubernetes xem xét bao gồm người dùng (user), nhóm (group), tài nguyên (resource), không gian tên (namespace) hoặc nhóm API (API group). Các thuộc tính này sẽ được đánh giá dựa trên các chính sách (policy) đã được thiết lập. Nếu đánh giá thành công, yêu cầu được cho phép, ngược lại, yêu cầu sẽ bị từ chối. Tương tự như bước Xác thực, bước Ủy quyền cũng có nhiều mô-đun hoặc trình ủy quyền (authorizer).

Ta có thể cấu hình nhiều hơn một mô-đun cho một cụm Kubernetes, và mỗi mô-đun được kiểm tra theo thứ tự. Nếu bất kỳ trình ủy quyền nào chấp thuận hoặc từ chối yêu cầu, quyết định đó sẽ được trả về ngay lập tức.

Cụ thể:

  • Ủy quyền Node (Node Authorization): Chế độ ủy quyền đặc biệt, cấp phép cụ thể cho các yêu cầu API do kubelet thực hiện. Nó cho phép các hoạt động đọc của kubelet đối với dịch vụ (service), điểm cuối (endpoint) hoặc node, cũng như các hoạt động ghi đối với node, pod và event (sự kiện).

  • Kiểm soát truy cập dựa trên thuộc tính (Attribute-Based Access Control – ABAC): Với trình ủy quyền ABAC, Kubernetes cấp quyền truy cập cho các yêu cầu API dựa trên sự kết hợp giữa chính sách (policy) và các thuộc tính (attribute). Trong ví dụ sau, người dùng bob chỉ có thể đọc các Pod trong Namespace lfs158.

{
  "apiVersion": "abac.authorization.kubernetes.io/v1beta1",
  "kind": "Policy",
  "spec": {
    "user": "bob",
    "namespace": "lfs158",
    "resource": "pods",
    "readonly": true
  }
}

Để bật chế độ ABAC, chúng ta khởi động máy chủ API với tùy chọn --authorization-mode=ABAC, đồng thời chỉ định chính sách ủy quyền (authorization policy) bằng cách dùng --authorization-policy-file=PolicyFile.json. Để tìm hiểu thêm, vui lòng tham khảo tài liệu về ủy quyền ABAC.

Webhook

Ở chế độ Webhook, Kubernetes có thể gửi yêu cầu ủy quyền đến các dịch vụ bên thứ ba. Các dịch vụ này sẽ trả về “true” nếu ủy quyền thành công và “false” nếu thất bại. Để kích hoạt trình ủy quyền Webhook, chúng ta cần khởi động máy chủ API với tùy chọn --authorization-webhook-config-file=SOME_FILENAME, trong đó SOME_FILENAME là cấu hình của dịch vụ ủy quyền từ xa. Muốn biết thêm chi tiết, vui lòng tham khảo tài liệu về Webhook mode documentation.

Authorization (2)

Kiểm soát truy cập dựa trên vai trò (Role-Based Access Control – RBAC)

  • Tổng quan: Với RBAC, nói chung, chúng ta điều chỉnh quyền truy cập vào các tài nguyên dựa trên Vai trò (Role) của từng người dùng. Trong Kubernetes, nhiều Role có thể được gắn với các đối tượng (subject) như người dùng, tài khoản dịch vụ (service account), v.v.

  • Chi tiết: Khi tạo Role, chúng ta hạn chế quyền truy cập tài nguyên theo các thao tác cụ thể như tạo (create), lấy (get), cập nhật (update), sửa một phần (patch) v.v.. Các thao tác này được gọi là các “động từ” (verb). Trong RBAC, ta có thể tạo hai loại Role:

    • Role: Cấp quyền truy cập đến các tài nguyên trong một không gian tên (Namespace) cụ thể.

    • ClusterRole: Cấp các quyền giống như Role, nhưng phạm vi của nó là toàn cụm (cluster-wide).

Trong khóa học này, chúng ta sẽ tập trung vào loại thứ nhất, Role. Dưới đây là một ví dụ:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: lfs158
  name: pod-reader
rules:
- apiGroups: [""] # "" indicates the core API group
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

Manifest này định nghĩa một role có tên “pod-reader”, role này cho phép người được gán chỉ có quyền đọc Pod trong Namespace “lfs158”. Sau khi role được tạo, chúng ta có thể gắn nó cho người dùng bằng một đối tượng RoleBinding. Có hai loại RoleBinding:

  • RoleBinding: Cho phép gắn role với người dùng ở cùng một Namespace. Chúng ta cũng có thể dùng RoleBinding để gắn một ClusterRole – khi đó người dùng được gán RoleBinding sẽ có các quyền do ClusterRole đó quy định trong phạm vi Namespace của RoleBinding.

  • ClusterRoleBinding: Cấp quyền truy cập đến các tài nguyên ở cấp độ cụm (cluster) và cho tất cả các Namespace.

Trong khóa học này, chúng ta sẽ tập trung vào loại thứ nhất, RoleBinding. Dưới đây là một ví dụ:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-read-access
  namespace: lfs158
subjects:
- kind: User
  name: bob
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

Manifest này tạo ra sự ràng buộc (bind) giữa Role “pod-reader” với người dùng “bob”, hạn chế người dùng chỉ có thể đọc các Pod trong Namespace “lfs158”.

Để bật chế độ RBAC, chúng ta khởi động máy chủ API với tùy chọn --authorization-mode=RBAC, cho phép cấu hình các chính sách một cách linh hoạt. Để biết thêm chi tiết, vui lòng tham khảo tài liệu RBAC mode.

Authentication and Authorization Demo Guide

Hướng dẫn bài tập này sử dụng môi trường cụ thể như sau. Môi trường này mặc định sử dụng chứng chỉ và khóa từ /var/lib/minikube/certs/, và chế độ ủy quyền RBAC:

  • Minikube v1.28.0

  • Kubernetes v1.25.3

  • containerd 1.6.8

Lưu ý: Hướng dẫn này được chuẩn bị cho video hướng dẫn ở cuối chương.

Khởi động Minikube:

minikube start

Xem nội dung của tệp cấu hình client kubectl, chú ý đến context duy nhất là “minikube” và người dùng duy nhất là “minikube”, được tạo ra mặc định:

$ kubectl config view

apiVersion: v1
clusters:
- cluster:
    certificate-authority: /home/student/.minikube/ca.crt
    extensions:
    - extension:
        last-update: Mon, 21 Nov 2022 08:14:56 CDT
        provider: minikube.sigs.k8s.io
        version: v1.25.3
      name: cluster_info
    server: https://192.168.99.100:8443
  name: minikube
contexts:
- context:
    cluster: minikube
    extensions:
    - extension:
        last-update: Mon, 21 Nov 2022 08:14:56 CDT
        provider: minikube.sigs.k8s.io
        version: v1.25.3
      name: context_info
    namespace: default
    user: minikube
  name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
  user:
    client-certificate: /home/student/.minikube/profiles/minikube/client.crt
    client-key: /home/student/.minikube/profiles/minikube/client.key

Tạo lfs158 namespace:

$ kubectl create namespace lfs158

namespace/lfs158 created

Create the rbac directory and cd into it:

$ mkdir rbac
$ cd rbac/

Tạo một người dùng mới tên “bob” trên máy làm việc (workstation) của bạn, và đồng thời thiết lập mật khẩu cho ‘bob’ (hệ thống sẽ yêu cầu bạn nhập mật khẩu hai lần):

~/rbac$ sudo useradd -s /bin/bash bob
~/rbac$ sudo passwd bob

Tạo một khóa riêng (private key) cho người dùng mới “bob” sử dụng công cụ openssl, sau đó tạo một yêu cầu cấp chứng chỉ (certificate signing request) cho “bob” cũng với công cụ openssl:

~/rbac$ openssl genrsa -out bob.key 2048

Generating RSA private key, 2048 bit long modulus (2 primes)
.................................................+++++
.........................+++++
e is 65537 (0x010001)
~/rbac$ openssl req -new -key bob.key \
-out bob.csr -subj "/CN=bob/O=learner"

Tạo một manifest định nghĩa YAML cho đối tượng yêu cầu cấp chứng chỉ (certificate signing request) và lưu với giá trị trống cho trường request:

Đối tượng yêu cầu cấp chứng chỉ (Certificate Signing Request): Đối tượng này đại diện cho một yêu cầu của người dùng muốn được cấp chứng chỉ để xác thực danh tính trong cụm Kubernetes.

Trường request: Chứa mã base64 đại diện cho nội dung thực sự của yêu cầu cấp chứng chỉ (CSR) được tạo ở bước trước đó.

~/rbac$ vim signing-request.yaml

apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: bob-csr
spec:
  groups:
  - system:authenticated
  request: <assign encoded value from next cat command>
  signerName: kubernetes.io/kube-apiserver-client
  usages:
  - digital signature
  - key encipherment
  - client auth

Xem nội dung của chứng chỉ (certificate), mã hóa nó bằng base64, và gán kết quả vào trường request trong tệp signing-request.yaml:

~/rbac$ cat bob.csr | base64 | tr -d '\n','%'

LS0tLS1CRUd...1QtLS0tLQo=

~/rbac$ vim signing-request.yaml

apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: bob-csr
spec:
  groups:
  - system:authenticated
  request: LS0tLS1CRUd...1QtLS0tLQo=
  signerName: kubernetes.io/kube-apiserver-client
  usages:
  - digital signature
  - key encipherment
  - client auth

Tạo đối tượng yêu cầu cấp chứng chỉ (Certificate Signing Request – CSR), sau đó liệt kê các đối tượng CSR. Kết quả sẽ hiển thị trạng thái “pending” (đang chờ):

~/rbac$ kubectl create -f signing-request.yaml

certificatesigningrequest.certificates.k8s.io/bob-csr created

~/rbac$ kubectl get csr

NAME      AGE   SIGNERNAME                            REQUESTOR       CONDITION
bob-csr   12s   kubernetes.io/kube-apiserver-client   minikube-user   Pending

Phê duyệt đối tượng yêu cầu cấp chứng chỉ (Certificate Signing Request – CSR), sau đó liệt kê lại các đối tượng CSR. Kết quả sẽ hiển thị trạng thái “approved” (đã được phê duyệt) và “issued” (đã cấp phát):

~/rbac$ kubectl certificate approve bob-csr

certificatesigningrequest.certificates.k8s.io/bob-csr approved

~/rbac$ kubectl get csr

NAME      AGE   SIGNERNAME                            REQUESTOR       CONDITION
bob-csr   57s   kubernetes.io/kube-apiserver-client   minikube-user   Approved,Issued

Tách chứng chỉ đã được phê duyệt từ yêu cầu cấp chứng chỉ (Certificate Signing Request), giải mã nó với base64, và lưu lại thành một tệp chứng chỉ. Sau đó, xem nội dung của tệp chứng chỉ vừa được tạo:

~/rbac$ kubectl get csr bob-csr \
  -o jsonpath='{.status.certificate}' | \
  base64 -d > bob.crt

~/rbac$ cat bob.crt

-----BEGIN CERTIFICATE-----
MIIDGzCCA...
...
...NOZRRZBVunTjK7A==
-----END CERTIFICATE-----

Cấu hình tệp cấu hình (configuration manifest) của kubectl client với thông tin xác thực của người dùng ‘bob’ bằng cách gán khóa (key) và chứng chỉ (certificate) của anh ấy:

~/rbac$ kubectl config set-credentials bob \
  --client-certificate=bob.crt --client-key=bob.key

User "bob" set.

Tạo một mục ngữ cảnh (context) mới trong tệp cấu hình (configuration manifest) của kubectl client dành cho người dùng ‘bob’, liên kết với namespace ‘lfs158’ trong cụm ‘minikube’:

~/rbac$ kubectl config set-context bob-context \
  --cluster=minikube --namespace=lfs158 --user=bob

Context "bob-context" created.

Xem lại nội dung của tệp cấu hình (configuration manifest) kubectl client, chú ý đến mục context mới bob-context và mục người dùng mới bob (đầu ra dưới đây đã được lược bớt để dễ đọc):

~/rbac$ kubectl config view

apiVersion: v1
clusters:
- cluster:
    certificate-authority: /home/student/.minikube/ca.crt
    ...
    server: https://192.168.99.100:8443
  name: minikube
contexts:
- context:
    cluster: minikube
    ...
    user: minikube
  name: minikube
- context:
    cluster: minikube
    namespace: lfs158
    user: bob
  name: bob-context
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
  user:
    client-certificate: /home/student/.minikube/profiles/minikube/client.crt
    client-key: /home/student/.minikube/profiles/minikube/client.key
- name: bob
  user:
    client-certificate: /home/student/rbac/bob.crt
    client-key: /home/student/rbac/bob.key

Khi đang ở ngữ cảnh (context) mặc định minikube, tạo một deployment mới trong namespace ‘lfs158’:

~/rbac$ kubectl -n lfs158 create deployment nginx --image=nginx:alpine

deployment.apps/nginx created

Từ ngữ cảnh mới ‘bob-context’, thử liệt kê các pod. Nỗ lực này sẽ thất bại bởi vì người dùng ‘bob’ chưa có quyền (permissions) được cấu hình cho ngữ cảnh ‘bob-context’:

~/rbac$ kubectl --context=bob-context get pods

Error from server (Forbidden): pods is forbidden: User "bob" cannot list resource "pods" in API group "" in the namespace "lfs158"

Các bước sau sẽ gán một tập hợp quyền hạn chế cho người dùng ‘bob’ trong ngữ cảnh ‘bob-context’.

Tạo manifest YAML cho đối tượng Role ‘pod-reader’: Manifest này cho phép các hành động/động từ (actions/verbs) ‘get’, ‘watch’, ‘list’ trong namespace ‘lfs158’ đối với tài nguyên ‘pod’.

Tạo đối tượng role và liệt kê nó từ context minikube mặc định, trong namespace ‘lfs158’

~/rbac$ vim role.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: lfs158
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

~/rbac$ kubectl create -f role.yaml

role.rbac.authorization.k8s.io/pod-reader created

~/rbac$ kubectl -n lfs158 get roles

NAME         CREATED AT
pod-reader   2022-04-11T03:47:45Z

Tạo một manifest định nghĩa YAML cho đối tượng RoleBinding (ràng buộc role), để gán các quyền hạn từ Role ‘pod-reader’ cho người dùng ‘bob’. Sau đó, tạo đối tượng RoleBinding và liệt kê đối tượng này từ context ‘minikube’ mặc định, trong namespace ‘lfs158’:

~/rbac$ vim rolebinding.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-read-access
  namespace: lfs158
subjects:
- kind: User
  name: bob
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

~/rbac$ kubectl create -f rolebinding.yaml 

rolebinding.rbac.authorization.k8s.io/pod-read-access created

~/rbac$ kubectl -n lfs158 get rolebindings

NAME              ROLE              AGE
pod-read-access   Role/pod-reader   28s

Giờ đây, sau khi đã gán quyền cho ‘bob’, chúng ta có thể liệt kê thành công các pod từ ngữ cảnh mới ‘bob-context’.

~/rbac$ kubectl --context=bob-context get pods

NAME                     READY   STATUS    RESTARTS   AGE
nginx-565785f75c-kl25r   1/1     Running   0          7m41s

Admission Control

Admission controllers (Bộ điều khiển truy cập) được sử dụng để thiết lập các chính sách kiểm soát truy cập chi tiết granular, bao gồm cho phép các container có đặc quyền (privileged container), kiểm tra hạn ngạch tài nguyên (resource quota), v.v. Các chính sách này được áp dụng bằng các bộ điều khiển truy cập khác nhau, chẳng hạn như ResourceQuota, DefaultStorageClass, AlwaysPullImages, v.v. Chúng chỉ có hiệu lực sau khi các yêu cầu API đã được xác thực (authenticated) và ủy quyền (authorized).

Để sử dụng admission control, chúng ta phải khởi động máy chủ Kubernetes API với tùy chọn --enable-admission-plugins , trong đó giá trị là một danh sách tên các bộ điều khiển phân cách bằng dấu phẩy theo thứ tự ưu tiên:

--enable-admission-plugins=NamespaceLifecycle,ResourceQuota,PodSecurity,DefaultStorageClass

Kubernetes đã kích hoạt một số admission controller theo mặc định. Để biết thêm chi tiết, vui lòng tham khảo list of admission controllers.

Việc kiểm soát truy cập Kubernetes cũng có thể được thực hiện thông qua các plugin tùy chỉnh, tạo ra phương pháp kiểm soát truy cập động (dynamic admission control). Các plugin này được phát triển như phần mở rộng và chạy dưới dạng admission webhook.

Chapter 10. Services

Mặc dù kiến trúc điều khiển vi dịch vụ (microservices) hướng tới mục tiêu tách rời các thành phần của một ứng dụng, các vi dịch vụ vẫn cần có cơ chế nhóm hợp lý để quản lý, hoặc để cân bằng tải (load balance) lưu lượng đến các vi dịch vụ thành viên.

Trong chương này, chúng ta sẽ tìm hiểu về đối tượng Service, được sử dụng để trừu tượng hóa giao tiếp giữa các vi dịch vụ bên trong cụm Kubernetes, hoặc với thế giới bên ngoài. Một Service cung cấp một địa chỉ DNS duy nhất cho một ứng dụng chạy trong container không lưu trạng (stateless) được quản lý bởi cụm, bất kể số lượng bản sao (replica). Service đạt được điều này bằng cách cung cấp điểm truy cập cân bằng tải chung tới tập hợp các pod được nhóm hợp lý và quản lý bởi một bộ điều khiển như Deployment, ReplicaSet, hay DaemonSet.

Chúng ta cũng sẽ tìm hiểu về tiến trình kube-proxy chạy trên mỗi control plane và worker node – thành phần giúp triển khai cấu hình của các service và cung cấp quyền truy cập. Bên cạnh đó, ta sẽ thảo luận về cơ chế khám phá dịch vụ (service discovery) và các loại dịch vụ (service type), quyết định phạm vi truy cập của từng service.

Đến cuối chương này, bạn sẽ có thể:

  • Giải thích các lợi ích của việc nhóm các Pod lại một cách hợp lý bằng Service để truy cập ứng dụng.

  • Mô tả vai trò của tiến trình kube-proxy chạy trên mỗi node.

  • Khám phá các phương thức khám phá dịch vụ (service discovery) trong Kubernetes.

  • Thảo luận về các loại dịch vụ (Service type) khác nhau.

Connecting Users or Applications to Pods

Để truy cập một ứng dụng, người dùng hoặc một ứng dụng khác cần kết nối đến các Pod. Do bản chất của Pod là tạm thời (ephemeral), các tài nguyên như địa chỉ IP được gán cho chúng không cố định. Pod có thể bị kết thúc đột ngột hoặc được lên lịch lại (reschedule) dựa trên nhu cầu hiện tại.

Ví dụ, xét trường hợp một người quản trị đang quản lý một tập hợp các Pod và người dùng/khách hàng (user/client) đang truy cập trực tiếp vào các Pod bằng địa chỉ IP riêng của từng Pod.

Bỗng nhiên, một trong các Pod mà người dùng/khách hàng (user/client) đang truy cập bị hủy (terminated), và bộ điều khiển (controller) tạo ra một Pod mới thay thế. Pod mới này sẽ có địa chỉ IP mới và người dùng/khách hàng không biết được ngay lập tức.

Để giải quyết tình trạng này, Kubernetes cung cấp một cơ chế trừu tượng hóa ở mức cao hơn gọi là Service (Dịch vụ). Service giúp nhóm hợp lý các Pod lại với nhau và định ra một chính sách để truy cập chúng. Việc nhóm này được thực hiện thông qua các Nhãn (Label) và Bộ chọn (Selector).

Services

Nhãn (Label) và Bộ chọn (Selector) sử dụng định dạng cặp key-value (cặp khóa-giá trị). Trong biểu diễn đồ họa sau, app là khóa (key) của Nhãn, trong khi frontenddb là các giá trị (value) của Nhãn cho các Pod khác nhau.

Sử dụng các bộ chọn app==frontendapp==db, chúng ta nhóm các Pod thành hai tập hợp logic: một tập hợp chứa 3 Pod và tập hợp còn lại chứa một Pod duy nhất.

Ta đặt tên cho các nhóm logic này, và những nhóm này được gọi là Service (Dịch vụ). Tên của Service cũng được đăng ký với dịch vụ DNS nội bộ của cụm Kubernetes. Trong ví dụ của chúng ta, ta tạo hai Service có tên frontend-svcdb-svc, lần lượt có bộ chọn là app==frontendapp==db.

Service có thể hoạt động trên các đối tượng Kubernetes khác nhau như Pod đơn lẻ, ReplicaSet, Deployment, DaemonSet, và StatefulSet. Khi sử dụng Service với các Pod được quản lý bởi một bộ điều khiển (operator), bộ chọn của Service có thể sử dụng chung các nhãn (label) với bộ điều khiển đó.

Service Object Example

apiVersion: v1
kind: Service
metadata:
  name: frontend-svc
spec:
  selector:
    app: frontend
  ports:
  - protocol: TCP
    port: 80
    targetPort: 5000

Trong ví dụ này, chúng ta đang tạo ra một Service ‘frontend-svc’ bằng cách chọn tất cả các Pod có nhãn với khóa (key) app được đặt thành giá trị (value) frontend. Theo mặc định, mỗi Service được cấp một địa chỉ IP chỉ có thể định tuyến được bên trong cụm, được gọi là ClusterIP. Trong ví dụ của chúng ta, 172.17.0.4172.17.0.5 lần lượt là các ClusterIP được gán cho Service ‘frontend-svc’ và ‘db-svc’.

Giờ đây, người dùng/khách hàng (user/client) kết nối đến một Service thông qua ClusterIP của nó, và Service sẽ chuyển tiếp lưu lượng đến một trong các Pod được liên kết. Service mặc định cung cấp cơ chế cân bằng tải (load balancing) khi chọn Pod cho việc chuyển tiếp.

Trong khi Service chuyển tiếp lưu lượng đến các Pod, chúng ta có thể chọn targetPort trên Pod đích để nhận lưu lượng. Ví dụ, Service frontend-svc nhận các yêu cầu từ người dùng/khách hàng trên port: 80 và sau đó chuyển tiếp các yêu cầu này đến một trong các Pod được liên kết trên targetPort: 5000. Nếu targetPort không được định nghĩa rõ ràng, lưu lượng sẽ được chuyển tiếp đến các Pod trên cùng port mà Service đang lắng nghe.

Cần lưu ý quan trọng: giá trị của targetPort trong ví dụ này là 5000 phải khớp với giá trị của thuộc tính containerPort trong phần spec của Pod.

Một bộ logic gồm địa chỉ IP của Pod, cùng với targetPort được gọi là một endpoint của Service. Trong ví dụ này, Service frontend-svc có 3 endpoint: 10.0.1.3:5000, 10.0.1.4:500010.0.1.5:5000. Các endpoint được tạo và quản lý tự động bởi Service, không phải bởi quản trị viên cụm Kubernetes.

kube-proxy

Mỗi node (máy chủ) trong cụm chạy một tiến trình (daemon) có tên kube-proxy. Đây là một tác nhân (agent) trên node, có nhiệm vụ theo dõi API server trên node chính (master node) để phát hiện các thao tác thêm, cập nhật, hay xóa Service và endpoint. kube-proxy chịu trách nhiệm hiện thực hóa cấu hình của Service thay mặt cho người quản trị hoặc người phát triển, cho phép định tuyến lưu lượng đến ứng dụng đang chạy trong các Pod.

Ví dụ, với mỗi Service mới, trên mỗi node, kube-proxy cấu hình các luật iptables để bắt lưu lượng đến ClusterIP của Service và chuyển tiếp nó đến một trong các endpoint của Service. Do đó, bất kỳ node nào cũng có thể nhận được lưu lượng từ bên ngoài và định tuyến nội bộ trong cụm dựa trên các luật iptables. Khi Service bị xóa, kube-proxy cũng sẽ loại bỏ các luật iptables tương ứng trên tất cả các node.

Giải thích thêm:

  • iptables: Phần mềm tường lửa linh hoạt trên các hệ điều hành Linux, cho phép kiểm soát lưu lượng mạng dựa trên các luật (rule).

  • Node chính (Master node): Các node trong cụm Kubernetes quản lý việc điều phối cụm, API server là một thành phần phần mềm quan trọng thường được triển khai trên master node.

Traffic Policies

Tác nhân (agent) kube-proxy trên mỗi node, cùng với iptables, hiện thực hóa cơ chế cân bằng tải của Service khi lưu lượng được định tuyến đến các Endpoint của ứng dụng. Do những đặc điểm hạn chế của iptables, kiểu cân bằng tải này mặc định sẽ có tính ngẫu nhiên. Nghĩa là, Endpoint Pod nhận yêu cầu được chuyển tiếp bởi Service sẽ được chọn ngẫu nhiên từ nhiều bản sao (replica). Cơ chế này không đảm bảo rằng Pod nhận yêu cầu được chọn là Pod gần nhất, hoặc thậm chí nằm trên cùng node với nguồn yêu cầu (requester), vì vậy chưa phải là giải pháp tối ưu nhất. Vì đây là cơ chế cân bằng tải được iptables hỗ trợ, nếu chúng ta muốn có kết quả tốt hơn, chúng ta cần sử dụng các traffic policy (chính sách lưu lượng).

Traffic policy cho phép người dùng cung cấp thông tin ngữ cảnh cho kube-proxy khi định tuyến lưu lượng. Hai tùy chọn chính là Cluster và Local:

  • Cluster: Cho phép kube-proxy bao gồm tất cả Endpoint sẵn sàng của Service trong quá trình cân bằng tải.

  • Local: Giới hạn quá trình cân bằng tải, chỉ sử dụng những Endpoint của Service nằm trên cùng node với Pod gửi yêu cầu (requester Pod). Mặc dù đây có vẻ là phương án lý tưởng, nó có một điểm yếu – nếu Service không có Endpoint sẵn sàng trên node nơi requester Pod đang chạy, Service sẽ không định tuyến yêu cầu đến Endpoint trên các node khác.

Cả hai tùy chọn Cluster và Local đều khả dụng cho các yêu cầu được tạo từ bên trong cụm, hoặc từ ứng dụng/client bên ngoài cụm.

Service Discovery

Vì Service là phương thức liên lạc chính giữa các ứng dụng trong container được Kubernetes quản lý, việc có thể khám phá (discover) chúng một cách linh hoạt là rất quan trọng. Kubernetes hỗ trợ hai phương thức khám phá Service:

Biến môi trường (Environment Variables):

  • Ngay khi một Pod được khởi động trên bất kỳ node worker (node làm việc) nào, tiến trình kubelet chạy trên node đó sẽ thêm một tập hợp các biến môi trường vào trong Pod, mỗi biến đại diện cho một Service đang hoạt động.

  • Ví dụ: Nếu có một Service đang hoạt động tên là redis-master, nó cung cấp dịch vụ ở cổng 6379 và ClusterIP của nó là ‘172.17.0.6`, thì mọi Pod mới được tạo trên cụm sẽ thấy các biến môi trường sau:

REDIS_MASTER_SERVICE_HOST=172.17.0.6
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://172.17.0.6:6379
REDIS_MASTER_PORT_6379_TCP=tcp://172.17.0.6:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=172.17.0.6

Với giải pháp này, chúng ta cần cẩn trọng về thứ tự khởi tạo các Service, bởi vì các Pod sẽ không có các biến môi trường đại diện cho những Service được tạo ra sau khi Pod đã khởi động.

DNS

  • DNS add-on trong Kubernetes: Kubernetes cung cấp một tiện ích bổ sung (add-on) tạo ra các bản ghi (record) DNS cho mỗi Service. Định dạng của những bản ghi này là: my-svc.my-namespace.svc.cluster.local.

  • Tìm kiếm Service trong cùng Namespace: Service trong cùng một Namespace có thể được tìm thấy đơn giản chỉ bằng tên của chúng. Ví dụ, nếu chúng ta thêm Service redis-master vào Namespace my-ns, tất cả Pod trong cùng Namespace my-ns có thể tìm kiếm Service này chỉ bằng tên redis-master.

  • Tìm kiếm Service từ Namespace khác: Các Pod từ Namespace khác, ví dụ test-ns, có thể tìm kiếm cùng một Service bằng cách thêm Namespace của Service vào như một hậu tố, ví dụ: redis-master.my-ns. Hoặc có thể dùng tên đầy đủ (FQDN) của Service: redis-master.my-ns.svc.cluster.local.

Đây là giải pháp phổ biến nhất và được khuyến nghị sử dụng. Ví dụ, trong hình ảnh ở phần trước, chúng ta thấy một dịch vụ DNS nội bộ được cấu hình, ánh xạ các Service frontend-svcdb-svc tới các địa chỉ IP 172.17.0.4172.17.0.5 tương ứng.

ServiceType

Khi định nghĩa một Service, chúng ta cũng có thể chọn phạm vi truy cập của nó. Chúng ta có thể quyết định liệu Service:

  • Chỉ truy cập được từ bên trong cụm (cluster).

  • Có thể truy cập từ bên trong cụm và từ thế giới bên ngoài.

  • Liên kết (map) đến một thực thể nằm bên trong hoặc bên ngoài cụm.

Phạm vi truy cập được quyết định bởi thuộc tính ServiceType , được định nghĩa lúc tạo Service.

Giải thích thêm:

  • Phạm vi truy cập (Access scope): Kiểm soát những thành phần nào (bên trong cụm hay từ internet) có thể kết nối tới Service. Điều này quan trọng cho cả mục đích bảo mật và định tuyến lưu lượng đến ứng dụng của bạn.

ServiceType: ClusterIP and NodePort

ClusterIP

  • Loại Service (ServiceType) mặc định. Service được cấp một địa chỉ IP ảo, được gọi là ClusterIP.

  • Địa chỉ IP ảo (ClusterIP): Chỉ truy cập được từ bên trong cụm, và dùng để giao tiếp với Service.

  • Phạm vi truy cập: ClusterIP giới hạn khả năng kết nối đến Service chỉ ở các tiến trình (Pod, ứng dụng) đã ở bên trong cụm Kubernetes.

NodePort

  • Ngoài ClusterIP, Service loại NodePort còn được ánh xạ sang một cổng (port) có số hiệu cao (trong khoảng 30000-32767) trên tất cả các worker node (node làm việc). Cách ánh xạ này được chọn ngẫu nhiên.

  • Ví dụ: Nếu NodePort được ánh xạ là 32233 tới Service frontend-svc, và ta kết nối đến bất kỳ worker node nào tại cổng 32233, node đó sẽ chuyển tiếp toàn bộ lưu lượng đến ClusterIP 172.17.0.4 đã được gắn cho Service.

  • Tùy chọn cổng cụ thể: Nếu muốn dùng một cổng số hiệu cao cụ thể, ta có thể chỉ định khi tạo Service.

Loại Service NodePort hữu ích khi chúng ta muốn cung cấp quyền truy cập Service từ thế giới bên ngoài (internet). Người dùng cuối kết nối đến bất kỳ worker node nào trên cổng có số hiệu cao (nodePort) đã chỉ định, và node đó sẽ chuyển tiếp (proxy) yêu cầu vào bên trong cụm đến địa chỉ ClusterIP của Service. Từ đó, yêu cầu sẽ được chuyển tới ứng dụng đang chạy bên trong cụm. Đừng quên rằng Service có cơ chế cân bằng tải – nó sẽ chỉ chuyển tiếp yêu cầu đến một trong số các Pod đang chạy ứng dụng mong muốn.

Để quản lý truy cập đến nhiều Service ứng dụng từ bên ngoài, quản trị viên có thể cấu hình một reverse proxy – được gọi là Ingress, và định nghĩa các luật (rule) nhắm đến các Service cụ thể trong cụm.

Giải thích thêm:

  • Reverse proxy: Phần mềm đóng vai trò trung gian, nhận yêu cầu từ bên ngoài và chuyển tiếp chúng đến các server (hoặc trong trường hợp này là các Service) ở bên trong mạng nội bộ.

  • Ingress trong Kubernetes: Là một dạng đặc biệt của reverse proxy, tích hợp với Kubernetes để triển khai các cơ chế định tuyến phức tạp, điều hướng lưu lượng đến các Service khác nhau dựa trên các thuộc tính của yêu cầu (đường dẫn URL, header, v.v).

ServiceType: LoadBalancer

Với loại Service LoadBalancer:

  • NodePort và ClusterIP được tạo tự động, và bộ cân bằng tải (load balancer) bên ngoài sẽ định tuyến lưu lượng đến chúng.

  • Service được cung cấp ra bên ngoài cụm trên một cổng cố định trên mỗi worker node.

  • Service được cung cấp ra ngoài cụm bằng việc tận dụng tính năng load balancer sẵn có của nhà cung cấp dịch vụ đám mây (cloud provider).

Service loại LoadBalancer sẽ chỉ hoạt động nếu hạ tầng (infrastructure) hỗ trợ việc tự động tạo các bộ cân bằng tải (Load Balancer) và có hỗ trợ tương ứng trong Kubernetes, ví dụ như Google Cloud Platform hay AWS.

Nếu không có tính năng như vậy, trường địa chỉ IP của LoadBalancer sẽ không được thiết lập và trạng thái sẽ là Pending (Đang chờ). Tuy nhiên, Service vẫn có thể hoạt động như một NodePort Service thông thường.

ServiceType: ExternalIP

Một Service có thể được ánh xạ sang một địa chỉ ExternalIP (IP ngoài) nếu có khả năng định tuyến đến một hoặc nhiều worker node (node làm việc). Lưu lượng đi vào cụm với ExternalIP (như địa chỉ đích) trên cổng của Service, sẽ được định tuyến đến một trong các endpoint của Service.

Loại Service này yêu cầu một nhà cung cấp dịch vụ đám mây (cloud provider) bên ngoài như Google Cloud Platform hay AWS, và một Load Balancer được cấu hình trên hạ tầng của nhà cung cấp đó.

Giải thích thêm:

  • ExternalIP: Cung cấp một địa chỉ IP cố định bên ngoài cụm Kubernetes. Dịch vụ Load Balancer do nhà cung cấp đám mây quản lý sẽ kết nối và định tuyến lưu lượng đến địa chỉ này.

  • Điểm truy cập duy nhất: ExternalIP giúp đơn giản hóa việc kết nối cho client bên ngoài – họ chỉ cần một địa chỉ duy nhất để truy cập ứng dụng, và việc chọn endpoint, cân bằng tải phía sau được xử lý ngầm bởi Kubernetes và Load Balancer.

Xin lưu ý rằng các địa chỉ ExternalIP không được quản lý trực tiếp bởi Kubernetes. Quản trị viên của cụm cần phải cấu hình định tuyến để ánh xạ địa chỉ ExternalIP đến một trong số các node.

ServiceType: ExternalName

ExternalName là một loại ServiceType đặc biệt, không có Bộ chọn (Selector) và không định nghĩa endpoint nào. Khi được truy cập từ bên trong cụm, nó trả về một bản ghi CNAME của một Service được cấu hình ở bên ngoài cụm.

Công dụng chính của ServiceType này là để cung cấp quyền truy cập đến các Service được cấu hình bên ngoài (ví dụ my-database.example.com) cho các ứng dụng bên trong cụm Kubernetes. Nếu Service ngoài đó nằm trong cùng một Namespace, việc sử dụng tên đơn giản my-database sẽ đủ để làm nó truy cập được từ ứng dụng và Service khác trong cùng Namespace đó.

Multi-Port Services

Một tài nguyên Service có thể cung cấp nhiều cổng (port) cùng một lúc nếu cần. Cấu hình của nó đủ linh hoạt để cho phép định nghĩa nhiều nhóm cổng trong manifest. Đây là một tính năng hữu ích khi cung cấp (expose) các Pod với một container lắng nghe trên nhiều cổng, hoặc khi cung cấp các Pod có nhiều container và mỗi container lắng nghe trên một hoặc nhiều cổng.

Manifest cho một Service đa cổng (multi-port):

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: myapp
  type: NodePort
  ports:
  - name: http
    protocol: TCP
    port: 8080
    targetPort: 80
    nodePort: 31080
  - name: https
    protocol: TCP
    port: 8443
    targetPort: 443
    nodePort: 31443

Tài nguyên Service my-service cung cấp truy cập đến các Pod có nhãn app==myapp. Các Pod này có khả năng chứa một container đang lắng nghe trên các cổng 80443, như mô tả trong hai phần targetPort. Service sẽ truy cập được bên trong cụm thông qua ClusterIP của nó trên các cổng 80808443 (mô tả bởi phần port), và cũng sẽ truy cập được từ các yêu cầu từ bên ngoài cụm trên hai cổng nodePort là 3108031443.

Khi manifest mô tả nhiều cổng, các cổng cần được đặt tên nhằm đảm bảo rõ ràng, như trong ví dụ với hai phần name có giá trị lần lượt là httphttps. Service này được cấu hình để bắt lưu lượng trên cổng 8080 và 8443 từ bên trong cụm, hoặc trên cổng 31080 và 31443 từ bên ngoài, và chuyển tiếp lưu lượng đó đến cổng 80 và 443 của các Pod đang chạy container.

Chapter 11. Deploying a Stand-Alone Application

Trong chương này, chúng ta sẽ học cách triển khai một ứng dụng sử dụng Dashboard (Kubernetes WebUI) và công cụ dòng lệnh (Command Line Interface – CLI). Chúng ta cũng sẽ cung cấp (expose) ứng dụng với một Service loại NodePort, và truy cập nó từ bên ngoài cụm Minikube.

Đến cuối chương này, bạn sẽ có thể:

  • Triển khai một ứng dụng từ Dashboard.

  • Triển khai một ứng dụng từ một file YAML sử dụng kubectl.

  • Cung cấp một Service sử dụng NodePort.

  • Truy cập ứng dụng từ bên ngoài cụm Minikube.

Deploying an Application Using the Dashboard

Hãy cùng tìm hiểu cách triển khai máy chủ web nginx sử dụng image container nginx trên Docker.

Khởi động Minikube và kiểm tra trạng thái:

Khởi chạy Minikube: Dùng câu lệnh sau:

minikube start

Kiểm tra trạng thái Minikube: Chạy câu lệnh:

minikube status

Đầu ra mong đợi sẽ có những dòng như minikube: Running, kubelet: Running, v.v…:

minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

Khởi động Dashboard của Minikube:

Để truy cập giao diện web của Kubernetes, chúng ta cần chạy lệnh sau:

minikube dashboard

Lệnh này sẽ tự động mở trang Dashboard của Kubernetes trên một tab trình duyệt. Đây là công cụ giúp quản lý các ứng dụng trong container bằng giao diện đồ họa. Mặc định, Dashboard sẽ kết nối đến Namespace có tên default. Vì thế các thao tác trong chương này sẽ được thực hiện trên Namespace này.

LƯU Ý: Nếu trình duyệt không tự động mở tab mới và hiển thị Dashboard như mong đợi, hãy kiểm tra đầu ra (output) trong terminal của bạn. Terminal có thể hiển thị một liên kết (link) đến Dashboard (kèm theo một số thông báo lỗi). Hãy sao chép (copy) liên kết đó và dán (paste) vào một tab trình duyệt mới. Tùy vào tính năng của terminal, bạn có thể click trực tiếp hoặc click chuột phải vào liên kết để mở trong trình duyệt.

Ví dụ về liên kết:

http://127.0.0.1:40235/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/

Điểm khác biệt có thể có: Số hiệu cổng (PORT) – trong ví dụ này là 40235, con số này có thể khác trong trường hợp của bạn.

Sau khi đăng xuất/đăng nhập lại hoặc khởi động lại máy, minikube dashboard có thể sẽ hoạt động trở lại bình thường (mở Dashboard trong tab trình duyệt mới).

Triển khai máy chủ web sử dụng image nginx. Trong giao diện Dashboard, click vào dấu “+” ở góc trên cùng bên phải. Hành động này sẽ mở giao diện tạo tài nguyên mới như hình dưới đây:

Từ giao diện này, chúng ta có thể tạo một ứng dụng sử dụng định nghĩa hợp lệ bằng YAML/JSON, từ một file, hoặc tạo thủ công bằng tab “Create from form”. Hãy click vào tab “Create from form” và cung cấp các chi tiết sau đây:

  • Tên ứng dụng (application name): web-dash

  • Image Docker được sử dụng: nginx

  • Số lượng bản sao (replica count, hoặc số Pod): 1

  • Dịch vụ (Service): External (Loại dịch vụ ở bên ngoài), Cổng (Port) 8080, Cổng đích (Target Port) 80, Giao thức (Protocol) TCP.

Absolutely! Here’s the translation of your text explaining how to view the deployment details, along with additional clarifications:

Nếu click vào “Show Advanced Options”, chúng ta có thể chỉ định thêm các tùy chọn như Nhãn (Label), Namespace, v.v. Theo mặc định, nhãn "app" được đặt trùng với tên ứng dụng. Trong ví dụ của chúng ta, nhãn k8s-app: web-dash được gắn cho mọi đối tượng được tạo bởi Deployment này: cả các Pod và Service (khi được cung cấp).

Bằng việc click nút “Deploy”, chúng ta khởi tạo quá trình triển khai (deployment). Như dự kiến, Deployment web-dash sẽ tạo ra một ReplicaSet (có tên web-dash-74d8bd488f), và ReplicaSet này sẽ tạo một Pod (web-dash-74d8bd488f-dwbzz).

Lưu ý: Thêm URL đầy đủ cho “Container Image” (ví dụ docker.io/library/nginx) nếu bạn gặp lỗi khi chỉ dùng tên image đơn giản (nginx ).

Sau khi tạo Deployment web-dash, chúng ta có thể sử dụng thanh điều hướng bên trái trong Dashboard để xem chi tiết về các Deployment, ReplicaSet, và Pod trong Namespace default. Các tài nguyên này tương ứng một-một với những gì chúng ta có thể xem bằng dòng lệnh kubectl.

Liệt kê Deployment: Sử dụng lệnh sau:

kubectl get deployments

Liệt kê ReplicaSet: Sử dụng lệnh:

kubectl get replicasets

Liệt kê Pod: Sử dụng lệnh:

kubectl get pods

Exploring Labels and Selectors

Trước đó, chúng ta đã thấy rằng nhãn (label) và bộ chọn (selector) đóng vai trò quan trọng trong việc nhóm hợp lý một tập con các đối tượng để thực hiện các thao tác. Cùng tìm hiểu chi tiết hơn về chúng.

$ kubectl describe pod web-dash-74d8bd488f-dwbzz

Name:           web-dash-74d8bd488f-dwbzz
Namespace:      default
Priority:       0
Node:           minikube/192.168.99.100
Start Time:     Mon, 4 Apr 2022 13:17:33 -0500
Labels:         k8s-app=web-dash
                pod-template-hash=74d8bd488f
Annotations:    <none>
Status:         Running
IP:             172.17.0.5
Controlled By:  ReplicaSet/web-dash-74d8bd488f
Containers:
  webserver:
    Container ID:   docker://96302d70903fe3b45d5ff3745a706d67d77411c5378f1f293a4bd721896d6420
    Image:          nginx
    Image ID:       docker-pullable://nginx@sha256:8d5341da24ccbdd195a82f2b57968ef5f95bc27b3c3691ace0c7d0acf5612edd
    Port:           <none>
    State:          Running
      Started:      Mon, 4 Apr 2022 13:17:33 -0500
    Ready:          True
    Restart Count:  0
...

Lệnh kubectl describe hiển thị rất nhiều chi tiết của một Pod. Hiện tại, chúng ta sẽ tập trung vào phần Labels, nơi ta có một nhãn được thiết lập với giá trị k8s-app=web-dash.

Liệt kê Pod kèm theo nhãn. Sử dụng tùy chọn -L với lệnh kubectl get pods, chúng ta có thể thêm cột vào đầu ra để liệt kê Pod cùng với khóa (key) và giá trị (value) của nhãn đi kèm. Ví dụ sau đây liệt kê các Pod có nhãn k8s-applabel2:

kubectl get pods -L k8s-app,label2

Giải thích: Mọi Pod đều được liệt kê bởi vì chúng có nhãn k8s-app với giá trị web-dash. Cột LABEL2 không có dữ liệu vì không Pod nào có nhãn label2.

Chọn lọc Pod với nhãn cụ thể. Để sử dụng bộ chọn (selector) với kubectl get pods, dùng tham số -l. Ví dụ sau đây chọn mọi Pod có nhãn k8s-app với giá trị web-dash:

kubectl get pods -l k8s-app=web-dash

Thử dùng bộ chọn khác: Sử dụng bộ chọn k8s-app=webserver:

kubectl get pods -l k8s-app=webserver

Lệnh này sẽ không trả về kết quả nào, vì chúng ta chưa tạo Pod với nhãn phù hợp.

Deploying an Application Using the CLI

Để triển khai ứng dụng sử dụng giao diện dòng lệnh (CLI), hãy bắt đầu bằng việc xóa Deployment đã tạo trước đó.

Xóa Deployment: Dùng lệnh kubectl delete để xóa bất kỳ đối tượng nào. Cụ thể chúng ta sẽ xóa Deployment web-dash được tạo từ Dashboard trước đó:

kubectl delete deployments web-dash

Lưu ý: Xóa Deployment cũng sẽ xóa ReplicaSet và các Pod mà nó đã tạo ra. Bạn có thể xác nhận điều này bằng các lệnh kubectl get replicasetskubectl get pods.

Tạo file manifest YAML cho bộ điều khiển Deployment: Hãy tạo file webserver.yaml với nội dung sau:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webserver
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80

Here’s the translation of your text explaining different ways to create a Deployment using kubectl, along with some additional explanations:

Sử dụng kubectl, chúng ta sẽ tạo Deployment từ file định nghĩa YAML. Dùng tùy chọn -f với lệnh kubectl create, ta có thể truyền một file YAML làm cấu hình (specification) của đối tượng, hoặc truyền một URL trực tiếp đến file cấu hình ở trên mạng. Trong ví dụ dưới đây, chúng ta tạo một Deployment có tên webserver:

$ kubectl create -f webserver.yaml

deployment.apps/webserver created

Lệnh này cũng sẽ tạo ra ReplicaSet và các Pod, như được định nghĩa trong file YAML.

$  kubectl get replicasets

NAME                  DESIRED   CURRENT   READY     AGE
webserver-b477df957   3         3         3         45s

$ kubectl get pods

NAME                        READY     STATUS    RESTARTS   AGE
webserver-b477df957-7lnw6   1/1       Running   0          2m
webserver-b477df957-j69q2   1/1       Running   0          2m
webserver-b477df957-xvdkf   1/1       Running   0          2m

Cách thứ hai (lệnh trực tiếp): Chúng ta cũng có thể triển khai ứng dụng webserver trực tiếp bằng dòng lệnh sau:

kubectl create deployment webserver --image=nginx:alpine --replicas=3 --port=80

Trong chương trước, chúng ta đã tìm hiểu về các loại ServiceType khác nhau. ServiceType quyết định cách thức một Service được truy cập. Với loại NodePort, Kubernetes mở ra một cổng (port) tĩnh trên mọi worker node. Nếu ta kết nối tới cổng đó từ bất kỳ node nào, lưu lượng này sẽ được chuyển tiếp (proxy) đến ClusterIP của Service.

Hãy tạo một Service loại NodePort: Tạo file có tên webserver-svc.yaml với nội dung sau đây:

apiVersion: v1
kind: Service
metadata:
  name: web-service
  labels:
    app: nginx
spec:
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
  selector:
    app: nginx
$ kubectl create -f webserver-svc.yaml

service/web-service created

Một cách trực tiếp hơn để tạo Service là sử dụng lệnh kubectl expose trên một Deployment đã được tạo trước đó (cách này chỉ áp dụng nếu Deployment đã tồn tại).

Sử dụng lệnh kubectl expose:

$ kubectl expose deployment webserver --name=web-service --type=NodePort

service/web-service exposed

List the Services:

$ kubectl get services

NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes    ClusterIP   10.96.0.1      <none>        443/TCP        1d
web-service   NodePort    10.110.47.84   <none>        80:31074/TCP   22s

Service web-service của chúng ta đã được tạo và ClusterIP của nó là 10.110.47.84. Trong phần PORT(S), ta thấy ánh xạ 80:31074, nghĩa là ta đã dành riêng một cổng tĩnh 31074 trên node. Nếu ta kết nối tới node trên cổng đó, yêu cầu của ta sẽ được chuyển tiếp (proxied) tới ClusterIP trên cổng 80.

Thứ tự tạo Deployment và Service: Thực tế, bạn có thể tạo chúng theo thứ tự bất kỳ. Service sẽ tìm và kết nối đến các Pod dựa trên bộ chọn (Selector) của nó.

Xem chi tiết của Service: Sử dụng lệnh kubectl describe để xem thêm thông tin, ví dụ:

$ kubectl describe service web-service

Name:                     web-service
Namespace:                default
Labels:                   app=nginx
Annotations:              <none>
Selector:                 app=nginx
Type:                     NodePort
IP:                       10.110.47.84
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  31074/TCP
Endpoints:                172.17.0.4:80,172.17.0.5:80,172.17.0.6:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

Service web-service sử dụng app=nginx làm bộ chọn (Selector) để nhóm hợp lý ba Pod của chúng ta lại với nhau. Các Pod này được liệt kê trong phần endpoint. Khi một yêu cầu đến Service, nó sẽ được chuyển tới xử lý bởi một trong số các Pod trong danh sách Endpoint.

Accessing an Application

Ứng dụng của chúng ta đang chạy trên Minikube VM (máy ảo). Để truy cập ứng dụng từ máy làm việc, trước tiên chúng ta cần lấy địa chỉ IP của máy ảo Minikube:

minikube ip

Lệnh này sẽ trả về địa chỉ, ví dụ: 192.168.99.100

Bây giờ, mở trình duyệt của bạn và truy cập ứng dụng theo địa chỉ 192.168.99.100 tại cổng 31074:

Chú ý: Thay 192.168.99.100 bằng địa chỉ IP thực tế mà lệnh minikube ip trả về trên máy của bạn.

Chúng ta cũng có thể sử dụng lệnh minikube sau để mở ứng dụng trực tiếp trên trình duyệt:

minikube service web-service

Lệnh này sẽ tự động mở trang chào mừng của Nginx, được cung cấp bởi ứng dụng webserver đang chạy bên trong các Pod. Yêu cầu của bạn có thể được xử lý bởi bất kỳ endpoint nào trong số ba endpoint được nhóm bởi Service, do Service đóng vai trò là bộ cân bằng tải (load balancer).

Nếu lệnh trên không thành công (không mở được trình duyệt), hãy thử chạy lệnh sau – nó sẽ trả về URL của Service. Sao chép (copy) và dán (paste) URL này vào thanh địa chỉ của trình duyệt sẽ mở được trang mong muốn:

minikube service web-service --url

Liveness and Readiness Probes

Khi các ứng dụng chạy trong container được lên lịch triển khai trên các node trong cụm, sẽ có những lúc chúng trở nên không phản hồi hoặc bị chậm khi khởi động. Thực thi các Liveness Probe (thăm dò sự tồn tại) và Readiness Probe (thăm dò sự sẵn sàng) cho phép kubelet kiểm soát sức khỏe của ứng dụng đang chạy trong container của Pod và có thể buộc khởi động lại container nếu ứng dụng không phản hồi.

Khi thiết lập cả hai loại thăm dò, chúng ta nên cho Readiness Probe một khoảng thời gian để thử lại khi thất bại trước khi Liveness Probe được kiểm tra. Nếu các thăm dò này chạy đồng thời, có rủi ro rằng container sẽ không bao giờ đạt trạng thái sẵn sàng, bị kẹt trong vòng lặp tạo-thất bại vô tận.

Liveness

Nếu một container trong Pod đã chạy ổn định một thời gian, nhưng ứng dụng bên trong container này đột nhiên ngừng phản hồi các yêu cầu, container đó không còn hữu ích cho chúng ta. Tình trạng này có thể xảy ra, chẳng hạn, do ứng dụng bị deadlock hoặc áp lực bộ nhớ. Trong trường hợp này, nên khởi động lại container để ứng dụng trở nên khả dụng.

Thay vì khởi động lại thủ công, chúng ta có thể dùng Liveness Probe (Thăm dò sự tồn tại). Liveness Probe kiểm tra sức khỏe của ứng dụng, và nếu kiểm tra thất bại, kubelet sẽ tự động khởi động lại container.

Các cách thiết lập Liveness Probe:

  • Lệnh kiểm tra trực tiếp (Liveness Command): Thực thi một lệnh bên trong container.

  • Yêu cầu HTTP (Liveness HTTP request): Gửi yêu cầu HTTP đến một đường dẫn (path) và cổng (port) do ta chỉ định.

  • TCP Liveness Probe: Thử kết nối TCP đến một cổng cụ thể trên container.

Liveness Command

In the following example, the liveness command is checking the existence of a file /tmp/healthy:

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 15
      failureThreshold: 1
      periodSeconds: 5

Liveness Probe được cấu hình để kiểm tra sự tồn tại của file /tmp/healthy cứ mỗi 5 giây bằng tham số periodSeconds. Tham số initialDelaySeconds yêu cầu kubelet đợi 15 giây trước khi thực hiện lần kiểm tra đầu tiên.

Giải thích chi tiết:

  • Kiểm tra:Liveness Probe sẽ chạy lệnh được cung cấp làm đối số dòng lệnh cho container.

  • Lệnh này sẽ tạo file /tmp/healthy trước, và xóa nó đi sau 30 giây.

  • Ngưỡng thất bại:

  • Việc xóa file sẽ kích hoạt thất bại của probe.

  • Tham số failureThreshold được đặt thành 1 nghĩa là kubelet sẽ coi container không hoạt động chỉ sau một lần thất bại của probe, và sẽ khởi động lại container.

Tóm lại, Liveness Probe này được thiết lập để tự động khởi động lại container mỗi 30 giây, giả sử ứng dụng bên trong container không thể tạo lại file /tmp/healthy kịp thời.

Liveness HTTP Request

Trong ví dụ này, kubelet gửi một yêu cầu HTTP GET tới đường dẫn /healthz của ứng dụng trên cổng 8080. Nếu yêu cầu đó trả về trạng thái lỗi (thất bại), kubelet sẽ khởi động lại container; trường hợp ngược lại, nó sẽ coi ứng dụng đang hoạt động bình thường:

...
     livenessProbe:
       httpGet:
         path: /healthz
         port: 8080
         httpHeaders:
         - name: X-Custom-Header
           value: Awesome
       initialDelaySeconds: 15
       periodSeconds: 5
...

TCP Liveness Probe

Với TCP Liveness Probe, kubelet cố gắng mở một kết nối TCP Socket tới container đang chạy ứng dụng. Nếu mở thành công, ứng dụng được coi là khỏe mạnh, ngược lại kubelet sẽ đánh dấu nó là không hoạt động và khởi động lại container.

Điểm khác biệt so với HTTP Probe:

  • TCP Probe kiểm tra ở mức thấp hơn. Nó chỉ kiểm tra xem có dịch vụ nào đang lắng nghe (listen) trên cổng chỉ định chứ không kiểm tra xem dịch vụ đó trả về nội dung hợp lệ hay không (như một HTTP Probe có thể làm).

  • Ứng dụng cho TCP Probe: Loại Probe này hữu dụng khi bạn cần kiểm tra một dịch vụ đơn giản mà không có endpoint HTTP kiểm tra sức khỏe riêng, hoặc để kiểm tra xem cổng có đang thực sự lắng nghe kết nối hay không.

...
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 5
...

Readiness Probes

Đôi khi, trong quá trình khởi tạo, ứng dụng cần đáp ứng một vài điều kiện nhất định trước khi “sẵn sàng” để phục vụ lưu lượng truy cập. Những điều kiện này có thể bao gồm việc đảm bảo một dịch vụ phụ thuộc khác đã sẵn sàng, hoặc để tải một tập dữ liệu lớn, v.v. Trong những trường hợp này, chúng ta dùng Readiness Probe (thăm dò sự sẵn sàng) và chờ một điều kiện được thỏa mãn, và chỉ khi đó ứng dụng mới phục vụ lưu lượng.

Pod với các container chưa báo cáo trạng thái “sẵn sàng” (ready) sẽ không nhận các yêu cầu được chuyển đến từ các Kubernetes Service.

Giải thích thêm:

  • Mục đích của Readiness Probe: Cho phép Kubernetes tạm thời không gửi lưu lượng đến Pod cho đến khi nó thực sự sẵn sàng để xử lý các yêu cầu. Điều này giúp tránh lỗi và tình trạng người dùng nhìn thấy trang web đang trong trạng thái chưa hoàn chỉnh.
...
    readinessProbe:
          exec:
            command:
            - cat
            - /tmp/healthy
          initialDelaySeconds: 5 
          periodSeconds: 5
...

Readiness Probes are configured similarly to Liveness Probes. Their configuration also remains the same.

Please review the Readiness Probes for more details.

Chapter 12. Kubernetes Volume Management

Trong các mô hình kinh doanh hiện nay, dữ liệu là tài sản quý giá nhất với nhiều công ty khởi nghiệp (startups) và doanh nghiệp (enterprises). Trong một cụm Kubernetes, các container bên trong Pod có thể là nơi sản sinh (produce) dữ liệu, tiêu thụ (consume) dữ liệu, hoặc cả hai. Trong khi một số dữ liệu trong container chỉ cần tồn tại tạm thời và không cần sống lâu hơn Pod, các loại dữ liệu khác lại phải có vòng đời dài hơn Pod để được tổng hợp và đưa vào các hạ tầng phân tích.

Kubernetes phải cung cấp các tài nguyên lưu trữ để container có thể sử dụng hoặc lưu trữ dữ liệu. Kubernetes sử dụng Volume (có nhiều loại) và một số dạng tài nguyên lưu trữ khác cho việc quản lý dữ liệu của container. Trong chương này, chúng ta sẽ tìm hiểu các đối tượng PersistentVolume và PersistentVolumeClaim, giúp đính kèm các Volume lưu trữ bền vững (persistent) vào các Pod.

Mục tiêu học tập:

  • Giải thích sự cần thiết của cơ chế quản lý dữ liệu bền vững.

  • So sánh các loại Volume trong Kubernetes.

  • Thảo luận về PersistentVolume và PersistentVolumeClaim.

Volumes

Như chúng ta đã biết, container chạy trong Pod mang bản chất tạm thời (ephemeral). Tất cả dữ liệu được lưu bên trong container sẽ bị xóa nếu container gặp sự cố. Tuy nhiên, kubelet sẽ khởi động lại container với trạng thái ban đầu, nghĩa là nó sẽ không có dữ liệu cũ.

Để giải quyết vấn đề này, Kubernetes sử dụng Volume (Ổ đĩa). Volume là một sự trừu tượng hóa cho phép Kubernetes cung cấp các công nghệ lưu trữ khác nhau cho container trong Pod sử dụng làm nơi lưu trữ dữ liệu. Volume về cơ bản là một điểm kết nối (mount point) trong hệ thống tập tin của container, được cung cấp bởi một thiết bị lưu trữ. Loại và chế độ truy cập của thiết bị lưu trữ này được xác định bởi loại Volume (Volume Type).

Trong Kubernetes, một Volume được liên kết đến một Pod và có thể được chia sẻ giữa các container trong Pod đó. Mặc dù Volume có vòng đời (lifespan) gắn với Pod (nghĩa là sẽ bị xóa khi Pod bị xóa), vòng đời của nó không hoàn toàn phụ thuộc vào các container trong Pod – điều này cho phép dữ liệu được bảo toàn ngay cả khi container bị khởi động lại.

Volume Types

Một thư mục được gắn kết vào Pod sẽ sử dụng một loại Volume (Volume Type) nhất định. Loại Volume quyết định các tính chất của thư mục này, như kích thước, nội dung, chế độ truy cập mặc định, v.v. Một vài loại Volume phổ biến:

  • emptyDir: Volume “trống” được tạo khi Pod được khởi tạo trên node. Vòng đời của Volume này gắn chặt với vòng đời của Pod. Nếu Pod bị xóa (terminate), dữ liệu trên emptyDir cũng bị xóa vĩnh viễn.

  • hostPath: Cho phép chia sẻ một thư mục giữa node và Pod. Dù Pod có bị xóa, dữ liệu trên Volume vẫn tồn tại trên node.

  • gcePersistentDisk, awsElasticBlockStore, azureDisk: Cho phép gắn các loại đĩa lưu trữ bền vững (persistent disk) do nhà cung cấp dịch vụ đám mây (Google, AWS, Azure) quản lý vào Pod.

  • azureFile, cephfs, nfs, iscsi: Cho phép gắn các hệ thống lưu trữ tập tin mạng (NFS, iSCSI, CephFS, Azure File) vào Pod.

  • secret: Chứa thông tin nhạy cảm như mật khẩu.

  • configMap: Cung cấp dữ liệu cấu hình hoặc các câu lệnh shell cho Pod.

  • persistentVolumeClaim: Cơ chế để Pod yêu cầu một PersistentVolume có sẵn. (Chúng ta sẽ thảo luận chi tiết hơn về loại này).

Tìm hiểu thêm: Bạn có thể xem chi tiết về các loại Volume tại tài liệu chính thức của Kubernetes: https://kubernetes.io/docs/concepts/storage/volumes/

PersistentVolumes

Trong môi trường IT truyền thống, việc quản lý lưu trữ thường được đảm trách bởi quản trị viên hệ thống hoặc chuyên gia lưu trữ. Người dùng cuối thường chỉ nhận hướng dẫn để sử dụng tài nguyên lưu trữ mà không cần quan tâm tới các hệ thống lưu trữ bên dưới.

Với thế giới container, ta cũng muốn có một cách làm tương tự, nhưng điều này trở nên thử thách do có nhiều loại Volume khác nhau như chúng ta đã thấy trước đó. Kubernetes giải quyết vấn đề này bằng hệ thống PersistentVolume (PV), cung cấp các API để người dùng và quản trị viên có thể quản lý và sử dụng các giải pháp lưu trữ bền vững (persistent storage). Để quản lý Volume, Kubernetes dùng API resource PersistentVolume và để sử dụng Volume, nó dùng API resource PersistentVolumeClaim.

Giải thích thêm:

  • Persistent Volume (PV): Là một tài nguyên lưu trữ đã được cung cấp (provisioned) bởi quản trị viên cụm. Một PV có thể là ổ cứng gắn với máy chủ nơi Pod chạy (local storage), là hệ thống lưu trữ trên mạng (network attached storage – NAS), lưu trữ đám mây (cloud storage), hoặc một hệ thống lưu trữ phân tán chuyên dụng.

PersistentVolume có thể được cấp phát động (dynamically provisioned) dựa trên tài nguyên StorageClass (lớp lưu trữ). Một StorageClass chứa các cơ chế cung cấp (provisioner) và các thông số được định nghĩa trước để tạo ra một PersistentVolume.

Sử dụng PersistentVolumeClaim (PVC), người dùng có thể gửi yêu cầu tạo PV động, yêu cầu này sẽ được liên kết tới một tài nguyên StorageClass phù hợp.

Một số loại Volume hỗ trợ việc quản lý lưu trữ bằng PersistentVolume:

  • GCEPersistentDisk

  • AWSElasticBlockStore

  • AzureFile

  • AzureDisk

  • CephFS

  • NFS

  • iSCSI.

Để có danh sách đầy đủ và thêm chi tiết, bạn có thể xem phần tài liệu về các loại Persistent Volume: https://kubernetes.io/docs/concepts/storage/persistent-volumes/

PersistentVolumeClaims

PersistentVolumeClaim (PVC) là một yêu cầu lưu trữ từ người dùng. Người dùng gửi yêu cầu tài nguyên PersistentVolume dựa trên lớp lưu trữ (storge class), chế độ truy cập (access mode), kích thước, và tùy chọn về volume mode. Có bốn chế độ truy cập:

  • ReadWriteOnce: Cho phép đọc – ghi trên một node duy nhất.

  • ReadOnlyMany: Chỉ đọc, cho nhiều node.

  • ReadWriteMany: Đọc-ghi, cho phép nhiều node.

  • ReadWriteOncePod: Đọc-ghi chỉ cho phép bởi một Pod duy nhất.

Các Volume mode bổ sung: filesystem (hệ thống tập tin) hoặc block device (thiết bị khối), cho phép Volume được gắn kết vào một thư mục trong Pod, hoặc như một thiết bị khối thô (raw block device).

Khi một PersistentVolume phù hợp được tìm thấy, nó sẽ được gắn (bound) vào PersistentVolumeClaim.

Sau khi được gắn kết (bind) thành công, tài nguyên PersistentVolumeClaim có thể được sử dụng bởi các container trong Pod.

Khi người dùng hoàn tất công việc của họ, các PersistentVolume đã được gắn kết có thể được giải phóng. Dựa trên thuộc tính persistentVolumeReclaimPolicy được cấu hình, các PersistentVolume này có thể được:

  • Thu hồi (Reclaim): Quản trị viên cụm sẽ kiểm tra và/ hoặc tổng hợp dữ liệu trước khi tái sử dụng.

  • Xóa (Delete): Cả dữ liệu và Volume đều bị xóa.

  • Tái sử dụng (Recycle): Chỉ dữ liệu cũ bị xóa, Volume được giữ lại cho sử dụng trong tương lai.

Lưu ý: Chính sách giải phóng (Reclaim Policy) là một phần quan trọng trong việc quản lý vòng đời của PersistentVolumes.

Để tìm hiểu thêm, bạn có thể xem phần tài liệu về PersistentVolumeClaims: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims

Container Storage Interface (CSI)

Các hệ thống điều phối container (container orchestrator) như Kubernetes, Mesos, Docker, hay Cloud Foundry vốn có các phương thức riêng để quản lý các thiết bị lưu trữ bên ngoài sử dụng Volume. Đối với các nhà cung cấp dịch vụ lưu trữ, việc quản lý các plugin Volume khác nhau cho các hệ thống điều phối khác nhau là một thử thách. Đây cũng là thử thách trong việc bảo trì code của chính Kubernetes, do các plugin được viết trực tiếp trong code của chương trình điều phối. Đáp lại vấn đề này, các nhà cung cấp dịch vụ lưu trữ và các thành viên trong cộng đồng của các hệ điều phối khác nhau đã bắt đầu hợp tác để chuẩn hóa giao diện Volume. Kết quả là Giao diện Lưu trữ Container (Container Storage Interface – CSI) được thiết kế để tương thích với nhiều hệ thống điều phối và nhà cung cấp lưu trữ khác nhau. Hãy tìm hiểu chi tiết trong phần đặc tả (specification) của CSI

Từ phiên bản Kubernetes v1.9 tới v1.13, CSI đã phát triển và trưởng thành từ giai đoạn thử nghiệm alpha tới được hỗ trợ ổn định (stable support). Điều này làm cho việc cài đặt các trình điều khiển Volume (driver) tương thích CSI trở nên đơn giản. Với CSI, các nhà cung cấp dịch vụ lưu trữ bên thứ ba có thể phát triển giải pháp của họ mà không cần phải tích hợp chúng vào code gốc của Kubernetes. Những giải pháp này là các trình điều khiển CSI, và chỉ cần được cài đặt bởi quản trị viên cụm khi cần thiết.

Using a Shared hostPath Volume Type Demo Guide

Hướng dẫn bài tập này được thiết kế để hướng dẫn bài thực hành có trong video ở cuối chương. Nó bao gồm định nghĩa manifest Deployment có thể được dùng làm mẫu để định nghĩa các đối tượng tương tự khác khi cần. Bên cạnh các phần định nghĩa volume và cách gắn kết volume vào từng container, đoạn command cho phép chúng ta chạy một lệnh cụ thể bên trong một container. Trong ví dụ này, chương trình shell (sh) của container Debian được gọi để thực thi lệnh echosleep (sử dụng tham số -c).

Giải thích thêm về command:

  • Mục đích: command trong manifest của Kubernetes cho phép bạn ghi đè (override) lệnh mặc định sẽ chạy khi container khởi động. Điều này rất hữu dụng cho mục đích chạy tác vụ thủ công, gỡ lỗi (debugging), hoặc thử nghiệm.

  • Lưu ý: Nếu Pod của bạn cần luôn chạy một dịch vụ như một web server, bạn nên chỉ định lệnh đó trong phần containers của manifest, không nên dùng command.

$ vim app-blue-shared-vol.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: blue-app
  name: blue-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: blue-app
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: blue-app
        type: canary
    spec:
      volumes:
      - name: host-volume
        hostPath:
          path: /home/docker/blue-shared-volume
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: host-volume
      - image: debian
        name: debian
        volumeMounts:
        - mountPath: /host-vol
          name: host-volume
        command: ["/bin/sh", "-c", "echo Welcome to BLUE App! > /host-vol/index.html ; sleep infinity"]
status: {}