- vừa được xem lúc

Hành trình triển khai một ứng dụng Full-stack với Kubernetes (Minikube) cho người mới

0 0 1

Người đăng: Nguyen Duy Hieu

Theo Viblo Asia

1. Giới thiệu

Lời mở đầu

Trong thế giới phát triển phần mềm hiện đại, việc quản lý và triển khai các ứng dụng container hóa ngày càng trở nên quan trọng. Kubernetes nổi lên mạnh mẽ, giúp tự động hóa, mở rộng quy mô và quản lý các ứng dụng này một cách hiệu quả. Tuy nhiên, việc tiếp cận Kubernetes ban đầu có thể khá choáng ngợp với nhiều khái niệm và cấu hình phức tạp.

Bài viết này được tạo ra với mong muốn chia sẻ lại hành trình cá nhân của mình trong việc tìm hiểu và từng bước triển khai một ứng dụng web full-stack – bao gồm Frontend Next, Backend Nest và Database PostgreSQL – lên môi trường Kubernetes local sử dụng Minikube. Hy vọng rằng, qua những trải nghiệm thực tế này, các bạn, đặc biệt là những người mới bắt đầu, sẽ có được cái nhìn trực quan và những bước đi cụ thể để tự tin hơn trên con đường chinh phục Kubernetes.

Tổng quan

Ứng dụng sẽ triển khai vào Kubernetes trong bài viết này là một dự án web khá điển hình, bao gồm ba thành phần chính:

  • Frontend: Được xây dựng bằng Next.

  • Backend: Một API service được phát triển với Nest

  • Database: Sử dụng PostgreSQL

Mỗi thành phần này sẽ được đóng gói thành Docker image riêng biệt và sau đó được triển khai, kết nối với nhau bên trong cụm Minikube.

2. Các thành phần cơ bản

Để bắt đầu hành trình với Kubernetes (thường gọi tắt là K8s), chúng ta cần làm quen với một vài ý tưởng và thành phần cốt lõi. Trong bài này, thay vì trình bày các định nghĩa phức tạp và đôi khi khó hiểu, mình sẽ trình bày dưới dạng "ví dụ trực quan" để dễ dàng hình dung mô hình của công cụ tuyệt vời này. Hãy tưởng tượng Kubernetes như một thành phố thu nhỏ, nơi các ứng dụng của bạn sẽ hoạt động.

1. Kiến trúc cơ bản của một "thành phố K8S"

Mình sẽ mô tả kiến trúc trong 1 cluster k8s

Control Plane (Bộ não của phành phố)

Đưa ra mọi quyết định hoạt động của "thành phố k8s". Các thành phần, tiến trình của Control Plane chạy trên Master Nodes.

Control Plane là một khái niệm trừu tượng. Nó được thể hiện bằng 4 thành phần chính sau:

  • etcd: Lưu trữ các key-value, duy trì state của cluster
  • kube-apiserver: Cổng chính của Control Plane, các thành phần trong và ngoài cluster giao tiếp với nhau thông qua cổng này. Nó như 1 quầy lễ tân trung tâm, nơi mọi request và thông tin đều phải đi qua
  • kube-controller-manager: Nơi chạy các controller, theo dõi trạng thái và thực hiện thao tác để đưa trạng thái của cluster về trạng thái mong muốn. Nó như 1 đội ngũ chuyên biệt, mỗi người theo dõi 1 khía cạnh của "thành phố", như là Node, Replication, Endpoints, ... và tự sửa chữa hoặc điều chỉnh khi cần.
  • kube-scheduler: Phân công Pod mới lên các Node phù hợp. Giống 1 người điều phối, dựa vào yêu cầu tài nguyên của Pod, các ràng buộc để quyết định xem Pod nào chạy trong Node nào.

Worker Nodes (Khu cư dân)

Là các máy chủ vật lý (hoặc ảo) nhận lệnh từ Control Plane và chạy các ứng dụng trong container

Các "công trình" trong "thành phố K8S"

Node (Tòa nhà)

Đơn giản là một máy chủ (Worker Node) trong cụm Kubernetes của bạn. Đây là nơi các ứng dụng thực sự được triển khai và chạy.

Ví dụ: Mỗi Worker Node là một tòa nhà trong thành phố, có thể chứa nhiều căn hộ.

Pod (Căn hộ)

Đây là đơn vị nhỏ nhất và cơ bản nhất có thể được triển khai trong Kubernetes. Hãy coi nó như một "căn hộ" trong tòa nhà Node.

Một Pod có thể chứa một hoặc nhiều Container. Các container trong cùng một Pod chia sẻ chung tài nguyên mạng (cùng địa chỉ IP) và có thể chia sẻ không gian lưu trữ.

