+19

CI/CD - GitHub Actions và các kiến thức cơ bản

CI/CD cơ bản

Đặt vấn đề

Như ở 2 bài trước mình đã giới thiệu về Docker và Docker Compose, chúng ta đã đi qua các khái niệm và cách tạo ra các container riêng của mình. Khi bạn đã code xong 1 tính năng và sau đó bạn build ứng dụng bằng Docker sau đó bạn push lên Docker Hub và nếu bạn muốn ứng dụng đó public ra bên ngoài để mọi người có thể truy cập được vào thì bạn phải vào server ( vps, server, ... ) rồi bạn sẽ pull và chạy cái image đó lên bằng tay. Như vậy khá là thủ công bởi vì mình làm bằng tay từ đầu đến cuối. Hoặc đôi khi trong công việc, người deploy trong team bạn nghỉ do có việc đột xuất, các merge request đã được merge ầm ầm rồi nhưng code trên server cũ vì không có ai deploy cả. Vậy chúng ta cần 1 giải pháp để khi merge code vào nhánh chính, thì phải có 1 quy trình nào đó tự động việc deploy để đảm bảo rằng trên server luôn là code mới nhất. Vậy hôm nay chúng ta sẽ tìm hiểu về CI/CD để giải quyết vấn đề đó và có cái nhìn đầu tiên về việc tự động hoá quá trình build và deploy ứng dụng của chúng ta nhé!

Lý thuyết

Tổng quan về CI/CD

CI/CD là gì?

CI/CD được viết tắt của Continuous Integration / Continuous Deployment. Đó là quá trình tự động hoá việc kiểm tra ( test ) mã nguồn, xây dựng ( build ) và triển khai ( deploy ) ứng dụng của mình lên các môi trường như Dev, Staging hoặc Production

image.png

Hình 1: CI/CD

Cách hoạt động

Trong phần này, chúng ta sẽ giải thích chi tiết hơn về các khái niệm sau CI và CD:
CI (Continuous Integration): là một quá trình tự động hóa trong phát triển phần mềm, cho phép các thành viên trong team kiểm tra và hợp nhất mã nguồn của họ vào kho chung một cách tự động và định kỳ. Mỗi lần được hợp nhất thì được xây dựng 1 cách tự động để phát hiện lỗi phát sinh 1 cách nhanh nhất có thể.
Quy trình của CI được diễn ra như sau:

  • Tích hợp mã nguồn: Các lập trình viên commit và push code của mình lên repository.
  • Kích hoạt CI: CI server giám sát repository và kiểm tra liên tục sự xem có sự thay đổi nào hay không. Khi phát ra sự thay đổi, CI server bắt đầu quá trình CI.
  • Build code: Với code mới nhất trên repository, CI server sẽ build mã nguồn này thành một phần mềm hoàn chỉnh.
  • Chạy các test case: Sau khi quá trình build hoàn tất, công cụ CI server bắt đầu chạy kiển tra đơn vị (unit test), kiểm tra tích hợp (integration test), kiểm tra giao diện (UI test) hoặc các kiểm tra khác tùy thuộc vào yêu cầu của dự án.
  • Đưa ra thông báo: Nếu có lỗi trong quá trình kiểm tra, CI server sẽ đưa ra thông báo lỗi cho các lập trình viên biết. Các thông báo này thường được gửi qua email, chat...
  • Hợp nhất mã nguồn: Nếu không có lỗi, CI server sẽ tự động hợp nhất mã nguồn của các lập trình viên vào một phiên bản mới nhất của phần mềm. Phiên bản mới này được lưu trữ trong kho chung của dự án và có thể được triển khai vào các môi trường khác nhau của dự án.

CD ( Continuos Deployment ): Là quá trình triển khai tự động sau khi quá trình CI được diễn ra thành công. Quá trình CD sẽ được kích hoạt để triển khai ứng dụng của chúng ta vào các môi trường của dự án. Các gói phần mềm đã build thành công được triển khai bằng cách sử dụng các công cụ tự động hoặc được triển khai thủ công bằng tay. Điều này đảm bảo rằng phần mềm được cài đặt đúng cách và hoạt động tốt trên các môi trường

GitHub Actions

GitHub Actions là gì?

  • GitHub Actions là 1 nền tảng miễn phí do GitHub cung cấp để giúp chúng ta tự động hoá quá trình CI/CD, cho phép người dùng định nghĩa các workflow tự động hoá các hoạt động trong phát triển phần mềm.
  • Mỗi workflow trong GitHub Actions là một tập hợp các hành động (actions) được định nghĩa trong file YAML ( syntax thì các bạn có thể đọc ở đây ). Các actions này có thể là các lệnh cụ thể như: build ứng dụng, test, triển khai ứng dụng. Bạn có thể sử dụng các actions được cung cấp sẵn bởi GitHub, các actions mà cộng đồng lập trình viên tạo ra hoặc tạo các hành động tùy chỉnh để phù hợp với nhu cầu của dự án của mình, các actions đó các bạn có thể tìm thấy ở đây

Các tính năng nổi bật của GitHub Actions

  • Tích hợp sẵn với GitHub: Vì GitHub Actions được phát triển bởi GitHub nên nó được tích hợp sẵn với nền tảng GitHub. Điều này có nghĩa là bạn có thể sử dụng các tính năng của GitHub Actions trực tiếp từ trang web của GitHub, và không cần phải cài đặt hoặc cấu hình bất kỳ phần mềm nào khác.
  • Hỗ trợ chạy trên nhiều hệ điều hành: GitHub Actions có thể chạy trên nhiều hệ điều hành khác nhau: Windows, macOS, Ubuntu... Điều này cho phép bạn kiểm tra ứng dụng của mình trên nhiều nền tảng khác nhau và đảm bảo rằng nó hoạt động đúng cách trên mọi nền tảng.
  • Hỗ trợ chạy trên các môi trường ảo: Cho phép bạn thực hiện trong các môi trường ảo khác nhau, giúp chúng ta kiểm tra ứng dụng của mình trên nhiều môi trường đấy một cách dễ dàng. Ví dụ, khi bạn phát triển ứng dụng sử dụng Nodejs, thì bạn có thể định nghĩa các phiên bản Nodejs khác nhau để đảm bảo rằng ứng dụng hoạt động đúng trên các version khác nhau.
  • Tích hợp các công cụ bên thứ ba: Bạn có thể tích hợp các công cụ bên thứ ba vào quá trình CI/CD của mình. Ví dụ, bạn có thể sử dụng GitHub Actions để triển khai ứng dụng của mình lên AWS hoặc Azure.
  • Truy cập các thông tin về quá trình CI/CD: Bạn cũng có thể truy cập các thông tin về quá trình CI/CD của bạn, bao gồm các thông tin về phiên bản mã nguồn, kết quả kiểm tra, và các lỗi phát hiện được. Điều này giúp bạn quản lý và phát triển mã nguồn của mình một cách hiệu quả hơn.

Thực Hành

Chạy pipeline CI/CD đầu tiên

Chuẩn bị

Cụ thể chúng ta sẽ thực hiện quy trình sau:

cicd.drawio (1).png

Hình 2: Thực hành

Giải thích: Sau khi chúng ta push code lên Github trên nhánh develop hoặc merged code vào nhánh đó thì GitHub Actions sẽ thực hiện các actions mà ta đã định nghĩa trong file pipeline.yml. Sau quá trình build và test thành công thì sẽ push image đã build lên DockerHub. Sau đó thực hiện việc deploy bằng cách SSH vào server và pull image vừa đẩy lên DockerHub

Note: Bởi vì tuỳ thuộc vào từng công ty và tuỳ thuộc vào mục đích sử dụng, nên có thể sẽ có những cách viết khác nhau. Mình sẽ lấy ví dụ 1 cách dễ hiểu và dễ tiếp cận nhất.

Trước khi chúng ta thực hiện việc viết pipeline đầu tiên thì chúng ta cần chuẩn bị:

  • Server. Mình sử dụng droplet của DigitalOcean, các bạn có thể sử dụng EC2 của AWS hoặc Virtual Machine của Google Could
  • Tài khoản DockerHub: DockerHub Token, các bạn có thể xem lại bài viết này của mình.

Đây là những thứ mình cần lấy để lưu lại vào trong Github Secrets:

  • DOCKERHUB_USERNAME
  • DOCKERHUB_TOKEN
  • SERVER_HOST
  • SERVER_USER
  • SSH_PRIVATE_KEY

Bước 1: Trước tiên cần lấy DOCKERHUB_USERNAMEDOCKERHUB_TOKEN.

  • DOCKERHUB_USERNAME ở đây chính là tuanops
  • DOCKERHUB_TOKEN ở đây chính là phần token bên dưới

image.png

Hình 3: Lấy thông tin trên Docker Hub

Bước 2: Tiếp theo cần chuẩn bị server, các bạn có thể tham khảo cách tạo Droplet ở docs của DigitalOcean ( docs rất chi tiết )

image.png

Hình 4: Tạo server trên DigitalOcean

Note: Phần Authentication Method các bạn phải chọn phương SSH để chúng ta có thể truy cập vào server thông qua giao thức SSH, nếu chưa add SSH Key thì các bạn hãy tạo mới theo hướng dẫn để generate ra public và private key.

Screenshot 2023-07-03 at 16.27.57.png

Hình 5: Hướng dẫn và thêm public key ở DigitalOcean

Sau khi làm xong các bước ở ảnh trên, thì các bạn di chuyển đến thư mục chứa folder .ssh của bạn:

image.png

Hình 6: Kết quả sau khi tạo

Các bạn hãy copy nội dung của file id_rsa.pub và ném vào phần SSH Key Content của DigitalOcean:

image.png

Hình 7: Thêm public key

Sau khi tạo server xong thì ta sẽ biết được thông tin như SERVER_HOST ( là IP Address ), SERVER_USER ( của DigitalOcean mặc định là root )

Screenshot 2023-07-03 at 16.42.18.png

Hình 8: Kết quả sau khi tạo thành công server

Còn SSH_PRIVATE_KEY chính là nội dung của file id_rsa ( hay các bạn còn có thể nghe nói nó là file pem để vào server ):

Screenshot 2023-07-03 at 16.46.28.png

Hình 9: Nội dung file private key

Vậy là đã chuẩn bị xong, giờ các bạn chỉ cần thêm cái biến secrets đó vào vào phần GitHub Secrects là được rồi

image.png

Hình 10: Lưu các biến vào secrets của GitHub

Đây là nơi lưu trữ các biến để dùng phục vụ cho việc viết pipeline

Note: Trước khi bắt cầu viết pipeline đầu tiên thì các bạn hãy SSH vào server cài sẵn Docker ở trên server nhé, các bạn có thể xem lại bài đầu tiên về Docker của mình. Các bạn truy cập vào server bằng lệnh ssh -i ./.ssh/id_rsa root@public_ip_server, các bạn hãy thay user và public_ip_server bằng Public IP Server của các bạn

image.png

Hình 11: Truy cập server thông qua SSH

Vậy là phần chuẩn bị đã xong, tiếp theo chúng ta đến phần thực hiện viết pipeline đầu tiên nhé!

Bắt đầu viết pipeline đầu tiên

Note: Mình dùng project Front End của bài hôm trước, ( các bạn hãy checkout sang nhánh feature/cicd hoặc nhánh develop để thấy toàn bộ code nhé)

Để thực hiện ý tưởng trên thì đây là file pipeline.yml của chúng ta:

name: Docker CI/CD Pipeline

# Xác định sự kiện trigger cho pipeline, trong trường hợp này là push lên nhánh develop
on:
  push:
    branches:
      - develop

# Định nghĩa các jobs cần thực hiện 
jobs:
   # Job 1: build và test
   # Job thực thi trên máy ảo ubuntu
  build_and_test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        # Sử dụng action "checkout" để sao copy code từ repository vào máy ảo Ubuntu
        uses: actions/checkout@v2

      - name: Login to Docker Hub
          # Sử dụng action "docker/login-action" để đăng nhập vào Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and test
        # Build và test image được build ra bằng Docker 
        run: |
          docker build -t reactjs-basic .
          docker run reactjs-basic npm test

      - name: Push image to Docker Hub
        uses: docker/build-push-action@v2
        # Sử dụng action "docker/build-push-action" để đẩy image lên Docker Hub
        with:
          context: .
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/reactjs-basic:${{ github.sha }}

 # Job 2: Deploy
  deploy:
    needs: build_and_test
    runs-on: ubuntu-latest

    steps:
      - name: Deploy to server
       # Sử dụng action "appleboy/ssh-action" để triển khai image lên server thông qua SSH
        uses: appleboy/ssh-action@v0.1.3
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            # Pull image mà được push lên DockerHub bên trên
            docker pull ${{ secrets.DOCKERHUB_USERNAME }}/reactjs-basic:${{ github.sha }}

            # Dừng và gỡ bỏ container hiện tại (nếu có)
            docker stop reactjs-container
            docker rm reactjs-container 

            # Chạy container mới với image đã pull về bên trên
            docker run -d -p 80:3000 --name reactjs-container ${{ secrets.DOCKERHUB_USERNAME }}/reactjs-basic:${{ github.sha }}

Giải thích:

  • Đầu tiên là mình đặt tên pipeline là "Docker CI/CD Pipeline".
  • Tiếp theo là xác định sự kiện trigger cho pipeline là push lên nhánh develop.
  • Job đầu tiênbuild_and_test:
    • Sử dụng máy ảo Ubuntu version mới nhất để thực hiện các bước bên dưới.
    • Sử dụng action "checkout" để sao copy code từ repository vào máy ảo Ubuntu.
    • Đăng nhập vào Docker Hub.
    • Build image Docker từ mã nguồn và đặt tên là reactjs-basic.
    • Chạy kiểm thử bằng lệnh npm test.
    • Khi thành công thì push image đã build lên Docker Hub.
  • Job thứ 2deploy :
    • Điều kiện của job này là sau khi job 1 hoàn thiện.
    • Deploy phiên bản mới trên server thông qua SSH.
    • Pull image vừa build về máy.
    • Stop và xoá container đang chạy.
    • Triển khai container mới, ánh xạ cổng 80 từ máy host vào cổng 3000 của container.

Note: Trong pipeline GitHub Actions, runs-on: ubuntu-latest chỉ định rằng môi trường thực thi pipeline là Ubuntu. Mặc định, môi trường Ubuntu đã được cài đặt sẵn Docker Engine, do đó chúng ta có thể sử dụng Docker trong pipeline mà không cần cài đặt thêm.

Ở lần push code đầu tiên thì mình sẽ hiển thị ra chữ Hello world, và đây là code của mình ở file App.tsx:

import "./App.css";

function App() {
  return (
    <div className="App">
      Hello world
    </div>
  );
}

export default App;

Và sau khi tạo merge nhánh feature/cicd vào nhánh develop thì quá trình trên được bắt đầu, các bạn có thể thấy:

Screenshot 2023-07-03 at 22.33.32.png

Hình 12: Các trạng thái của pipeline

Khi GitHub Actions được kích hoạt (trigger), pipeline có thể đi qua các trạng thái sau:

  • In Progress: Pipeline đang chạy trên tài nguyên chạy. Các bước (steps) trong pipeline đang được thực thi. ( màu vàng )
  • Success: Tất cả các bước trong pipeline đã hoàn thành mà không gặp lỗi. Pipeline được coi là thành công. ( màu xanh )
  • Cancelled: Pipeline đã bị hủy bỏ trước khi hoàn thành. Điều này có thể do hành động của người dùng hoặc cấu hình pipeline. ( màu xám )
  • Failure: Ít nhất một bước trong pipeline đã thất bại. Pipeline được coi là không thành công. ( màu đỏ )

Các trạng thái này cung cấp thông tin về quá trình thực thi của pipeline, cho phép chúng ta theo dõi và kiểm soát quy trình CI/CD của mình. Chúng ta có thể sử dụng các trạng thái này để xác định liệu pipeline đã thành công hay không và để xem thông tin chi tiết về các bước và lỗi trong quá trình chạy.

Sau khi pipeline thành công thì chúng ta hãy vào địa chỉ Public IP Server của mình, nếu hiển thị như sau thì chúc mừng bạn thành công chạy và hoàn thành pipeline đầu tiên của mình:

Screenshot 2023-07-03 at 22.31.31.png

Hình 13: Deploy thành công

Tiếp theo chúng ta sửa App.tsx 1 xíu để xác nhận quá trình CI/CD của mình thành công nhé:

import "./App.css";

function App() {
  return (
    <div className="App">
      Hello world
      <div>I'm Grey</div>
    </div>
  );
}

export default App;

Sau khi merge code vào nhánh develop thì quá trình CI/CD mới được bắt đầu, nếu kết quả sau khi bạn vào Public IP Server của mình hiển thị như sau là thành công:

image.png

Hình 14: Trạng thái pipeline

Screenshot 2023-07-03 at 22.37.52.png

Hình 15: Sau khi push code mới và deploy thành công

Note: các bạn hãy SSH vào server vào kiểm tra các container đang chạy bằng lệnh docker ps để xem kết quả nhé.

Vậy là chúng ta đã build 1 pipeline hoàn chỉnh và hoàn thành được yêu cầu bài toán đã đặt ra bên trên, từ giờ code của bạn luôn trong trạng thái Up-To-Date. Các bạn hãy tự thực hành lại bằng cách viết pipeline cho phần Back End thực hiện theo đúng như quy trình bên trên. Code Back End các bạn có thể tìm ở đây, các bạn hãy clone về và thực hiện bài thực hành này nhé

1 số lưu ý

Trong quá trình thực hiện bài thực hành trên, có 1 số điểm cần lưu ý như:

  • Nếu đã quy trình CI/CD đã thành công, nhưng khi bạn vào Public IP nhưng vẫn không hiển thị gì hoặc báo lỗi không tìm thấy thì các bạn có thể kiểm tra tường lửa hoặc đã public port ra bên ngoài chưa. Ở đây phải public port 80 của server. Nếu sử dụng AWS thì các bạn hãy tìm đến mục Security Group để sửa inbound, mở port 80 và cho phép truy cập từ bên ngoài. Còn DigitalOcean nếu không sửa gì đến phần NetWorking thì mặc định mở toàn bộ các port.

Best practices và tips:

  • Tách riêng các job: Sử dụng nhiều job trong pipeline để phân chia công việc thành các phần nhỏ hơn và nếu có job nào có thể chạy mà không liên quan đến job khác thì cho nó đồng thời thực thi song song, giúp tăng tốc quy trình CI/CD.
  • Sử dụng secrets: Sử dụng tính năng secrets của GitHub để lưu trữ và quản lý thông tin nhạy cảm như mật khẩu, khóa SSH. Đảm bảo các secrets được quản lý an toàn và không tiết lộ.
  • Review code kỹ trước khi push: Áp dụng quy trình kiểm tra code đầy đủ và kỹ lưỡng để đảm bảo chất lượng mã nguồn trước khi tích hợp vào quy trình CI/CD. Sử dụng các công cụ và tiện ích hỗ trợ như eslint, linter, unit tests để tìm và sửa lỗi kịp thời.
  • Test trước khi push: Chắc chắn rằng chạy lệnh test trước khi push, để tránh phát sinh các lỗi vặt phát sinh trong quá trình CI/CD.
  • Luôn dựa vào các logs: Khi các job được thực thi, chúng sẽ đẩy ra các logs giống như việc chúng ta thực hiện các bước ở máy chúng ta, dựa vào đó chúng ta có thể cải thiện, phát hiện lỗi trong suốt quá trình CI/CD thực hiện.

Kết luận

Qua bài viết này, chúng ta đã thấy được tác dụng khi áp dụng quy trình CI/CD vào phát triển phần mềm của mình, biết được những khái niệm căn bản, cách xây dựng lên 1 pipeline cho riêng mình và cũng như đã có cái nhìn đầu tiên về nó. Nó giúp tăng tốc quy trình phát triển phần mềm của chúng ta vì code trên server của chúng ta luôn được Up-To-Date, tiết kiệm thời gian vì không phải deploy thủ công bằng tay nữa.

Chúng ta đã hoàn thành bài viết CI/CD - GitHub Actions và các kiến thức cơ bản. Khi ứng dụng của các bạn đã lên server thực tế và mọi người khác đã có thể truy cập thông qua địa chỉ Public IP rồi, nhưng việc truy cập vào Public IP khá khó vì phải nhớ toàn bộ Public IP thì chúng ta mới có thể vào được. Chính vì thế bài tiếp theo mình sẽ chia sẻ các bạn về việc trỏ Domain về IP Server của bạn, và setup chứng chỉ TLS/SSL để chứng nhận web của bạn an toàn đối với người dùng nhé.
Cảm ơn các bạn đã ủng hộ mình và đã theo dõi đến cuối bài viết của mình 😄


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí