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

Deploy server lưu trữ và resize ảnh như Viblo trên Kubernetes

0 0 10

Người đăng: Mai Trung Đức

Theo Viblo Asia

Hello các bạn, vẫn là mình đây 👋👋👋. Chúc các bạn kì nghỉ lễ 30/4-1/5 vui vẻ 🇻🇳🇻🇳🇻🇳

Hôm nay ta sẽ cùng nhau làm một bài nhiều não tí nhé 😂. Khá dài đấy các bạn kiếm cốc nước uống và lên thuyền cùng mình nha ⛵️⛵️⛵️

Mở đầu

Không biết các bạn có như mình không, vô trang web nào thấy hay hay là phải inspect liền xem tech stack rồi network http/websocket các thứ xem nó như thế nào, nhất là những cái mà mình hay dùng hàng ngày như Facebook, Youtube, Github, Tiktok,... Và cả Viblo cũng vậy

Các đây chừng 1 tháng thì team Viblo có publish bài xây dựng server ảnh mình thấy khá là thú vị, vì nó giúp mình hiểu hơn cách mà người ta xây dựng sản phẩm mà mình vẫn sử dụng thường xuyên (để viết blog). Mình cũng đã thử vọc vạch qua.

Và hôm nay ta sẽ cùng nhau deploy một server ảnh giống với những gì mà Viblo làm nha 😎😎 Tất nhiên có thể bên dưới server của họ phức tạp, nhưng ta sẽ làm theo những gì mà ta hiểu được từ bài viết blog của họ nhé

Server ảnh của Viblo

Mình sẽ lấy luôn ảnh từ bài viết của Viblo nhé:

Flow cơ bản như sau:

  • khi ta đọc blog trên viblo, và đến đoạn xem hình ảnh thì từ trình duyệt (client) sẽ tạo 1 request lên để lấy ảnh
  • request đầu tiên đi qua Nginx, sau đó tới Server ảnh (image server). Server ảnh sẽ gọi tới Object Storage Bucket (AWS S3) để lấy ảnh về và thực hiện resize tối ưu ảnh trước khi gửi trả về lại client
  • Về server ảnh thì Viblo dùng 1 cái Opensource tên là Imaginary viết bằng Go bên dưới gọi tới libvips (C). Có Docker image dễ deploy, cung cấp API cho nhiều thao tác với ảnh (resize, crop, rotate,....)
  • Ở phía Nginx thì Viblo cũng cache lại ảnh khi nhận được từ Server ảnh để giảm tải cho server ảnh. Vì thường thao tác xử lý ảnh tốn resource (RAM/CPU)
  • Nếu đường dẫn ảnh có dạng "full", ví dụ: https://i.2kvn.com/img/vib-2024-98eef5e0-42cd-491d-9547-8dd3673ef843.png. Thì sẽ trả về ảnh gốc luôn mà không gọi qua server ảnh nữa

Kiến trúc trên được Viblo deploy với Kubernetes (K8S), có autoscale

Những gì ta sẽ làm

Những gì Viblo viết trong bài blog của họ thì ta cũng sẽ làm như vậy 😉 bài này ta cũng sẽ dùng K8S 😉. Họ nói rằng của họ có autoscale, bài này ta cũng có autoscale với K8S HorizontalPodAutoscaling. Họ có nginx cache, ta cũng sẽ cache ảnh với nginx 💪💪.

Ở bài này ta sẽ build một app demo cho phép upload ảnh lên server, và sau đó xem ảnh vừa tải lên:

Viblo có Progessive image loading, chúng ta cũng có😉 (cái mà ban đầu load ảnh nhẹ mờ mờ, sau đó load ảnh nét hơn á 😉)

Cụ thể các bước ta sẽ làm trong bài này gồm:

  • chạy test local, vọc vạch để hiểu ý tưởng
  • setup/build cho production
  • đẩy image lên dockerhub
  • deploy lên k8s
  • cấu hình autoscale, caching

Nãy giờ dài dòng quá, ta vô phần chính thôi 🚀🚀

Clone source

Đầu tiên các bạn clone source code cho bài này ở đây nhé. Nhánh master nhé

Sau khi clone về thì ta có như sau:

Ở đây ta có các phần như sau:

  • Frontend: Giao diện, tí nữa khi deploy ta cũng sẽ đặt Nginx ở đây
  • Backend: chính là Object Storage Bucket
  • Server resize ảnh thì ta sẽ dùng Imgproxy - server ảnh cũng code bằng Go và dùng libvips, lí do mình không dùng Imaginary vì mình bị lỗi crash liên tục khi setup nó 🥲🥲 (tính năng thì Imgproxy thậm chí còn nhiều hơn Imaginary nữa đó 😜)

Setup local

Bài này yêu cầu máy các bạn phải có Docker và đã có tài khoản Dockerhub để lát ta push image lên nhé

Ta chạy thử lên coi xem nó như nào nhé. Yêu cầu là máy các bạn phải có Docker.

Đầu tiên là ở từng folder frontendbackend ta install dependencies cho chúng nha (2 folder chạy y hệt 1 command):

# Mac và Linux
docker run --rm -v $(pwd):/app -w /app node:20-alpine npm install # Dành cho Windows:
# Git bash
docker run --rm -v "/$(pwd)":/app -w //app node:20-alpine npm install # PowerShell
docker run --rm -v "$(pwd):/app" -w /app node:20-alpine npm install # Command Prompt
docker run --rm -v "%cd%:/app" -w /app node:20-alpine npm install

Ở root folder mình đã để sẵn file docker-compose.local.yml rồi các bạn chỉ việc up lên là xong:

docker compose -f docker-compose.local.yml up -d

trong file docker-compose.local.yml mình có chạy cả service imgproxy nó chính là server để resize ảnh

Sau khi chạy lên ta có thể truy cập từ trình duyệt ở địa chỉ http://localhost:5173:

Sau đó ta test thử từ trình duyệt xem backend lên chưa nha. Ta mở địa chỉ http://localhost:3000/api:

