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

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

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) (bài viết này).
  • Sổ tay Kubernetes: Triển khai ứng dụng đa container (p2).
  • 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 trước, bạn đã triển khai cùng một ứng dụng hello-kube theo cách tiếp cận khai báo.

Cho đến lúc này bạn đã làm việc với các ứng dụng chạy trong một container.

Trong phần này, bạn sẽ làm việc với một ứng dụng bao gồm hai container. Bạn cũng sẽ làm quen với kỹ thuật Deployment, ClusterIP, PersistentVolume, PersistentVolumeClaim và một số lỗi.

Ứng dụng của bạn sẽ làm việc là một API ghi chú nhanh đơn giản với đầy đủ chức năng CRUD. Ứng dụng sử dụng PostgreSQL làm hệ thống cơ sở dữ liệu của nó. Vì vậy, bạn không chỉ triển khai ứng dụng mà còn thiết lập mạng nội bộ giữa ứng dụng và cơ sở dữ liệu.

Mã cho ứng dụng nằm trong thư mục notes-api bên trong repository dự án.

.
├── api
├── docker-compose.yaml
└── postgres

2 directories, 1 file

Mã nguồn ứng dụng nằm bên trong thư mục api và thư mục postgres chứa một Dockerfile để tạo image postgres tùy chỉnh. Tập tin docker-compose.yaml chứa các cấu hình cần thiết để chạy các ứng dụng sử dụng lệnh docker-compose.

Cũng giống như với dự án trước, bạn có thể xem tập tin Dockerfile của từng dịch vụ riêng lẻ để biết cách ứng dụng chạy bên trong container. Hoặc bạn cũng có thể xem tập tin docker-compose.yaml.

version: "3.8"

services: 
    db:
        build:
            context: ./postgres
            dockerfile: Dockerfile.dev
        volumes: 
            - db-data:/var/lib/postgresql/data
        environment:
            POSTGRES_PASSWORD: 63eaQB9wtLqmNBpg
            POSTGRES_DB: notesdb
    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

volumes:
    db-data:
        name: notes-db-dev-data

Nhìn vào định nghĩa dịch vụ api, bạn có thể thấy rằng ứng dụng chạy trên cổng 3000 bên trong container. Nó cũng yêu cầu một loạt các biến môi trường để hoạt động bình thường.

Có thể bỏ qua các volumne vì chúng chỉ cần thiết cho mục đích phát triển và cấu hình xây dựng dành riêng cho Docker. Vì vậy, hai bộ thông tin mà bạn có thể chuyển sang tệp cấu hình Kubernetes của mình hầu như không thay đổi như sau:

  • Ánh xạ cổng – bởi vì bạn sẽ phải hiển thị cùng một cổng từ vùng chứa.
  • Các biến môi trường – bởi vì các biến này sẽ giống nhau trên tất cả các môi trường (mặc dù vậy, các giá trị sẽ thay đổi).

Dịch vụ db thậm chí còn đơn giản hơn. Tất cả những gì nó có là một loạt các biến môi trường. Bạn thậm chí có thể sử dụng image postgres chính thức thay vì image tùy chỉnh.

Nhưng lý do duy nhất cho một image tùy chỉnh là nếu bạn muốn instance cơ sở dữ liệu đi kèm với bảng notes được tạo trước.

Bảng này là cần thiết cho ứng dụng. Nếu bạn nhìn vào bên trong thư mục postgres/docker-entrypoint-initdb.d, bạn sẽ thấy một tệp có tên notes.sql được sử dụng để tạo cơ sở dữ liệu trong quá trình khởi tạo.

Kế hoạch triển khai

Không giống như dự án trước mà bạn đã triển khai, dự án này sẽ phức tạp hơn một chút.

Trong dự án này, bạn sẽ không tạo một mà là ba instance của Notes API. Ba instance này sẽ được hiển thị bên ngoài cụm bằng cách sử dụng dịch vụ LoadBalancer.

Mô hình triển khai ứng dụng đa container trong Kubernetes

Ngoài ba instance này, sẽ có một instance khác của hệ thống cơ sở dữ liệu PostgreSQL. Cả ba instance của ứng dụng Notes API sẽ giao tiếp với instance cơ sở dữ liệu này bằng dịch vụ ClusterIP.

Dịch vụ ClusterIP là một loại khác của dịch vụ Kubernetes hiển thị một ứng dụng trong cụm của bạn. Điều đó có nghĩa là không có lưu lượng truy cập bên ngoài nào có thể tiếp cận ứng dụng bằng dịch vụ ClusterIP.

Dịch vụ ClusterIP trong Kubernetes

Trong dự án này, cơ sở dữ liệu chỉ được truy cập bởi Notes API, vì vậy việc hiển thị dịch vụ cơ sở dữ liệu trong cụm là một lựa chọn lý tưởng.

