Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f99f31475 | |||
| 43580dd1fd | |||
| d5d9b7aa99 | |||
| 6eca6de249 | |||
| a42df9d521 | |||
| 015ecd13d8 | |||
| 8abd750dfb | |||
| f8e24ffdf0 | |||
| 688be6b65f | |||
| a6e75f6e97 | |||
| 6cc5212eba | |||
| 2d87b96a09 | |||
| eb2869a71d | |||
| 1789141a7b | |||
| b80e4c01f5 | |||
| b70d90d18a | |||
| 73fdaf82e7 | |||
| 5418f23111 | |||
| 58e9e4db22 | |||
| a6dab438d5 | |||
| 174b4265d7 | |||
| 3d157ed30c |
@@ -0,0 +1,185 @@
|
||||
name: lpt-be Build & Deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: '部署环境(dev 仅允许 dev 分支;prod 仅允许 master/main)'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- dev
|
||||
- prod
|
||||
|
||||
env:
|
||||
REGISTRY: 192.168.123.199:5000
|
||||
APP: lpt-be
|
||||
REPO_URL: http://git.cat-shark.xyz/cat-shark/lpt-be.git
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: maven:3.9-eclipse-temurin-17
|
||||
volumes:
|
||||
- maven-cache:/root/.m2
|
||||
steps:
|
||||
- name: Validate branch ↔ environment
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REF="${{ gitea.ref }}"
|
||||
BRANCH="${REF#refs/heads/}"
|
||||
if [[ "$REF" == refs/tags/* ]]; then
|
||||
echo "ERROR: 请从分支触发部署,不要用 tag。当前 ref=$REF"
|
||||
exit 1
|
||||
fi
|
||||
ENV="${{ inputs.environment }}"
|
||||
echo "branch=$BRANCH environment=$ENV sha=${{ gitea.sha }}"
|
||||
case "$ENV" in
|
||||
prod)
|
||||
case "$BRANCH" in
|
||||
master|main) echo "OK: prod 允许从 $BRANCH 部署" ;;
|
||||
*)
|
||||
echo "ERROR: prod 只能从 master/main 部署,当前分支是 '$BRANCH'"
|
||||
echo "请切换到 master 后再 Run workflow,并选择 environment=prod"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
dev)
|
||||
case "$BRANCH" in
|
||||
dev) echo "OK: dev 允许从 $BRANCH 部署" ;;
|
||||
*)
|
||||
echo "ERROR: dev 只能从 dev 分支部署,当前分支是 '$BRANCH'"
|
||||
echo "请切换到 dev 后再 Run workflow,并选择 environment=dev"
|
||||
echo "(禁止用 master 代码部署到 lpt-dev,避免环境错配)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: 未知 environment=$ENV"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Checkout code
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# maven 容器可能无 git
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq git
|
||||
fi
|
||||
git clone "$REPO_URL" .
|
||||
git checkout "${{ gitea.sha }}"
|
||||
|
||||
- name: Install Docker CLI
|
||||
run: |
|
||||
set -euo pipefail
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq docker.io
|
||||
docker --version
|
||||
|
||||
- name: Build with Maven
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mvn clean package -DskipTests -B
|
||||
|
||||
- name: Login to Docker Registry
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login "$REGISTRY" -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
|
||||
|
||||
- name: Build & Push Docker image
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ENV="${{ inputs.environment }}"
|
||||
TAG="${{ gitea.sha }}-$(date +%s)"
|
||||
echo "Building $REGISTRY/$APP:$TAG (env tag=$ENV)"
|
||||
docker build -t "$REGISTRY/$APP:$TAG" -t "$REGISTRY/$APP:$ENV" .
|
||||
docker push "$REGISTRY/$APP:$TAG"
|
||||
docker push "$REGISTRY/$APP:$ENV"
|
||||
if [ -n "${GITHUB_ENV:-}" ]; then
|
||||
echo "IMAGE_TAG=$TAG" >> "$GITHUB_ENV"
|
||||
fi
|
||||
if [ -n "${GITEA_ENV:-}" ]; then
|
||||
echo "IMAGE_TAG=$TAG" >> "$GITEA_ENV"
|
||||
fi
|
||||
# runner.temp 在 container job 里可能不可用,用工作区文件兜底
|
||||
echo "$TAG" > image_tag.txt
|
||||
mkdir -p /tmp/lpt-ci
|
||||
echo "$TAG" > /tmp/lpt-ci/image_tag.txt
|
||||
echo "IMAGE_TAG=$TAG"
|
||||
|
||||
- name: Setup kubectl
|
||||
run: |
|
||||
set -euo pipefail
|
||||
curl -sLO "https://dl.k8s.io/release/v1.30.0/bin/linux/amd64/kubectl"
|
||||
chmod +x kubectl
|
||||
mv kubectl /usr/local/bin/
|
||||
mkdir -p ~/.kube
|
||||
echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > ~/.kube/config
|
||||
|
||||
- name: Create/Update imagePullSecret
|
||||
run: |
|
||||
set -euo pipefail
|
||||
kubectl create secret docker-registry regcred \
|
||||
--docker-server="$REGISTRY" \
|
||||
--docker-username="${{ secrets.REGISTRY_USERNAME }}" \
|
||||
--docker-password="${{ secrets.REGISTRY_PASSWORD }}" \
|
||||
-n "lpt-${{ inputs.environment }}" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
- name: Deploy to K8s
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ENV="${{ inputs.environment }}"
|
||||
IMAGE_TAG="${IMAGE_TAG:-}"
|
||||
if [ -z "$IMAGE_TAG" ] && [ -f image_tag.txt ]; then
|
||||
IMAGE_TAG="$(cat image_tag.txt)"
|
||||
fi
|
||||
if [ -z "$IMAGE_TAG" ] && [ -f /tmp/lpt-ci/image_tag.txt ]; then
|
||||
IMAGE_TAG="$(cat /tmp/lpt-ci/image_tag.txt)"
|
||||
fi
|
||||
if [ -z "$IMAGE_TAG" ]; then
|
||||
echo "ERROR: IMAGE_TAG 为空,无法部署"
|
||||
exit 1
|
||||
fi
|
||||
echo "Deploying $REGISTRY/$APP:$IMAGE_TAG -> namespace lpt-$ENV"
|
||||
# Spring profile 由 K8s Deployment 的 SPRING_PROFILES_ACTIVE 控制,镜像本身不区分
|
||||
kubectl set image "deployment/$APP" \
|
||||
"$APP=$REGISTRY/$APP:$IMAGE_TAG" \
|
||||
-n "lpt-$ENV" --record
|
||||
kubectl rollout status "deployment/$APP" \
|
||||
-n "lpt-$ENV" --timeout=5m
|
||||
|
||||
- name: Debug on failure
|
||||
if: failure()
|
||||
run: |
|
||||
ENV="${{ inputs.environment }}"
|
||||
echo "=== Deployment Status ==="
|
||||
kubectl get deployment "$APP" -n "lpt-$ENV" || true
|
||||
echo ""
|
||||
echo "=== Pod Status ==="
|
||||
kubectl get pods -n "lpt-$ENV" -l "app=$APP" || true
|
||||
echo ""
|
||||
echo "=== Recent Events ==="
|
||||
kubectl get events -n "lpt-$ENV" --sort-by='.lastTimestamp' | tail -20 || true
|
||||
echo ""
|
||||
echo "=== Pod Describe (latest) ==="
|
||||
POD=$(kubectl get pods -n "lpt-$ENV" -l "app=$APP" --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}' 2>/dev/null || true)
|
||||
if [ -n "${POD:-}" ]; then
|
||||
kubectl describe pod "$POD" -n "lpt-$ENV" || true
|
||||
echo ""
|
||||
echo "=== Pod Logs (latest) ==="
|
||||
kubectl logs "$POD" -n "lpt-$ENV" --tail=50 || true
|
||||
fi
|
||||
|
||||
- name: Rollback on failure
|
||||
if: failure()
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ENV="${{ inputs.environment }}"
|
||||
kubectl rollout undo "deployment/$APP" -n "lpt-$ENV" || true
|
||||
kubectl rollout status "deployment/$APP" -n "lpt-$ENV" || true
|
||||
Vendored
+27
-19
@@ -1,7 +1,7 @@
|
||||
pipeline {
|
||||
agent none // 全局不指定,局部自己声明
|
||||
agent none
|
||||
environment {
|
||||
IMAGE_NAME = 'lpt-prod:0.0'
|
||||
IMAGE_NAME = "lpt-prod:${env.GIT_COMMIT?.take(8) ?: '0.0'}"
|
||||
CONTAINER_NAME = 'LPT-prod'
|
||||
CONTAINER_PORT = '8888'
|
||||
}
|
||||
@@ -31,23 +31,24 @@ pipeline {
|
||||
sh '''
|
||||
docker network create traefik-public || true
|
||||
docker rm -f $CONTAINER_NAME || true
|
||||
docker run -d --name $CONTAINER_NAME --network mysql-prod_mysql-prod \\
|
||||
--restart=always \\
|
||||
-e SPRING_PROFILES_ACTIVE=prod \\
|
||||
--label "traefik.enable=true" \\
|
||||
--label "traefik.docker.network=traefik-public" \\
|
||||
--label 'traefik.http.routers.lpt-api.rule=Host(`lpt.cat-shark.xyz`) && PathPrefix(`/api`)' \\
|
||||
--label "traefik.http.routers.lpt-api.entrypoints=websecure" \\
|
||||
--label "traefik.http.routers.lpt-api.tls.certresolver=le" \\
|
||||
--label "traefik.http.routers.lpt-api.priority=100" \\
|
||||
--label "traefik.http.routers.lpt-api.service=lpt-api" \\
|
||||
--label "traefik.http.routers.lpt-api.middlewares=lpt-api-strip" \\
|
||||
--label "traefik.http.middlewares.lpt-api-strip.stripprefix.prefixes=/api" \\
|
||||
--label "traefik.http.services.lpt-api.loadbalancer.server.port=$CONTAINER_PORT" \\
|
||||
--log-driver=loki \\
|
||||
--log-opt loki-url="http://192.168.123.199:3100/loki/api/v1/push" \\
|
||||
docker run -d --name $CONTAINER_NAME \
|
||||
--network traefik-public \
|
||||
--restart=always \
|
||||
-e SPRING_PROFILES_ACTIVE=prod \
|
||||
--label "traefik.enable=true" \
|
||||
--label "traefik.docker.network=traefik-public" \
|
||||
--label 'traefik.http.routers.lpt-api.rule=Host(`lpt.cat-shark.xyz`) && PathPrefix(`/api`)' \
|
||||
--label "traefik.http.routers.lpt-api.entrypoints=websecure" \
|
||||
--label "traefik.http.routers.lpt-api.tls.certresolver=le" \
|
||||
--label "traefik.http.routers.lpt-api.priority=100" \
|
||||
--label "traefik.http.routers.lpt-api.service=lpt-api" \
|
||||
--label "traefik.http.routers.lpt-api.middlewares=lpt-api-strip" \
|
||||
--label "traefik.http.middlewares.lpt-api-strip.stripprefix.prefixes=/api" \
|
||||
--label "traefik.http.services.lpt-api.loadbalancer.server.port=$CONTAINER_PORT" \
|
||||
--log-driver=loki \
|
||||
--log-opt loki-url="http://192.168.123.199:3100/loki/api/v1/push" \
|
||||
$IMAGE_NAME
|
||||
docker network connect traefik-public $CONTAINER_NAME || true
|
||||
docker network connect mysql-prod_mysql-prod $CONTAINER_NAME || true
|
||||
'''
|
||||
}
|
||||
}
|
||||
@@ -57,7 +58,7 @@ pipeline {
|
||||
steps {
|
||||
script {
|
||||
def lastStatus = ''
|
||||
timeout(time: 60, unit: 'SECONDS') {
|
||||
timeout(time: 120, unit: 'SECONDS') {
|
||||
waitUntil {
|
||||
def status = sh(
|
||||
script: "docker inspect -f '{{.State.Health.Status}}' $CONTAINER_NAME || echo 'unhealthy'",
|
||||
@@ -74,5 +75,12 @@ pipeline {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('清理旧镜像') {
|
||||
agent any
|
||||
steps {
|
||||
sh 'docker image prune -af --filter "until=168h" || true'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: lpt-be
|
||||
namespace: lpt-dev
|
||||
labels:
|
||||
app: lpt-be
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: lpt-be
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 25%
|
||||
maxUnavailable: 25%
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: lpt-be
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: lpt-be
|
||||
image: 192.168.123.199:5000/lpt-be:dev
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 8888
|
||||
env:
|
||||
- name: SPRING_PROFILES_ACTIVE
|
||||
value: dev
|
||||
- name: SPRING_DATASOURCE_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: lpt-config
|
||||
key: SPRING_DATASOURCE_URL
|
||||
- name: SPRING_DATASOURCE_USERNAME
|
||||
value: root
|
||||
- name: SPRING_DATASOURCE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: lpt-secrets
|
||||
key: mysql-root-password
|
||||
- name: LPT_AI-SERVICE_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: lpt-config
|
||||
key: LPT_AI-SERVICE_URL
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 1Gi
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health
|
||||
port: 8888
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health
|
||||
port: 8888
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: lpt-be
|
||||
namespace: lpt-dev
|
||||
labels:
|
||||
app: lpt-be
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
app: lpt-be
|
||||
ports:
|
||||
- port: 8888
|
||||
targetPort: 8888
|
||||
nodePort: 30082
|
||||
protocol: TCP
|
||||
Reference in New Issue
Block a user