âu cây các thành phần lên hết rồi ta quay lại frontend và test upload ảnh nha (Các bạn có thể chọn nhiều ảnh cũng được:

Thấy thông báo như trên là upload ảnh thành công rồi nhé 💪

Vọc vạch

Ta cùng nhau xem tổng quan hệ thống hoạt động như nào nha.

Bắt đầu từ frontend trước, các bạn mở file vite.config.ts ta sẽ thấy mình cấu hình proxy (dành cho việc dev ở local):

const createProxyConfig = (width: number) => ({ target: "http://imgproxy:8080", changeOrigin: true, rewrite: (path: string) => { const filename = path.split("/").pop(); return `/insecure/resize:fit:${width}:0:no:0/plain/http://backend:3000/images/${filename}`; },
}); export default defineConfig({ plugins: [react()], server: { proxy: { "/api": "http://backend:3000", "/images/full": { target: "http://backend:3000", changeOrigin: true, rewrite: (path) => path.replace(/^\/images\/full/, "/images"), }, "/images/tiny": createProxyConfig(20), "/images": createProxyConfig(825), }, },
});

Ở trên ta có:

  • Khi frontend (FE) call vào /api... thì sẽ proxy thẳng vào backend đang chạy ở môi trường ngoài cổng 3000. Chú ý rằng tất cả các service đang chạy chung 1 network nên ta có thể proxy thẳng container->container thông qua service name
  • tiếp theo với image mà có /full thì sẽ bỏ qua server resize ảnh mà trả thẳng về ảnh gốc luôn
  • cuối cùng ta có 2 đường dẫn /images/tiny để show ảnh mờ, và /images để show ảnh resize. Request tới cả 2 path này sẽ đi qua Imgproxy để resize
  • ta có function createProxyConfig để rewrite lại url khi proxy tới server resize cho phù hợp với định dạng mà Imgproxy yêu cầu (Các bạn có thể xem thêm ở đây)

những gì ta có ở createProxyConfig về cơ bản như sau:

từ FE nếu load ảnh /images/tiny/myimage.png sẽ map thành http://imgproxy:8080/insecure/resize:fit:20:0:no:0/plain/http://backend:3000/images/myimage.png

Với http://imgproxy:8080 là server resize ảnh Imgproxy. và http://backend:3000 là Object storage bucket (backend nơi lưu ảnh gốc)

Cái URL kia bản chất là ta gọi vào server resize ảnh, truyền các tham số theo sau:

  • width=20, height=0 (auto height). (Xem thêm về tham số ở đây)
  • đường dẫn để lấy ảnh gốc về resize là http://backend:3000/images/myimage.png

con số 20 và 825 là mình debug biết được Viblo họ dùng tham số đó và ở đây ta dùng y hệt nha

Với những tham số kia thì server resize (Imgproxy) sẽ download ảnh gốc về thực hiện resize và trả lại cho Frontend

Vọc sang tới backend (hay ở bài này ta cũng gọi là Object storage bucket), thì nó là một app NodeJS rất đơn giản 😂:

  • ảnh gốc lưu ở public/images, được setup là static assets
  • 3 API để lưu ảnh, upload ảnh, xoá ảnh thì ở routes/index.js
  • backend không có DB gì cả, vì ta sẽ làm ở mức đơn giản bài này thôi, ảnh được lưu theo tên có gắn timestamp, và mỗi khi call API thì mình list files ảnh ra và trả lại FE thôi

Nếu các bạn để ý ở file docker-compose.local.yml thì ta chỉ map port cho frontend, còn lại 2 cái kia sẽ chỉ được gọi bằng cách proxy từ frontend vào

Flow cho toàn bộ kiến trúc của chúng ta trông như sau:

Giờ ta quay ra trình duyệt debug tí network xem nó như nào nhé, cơ mà trước đó thì upload cho mình ít ảnh để test nha.

Đầu tiên thì như các bạn thấy là ta sẽ load ảnh mờ /tiny:

Và như ta đã khai báo proxy là request dạng này thì sẽ đưa tới server resize ảnh, do vậy ta có thể check header trả về từ server là Imgproxy và có X-request-id:

Tương tự khi ta cuộn trang xuống dưới thì sẽ bắt đầu load ảnh chính, ảnh này cũng sẽ đưa tới server resize. Phần này các bạn tự check nhé

Khi chúng ta bấm vào xem detail ảnh thì request có dạng /full, và cái này sẽ gọi thẳng tới Object storage server - ở đây chính là backend và trả ra ảnh gốc:

Ở phần header ta sẽ thấy nó tới từ Express server:

Như vậy là ta đã xong phần test với development mode rồi nha 😁. Tiếp theo ta sẽ cấu hình cho production, và khi oke thì sẽ build image đẩy lên Dockerhub và deploy trên Kubernetes nhé 💪

Setup cho production

Build image

Về cơ bản ở bài này khi setup cho production thì ý tưởng cũng rất giống như khi ta chạy local, chỉ với frontend sau khi build cho production thì ta chạy với nginx, và nginx cũng làm nhiệm vụ proxy request + caching, tiện cả luôn 😉

Về phần này thì để cho tiện mình đã làm sẵn 2 file Dockerfile cho backendfrontend sẵn sàng cho production rồi. Các bạn có thể mở từng file trong từng folder ra và xem nhé, nội dung cũng không có gì mấy đâu (giá mà các bạn đều xem series học Docker và CICD của mình rồi thì hiểu hết ấy 😉)

Các bạn chỉ cần tạo cho mình file cấu hình nginx nữa thôi là được nhé. Ở trong folder frontend các bạn tạo cho mình file app.nginx.conf với nội dung như sau:

server { listen 80; listen [::]:80; server_name localhost; server_tokens off; client_max_body_size 1G; proxy_intercept_errors on; error_page 404 /404.html; location / { root /usr/share/nginx/html; index index.html index.htm; } location /api { proxy_pass http://backend:3000; proxy_set_header Host $host; proxy_set_header x-forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } location /images/full { proxy_pass http://backend:3000/images/; proxy_set_header Host $host; proxy_set_header x-forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } location /images/tiny { rewrite ^/images/tiny/(.*)$ /insecure/resize:fit:20:0:no:0/plain/http://backend:3000/images/$1 break; proxy_pass http://imgproxy:8080; proxy_set_header Host $host; proxy_set_header x-forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } location /images { rewrite ^/images/(.*)$ /insecure/resize:fit:825:0:no:0/plain/http://backend:3000/images/$1 break; proxy_pass http://imgproxy:8080; proxy_set_header Host $host; proxy_set_header x-forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; }
}

Nội dung ở file cấu hình trên thì cũng xêm như những gì ta đã làm ở vite.config.ts khi ở local dev (mình copy y nguyên file mặc định từ image nginx rồi thêm vào). Nhưng ta có thêm một số thứ như sau:

  • server_tokens off; để ẩn nginx version ở response header đi (giấu càng nhiều thông tin về server càng tốt 😜), vì mặc định nó sẽ trả về thêm nginx version như này:

  • Mặc định nginx chỉ cho upload tối đa 1MB, ở đây ta set lên tận 1GB cho nó máu 🤣 (client_max_body_size 1G;)
  • Tiếp theo ta "intercept" lỗi trả về khi proxy request với proxy_intercept_errors on, nếu gặp lỗi là 404 thì show trang 404 mặc định của nginx, error_page 404 /404.html (các bạn có thể thêm những lỗi khác và trang khác nếu muốn)

Oke ngon rồi, giờ ở root folder các bạn tạo cho mình file docker-compose.yml nhé:

services: app: build: context: ./frontend dockerfile: Dockerfile ports: - "8081:80" volumes: - ./frontend/app.nginx.conf:/etc/nginx/conf.d/default.conf backend: build: context: ./backend dockerfile: Dockerfile volumes: - ./images:/app/public/images imgproxy: image: darthsim/imgproxy

Ở trên thì nội dung quá rõ ràng rồi đúng không 😉, các bạn tự thẩm và có thắc mắc gì thì comment cho mình nhé

À và tạo luôn cho mình folder images ở root folder để lát mount volume lưu lại ảnh upload lên nhé. Sau khi tạo các file mới xong thì folder của ta nom sẽ như sau:

Âu cây ngon rồi, start tất cả lên thôi thôi:

docker compose up -d

Sau khi chạy lên thì các bạn truy cập ở địa chỉ http://localhost:8081 và ta lại có kết quả như local, nhưng mọi thứ đã được build ở production mode 😎:

Thử vọc vạch một chút thì nếu các bạn để ý, request image tới server resize (imgproxy) ở response header hiện tại thì cái server đã bị nginx override rồi, nhưng ta vẫn có thể xác định được request đó tới server resize bởi vì nó có trường X-request-Id

Và khi bấm xem detail image thì vẫn sẽ gọi thẳng tới backend, ta có thể check trường X-Powered-By:

Ồ ké, vậy là mọi thứ đã sẵn sàng lên production. Giờ ta tiến hành build image cho backend và frontend nhé:

# chạy ở folder backend
docker build -t my-backend . # chạy ở folder frontend
docker build -t my-frontend .

Ở bước tiếp theo ta sẽ đẩy image lên Dockerhub nha 🚀🚀

Chú ý với Mac Apple chip: vì image được build bởi Apple Chip thì architecture của nó là ARM64 và sẽ gặp lỗi khi deploy ở trên Linux/K8S,...vì hiện tại thường chúng dùng AMD64, do vậy với các bạn dùng Apple Chip thì ở Dockerfile cho backend và frontend, ở những dòng có FROM thì các bạn phải sửa thành FROM --platform=linux/amd64 nhé. Ví dụ:

FROM --platform=linux/amd64 node:20-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build FROM --platform=linux/amd64 nginx:1.25-alpine as production-stage
WORKDIR /usr/share/nginx/html
COPY --from=build-stage /app/dist .
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Push image lên Dockerhub

Dockerhub cũng như kiểu Github vậy, Github thì lưu code, Dockerhub thì lưu image. Và để đẩy được image của các bạn lên đây thì điều đầu tiên là các bạn cần phải đăng kí tài khoản nhé. Sau đó đăng nhập vào và sẽ thấy giao diện chính nom như sau:

Ở trên Dockerhub thì họ cũng lưu thành Repository, mỗi repository thường lưu 1 loại image, mỗi image có nhiều tag. Ở bài này ta sẽ tạo 2 Repository, 1 cho backend và 1 cho frontend nhé.

Các bạn bấm Create repository và nhập tên image cho backend trước nha:

Sau đó ta bấm Create, thấy giao diện như sau là oke nhé:

Như các bạn thấy thì xêm xêm như Git repository, ở đây ta cũng có thể viết mô tả cho image (bằng Markdown), set category,...

Tiếp theo ta tạo 1 cái repository nữa cho frontend nha, tạo xong thì ta có 2 repository như sau:

Giờ ta quay lại local, và push 2 image mà khi nãy ta build lên nhé, nhưng ta phải tag lại image với tên như ở trên Dockerhub thì mới upload được.

docker tag my-frontend maitrungduc1410/viblo-image-server-frontend docker tag my-backend maitrungduc1410/viblo-image-server-backend

Các bạn thay username của các bạn vào cho đúng nhé

Ở trên ta không nói tag của image là gì thì mặc định nó là latest

Giờ ta push image lên Dockerhub thôi:

docker push maitrungduc1410/viblo-image-server-frontend docker push maitrungduc1410/viblo-image-server-backend

Nếu ta gặp lỗi denied: requested access to the resource is denied thì là ta cần login nhé. Ta chạy command:

docker login

Sau đó ta nhập username + password vào là được (bạn nào muốn bảo mật tốt hơn thì thay vì password thì ta dùng Personal Access Token nhé). Sau khi login thì ta push lại image là được nha

Khi image đã được push lên thành công thì ta quay lại Dockerhub F5 và sẽ thấy image của ta đã ở trên đó 😎😎:

Hover vào chữ OS sẽ show architecture image của ta:

Screenshot 2024-05-04 at 10.33.19 PM.png

Đến đây là image của ta đã sẵn sàng để deploy lên Kubernetes rồi

Bạn nào muốn chắc cốp test lại thì sửa ở local docker-compose.yml, dùng image bằng image trên Dockerhub nhé 😉

Deploy lên K8S

Các bạn tạo cho mình 1 folder bất kì để lưu tất cả manifest cho phần này nhé. Đặt tên là gì cũng được, ở đây mình chọn viblo-image-server-k8s 😁

Trước khi zô viết code thì ta lắc não chút xem ta cần những gì nha:

  • Deployment + Service cho 3 cái: frontend, backend (Object storage), và Server resize (Imgproxy)
  • Ảnh sẽ được lưu lại với volume -> ta cần 1 PersistentVolumeClaim cho backend
  • Chỉ cần frontend được "expose" ra ngoài, 2 service còn lại thì ClusterIP là đủ, traffic đi vào frontend và được proxy internal trong cluster. Và ta sẽ lấy domain + HTTPS xịn luôn nên ta sẽ cần có 1 Ingress cho frontend

Âu cây hòm hòm rồi đó, triển thôi 💪💪💪

Lấy Session

Như mọi bài trong series học K8S của mình, ấy là ta sẽ chạy trên cluster của mình nên ta sẽ cần lấy 1 session để làm việc.

Các bạn vào trang của mình: https://learnk8s.jamesisme.com. Login qua gmail để lấy session nhé, nhớ tick Require domain để lấy domain nhé

Khi lấy được session thành công thì thông tin về namespace và domain sẽ hiện ra như sau:

Sau khi lấy được session thì các bạn lưu file kubernetes-config vào folder làm việc hiện tại và ta test thử get pods coi nha:

kubectl get po --kubeconfig=./kubernetes-config ---->> In ra
No resources found in lk8s-506573 namespace.

Oke ngon rồi 🤪🤪

các bạn phải nhớ và dùng domain xuyên suốt toàn bài nhé

Phần chính

Giờ ta vô phần chính nè. Đầu tiên các bạn tạo cho mình folder backend và ta sẽ deploy backend trước nhé:

Đầu tiên ta tạo file pvc.yml để tạo PersistentVolumeClaim (PVC) nhé:

apiVersion: v1
kind: PersistentVolumeClaim
metadata: name: backend
spec: accessModes: - ReadWriteOnce resources: requests: storage: "2Gi"

Ở trên ta tạo 1 PVC với dung lượng 2Gi, tên là backendaccessModes=ReadWriteOnce tức là PVC này chỉ được mount vào 1 node trên cluster, nhiều pod có thể đọc PVC này, miễn là chúng chạy trên 1 node

vì cluster của mình dùng của DigitalOcean và họ chỉ support ReadWriteOnce. Các cloud khác họ support các loại khác như ReadWriteMany hay ReadOnlyMany

Tiếp theo vẫn ở folder backend ta tạo file deployment.yml:

apiVersion: apps/v1
kind: Deployment
metadata: name: backend labels: app.kubernetes.io/name: viblo-image-server-backend
spec: selector: matchLabels: app: backend strategy: type: Recreate template: metadata: labels: app: backend spec: volumes: - name: images persistentVolumeClaim: claimName: backend containers: - name: my-backend image: maitrungduc1410/viblo-image-server-backend:latest ports: - containerPort: 3000 name: pod-http resources: requests: memory: "128Mi" cpu: "64m" limits: memory: "750Mi" cpu: "500m" volumeMounts: - name: images mountPath: /app/public/images

Ở trên ta định nghĩa 1 Deployment tên là backend, ở đó mỗi Pod có 1 container tên là backend. Ta sẽ expose port này ở cổng 3000 (theo IP của pod), đặt tên cổng này là pod-http để tí bên service ta gọi tới trông cho đẹp 😇. Tiếp đó ta có định nghĩa resource requests/limits (RAM/CPU)

Ta cũng khai báo 1 volume tên là images, lấy từ PVC backend (ở pvc.yml), và ta có đoạn volumeMounts để mount cái volume đó vào đường dẫn /app/public/images.

À và chú ý tí là vì PVC của ta là RWO nên ở Deployment ta set strategy: type=Recreate tức là khi ta deploy thì nó sẽ kill Pod cũ đi trước khi chạy lại Pod mới. Cái này là cần thiết nếu không thì sau này khi ta redeploy nó sẽ bị lỗi

Tên image thì các bạn phải thay vào cho khớp với image mà khi nãy bạn push lên repository trên Dockerhub của bạn nhé

Tiếp theo ta tạo file svc.yml vẫn ở folder backend:

apiVersion: v1
kind: Service
metadata: name: backend
spec: type: ClusterIP ports: - name: svc-http protocol: TCP port: 80 targetPort: pod-http selector: app: backend

Ở trên ta có service dành cho backend, service này có cổng là 80, và traffic sẽ được target vào pod-http mà ta khai báo ở Deployment lúc nãy, ta cũng có selector để select vào pod có labels là backend (cái này phải match với những gì ta khai báo ở Deployment nha)

Sau khi đã tạo đủ các file thì folder làm việc của ta trông sẽ như sau:

Âu cây giờ ta apply tất cả những gì có trong backend nhé:

kubectl apply -f backend --kubeconfig=./kubernetes-config --->> In ra
deployment.apps/backend created
persistentvolumeclaim/backend created
service/backend created

Sau đó ta get thử thông tin từng cái như nào nhé:

kubectl get po --kubeconfig=./kubernetes-config --->>>
NAME READY STATUS RESTARTS AGE
backend-74cffc7c9-6l9t8 1/1 Running 0 39s --- kubectl get svc --kubeconfig=./kubernetes-config --->>>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
backend ClusterIP 10.245.47.226 <none> 80/TCP 12m --- kubectl get pvc --kubeconfig=./kubernetes-config --->>>
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
backend Bound pvc-202b6d2d-6bbd-45fd-8be3-70cd2925b6d8 2Gi RWO do-block-storage 13m

Ô xờ kê, ngon cả rồi. Tiếp theo ta làm tới Server resize ảnh (Imgproxy) nhé. Ở root project ta tạo folder resize-server và trong đó ta tạo deployment.yml nha:

apiVersion: apps/v1
kind: Deployment
metadata: name: imgproxy labels: app.kubernetes.io/name: viblo-image-server-imgproxy
spec: selector: matchLabels: app: imgproxy template: metadata: labels: app: imgproxy spec: containers: - name: my-imgproxy image: darthsim/imgproxy:latest ports: - containerPort: 8080 name: pod-http resources: requests: memory: "128Mi" cpu: "64m" limits: memory: "500Mi" cpu: "200m"

Nội dung thì vẫn tương tự như backend các bạn tự thẩm nhé. Ở trên mình để limits thấp một chút, để tí nữa demo phần scaling cho dễ 😉

Tiếp theo vẫn ở folder resize-server ta tạo file svc.yml:

apiVersion: v1
kind: Service
metadata: name: imgproxy
spec: type: ClusterIP ports: - name: svc-http protocol: TCP port: 80 targetPort: pod-http selector: app: imgproxy

Sau đó lại ở root folder project ta apply tất cả cho imgproxy nhé:

kubectl apply -f resize-server --kubeconfig=./kubernetes-config --->>>
deployment.apps/imgproxy created
service/imgproxy created

Cuối cùng là tới frontend nha, ta tạo folder frontend ở root project

Ta nhớ lại khi ở local, đó là với production thì ta sẽ customize cấu hình nginx một chút để proxy request đi vào backendimgproxy. Do vậy khi deploy lên K8S ta sẽ tạo Configmap lưu cấu hình đó và mount vào frontend nha.

Các bạn tạo cho mình file configmap.yml trong frontend:

apiVersion: v1
kind: ConfigMap
metadata: name: frontend
data: app.nginx.config: | server { listen 80; listen [::]:80; server_name localhost; server_tokens off; client_max_body_size 1G; proxy_intercept_errors on; error_page 404 /404.html; location / { root /usr/share/nginx/html; index index.html index.htm; } location /api { proxy_pass http://backend:80; proxy_set_header Host $host; proxy_set_header x-forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } location /images/full { proxy_pass http://backend:80/images/; proxy_set_header Host $host; proxy_set_header x-forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } location /images/tiny { rewrite ^/images/tiny/(.*)$ /insecure/resize:fit:20:0:no:0/plain/http://backend:80/images/$1 break; proxy_pass http://imgproxy:80; proxy_set_header Host $host; proxy_set_header x-forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } location /images { rewrite ^/images/(.*)$ /insecure/resize:fit:825:0:no:0/plain/http://backend:80/images/$1 break; proxy_pass http://imgproxy:80; proxy_set_header Host $host; proxy_set_header x-forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }

Ở trên các bạn chú ý rằng cho backendimgproxy thì ta phải dùng service của chúng nhé, do vậy port đều là 80

Tiếp đó ta tạo file deployment.yml:

apiVersion: apps/v1
kind: Deployment
metadata: name: frontend labels: app.kubernetes.io/name: viblo-image-server-frontend
spec: selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: volumes: - name: nginx-config configMap: name: frontend items: - key: app.nginx.config path: app.nginx.config containers: - name: my-frontend image: maitrungduc1410/viblo-image-server-frontend:latest ports: - containerPort: 80 name: pod-http resources: requests: memory: "128Mi" cpu: "64m" limits: memory: "500Mi" cpu: "250m" volumeMounts: - name: nginx-config mountPath: "/etc/nginx/conf.d/default.conf" subPath: app.nginx.config readOnly: true

Ở trên ta mount configmap vào container như bình thường, đổi tên file thành default.conf, ta cũng set readOnly=true ý bảo volume này chỉ đọc chứ không được thay đổi

Và tiếp theo là file svc.yml:

apiVersion: v1
kind: Service
metadata: name: frontend
spec: type: ClusterIP ports: - name: svc-http protocol: TCP port: 80 targetPort: pod-http selector: app: frontend

Như mình đã để cập từ đầu bài, đó là ta sẽ chỉ expose frontend ra public, dùng domain cho nó và lấy HTTPS. Và ta sẽ lại làm giống với bài Bảo mật Nginx Ingress với Cert Manager nhé 😎

Vẫn ở folder frontend ta tạo file ingress.yml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: name: frontend
spec: ingressClassName: nginx rules: - host: 506573.learnk8s.jamesisme.com http: paths: - path: / pathType: Prefix backend: service: name: frontend port: name: svc-http

Ở trên các bạn phải thay domain của các bạn lấy được ở bước lấy session vào cho chính xác nhé

Phùuuu, mệt wa 😪😪

Sau khi tạo xong các file thì folder frontend nom như sau:

Giờ ta apply tất cả mọi thứ trong frontend nha:

kubectl apply -f frontend --kubeconfig=./kubernetes-config --->>>
configmap/frontend created
deployment.apps/frontend created
ingress.networking.k8s.io/frontend created
service/frontend created

Sau khi tất cả đã lên ta thử get ingress xem nha:

kubectl get ing --kubeconfig=./kubernetes-config --->>>
NAME CLASS HOSTS ADDRESS PORTS AGE
frontend nginx 506573.learnk8s.jamesisme.com k8s.jamesisme.com 80 6m5s

Có vẻ oke rồi đó, giờ ta mở trình duyệt ở địa chỉ 506573.learnk8s.jamesisme.com truy cập nha (thay domain của các bạn vào cho đúng nhé)

Pằng pằng chíu chíu, lên rồi 🥳🥳🥳🥳:

Các bạn thử upload các kiểu để đảm bảo là app chạy ngon nghẻ xem nhé

chú ý rằng ở bước này ta mới có HTTP thôi chưa có HTTPS, và nếu bạn mở ở Chrome và nó báo lỗi bảo mật Your connection is not private thì là do setup trình duyệt của bạn đang mặc định redirect về HTTPS, các bạn thử mở ở tab ẩn danh và tự tay gõ http://.... vào nhé

Lost+found

Nếu ta để ý ở console, thì thấy rằng có lỗi gì đó:

Check response trả về thì thấy có file lạ tên là lost+found:

Ủa cái gì z trời?????!! 🙄🙄

Ta thử exec vào pod backend xem nhé:

kubectl exec -it backend-d46dd9b75-54nq6 --kubeconfig=./kubernetes-config -- sh

Thay tên pod backend của các bạn vào cho đúng nhé, get po để xem tên nó là gì nha

Sau khi vào trong container thì ta vô folder images nha:

cd public/images/
ls -la

Ủa sao lại có folder lost+found ở đây? Volume tạo mới tinh mà???? 🧐🧐

Thì theo giải thích ở đây nó là mặc định của EXT4 filesystem. Vì Cluster của mình trên DigitalOcean và default volume của nó là ext4:

Giờ ta xoá nó đi là được nhé (không sao cả nha 😜):

rm -rf lost+found/

Sau đó quay lại trình duyệt F5 là thấy hết lỗi 😎😎😎

Lấy HTTPS

Tiếp theo ta lấy HTTPS cho frontend nhé. Cách làm thì vẫn như bài nginx ingress của mình. Trong folder frontend ta tạo file `issuer.yml:

apiVersion: cert-manager.io/v1
kind: Issuer
metadata: name: letsencrypt-prod
spec: acme: # The ACME server URL server: https://acme-v02.api.letsencrypt.org/directory # Email address used for ACME registration email: test@gmail.com # Name of a secret used to store the ACME account private key privateKeySecretRef: name: letsencrypt-prod # Enable the HTTP-01 challenge provider solvers: - http01: ingress: class: nginx

Nội dung thì các bạn xem lại bài kia của mình để hiểu rõ hơn nha. Về cơ bản ở trên thì ta khai báo dạng SSL cert mà ta sẽ lấy (LetsEncrypt)

Sau đó ta apply file issuer.yml này nha:

kubectl apply -f frontend/issuer.yml --kubeconfig=./kubernetes-config --->>>
issuer.cert-manager.io/letsencrypt-prod created

Sau đó ta sửa lại file ingress.yml như sau nhé:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: name: frontend annotations: cert-manager.io/issuer: "letsencrypt-prod"
spec: ingressClassName: nginx tls: - hosts: - 506573.learnk8s.jamesisme.com secretName: frontend-tls rules: - host: 506573.learnk8s.jamesisme.com http: paths: - path: / pathType: Prefix backend: service: name: frontend port: name: svc-http

Ở trên ta khai báo host mà ta muốn lấy HTTPS cert và một số cấu hình khác nữa, nếu có thể các bạn xem bài nginx ingress của mình nhé

Âu cây, apply lại ingress thôi nào:

kubectl apply -f frontend/ingress.yml --kubeconfig=./kubernetes-config --->>>
ingress.networking.k8s.io/frontend configured

Sau đó ta get ingress xem nó như nào nhé:

kubectl get ing --kubeconfig=./kubernetes-config --->>>
NAME CLASS HOSTS ADDRESS PORTS AGE
cm-acme-http-solver-j56xq <none> 506573.learnk8s.jamesisme.com k8s.jamesisme.com 80 12s
frontend nginx 506573.learnk8s.jamesisme.com k8s.jamesisme.com 80, 443 26m

Như trên ta thấy rằng cert-manager đang thực hiện lấy HTTPS, có 1 ingress mới được tạo ra, nếu ta get pod tại thời điểm này cũng sẽ thấy có 1 pod được tạo ra.

Chờ một tẹo và get ingress lại thì sẽ thấy như sau:

kubectl get ing --kubeconfig=./kubernetes-config --->>>
NAME CLASS HOSTS ADDRESS PORTS AGE
frontend nginx 506573.learnk8s.jamesisme.com k8s.jamesisme.com 80, 443 26m

Như vậy là oke rồi nè, ta quay lại trình duyệt F5 là thấy HTTPS nha 😎😎😎

Payload too large

Nếu ta thử upload nhiều ảnh một lúc cho nó nặng nặng chút sẽ thấy lỗi 413 Payload too large:

Thì ta chú ý rằng, ta đang ở trên Kubernetes, và đang dùng Nginx ingress, bản chất nó cũng là nginx thôi. Request đầu tiên khi đi vào cluster nó sẽ vào Nginx ingress trước khi vào tới frontend của ta, và ngay tạo ingress nó đã bị limit rồi. Do vậy. Mặc định limit=1MB, giờ ta update lại ingress.yml để cho nó bằng với những gì mà frontend của ta có, tức là 1GB nhé:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: name: frontend annotations: cert-manager.io/issuer: "letsencrypt-prod" nginx.ingress.kubernetes.io/proxy-body-size: 1G
spec: ingressClassName: nginx tls: - hosts: - 506573.learnk8s.jamesisme.com secretName: frontend-tls rules: - host: 506573.learnk8s.jamesisme.com http: paths: - path: / pathType: Prefix backend: service: name: frontend port: name: svc-http

Sau đó ta apply lại ingress nha:

kubectl apply -f frontend/ingress.yml --kubeconfig=./kubernetes-config --->>>
ingress.networking.k8s.io/frontend configured

Sau đó ta upload lại nhiều file sẽ thấy oke nhé 😚😚

Tối ưu performance

Auto scaling

Những thao tác xử lý media (media processing) ví dụ như resize image là những thao tác tốn CPU vì nó cần load ảnh lên và thực hiện encode/decode ABCXYZ các kiểu, và nếu traffic mà cao thì những thao tác kia sẽ rất tốn resource (CPU/RAM)

Ta có thể test từ trình duyệt và tick Disable Cache và sẽ thấy với chỉ có tí ảnh như này mà đã hơi chậm rồi 🥲:

Thật ra do mình cũng đã chủ đích limit CPU của imgproxy thấp một chút để demo cái này cho dễ 😂

Ở trong source code lúc clone về mình cũng để sẵn 1 file image 8k, ta thử upload file này lên và thấy rằng độ lag rất rõ 😂😂:

Ta copy lấy địa chỉ của file ảnh 8k kia, như của mình có dạng https://506573.learnk8s.jamesisme.com/images/1714817614268_8b0b2b77-51d8-491a-92a1-a791bd31b543.jpg

Chú ý rằng ta phải lấy URL mà nó chạy vào Server Resize (Imgproxy) ấy nhé (width=825 ấy)

Sau đó ta mở terminal và chạy command sau để fake trafic (thay URL file ảnh 8K các bạn vừa lấy vào nhé):

docker run -it --rm --name load-generator node:20-alpine sh -c "apk add curl && while sleep 0.01; do curl https://506573.learnk8s.jamesisme.com/images/1714817614268_8b0b2b77-51d8-491a-92a1-a791bd31b543.jpg; done"

Trong khi fake traffic được tạo thì ta top pod để lấy lượng resource mà các pod đang dùng xem nhé:

kubectl top po --kubeconfig=./kubernetes-config --->>>
NAME CPU(cores) MEMORY(bytes) backend-d46dd9b75-54nq6 10m 47Mi frontend-7b8bf9f7bb-p2qrc 1m 7Mi imgproxy-6867c85699-ph6ll 198m 195Mi 

Như các bạn thấy, chỉ một tẹo thôi là CPU của imgproxy đã chạm đỉnh:

backend-d46dd9b75-54nq6 11m 29Mi frontend-7b8bf9f7bb-p2qrc 2m 7Mi imgproxy-6867c85699-ph6ll 199m 175Mi

Lúc đó ta quay lại trình duyệt F5 cũng thấy ảnh load rất lâu:

Âu cây tới lúc cần setup auto scale cho imgproxy với HorizontalPodAutoscaling rồi đó 🚀🚀🚀. Ta stop fake traffic lại trước khi làm nha (CTRL+C)

Ở folder resize-server ta tạo file hpa.yml:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: name: imgproxy-hpa
spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: imgproxy minReplicas: 1 maxReplicas: 5 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70

Ở trên ta tạo HPA với cấu hình là min=1 pod, max 5 pods, và target mức CPU trung bình <= 70% (tức là khi CPU > 70% thì sẽ bắt đầu scale up)

Ta apply để tạo HPA nha:

kubectl apply -f resize-server/hpa.yml --kubeconfig=./kubernetes-config

Sau đó ta sẽ watch HPA và xem có gì xảy ra khi mà server resize bị quá tải nhé:

kubectl get hpa --kubeconfig=./kubernetes-config --watch --->>>
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
imgproxy-hpa Deployment/imgproxy 1%/70% 1 5 1 86s

Ở trên ta thấy mức tiêu thụ CPU hiện tại là 1%/70%

Ta mở terminal mới và bắt đầu fake traffic tiếp nha:

docker run -it --rm --name load-generator node:20-alpine sh -c "apk add curl && while sleep 0.1; do curl https://506573.learnk8s.jamesisme.com/images/1714817614268_8b0b2b77-51d8-491a-92a1-a791bd31b543.jpg; done"

Sau đó ta quan sát terminal watch hpa đang chạy nãy giờ nhé:

NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
imgproxy-hpa Deployment/imgproxy 1%/70% 1 5 1 86s
imgproxy-hpa Deployment/imgproxy 234%/70% 1 5 1 3m30s
imgproxy-hpa Deployment/imgproxy 298%/70% 1 5 4 3m45s
imgproxy-hpa Deployment/imgproxy 175%/70% 1 5 4 4m
imgproxy-hpa Deployment/imgproxy 41%/70% 1 5 5 4m15s
^[[A
imgproxy-hpa Deployment/imgproxy 73%/70% 1 5 5 4m30s
imgproxy-hpa Deployment/imgproxy 70%/70% 1 5 5 4m45s
imgproxy-hpa Deployment/imgproxy 65%/70% 1 5 5 5m
imgproxy-hpa Deployment/imgproxy 54%/70% 1 5 5 5m15s

Trong quá trình fake traffic nếu ta quan sát từ trình duyệt sẽ thấy đoạn đầu khá chậm, chờ tẹo thì thấy ổn định dần

Quan sát terminal phía HPA ta thấy rằng HPA đã scale số pod lên 5 pod và lượng CPU trung bình đã giảm dần đi

Thử get pod cũng sẽ thấy số pod imgproxy cũng đã tự động tăng lên:

kubectl get po --kubeconfig=./kubernetes-config --->>>
NAME READY STATUS RESTARTS AGE
backend-d46dd9b75-54nq6 1/1 Running 0 115m
frontend-7b8bf9f7bb-p2qrc 1/1 Running 0 33m
imgproxy-6867c85699-6zvxf 1/1 Running 0 4m
imgproxy-6867c85699-htkz5 1/1 Running 0 3m30s
imgproxy-6867c85699-htlnx 1/1 Running 0 4m
imgproxy-6867c85699-ms2nl 1/1 Running 0 4m
imgproxy-6867c85699-ph6ll 1/1 Running 0 126m

Giờ ta stop fake traffic đi nhé (CTRL+C), và ta sẽ thấy rằng HPA sẽ dần dần scale down số lượng pod:

imgproxy-hpa Deployment/imgproxy 68%/70% 1 5 5 8m5s
imgproxy-hpa Deployment/imgproxy 1%/70% 1 5 5 8m15s
imgproxy-hpa Deployment/imgproxy 1%/70% 1 5 5 11m
imgproxy-hpa Deployment/imgproxy 1%/70% 1 5 5 13m
imgproxy-hpa Deployment/imgproxy 1%/70% 1 5 1 13m

Quá trình scale down mặc định hơi lâu vì HPA thấy "ổn thì mới scale xuống" tránh trường hợp vừa scale xuống thì traffic lại tăng. Cái này ta có thể config được nhé

Cache image

Ở bài này ta cũng sẽ làm theo như bài của Viblo, đó là ta sẽ cache image được resize để những request sau thì ta không cần phải gọi lại vào server resize (imgproxy) nữa. Và điều này cũng hợp lý, vì với 1 ảnh thì lần nào resize thì output cũng tương tự nhau và ta không cần lặp đi lặp lại điều này quá nhiều.

Ta sẽ tận dụng proxy-cache của nginx (ở frontend) luôn nhé.

Ta update lại file frontend/configmap.yml chút nha:

apiVersion: v1
kind: ConfigMap
metadata: name: frontend
data: nginx.conf: | user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; proxy_cache_path /var/cache/nginx keys_zone=image-cache:10m; include /etc/nginx/conf.d/*.conf; } app.nginx.config: | server { listen 80; listen [::]:80; server_name localhost; server_tokens off; client_max_body_size 1G; proxy_intercept_errors on; error_page 404 /404.html; location / { root /usr/share/nginx/html; index index.html index.htm; } location /api { proxy_pass http://backend:80; proxy_set_header Host $host; proxy_set_header x-forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } location /images/full { proxy_pass http://backend:80/images/; proxy_set_header Host $host; proxy_set_header x-forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } location /images/tiny { rewrite ^/images/tiny/(.*)$ /insecure/resize:fit:20:0:no:0/plain/http://backend:80/images/$1 break; proxy_pass http://imgproxy:80; proxy_set_header Host $host; proxy_set_header x-forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache image-cache; proxy_cache_key $uri$http_accept; proxy_cache_lock on; } location /images { rewrite ^/images/(.*)$ /insecure/resize:fit:825:0:no:0/plain/http://backend:80/images/$1 break; proxy_pass http://imgproxy:80; proxy_set_header Host $host; proxy_set_header x-forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache image-cache; proxy_cache_key $uri$http_accept; proxy_cache_lock on; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }

Ở trên mình đã thêm vào file nginx.conf đây là file cấu hình tổng của nginx, ở đó thì mình chỉ copy content từ file mặc định và thêm vào dòng proxy_cache_path .... Sau đó ở file app.nginx.conf thì với 2 location mà proxy sang Resize Server (imgproxy) thì ta thêm vào proxy_cache ...

Ở trên ta cache ảnh trong 10m=10 phút

oke rồi giờ ta apply configmap nha:

kubectl apply -f frontend/configmap.yml --kubeconfig=./kubernetes-config --->>>
configmap/frontend configured

Sau đó ta sửa lại file frontend/deployment.yml để mount file cấu hình tổng của nginx kia từ configmap vào container nha:

apiVersion: apps/v1
kind: Deployment
metadata: name: frontend labels: app.kubernetes.io/name: viblo-image-server-frontend
spec: selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: volumes: - name: nginx-config configMap: name: frontend items: - key: app.nginx.config path: app.nginx.config - name: nginx-master-config configMap: name: frontend items: - key: nginx.conf path: nginx.conf containers: - name: my-frontend image: maitrungduc1410/viblo-image-server-frontend:latest ports: - containerPort: 80 name: pod-http resources: requests: memory: "128Mi" cpu: "64m" limits: memory: "500Mi" cpu: "250m" volumeMounts: - name: nginx-config mountPath: "/etc/nginx/conf.d/default.conf" subPath: app.nginx.config readOnly: true - name: nginx-master-config mountPath: "/etc/nginx/nginx.conf" subPath: nginx.conf readOnly: true

Ở trên thì vì ta mount configmap ra một đường dẫn khác, nên ta khai báo thêm 1. volumes nữa, nhưng vẫn là chung 1 configmap

Oke rồi ta apply lại deployment của frontend nhé:

kubectl apply -f frontend/deployment.yml --kubeconfig=./kubernetes-config

Sau đó ta quay lại trình duyệt để test nha, các bạn nhớ check vào Disable cache để tắt cache từ trình duyệt để quan sát cho rõ nhé. Ta sẽ thấy là với request đầu tiên thì vẫn chậm, nhưng từ lần thứ 2 trở đi thì nhanh đột biến:

Bình thường nếu không có cache thì nếu ta check logs realtime của imgproxy (thay tên pod của các bạn vào cho đúng nhé):

kubectl logs imgproxy-6867c85699-htlnx -f --kubeconfig=./kubernetes-config

Thì sẽ thấy là mỗi khi imgproxy nhận được request là nó log ra console ngay:

Nhưng kể từ khi có caching phía frontend(nginx) thì request không cần tới nó nữa nên log không còn mấy nữa. Chỉ khi ta upload ảnh mới lên thì nó mới cần làm việc.

Và vì như vậy nên HPA sẽ rất ít khi phải làm việc, quá toẹt vời 🥳🥳🥳🥳🥳

À chú ý là vì ta không có mount volume gì để giữ lại cache cho frontend hết trơn, nên nếu ta redploy lại frontend thì cache sẽ mất

Chấm hết

Phewwwwwwwwwww 🥶🥶🥶😵😵😵 Viết xong bài này suýt tụt đường huyết 🤣 dài quá trời dài

Nhưng hi vọng qua đây các bạn đã có thể hiểu + triển khai được một ví dụ từ thực tế đó là deploy server để lưu trữ và resize ảnh giống như của Viblo đang làm, từ đó ta hiểu hơn về những gì đang xảy ra xung quanh, những product mà ta dùng hàng ngày nó hoạt động ra sao, chạy như thế nào. Và chính chúng ta cũng có thể làm được điều tương tự 🥰

Đây là tất cả những file manifest ta có khi làm xong bài này 🤩🤩🤩:

Screenshot 2024-05-04 at 10.30.13 PM.png

Chúc các bạn buổi tối cuối tuần vui vẻ và tràn đầy yêu thương. Hẹn gặp lại các bạn ở những bài sau 👋

Bình luận

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

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

Caching đại pháp 1: Nấc thang lên level của developer

Bí quyết thành công trong việc đáp ứng hệ thống triệu user của những công ty lớn (và cả công ty nhỏ). Tại sao caching lại là kỹ thuật tối quan trọng để phù phép ứng dụng rùa bò của chúng ta thành siêu phẩm vạn người mê.

0 0 82

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

Cache dữ liệu Nodejs với Redis

Một tí gọi là lý thuyết để anh em tham khảo. Cache là gì. Lợi ích của việc cache data. .

0 0 111

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

In-memory cache là gì?

Bài viết gốc: https://www.tranthanhdeveloper.com/2020/12/in-memory-cache-la-gi.html.

0 0 38

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

Cache là gì và nguyên lý hoạt động của cache

Trong vài trở lại đây, công nghệ thông tin đã trở thành bàn đạp cho sự cải thiện mạnh mẽ cho vô số các loại hình kinh doanh. Đồng thời, các kiến trúc phần mềm ngày càng trở nên phức tạp và số lượng ng

0 0 257

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

Tăng tốc website: Sử dụng Page Cache - Application Cache

Như đã đề cập trong Lời mở đầu, trong phần này tôi sẽ đề cập tới cách thực hiện page cache đầu tiên đó là Application Cache - tôi tạm gọi như vậy, với ý nghĩa là thực hiện cache ngay trong web applica

0 0 55

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

Tăng tốc website: Sử dụng Page Cache - Lời mở đầu

Bài đầu tiên trong series này, tôi sẽ điểm lại một vài nội dung cơ bản nhất để các bạn chưa biết đến kỹ thuật Page Cache cũng có thể nắm được. Các bạn nào đã biết thì cũng có thể đọc cho vui rồi cho t

0 0 41