+3

[CI-CD] Triển khai CICD đơn giản cho Raspberry Pi

Trên công ty có ông bạn mang con Raspberry Pi B+ lên công ty dựng server để giám sát mạng, thế là mình xin ké 1 chân vào để deploy gì đấy vui vui 😁

Thế là bắt đầu tìm ý tưởng lặt vặt dựng con server nodejs gì đấy lên cho vui

Nhưng đến đoạn deploy lên, thì thấy nếu update code phải pull git về rồi chạy lại mất thời gian quá, bèn nghĩ ra kế để deploy tự động 1 cách đơn giản nhất

Ý tưởng bắt đầu nhen nhóm bằng việc từ source code mình dùng docker build ra image và push thẳng lên docker hub, và trên raspberry pi mình chạy script python để kiểm tra docker hub có update phiên bản image mới ko, nếu có thì pull image về và restart lại container 😅

Repository cho các bạn tham khảo: Repository Demo

Triển thôi nào 🤪

Tạo docker file để build image từ source nodejs

FROM node:latest

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY dist/ ./dist/

EXPOSE 3000

CMD ["node", "dist/app.js"]

Tạo github workflow để build và push image lên Docker Hub

name: Deploy to Docker Hub

on:
  push:
    branches:
      - main

jobs:
  send-notification-started:
    runs-on: ubuntu-latest
    steps:
      - name: Send Telegram Notification
        env:
          TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          TELEGRAM_CHAT_ID: "${{ secrets.TELEGRAM_GROUP_DEPLOYMENTS }}"
        run: |
          curl -X POST \
            https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage \
            -d chat_id=${TELEGRAM_CHAT_ID} \
            -d text="🚀 <b>Sms Bot</b> Deployment has started!" \
            -d parse_mode=HTML

  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '14'

      - name: Install dependencies and build
        run: |
          npm install
          npm run build

      - name: Login to Docker Hub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Set up QEMU
        run: |
          sudo apt-get update
          sudo apt-get install -y qemu-user-static

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Build image for aarch64
        run: |
          docker buildx create --name builder --use
          docker buildx inspect builder --bootstrap
          docker buildx build --platform linux/arm64 -t ${{ secrets.DOCKER_HUB_IMAGE_NAME }}:latest --push .

  send-notification-successful:
    needs: build
    runs-on: ubuntu-latest
    if: ${{ success() && needs.build.result == 'success' }}
    steps:
      - name: Send Telegram Notification
        env:
          TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          TELEGRAM_CHAT_ID: "${{ secrets.TELEGRAM_GROUP_DEPLOYMENTS }}"
        run: |
          curl -X POST \
            https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage \
            -d chat_id=${TELEGRAM_CHAT_ID} \
            -d text="🎉 <b>Sms Bot</b> Deployment to docker hub was successful!" \
            -d parse_mode=HTML

  send-notification-failed:
    needs: build
    runs-on: ubuntu-latest
    if: ${{ failure() && needs.build.result == 'failure' }}
    steps:
      - name: Send Telegram Notification
        env:
          TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          TELEGRAM_CHAT_ID: "${{ secrets.TELEGRAM_GROUP_DEPLOYMENTS }}"
        run: |
          curl -X POST \
            https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage \
            -d chat_id=${TELEGRAM_CHAT_ID} \
            -d text="❌ Oh no! <b>Sms Bot</b> Deployment failed!
            There might be something wrong with the process. 
            Please check it out! 🛠️🔍" \
            -d parse_mode=HTML

Script python để pull và restart Docker image

import docker
import time
import logging
from logging.handlers import RotatingFileHandler
from telegram import Bot
import requests

TELEGRAM_BOT_TOKEN = ''
TELEGRAM_CHAT_ID = ''

def send_telegram_message(message):
    bot = Bot(token=TELEGRAM_BOT_TOKEN)
    bot.send_message(chat_id=TELEGRAM_CHAT_ID, text=message, parse_mode='HTML')

log_formatter = logging.Formatter('%(asctime)s - %(levelname)s: %(message)s')
log_handler = RotatingFileHandler('logs.log', maxBytes=1024 * 1024, backupCount=5)
log_handler.setFormatter(log_formatter)
logger = logging.getLogger()
logger.addHandler(log_handler)
logger.setLevel(logging.INFO)

def get_image_info(image_name):
    try:
        response = requests.get(f"https://hub.docker.com/v2/repositories/{image_name}/tags/latest")
        if response.status_code == 200:
            image_info = response.json()
            return image_info
        else:
            print(f"Failed to get image info, status code: {response.status_code}")
            return None
    except requests.RequestException as e:
        print(f"Request error: {e}")
        return None

def check_and_update_image(container_name, image_name):
    client = docker.from_env()

    logger.info(f"Start pull image..")

    try:
        version = get_image_info(image_name)
        digest = image_name + '@' + version['digest']
        
        container = client.containers.get(container_name)
        current_image_id = container.image.id
        current_image_digest = container.image.attrs['RepoDigests'][0] if 'RepoDigests' in container.image.attrs else None

        logger.info(f"current_image_id {current_image_id}")
        logger.info(f"current_image_digest {current_image_digest}")
        logger.info(f"digest {digest}")

        if current_image_digest != digest:
            logger.info("Updating...")
            send_telegram_message("🚀 <b>Auto Pull Docker - Raspberry pi</b> Deployment has started!")
            client.images.pull(image_name)
            container.stop()
            container.remove()

            client.containers.run(image_name, detach=True, name=container_name, volumes={
                '/home/dongtran/py/.env': {'bind': '/usr/src/app/.env', 'mode': 'rw'}
            })
            logger.info("Container update successful.")
            send_telegram_message("🚀 <b>Auto Pull Docker - Raspberry pi</b> Deployment on raspberry pi sucessful!")
        else:
            logger.info("No action.")
    except docker.errors.NotFound as e:
        logger.error(f"Container '{container_name}' not found: {e}")
        return
    except docker.errors.APIError as e:
        logger.error(f"APIError: {e}")
        return
    except docker.errors.ImageNotFound:
        logger.error(f"Image '{image_name}' not exist")
        return

if __name__ == "__main__":
    container_name = ""
    image_name = ""

    while True:
        check_and_update_image(container_name, image_name)
        time.sleep(30)

Như vậy chúng ta đã có thể deploy ứng dụng 1 cách tự động trên raspberry pi rồi 🥲

Các bạn có thắc mắc comment bên dưới nha 😍😍


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.