Ví dụ: Ứng dụng Next.js được chạy trong một phòng (container) nằm trong căn hộ frontend-pod

Container (Căn phòng)

Đây chính là nơi ứng dụng hoặc service của bạn (ví dụ: code Next.js, NestJS, hoặc database PostgreSQL) được đóng gói cùng với tất cả thư viện và phụ thuộc của nó.

Ví dụ: Bên trong frontend-pod, có một "căn phòng" là nextjs-container chứa ứng dụng Next.js

Deployment (Ban quản lý chung cư)

Dùng để quản lý các ứng dụng stateless (không lưu trữ trạng thái dữ liệu lâu dài trực tiếp trong nó), ví dụ như frontend và backend API của bạn.

Bạn nói với Deployment: "Tôi muốn chạy 3 bản sao của ứng dụng frontend". Deployment sẽ đảm bảo luôn có 3 Pods (căn hộ) giống hệt nhau chạy ứng dụng đó. Nếu một Pod bị lỗi, Deployment sẽ tự động tạo một Pod mới để thay thế. Nó cũng giúp bạn cập nhật ứng dụng lên phiên bản mới một cách từ từ.

Ví dụ: Ban quản lý khu chung cư frontend-deployment đảm bảo luôn có đủ số lượng căn hộ frontend-pod cho cư dân.

StatefulSet (Dãy nhà được đánh số)

Dùng để quản lý các ứng dụng stateful (cần lưu trữ trạng thái dữ liệu ổn định và có định danh riêng), ví dụ như database của bạn.

Mỗi Pod do StatefulSet tạo ra sẽ có một tên cố định (ví dụ: postgres-0, postgres-1) và được gắn với một không gian lưu trữ cố định riêng.

Ví dụ: Dãy nhà phố postgres-statefulset có các nhà postgres-0, postgres-1, mỗi nhà có một mảnh đất (dữ liệu) riêng không thay đổi.

PersistentVolumeClaim (PVC) (Đơn xin cấp đất) & PersistentVolume (PV) (Mảnh đất)

Khi ứng dụng (như database) cần lưu trữ dữ liệu lâu dài, nó không lưu trực tiếp vào Pod (vì Pod có thể bị xóa). Thay vào đó:

  • PVC: Pod sẽ tạo một "đơn xin cấp đất", yêu cầu một không gian lưu trữ với dung lượng nhất định.
  • PV: Kubernetes sẽ tìm một "mảnh đất" phù hợp trong kho tài nguyên lưu trữ của cluster để đáp ứng yêu cầu đó.

Ví dụ: Căn nhà postgres-0 gửi "đơn xin" postgres-pvc để được cấp "mảnh đất" my-postgres-pv để trồng cây (lưu dữ liệu).

Service (Tổng đài)

Pods có thể bị tạo mới và có địa chỉ IP thay đổi. Vậy làm sao các thành phần khác (ví dụ: frontend gọi backend, backend gọi database) biết địa chỉ để kết nối? Đó là lúc Service ra tay!

Service cung cấp một địa chỉ IP nội bộ cố định (ClusterIP) và một tên DNS ổn định bên trong cluster. Các ứng dụng sẽ kết nối đến Service này, và Service sẽ tự động chuyển yêu cầu đến một Pod phù hợp đang chạy.

Ví dụ: Tất cả thư từ cho "dãy nhà phố postgres" đều được gửi đến "địa chỉ bưu điện postgres-service", nhân viên bưu điện sẽ tự biết chuyển đến đúng căn nhà postgres-0.

Secret (Két)

Dùng để lưu trữ các thông tin nhạy cảm như mật khẩu database, API keys, chứng chỉ TLS.

Thay vì viết thẳng vào file cấu hình hoặc code, bạn lưu chúng vào Secret, và Pod có thể truy cập một cách an toàn hơn.

Namespace (Quận)

Cho phép bạn chia một cluster Kubernetes thành nhiều "không gian làm việc" ảo, riêng biệt. Điều này hữu ích khi nhiều đội, nhiều dự án cùng sử dụng chung một cluster mà không muốn bị ảnh hưởng lẫn nhau.

Ví dụ: Thành phố K8s có thể được chia thành "Quận Dev", "Quận Test", "Quận Production".

Kubectl

Đây là công cụ dòng lệnh (Command Line Interface - CLI) chính mà bạn sẽ sử dụng để "nói chuyện" với cụm Kubernetes của mình.

Bạn dùng kubectl để triển khai ứng dụng, kiểm tra trạng thái, xem logs, và thực hiện hầu hết các tác vụ quản trị.