Tôi đã đề cập trong phần trước rằng bạn không nên tạo pod trực tiếp. Vì vậy, trong dự án này, bạn sẽ sử dụng Deployment thay vì Pod.

Replication Controllers, Replica Sets và Deployments

Theo tài liệu Kubernetes –

“Trong Kubernetes, controller là các vòng lặp điều khiển theo dõi trạng thái của cụm của bạn, sau đó thực hiện hoặc yêu cầu thay đổi nếu cần. Mỗi controller cố gắng di chuyển trạng thái cụm hiện tại gần với trạng thái mong muốn. Vòng lặp điều khiển là một vòng lặp không kết thúc điều chỉnh trạng thái của một hệ thống. “

ReplicationController cho phép bạn dễ dàng tạo nhiều bản sao rất dễ dàng. Khi số lượng bản sao mong muốn được tạo, controller sẽ đảm bảo rằng trạng thái vẫn như vậy.

Nếu sau một thời gian, bạn quyết định giảm số lượng bản sao, thì ReplicationController sẽ thực hiện các hành động ngay lập tức và loại bỏ các pod thừa.

Ngược lại, nếu số lượng bản sao thấp hơn những gì bạn muốn (có thể một số pod bị lỗi) thì ReplicationController sẽ tạo ra những bản sao mới để phù hợp với trạng thái mong muốn.

Nghe có vẻ hữu ích đối với bạn, nhưng ReplicationController hiện nay không phải là cách được khuyến nghị để tạo bản sao. Một API mới hơn được gọi là ReplicaSet đã thay thế nó.

Ngoài thực tế là ReplicaSet có thể cung cấp cho bạn nhiều tùy chọn hơn, cả ReplicationControllerReplicaSet ít nhiều đều giống nhau.

Có nhiều tùy chọn hơn là tốt nhưng điều tốt hơn nữa là có sự linh hoạt hơn trong việc triển khai và quay lại các bản cập nhật. Đây là lúc một API Kubernetes khác có tên là Deployment xuất hiện.

Deployment giống như một phần mở rộng cho ReplicaSet vốn đã rất tuyệt. Deployment không chỉ cho phép bạn tạo bản sao ngay lập tức mà còn cho phép bạn phát hành bản cập nhật hoặc quay lại chức năng trước đó chỉ bằng một hoặc hai lệnh kubectl.

ReplicationController ReplicaSet Deployment
Cho phép tạo nhiều pod một cách dễ dàng Cho phép tạo nhiều pod một cách dễ dàng Cho phép tạo nhiều pod một cách dễ dàng
Phương pháp sao chép ban đầu trong Kubernetes Có nhiều tùy chọn linh hoạt hơn Mở rộng ReplicaSet với việc triển khai và khôi phục bản cập nhật dễ dàng

Trong dự án này, bạn sẽ sử dụng Deployment để duy trì các phiên bản ứng dụng.

Tạo Deployment đầu tiên của bạn

Hãy bắt đầu bằng cách viết tệp cấu hình để triển khai Notes API. Tạo thư mục k8s bên trong thư mục dự án notes-api.

Bên trong thư mục đó, hãy tạo một tập tin có tên api-deployment.yaml và đặt nội dung sau vào đó:

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

Trong tập này, các trường apiVersion, kind, metadataspec phục vụ mục đích tương tự như các dự án trước đó. Những thay đổi đáng chú ý trong tệp này so với tệp cuối cùng như sau:

  • Để tạo một Pod, bắt buộc apiVersionv1. Nhưng để tạo Deployment, phiên bản bắt buộc là apps/v1. Các phiên bản API Kubernetes đôi khi có thể hơi khó hiểu, nhưng khi bạn tiếp tục làm việc với Kubernetes, bạn sẽ hiểu được chúng. Ngoài ra, bạn có thể tham khảo các tài liệu chính thức ví dụ các tệp YAML để tham khảo. Đại loại là Deployment khá dễ hiểu.
  • spec.replicas xác định số lượng bản sao đang chạy. Đặt giá trị này thành 3 nghĩa là bạn cho Kubernetes biết rằng bạn muốn ba phiên bản ứng dụng của mình luôn chạy.
  • spec.selector là nơi bạn cho Deployment biết cần kiểm soát nhóm nào. Tôi đã đề cập rằng Deployment là một phần mở rộng cho ReplicaSet và có thể kiểm soát một tập hợp các đối tượng Kubernetes. Đặt selector.matchLabels thành component: api có nghĩa là Deployment sẽ kiểm soát các nhóm có nhãn component: api. Dòng này cho Kubernetes biết rằng bạn muốn Deployment kiểm soát tất cả các nhóm có nhãn component: api.
  • spec.template là mẫu để cấu hình nhóm. Nó gần giống với tệp cấu hình trước đó.

Bây giờ hãy chạy lệnh sau:

kubectl apply -f api-deployment.yaml

# deployment.apps/api-deployment created

Để đảm bảo rằng Deployment đã được tạo, hãy thực hiện lệnh sau:

kubectl get deployment

# NAME             READY   UP-TO-DATE   AVAILABLE   AGE
# api-deployment   0/3     3            0           2m7s

Nếu bạn nhìn vào cột READY, bạn sẽ thấy 0/3. Điều này có nghĩa là các pod chưa được tạo. Chờ một vài phút và thử lại một lần nữa.

kubectl get deployment

# NAME             READY   UP-TO-DATE   AVAILABLE   AGE
# api-deployment   0/3     3            0           28m

Như bạn có thể thấy, tôi đã đợi gần nửa giờ mà vẫn chưa có pod nào sẵn sàng. Bản thân API chỉ là vài trăm kilobyte. Việc triển khai ở kích thước này sẽ không mất nhiều thời gian. Có nghĩa là có một vấn đề và chúng ta phải khắc phục điều đó.

Kiểm tra Kubernetes Resources

Trước khi bạn có thể giải quyết một vấn đề, trước tiên bạn phải tìm ra nguồn gốc. Một điểm khởi đầu tốt là lệnh get.

Bạn đã biết lệnh get in một bảng chứa thông tin quan trọng về một hoặc nhiều tài nguyên Kubernetes. Cú pháp chung của lệnh như sau:

kubectl get <resource type> <resource name>

Để chạy lệnh get trên api-deployment của bạn, hãy thực thi dòng mã sau trong thiết bị đầu cuối của bạn:

kubectl get deployment api-deployment

# NAME             READY   UP-TO-DATE   AVAILABLE   AGE
# api-deployment   0/3     3            0           15m

Bạn có thể bỏ qua tên api-deployment để nhận danh sách tất cả các deployment có sẵn. Bạn cũng có thể chạy lệnh get trên tệp cấu hình.

Nếu bạn muốn nhận thông tin về các deployment được mô tả trong tệp api-deployment.yaml, lệnh sẽ như sau:

kubectl get -f api-deployment

# NAME             READY   UP-TO-DATE   AVAILABLE   AGE
# api-deployment   0/3     3            0           18m

Theo mặc định, lệnh get hiển thị một lượng rất nhỏ thông tin. Bạn có thể tận dụng nó nhiều hơn bằng cách sử dụng tùy chọn -o.

Tùy chọn -o thiết lập định dạng đầu ra cho các lệnh get. Bạn có thể sử dụng  định dạng đầu ra wide để xem thêm chi tiết.

kubectl get -f api-deployment.yaml

# NAME             READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES               SELECTOR
# api-deployment   0/3     3            0           19m   api          fhsinchy/notes-api   component=api

Như bạn có thể thấy, bây giờ danh sách chứa nhiều thông tin hơn trước. Bạn có thể tìm hiểu về các tùy chọn cho lệnh get từ các tài liệu chính thức .

Thành thật mà nói, việc chạy get trên Deployment không tạo ra bất cứ điều gì thú vị. Trong những trường hợp như vậy, bạn phải xuống các tài nguyên cấp thấp hơn.

Hãy xem danh sách pod và xem liệu bạn có thể tìm thấy điều gì đó thú vị ở đó không:

kubectl get pod

# NAME                             READY   STATUS             RESTARTS   AGE
# api-deployment-d59f9c884-88j45   0/1     CrashLoopBackOff   10         30m
# api-deployment-d59f9c884-96hfr   0/1     CrashLoopBackOff   10         30m
# api-deployment-d59f9c884-pzdxg   0/1     CrashLoopBackOff   10         30m

Tất cả các pod có thông tin cột STATUSCrashLoopBackOff. Trước đây bạn chỉ thấy trạng thái ContainerCreatingRunning. Bạn cũng có thể thấy Error ở vị trí của CrashLoopBackOff.

Nhìn vào cột RESTARTS, bạn có thể thấy rằng các pod đã được khởi động lại 10 lần. Điều này có nghĩa là vì một số lý do mà các pod không khởi động được.

Bây giờ để có cái nhìn chi tiết hơn về một trong các pod, bạn có thể sử dụng lệnh khác được gọi là describe. Nó rất giống lệnh get. Cú pháp chung của lệnh như sau:

kubectl describe <resource type> <resource name>

Để biết chi tiết về pod api-deployment-d59f9c884-88j45, bạn có thể thực hiện lệnh sau:

kubectl describe pod api-deployment-d59f9c884-88j45

