Kubernetes, viết tắt là “k8s” hoặc “kube”, cho phép người dùng khai báo trạng thái mong muốn của một ứng dụng bằng cách sử dụng các khái niệm như “triển khai” và “dịch vụ”. Ví dụ: người dùng có thể chỉ định rằng họ muốn triển khai với ba phiên bản của ứng dụng web Tomcat đang chạy. Kubernetes khởi động và sau đó liên tục giám sát các container, cung cấp tính năng tự động khởi động lại, lên lịch lại và sao chép để đảm bảo ứng dụng luôn ở trạng thái mong muốn.
Các khái niệm chính của Kubernetes
Các tài nguyên Kubernetes có thể được tạo trực tiếp trên dòng lệnh nhưng thường được chỉ định bằng cách sử dụng Yet Another Markup Language (YAML). Các tài nguyên có sẵn và các trường cho mỗi tài nguyên có thể thay đổi với các phiên bản Kubernetes mới, vì vậy, điều quan trọng là phải kiểm tra kỹ tham chiếu API cho phiên bản của bạn để biết những gì có sẵn. Điều quan trọng nữa là sử dụng đúng “apiVersion” phù hợp với phiên bản Kubernetes của bạn. Bài tham khảo này sử dụng API từ Kubernetes 1.12, được phát hành ngày 27 tháng 9 năm 2018.
POD
Một Pod là một nhóm gồm một hoặc nhiều container. Kubernetes sẽ lên lịch tất cả các container cho một pod vào cùng một máy chủ lưu trữ, với cùng một không gian tên mạng, vì vậy tất cả chúng đều có cùng địa chỉ IP và có thể truy cập lẫn nhau bằng cách sử dụng localhost. Đây là một định nghĩa pod ví dụ:
apiVersion: v1
kind: Pod
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- mountPath: /srv/www
name: www-data
readOnly: true
- name: git-monitor
image: kubernetes/git-monitor
env:
- name: GIT_REPO
value: http://github.com/some/repo.git
volumeMounts:
- mountPath: /data
name: www-data
volumes:
- name: www-data
emptyDir: {}
Không chỉ các container trong một pod có thể chia sẻ cùng một giao diện mạng mà chúng còn có thể chia sẻ dung lượng, như được minh họa trong ví dụ. Ví dụ này sử dụng container “git-monitor” để giữ cho kho lưu trữ Git được cập nhật trong /data để container “nginx” có thể chạy một máy chủ web để cung cấp các tệp của kho lưu trữ.
Khi một pod được tạo, Kubernetes sẽ giám sát nó và tự động khởi động lại nếu một tiến trình kết thúc. Ngoài ra, Kubernetes có thể được định cấu hình để cố gắng kết nối với một container qua mạng để xác định xem pod đã sẵn sàng (readinessProbe) và vẫn còn sống (livenessProbe) hay không.
Triển khai (DEPLOYMENT)
Việc triển khai cung cấp các bản cập nhật thay đổi quy mô pod và cập nhật liên tục (rolling updates). Kubernetes sẽ đảm bảo rằng số lượng pod được chỉ định đang chạy và trên bản cập nhật liên tục sẽ thay thế từng pod một, cho phép cập nhật ứng dụng mà không có thời gian chết.
Các triển khai đã tốt nghiệp từ bản beta trong Kubernetes 1.11 và thay thế khái niệm cũ hơn về Bộ bản sao (Replica Sets). Việc triển khai tạo ra một tập hợp bản sao (replica set), nhưng không cần thiết phải tương tác trực tiếp với tập hợp bản sao.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.15.4
ports:
- containerPort: 80
Việc triển khai bao gồm một “mẫu” (template) chỉ định các pod được tạo sẽ trông như thế nào, vì vậy không cần phải định nghĩa riêng một pod. Kubernetes sẽ tự động tạo số lượng pod cần thiết từ mẫu triển khai.
Lưu ý rằng việc triển khai sẽ xác định các pod phù hợp bằng cách sử dụng trường matchLabels. Trường này phải luôn có cùng dữ liệu với trường metadata.labels bên trong mẫu. Việc triển khai sẽ có quyền sở hữu bất kỳ pod nào đang chạy khác khớp với bộ chọn matchLabels, ngay cả khi chúng được tạo riêng, vì vậy hãy giữ những tên này là duy nhất.
Dịch vụ (SERVICE)
Một dịch vụ cung cấp cân bằng tải cho một triển khai. Trong triển khai, mỗi pod được gán một địa chỉ IP duy nhất và khi một pod được thay thế, pod mới thường nhận được một địa chỉ IP mới.
Bằng cách khai báo một dịch vụ, chúng ta có thể cung cấp một điểm vào duy nhất cho tất cả các pod trong một lần triển khai. Điểm nhập duy nhất này (tên máy chủ và địa chỉ IP) vẫn hợp lệ khi các pod đến và đi.
kind: Service
apiVersion: v1
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
Các dịch vụ và triển khai có thể được tạo theo bất kỳ thứ tự nào. Dịch vụ chủ động giám sát Kubernetes để tìm các pod phù hợp với trường bộ chọn (selector). Ví dụ: trong trường hợp này, dịch vụ sẽ khớp các pod với nội dung metadata.labels của app: nginx, giống như được hiển thị trong ví dụ triển khai ở trên.
Các dịch vụ dựa vào Kubernetes để cung cấp một địa chỉ IP duy nhất và định tuyến lưu lượng truy cập đến chúng, do đó, cách các dịch vụ được định cấu hình có thể khác nhau tùy thuộc vào cách cấu hình cài đặt Kubernetes của bạn.
Yêu cầu dung lượng thường trú (PERSISTENT VOLUME CLAIM)
Kubernetes có nhiều loại tài nguyên lưu trữ. Ví dụ về Pod ở trên cho thấy đơn giản nhất, một thư mục trống được gắn vào nhiều container trong cùng một pod. Để lưu trữ thực sự thường trú, cách tiếp cận linh hoạt nhất là sử dụng một Yêu cầu Dung lượng Thường trú.
Yêu cầu Dung lượng Thường trú yêu cầu Kubernetes phân bổ động dung lượng từ Lớp lưu trữ (Storage Class). Lớp lưu trữ thường được tạo bởi quản trị viên của cụm Kubernetes và phải tồn tại. Sau khi Yêu cầu Dung lượng Thường trú được tạo, nó có thể được đính kèm vào một Pod. Kubernetes sẽ giữ bộ nhớ trong khi Yêu cầu Dung lượng Thường trú tồn tại, ngay cả khi Pod đính kèm bị xóa.
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: web-static-files
spec:
resources:
requests:
storage: 8Gi
storageClassName: file-store
---
apiVersion: v1
kind: Pod
metadata:
name: webserver
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html
name: web-files
volumes:
- name: web-files
persistentVolumeClaim:
claimName: web-static-files
Ví dụ trên khai báo cả Yêu cầu Dung lượng Thường trú và một Pod sử dụng nó. Ví dụ này tận dụng khả năng đặt nhiều tài liệu YAML trong cùng một tệp bằng cách sử dụng — làm dấu phân tách. Tất nhiên, trong một ví dụ thực tế, sẽ tốt hơn nếu tách riêng Yêu cầu Dung lượng Thường trú để nó có thể dễ dàng được giữ lại ngay cả khi Pod bị xóa.
Để biết thêm thông tin về các nhà cung cấp có sẵn cho Lớp lưu trữ Kubernetes và để biết nhiều ví dụ về cách định cấu hình lưu trữ thường trú (persistent storage), hãy xem Kho lưu trữ Container Thường trú (Persistent Container Storage) của DZone Refcard.
Kiến trúc Kubernetes
Kubernetes sử dụng kiến trúc client-server, như được thấy ở đây:
Cụm Kubernetes là một tập hợp các máy vật lý hoặc máy ảo và các tài nguyên cơ sở hạ tầng khác được sử dụng để chạy các ứng dụng. Các máy quản lý cụm được gọi là Máy chủ (Masters) và máy chạy các container được gọi là Nút (Nodes).
Máy chủ (MASTER)
Master chạy các dịch vụ quản lý cụm. Quan trọng nhất là kube-apiserver, là dịch vụ chính mà các máy khách và các nút sử dụng để truy vấn và sửa đổi các tài nguyên đang chạy trong cụm. Máy chủ API được hỗ trợ bởi: etcd, một kho lưu trữ khóa-giá trị phân tán được sử dụng để ghi lại trạng thái cụm; kube-controller-manager, một chương trình giám sát quyết định những thay đổi cần thực hiện khi tài nguyên được thêm vào, thay đổi hoặc loại bỏ; và kube-scheduler, một chương trình quyết định nơi chạy các pod dựa trên các nút có sẵn và cấu hình của chúng.
Trong bản cài đặt Kubernetes có tính khả dụng cao, sẽ có nhiều master, trong đó một bản đóng vai trò là bản chính (primary) và các bản khác đóng vai trò là bản sao (replica).
Nút (NODE)
Node là một máy vật lý hoặc máy ảo với các dịch vụ cần thiết để chạy các container. Một cụm Kubernetes nên có nhiều nút cần thiết cho tất cả các pod được yêu cầu. Mỗi nút có hai dịch vụ Kubernetes: kubelet, nhận lệnh để chạy các container và sử dụng công cụ container (container engine) (ví dụ: Docker) để chạy chúng; và kube-proxy, quản lý các quy tắc mạng để các kết nối đến địa chỉ IP dịch vụ được định tuyến chính xác đến các pod.
Như trong hình, mỗi nút có thể chạy nhiều pod và mỗi pod có thể bao gồm một hoặc nhiều container. Pod chỉ là một khái niệm của Kubernetes; kubelet định cấu hình công cụ container để đặt nhiều container trong cùng một không gian tên mạng (network namespace) để các container đó chia sẻ một địa chỉ IP.
Bắt đầu với Kubernetes
THIẾT LẬP KUBERNETES
Có nhiều cách khác nhau để thiết lập, cấu hình và chạy Kubernetes. Nó có thể được chạy trên đám mây bằng cách sử dụng các nhà cung cấp như Amazon Elastic Container Service cho Kubernetes, Google Kubernetes Engine, Azure Kubernetes Service, Packet, Pivotal Container Service, v.v. Nó cũng có thể được chạy tại chỗ (on-premise) bằng cách xây dựng một cụm từ đầu trên phần cứng vật lý hoặc thông qua máy ảo. Các tùy chọn khác nhau được mô tả trong tài liệu thiết lập Kubernetes (Kubernetes setup documentation), nơi bạn có thể tìm ra giải pháp nào tốt nhất cho mình và nhận hướng dẫn từng bước. Tùy chọn phổ biến nhất để xây dựng cụm Kubernetes đa máy chủ (multi-host) từ đầu là kubeadm, trong khi cách được khuyến nghị để bắt đầu và chạy một cụm nút đơn (single-node) để phát triển và thử nghiệm là sử dụng Minikube.
Tuy nhiên khi bạn thiết lập cụm của mình, bạn sẽ tương tác với nó bằng cách sử dụng kubectl chương trình khách dòng lệnh Kubernetes tiêu chuẩn.
MINIKUBE
Minikube sử dụng phần mềm ảo hóa như VirtualBox, VMware hoặc KVM để chạy cụm. Sau khi Minikube được cài đặt, bạn có thể sử dụng dòng lệnh minikube để bắt đầu một cụm bằng cách chạy lệnh sau:
minikube start
Để dừng cụm, bạn có thể chạy:
minikube stop
Để xác định địa chỉ IP của cụm đang sử dụng:
minikube ip
Nếu bạn đang gặp sự cố, bạn có thể xem nhật ký (logs) hoặc ssh vào máy chủ để giúp gỡ lỗi sự cố bằng cách sử dụng:
minikube logs
minikube ssh
Bạn cũng có thể mở chế độ xem bảng điều khiển trong trình duyệt để xem và thay đổi những gì đang diễn ra trong cụm.
minikube dashboard
KUBECTL
kubectl là một tiện ích dòng lệnh điều khiển cụm Kubernetes. Các lệnh sử dụng định dạng này:
kubectl [command] [type] [name] [flags]
- [command] chỉ định thao tác cần được thực hiện trên tài nguyên. Ví dụ: create, get, describe, delete hoặc scale.
- [type] chỉ định loại tài nguyên Kubernetes. Ví dụ: pod (po), service (svc), deployment (deploy) hoặc persistentvolumeclaim (pvc). Các loại tài nguyên không phân biệt chữ hoa chữ thường và bạn có thể chỉ định các dạng số ít, số nhiều hoặc viết tắt.
- [name] Chỉ định tên của tài nguyên, nếu có. Tên có phân biệt chữ hoa chữ thường. Nếu tên bị bỏ qua, thông tin chi tiết cho tất cả các tài nguyên sẽ được hiển thị (ví dụ: kubectl get pods).
- [flags] Các tùy chọn cho lệnh.
Một số ví dụ về lệnh kubectl và mục đích của chúng:
COMMAND | MỤC ĐÍCH |
kubectl create -f nginx.yaml | Tạo tài nguyên được chỉ định trong tệp YAML. Nếu tồn tại bất kỳ tài nguyên nào được chỉ định, lỗi sẽ được trả về. |
kubectl delete -f nginx.yml | Xóa các tài nguyên được chỉ định trong tệp YAML. Nếu bất kỳ tài nguyên nào không tồn tại, chúng sẽ bị bỏ qua. |
kubectl get pods | Liệt kê tất cả các pod trong không gian tên “default”. Xem bên dưới để biết thêm thông tin về không gian tên. |
kubectl describe pod nginx | Hiển thị siêu dữ liệu cho pod “nginx”. Tên phải khớp chính xác. |
kubectl –help | Hiển thị danh sách đầy đủ các lệnh có sẵn. |
CHẠY CONTAINER ĐẦU TIÊN CỦA BẠN
Hầu hết thời gian khi sử dụng kubectl, chúng ta tạo các tệp tài nguyên YAML, vì vậy chúng ta có thể định cấu hình cách chúng ta muốn ứng dụng của mình chạy. Tuy nhiên, chúng ta có thể tạo một Triển khai đơn giản bằng kubectl mà không cần sử dụng tệp YAML:
kubectl create deployment nginx --image=nginx deployment.apps/nginx created
Lệnh này sẽ bắt đầu một Triển khai, trong đó chứa Repilca Set, chứa một Pod, chứa một Docker container đang chạy một máy chủ web NGINX. Chúng ta có thể sử dụng kubectl để nhận trạng thái của việc triển khai:
kubectl get deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx 1 1 1 1 1m
Trạng thái của Replica Set có thể được nhìn thấy bằng cách sử dụng:
kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-65899c769f 1 1 1 1m
Trạng thái của Pod có thể được nhìn thấy bằng cách sử dụng:
kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-65899c769f-kp5c7 1/1 Running 0 1m
Tất nhiên, hầu hết thời gian chúng ta sẽ sử dụng tệp cấu hình YAML:
kubectl create -f nginx-deployment.yaml
Tệp nginx-deployment.yaml chứa định nghĩa Triển khai được hiển thị ở trên.
QUY MÔ ỨNG DỤNG
Việc triển khai có thể được mở rộng và thu nhỏ:
kubectl scale --replicas=3 deploy/nginx deployment.extensions/nginx scaled
Sau đó, bộ điều khiển Kubernetes sẽ làm việc với bộ lập lịch để tạo hoặc xóa các pod nếu cần để đạt được số lượng được yêu cầu. Điều này được phản ánh trong việc triển khai:
kubectl get deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx 3 3 3 3 3m
Bạn có thể xác minh có ba pod bằng cách chạy:
kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-65899c769f-c46xx 1/1 Running 0 38s
nginx-65899c769f-j484j 1/1 Running 0 38s
nginx-65899c769f-kp5c7 1/1 Running 0 3m
Lưu ý rằng Pod ban đầu tiếp tục chạy trong khi thêm hai Pod nữa. Tất nhiên, chúng ta cũng muốn tạo một Dịch vụ (Service) để hỗ trợ cân bằng tải trong các trường hợp này; xem bên dưới để có một ví dụ đầy đủ hơn.
XÓA ỨNG DỤNG
Sau khi sử dụng xong ứng dụng, bạn có thể hủy nó bằng lệnh delete.
kubectl delete deployment nginx deployment.extensions "nginx" deleted
Vì Kubernetes giám sát các pod để đạt được số lượng bản sao mong muốn, chúng ta phải xóa Triển khai để loại bỏ ứng dụng. Chỉ cần dừng container hoặc xóa pod sẽ chỉ khiến Kubernetes tạo một pod khác.
Ứng dụng mẫu
Hãy kết hợp nhiều tính năng Kubernetes với nhau để triển khai ứng dụng Node.js cùng với máy chủ cơ sở dữ liệu PostgreSQL. Đây là kiến trúc quy hoạch (xem bên phải).
Chúng ta sẽ làm việc từ cuối sơ đồ. Đầu tiên, chúng ta sẽ định nghĩa triển khai PostgreSQL:
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgresql
labels:
app: postgresql
spec:
replicas: 1
selector:
matchLabels:
app: postgresql
template:
metadata:
labels:
app: postgresql
spec:
containers:
- name: postgresql
image: postgres:10.4
env:
- name: PGDATA
value: "/data/pgdata"
volumeMounts:
- mountPath: /data
name: postgresql-data
volumes:
- name: postgresql-data
persistentVolumeClaim:
claimName: postgresql-data
Lưu ý rằng chúng ta sử dụng PersistentVolumeClaim để chúng ta nhận được dữ liệu thường trú; chúng ta sẽ cho rằng điều này đã được tạo vì chúng ta muốn nó tồn tại trong một thời gian dài ngay cả khi chúng ta cập nhật ứng dụng.
Mặc dù sẽ chỉ có một phiên bản cơ sở dữ liệu, chúng ta sẽ tạo một Dịch vụ để địa chỉ IP sẽ giữ nguyên ngay cả khi pod PostgreSQL được thay thế.
kind: Service
apiVersion: v1
metadata:
name: postgres-service
spec:
selector:
app: postgresql
ports:
- protocol: TCP
port: 5432
Tiếp theo, chúng ta tạo triển khai cho ứng dụng Node.js:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs
labels:
app: nodejs
spec:
replicas: 3
selector:
matchLabels:
app: nodejs
template:
metadata:
labels:
app: nodejs
spec:
containers:
- name: nodejs
image: nodejs:10-alpine
command: ["npm"]
args: ["start"]
env:
- name: NODE_ENV
value: production
workingDir: /app
volumeMounts:
- mountPath: /app
name: node-app
readOnly: true
- name: git-monitor
image: kubernetes/git-monitor
env:
- name: GIT_REPO
value: http://github.com/some/repo.git
volumeMounts:
- mountPath: /data
name: node-app
volumes:
- name: www-data
emptyDir: {}
Ví dụ này sử dụng một container “sidecar” (thùng của mô tô thùng), kubernetes/git-monitor, để giữ cho ứng dụng của chúng ta được cập nhật dựa trên kho lưu trữ Git. Sidecar điền một ổ đĩa được chia sẻ với container Node.js.
Cuối cùng, chúng ta tạo dịch vụ cung cấp điểm nhập người dùng cho ứng dụng của chúng ta:
kind: Service
apiVersion: v1
metadata:
name: nodejs-service
spec:
selector:
app: nodejs
ports:
- protocol: TCP
port: 3000
type: LoadBalancer
Cung cấp dịch vụ với IP bên ngoài để chúng có thể được nhìn thấy từ bên ngoài cụm là một chủ đề phức tạp vì nó phụ thuộc vào môi trường cụm của bạn (ví dụ: cloud, máy ảo hoặc bare metal). Ví dụ này sử dụng dịch vụ LoadBalancer, dịch vụ này yêu cầu một số bộ cân bằng tải bên ngoài như Amazon Elastic Load Balancer có sẵn và được định cấu hình trong cụm.
Không gian tên, hạn ngạch tài nguyên và giới hạn
Kubernetes sử dụng không gian tên để tránh xung đột tên, để kiểm soát quyền truy cập và đặt hạn ngạch. Khi chúng ta tạo các tài nguyên ở trên, những tài nguyên này đã trở thành không gian tên default. Các tài nguyên khác là một phần của cơ sở hạ tầng cụm nằm trong không gian tên kube-system.
Để xem các pod trong kube-system, chúng ta có thể chạy:
$ kubectl get po -n kube-system
NAME READY STATUS RESTARTS AGE ...
kube-apiserver-minikube 1/1 Running 0 17m ...
CÔ LẬP TÀI NGUYÊN
Một không gian tên mới có thể được tạo từ định nghĩa tài nguyên YAML:
apiVersion: v1
kind: Namespace
metadata:
name: development
labels:
name: development
Khi chúng ta đã tạo không gian tên, chúng ta có thể tạo tài nguyên trong đó bằng cờ –namespace (-n) hoặc bằng cách chỉ định không gian tên trong siêu dữ liệu của tài nguyên:
apiVersion: v1
kind: Pod
metadata:
name: webserver
namespace: development
...
Bằng cách sử dụng các không gian tên riêng biệt, chúng ta có thể có nhiều pod được gọi là webserver và không phải lo lắng về các xung đột tên.
KIỂM SOÁT TRUY CẬP
Kubernetes hỗ trợ Kiểm soát truy cập dựa trên vai trò /Role Based Access Control (RBAC).
Dưới đây là một ví dụ giới hạn các nhà phát triển quyền truy cập chỉ đọc (read-only) cho các pod trong production. Đầu tiên, chúng ta tạo một ClusterRole, một tập hợp các quyền phổ biến mà chúng ta có thể áp dụng cho bất kỳ không gian tên nào:
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: pod-read-only
rules:
- apiGroups: [""] # "" indicates the core API group resources: ["pods"]
verbs: ["get", "watch", "list"]
Tiếp theo, chúng ta sử dụng RoleBinding để áp dụng ClusterRole này cho một nhóm cụ thể trong một không gian tên cụ thể:
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: read-only
namespace: production
subjects:
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: pod-read-only
apiGroup: rbac.authorization.k8s.io
Ngoài ra, chúng ta có thể sử dụng ClusterRoleBinding để áp dụng vai trò cho người dùng hoặc nhóm trong tất cả các không gian tên.
HẠN NGẠCH TÀI NGUYÊN
Theo mặc định, pod có tài nguyên không giới hạn. Chúng ta có thể áp dụng một hạn ngạch cho một không gian tên:
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-resources
namespace: sandbox
spec:
hard:
cpu: "5"
memory: 10Gi
Kubernetes hiện sẽ từ chối các pod không giới hạn trong không gian tên này. Thay vào đó, chúng ta cần áp dụng một giới hạn:
apiVersion: v1
kind: Pod
metadata:
name: webserver
namespace: sandbox
spec:
containers:
- image: nginx
name: nginx
resources:
limits:
memory: "128Mi"
cpu: "500m"
Lưu ý rằng chúng ta có thể yêu cầu các phân số của CPU và sử dụng các đơn vị khác nhau cho bộ nhớ.