Compare commits
2 Commits
d83720c8fd
...
ce4ed32dfc
| Author | SHA1 | Date |
|---|---|---|
|
|
ce4ed32dfc | |
|
|
63a7978d50 |
|
|
@ -160,7 +160,9 @@ gitlabBranch(集成/推送触发)
|
|||
1. 服务启动时数据库未就绪,首页显示"前往初始化"。
|
||||
2. 走完 `/init/initdb` 向导:建库建表 + 写入种子数据(默认账号 admin)。
|
||||
3. 向导最后一步把数据库连接**写回**节点目录中的 `/data/conf/config.yaml`,配置不会随 Pod 重建消失。
|
||||
4. 初始化期间建议先把后端缩容到 1 个副本:`kubectl -n <APP_NAME>-dev scale deployment/<APP_NAME>-backend --replicas=1`。初始化完成后再恢复副本数并滚动重启:`kubectl -n <APP_NAME>-dev scale deployment/<APP_NAME>-backend --replicas=3`,然后 `kubectl -n <APP_NAME>-dev rollout status deployment/<APP_NAME>-backend`。后续手工改配置也需要执行滚动重启。
|
||||
4.
|
||||
5.
|
||||
6. 当前 Jenkins 默认将前后端都部署为 1 个副本,MySQL 和 Redis 也默认启动。若环境已有外部 MySQL,可将 `KRA_DEPLOY_MYSQL` 设为 `false`;若不需要 Redis,可将 `KRA_DEPLOY_REDIS` 设为 `false`。关闭时对应工作负载副本数为 0,backend 不等待内置 MySQL,并关闭 Redis 使用。单节点下保持 `KRA_REPLICAS=1`;未来如需扩容,必须先迁移到真正的共享存储,再调整副本数并重新部署。
|
||||
|
||||
> `<APP_NAME>-config-template` Secret 只负责首次创建配置文件。初始化后,运行时配置以 hostPath 中的 `/data/conf/config.yaml` 为准;更新 Secret 或 Jenkins JWT 凭据不会覆盖已有配置,避免覆盖初始化写入的数据库连接。JWT 后续轮换应通过系统设置完成并滚动重启后端。
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,11 @@ pipeline {
|
|||
IMAGE_REPO = 'library/kra' // Harbor 中镜像仓库路径
|
||||
REGISTRY = '192.168.200.132'
|
||||
BIN_NAME = 'kratos-admin' // Makefile build 的产物名
|
||||
KRA_REPLICAS = '3'
|
||||
// 当前单节点部署固定使用单副本,避免首次初始化和 hostPath 数据共享产生竞争。
|
||||
KRA_REPLICAS = '1'
|
||||
// 中间件按环境开关;关闭时对应工作负载缩容到 0,不创建运行 Pod。
|
||||
KRA_DEPLOY_MYSQL = 'true'
|
||||
KRA_DEPLOY_REDIS = 'true'
|
||||
KRA_FRONTEND_NODEPORT = '30080'
|
||||
KRA_BACKEND_NODEPORT = '30081'
|
||||
KRA_MYSQL_IMAGE = 'docker.m.daocloud.io/library/mysql:8.4'
|
||||
|
|
@ -32,7 +36,7 @@ pipeline {
|
|||
CRED_JWT_SIGNING_KEY = 'kra-jwt-signing-key'
|
||||
|
||||
// k8s 清单渲染白名单:只替换列出的变量,避免误伤清单内 shell 变量(如 probe 里的 $MYSQL_ROOT_PASSWORD)
|
||||
K8S_RENDER_VARS = '$KRA_APP $KRA_NAMESPACE $KRA_ENV $KRA_BACKEND_IMAGE $KRA_FRONTEND_IMAGE $KRA_REPLICAS $KRA_FRONTEND_NODEPORT $KRA_BACKEND_NODEPORT $KRA_MYSQL_IMAGE $KRA_REDIS_IMAGE $BIN_NAME'
|
||||
K8S_RENDER_VARS = '$KRA_APP $KRA_NAMESPACE $KRA_ENV $KRA_BACKEND_IMAGE $KRA_FRONTEND_IMAGE $KRA_REPLICAS $KRA_DEPLOY_MYSQL $KRA_DEPLOY_REDIS $KRA_MYSQL_REPLICAS $KRA_REDIS_REPLICAS $KRA_MYSQL_WAIT_IMAGE $KRA_FRONTEND_NODEPORT $KRA_BACKEND_NODEPORT $KRA_MYSQL_IMAGE $KRA_REDIS_IMAGE $BIN_NAME'
|
||||
}
|
||||
|
||||
stages {
|
||||
|
|
@ -155,11 +159,23 @@ pipeline {
|
|||
|
||||
stage('Deploy to Rancher cluster') {
|
||||
steps {
|
||||
withCredentials([file(credentialsId: "${env.KUBECONFIG_CREDENTIALS}", variable: 'KUBECONFIG_FILE'),
|
||||
string(credentialsId: env.CRED_MYSQL_PASSWORD, variable: 'KRA_MYSQL_ROOT_PASSWORD'),
|
||||
string(credentialsId: env.CRED_JWT_SIGNING_KEY, variable: 'KRA_JWT_SIGNING_KEY'),
|
||||
usernamePassword(credentialsId: env.CRED_HARBOR_PUSH, usernameVariable: 'HARBOR_USER', passwordVariable: 'HARBOR_PASSWORD')]) {
|
||||
sh '''
|
||||
script {
|
||||
if (!(env.KRA_DEPLOY_MYSQL in ['true', 'false'])) {
|
||||
error("KRA_DEPLOY_MYSQL must be true or false: ${env.KRA_DEPLOY_MYSQL}")
|
||||
}
|
||||
if (!(env.KRA_DEPLOY_REDIS in ['true', 'false'])) {
|
||||
error("KRA_DEPLOY_REDIS must be true or false: ${env.KRA_DEPLOY_REDIS}")
|
||||
}
|
||||
def credentialBindings = [
|
||||
file(credentialsId: "${env.KUBECONFIG_CREDENTIALS}", variable: 'KUBECONFIG_FILE'),
|
||||
string(credentialsId: env.CRED_JWT_SIGNING_KEY, variable: 'KRA_JWT_SIGNING_KEY'),
|
||||
usernamePassword(credentialsId: env.CRED_HARBOR_PUSH, usernameVariable: 'HARBOR_USER', passwordVariable: 'HARBOR_PASSWORD')
|
||||
]
|
||||
if (env.KRA_DEPLOY_MYSQL == 'true') {
|
||||
credentialBindings << string(credentialsId: env.CRED_MYSQL_PASSWORD, variable: 'KRA_MYSQL_ROOT_PASSWORD')
|
||||
}
|
||||
withCredentials(credentialBindings) {
|
||||
sh '''
|
||||
set -eu
|
||||
export KUBECONFIG="$KUBECONFIG_FILE"
|
||||
umask 077
|
||||
|
|
@ -174,11 +190,13 @@ pipeline {
|
|||
--from-file=.dockerconfigjson="$DOCKER_CONFIG_DIR/config.json" \\
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
docker --config "$DOCKER_CONFIG_DIR" logout "$REGISTRY" >/dev/null 2>&1 || true
|
||||
test -n "$KRA_MYSQL_ROOT_PASSWORD" || { echo 'MySQL root password credential is empty' >&2; exit 1; }
|
||||
printf '%s' "$KRA_MYSQL_ROOT_PASSWORD" > "$TMP_DIR/mysql-root-password"
|
||||
kubectl -n "$KRA_NAMESPACE" create secret generic "${APP_NAME}-runtime" \\
|
||||
--from-file=mysql-root-password="$TMP_DIR/mysql-root-password" \\
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
if [ "$KRA_DEPLOY_MYSQL" = 'true' ]; then
|
||||
test -n "$KRA_MYSQL_ROOT_PASSWORD" || { echo 'MySQL root password credential is empty' >&2; exit 1; }
|
||||
printf '%s' "$KRA_MYSQL_ROOT_PASSWORD" > "$TMP_DIR/mysql-root-password"
|
||||
kubectl -n "$KRA_NAMESPACE" create secret generic "${APP_NAME}-runtime" \\
|
||||
--from-file=mysql-root-password="$TMP_DIR/mysql-root-password" \\
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
fi
|
||||
test -n "$KRA_JWT_SIGNING_KEY" || { echo 'JWT signing key credential is empty' >&2; exit 1; }
|
||||
if [ "$(printf '%s' "$KRA_JWT_SIGNING_KEY" | wc -l)" -ne 0 ]; then
|
||||
echo 'JWT signing key credential must be a single line' >&2
|
||||
|
|
@ -196,26 +214,39 @@ pipeline {
|
|||
export KRA_APP="$APP_NAME"
|
||||
export KRA_BACKEND_IMAGE="$REGISTRY/$IMAGE_REPO-backend:$IMAGE_TAG"
|
||||
export KRA_FRONTEND_IMAGE="$REGISTRY/$IMAGE_REPO-frontend:$IMAGE_TAG"
|
||||
case "$KRA_DEPLOY_MYSQL" in
|
||||
true) export KRA_MYSQL_REPLICAS=1; export KRA_MYSQL_WAIT_IMAGE="$KRA_MYSQL_IMAGE" ;;
|
||||
false) export KRA_MYSQL_REPLICAS=0; export KRA_MYSQL_WAIT_IMAGE="$KRA_BACKEND_IMAGE" ;;
|
||||
*) echo 'KRA_DEPLOY_MYSQL must be true or false' >&2; exit 1 ;;
|
||||
esac
|
||||
case "$KRA_DEPLOY_REDIS" in
|
||||
true) export KRA_REDIS_REPLICAS=1 ;;
|
||||
false) export KRA_REDIS_REPLICAS=0 ;;
|
||||
*) echo 'KRA_DEPLOY_REDIS must be true or false' >&2; exit 1 ;;
|
||||
esac
|
||||
RENDERED_MANIFEST="$TMP_DIR/manifest.yaml"
|
||||
envsubst "$K8S_RENDER_VARS" < deploy/k8s/kra.yaml > "$RENDERED_MANIFEST"
|
||||
MYSQL_SERVICE_IP=$(kubectl -n "$KRA_NAMESPACE" get service "${APP_NAME}-mysql" -o jsonpath='{.spec.clusterIP}' 2>/dev/null || true)
|
||||
if [ -n "$MYSQL_SERVICE_IP" ] && [ "$MYSQL_SERVICE_IP" != 'None' ]; then
|
||||
if [ "$KRA_DEPLOY_MYSQL" = 'true' ] && [ -n "$MYSQL_SERVICE_IP" ] && [ "$MYSQL_SERVICE_IP" != 'None' ]; then
|
||||
echo "${APP_NAME}-mysql already exists as a ClusterIP Service; delete it during a maintenance window before switching to headless mode" >&2
|
||||
exit 1
|
||||
fi
|
||||
kubectl apply --dry-run=server -f "$RENDERED_MANIFEST"
|
||||
kubectl apply -f "$RENDERED_MANIFEST"
|
||||
RUNTIME_SECRET_VERSION=$(kubectl -n "$KRA_NAMESPACE" get secret "${APP_NAME}-runtime" -o jsonpath='{.metadata.resourceVersion}')
|
||||
PATCH_FILE="$TMP_DIR/mysql-patch.json"
|
||||
printf '{"spec":{"template":{"metadata":{"annotations":{"kra.dev/runtime-secret-version":"%s"}}}}}\n' \\
|
||||
"$RUNTIME_SECRET_VERSION" > "$PATCH_FILE"
|
||||
kubectl -n "$KRA_NAMESPACE" patch statefulset "${APP_NAME}-mysql" --type=merge \\
|
||||
--patch-file="$PATCH_FILE"
|
||||
kubectl -n "$KRA_NAMESPACE" rollout status statefulset/${APP_NAME}-mysql --timeout=10m
|
||||
if [ "$KRA_DEPLOY_MYSQL" = 'true' ]; then
|
||||
RUNTIME_SECRET_VERSION=$(kubectl -n "$KRA_NAMESPACE" get secret "${APP_NAME}-runtime" -o jsonpath='{.metadata.resourceVersion}')
|
||||
PATCH_FILE="$TMP_DIR/mysql-patch.json"
|
||||
printf '{"spec":{"template":{"metadata":{"annotations":{"kra.dev/runtime-secret-version":"%s"}}}}}\n' \\
|
||||
"$RUNTIME_SECRET_VERSION" > "$PATCH_FILE"
|
||||
kubectl -n "$KRA_NAMESPACE" patch statefulset "${APP_NAME}-mysql" --type=merge \\
|
||||
--patch-file="$PATCH_FILE"
|
||||
kubectl -n "$KRA_NAMESPACE" rollout status statefulset/${APP_NAME}-mysql --timeout=10m
|
||||
fi
|
||||
kubectl -n "$KRA_NAMESPACE" rollout status deployment/${APP_NAME}-redis --timeout=10m
|
||||
kubectl -n "$KRA_NAMESPACE" rollout status deployment/${APP_NAME}-backend --timeout=10m
|
||||
kubectl -n "$KRA_NAMESPACE" rollout status deployment/${APP_NAME}-frontend --timeout=10m
|
||||
'''
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ Harbor 项目和 Kubernetes 两个环境都会由流水线自动创建 `harbor-r
|
|||
| 密码 | Jenkins 凭据 `kra-mysql-root-password` 的值 |
|
||||
| 库名 | `kra`(与应用名一致) |
|
||||
|
||||
提交后框架自动建表、写入种子数据(管理员账号、菜单、casbin)并把连接配置写回节点目录中的 `config.yaml`,Pod 重建后配置不丢失。初始化期间建议暂时保持后端为 1 个副本,完成后再扩容到 `KRA_REPLICAS`,避免多个 Pod 同时执行首次初始化。初始化完成后请执行一次后端滚动重启,让所有副本加载新配置。**不要**通过环境变量注入数据库 DSN:那会让应用跳过初始化向导,导致数据库有表无种子数据、无法登录。
|
||||
提交后框架自动建表、写入种子数据(管理员账号、菜单、casbin)并把连接配置写回节点目录中的 `config.yaml`,Pod 重建后配置不丢失。当前单节点部署固定使用 1 个后端副本,避免多个 Pod 同时执行首次初始化。未来如需扩容,必须先迁移到真正的共享存储,再调整 `KRA_REPLICAS` 并滚动重启,让所有副本加载新配置。**不要**通过环境变量注入数据库 DSN:那会让应用跳过初始化向导,导致数据库有表无种子数据、无法登录。
|
||||
|
||||
## 基于 kra 模板接入新项目
|
||||
|
||||
|
|
@ -69,7 +69,9 @@ Harbor 项目和 Kubernetes 两个环境都会由流水线自动创建 `harbor-r
|
|||
| `IMAGE_REPO` | Harbor 镜像仓库路径 | `library/kra` |
|
||||
| `REGISTRY` | Harbor 地址 | `192.168.200.132` |
|
||||
| `BIN_NAME` | 后端二进制名,须与 `Makefile` 的 `make build` 产物一致 | `kratos-admin` |
|
||||
| `KRA_REPLICAS` | 前后端副本数 | `3` |
|
||||
| `KRA_REPLICAS` | 前后端副本数(当前单节点部署使用 1) | `1` |
|
||||
| `KRA_DEPLOY_MYSQL` | 是否启动清单内 MySQL(`true`/`false`,关闭时副本数为 0) | `true` |
|
||||
| `KRA_DEPLOY_REDIS` | 是否启动清单内 Redis(`true`/`false`,关闭时副本数为 0) | `true` |
|
||||
| `KRA_FRONTEND_NODEPORT` / `KRA_BACKEND_NODEPORT` | NodePort 端口,同一集群多个项目必须错开 | `30080` / `30081` |
|
||||
| `KRA_MYSQL_IMAGE` / `KRA_REDIS_IMAGE` | 中间件镜像 | daocloud mysql:8.4 / redis:7-alpine |
|
||||
| `GO_IMAGE` / `NODE_IMAGE` / `PNPM_VERSION` | 构建镜像与工具版本 | 见文件 |
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
# 说明:
|
||||
# - data.database 保持为空:应用启动后首页会出现"前往初始化",
|
||||
# 在初始化页面填写数据库连接后,框架会自动把连接配置写回本文件。
|
||||
# - data.redis.addr 指向集群内 Redis Service(<APP_NAME>-redis:6379)。
|
||||
# - data.redis.addr 默认指向集群内 Redis Service(<APP_NAME>-redis:6379);
|
||||
# 关闭内置 Redis 时,Jenkins 会同时关闭 admin.system.use_redis。
|
||||
# - admin.app.env / admin.app.app_id / admin.jwt.issuer 由 Jenkins 按项目和分支渲染。
|
||||
server:
|
||||
http:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@
|
|||
# KRA_NAMESPACE target namespace
|
||||
# KRA_ENV development | production
|
||||
# KRA_REPLICAS replicas per deployment
|
||||
# KRA_DEPLOY_MYSQL true/false; whether the bundled MySQL runs
|
||||
# KRA_DEPLOY_REDIS true/false; whether the bundled Redis runs
|
||||
# KRA_MYSQL_REPLICAS rendered MySQL replica count (0 or 1)
|
||||
# KRA_REDIS_REPLICAS rendered Redis replica count (0 or 1)
|
||||
# KRA_MYSQL_WAIT_IMAGE image used by the optional MySQL readiness wait
|
||||
# KRA_FRONTEND_NODEPORT node port for the frontend service
|
||||
# KRA_BACKEND_NODEPORT node port for the backend debug service
|
||||
# KRA_MYSQL_IMAGE mysql server image
|
||||
|
|
@ -28,7 +33,7 @@ metadata:
|
|||
app.kubernetes.io/part-of: ${KRA_APP}
|
||||
spec:
|
||||
serviceName: ${KRA_APP}-mysql
|
||||
replicas: 1
|
||||
replicas: ${KRA_MYSQL_REPLICAS}
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: ${KRA_APP}-mysql
|
||||
|
|
@ -119,7 +124,7 @@ metadata:
|
|||
app.kubernetes.io/name: ${KRA_APP}-redis
|
||||
app.kubernetes.io/part-of: ${KRA_APP}
|
||||
spec:
|
||||
replicas: 1
|
||||
replicas: ${KRA_REDIS_REPLICAS}
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: ${KRA_APP}-redis
|
||||
|
|
@ -145,11 +150,13 @@ spec:
|
|||
mountPath: /data
|
||||
affinity:
|
||||
podAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- topologyKey: kubernetes.io/hostname
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: ${KRA_APP}-mysql
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: ${KRA_APP}-mysql
|
||||
containers:
|
||||
- name: redis
|
||||
image: ${KRA_REDIS_IMAGE}
|
||||
|
|
@ -215,14 +222,15 @@ spec:
|
|||
automountServiceAccountToken: false
|
||||
initContainers:
|
||||
- name: wait-for-mysql
|
||||
image: ${KRA_MYSQL_IMAGE}
|
||||
image: ${KRA_MYSQL_WAIT_IMAGE}
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["sh", "-c"]
|
||||
args:
|
||||
- >-
|
||||
until mysqladmin ping -h "${KRA_APP}-mysql" -P 3306
|
||||
--connect-timeout=2 --silent;
|
||||
do sleep 2; done
|
||||
if [ "${KRA_DEPLOY_MYSQL}" = "true" ]; then
|
||||
until mysqladmin ping -h "${KRA_APP}-mysql" -P 3306
|
||||
--connect-timeout=2 --silent; do sleep 2; done;
|
||||
fi
|
||||
# 这里只等待 MySQL 服务可达,不要求应用数据库已经初始化;首次部署
|
||||
# 仍需进入 bootstrap 模式完成 /init/initdb 向导。
|
||||
- name: initialize-shared-storage
|
||||
|
|
@ -254,11 +262,13 @@ spec:
|
|||
- name: harbor-registry
|
||||
affinity:
|
||||
podAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- topologyKey: kubernetes.io/hostname
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: ${KRA_APP}-mysql
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: ${KRA_APP}-mysql
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
|
|
@ -282,7 +292,7 @@ spec:
|
|||
# JWT 签名密钥由 /data/conf/config.yaml 唯一管理,避免环境变量覆盖管理页面的配置变更;
|
||||
# Redis 地址已在下方 config.yaml 中指定。
|
||||
- name: KRA_ADMIN_SYSTEM_USE_REDIS
|
||||
value: "true"
|
||||
value: "${KRA_DEPLOY_REDIS}"
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /data/conf
|
||||
|
|
|
|||
Loading…
Reference in New Issue