name: OpenIM Chat 构建和发布 on: push: branches: [ main,wallet, master, develop, release-* ] paths: - '**' pull_request: branches: [ main,wallet, master ] paths: - '**' release: types: [published] workflow_dispatch: inputs: tag: description: "Tag version to be used for Docker image" required: true default: "prod" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: REGISTRY: docker.io DOCKER_USER: ${{ secrets.DOCKER_USERNAME || 'openim' }} GO_VERSION: "1.24" jobs: build-and-push: runs-on: openim permissions: contents: read packages: write steps: - name: 检出代码 uses: actions/checkout@v4 with: submodules: recursive - name: 设置 SSH 密钥 run: | echo "🔑 配置 SSH 密钥..." # 创建 .ssh 目录 mkdir -p ~/.ssh chmod 700 ~/.ssh # 解码 base64 编码的 SSH 私钥 echo "${{ secrets.SSH_PRIVATE_KEY }}" | base64 -d > ~/.ssh/id_rsa # 验证解码是否成功 if [ ! -s ~/.ssh/id_rsa ]; then echo "❌ SSH 私钥解码失败或为空" exit 1 fi # 设置权限 chmod 600 ~/.ssh/id_rsa # 配置 SSH 主机密钥验证(可选,根据需要调整) ssh-keyscan git.imall.cloud >> ~/.ssh/known_hosts 2>/dev/null || true chmod 644 ~/.ssh/known_hosts echo "✅ SSH 密钥配置完成" - name: 检出 protocol 仓库 run: | # 克隆 protocol 仓库到当前目录的 protocol 子目录(使用 SSH) git clone git@git.imall.cloud:openim/protocol.git ./protocol || true cd ./protocol git fetch --all git checkout v1.0.4 - name: 设置 Go 缓存 uses: actions/cache@v4 with: path: | ~/.cache/go-build ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: 设置 Go 环境 uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} cache: true - name: 设置 Docker Buildx uses: docker/setup-buildx-action@v3.8.0 with: platforms: linux/amd64 - name: 登录到 Docker Hub uses: docker/login-action@v3.1.0 with: username: ${{ env.DOCKER_USER }} password: ${{ secrets.DOCKER_PASSWORD }} - name: 构建和推送 Docker 镜像 run: | set -e # 设置版本标签 - 默认使用 prod if [ "${{ github.event_name }}" = "release" ]; then VERSION_TAG="${{ github.event.release.tag_name }}" elif [ -n "${{ github.event.inputs.tag }}" ]; then VERSION_TAG="${{ github.event.inputs.tag }}" elif [ "${{ github.ref }}" = "refs/heads/main" ] || [ "${{ github.ref }}" = "refs/heads/master" ]; then VERSION_TAG="prod" else VERSION_TAG="prod" # 所有分支默认都使用 prod fi echo "版本标签: $VERSION_TAG" echo "Docker用户: $DOCKER_USER" # 获取CPU核心数 CPU_CORES=$(nproc) echo "CPU核心数: $CPU_CORES" # 定义服务列表 API_SERVICES=( "openim-chat-api" ) RPC_SERVICES=( "openim-chat-rpc" ) ADMIN_SERVICES=( "openim-admin-api" "openim-admin-rpc" ) # 合并所有服务 ALL_SERVICES=("${API_SERVICES[@]}" "${RPC_SERVICES[@]}" "${ADMIN_SERVICES[@]}") # 资源监控函数 monitor_resources() { local cpu_usage=0 local mem_usage=0 local load_1min=0 # 获取CPU使用率 if command -v top >/dev/null 2>&1; then cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | sed 's/%us,//' | cut -d. -f1) elif [ -f /proc/stat ]; then local prev_idle=$(cat /tmp/cpu_idle 2>/dev/null || echo "0") local prev_total=$(cat /tmp/cpu_total 2>/dev/null || echo "0") local curr_cpu=($(cat /proc/stat | head -1)) local curr_idle=${curr_cpu[4]} local curr_total=0 for val in "${curr_cpu[@]:1}"; do curr_total=$((curr_total + val)) done local diff_idle=$((curr_idle - prev_idle)) local diff_total=$((curr_total - prev_total)) local diff_usage=$((diff_total - diff_idle)) if [ $diff_total -gt 0 ]; then cpu_usage=$((diff_usage * 100 / diff_total)) fi echo "$curr_idle" > /tmp/cpu_idle echo "$curr_total" > /tmp/cpu_total fi # 限制CPU使用率在0-100%之间 if [ "$cpu_usage" -gt 100 ]; then cpu_usage=100 elif [ "$cpu_usage" -lt 0 ]; then cpu_usage=0 fi # 获取内存使用率 if command -v free >/dev/null 2>&1; then mem_usage=$(free | awk 'NR==2{printf "%.0f", $3*100/$2}') fi # 获取1分钟负载 if [ -f /proc/loadavg ]; then load_1min=$(cat /proc/loadavg | awk '{print $1}' | cut -d. -f1) fi echo "CPU: ${cpu_usage}%, 内存: ${mem_usage}%, 负载: ${load_1min}" } # 检查资源状态 check_resources() { local cpu_usage=$(monitor_resources | awk '{print $1}' | sed 's/CPU://' | sed 's/%//') local mem_usage=$(monitor_resources | awk '{print $2}' | sed 's/内存://' | sed 's/%//') local load_1min=$(monitor_resources | awk '{print $3}' | sed 's/负载://') # 如果CPU使用率超过90%或内存使用率超过85%,等待资源释放 if [ "$cpu_usage" -gt 90 ] || [ "$mem_usage" -gt 85 ]; then echo "资源使用率过高,等待释放..." local wait_count=0 local max_wait=20 while [ $wait_count -lt $max_wait ]; do sleep 5 wait_count=$((wait_count + 1)) # 清理Docker缓存 if [ $((wait_count % 5)) -eq 0 ]; then docker system prune -f >/dev/null 2>&1 || true docker builder prune -f >/dev/null 2>&1 || true fi cpu_usage=$(monitor_resources | awk '{print $1}' | sed 's/CPU://' | sed 's/%//') mem_usage=$(monitor_resources | awk '{print $2}' | sed 's/内存://' | sed 's/%//') if [ "$cpu_usage" -le 90 ] && [ "$mem_usage" -le 85 ]; then echo "资源已释放,继续构建" break fi echo "等待资源释放... ($wait_count/$max_wait)" done if [ $wait_count -eq $max_wait ]; then echo "警告:等待超时,继续执行构建" fi fi } # 构建服务函数 build_service() { local service=$1 local dockerfile="build/images/$service/Dockerfile" echo "检查服务: $service" echo " Dockerfile 路径: $dockerfile" echo " Dockerfile 是否存在: $([ -f "$dockerfile" ] && echo "是" || echo "否")" # 检查资源状态 check_resources if [ -f "$dockerfile" ]; then echo "📦 开始构建 Docker 镜像: $service" echo " 📁 Dockerfile: $dockerfile" echo " 🏷️ 版本标签: $VERSION_TAG" echo " 👤 Docker用户: $DOCKER_USER" # 降低日志噪音:不打印 buildx 状态 # 构建和推送 Docker 镜像 echo " 尝试使用 buildx 构建..." # 降低日志噪音:省略上下文验证输出 local start_time=$(date +%s) # 尝试使用 buildx 构建 # 使用 git commit SHA 作为构建参数,确保代码变化时缓存失效 BUILD_SHA=$(git rev-parse --short HEAD) BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") if docker buildx build \ --platform linux/amd64 \ --file "$dockerfile" \ --tag "$DOCKER_USER/$service:$VERSION_TAG" \ --push \ --cache-from type=gha,scope=openim-chat-build-${{ github.ref_name }}-$service \ --cache-to type=gha,mode=max,scope=openim-chat-build-${{ github.ref_name }}-$service \ --build-arg BUILDKIT_INLINE_CACHE=1 \ --build-arg GOCACHE=/go-cache \ --build-arg GOMAXPROCS=$CPU_CORES \ --build-arg BUILD_SHA=$BUILD_SHA \ --build-arg BUILD_TIME=$BUILD_TIME \ --provenance=false \ --sbom=false \ . > "/tmp/build_${service}.log" 2>&1; then local end_time=$(date +%s) local duration=$((end_time - start_time)) echo "" echo "✅ 构建成功: $service" echo " ⏱️ 耗时: ${duration}秒" echo " 🏷️ 镜像: $DOCKER_USER/$service:$VERSION_TAG" echo " 📤 推送状态: 已完成" else echo " buildx 构建失败,查看详细错误日志..." echo " Buildx 构建日志:" if [ -f "/tmp/build_${service}.log" ]; then cat "/tmp/build_${service}.log" fi echo " 尝试使用传统 docker build..." # 回退到传统 docker build local build_cmd="docker build --file $dockerfile --tag $DOCKER_USER/$service:$VERSION_TAG --build-arg BUILDKIT_INLINE_CACHE=1 --build-arg GOCACHE=/go-cache --build-arg GOMAXPROCS=$CPU_CORES ." echo " 传统构建命令: $build_cmd" if eval "$build_cmd" > "/tmp/traditional_build_${service}.log" 2>&1; then echo " 传统构建成功,推送镜像..." docker push "$DOCKER_USER/$service:$VERSION_TAG" || echo "推送 $VERSION_TAG 失败" local end_time=$(date +%s) local duration=$((end_time - start_time)) echo "" echo "✅ 构建成功: $service (传统构建)" echo " ⏱️ 耗时: ${duration}秒" echo " 🏷️ 镜像: $DOCKER_USER/$service:$VERSION_TAG" echo " 📤 推送状态: 已完成" else local end_time=$(date +%s) local duration=$((end_time - start_time)) echo "" echo "❌ 构建失败: $service" echo " ⏱️ 耗时: ${duration}秒" echo " 🔍 查看详细错误日志:" echo "=== 详细错误日志 ===" echo "服务: $service" echo "Dockerfile: $dockerfile" echo "构建命令:" echo "docker buildx build --platform linux/amd64 --file $dockerfile --tag $DOCKER_USER/$service:$VERSION_TAG --push --quiet --cache-from type=gha,scope=openim-chat-build-${{ github.ref_name }}-$service --cache-to type=gha,mode=max,scope=openim-chat-build-${{ github.ref_name }}-$service --build-arg BUILDKIT_INLINE_CACHE=1 --build-arg GOCACHE=/go-cache --build-arg GOMAXPROCS=$CPU_CORES --provenance=false --sbom=false ." echo "" echo "Buildx 构建日志:" if [ -f "/tmp/build_${service}.log" ]; then cat "/tmp/build_${service}.log" fi echo "" echo "传统构建日志:" if [ -f "/tmp/traditional_build_${service}.log" ]; then cat "/tmp/traditional_build_${service}.log" fi echo "=== 错误日志结束 ===" return 1 fi fi else echo "❌ 错误: 找不到 Dockerfile: $dockerfile" echo " 当前目录: $(pwd)" echo " 目录内容: $(ls -la build/images/ 2>/dev/null || echo 'build/images 目录不存在')" echo " 查找所有 Dockerfile: $(find . -name "Dockerfile" -type f 2>/dev/null | head -5)" return 1 fi } # 初始化构建统计 failed_services=() successful_services=() # 计算总服务数 total_services=$((${#API_SERVICES[@]} + ${#RPC_SERVICES[@]} + ${#ADMIN_SERVICES[@]})) echo "" echo "🎯 开始构建 OpenIM Chat 镜像" echo "📊 构建统计:" echo " 📦 API服务: ${#API_SERVICES[@]} 个" echo " 🔧 RPC服务: ${#RPC_SERVICES[@]} 个" echo " 👑 管理服务: ${#ADMIN_SERVICES[@]} 个" echo " 📈 总计: $total_services 个服务" echo " 🏷️ 版本标签: $VERSION_TAG" echo " 👤 Docker用户: $DOCKER_USER" echo "" # 先构建API服务 echo "API服务构建:" echo "总服务数: ${#API_SERVICES[@]}" echo "==========================================" for i in "${!API_SERVICES[@]}"; do service="${API_SERVICES[$i]}" current=$((i + 1)) total=${#API_SERVICES[@]} echo "" echo "🚀 [${current}/${total}] 开始构建: $service" echo "⏰ 开始时间: $(date '+%H:%M:%S')" echo "------------------------------------------" if build_service "$service"; then successful_services+=("$service") echo "✅ [${current}/${total}] 构建完成: $service" else failed_services+=("$service") echo "❌ [${current}/${total}] 构建失败: $service" fi echo "⏰ 完成时间: $(date '+%H:%M:%S')" echo "------------------------------------------" done # 构建RPC服务 echo "RPC服务构建:" echo "总服务数: ${#RPC_SERVICES[@]}" echo "==========================================" for i in "${!RPC_SERVICES[@]}"; do service="${RPC_SERVICES[$i]}" current=$((i + 1)) total=${#RPC_SERVICES[@]} echo "" echo "🚀 [${current}/${total}] 开始构建: $service" echo "⏰ 开始时间: $(date '+%H:%M:%S')" echo "------------------------------------------" if build_service "$service"; then successful_services+=("$service") echo "✅ [${current}/${total}] 构建完成: $service" else failed_services+=("$service") echo "❌ [${current}/${total}] 构建失败: $service" fi echo "⏰ 完成时间: $(date '+%H:%M:%S')" echo "------------------------------------------" done # 构建管理服务 echo "管理服务构建:" echo "总服务数: ${#ADMIN_SERVICES[@]}" echo "==========================================" for i in "${!ADMIN_SERVICES[@]}"; do service="${ADMIN_SERVICES[$i]}" current=$((i + 1)) total=${#ADMIN_SERVICES[@]} echo "" echo "🚀 [${current}/${total}] 开始构建: $service" echo "⏰ 开始时间: $(date '+%H:%M:%S')" echo "------------------------------------------" if build_service "$service"; then successful_services+=("$service") echo "✅ [${current}/${total}] 构建完成: $service" else failed_services+=("$service") echo "❌ [${current}/${total}] 构建失败: $service" fi echo "⏰ 完成时间: $(date '+%H:%M:%S')" echo "------------------------------------------" done # 输出构建结果统计 echo "" echo "🎉 构建完成总结" echo "==========================================" echo "✅ 成功构建: ${#successful_services[@]} 个服务" echo "❌ 构建失败: ${#failed_services[@]} 个服务" echo "📊 成功率: $(( ${#successful_services[@]} * 100 / total_services ))%" echo "" if [ ${#successful_services[@]} -gt 0 ]; then echo "✅ 成功构建的服务:" for service in "${successful_services[@]}"; do echo " - $service" done echo "" fi if [ ${#failed_services[@]} -gt 0 ]; then echo "❌ 构建失败的服务:" for service in "${failed_services[@]}"; do echo " - $service" done echo "" fi if [ ${#failed_services[@]} -gt 0 ]; then echo "" echo "失败服务错误日志:" for service in "${failed_services[@]}"; do echo "=== $service 错误日志 ===" if [ -f "/tmp/build_${service}.log" ]; then echo "Buildx 构建日志:" cat "/tmp/build_${service}.log" fi if [ -f "/tmp/traditional_build_${service}.log" ]; then echo "传统构建日志:" cat "/tmp/traditional_build_${service}.log" fi echo "=== $service 错误日志结束 ===" echo "" done fi # 如果有失败的服务,退出码为1 if [ ${#failed_services[@]} -gt 0 ]; then echo "❌ 构建失败,有 ${#failed_services[@]} 个服务构建失败" exit 1 else echo "🎉 所有服务构建成功!" fi - name: 配置 kubectl if: success() run: | echo "🔧 配置 kubectl 连接到阿里云 ACK..." # 创建 kubeconfig 目录 mkdir -p ~/.kube # 直接解码 base64 内容 echo "解码 KUBECONFIG..." echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config # 验证解码是否成功 if [ ! -s ~/.kube/config ]; then echo "❌ KUBECONFIG 解码失败或为空" exit 1 fi # 设置权限 chmod 600 ~/.kube/config echo "✅ kubeconfig 配置完成" # 降低日志噪音:不输出 kubeconfig 内容 - name: 部署到阿里云 ACK if: success() run: | echo "🚀 开始部署到阿里云 ACK..." # 简单验证 kubectl if ! kubectl version --client >/dev/null 2>&1; then echo "❌ kubectl 不可用,跳过部署步骤" exit 0 fi echo "✅ kubectl 可用,尝试部署..." # 获取版本标签 if [ "${{ github.event_name }}" = "release" ]; then VERSION_TAG="${{ github.event.release.tag_name }}" else VERSION_TAG="${{ github.event.inputs.tag || github.ref_name || 'prod' }}" fi DOCKER_USER="${{ env.DOCKER_USER }}" NS=${NS:-default} echo "部署参数:" echo "版本标签: $VERSION_TAG" echo "Docker用户: $DOCKER_USER" # 检查部署文件是否存在 DEPLOY_DIR=".gitea/deployments/deploy" if [ ! -d "$DEPLOY_DIR" ]; then echo "❌ 部署目录不存在: $DEPLOY_DIR" echo "📁 当前目录内容:" ls -la exit 1 fi echo "📁 部署目录: $DEPLOY_DIR" echo "使用命名空间: $NS" # 创建命名空间(如果不存在) kubectl get ns "$NS" >/dev/null 2>&1 || kubectl create ns "$NS" kubectl config set-context --current --namespace="$NS" # 尝试部署,如果失败则继续 echo "🚀 开始部署 Chat 服务..." # 定义所有部署文件 DEPLOY_FILES=( "openim-chat-api-deployment.yml" "openim-chat-api-service.yml" "openim-chat-rpc-deployment.yml" "openim-chat-rpc-service.yml" "openim-admin-api-deployment.yml" "openim-admin-api-service.yml" "openim-admin-rpc-deployment.yml" "openim-admin-rpc-service.yml" ) # 部署所有文件 for file in "${DEPLOY_FILES[@]}"; do if [ -f "$DEPLOY_DIR/$file" ]; then echo "📄 部署文件: $file" # 更新镜像标签 sed -i "s|image: .*/openim-|image: $DOCKER_USER/openim-|g" "$DEPLOY_DIR/$file" sed -i "s|:latest|:$VERSION_TAG|g" "$DEPLOY_DIR/$file" # 应用文件 kubectl apply -f "$DEPLOY_DIR/$file" || echo "⚠️ $file 部署失败,但继续" else echo "⚠️ $file 不存在,跳过" fi done echo "" echo "🔍 验证部署状态..." # 简单检查,如果失败也不阻塞流程 kubectl get pods -l app=openim-chat-api || echo "⚠️ 无法获取 chat-api Pod 状态" kubectl get pods -l app=openim-chat-rpc || echo "⚠️ 无法获取 chat-rpc Pod 状态" echo "✅ 验证完成" - name: 清理 if: always() run: | echo "🧹 清理临时文件..." rm -f /tmp/build_*.log echo "✅ 清理完成"