cert-manager、CloudNativePG 与 Barman 清单改用 server-side apply,避免 CNPG CRD 的 last-applied annotation 超过 Kubernetes 256KiB 限制;force-conflicts 仅用于已固定 checksum 的 operator 清单。 验证:cert-manager 组件已实际滚动成功,CNPG 客户端 apply 在 annotation 门禁处复现。
228 lines
9.3 KiB
Bash
Executable File
228 lines
9.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
# shellcheck source=scripts/cluster/common.sh
|
|
source "$script_dir/common.sh"
|
|
|
|
mode=${1:-}
|
|
release_manifest=${2:-}
|
|
[[ $mode == prepare || $mode == activate ]] || {
|
|
echo 'usage: bootstrap-platform.sh {prepare|activate} dist/releases/<sha>.json' >&2
|
|
exit 64
|
|
}
|
|
[[ -f $release_manifest && ! -L $release_manifest ]] || {
|
|
echo 'release manifest must be a regular file' >&2
|
|
exit 1
|
|
}
|
|
|
|
load_cluster_env
|
|
require_commands curl kubectl node openssl rg shasum
|
|
|
|
node "$cluster_root/scripts/release-manifest.mjs" validate "$release_manifest" >/dev/null
|
|
api_image=$(node "$cluster_root/scripts/release-manifest.mjs" get "$release_manifest" images.api)
|
|
web_image=$(node "$cluster_root/scripts/release-manifest.mjs" get "$release_manifest" images.web)
|
|
source_sha=$(node "$cluster_root/scripts/release-manifest.mjs" get "$release_manifest" sourceSha)
|
|
[[ $api_image =~ ^registry\.cn-shanghai\.aliyuncs\.com/easyaigc/ai-gateway@sha256:[0-9a-f]{64}$ ]]
|
|
[[ $web_image =~ ^registry\.cn-shanghai\.aliyuncs\.com/easyaigc/ai-gateway-web@sha256:[0-9a-f]{64}$ ]]
|
|
|
|
secret_root="$cluster_root/.local-secrets/cluster"
|
|
postgres_environment="$secret_root/postgres.env"
|
|
working_directory=$(mktemp -d "$secret_root/platform.XXXXXX")
|
|
trap '[[ $working_directory == "$secret_root"/platform.* ]] && rm -rf -- "$working_directory"' EXIT HUP INT TERM
|
|
install -d -m 0700 "$secret_root"
|
|
|
|
if [[ ! -f $postgres_environment ]]; then
|
|
umask 077
|
|
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 32)" >"$postgres_environment"
|
|
chmod 0600 "$postgres_environment"
|
|
fi
|
|
assert_private_file "$postgres_environment"
|
|
# shellcheck source=/dev/null
|
|
source "$postgres_environment"
|
|
: "${POSTGRES_PASSWORD:?}"
|
|
[[ $POSTGRES_PASSWORD =~ ^[0-9a-f]{64}$ ]] || {
|
|
echo 'invalid generated PostgreSQL password' >&2
|
|
exit 1
|
|
}
|
|
|
|
operators=(cert-manager.yaml cnpg.yaml barman.yaml)
|
|
operator_urls=(
|
|
https://github.com/cert-manager/cert-manager/releases/download/v1.21.0/cert-manager.yaml
|
|
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.29/releases/cnpg-1.29.1.yaml
|
|
https://github.com/cloudnative-pg/plugin-barman-cloud/releases/download/v0.13.0/manifest.yaml
|
|
)
|
|
operator_checksums=(
|
|
6e499c3f1ab356abe79a7853911f80cb09c213885bfdf81092fdff142ba63c4a
|
|
e0c5ff41bf5b01c0775bf225929b8423d543cd23e9742e4274fe7de8499cf93a
|
|
d2e71e7b06822448f1a421f05781846cfdb9cc621e7ef32eef5e20c5133213b0
|
|
)
|
|
for index in 0 1 2; do
|
|
operator=${operators[$index]}
|
|
curl -fsSL "${operator_urls[$index]}" -o "$working_directory/$operator"
|
|
actual_checksum=$(shasum -a 256 "$working_directory/$operator" | awk '{print $1}')
|
|
[[ $actual_checksum == "${operator_checksums[$index]}" ]] || {
|
|
echo "operator manifest checksum mismatch: $operator" >&2
|
|
exit 1
|
|
}
|
|
done
|
|
|
|
cp -R "$cluster_root/deploy/kubernetes/production" "$working_directory/production"
|
|
api_digest=${api_image##*@}
|
|
web_digest=${web_image##*@}
|
|
awk -v api_digest="$api_digest" -v web_digest="$web_digest" '
|
|
$3 == "easyai-api" { image = "api" }
|
|
$3 == "easyai-web" { image = "web" }
|
|
$1 == "digest:" && image == "api" { print " digest: " api_digest; image = ""; next }
|
|
$1 == "digest:" && image == "web" { print " digest: " web_digest; image = ""; next }
|
|
{ print }
|
|
' "$working_directory/production/kustomization.yaml" \
|
|
>"$working_directory/production/kustomization.yaml.next"
|
|
mv "$working_directory/production/kustomization.yaml.next" \
|
|
"$working_directory/production/kustomization.yaml"
|
|
|
|
if [[ $mode == prepare ]]; then
|
|
sed -i.bak 's/^ replicas: 1$/ replicas: 0/' "$working_directory/production/application.yaml"
|
|
rm -f -- "$working_directory/production/application.yaml.bak"
|
|
fi
|
|
if [[ ${AI_GATEWAY_ACTIVATE_WORKERS:-true} == false ]]; then
|
|
sed -i.bak \
|
|
's/^ AI_GATEWAY_ASYNC_QUEUE_WORKER_ENABLED: "true"$/ AI_GATEWAY_ASYNC_QUEUE_WORKER_ENABLED: "false"/' \
|
|
"$working_directory/production/application-config.yaml"
|
|
rm -f -- "$working_directory/production/application-config.yaml.bak"
|
|
fi
|
|
kubectl kustomize "$working_directory/production" >"$working_directory/easyai-production.yaml"
|
|
if rg -q 'sha256:0{64}|image: (easyai-api|easyai-web)$' "$working_directory/easyai-production.yaml"; then
|
|
echo 'application images were not digest-pinned during rendering' >&2
|
|
exit 1
|
|
fi
|
|
if rg -i -q '^[[:space:]]*(password|secret|token|credential|private.?key):[[:space:]]+[^<{[:space:]]' \
|
|
"$working_directory/easyai-production.yaml"; then
|
|
echo 'rendered production manifests appear to contain a literal secret' >&2
|
|
exit 1
|
|
fi
|
|
|
|
umask 077
|
|
cat >"$working_directory/bootstrap-secrets.yaml" <<EOF
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: easyai-postgres-app
|
|
namespace: easyai
|
|
type: kubernetes.io/basic-auth
|
|
stringData:
|
|
username: easyai
|
|
password: ${POSTGRES_PASSWORD}
|
|
---
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: easyai-oss-backup
|
|
namespace: easyai
|
|
type: Opaque
|
|
stringData:
|
|
ACCESS_KEY_ID: ${ALI_KEY}
|
|
ACCESS_SECRET_KEY: ${ALI_SECRET}
|
|
EOF
|
|
chmod 0600 "$working_directory/bootstrap-secrets.yaml"
|
|
|
|
for file in cert-manager.yaml cnpg.yaml barman.yaml easyai-production.yaml bootstrap-secrets.yaml; do
|
|
cluster_scp "$working_directory/$file" "$CLUSTER_NINGBO_HOST:/root/$file"
|
|
done
|
|
|
|
cluster_ssh "$CLUSTER_NINGBO_HOST" bash -s -- "$source_sha" "$mode" <<'REMOTE'
|
|
set -euo pipefail
|
|
source_sha=$1
|
|
mode=$2
|
|
kubectl='k3s kubectl'
|
|
|
|
$kubectl apply --server-side --force-conflicts -f /root/cert-manager.yaml >/dev/null
|
|
$kubectl rollout status deployment/cert-manager -n cert-manager --timeout=180s
|
|
$kubectl rollout status deployment/cert-manager-webhook -n cert-manager --timeout=180s
|
|
$kubectl rollout status deployment/cert-manager-cainjector -n cert-manager --timeout=180s
|
|
$kubectl apply --server-side --force-conflicts -f /root/cnpg.yaml >/dev/null
|
|
$kubectl rollout status deployment/cnpg-controller-manager -n cnpg-system --timeout=180s
|
|
$kubectl apply --server-side --force-conflicts -f /root/barman.yaml >/dev/null
|
|
$kubectl rollout status deployment/barman-cloud -n cnpg-system --timeout=180s
|
|
|
|
$kubectl create namespace easyai --dry-run=client -o yaml | $kubectl apply -f - >/dev/null
|
|
$kubectl apply -f /root/bootstrap-secrets.yaml >/dev/null
|
|
postgres_password=$($kubectl get secret easyai-postgres-app -n easyai \
|
|
-o jsonpath='{.data.password}' | base64 -d)
|
|
|
|
runtime_environment=$(mktemp)
|
|
trap 'rm -f -- "$runtime_environment"' EXIT
|
|
chmod 0600 "$runtime_environment"
|
|
docker inspect easyai-ai-gateway-api-1 \
|
|
--format '{{range .Config.Env}}{{println .}}{{end}}' |
|
|
grep -E '^(CONFIG_JWT_SECRET|SERVER_MAIN_INTERNAL_TOKEN|SERVER_MAIN_INTERNAL_KEY|SERVER_MAIN_INTERNAL_SECRET)=' \
|
|
>"$runtime_environment"
|
|
printf 'AI_GATEWAY_DATABASE_URL=postgresql://easyai:%s@easyai-postgres-rw:5432/easyai_ai_gateway?sslmode=require\n' \
|
|
"$postgres_password" >>"$runtime_environment"
|
|
[[ $(grep -c '^[A-Z0-9_]*=' "$runtime_environment") -eq 5 ]] || {
|
|
echo 'runtime Secret allowlist is incomplete' >&2
|
|
exit 1
|
|
}
|
|
$kubectl create secret generic easyai-ai-gateway-runtime -n easyai \
|
|
--from-env-file="$runtime_environment" --dry-run=client -o yaml |
|
|
$kubectl apply -f - >/dev/null
|
|
|
|
identity_directory=/var/lib/docker/volumes/easyai-ai-gateway_api_data/_data/identity/secrets
|
|
[[ -d $identity_directory && ! -L $identity_directory ]] || {
|
|
echo 'legacy identity SecretStore directory is unavailable' >&2
|
|
exit 1
|
|
}
|
|
mapfile -d '' identity_files < <(find "$identity_directory" -maxdepth 1 -type f -print0)
|
|
[[ ${#identity_files[@]} -eq 4 ]] || {
|
|
echo 'identity SecretStore file count changed from the accepted baseline' >&2
|
|
exit 1
|
|
}
|
|
identity_bytes=0
|
|
identity_arguments=()
|
|
for identity_file in "${identity_files[@]}"; do
|
|
identity_name=$(basename "$identity_file")
|
|
[[ $identity_name =~ ^[A-Za-z0-9._-]+$ ]] || {
|
|
echo 'identity SecretStore contains an invalid key name' >&2
|
|
exit 1
|
|
}
|
|
identity_bytes=$((identity_bytes + $(stat -c '%s' "$identity_file")))
|
|
identity_arguments+=("--from-file=$identity_name=$identity_file")
|
|
done
|
|
[[ $identity_bytes -eq 247 ]] || {
|
|
echo 'identity SecretStore byte count changed from the accepted baseline' >&2
|
|
exit 1
|
|
}
|
|
$kubectl create secret generic easyai-gateway-security-events -n easyai \
|
|
"${identity_arguments[@]}" --dry-run=client -o yaml |
|
|
$kubectl apply -f - >/dev/null
|
|
|
|
registry_config=/root/.docker/config.json
|
|
[[ -f $registry_config && ! -L $registry_config ]] || {
|
|
echo 'Docker registry config is unavailable on Ningbo' >&2
|
|
exit 1
|
|
}
|
|
$kubectl create secret generic easyai-registry -n easyai \
|
|
--type=kubernetes.io/dockerconfigjson \
|
|
--from-file=.dockerconfigjson="$registry_config" --dry-run=client -o yaml |
|
|
$kubectl apply -f - >/dev/null
|
|
|
|
$kubectl apply -f /root/easyai-production.yaml >/dev/null
|
|
$kubectl annotate namespace easyai easyai.io/release="$source_sha" --overwrite >/dev/null
|
|
if [[ $mode == activate ]]; then
|
|
$kubectl rollout status deployment/easyai-api-hongkong -n easyai --timeout=300s
|
|
$kubectl rollout status deployment/easyai-web-hongkong -n easyai --timeout=300s
|
|
$kubectl rollout status deployment/easyai-api-ningbo -n easyai --timeout=300s
|
|
$kubectl rollout status deployment/easyai-web-ningbo -n easyai --timeout=300s
|
|
fi
|
|
|
|
rm -f -- /root/cert-manager.yaml /root/cnpg.yaml /root/barman.yaml \
|
|
/root/easyai-production.yaml /root/bootstrap-secrets.yaml
|
|
REMOTE
|
|
|
|
if [[ $mode == prepare ]]; then
|
|
cluster_ssh "$CLUSTER_NINGBO_HOST" \
|
|
'k3s kubectl wait --for=condition=Ready cluster/easyai-postgres -n easyai --timeout=600s'
|
|
fi
|
|
|
|
echo "platform_bootstrap=PASS mode=$mode release=$source_sha"
|