name: OpenIM Server 构建和发布 on: push: branches: [ main, master, wallet, develop, release-* ] paths: - '**' pull_request: branches: [ main, master, wallet ] paths: - '**' release: types: [published] workflow_dispatch: inputs: tag: description: "Tag version to be used for Docker image" required: true default: "latest" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: REGISTRY: docker.io # Docker Hub 个人命名空间(需与 DOCKER_USERNAME 一致) DOCKER_USER: kim6789 # Docker Hub 凭证来自仓库 Secrets DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 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 # 从 secrets 中获取 SSH 私钥并 base64 解码 echo "${{ secrets.SSH_PRIVATE_KEY }}" | base64 -d > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa # 配置 SSH 主机密钥验证(可选,避免首次连接时的确认提示) ssh-keyscan -H git.imall.cloud >> ~/.ssh/known_hosts 2>/dev/null || true chmod 644 ~/.ssh/known_hosts # 验证 SSH 密钥 if [ -f ~/.ssh/id_rsa ]; then echo "✅ SSH 密钥配置成功" else echo "❌ SSH 密钥配置失败" exit 1 fi - name: 检出 protocol 仓库 run: | echo "📦 克隆 protocol 仓库..." # 使用 SSH 方式克隆 protocol 仓库 git clone git@git.imall.cloud:openim/protocol.git ./protocol || true cd ./protocol git fetch --all git checkout v1.0.4 cd .. # 创建父目录的protocol符号链接,使replace ../protocol 能正确找到 ln -sf $PWD/protocol ../protocol || true echo "✅ protocol 仓库检出完成" - 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: 检查 Go 环境 run: | if command -v go &> /dev/null; then echo "Go: $(go version)" go mod verify >/dev/null 2>&1 go mod tidy >/dev/null 2>&1 go mod download >/dev/null 2>&1 echo "Go modules verified" else echo "Go not found, skipping Go steps" fi - name: 检查系统资源 run: | echo "系统资源信息:" echo "CPU核心数: $(nproc)" echo "内存总量: $(free -h | awk '/^Mem:/ {print $2}')" echo "可用内存: $(free -h | awk '/^Mem:/ {print $7}')" echo "磁盘空间: $(df -h / | awk 'NR==2 {print $4}')" echo "" - name: 清理 Docker 环境 run: | echo "清理Docker环境..." docker image prune -f >/dev/null 2>&1 || true - name: 登录到 Docker Hub uses: docker/login-action@v3.3.0 with: registry: ${{ env.REGISTRY }} # 使用账号登录,镜像仍推送到 DOCKER_USER 命名空间 username: ${{ env.DOCKER_USERNAME }} password: ${{ env.DOCKER_PASSWORD }} - name: 设置 Docker Buildx uses: docker/setup-buildx-action@v3.8.0 with: driver-opts: | image=moby/buildkit:buildx-stable-1 network=host - name: 构建和推送所有服务镜像 working-directory: ${{ github.workspace }} run: | # 设置错误处理 trap 'echo "脚本在行 $LINENO 处失败,退出状态: $?"' ERR set -e # 获取版本标签(使用分支名) 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 }}" else # 使用分支名作为标签 VERSION_TAG="${{ github.ref_name }}" fi echo "构建OpenIM Server镜像 (标签: $VERSION_TAG)" echo "Docker用户: $DOCKER_USER" # 定义构建顺序(openim-msggateway 和 openim-push 优先构建) CORE_SERVICES=( "openim-msggateway" "openim-push" "openim-rpc-msg" "openim-api" "openim-msgtransfer" "openim-crontask" ) RPC_SERVICES=( "openim-rpc-auth" "openim-rpc-user" "openim-rpc-friend" "openim-rpc-group" "openim-rpc-conversation" "openim-rpc-third" ) # 构建函数 build_service() { local service=$1 local dockerfile="build/images/$service/Dockerfile" local start_time=$(date +%s) if [ -f "$dockerfile" ]; then echo "📦 构建 $service..." if docker buildx build \ --platform linux/amd64 \ --file "$dockerfile" \ --tag "$DOCKER_USER/$service:$VERSION_TAG" \ --tag "$DOCKER_USER/$service:prod" \ --push \ --progress=plain \ --cache-from type=gha,scope=openim-build-${{ github.ref_name }}-$service \ --cache-to type=gha,mode=max,scope=openim-build-${{ github.ref_name }}-$service \ --build-arg BUILDKIT_INLINE_CACHE=1 \ --build-arg GOCACHE=/go-cache \ --build-arg GOMAXPROCS=$(nproc) \ --provenance=false \ --sbom=false \ . > "/tmp/build_${service}.log" 2>&1; then local end_time=$(date +%s) local duration=$((end_time - start_time)) echo "✅ $service 构建成功 (${duration}秒)" else echo "❌ $service 构建失败,查看详细错误日志:" echo "==========================================" cat "/tmp/build_${service}.log" echo "==========================================" echo "最后 50 行错误日志:" tail -50 "/tmp/build_${service}.log" return 1 fi else echo "❌ $service - 未找到 Dockerfile: $dockerfile" return 1 fi } # 初始化构建统计 failed_services=() successful_services=() # 计算总服务数 total_services=$((${#CORE_SERVICES[@]} + ${#RPC_SERVICES[@]})) echo "开始构建 $total_services 个服务..." # 先构建核心服务 for service in "${CORE_SERVICES[@]}"; do if build_service "$service"; then successful_services+=("$service") else failed_services+=("$service") fi done # 构建RPC服务 for service in "${RPC_SERVICES[@]}"; do if build_service "$service"; then successful_services+=("$service") else failed_services+=("$service") fi done # 输出构建结果统计 echo "" echo "🎉 构建完成总结" echo "✅ 成功构建: ${#successful_services[@]} 个服务" echo "❌ 构建失败: ${#failed_services[@]} 个服务" echo "📊 成功率: $(( ${#successful_services[@]} * 100 / total_services ))%" if [ ${#failed_services[@]} -gt 0 ]; then echo "" echo "失败服务错误日志:" for service in "${failed_services[@]}"; do echo "=== $service ===" cat "/tmp/build_${service}.log" 2>/dev/null || echo "无法读取日志文件" echo "" done exit 1 fi - name: 配置 kubectl if: success() run: | echo "🔧 配置 kubectl 连接到阿里云 ACK..." # 创建 kubeconfig 目录 mkdir -p ~/.kube # 从 secrets 中获取 kubeconfig 内容并处理 echo "📝 处理 kubeconfig 文件..." # 直接解码 base64 内容 echo "🔍 解码 base64 编码的 kubeconfig..." echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config # 验证解码是否成功 if [ ! -s ~/.kube/config ]; then echo "❌ kubeconfig 解码失败或文件为空" exit 1 fi # 显示 kubeconfig 内容供手动检查 echo "🔍 解码后的 kubeconfig 内容:" cat ~/.kube/config chmod 600 ~/.kube/config echo "✅ kubeconfig 配置完成" # 详细验证 kubectl 配置 echo "🔍 验证 kubectl 配置..." echo "当前上下文:" kubectl config current-context 2>&1 || echo "无法获取当前上下文" echo "可用上下文:" kubectl config get-contexts 2>&1 || echo "无法获取上下文列表" echo "集群信息:" kubectl cluster-info 2>&1 || echo "无法获取集群信息" echo "API 版本:" kubectl version --short 2>&1 || echo "无法获取版本信息" - 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 }}" elif [ -n "${{ github.event.inputs.tag }}" ]; then VERSION_TAG="${{ github.event.inputs.tag }}" else # 使用分支名作为标签 VERSION_TAG="${{ github.ref_name }}" fi echo "版本标签: $VERSION_TAG" echo "Docker用户: $DOCKER_USER" # 检查部署文件是否存在 DEPLOY_DIR="deployments/deploy" if [ ! -d "$DEPLOY_DIR" ]; then echo "❌ 部署目录不存在: $DEPLOY_DIR" echo "📁 当前目录内容:" ls -la echo "⚠️ 部署失败,但构建流程继续" exit 0 fi echo "📁 进入部署目录: $DEPLOY_DIR" cd "$DEPLOY_DIR" # 设置命名空间 NS=${NS:-default} echo "使用命名空间: $NS" # 创建命名空间(如果不存在) kubectl get ns "$NS" >/dev/null 2>&1 || kubectl create ns "$NS" kubectl config set-context --current --namespace="$NS" # 尝试部署,如果失败则继续 echo "🚀 开始部署OpenIM Server服务..." # 定义所有部署文件 DEPLOYMENT_FILES=( "openim-api-deployment.yml" "openim-crontask-deployment.yml" "openim-rpc-user-deployment.yml" "openim-msggateway-deployment.yml" "openim-push-deployment.yml" "openim-msgtransfer-deployment.yml" "openim-rpc-conversation-deployment.yml" "openim-rpc-auth-deployment.yml" "openim-rpc-group-deployment.yml" "openim-rpc-friend-deployment.yml" "openim-rpc-msg-deployment.yml" "openim-rpc-third-deployment.yml" ) # 更新镜像标签并部署 DEPLOYMENT_ERRORS=() for file in "${DEPLOYMENT_FILES[@]}"; do if [ -f "$file" ]; then echo "📦 处理 $file..." # 备份原文件(保留 imagePullSecrets 等配置) cp "$file" "$file.bak" # 更新镜像标签(替换用户名和标签,但保留 imagePullSecrets) sed -i "s|image: .*/openim-|image: $DOCKER_USER/openim-|g" "$file" sed -i "s|:prod|:prod|g" "$file" # 验证 imagePullSecrets 是否保留 if ! grep -q "imagePullSecrets:" "$file"; then echo "⚠️ 警告: $file 中缺少 imagePullSecrets,从备份恢复..." cp "$file.bak" "$file" # 只更新镜像,不修改其他内容 sed -i "s|image: .*/openim-|image: $DOCKER_USER/openim-|g" "$file" sed -i "s|:prod|:prod|g" "$file" fi # 应用文件并捕获详细错误 echo "🔍 执行: kubectl apply -f $file" if kubectl apply -f "$file" 2>&1; then echo "✅ $file 部署成功" else ERROR_OUTPUT=$(kubectl apply -f "$file" 2>&1) echo "❌ $file 部署失败:" echo "$ERROR_OUTPUT" DEPLOYMENT_ERRORS+=("$file: $ERROR_OUTPUT") echo "⚠️ 继续处理下一个文件..." fi # 清理备份文件 rm -f "$file.bak" else echo "⚠️ $file 不存在,跳过" fi done # 显示部署错误总结 if [ ${#DEPLOYMENT_ERRORS[@]} -gt 0 ]; then echo "" echo "🚨 部署错误总结:" echo "==========================================" for error in "${DEPLOYMENT_ERRORS[@]}"; do echo "❌ $error" done echo "==========================================" fi echo "✅ 部署步骤完成" # 强制重启所有部署以使用新镜像 echo "🔄 强制重启所有部署以使用新镜像..." DEPLOYMENTS=( "openim-api" "openim-crontask" "messagegateway-rpc-server" "openim-msgtransfer-server" "push-rpc-server" "auth-rpc-server" "user-rpc-server" "friend-rpc-server" "group-rpc-server" "conversation-rpc-server" "third-rpc-server" "msg-rpc-server" ) for deployment in "${DEPLOYMENTS[@]}"; do echo "🔄 重启部署: $deployment" # 先删除 pod 以强制拉取新镜像(即使 imagePullPolicy 是 Always,有时也需要删除 pod) if kubectl delete pods -l app="$deployment" --grace-period=0 --force 2>&1; then echo "✅ $deployment Pod 已删除,将重新创建并拉取新镜像" fi # 然后重启 deployment if kubectl rollout restart deployment "$deployment" 2>&1; then echo "✅ $deployment 重启成功" else echo "⚠️ $deployment 重启失败,可能不存在" fi done echo "⏳ 等待部署完成..." for deployment in "${DEPLOYMENTS[@]}"; do echo "⏳ 等待 $deployment 就绪..." if kubectl rollout status deployment "$deployment" --timeout=300s; then echo "✅ $deployment 部署完成" else echo "⚠️ $deployment 超时,检查Pod状态..." echo "Pod状态:" kubectl get pods -l app="$deployment" 2>&1 || echo "无法获取Pod状态" echo "Pod事件:" kubectl get events --field-selector involvedObject.name="$deployment" --sort-by='.lastTimestamp' 2>&1 | tail -10 || echo "无法获取事件" echo "部署详情:" kubectl describe deployment "$deployment" 2>&1 | tail -20 || echo "无法获取部署详情" fi done - name: 验证部署状态 if: success() run: | echo "🔍 验证部署状态..." # 详细检查集群连接和权限 echo "🔍 检查集群连接..." if kubectl cluster-info 2>&1; then echo "✅ 集群连接正常" else CLUSTER_ERROR=$(kubectl cluster-info 2>&1) echo "❌ 集群连接失败: $CLUSTER_ERROR" fi echo "🔍 检查命名空间权限..." if kubectl get namespaces 2>&1; then echo "✅ 命名空间权限正常" else NS_ERROR=$(kubectl get namespaces 2>&1) echo "❌ 命名空间权限不足: $NS_ERROR" fi echo "🔍 检查 Pod 状态..." if kubectl get pods -l app=openim 2>&1; then echo "✅ Pod 状态检查完成" else POD_ERROR=$(kubectl get pods -l app=openim 2>&1) echo "❌ Pod 状态检查失败: $POD_ERROR" fi echo "🔍 检查部署状态..." if kubectl get deployments -l app=openim 2>&1; then echo "✅ 部署状态检查完成" else DEPLOY_ERROR=$(kubectl get deployments -l app=openim 2>&1) echo "❌ 部署状态检查失败: $DEPLOY_ERROR" fi echo "✅ 验证完成" - name: 生成构建报告 run: | # 获取版本标签(使用分支名) 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 }}" else # 使用分支名作为标签 VERSION_TAG="${{ github.ref_name }}" fi echo "## OpenIM Server Build Complete" >> $GITHUB_STEP_SUMMARY echo "**Tag:** $VERSION_TAG" >> $GITHUB_STEP_SUMMARY echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY echo "**Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY echo "**Platform:** linux/amd64" >> $GITHUB_STEP_SUMMARY echo "**Mode:** 完整构建和部署" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Images:**" >> $GITHUB_STEP_SUMMARY echo "- \`$DOCKER_USER/openim-api:$VERSION_TAG\`" >> $GITHUB_STEP_SUMMARY echo "- \`$DOCKER_USER/openim-msggateway:$VERSION_TAG\`" >> $GITHUB_STEP_SUMMARY echo "- \`$DOCKER_USER/openim-msgtransfer:$VERSION_TAG\`" >> $GITHUB_STEP_SUMMARY echo "- \`$DOCKER_USER/openim-push:$VERSION_TAG\`" >> $GITHUB_STEP_SUMMARY echo "- \`$DOCKER_USER/openim-crontask:$VERSION_TAG\`" >> $GITHUB_STEP_SUMMARY echo "- \`$DOCKER_USER/openim-rpc-auth:$VERSION_TAG\`" >> $GITHUB_STEP_SUMMARY echo "- \`$DOCKER_USER/openim-rpc-user:$VERSION_TAG\`" >> $GITHUB_STEP_SUMMARY echo "- \`$DOCKER_USER/openim-rpc-friend:$VERSION_TAG\`" >> $GITHUB_STEP_SUMMARY echo "- \`$DOCKER_USER/openim-rpc-group:$VERSION_TAG\`" >> $GITHUB_STEP_SUMMARY echo "- \`$DOCKER_USER/openim-rpc-conversation:$VERSION_TAG\`" >> $GITHUB_STEP_SUMMARY echo "- \`$DOCKER_USER/openim-rpc-third:$VERSION_TAG\`" >> $GITHUB_STEP_SUMMARY echo "- \`$DOCKER_USER/openim-rpc-msg:$VERSION_TAG\`" >> $GITHUB_STEP_SUMMARY