feat(objectstorage): add Aliyun OSS support and auto-detect provider in deploy workflows

Introduces Aliyun OSS as an alternative to MinIO; deploy scripts and CI
workflows now read object_storage.provider from config and conditionally
include or skip MinIO resources in both Docker Compose and k3s paths.
Also adds ops scheduler domain and its migration tables.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 10:25:02 +08:00
parent 00eb514efc
commit 98f73c5bea
18 changed files with 901 additions and 34 deletions
+14
View File
@@ -45,6 +45,20 @@ jobs:
run: |
docker compose -f deploy/docker-compose.yaml config >/tmp/compose.yaml
docker compose -f deploy/docker-compose.yaml -f deploy/docker-compose.offline.yaml config >/tmp/compose-offline.yaml
docker compose -f deploy/docker-compose.yaml -f deploy/docker-compose.offline.yaml -f deploy/docker-compose.aliyun-oss.yaml config >/tmp/compose-aliyun-oss.yaml
- name: Validate k3s manifests
run: |
if ! command -v kubectl >/dev/null 2>&1; then
echo "kubectl not found; skipping kustomize manifest validation."
exit 0
fi
kubectl kustomize deploy/k3s >/tmp/k3s.yaml
kubectl kustomize deploy/k3s-aliyun-oss >/tmp/k3s-aliyun-oss.yaml
if grep -Eq 'name: (minio|minio-init)$' /tmp/k3s-aliyun-oss.yaml; then
echo "aliyun object-storage overlay must not render MinIO resources"
exit 1
fi
- name: Smoke build server runtime image
run: |
+100 -7
View File
@@ -573,6 +573,90 @@ jobs:
docker_cmd compose "$@"
}
ensure_config_aliases() {
for yaml in config.yaml config.local.yaml ops-config.yaml ops-config.local.yaml; do
yml="${yaml%.yaml}.yml"
if [ ! -f "${release_dir}/deploy/${yaml}" ] && [ -f "${release_dir}/deploy/${yml}" ]; then
cp "${release_dir}/deploy/${yml}" "${release_dir}/deploy/${yaml}"
echo "Created ${yaml} from ${yml} for deployment mounts."
fi
done
if [ ! -f "${release_dir}/deploy/prompts.yml" ] && [ -f "${release_dir}/deploy/prompts.yaml" ]; then
cp "${release_dir}/deploy/prompts.yaml" "${release_dir}/deploy/prompts.yml"
echo "Created prompts.yml from prompts.yaml for deployment mounts."
fi
}
config_file_path() {
base="$1"
for file in "${release_dir}/deploy/${base}.yaml" "${release_dir}/deploy/${base}.yml"; do
if [ -f "${file}" ]; then
printf '%s' "${file}"
return 0
fi
done
}
read_object_storage_provider() {
provider="minio"
for file in "$(config_file_path config)" "$(config_file_path config.local)"; do
if [ -z "${file}" ] || [ ! -f "${file}" ]; then
continue
fi
next="$(
awk '
/^[[:space:]]*object_storage:[[:space:]]*$/ {
in_section=1
base=match($0, /[^[:space:]]/) - 1
next
}
in_section {
indent=match($0, /[^[:space:]]/) - 1
if ($0 ~ /^[[:space:]]*$/ || $0 ~ /^[[:space:]]*#/) {
next
}
if (indent <= base) {
in_section=0
next
}
if ($0 ~ /^[[:space:]]*provider[[:space:]]*:/) {
value=$0
sub(/^[[:space:]]*provider[[:space:]]*:[[:space:]]*/, "", value)
sub(/[[:space:]]+#.*$/, "", value)
gsub(/^["'\'']|["'\'']$/, "", value)
print value
}
}
' "${file}" | tail -1
)"
if [ -n "${next}" ]; then
provider="${next}"
fi
done
printf '%s' "$(printf '%s' "${provider}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
}
use_minio_for_object_storage() {
case "$(read_object_storage_provider)" in
""|minio|mino)
return 0
;;
aliyun|aliyun_oss|aliyun-oss|oss)
return 1
;;
*)
return 0
;;
esac
}
compose_files() {
printf '%s\n' -f "${release_dir}/deploy/docker-compose.yaml"
if ! use_minio_for_object_storage; then
printf '%s\n' -f "${release_dir}/deploy/docker-compose.aliyun-oss.yaml"
fi
}
docker_login() {
if [ "${DOCKER_MODE}" = "sudo" ]; then
printf '%s\n' "${SUDO_PASSWORD}" | sudo -S -p "" -v
@@ -606,11 +690,20 @@ jobs:
fi
docker_login
ensure_config_aliases
provider="$(read_object_storage_provider)"
if use_minio_for_object_storage; then
echo "object_storage.provider=${provider}; deploying MinIO."
else
echo "object_storage.provider=${provider}; skipping MinIO."
export COMPOSE_PROFILES=""
docker_cmd rm -f geo-minio geo-minio-init >/dev/null 2>&1 || true
fi
IMAGE_REGISTRY="${REGISTRY_HOST}/${REGISTRY_OWNER}/${REGISTRY_IMAGE_PREFIX}" \
IMAGE_TAG="${IMAGE_TAG}" compose_cmd \
--env-file "${shared_dir}/.env" \
-f "${release_dir}/deploy/docker-compose.yaml" \
$(compose_files) \
config >/tmp/geo-rankly-compose-service.yaml
if [ "${RUN_MIGRATIONS}" = "true" ]; then
@@ -618,19 +711,19 @@ jobs:
IMAGE_REGISTRY="${REGISTRY_HOST}/${REGISTRY_OWNER}/${REGISTRY_IMAGE_PREFIX}" \
IMAGE_TAG="${IMAGE_TAG}" compose_cmd \
--env-file "${shared_dir}/.env" \
-f "${release_dir}/deploy/docker-compose.yaml" \
$(compose_files) \
rm -f -s migrate >/dev/null 2>&1 || true
IMAGE_REGISTRY="${REGISTRY_HOST}/${REGISTRY_OWNER}/${REGISTRY_IMAGE_PREFIX}" \
IMAGE_TAG="${IMAGE_TAG}" compose_cmd \
--env-file "${shared_dir}/.env" \
-f "${release_dir}/deploy/docker-compose.yaml" \
$(compose_files) \
pull migrate
IMAGE_REGISTRY="${REGISTRY_HOST}/${REGISTRY_OWNER}/${REGISTRY_IMAGE_PREFIX}" \
IMAGE_TAG="${IMAGE_TAG}" compose_cmd \
--env-file "${shared_dir}/.env" \
-f "${release_dir}/deploy/docker-compose.yaml" \
$(compose_files) \
up --no-build --force-recreate migrate
fi
@@ -638,14 +731,14 @@ jobs:
IMAGE_REGISTRY="${REGISTRY_HOST}/${REGISTRY_OWNER}/${REGISTRY_IMAGE_PREFIX}" \
IMAGE_TAG="${IMAGE_TAG}" compose_cmd \
--env-file "${shared_dir}/.env" \
-f "${release_dir}/deploy/docker-compose.yaml" \
$(compose_files) \
pull ${SERVICE_LIST}
echo "Restarting selected service(s): ${SERVICE_LIST}"
IMAGE_REGISTRY="${REGISTRY_HOST}/${REGISTRY_OWNER}/${REGISTRY_IMAGE_PREFIX}" \
IMAGE_TAG="${IMAGE_TAG}" compose_cmd \
--env-file "${shared_dir}/.env" \
-f "${release_dir}/deploy/docker-compose.yaml" \
$(compose_files) \
up -d --no-build --no-deps --force-recreate ${SERVICE_LIST}
ln -sfn "${release_dir}" "${DEPLOY_BASE}/current"
@@ -653,7 +746,7 @@ jobs:
IMAGE_REGISTRY="${REGISTRY_HOST}/${REGISTRY_OWNER}/${REGISTRY_IMAGE_PREFIX}" \
IMAGE_TAG="${IMAGE_TAG}" compose_cmd \
--env-file "${shared_dir}/.env" \
-f "${release_dir}/deploy/docker-compose.yaml" \
$(compose_files) \
ps ${SERVICE_LIST}
if [ "${KEEP_RELEASES}" -gt 0 ] 2>/dev/null; then
+244
View File
@@ -697,6 +697,103 @@ jobs:
printf '%s:%s' "$(image_name "$1")" "${IMAGE_TAG}"
}
ensure_config_aliases() {
for yaml in config.yaml config.local.yaml ops-config.yaml ops-config.local.yaml; do
yml="${yaml%.yaml}.yml"
if [ ! -f "${release_dir}/deploy/${yaml}" ] && [ -f "${release_dir}/deploy/${yml}" ]; then
cp "${release_dir}/deploy/${yml}" "${release_dir}/deploy/${yaml}"
echo "Created ${yaml} from ${yml} for deployment mounts."
fi
done
if [ ! -f "${release_dir}/deploy/prompts.yml" ] && [ -f "${release_dir}/deploy/prompts.yaml" ]; then
cp "${release_dir}/deploy/prompts.yaml" "${release_dir}/deploy/prompts.yml"
echo "Created prompts.yml from prompts.yaml for deployment mounts."
fi
}
config_file_path() {
base="$1"
for file in "${release_dir}/deploy/${base}.yaml" "${release_dir}/deploy/${base}.yml"; do
if [ -f "${file}" ]; then
printf '%s' "${file}"
return 0
fi
done
}
read_object_storage_provider() {
provider="minio"
for file in "$(config_file_path config)" "$(config_file_path config.local)"; do
if [ -z "${file}" ] || [ ! -f "${file}" ]; then
continue
fi
next="$(
awk '
/^[[:space:]]*object_storage:[[:space:]]*$/ {
in_section=1
base=match($0, /[^[:space:]]/) - 1
next
}
in_section {
indent=match($0, /[^[:space:]]/) - 1
if ($0 ~ /^[[:space:]]*$/ || $0 ~ /^[[:space:]]*#/) {
next
}
if (indent <= base) {
in_section=0
next
}
if ($0 ~ /^[[:space:]]*provider[[:space:]]*:/) {
value=$0
sub(/^[[:space:]]*provider[[:space:]]*:[[:space:]]*/, "", value)
sub(/[[:space:]]+#.*$/, "", value)
gsub(/^["'\'']|["'\'']$/, "", value)
print value
}
}
' "${file}" | tail -1
)"
if [ -n "${next}" ]; then
provider="${next}"
fi
done
printf '%s' "$(printf '%s' "${provider}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
}
use_minio_for_object_storage() {
case "$(read_object_storage_provider)" in
""|minio|mino)
return 0
;;
aliyun|aliyun_oss|aliyun-oss|oss)
return 1
;;
*)
return 0
;;
esac
}
sync_k3s_config() {
mkdir -p "${release_dir}/deploy/k3s/config"
for name in config ops-config prompts; do
output="${name}.yaml"
if [ "${name}" = "prompts" ]; then
output="prompts.yml"
fi
source=""
for candidate in "${release_dir}/deploy/${name}.yaml" "${release_dir}/deploy/${name}.yml"; do
if [ -f "${candidate}" ]; then
source="${candidate}"
break
fi
done
if [ -n "${source}" ]; then
cp "${source}" "${release_dir}/deploy/k3s/config/${output}"
fi
done
}
echo "Sudo mode: ${SUDO_MODE}"
kubectl_cmd version --client
@@ -797,6 +894,128 @@ jobs:
sed -i "\#name: ${old_name}#,\#newTag:# s#newTag: .*#newTag: ${IMAGE_TAG}#" "${rendered_dir}/kustomization.yaml"
}
apply_object_storage_kustomize_mode() {
sync_k3s_config
provider="$(read_object_storage_provider)"
if use_minio_for_object_storage; then
echo "object_storage.provider=${provider}; deploying MinIO."
return 0
fi
echo "object_storage.provider=${provider}; skipping MinIO."
sed -i '/^patches:[[:space:]]*$/,/^[^[:space:]-]/{/^patches:[[:space:]]*$/d;/^[^[:space:]-]/!d;}' "${rendered_dir}/kustomization.yaml"
mkdir -p "${rendered_dir}/patches"
cat > "${rendered_dir}/patches/object-storage-env.yaml" <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: tenant-api
spec:
template:
spec:
containers:
- name: tenant-api
env:
- name: OBJECT_STORAGE_ACCESS_KEY
$patch: delete
- name: OBJECT_STORAGE_SECRET_KEY
$patch: delete
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker-generate
spec:
template:
spec:
containers:
- name: worker-generate
env:
- name: OBJECT_STORAGE_ACCESS_KEY
$patch: delete
- name: OBJECT_STORAGE_SECRET_KEY
$patch: delete
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kol-assist-worker
spec:
template:
spec:
containers:
- name: kol-assist-worker
env:
- name: OBJECT_STORAGE_ACCESS_KEY
$patch: delete
- name: OBJECT_STORAGE_SECRET_KEY
$patch: delete
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: scheduler
spec:
template:
spec:
containers:
- name: scheduler
env:
- name: OBJECT_STORAGE_ACCESS_KEY
$patch: delete
- name: OBJECT_STORAGE_SECRET_KEY
$patch: delete
EOF
cat >> "${rendered_dir}/kustomization.yaml" <<'EOF'
patches:
- target:
version: v1
kind: Service
name: minio
patch: |-
$patch: delete
apiVersion: v1
kind: Service
metadata:
name: minio
- target:
group: apps
version: v1
kind: StatefulSet
name: minio
patch: |-
$patch: delete
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: minio
- target:
group: batch
version: v1
kind: Job
name: minio-init
patch: |-
$patch: delete
apiVersion: batch/v1
kind: Job
metadata:
name: minio-init
- path: patches/object-storage-env.yaml
EOF
}
cleanup_minio_if_external_object_storage() {
if use_minio_for_object_storage; then
return 0
fi
kubectl_cmd -n "${K3S_NAMESPACE}" delete statefulset minio --ignore-not-found
kubectl_cmd -n "${K3S_NAMESPACE}" delete service minio --ignore-not-found
kubectl_cmd -n "${K3S_NAMESPACE}" delete job minio-init --ignore-not-found
for svc in tenant-api worker-generate kol-assist-worker scheduler; do
kubectl_cmd -n "${K3S_NAMESPACE}" set env "deployment/${svc}" OBJECT_STORAGE_ACCESS_KEY- OBJECT_STORAGE_SECRET_KEY- >/dev/null 2>&1 || true
done
}
render_kustomize() {
rm -rf "${rendered_dir}"
mkdir -p "${rendered_dir}"
@@ -808,6 +1027,7 @@ jobs:
# Migrations are applied explicitly by this workflow after preflight.
sed -i '/^[[:space:]]*- jobs.yaml[[:space:]]*$/d' "${rendered_dir}/kustomization.yaml"
apply_object_storage_kustomize_mode
patch_kustomize_image "geo-rankly/migrate" "migrate"
patch_kustomize_image "geo-rankly/tenant-api" "tenant-api"
@@ -832,6 +1052,7 @@ jobs:
}
apply_config_map() {
sync_k3s_config
kubectl_cmd -n "${K3S_NAMESPACE}" create configmap geo-rankly-app-config \
--from-file=config.yaml="${release_dir}/deploy/k3s/config/config.yaml" \
--from-file=prompts.yml="${release_dir}/deploy/k3s/config/prompts.yml" \
@@ -847,6 +1068,27 @@ jobs:
sed -i "s#image: geo-rankly/migrate:latest#image: $(image_ref migrate)#" "${rendered_jobs}"
kubectl_cmd -n "${K3S_NAMESPACE}" delete job migrate minio-init --ignore-not-found
if ! use_minio_for_object_storage; then
awk '
/^---[[:space:]]*$/ { doc=doc "\n" $0 "\n"; next }
{ doc=doc $0 "\n" }
END {
n=split(doc, docs, /\n---\n/)
for (i=1; i<=n; i++) {
if (docs[i] ~ /kind:[[:space:]]*Job/ && docs[i] ~ /name:[[:space:]]*minio-init/) {
continue
}
if (docs[i] ~ /[^[:space:]]/) {
if (printed) print "---"
printf "%s", docs[i]
if (substr(docs[i], length(docs[i]), 1) != "\n") print ""
printed=1
}
}
}
' "${rendered_jobs}" > "${rendered_jobs}.tmp"
mv "${rendered_jobs}.tmp" "${rendered_jobs}"
fi
kubectl_cmd -n "${K3S_NAMESPACE}" apply -f "${rendered_jobs}"
}
@@ -959,7 +1201,9 @@ jobs:
if [ -n "${IMAGE_LIST}" ]; then
ensure_plain_http_registry
fi
ensure_config_aliases
ensure_namespace
cleanup_minio_if_external_object_storage
if [ -n "${IMAGE_LIST}" ]; then
ensure_registry_secret
else