Sổ tay Kubernetes: Triển khai ứng dụng đa container (p2)

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:

Mô hình triển khai database PostgreSQL trong Kubernetes

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.

Mô hình lưu trữ dữ liệu bên ngoài Pod trong Kubernetes

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ụng StorageClass. 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, kindmetadata 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, nhanhrấ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ụng manual 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ập spec.storageClassmanual sẽ được sử dụng bởi claim này. Nếu bạn có nhiều volume với lớp manual, 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ớp manual 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ó accessModeReadWriteOnce. Giả sử rằng bạn có hai volume với storageClass được đặt thành manual. Một trong số chúng đã được thiết lập accessModes thành ReadWriteOnce 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ữ.

Sổ tay Kubernetes: Triển khai ứng dụng đa container (p3)
Ở phần 3 này, chúng ta sẽ tìm hiểu cách cung cấp động Persistent Volume, kết nối volume với pod và kết nối các thành phần trong cụm với nhau.

Bài viết gốc.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *