Ở bài viết trước, mình đã hướng dẫn các
bạn cách cài đặt và cấu hình Portainer để quản lý docker
image và container trên VPS. Hôm nay, mình sẽ tiếp tục ví dụ cách deploy trang web thông qua Portainer
, cũng như cách
để trang web này tự động deploy lại khi có commit thay đổi nhé (CI/CD ở mức cơ bản nhất)
Chuẩn bị ứng dụng
Để mình hoạ cho bài viết hôm nay, mình sẽ ví dụ bằng 1 trang web Hello world đơn giản viết bằng Sring Boot
. Tất nhiên
các bạn có thể sử dụng bất cứ ngôn ngữ và công nghệ nào các bạn yêu thích (NodeJS, Laravel, ...) miễn là các bạn có thể
build được docker image cho nó.
Bạn nào biết cách khởi tạo ứng dụng và build docker image cho ứng dụng spring boot rồi thì có thể bỏ qua phần này nhé.
Đầu tiên chúng ta cần khởi tạo ứng dụng Spring Boot mới tại đây: Spring Initializr
Các options mình chọn giống như hình:
Tiến hành khởi tạo và tải project về bằng cách nhấn vào nút Generate
Sau khi giải nén project, ta được cấu trúc thư mục như sau:
Ta tiến hành tạo class controler cho trang chủ trong package com.example.demo
package com.example.demo; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; @Controller
package com.example.demo; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping; @Controller
public class HomeController { @RequestMapping("/") public String home(final Model model) { model.addAttribute("message", "hello"); return "index"; } }
Tiếp theo tạo view bằng cách tạo file index.html
trong thư mục \src\main\resources\templates
như sau:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head> <meta charset="UTF-8"> <title>Spring Boot Hello world</title>
</head>
<body>
<h1>Hello world</h1>
<p th:text="'message: ' + ${message} + '!'"/>
</body>
</html>
Chạy thử ứng dụng bằng lệnh sau:
./gradle bootRun # hoặc 'gradle bootRun' nếu sử dụng Windows
Mở http://localhost:8080
ta được kết quả như sau:
Nếu mọi thứ đã ổn, hãy tiến hành tạo repository trên github của bạn và push code lên nhé.
Như vậy đã xong phần chuẩn bị ứng dụng, tiếp theo chúng ta hãy đến với cách sử dụng Github Action
để tự động thực hiện
build và push docker image lên registry của github
Sử dụng Github Action để tự động build docker image và push lên kho lưu trữ Github Packages (ghcr.io)
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)
Để sử dụng được Github Action, đầu tiên bạn cần tạo Personal Access Token cho tài khoản của bạn (nếu chưa có). Chi tiết cách tạo các bạn có thể xem hướng dẫn từ chính chủ Github ở đây
Để workflow có thể push docker image vào Github Packages
chúng ta cần cấp quyền write
cho nó. Truy cập vào
trang Settings
của repository của bạn, chọn Action
→ General
Kéo xuống mục Workflow permissions
, chọn vào option Read and write permissions
, sau đó nhấn Save
Bây giờ, ta tiến hành định nghĩa 1 workflow mới bằng cách tạo file .github/workflows/main.yml
. Nội dung như sau:
name: CI/CD
'on': push: branches: - main
jobs: build: runs-on: ubuntu-latest timeout-minutes: 40 steps: - name: Checkout uses: actions/checkout@v2 - name: Set up JDK 17 uses: actions/setup-java@v2 with: distribution: temurin java-version: 17 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Cache Gradle packages uses: actions/cache@v2 with: path: ~/.gradle/caches key: '${{ runner.os }}-gradle-${{ hashFiles(''**/*.gradle'') }}' restore-keys: '${{ runner.os }}-gradle' - name: Build Image run: | ./gradlew clean bootBuildImage --imageName=app:latest - name: Login to GitHub Container Registry uses: docker/login-action@v2 with: registry: ghcr.io username: '${{ github.actor }}' password: '${{ secrets.GITHUB_TOKEN }}' - name: Push image to GitHub Container Registry run: | docker tag app:latest ghcr.io/${{ github.actor }}/${{ github.repository }}:latest docker push ghcr.io/${{ github.actor }}/${{ github.repository }}:latest
Sau đó tiến hành commit và push lên branch main
của bạn.
Truy cập vào mục Actions
trong repository, ta thấy workflow đã được kích hoạt và chạy:
Các bạn có thể nhấn vào workflow đó để xem log quá trình chạy. Việc của bạn hiện giờ là chỉ cần chờ vài phút cho đến khi workflow chạy hoàn tất
Quay lại trang chính của Repository, ta thấy đã tạo được image như hình
Tiến hành deploy docker image lên Portainer trên VPS
Theo đúng quy trình GitOps được mô tả trên trang chủ của Portainer, thì chúng ta cần tạo thêm config-repo, sau đó cấu hình Portainer liên kết với repo này. Tuy nhiên, trong bài viết này, để cho đơn giản thì mình sử dụng chung 1 repository với source code luôn nhé
Chúng ta sẽ triển khai bằng cách sử dụng tính năng Stack
của Portainer. Stack sử dụng cú pháp docker compose nên chúng
ta cần tạo file docker-compose.yml
ngay trong thư mục gốc của project sau đó tiến hành commit push file này lên
version: '3.8'
services: app: image: ghcr.io/ndanhkhoi/spring-demo # Thay bằng image của bạn theo đã buikd ở bước trên ports: - "8080:8080"
Các bạn lưu ý nhớ thay tên image đã build ở bước trên vào sau
ghcr.io/
nhé. Tên image trong workflow mình đã cấu hình là repository name luôn, nên cú pháp sẽ làghcr.io/<username>/<repository>
, ví dụ của mình làndanhkhoi/spring-demo
TIP: Khi commit, các bạn thêm
[skip ci]
vào trước message, ví dụ[skip ci] add docker-compose.yml
để bỏ qua quá trình chạy workflow vì thêm file docker-compose.yml này không ảnh hưởng đến code nên chung ta không cần build lại image
Tiến hành thêm Github Registry
vào Portainer của bạn, truy cập Portainer, vào menu Registries
, nhấn
nút Add registry
Chọn Custom registry, nhập vào các thông tin sau:
- Name: ghcr.io
- Registry URL: ghcr.io
- Bật tuỳ chọn Authentication
- Username: username github của bạn
- Password: Personal access token của bạn (nhớ là không phải mật khẩu github nha)
Nhấn Add registry
Tiếp theo, chúng ta tiến hành thêm stack và deploy ứng dụng. Truy cập menu Stack
, sau đó nhấn chọn Add stack
Chọn sang Use a git repository
, nhập các thông tin cần thiết
- Tên stack (tuỳ ý)
- Bật tuỳ chọn authentication
- Username và Personal Access Token của bạn
- Repository URL
- Các thông tin
Repository reference
vàCompose path
để mặc định - Bật tuỳ chọn
GitOps updates
- Mục
Mechanism
các bạn chọn sangWebhook
, sau đó nhấnCopy link
. Các bạn dán link này ra đâu đó để lưu lại sử dụng cho bước sau nhé
- Sau đó nhấn
Deploy stack
và chờ vài phút để Portainer tiến hành pull image về và deploy
Sau khi có thông báo deploy thành công, các bạn tiến hành truy cập:
http://<IP_ADDRESS>:8080
Tadaaaa! Ứng dụng spring boot của bạn đã được deploy thành công trên VPS
Cấu hình tự động deploy lại khi có commit thay đổi (GitOps updates)
Truy cập repository của bạn, tiến hành tạo secrets mới. Vào Settings
→ Secrets and variables
→ Actions
→ New repository secret
- Mục name nhập:
PORTAINER_STACK_WEBHOOK
- Mục secret nhập URL ở bước trên các bạn đã Copy khi tạo Stack
Điều chỉnh workflow gọi webhook sau khi build và push xong image, file .github/workflows/main.yml
sau khi chỉnh sửa:
name: CI/CD
'on': push: branches: - main
jobs: build: runs-on: ubuntu-latest timeout-minutes: 40 steps: - name: Checkout uses: actions/checkout@v2 - name: Set up JDK 17 uses: actions/setup-java@v2 with: distribution: temurin java-version: 17 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Cache Gradle packages uses: actions/cache@v2 with: path: ~/.gradle/caches key: '${{ runner.os }}-gradle-${{ hashFiles(''**/*.gradle'') }}' restore-keys: '${{ runner.os }}-gradle' - name: Build Image run: | ./gradlew clean bootBuildImage --imageName=app:latest - name: Login to GitHub Container Registry uses: docker/login-action@v2 with: registry: ghcr.io username: '${{ github.actor }}' password: '${{ secrets.GITHUB_TOKEN }}' - name: Push image to GitHub Container Registry run: | docker tag app:latest ghcr.io/${{ github.repository }}:latest docker push ghcr.io/${{ github.repository }}:latest - name: Send portainer webhook run: | curl --insecure --connect-timeout 60 -X POST ${{ secrets.PORTAINER_STACK_WEBHOOK }}
Thử chỉnh sửa file \src\main\resources\templatesindex.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head> <meta charset="UTF-8"> <title>Spring Boot Hello world</title>
</head>
<body>
<h1>Hello world</h1>
<p th:text="'message: ' + ${message} + '!'"/>
<p> CI/CD demo
</p>
</body>
</html>
Sau đó tiến hành commit, đợi và kiểm tra workflow chạy xong. Tiến hành F5 và kiểm tra lại trang web chúng ta vừa deploy:
Đã xuất hiện dòng chữ CI/CD demo
chúng ta vừa thêm vào vùa nảy ^^
Vậy là xong, từ nay, khi có cần chỉnh sửa gì trang web, bạn chỉ việc commit thay đổi lên github mà không cần phải làm thêm bất cứ thao tác nào cả, website của bạn sẽ được tự động cập nhật các thay đổi sau ít phút. Ngon lành cành đào rồi 🤣
Tổng kết
Cảm ơn các bạn đã đọc đến dòng này, đây toàn bộ đều là những gì mình đã tự mài mò được khi phát triển một trang web cá nhân của mình. Bài viết hôm nay hơi dài, do có nhiều nội dung, mình đã cố gắng viết ngắn gọn và đơn giản nhất có thể. Những gì mình chia sẻ ở bài viết này là ở mức độ cơ bản, minh hoạ cho quá trình CI/CD dựa vào Github Action và Portainer, các bạn có thể dựa vào từng bước mình đã liệt kê trong bài để tìm hểu và nghiên cứu sâu hơn nếu có hứng thú nhé.
See you next time !