mirror of
https://github.com/go-admin-team/go-admin.git
synced 2026-09-27 12:10:46 +00:00
Closes #871. The deploy did docker rm -f then docker run. Nothing ran migrations, so new code met old tables, and nothing checked the result - a container that exits immediately left the site down with a green deploy. Now, in order: pull the image, run the migration with it, and only then touch what is running. A failed migration stops there, leaving old code with the old schema, which is at least self-consistent. The running container is renamed rather than removed, so it can be started again unchanged if the new one does not become healthy. Healthy means both an HTTP response and a database connection in the log: the captcha endpoint answers without touching the database, so it alone would call a container healthy that cannot reach MySQL.
130 lines
5.8 KiB
YAML
130 lines
5.8 KiB
YAML
name: Build
|
|
|
|
on:
|
|
push:
|
|
branches: [ master ]
|
|
pull_request:
|
|
branches: [ master ]
|
|
|
|
# One deploy at a time. Two merges seconds apart raced here: both runs did
|
|
# docker rm -f then docker run, the second removed the container the first had
|
|
# just created, and the first's docker run then failed on a name conflict -
|
|
# leaving the demo on the older image with a red deploy.
|
|
concurrency:
|
|
group: deploy-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
IMAGE_NAME: registry.ap-northeast-1.aliyuncs.com/go-admin/go-admin-api # 镜像名称
|
|
TAG: ${{ github.sha }}
|
|
IMAGE_NAME_TAG: registry.ap-northeast-1.aliyuncs.com/go-admin/go-admin-api:${{ github.sha }}
|
|
|
|
jobs:
|
|
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
with:
|
|
go-version: 1.26.5
|
|
|
|
- name: Tidy
|
|
run: go mod tidy
|
|
|
|
- name: Build
|
|
run: env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags "sqlite3,json1" --ldflags "-extldflags -static" -o main .
|
|
|
|
# 以下推镜像与重启步骤仅在 master 收到 push 时执行。
|
|
# pull_request 事件同样会触发本工作流,若不加限制,任何指向 master 的
|
|
# PR 一经创建就会把 PR 分支的镜像推上仓库,并直接重启线上 API 服务,
|
|
# 且发生在合并之前。构建与编译校验不受影响,PR 仍会执行。
|
|
- name: Build the Docker image and push
|
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
run: |
|
|
docker login --username=${{ secrets.DOCKER_USERNAME }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PASSWORD }}
|
|
echo "************ docker login end"
|
|
docker build -t go-admin-api:latest .
|
|
echo "************ docker build end"
|
|
docker tag go-admin-api ${{ env.IMAGE_NAME_TAG }}
|
|
echo "************ docker tag end"
|
|
docker images
|
|
echo "************ docker images end"
|
|
docker push ${{ env.IMAGE_NAME_TAG }} # 推送
|
|
echo "************ docker push end"
|
|
|
|
- name: Restart server # 第五步,重启服务
|
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5
|
|
env:
|
|
GITHUB_SHA_X: ${GITHUB_SHA}
|
|
with:
|
|
host: ${{ secrets.SSH_HOST }} # 下面三个配置与上一步类似
|
|
username: ${{ secrets.SSH_USERNAME }}
|
|
key: ${{ secrets.DEPLOY_KEY }}
|
|
# 重启的脚本,根据自身情况做相应改动,一般要做的是migrate数据库以及重启服务器
|
|
#
|
|
# 配置从宿主机挂载,不使用镜像里的那份:演示站连的是托管数据库,
|
|
# 而 config/settings.demo.yml 会随仓库公开、也会打进镜像,凭据不能写在那里。
|
|
# 镜像里那份保持 sqlite,供 clone 仓库的人开箱即用。
|
|
#
|
|
# 路径本身走 secret:它不是凭据,但本仓库公开,没有理由把服务器的
|
|
# 目录结构一并公布。DEMO_CONFIG_PATH 指向宿主机上那份配置。
|
|
#
|
|
# 顺序是有意的:迁移先跑,跑不过就保持现有版本不动;
|
|
# 旧容器改名保留而不是删除,新容器不健康时能原样恢复。
|
|
# 健康检查两条都要过——HTTP 活着不代表数据库通了。
|
|
script: |
|
|
set -u
|
|
CFG="${{ secrets.DEMO_CONFIG_PATH }}"
|
|
IMG="${{ env.IMAGE_NAME_TAG }}"
|
|
NAME=go-admin-api
|
|
PREV="$NAME-prev"
|
|
|
|
test -f "$CFG" || { echo "宿主机配置缺失,中止部署"; exit 1; }
|
|
|
|
sudo docker login --username=${{ secrets.DOCKER_USERNAME }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PASSWORD }}
|
|
sudo docker pull "$IMG" || { echo "拉取镜像失败,中止部署"; exit 1; }
|
|
|
|
# 迁移用新镜像跑。失败时线上仍是旧版本配旧 schema,是自洽的;
|
|
# 硬切过去才会得到代码与表对不上的服务。
|
|
if ! sudo docker run --rm -v "$CFG":/config/settings.yml:ro "$IMG" \
|
|
/main migrate -c /config/settings.yml; then
|
|
echo "迁移失败,保持现有版本"; exit 1
|
|
fi
|
|
|
|
if sudo docker ps -a --format '{{.Names}}' | grep -qx "$NAME"; then
|
|
sudo docker rm -f "$PREV" >/dev/null 2>&1 || true
|
|
sudo docker rename "$NAME" "$PREV"
|
|
sudo docker stop "$PREV" >/dev/null
|
|
fi
|
|
|
|
sudo docker run -d -p 8000:8000 \
|
|
-v "$CFG":/config/settings.yml:ro \
|
|
--name "$NAME" "$IMG"
|
|
|
|
ok=0
|
|
for i in $(seq 1 20); do
|
|
sleep 3
|
|
code=$(curl -s -o /dev/null -w '%{http_code}' -m 5 http://127.0.0.1:8000/api/v1/captcha 2>/dev/null || true)
|
|
if [ "$code" = "200" ] && sudo docker logs "$NAME" 2>&1 | grep -q 'connect success'; then
|
|
ok=1; echo "健康检查通过(第 $i 次探测)"; break
|
|
fi
|
|
done
|
|
|
|
if [ "$ok" = "1" ]; then
|
|
sudo docker rm -f "$PREV" >/dev/null 2>&1 || true
|
|
else
|
|
echo "健康检查失败,回滚到上一版本"
|
|
sudo docker logs --tail 40 "$NAME" 2>&1 || true
|
|
sudo docker rm -f "$NAME" >/dev/null 2>&1 || true
|
|
if sudo docker ps -a --format '{{.Names}}' | grep -qx "$PREV"; then
|
|
sudo docker rename "$PREV" "$NAME"
|
|
sudo docker start "$NAME" >/dev/null
|
|
echo "已恢复"
|
|
fi
|
|
exit 1
|
|
fi
|