Sổ tay Kubernetes: Triển khai ứng dụng đa container (p2)
Loạt bài viết sổ tay Kubernetes:
- Sổ tay Kubernetes: Giới thiệu về Kubernetes.
- Sổ tay Kubernetes: Các khái niệm cơ bản trong Kubernetes.
- Sổ tay Kubernetes: Phương pháp triển khai bằng khai báo.
- Sổ tay Kubernetes: Triển khai ứng dụng đa container (p1).
- Sổ tay Kubernetes: Triển khai ứng dụng đa container (p2) (bài viết này).
- Sổ tay Kubernetes: Triển khai ứng dụng đa container (p3).
- Sổ tay Kubernetes: làm việc với Ingress Controller (p1).
- Sổ tay Kubernetes: làm việc với Ingress Controller (p2).
Ở phần 1, bạn đã bắt đầu triển khai Notes API trên 3 container và cơ sở dữ liệu PostgreSQL trên 1 container. Trong quá trình deploy đã có một số lỗi xảy ra. Ở phần 2 này, chúng ta sẽ tìm hiểu nguyên nhân và khắc phục những lỗi này. Ngoài ra chúng ta cũng tìm hiểu về Persistent Volume, Persistent Volume Claim.
Nhận Container Log từ Pod
Có một lệnh kubectl
khác được gọi là logs
có thể giúp bạn lấy container log từ bên trong một pod. Cú pháp chung cho lệnh như sau:
kubectl logs <pod>
Để xem nhật ký bên trong pod api-deployment-d59f9c884-88j45
, lệnh sẽ như sau:
kubectl logs api-deployment-d59f9c884-88j45
# > api@1.0.0 start /usr/app
# > cross-env NODE_ENV=production node bin/www
# /usr/app/node_modules/knex/lib/client.js:55
# throw new Error(`knex: Required configuration option 'client' is missing.`);
^
# Error: knex: Required configuration option 'client' is missing.
# at new Client (/usr/app/node_modules/knex/lib/client.js:55:11)
# at Knex (/usr/app/node_modules/knex/lib/knex.js:53:28)
# at Object.<anonymous> (/usr/app/services/knex.js:5:18)
# at Module._compile (internal/modules/cjs/loader.js:1138:30)
# at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
# at Module.load (internal/modules/cjs/loader.js:986:32)
# at Function.Module._load (internal/modules/cjs/loader.js:879:14)
# at Module.require (internal/modules/cjs/loader.js:1026:19)
# at require (internal/modules/cjs/helpers.js:72:18)
# at Object.<anonymous> (/usr/app/services/index.js:1:14)
# npm ERR! code ELIFECYCLE
# npm ERR! errno 1
# npm ERR! api@1.0.0 start: `cross-env NODE_ENV=production node bin/www`
# npm ERR! Exit status 1
# npm ERR!
# npm ERR! Failed at the api@1.0.0 start script.
# npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
# npm ERR! A complete log of this run can be found in:
# npm ERR! /root/.npm/_logs/2020-08-09T10_28_52_779Z-debug.log
Trên đây là những gì bạn cần để gỡ lỗi vấn đề. Có vẻ như thư viện doux.js thiếu giá trị bắt buộc, điều này đang ngăn ứng dụng khởi động. Bạn có thể tìm hiểu thêm về lệnh logs
từ các tài liệu chính thức .
Điều này đang xảy ra vì bạn thiếu một số biến môi trường bắt buộc trong định nghĩa triển khai.
Nếu bạn xem xét lại định nghĩa dịch vụ api
bên trong tệp docker-compose.yaml
, bạn sẽ thấy một cái gì đó như sau:
api:
build:
context: ./api
dockerfile: Dockerfile.dev
ports:
- 3000:3000
volumes:
- /home/node/app/node_modules
- ./api:/home/node/app
environment:
DB_CONNECTION: pg
DB_HOST: db
DB_PORT: 5432
DB_USER: postgres
DB_DATABASE: notesdb
DB_PASSWORD: 63eaQB9wtLqmNBpg
Các biến môi trường này là bắt buộc để ứng dụng giao tiếp với cơ sở dữ liệu. Vì vậy, thêm chúng vào cấu hình triển khai sẽ khắc phục được sự cố.
Các biến môi trường
Việc thêm các biến môi trường vào tệp cấu hình Kubernetes rất đơn giản. Mở tệp api-deployment.yaml
và cập nhật nội dung của nó để trông giống như sau:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-deployment
spec:
replicas: 3
selector:
matchLabels:
component: api
template:
metadata:
labels:
component: api
spec:
containers:
- name: api
image: fhsinchy/notes-api
ports:
- containerPort: 3000
# these are the environment variables
env:
- name: DB_CONNECTION
value: pg
Các trường containers.env
có chứa tất cả các biến môi trường. Nếu bạn quan sát kỹ, bạn sẽ thấy rằng tôi chưa thêm tất cả các biến môi trường từ tệp docker-compose.yaml
. Tôi chỉ thêm một.
DB_CONNECTION
chỉ ra rằng ứng dụng đang sử dụng cơ sở dữ liệu PostgreSQL. Thêm biến đơn này sẽ khắc phục được sự cố.
Bây giờ chạy lại tệp cấu hình bằng cách thực hiện lệnh sau:
kubectl apply -f api-deployment.yaml
# deployment.apps/api-deployment configured
Không giống như những lần khác, đầu ra ở đây nói rằng một tài nguyên đã được configured
. Đây là vẻ đẹp của Kubernetes. Bạn chỉ có thể khắc phục sự cố và áp dụng lại cùng một tệp cấu hình ngay lập tức.
Bây giờ sử dụng lệnh get
một lần nữa để đảm bảo mọi thứ đang chạy bình thường.
kubectl get deployment
# NAME READY UP-TO-DATE AVAILABLE AGE
# api-deployment 3/3 3 3 68m
kubectl get pod
# NAME READY STATUS RESTARTS AGE
# api-deployment-66cdd98546-l9x8q 1/1 Running 0 7m26s
# api-deployment-66cdd98546-mbfw9 1/1 Running 0 7m31s
# api-deployment-66cdd98546-pntxv 1/1 Running 0 7m21s
Tất cả ba pod đang chạy và Deployment
cũng chạy tốt.
Tạo triển khai cơ sở dữ liệu
Bây giờ API đã được thiết lập và chạy, đã đến lúc viết cấu hình cho instance cơ sở dữ liệu.
Tạo một tệp khác có tên postgres-deployment.yaml
bên trong thư mục k8s
và đưa nội dung sau vào đó:
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres-deployment
spec:
replicas: 1
selector:
matchLabels:
component: postgres
template:
metadata:
labels:
component: postgres
spec:
containers:
- name: postgres
image: fhsinchy/notes-postgres
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
value: 63eaQB9wtLqmNBpg
- name: POSTGRES_DB
value: notesdb
Bản thân cấu hình rất giống với cấu hình trước đó. Tôi sẽ không giải thích mọi thứ trong tệp này – hy vọng bạn tự hiểu nó với kiến thức bạn đã thu được từ bài viết này cho đến nay.
PostgreSQL chạy trên cổng 5432 theo mặc định và biến POSTGRES_PASSWORD
là bắt buộc để chạy container postgres
. Mật khẩu này cũng sẽ được sử dụng để kết nối với cơ sở dữ liệu này bởi API.
Biến POSTGRES_DB
là không bắt buộc. Nhưng vì cách dự án này đã được cấu trúc, nó cần thiết ở đây – nếu không quá trình khởi tạo sẽ thất bại.
Bạn có thể tìm hiểu thêm về image postgres chính thức từ trang Docker Hub của họ. Để đơn giản, tôi tạo 1 bản sao (replicas) cơ sở dữ liệu trong dự án này.
Để chạy tệp này, hãy thực hiện lệnh sau:
kubectl apply -f postgres-deployment.yaml
# deployment.apps/postgres-deployment created
Sử dụng lệnh get
để đảm bảo rằng deployment và các pod đang chạy đúng cách:
kubectl get deployment
# NAME READY UP-TO-DATE AVAILABLE AGE
# postgres-deployment 1/1 1 1 13m
kubectl get pod
# NAME READY STATUS RESTARTS AGE
# postgres-deployment-76fcc75998-mwnb7 1/1 Running 0 13m
Mặc dù việc triển khai và các nhóm đang chạy đúng cách, có một vấn đề lớn với việc triển khai cơ sở dữ liệu.
Nếu bạn đã làm việc với bất kỳ hệ thống cơ sở dữ liệu nào trước đây, bạn có thể đã biết rằng cơ sở dữ liệu lưu trữ dữ liệu trong hệ thống tệp. Ngay bây giờ việc triển khai cơ sở dữ liệu trông giống như sau:
Container postgres
được đóng gói bởi một pod. Bất kỳ dữ liệu nào được lưu vẫn nằm trong hệ thống tệp nội bộ của container.
Bây giờ, nếu vì lý do nào đó, container gặp sự cố hoặc pod đóng gói container gặp sự cố, tất cả dữ liệu trong hệ thống tệp sẽ bị mất.
Khi gặp sự cố, Kubernetes sẽ tạo một pod mới để duy trì trạng thái mong muốn, nhưng không có cơ chế chuyển dữ liệu nào giữa hai pod.
Để giải quyết vấn đề này, bạn có thể lưu trữ dữ liệu trong một không gian riêng biệt bên ngoài pod trong cụm.
Quản lý bộ nhớ như vậy là một vấn đề khác biệt với việc quản lý các instance máy tính. Hệ thống con PersistentVolume
trong Kubernetes cung cấp một API cho người dùng và quản trị viên để trừu tượng hóa chi tiết về cách cung cấp dung lượng lưu trữ từ cách nó được sử dụng.
PersistentVolume và PersistentVolumeClaim
Theo tài liệu Kubernetes –
“
PersistentVolume
(PV) là một phần lưu trữ trong cụm đã được cấp phép bởi quản trị viên hoặc được cấp phép động bằng cách sử dụngStorageClass
. Nó là một tài nguyên trong cụm giống như một nút là một tài nguyên cụm.”
Về cơ bản, PersistentVolume
là một cách để lấy một phần từ không gian lưu trữ của bạn và dành phần đó cho một pod nhất định. Volume luôn được sử dụng bởi các pod chứ không phải một số đối tượng cấp cao như deployment.
Nếu bạn muốn sử dụng volume với deployment có nhiều pod, bạn sẽ phải thực hiện một số bước bổ sung.
Tạo một tệp mới có tên database-persistent-volume.yaml
bên trong thư mục k8s
và đưa nội dung sau vào tệp đó:
apiVersion: v1
kind: PersistentVolume
metadata:
name: database-persistent-volume
spec:
storageClassName: manual
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
Các trường apiVersion
, kind
và metadata
phục vụ mục đích tương tự như bất kỳ tập tin cấu hình khác. Tuy nhiên, trường spec
có một số trường mới.
spec.storageClassName
cho biết tên lớp cho volume này. Giả sử rằng một nhà cung cấp đám mây có sẵn ba loại lưu trữ. Chúng có thể chậm, nhanh và rất nhanh. Loại dung lượng bạn nhận được từ nhà cung cấp sẽ phụ thuộc vào số tiền bạn phải trả. Nếu bạn yêu cầu một bộ nhớ quá nhanh, bạn sẽ phải trả thêm tiền. Các kiểu lưu trữ khác nhau này là các lớp. Tôi đang sử dụngmanual
làm ví dụ ở đây. Bạn có thể sử dụng bất cứ thứ gì bạn thích trong cụm cục bộ của mình.spec.capacity.storage
là dung lượng lưu trữ mà tập này sẽ có. Tôi đang cung cấp cho nó 5 gigabyte dung lượng lưu trữ trong dự án này.spec.accessModes
đặt chế độ truy cập cho volume. Có ba chế độ truy cập có thể có.ReadWriteOnce
có nghĩa là volume có thể được mount dưới dạng đọc-ghi bởi một nút duy nhất.ReadWriteMany
có nghĩa là volume có thể được mount dưới dạng đọc-ghi bởi nhiều nút.ReadOnlyMany
có nghĩa là ổ đĩa có thể được mount ở chế độ chỉ đọc bởi nhiều nút.spec.hostPath
cho biết thư mục trong cụm nút đơn cục bộ của bạn sẽ được coi là persistent volume./mnt/data
có nghĩa là dữ liệu được lưu trong persistent volume này sẽ nằm bên trong thư mục/mnt/data
trong cụm.
Để chạy tệp này, hãy thực hiện lệnh sau:
kubectl apply -f database-persistent-volume.yaml
# persistentvolume/database-persistent-volume created
Bây giờ sử dụng lệnh get
để xác minh rằng volume đã được tạo:
kubectl get persistentvolume
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
# database-persistent-volume 5Gi RWO Retain Available manual 58s
Bây giờ tập tin liên tục đã được tạo, bạn cần một cách để cho phép nhóm postgres truy cập vào nó. Đây là nơi PersistentVolumeClaim
(PVC) đi vào.
PersistentVolumeClaim
là yêu cầu lưu trữ cho một pod. Giả sử rằng trong một cụm, bạn có khá nhiều volume. Claim này sẽ xác định các đặc điểm mà một volume phải đáp ứng để có thể đáp ứng nhu cầu cần thiết của một pod.
Một ví dụ thực tế có thể là bạn mua một ổ SSD từ một cửa hàng. Bạn đến cửa hàng và nhân viên bán hàng cho bạn xem các mẫu sau:
Model 1 | Model 2 | Model 3 |
---|---|---|
128GB | 256GB | 512GB |
SATA | NVME | SATA |
Bây giờ, bạn yêu cầu một kiểu máy có ít nhất 200GB dung lượng lưu trữ và là ổ NVME.
Cái đầu tiên có ít hơn 200GB và là SATA, vì vậy nó không khớp với yêu cầu của bạn. Cái thứ ba có hơn 200GB, nhưng không phải là NVME. Tuy nhiên, cái thứ hai có hơn 200GB và cũng là một NVME. Vì vậy, đó là một trong những bạn nhận được.
Các mẫu SSD mà nhân viên bán hàng cho bạn xem tương đương với dung lượng ổn định và yêu cầu của bạn tương đương với yêu cầu về dung lượng liên tục.
Tạo một tệp mới khác có tên database-persistent-volume-claim.yaml
bên trong thư mục k8s
và đặt nội dung sau vào tệp đó:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: database-persistent-volume-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
Một lần nữa, các trường apiVersion
, kind
, và metadata
phục vụ mục đích tương tự như bất kỳ tập tin cấu hình khác.
spec.storageClass
trong tệp cấu hình claim cho biết loại bộ nhớ mà claim này muốn. Điều đó có nghĩa bất kỳPersistentVolume
nào có thiết lậpspec.storageClass
làmanual
sẽ được sử dụng bởi claim này. Nếu bạn có nhiều volume với lớpmanual
, claim sẽ nhận được bất kỳ volume nào trong số đó và nếu bạn không có volume với lớpmanual
nào – một volume sẽ được cấp động.spec.accessModes
thiết lập chế độ truy cập. Điều này cho thấy rằng claim này muốn có một lưu trữ cóaccessMode
làReadWriteOnce
. Giả sử rằng bạn có hai volume vớistorageClass
được đặt thànhmanual
. Một trong số chúng đã được thiết lậpaccessModes
thànhReadWriteOnce
và cái còn lại được thiết lập làReadWriteMany
. Claim này sẽ nhận được volume có thiết lập làReadWriteOnce
.resources.requests.storage
là dung lượng lưu trữ mà claim này muốn.2Gi
không có nghĩa là volume nhất định phải có chính xác 2 gigabyte dung lượng lưu trữ. Nó có nghĩa là nó phải có ít nhất 2 gigabyte. Tôi hy vọng bạn nhớ rằng bạn thiết lập dung lượng của ổ đĩa liên tục là 5 gigabyte, cao hơn 2 gigabyte.
Để chạy tệp này, hãy thực hiện lệnh sau:
kubectl apply -f database-persistent-volume-claim.yaml
# persistentvolumeclaim/database-persistent-volume-claim created
Bây giờ sử dụng lệnh get
để xác minh rằng volume đã được tạo:
kubectl get persistentvolumeclaim
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# database-persistent-volume-claim Bound database-persistent-volume 5Gi RWO manual 37s
Nhìn vào cột VOLUME
. Claim này bị ràng buộc với persitent volume database-persistent-volume
mà bạn đã tạo trước đó. Hãy nhìn vào cột CAPACITY
có giá trị 5Gi
, bởi vì claim đã yêu cầu một volume có ít nhất 2 gigabyte dung lượng lưu trữ.
Bài viết gốc.