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

Add a Comment

Scroll Up