Ví dụ: Bạn muốn "hỏi thăm tình hình các căn hộ" (kubectl get pods) hoặc "áp dụng bản thiết kế xây thêm một khu chung cư" (kubectl apply -f deployment.yaml).

3. Minikube

Sau khi đã làm quen với các khái niệm, bước tiếp theo là có một môi trường Kubernetes thực tế để thực hành. Minikube là một công cụ tuyệt vời cho mục đích này, giúp bạn chạy một cụm Kubernetes thu nhỏ ngay trên máy local

** Lý do chọn minikube **

Nhẹ và Đơn giản: Minikube tạo ra một cụm Kubernetes chỉ với một Node duy nhất, rất phù hợp để học tập và phát triển ứng dụng cục bộ mà không đòi hỏi nhiều tài nguyên máy tính.

Trải nghiệm Kubernetes Đầy đủ: Dù nhỏ gọn, Minikube vẫn cung cấp hầu hết các tính năng cốt lõi của Kubernetes, giúp bạn làm quen với kubectl và triển khai các loại tài nguyên khác nhau.

Đa Nền Tảng: Minikube hoạt động tốt trên Windows, macOS và Linux, giúp bạn dễ dàng bắt đầu bất kể đang sử dụng hệ điều hành nào.

** Cài đặt **

Cách tốt nhất để có hướng dẫn cài đặt cập nhật và chính xác nhất cho hệ điều hành của bạn là truy cập trang tài liệu chính thức của Minikube: https://minikube.sigs.k8s.io/docs/start/

Mở Terminal để khởi chạy: minikube start

Tuy nhiên mình dùng driver của docker để làm môi trường cho node chạy luôn thì dùng lệnh minikube start --driver=docker

Khi bạn triển khai ứng dụng của mình (ví dụ: backend NestJS, frontend Next.js), Kubernetes cần có thể truy cập được các Docker image đã được build của chúng. Có một số cách để có thể nạp được image vào trong k8s

4. Triển khai một ứng dụng Full-stack

Giả sử mình có một project full-stack đã chạy được ở local, với cấu trúc như dưới

Mỗi service đều có Dockerfile

Bước 1: Tạo 1 namespace riêng

  • Tạo namespace

kubectl create namespace practice-with-k8s

  • Chuyển context mặc định sang namespace này (để tránh việc phải gắn cờ -n practice-with-k8s sau mỗi lệnh của kubectl)

kubens practice-with-k8s

Thứ tự triển khai các thành phần sẽ là:

  • Database (PostgreSQL): Vì đây là nền tảng lưu trữ dữ liệu.
  • Backend (NestJS): Sau khi có database để kết nối.
  • Frontend (Next.js): Sau khi có backend API để gọi.

Bước 2: Triển khai database (thành phần có trạng thái)

Thành phần có trạng thái là do database cần lưu trữ dữ liệu 1 cách ổn định. Mình dùng StatefulSet

Bước 2.1: Tạo Secret cho các thông tin nhạy cảm

Có nhiều cách để tạo secret, tuy nhiên mình đã có 1 file .env, do vậy mình sẽ tận dụng nó luôn bằng lệnh sau:

kubectl create secret generic todoapp-secret --from-env-file=./.env

Kiểm tra: kubectl get secrets

Mô tả (Không giải mã): kubectl describe secret postgres-secret

Bước 2.2. Tạo PersistentVolumeClaim (PVC) cho lưu trữ dữ liệu:

Mục đích: Yêu cầu một không gian lưu trữ bền vững cho dữ liệu của PostgreSQL, đảm bảo dữ liệu không bị mất khi Pod khởi động lại.

File postgres-pvc.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata: name: postgres-pvc # Tên của PVC
spec: accessModes: - ReadWriteOnce # PV được đọc-ghi resources: requests: storage: 200Mi # Dung lượng 200Mb

Áp dụng: kubectl apply -f postgres-pvc.yaml -n practice-with-k8s hoặc kubectl apply -f postgres-pvc.yaml nếu đã chuyển context

Kiểm tra: kubectl get pvc postgres-pvc

Bước 2.3. Tạo StatefulSet để chạy PostgreSQL:

Mục đích: Quản lý Pod chạy PostgreSQL, đảm bảo định danh ổn định và gắn kết với PVC đã tạo.

File postgres-statefulset.yaml:

apiVersion: apps/v1
kind: StatefulSet
metadata: name: postgres # Tên statefullSet
spec: serviceName: "postgres" # Rất quan trọng: Tên của Headless Service mà chúng ta sẽ tạo ở bước sau. # Dùng để kiểm soát domain của các Pod (ví dụ: postgres-0.postgres.your-namespace.svc.cluster.local) replicas: 1 # Chạy 1 bản sao PostgreSQL selector: matchLabels: # Nói cho k8s biết là statefulSet này quản lý pod nào app: postgres # Phải khớp với labels trong template của Pod template: metadata: labels: app: postgres # Pod này sẽ có label app=postgres spec: terminationGracePeriodSeconds: 10 containers: - name: postgres # Tên của container image: postgres:15-alpine imagePullPolicy: IfNotPresent env: - name: POSTGRES_USER # Tên biến môi trường bạn muốn dùng trong container valueFrom: # Tham chiếu đến value trong Secret secretKeyRef: name: todoapp-secret # Tên Secret key: DB_USER # Tên key trong Secret (Key này xem trong .env, do Secret trên được tạo từ .env) - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: todoapp-secret key: DB_PASS - name: POSTGRES_DB valueFrom: secretKeyRef: name: todoapp-secret key: DB_NAME ports: - name: postgresql containerPort: 5432 volumeMounts: # Gắn volume vào container - name: postgres-storage # Phải khớp với tên volume được định nghĩa bên dưới mountPath: /var/lib/postgresql/data # Nơi PostgreSQL lưu trữ dữ liệu trong container volumes: - name: postgres-storage persistentVolumeClaim: claimName: postgres-pvc # Sử dụng PVC 'postgres-pvc' mà chúng ta đã tạo ở bước trước

Áp dụng: kubectl apply -f postgres-statefulset.yaml

Kiểm tra:

kubectl get statefulset postgres

kubectl get pods -l app=postgres (Chờ Pod postgres-0 chạy và READY)

kubectl logs postgres-0 (Kiểm tra log khởi động)

Bước 2.4. Tạo Service (ClusterIP) cho PostgreSQL:

Mục đích: Tạo một địa chỉ IP nội bộ và tên DNS ổn định trong cluster để backend có thể kết nối đến PostgreSQL.

File postgres-service.yaml:

apiVersion: v1
kind: Service
metadata: name: postgres # Tên của Service. # Quan trọng: Tên này NÊN khớp với giá trị `serviceName` của statefulset
spec: selector: app: postgres # Phải khớp với labels của Pods do StatefulSet tạo ra ports: - protocol: TCP port: 5432 # Cổng mà Service này sẽ lắng nghe. targetPort: 5432 # Cổng trên Pod (containerPort)

Áp dụng: kubectl apply -f postgres-service.yaml

Kiểm tra: kubectl get service postgres

Bước 3. Triển khai Backend NestJS (Ứng dụng stateless)

Sau khi "trái tim" PostgreSQL đã đập, chúng ta sẽ xây dựng "bộ não" xử lý logic: backend NestJS. Vì backend thường là stateless, mình sẽ dùng Deployment để quản lý.

Bước 3.1. Tạo Deployment cho Backend NestJS:

Mục đích: Chạy và quản lý các Pods của ứng dụng NestJS, đảm bảo chúng luôn sẵn sàng và có thể cập nhật.

File backend-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata: name: todoapp-backend # Tên của Deployment labels: app: todoapp-backend
spec: replicas: 1 # Số lượng Pods selector: matchLabels: app: todoapp-backend # Phải khớp với labels của Pod template template: metadata: labels: app: todoapp-backend spec: containers: - name: todoapp-backend-container # Tên container image: todoapp-backend:latest # Tên image imagePullPolicy: Never env: - name: DB_HOST # Tên biến môi trường mà dùng trong code backend value: "postgres" # Tên Service của PostgreSQL mà chúng ta đã tạo - name: DB_PORT value: "5432" # Cổng của Service PostgreSQL - name: DB_USER valueFrom: secretKeyRef: name: todoapp-secret key: DB_USER - name: DB_PASS valueFrom: secretKeyRef: name: todoapp-secret key: DB_PASS - name: DB_NAME valueFrom: secretKeyRef: name: todoapp-secret key: DB_NAME

Áp dụng: kubectl apply -f backend-deployment.yaml

Bước 3.2. Tạo Service (ClusterIP) cho Backend NestJS:

Mục đích: Tạo một địa chỉ IP nội bộ và tên DNS ổn định để frontend Next.js có thể giao tiếp với backend.

File backend-service.yaml:

apiVersion: v1
kind: Service
metadata: name: todoapp-backend-service # Tên của Service cho backend
spec: selector: app: todoapp-backend # QUAN TRỌNG: Phải khớp với labels của Pods backend NestJS ports: - name: http protocol: TCP port: 3001 # Cổng mà Service này sẽ lắng nghe targetPort: 3000 # Cổng trên Pod backend NestJS (containerPort) type: ClusterIP