# Name:         api-deployment-d59f9c884-88j45
# Namespace:    default
# Priority:     0
# Node:         minikube/172.28.80.217
# Start Time:   Sun, 09 Aug 2020 16:01:28 +0600
# Labels:       component=api
#               pod-template-hash=d59f9c884
# Annotations:  <none>
# Status:       Running
# IP:           172.17.0.4
# IPs:
#   IP:           172.17.0.4
# Controlled By:  ReplicaSet/api-deployment-d59f9c884
# Containers:
#  api:
#     Container ID:   docker://d2bc15bda9bf4e6d08f7ca8ff5d3c8593655f5f398cf8bdd18b71da8807930c1
#     Image:          fhsinchy/notes-api
#     Image ID:       docker-pullable://fhsinchy/notes-api@sha256:4c715c7ce3ad3693c002fad5e7e7b70d5c20794a15dbfa27945376af3f3bb78c
#     Port:           3000/TCP
#     Host Port:      0/TCP
#     State:          Waiting
#       Reason:       CrashLoopBackOff
#     Last State:     Terminated
#       Reason:       Error
#       Exit Code:    1
#       Started:      Sun, 09 Aug 2020 16:13:12 +0600
#       Finished:     Sun, 09 Aug 2020 16:13:12 +0600
#     Ready:          False
#     Restart Count:  10
#     Environment:    <none>
#     Mounts:
#      /var/run/secrets/kubernetes.io/serviceaccount from default-token-gqfr4 (ro)
# Conditions:
#   Type              Status
#   Initialized       True
#   Ready             False
#   ContainersReady   False
#   PodScheduled      True
# Volumes:
#   default-token-gqfr4:
#     Type:        Secret (a volume populated by a Secret)
#     SecretName:  default-token-gqfr4
#     Optional:    false
# QoS Class:       BestEffort
# Node-Selectors:  <none>
# Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
#                  node.kubernetes.io/unreachable:NoExecute for 300s
# Events:
#   Type     Reason     Age                         From               Message
#   ----     ------     ----                        ----               -------
#   Normal   Scheduled  <unknown>                   default-scheduler  Successfully assigned default/api-deployment-d59f9c884-88j45 to minikube
#   Normal   Pulled     2m40s (x4 over 3m47s)       kubelet, minikube  Successfully pulled image "fhsinchy/notes-api"
#   Normal   Created    2m40s (x4 over 3m47s)       kubelet, minikube  Created container api
#   Normal   Started    2m40s (x4 over 3m47s)       kubelet, minikube  Started container api
#   Normal   Pulling    107s (x5 over 3m56s)        kubelet, minikube  Pulling image "fhsinchy/notes-api"
#   Warning  BackOff    <invalid> (x44 over 3m32s)  kubelet, minikube  Back-off restarting failed container

Phần thú vị nhất trong toàn bộ kết quả trả về là phần Events. Hãy xem kỹ hơn:

Events:
  Type     Reason     Age                         From               Message
  ----     ------     ----                        ----               -------
  Normal   Scheduled  <unknown>                   default-scheduler  Successfully assigned default/api-deployment-d59f9c884-88j45 to minikube
  Normal   Pulled     2m40s (x4 over 3m47s)       kubelet, minikube  Successfully pulled image "fhsinchy/notes-api"
  Normal   Created    2m40s (x4 over 3m47s)       kubelet, minikube  Created container api
  Normal   Started    2m40s (x4 over 3m47s)       kubelet, minikube  Started container api
  Normal   Pulling    107s (x5 over 3m56s)        kubelet, minikube  Pulling image "fhsinchy/notes-api"
  Warning  BackOff    <invalid> (x44 over 3m32s)  kubelet, minikube  Back-off restarting failed container

Từ những sự kiện này, bạn có thể thấy rằng image đã được pull thành công. Container cũng được tạo, nhưng rõ ràng thông điệp Back-off restarting failed container có nghĩa là container không khởi động được.

Lệnh describe rất giống với lệnh get và có cùng một loại tùy chọn.

Bạn có thể bỏ qua tên api-deployment-d59f9c884-88j45 để nhận thông tin về tất cả các pod có sẵn. Hoặc bạn cũng có thể sử dụng tùy chọn -f để truyền tệp cấu hình vào lệnh. Truy cập tài liệu chính thức để tìm hiểu thêm.

Bây giờ bạn biết rằng có điều gì đó không ổn với vùng chứa, bạn phải đi xuống cấp độ vùng chứa và xem điều gì đang xảy ra ở đó.

Sổ tay Kubernetes: Triển khai ứng dụng đa container (p2)
Ở phần 2 này, chúng ta sẽ tìm cách khắc phục những lỗi xảy ra khi triển khai. Chúng ta cũng tìm hiểu thêm về Persistent Volume, Persistent Volume Claim.

Bài viết gốc.

Trả lời

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 *