Like! – Làm thế nào để chạy ứng dụng trên Kubernetes
Introduction
Một trong các vấn đề khi chạy sản phẩm trên internet public phục vụ số lượng người dùng lớn mà chúng ta thường gặp phải là khả năng mở rộng (Scalability) của hệ thống. Khi số lượng người dùng tăng lên với số lượng lớn, một hệ thống muốn đáp ứng được thì cần phải có tính mở rộng, tăng lượng tài nguyên để đảm bảo khả năng đáp ứng số lượng truy cập của người dùng tăng.
Kubernetes (thường gọi tắt là k8s) đang trở nên thông dụng với việc số lượng các công ty (cả enterprises lẫn startups) sử dụng k8s đang tăng lên nhanh chóng. K8s được ưa chuộng không chỉ bởi số star trên github của project đang là 42,941 với 1860 contributors (thời điểm viết bài), mà còn do các tính năng vượt trội mà nó mang lại trong việc thực hiện deployment, maintenance, và scaling các ứng dụng chạy trên đó.
Tìm hiểu thêm về Kubernetes trên Github
Trong bài viết này tôi sẽ chia sẻ về cách chúng tôi dùng Kubernetes, cụ thể là GKE (Google Kubernetes Engine), để chạy sản phẩm của dự án và Gitlab CI để hỗ trợ thực hiện tích hợn liên tục CI (Continuous Integration) giúp tăng tốc độ quá trình phát triển sản phẩm.
Tech Stack
Một trong các ưu điểm của k8s, phù hợp để chạy các ứng dụng microservice, trong đó mỗi service có thể được phát triển dùng các ngôn ngữ, công nghệ khác nhau và được đóng gói vào trong các docker image riêng biệt. Dưới đây là một số công nghệ và framework mà chúng tôi sử dụng trong dự án.
- nodejs – phát triển frontend
- vuejs – phát triển cms client
- akka-http – phát triển backend (rest api)
- docker – đóng gói ứng dụng
- kubernetes – chạy, quản lý, điều phối docker container
- google kubernetes engine (gke) – chạy kubernetes cluster
- google cloud sql – chạy mysql database
- gitlab ci – lưu trữ source code và thực hiện tích hợp liên tục
Dockerize Application
Đầu tiên chúng tôi cần viết các Dockerfile cho từng service frontend/cms/backend, Dockerfile là nơi viết các hướng dẫn, câu lệnh để build các docker images.
Dockerfile cho frontend, thực hiện cài đặt thư viện phụ thuộc, build nodejs app, sau đó copy kết quả vào image. Mở port 3000 cho kết nối từ bên ngoài, chạy lệnh npm start
khi bật container.
FROM node:carbon # Create app directory WORKDIR /usr/src/app # Install app dependencies COPY package*.json ./ RUN npm install # Bundle app source COPY . . EXPOSE 3000 CMD [ "npm", "start" ]
Tương tự cho các service cms và backend, chúng tôi cũng cần viết Dockerfile để đóng gói source code của ứng dụng vào trong docker image. Phần backend chúng tôi dùng sbt plugin là sbt-native-package để build docker image.
Kubernetes
Sau khi application đã được đóng gói vào trong các docker images bằng Dockerfile, bước tiếp theo cần tìm cách để chạy các containers từ các images này một cách hiệu quả? Đây là thời điểm k8s trở nên hữu ích với các tính năng scale không giới hạn của mình (thực ra giới hạn hiện tại của k8s thời điểm viết bài này là 5000 nodes, 100 pods trên một node, tổng số pods là 150000, tổng số containers là 300000).
Để chạy một ứng dụng trên k8s, chúng tôi viết các manifest file cho ứng dụng đó. Đây là các file (ở định dạng YAML) đặc tả chứa những thông tin liên quan tới việc chạy container trên k8s chẳng hạn như : tên image, container port, biến môi trường, đánh label … Trong k8s manifest file sẽ cần có 2 thành phần chính là Deployment và Service.
- Deployment : sẽ đảm bảo các containers chạy đúng về số lượng, cấu hình với cài đặt như trong manifest file
- Service : giống như một endpoint được exposed để cho phép truy cập từ bên ngoài
Dưới đây là k8s manifest file cho frontend service : `frontend.yaml`
apiVersion: v1 kind: Service metadata: name: frontend spec: selector: app: nodejs tier: frontend ports: - protocol: TCP port: 3000 targetPort: nodejs type: NodePort --- apiVersion: apps/v1 kind: Deployment metadata: name: nodejs spec: selector: matchLabels: app: nodejs tier: frontend track: stable replicas: 1 strategy: type: RollingUpdate template: metadata: labels: app: nodejs tier: frontend track: stable spec: containers: - name: frontend image: "FRONTEND_IMAGE_TAG" ports: - name: nodejs containerPort: 3000 env: - name: database value: "dbname" - name: username value: "dbuser" - name: password value: "PleaseChangeMe" - name: dialect value: "mysql" - name: host value: "dev-mysql" - name: apiBackendUrl value: "http://backend:8081" #local dns imagePullPolicy: Always imagePullSecrets: - name: gitlab-frontend-token
Để áp dụng các manifest file này lên k8s cluster, chúng tôi dùng kubectl (kubernetes client). Đây là một ứng dụng dòng lệnh giúp tương tác với k8s cluster. Trước khi dùng kubectl chúng tôi cần thực hiện việc cấu hình xác thực để kubectl làm việc với k8s cluster. Lệnh phía dưới sẽ đọc nội dung của file `frontend.yaml`, gọi vào API của k8s cluster để thực hiện lệnh theo yêu cầu.
kubectl create -f frontend.yaml
Một cách khác chuyên nghiệp hơn (maybe ;)) là viết manifest này thành một helm chart để deploy lên cluster, nếu các bạn muốn có thể tìm hiểu thêm về helm ở đây
Ngoài Deployment và Service, còn có các thành phần khác mà chúng tôi cần phải cài đặt để giúp hoàn thiện việc triển khai app chạy trên k8s.
- ingress : quản lý, điều khiển các request vào các service phía trong k8s, chúng tôi chọn nginx-ingress được cộng đồng hỗ trợ tốt với nhiều tính năng, dễ dàng cài đặt và sử dụng cho những người đã có kinh nghiệm với nginx.
- service account : giúp phân quyền truy cập thao tác vào k8s cluster
- configmap, secret : lưu trữ các cấu hình, tham số
- persistent volume : dùng cho các dữ liệu cần lưu trữ lâu dài
Google Kubernetes Engine
Để triển khai và chạy k8s cluster, chúng tôi cân nhắc các lựa chọn :
- Amazon Cloud AWS thì có thể dùng EKS (service cung cấp bởi AWS) hoặc sử dụng Kops xây dựng bởi cộng đồng
- Microsoft Cloud Azure có AKS
- Google Cloud GCP có GKE
Sau khi cân nhắc về khía cạnh maturity (mức độ ổn định của một sản phẩm), tính đơn giản dễ sử dụng chúng tôi đã quyết định lựa chọn GKE, một điểm cộng cho GKE là các maintainer chính của Kubernetes open source cũng là những người xây dựng nên GKE 😉
Trên GKE chúng tôi tạo 2 kubernetes cluster :
- development cluster sử dụng cho môi trường test/staging
- production cluster sử dụng cho môi trường live/production
Gitlab CI
Cuối cùng chúng tôi dùng Gitlab CI để tự động hoá các bước test, build, package và deploy. Việc thực hiện CI trên Gitlab được thiết lập qua .gitlab-ci.yml file. Các bước trong .gitlab-ci.yml bao gồm :
- test : chạy unit test code
- build : chạy các job build
- package : đóng gói docker image và đẩy lên image registry (gitlab có tích hợp sẵn)
- deploy : chạy deploy lên k8s cluster
file .gitlab-ci.yml
của frontend mô tả các jobs (test, build, package, deploy) sẽ tự động chạy khi có code được merge vào nhánh develop trên Gitlab.
stages: - test - build - package - deploy test: stage: test only: - "develop" script: - npm install - npm test build: stage: build only: - "develop" script: - npm install grunt --save-dev - grunt - npm install package: stage: package image: docker:latest only: - "develop" services: - docker:dind variables: IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA DOCKER_DRIVER: overlay DOCKER_HOST: tcp://localhost:2375 before_script: - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY script: - docker build -t $IMAGE_TAG . - docker push $IMAGE_TAG deploy: stage: deploy image: alpine only: - "develop" services: - docker:dind variables: IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA DOCKER_DRIVER: overlay DOCKER_HOST: tcp://localhost:2375 before_script: - echo "start deploying development ..." script: - apk update && apk add --no-cache curl - curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl - chmod +x ./kubectl && mv ./kubectl /usr/local/bin/kubectl - sed -i "s#FRONTEND_IMAGE_TAG#$IMAGE_TAG#g" k8s/k8s.frontend.dev.yaml - kubectl --namespace=app-dev apply -f k8s/k8s.frontend.dev.yaml
Conclusion
Đến đây chúng tôi đã xây dựng cơ bản một ứng dụng có thể chạy trên k8s cluster. Trong thời gian tiếp theo, chúng tôi sẽ tập trung vào các best practice để áp dụng vào hệ thống, giúp cải thiện hiệu năng, tiết kiệm tài nguyên và tăng tính bảo mật của hệ thống.
Cám ơn bạn đã đọc bài, welcome các ý kiến đóng góp của các bạn. Hãy để lại comments ở dưới bài viết 🙂
#HowWeBuild