Nodejs CI/CD CircleCI

Bài viết hướng dẫn tích hợp CI/CD với circleCI một cách đơn giản nhất.

1. Cái nhìn tổng quan

  • Mình sẽ không đi sâu vào lý thuyết về CI/CD là gì?, công dụng của CI/CD vì cái này mọi người có thể tìm thấy rất nhiều trên google.
  • Gần đây, một vài project mình đang làm có thêm thành viên, cần mở rộng tính năng. Để kiểm soát tốt hơn source code của mọi người cũng như tích hợp tự động cho project mình đã áp dụng CI/CD vào. Và cụ thể hơn mình sử dụng CircleCI - cái mà mình bị quảng cáo liên tục khi xem youtube 😅
  • Technical Stack: Nodejs (express - typescript), pm2 …
  • Trước khi có CI/CD, khi triển khai project mình sẽ thực hiện các bước sau:

Các bước triển khai dự án đơn giản

Triển khai dự án đơn giản
  • Sau khi code xong, chúng ta sẽ chạy test. Khi đã pass toàn bộ test case thì sẽ thực hiện push code lên git, tạo pull request, review code và được merge và master.
  • Sau đó sẽ ssh vào server - nơi deploy project. Kéo project từ git về và start server lên.
  • Ok, mọi thứ đều khá là thủ công và mất thời gian. Do đó CI/CD là giải pháp tiếp theo của mình để mọi thứ trở nên tự động hơn.
  • Hiểu một cách nông dân sẽ là như này: CircleCI sẽ giúp mình chạy các test case của mỗi commit được push lên repository. Ngoài ra, sau khi mỗi branch được meger vào master cũng sẽ được chạy test. Nếu pass toàn bộ test nó sẽ tự động deploy lên server và restart các tiến trình của pm2.

2. Triển khai trên CircleCI

2.1 Các bước chuẩn bị

  • project đơn giản với typescript được mình viết trong bài này: https://dattp.github.io/2020-08-08-typescript
  • Mọi người có thể clone project tạo github luôn: https://github.com/dattp/todo_typescript

Vì bài này mục đích chính là về CI/CD nên mình chỉ viết test case cho phần service. Đúng ra sẽ phải viết test case cho toàn bộ những class khác để có thể coverage toàn bộ code. Phần về unit test có tích hợp với cả database mình sẽ làm vào bài viết tới. Mọi người cùng theo dõi nhé.

Login circleCI

  • Chọn login bằng github và thực hiện

Set up project

  • Sau khi bấm vào Set Up Project chúng ta sẽ thấy đoạn mã config dành cho Node như sau:
version: 2.1
orbs:
  node: circleci/node@3.0.0
workflows:
  node-tests:
    jobs:
      - node/test
  • version: phiên bản của circleCI
  • orbs: là nơi khai báo command line được hỗ trợ bởi circle. Sử dụng command của node.
  • workflows: các luồng chạy trên project:
    • node-tests tên flow tự đặt.
    • jobs: các job cần chạy cho project.
    • node/test là lệnh mặc định của command node - thứ mà đã được khai báo ở orbs. Bình thường nếu không có sự hỗ trợ này ta sẽ phải chạy lệnh tương tự như npm run test.
  • Sau đó chọn Add config để tiếp tục thực hiện build trên CircleCI.
  • Chúng ta sẽ thấy quá trình chạy

running

  • Có thể xem chạy cụ thể từng step của từng bước bằng cách click vào node-test trong workflow.
  • Lần đầu chạy sẽ bị fail, vì chúng ta còn thiếu một vài config nữa.

2.2 Tiến hành config CI

  • Quay trở lại với project trên máy local. Ta cần tạo 1 thư mục .circleci theo đúng quy định của CircleCI. Trong .circleci tạo file config.yml như sau:
version: 2.1
orbs:
  node: circleci/node@3.0.0
jobs:
  build:
    docker:
      # specify the version you desire here
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # sử dụng môi trường chạy nodejs thông quan docker
      - image: circleci/node:12.13.0

    working_directory: ~/todo-typescript

    steps:
      - checkout
      # khôi phục lại folder chứa package (v1-dependencies) dựa vào file (package-lock.json)
      - restore_cache:
          keys:
          - v1-dependencies-
          # fallback to using the latest cache if no exact match is found
          - v1-dependencies-
      - run: npm install
      # lưu lại cache folder theo key nếu như (package-lock.json) có sự thay đổi
      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-

      - run: npm run test
  • Mọi thứ đều được mình giải thích ở comment tương ứng.
  • npm run test chính là chạy test case của project. Đoạn script này được định nghĩa trong file package.json.
  • Ok! bây giờ hãy commit và push lên github nhé. Chúng ta theo dõi tiếp trên Pipelines của circleCI.
  • Kết quả: Success xanh lè 🤣 success
  • Tức là CI của chúng ta đã hoạt động cũng như test case đã được pass. Nếu khi có 1 test case nào chưa pass thì sẽ bị báo fail.

2.3 Tiến hành deploy lên server.

  • Đầu tiên chúng ta sẽ thử ssh vào server để chạy project một cách thủ công.
  • cd /homegit clone git@github.com:dattp/todo_typescript.git sử dụng ssh để clone nhé.
  • Nếu bạn chưa có ssh-key thì search trên gg để add public-key vào github nha 😜
  • Bạn cần gen ra 2 key. Một key trên máy local để ssh vào server. Và một key trên server, sau đó add key server này vào phần setting ssh key của github để có thể clone được project về.
  • Sau khi đã clone được project về chúng ta sẽ thử start server lên nha.
  • Khi đã ssh được vào server thông qua ssh key mà không cần nhập mật khẩu. Chúng ta sẽ tiếp tục config trên circleCI như sau.
  • Chọn Project Setting Project Setting
  • Chọn SSH Keys ở cột menu bên trái.
  • Chọn Add SSH Key trong mục Additional SSH Keys Add SSH Key

  • Paste private key - cái mà bạn đã gen được ra ở trên máy local. (paste cả phần header và footer tức là phần —–BEGIN RSA PRIVATE KEY—– cho hết —–END RSA PRIVATE KEY—– nha)
  • Ngay sau khi add được ssh key. Chúng ta sẽ có Fingerprint tương ứng như sau: fingerprint

  • Quay trở lại với file config của circleCI trên máy local và hoàn thiện nó:
version: 2.1
orbs:
  node: circleci/node@3.0.0
jobs:
  build:
    docker:
      # specify the version you desire here
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # sử dụng môi trường chạy nodejs thông quan docker
      - image: circleci/node:12.13.0

    working_directory: ~/todo-typescript

    steps:
      - checkout
      # khôi phục lại folder chứa package (v1-dependencies) dựa vào file (package-lock.json)
      - restore_cache:
          keys:
            - v1-dependencies-
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-
      - run: npm install
      # lưu lại cache folder theo key nếu như (package-lock.json) có sự thay đổi
      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-

      - run: npm run test

  deploy:
    machine:
      enabled: true
    steps:
      - add_ssh_keys:
          fingerprints:
            - "76:8d:4d:4c:2c:db:3f:0d:a6:6e:5a:42:f4:dc:c5:23"
      - run:
          name: pull code and start api_services
          command: ssh -oStrictHostKeyChecking=no root@42.112.27.132 -p 87222 "cd /home/todo_typescript; git pull origin master; npm run install-npm; npm run dev"

workflows:
  my-flow:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only:
                - master
  • Chúng ta bổ sung thêm một job mới có tên là deploy sử dụng machine mặc định.
  • Mô tả các bước cần thực hiện:
    • add ssh key dựa vào fingerprints - cái mà đã lấy được ở bước trên.
    • run các commands tương ứng. Đầu tiên là ssh vào server với option oStrictHostKeyChecking = no để không hiển thị câu hỏi yes/no khi ssh.
    • Sau khi vào được server. Chúng ta sẽ di chuyển đến project - cái mà ta đã clone chạy thử ở bước phía trên.
    • Sau đó thực hiện pull code master về, install lại npm và cuối cùng là start server.
    • workflows là nơi ta định nghĩa luồng cần thực hiện:
      • tên flow: my-flow
      • jobs cần chạy: Đầu tiên là build để chạy các test case.
      • Sau đó sẽ chạy job deploy.Require build ở đây muốn job build phải SUCCESS thì deploy mới được chạy. Và chỉ chạy deploy khi có code được meger vào nhánh master.
  • Bây giờ chúng ta sẽ thử thay đổi 1 đoạn code nhỏ. Sau đó commit code lên github.
  • Khi meger nhánh vừa push lên vào master. Thì nó sẽ tự động deploy lên server.

Deploying

  • Quá trình deploy sẽ luôn bị giữ ở trạng thái running do nó đang giữ command start server. Lúc này chúng ta sẽ sử dụng package pm2 để chạy tiến trình của project nhé.
  • Có thể chọn 2 cách. Một là bổ sung thêm đoạn sudo npm install pm2 -g trong job deploy. Để khi deploy nó sẽ tự động cài pm2 hoặc cài trực tiếp pm2 trên server.
  • Sửa lại đoạn code trong package.json: pm2 start nodemon chạy development mode.
  • Kết quả: pipeline
  • Chúng ta có success ở cả 2 jobs builddeploy 😎. Bạn hãy check thử trên server nhé.

3. Kết luận gì ở đây

  • Như mọi người thấy, khi đã tích hợp CI/CD vào, mọi thứ trở nên tự động. Tự động deploy lên server nên việc viết test case là rất rất quan trọng. Nó sẽ kiểm soát những sự thay đổi có trong code, có ảnh hưởng gì đến các hàm đang chạy không.
  • Do vậy viết unit test càng cẩn thận càng tốt. Tốt nhất là nên coverage được toàn bộ các hàm - không còn dòng code nào xuất hiện trên cột Uncovered Line #s khi chạy test với Jest.
  • Như ví dụ trên mình chạy với project đơn giản. Chưa có tương tác với Database nên việc viết test cũng đơn giản. Ở những bài tiếp theo nếu có điều kiện mình sẽ viết thêm về test với DB từ việc chuẩn bị dữ liệu test, xoá dữ liệu sau khi chạy xong test.
  • Bản thân mình khi áp dụng CI/CD vào project cũng mới bắt đầu chú trọng vào việc viết test hơn, nên trong bài có thể có những cái chưa được phù hợp. Rất mong có sự góp ý của mọi người.