Áp dụng: kubectl apply -f backend-service.yaml

Bước 4: Triển khai Frontend Next.js (Ứng dụng stateless, giao diện người dùng)

Cuối cùng, mình sẽ đẩy giao diện người dùng của ứng dụng bằng cách triển khai frontend Next.js. Tương tự backend, frontend cũng là stateless và sẽ được quản lý bởi Deployment.

Bước 4.1. Tạo Deployment cho Frontend Next.js:

Mục đích: Chạy và quản lý các Pods của ứng dụng Next.js.

File web-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata: name: todoapp-web labels: app: todoapp-web
spec: replicas: 1 selector: matchLabels: app: todoapp-web template: metadata: labels: app: todoapp-web spec: containers: - name: todoapp-web image: todoapp-web:latest imagePullPolicy: Never ports: - containerPort: 3000 # Cổng Next.js app lắng nghe env: - name: NEXT_PUBLIC_API_URL value: "http://todoapp-backend-service.practice-with-k8s.svc.cluster.local:3001"

Áp dụng: kubectl apply -f web-deployment.yaml

Bước 4.2. Tạo Service (NodePort hoặc LoadBalancer) cho Frontend Next.js:

Mục đích: Đưa ứng dụng frontend ra ngoài để người dùng có thể truy cập từ trình duyệt.

File web-service.yaml:

apiVersion: v1
kind: Service
metadata: name: todoapp-web-service
spec: type: NodePort # Loại Service để expose ra ngoài Node selector: app: todoapp-web ports: - name: http protocol: TCP port: 80 # Cổng mà Service này sẽ lắng nghe BÊN TRONG CLUSTER targetPort: 3000 # Cổng trên Pod frontend Next.js (containerPort)

Áp dụng: kubectl apply -f web-service.yaml

Lấy địa chỉ ip của minikube: minikube ip. Ví dụ 192.168.49.2

Lấy port của service todoapp-web: kubectl get service todoapp-web-service. Tại cột PORT(S), sẽ có 80:<NodePort>/TCP. Ví dụ NodePort là 31116

Để truy cập web, nhập 192.168.49.2:31116 trên browser -> web của chúng ta hoạt động, vậy là đã thành công các bước cơ bản để deploy 1 project trên k8s tại máy local.

5. Tổng kết

Qua hành trình này, chúng ta đã cùng nhau đưa thành công một ứng dụng full-stack (Next.js, NestJS, PostgreSQL) lên Minikube, đặt những viên gạch vững chắc đầu tiên để bạn tiếp tục khám phá thế giới Kubernetes rộng lớn và đầy tiềm năng. Chúc bạn thành công trên con đường học tập phía trước! Nếu có gì thắc mắc về lý thuyết, hoặc phần thực hành với k8s,, đừng ngần ngại đặt câu hỏi ở phía dưới nhé!

Bình luận

Bài viết tương tự

- vừa được xem lúc

Phần 1: Giới thiệu về Kubernetes

Kubernetes là gì. Trang chủ: https://kubernetes.io/. Ai cần Kubernetes.

0 0 108

- vừa được xem lúc

Thực hành K8S trên Google Cloud

Kubernetes (K8S) trở nên quá phổ biến ở thời điểm hiện tại, ai cũng nói về nó. Trong bài hôm nay mình sẽ không đi quá nhiều vào các định nghĩa, mà đi thẳng vào thực tế để mọi người dễ hình dung.

0 0 49

- vừa được xem lúc

Kubernetes best practices - Liveness và Readiness Health checks

Mở đầu. Kubernetes cung cấp cho bạn một framework để chạy các hệ phân tán một cách mạnh mẽ.

0 0 62

- vừa được xem lúc

Kubernetes - deployment.yaml explained

Trong bài trước, mình có giới thiệu chạy các câu lệnh K8S bằng Command Line. Để tạo 1 deloyment đơn giản chỉ cần chạy lệnh.

0 0 96

- vừa được xem lúc

Tìm hiểu cơ bản về Kubernetes - K8s (Part 2): Minikube

Lời mở đầu. .

0 0 53

- vừa được xem lúc

ETCD - Bộ não của Kubernetes và cách cài đặt cụm ETCD Cluster (High Availability)

Hello anh em, sau vài ngày nghiên cứu đọc lại liệu cũng như cài cắm thủ công đủ thể loại, với vô số lỗi fail thì mình cũng cài đặt thành công cụm etcd cluster một cách thủ công. Trước giờ chuyên tạo c

0 0 53