Files
LokiStack/kubernetes/install.sh
T
opsx c759e111ec feat(k8s): install.sh 三级部署预检——kubectl 缺失/连不上集群时给出可执行的具体修法
用户反馈:部署时报错只说「未找到 kubectl,请先安装」「kubectl 无法连接集群,
请检查 kubeconfig」,对新人等于没说 —— 不知道是没装工具、网络不通、凭据过期
还是 RBAC 没权限,也不知道下一步查什么。

原实现只有两行:
    command -v kubectl || die "未找到 kubectl,请先安装"
    kubectl version --request-timeout=5s || die "kubectl 无法连接集群,请检查 kubeconfig"

改为三级预检 + 一级软检查:

【0.1 kubectl 是否存在】
  按 CentOS/TencentOS、Ubuntu/Debian、macOS 分别给安装命令;
  额外给「机器没有外网」的替代路径(控制台下载 / 内网跳板机 / 只 scp 二进制)。

【0.2 kubeconfig 文件是否存在】← 新增,堵一个误判
  KUBECONFIG 指向不存在的文件时,kubectl 会忽略它并退回默认规则,
  最终报出 "Authentication required" / "connection refused" 这类误导性错误,
  让人白查凭据和网络。现在单独判一次,直接给出查找路径 + 各云控制台下载入口。
  支持 KUBECONFIG 用 : 分隔多路径,任一存在即通过。

【0.3 能否连上 API Server】按错误类型分类,各给专属修法
  · context 不存在      → get-contexts / use-context / 重新下载
  · 401 认证失败        → 云厂商 token 有效期几小时,重新下载;自建查客户端证书
  · 403 RBAC 拒绝       → auth whoami / auth can-i --list,列出部署所需的具体资源
  · x509 证书问题       → 确认证书过期;可信内网可临时 insecure-skip-tls-verify
  · 网络不通            → curl -k <API>/version、VPN/专线、安全组放行 6443、换跳板机
  · 其他                → 原样打印 kubectl 错误 + 通用排查清单

【0.4 部署权限软检查】auth can-i 非 yes 时只告警不阻断
  (首次部署时目标命名空间可能还不存在,硬拦会误伤正常流程)

失败时统一打印环境信息(kubeconfig 路径 / context / API Server / 当前用户),
一眼看出连的是哪个集群。连接超时可用 KUBECTL_TIMEOUT=15s 放宽(默认 8s)。

README 同步新增「前置条件」章节:预检项对照表 + 错误分类表 + 失败输出示例。

=== 验证 ===
从真实 install.sh 抽出预检段,用假 kubectl 精确模拟 8 种场景(两个脚本各跑一遍):
  A 没有 kubectl            → 退出 1,打印各系统安装命令 ✅
  B API Server 超时         → 退出 1,提示 VPN/专线/安全组 ✅
  C 没有 context            → 退出 1,提示 get-contexts/use-context ✅
  D 401 凭据过期            → 退出 1,提示重新下载 kubeconfig ✅
  E 403 权限不足            → 退出 1,列出所需资源权限 ✅
  F x509 证书过期           → 退出 1,提示证书/insecure 排障 ✅
  G 正常环境(真实集群)     → 通过预检,退出 0 ✅
  H kubeconfig 文件不存在   → 退出 1,提示下载位置(不误报成 401)✅
  结果:阻断 7/7、正常通过 1/1;bash -n 语法检查通过

⚠️ 踩坑记录:kubeconfig 多路径判断最初写成
     while IFS= read -r _p; do ...; done < <(printf "%s" "$PATHS" | tr ":" "\n")
   printf 输出没有结尾换行 → read 带着数据返回非 0(EOF)→ 循环体一次都不执行,
   导致所有场景都误报「找不到 kubeconfig」。改用 IFS=":" + for 拆分。
2026-10-28 07:25:15 +00:00

498 lines
24 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ============================================================================
# lokistack 一键部署
#
# 自动适配:阿里云 ACK / 腾讯云 TKE / AWS EKS / 华为云 CCE / GCP GKE / 自建 K8s
# 命名空间固定:log
# 幂等:重复执行不覆盖密钥、不重建数据卷
#
# 用法:
# bash kubernetes/install.sh # 自动探测
# WITH_NODEPORT=1 bash kubernetes/install.sh # 同时开 NodePort(配 ingress)
# STORAGE_CLASS=local-path bash kubernetes/install.sh # 手动指定 StorageClass
# ============================================================================
set -euo pipefail
NS="log"
WITH_NODEPORT="${WITH_NODEPORT:-0}"
STORAGE_CLASS="${STORAGE_CLASS:-}"
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
info() { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m✅\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m⚠️ \033[0m %s\n' "$*"; }
die() { printf '\033[1;31m❌\033[0m %s\n' "$*" >&2; exit 1; }
# ============================================================================
# 0) 前置检查
#
# 分三级:kubectl 是否存在 → 能否连上集群 → 有没有部署权限。
# 设计原则:每级失败都要说清「是哪一类问题 + 下一步具体做什么」。
# 只丢一句「请检查 kubeconfig」等于没说 —— 新人根本不知道该查什么,
# 也不知道是没装工具、网络不通、凭据过期、还是 RBAC 没权限。
# ============================================================================
KUBECTL_TIMEOUT="${KUBECTL_TIMEOUT:-8s}"
# 当前 context / server / user:失败时一起打印,一眼看出连的是哪个集群
k8s_env_info() {
local ctx server user kc
ctx=$(kubectl config current-context 2>/dev/null || echo "(无)")
server=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' 2>/dev/null || true)
user=$(kubectl config view --minify -o jsonpath='{.users[0].name}' 2>/dev/null || true)
kc="${KUBECONFIG:-$HOME/.kube/config}"
printf ' kubeconfig : %s\n' "$kc" >&2
printf ' 当前 context : %s\n' "$ctx" >&2
printf ' API Server : %s\n' "${server:-(未知)}" >&2
printf ' 当前用户 : %s\n' "${user:-(未知)}" >&2
}
# 统一失败出口:一句话结论 + 环境信息 + 可执行的处理步骤
k8s_die() {
printf '\n\033[1;31m❌ %s\033[0m\n\n' "$1" >&2
k8s_env_info
printf '\n%b\n\n' "$2" >&2
exit 1
}
# ── 0.1 kubectl 是否存在 ──────────────────────────────────────────────
if ! command -v kubectl >/dev/null 2>&1; then
printf '\n\033[1;31m❌ 找不到 kubectl 命令\033[0m\n\n' >&2
cat >&2 <<'KUBECTL_INSTALL_HELP'
本脚本通过 kubectl 操作 K8s 集群,需要先安装这个命令行工具。
── 安装方式 ───────────────────────────────────────────────────────
通用 Linux(官方二进制,最省事):
curl -LO "https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
CentOS / TencentOS / RHEL : sudo yum install -y kubectl
Ubuntu / Debian : sudo apt-get update && sudo apt-get install -y kubectl
macOS : brew install kubectl
── 装完自检 ───────────────────────────────────────────────────────
kubectl version --client
── 这台机器没有外网怎么办 ─────────────────────────────────────────
· 云控制台「集群 → 连接信息」一般可直接下载 kubectl 与 kubeconfig
· 或者在内网跳板机 / 能访问集群的机器上执行本脚本
· 只需把 kubectl 二进制 scp 过来即可,不依赖 Docker / Jenkins
KUBECTL_INSTALL_HELP
exit 1
fi
# ── 0.2 kubeconfig 文件是否存在 ────────────────────────────────────────
# 必须先单独判一次:KUBECONFIG 指向不存在的文件时,kubectl 会忽略它并
# 退回默认规则,最终报出的往往是 "Authentication required"/"connection refused"
# 这类**误导性**错误,让人以为是凭据或网络问题,实际只是配置没放对位置。
_KC_PATHS="${KUBECONFIG:-$HOME/.kube/config}"
# KUBECONFIG 允许用 ':' 分隔多个路径,任意一个存在即算通过。
# 注意不要写成 while read 循环:printf 输出没有结尾换行时,read 会
# 带着数据返回非 0(EOF),`while read` 的循环体一次都不会执行 —— 踩过。
_kc_found=0
_OLD_IFS="${IFS}"
IFS=':'
for _p in ${_KC_PATHS}; do
if [ -n "${_p}" ] && [ -f "${_p}" ]; then
_kc_found=1
break
fi
done
IFS="${_OLD_IFS}"
if [ "${_kc_found}" -eq 0 ]; then
k8s_die "找不到 kubeconfig 文件(不知道该连哪个集群)" \
" 查找位置: ${_KC_PATHS}
(KUBECONFIG 环境变量未设置时,默认只找 ~/.kube/config)
── 怎么修 ─────────────────────────────────────────────────────────
1) 从云控制台下载 kubeconfig:
腾讯云 TKE : 集群 → 基本信息 → 连接信息 → 下载 kubeconfig
阿里云 ACK : 集群 → 集群信息 → 连接信息 → 复制/下载 kubeconfig
AWS EKS : aws eks update-kubeconfig --name <集群名> --region <区域>
2) 放到默认位置(推荐):
mkdir -p ~/.kube && mv <下载的文件> ~/.kube/config && chmod 600 ~/.kube/config
3) 或者用环境变量指向任意路径:
export KUBECONFIG=/path/to/kubeconfig
4) 确认生效: kubectl config get-contexts
5) 重新执行本脚本"
fi
# ── 0.3 能否连上集群(探测一次,按错误类型分别给建议)────────────────
if ! PROBE_OUT="$(kubectl version --request-timeout="${KUBECTL_TIMEOUT}" 2>&1)"; then
case "${PROBE_OUT}" in
*"does not exist"*|*"no context exists"*|*"current-context is not set"*|*"context was not found"*)
k8s_die "kubeconfig 里没有可用的 context(集群配置不对)" \
" ── 怎么修 ─────────────────────────────────────────────────────────
1) 确认 kubeconfig 在哪: echo \$KUBECONFIG ls -l ~/.kube/config
2) 看有哪些 context: kubectl config get-contexts
3) 切到正确的那一个: kubectl config use-context <名字>
4) 完全没有配置就下载: 腾讯云 → 集群 → 基本信息 → 连接信息 → 下载 kubeconfig
放到 ~/.kube/config,或 export KUBECONFIG=/path/to/config
5) 重新执行本脚本" ;;
*"Unauthorized"*|*"Authentication required"*|*"credentials"*|*"invalid bearer token"*)
k8s_die "集群认证失败(凭据无效或已过期)" \
" ── 怎么修 ─────────────────────────────────────────────────────────
· 云厂商 kubeconfig 里一般是**临时 token**,有效期几小时,过期就 401
→ 回控制台重新下载 kubeconfig,覆盖 ~/.kube/config
· 自建集群看客户端证书是否过期:
kubectl config view --minify --raw | grep client-certificate-data
· 验证当前身份: kubectl auth whoami (旧版用 kubectl get ns 试)" ;;
*"Forbidden"*|*"(403)"*)
k8s_die "已经连上集群,但当前账号没有权限(RBAC 拒绝)" \
" ── 怎么修 ─────────────────────────────────────────────────────────
· 确认身份: kubectl auth whoami
· 看能干什么: kubectl auth can-i --list -n ${NS}
· 部署本平台需要在 ${NS} 命名空间具备:
deployments.apps / services / persistentvolumeclaims / configmaps / secrets 的 create、get
若该命名空间还不存在,还需要集群级 create namespaces
· 找集群管理员绑定具备上述权限的 Role,或直接用管理员 kubeconfig 执行本脚本" ;;
*"x509"*|*"certificate"*|*"tls:"*|*"TLS"*)
k8s_die "集群证书校验失败(证书过期 / 域名不匹配 / 自签证书)" \
" ── 怎么修 ─────────────────────────────────────────────────────────
· 云托管集群极少出现,多见于自建集群;先确认 API Server 证书是否过期
· 可信内网内临时跳过校验(仅用于排障,不要长期使用):
kubectl --insecure-skip-tls-verify=true get ns
· 检查 kubeconfig 里 server 的地址/域名是否在证书 SAN 内" ;;
*"connection refused"*|*"no such host"*|*"i/o timeout"*|*"context deadline exceeded"*|*"network is unreachable"*|*"Unable to connect to the server"*|*"timeout"*)
k8s_die "连不上集群 API Server(网络不通)" \
" 实际报错: $(printf '%s' "${PROBE_OUT}" | tail -1)
── 按顺序排查 ───────────────────────────────────────────────────────
1) 地址对不对?用上面打印的 API Server 测一下:
curl -k --max-time 5 <API Server>/version
2) 要不要 VPN / 专线?云上 API Server 常是**内网地址**(10.x / 172.16~31.x / 192.168.x),
本机不在同一 VPC 时,必须先连 VPN、走专线,或把本机 IP 加进安全组白名单
3) 安全组 / 防火墙:云厂商默认只放行运维网段访问 6443 端口
4) 集群本身是否正常:控制台看集群状态是否「运行中」
5) 换台机器试:在能访问集群的跳板机上执行本脚本,可排除本机网络问题" ;;
*)
k8s_die "kubectl 无法连接集群" \
" 实际报错:
$(printf '%s\n' "${PROBE_OUT}" | sed 's/^/ /')
── 通用排查 ─────────────────────────────────────────────────────────
· kubectl config get-contexts # 当前用的是哪个 context
· kubectl cluster-info # API Server 是否可达
· echo \$KUBECONFIG # 确认读到的是哪个 kubeconfig
· 容器/精简镜像里执行时,注意镜像内可能没有 ca-certificates 与 kubectl" ;;
esac
fi
# ── 0.4 部署权限预检(只告警,不阻断)────────────────────────────────
# 说明:第一次部署时目标命名空间可能还不存在,can-i 对命名空间级资源会返回 no,
# 为避免误拦正常流程这里只提示;真正的拒绝会在 apply 阶段以 Forbidden 暴露。
if [ "$(kubectl auth can-i create deployments.apps -n "${NS}" --request-timeout="${KUBECTL_TIMEOUT}" 2>/dev/null || echo unknown)" != "yes" ]; then
warn "当前账号在命名空间 ${NS} 未确认具备部署权限(kubectl auth can-i 非 yes)"
warn "若随后 apply 报 Forbidden:请改用管理员 kubeconfig,或让管理员绑定 Role 后再执行"
fi
# ============================================================================
# 0.1) 云平台识别(信息展示 + 后续诊断用)
# ============================================================================
detect_cloud() {
local pid=""
pid=$(kubectl get nodes -o jsonpath='{.items[0].spec.providerID}' 2>/dev/null || true)
case "${pid}" in
qcloud://*) echo "腾讯云 TKE" ;;
aws://*) echo "AWS EKS" ;;
aliyun://*) echo "阿里云 ACK" ;;
huaweicloud*|huawei*) echo "华为云 CCE" ;;
gce://*) echo "GCP GKE" ;;
azure://*) echo "Azure AKS" ;;
k3s://*) echo "K3s(自建)" ;;
*) echo "自建 / 其他 K8s" ;;
esac
}
# ============================================================================
# 0.2) StorageClass 探测
# 优先级:显式 STORAGE_CLASS > default 注解 > 唯一 SC > 失败
# 注意:结果写入全局变量(RESOLVED_SC / SC_ERROR),不要用 $(...) 调用,
# 否则子 shell 里的赋值无法回传
# ============================================================================
RESOLVED_SC=""
SC_ERROR=""
resolve_storage_class() {
local sc="" all="" count=0
RESOLVED_SC=""
SC_ERROR=""
# 1) 显式指定
if [ -n "${STORAGE_CLASS}" ]; then
if ! kubectl get sc "${STORAGE_CLASS}" >/dev/null 2>&1; then
SC_ERROR="指定的 StorageClass '${STORAGE_CLASS}' 不存在。现有:$(kubectl get sc -o jsonpath='{range .items[*]}{.metadata.name}{" "}{end}' 2>/dev/null)"
return 1
fi
RESOLVED_SC="${STORAGE_CLASS}"; return 0
fi
# 2) 带 default 注解的 SC
sc=$(kubectl get sc -o jsonpath='{range .items[?(@.metadata.annotations.storageclass\.kubernetes\.io/is-default-class=="true")]}{.metadata.name}{"\n"}{end}' 2>/dev/null | head -1 || true)
if [ -n "${sc}" ]; then RESOLVED_SC="${sc}"; return 0; fi
# 3) 集群里只有一个 SC → 直接用它
all=$(kubectl get sc -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)
count=$(printf '%s' "${all}" | grep -c . || true)
if [ "${count}" = "1" ]; then RESOLVED_SC="${all}"; return 0; fi
# 4) 无法确定
return 1
}
# ============================================================================
# 0.3) 自建集群兜底:无任何 StorageClass 时,创建「静态 local PV」
#
# 设计取舍:
# - 零外部依赖:不拉 local-path-provisioner 镜像(国内网络常常拉不到)
# - hostPath + DirectoryOrCreate:kubelet 自动建目录,无需预先 mkdir
# - nodeAffinity 锁节点:Pod 永远调度到数据所在节点
# - WaitForFirstConsumer:先定 Pod 再绑 PV,避免跨节点错绑
# - reclaimPolicy: Retain:误删 PVC 不丢数据
#
# 代价:数据固定在一台节点,节点损毁需从备份恢复(README 有 CSI 备份说明)
# ============================================================================
LOCAL_SC_NAME="lokistack-local"
LOCAL_PV_NAME="lokistack-data-pv"
LOCAL_PV_SIZE="${PV_SIZE:-30Gi}"
LOCAL_PV_PATH="${PV_PATH:-/var/lib/lokistack-data}"
pick_local_node() {
if [ -n "${NODE_NAME:-}" ]; then echo "${NODE_NAME}"; return 0; fi
kubectl get nodes --no-headers \
-o custom-columns=NAME:.metadata.name,READY:.status.conditions[-1].status 2>/dev/null \
| awk '$2=="True"{print $1; exit}'
}
create_local_storage() {
local node
node="$(pick_local_node)"
[ -n "${node}" ] || { warn "找不到 Ready 节点,无法创建本地存储"; return 1; }
info "创建节点本地存储:节点=${node} 路径=${LOCAL_PV_PATH} 容量=${LOCAL_PV_SIZE}"
cat <<EOF | kubectl apply -f - >/dev/null
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ${LOCAL_SC_NAME}
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
EOF
cat <<EOF | kubectl apply -f - >/dev/null
apiVersion: v1
kind: PersistentVolume
metadata:
name: ${LOCAL_PV_NAME}
labels:
app: lokistack
spec:
capacity:
storage: ${LOCAL_PV_SIZE}
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ${LOCAL_SC_NAME}
hostPath:
path: ${LOCAL_PV_PATH}
type: DirectoryOrCreate
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- ${node}
EOF
ok "已创建 StorageClass ${LOCAL_SC_NAME} + PV ${LOCAL_PV_NAME}(节点 ${node})"
warn "注意:数据固定在该节点 ${LOCAL_PV_PATH},Pod 不会漂移到其他节点"
RESOLVED_SC="${LOCAL_SC_NAME}"
return 0
}
CLOUD="$(detect_cloud)"
info "lokistack 部署开始(namespace: ${NS})"
info "集群: $(kubectl config current-context 2>/dev/null || echo unknown)"
info "平台: ${CLOUD}"
# LOCAL_STORAGE 三态:auto(默认,无 SC 时自动建)/ off(只提示)/ force(强制建,测试用)
LOCAL_STORAGE="${LOCAL_STORAGE:-auto}"
if [ "${LOCAL_STORAGE}" = "force" ]; then
warn "LOCAL_STORAGE=force:强制使用节点本地存储"
create_local_storage || die "创建节点本地存储失败"
elif ! resolve_storage_class; then
if [ -n "${SC_ERROR}" ]; then
die "${SC_ERROR}"
fi
echo ""
if [ "${LOCAL_STORAGE}" = "auto" ]; then
warn "集群没有可用 StorageClass(自建集群常见)→ 自动启用节点本地存储"
create_local_storage || die "创建节点本地存储失败"
else
warn "未找到可用的 StorageClass,且 LOCAL_STORAGE=off"
echo ""
echo " 现有 StorageClass:"
kubectl get sc 2>/dev/null | sed 's/^/ /' || echo " (无)"
echo ""
echo " 解决办法(选一个):"
echo " 1) 手动指定(推荐,若已有可用 SC):"
echo " STORAGE_CLASS=<sc名称> bash kubernetes/install.sh"
echo ""
echo " 2) 标记某个 SC 为默认:"
echo " kubectl patch sc <sc名称> -p '{\"metadata\":{\"annotations\":{\"storageclass.kubernetes.io/is-default-class\":\"true\"}}}'"
echo ""
echo " 3) 自建集群安装 local-path-provisioner(动态供给,支持多 PVC):"
echo " kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml"
echo " STORAGE_CLASS=local-path bash kubernetes/install.sh"
echo ""
echo " 4) 让脚本用节点本地存储(零依赖):"
echo " 去掉 LOCAL_STORAGE=off 重跑即可"
echo ""
die "StorageClass 未就绪,部署中止(避免 PVC 永久 Pending)"
fi
else
ok "StorageClass: ${RESOLVED_SC}"
fi
# ============================================================================
# 1) 命名空间
# ============================================================================
kubectl apply -f "${DIR}/namespace.yaml"
# ============================================================================
# 2) Secret(幂等:已存在则跳过,绝不覆盖)
# ============================================================================
rand_hex() {
if command -v openssl >/dev/null 2>&1; then
openssl rand -hex 32
elif [ -c /dev/urandom ]; then
head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n'
else
die "无法生成随机密钥(缺 openssl 且无 /dev/urandom)"
fi
}
if kubectl -n "${NS}" get secret lokistack-secrets >/dev/null 2>&1; then
info "Secret lokistack-secrets 已存在,跳过(不覆盖现有密钥)"
else
info "生成随机密钥 JWT_SECRET / ENCRYPTION_KEY"
kubectl -n "${NS}" create secret generic lokistack-secrets \
--from-literal=JWT_SECRET="$(rand_hex)" \
--from-literal=ENCRYPTION_KEY="$(rand_hex)" >/dev/null
ok "Secret 已创建"
fi
# ============================================================================
# 3) 数据卷(显式写入探测到的 StorageClass)
# ============================================================================
RENDER_DIR="$(mktemp -d)"
trap 'rm -rf "${RENDER_DIR}"' EXIT
awk -v sc="${RESOLVED_SC}" '
/^spec:/ && !done { print; print " storageClassName: " sc; done=1; next }
{ print }
' "${DIR}/pvc.yaml" > "${RENDER_DIR}/pvc.yaml"
kubectl apply -f "${RENDER_DIR}/pvc.yaml"
# ============================================================================
# 4) 工作负载 + 服务
# ============================================================================
kubectl apply -f "${DIR}/deployment.yaml"
kubectl apply -f "${DIR}/service.yaml"
if [ "${WITH_NODEPORT}" = "1" ]; then
kubectl apply -f "${DIR}/service-nodeport.yaml"
ok "NodePort Service 已创建(30080)"
fi
# ============================================================================
# 5) 等待就绪(失败时给出按云定制的诊断)
# ============================================================================
info "等待 Deployment 就绪(最多 180 秒)..."
if ! kubectl -n "${NS}" rollout status deployment/lokistack --timeout=180s; then
echo ""
warn "部署未在 180 秒内就绪,开始诊断:"
echo ""
kubectl -n "${NS}" get po -l app=lokistack -o wide 2>/dev/null | sed 's/^/ /'
REASON=$(kubectl -n "${NS}" get po -l app=lokistack -o jsonpath='{.items[0].status.containerStatuses[0].state.waiting.reason}' 2>/dev/null || true)
echo ""
case "${REASON}" in
ImagePullBackOff|ErrImagePull)
warn "镜像拉取失败(${REASON})"
echo " → 当前镜像: $(grep -E '^\s+image:' "${DIR}/deployment.yaml" | head -1 | awk '{print $2}')"
echo " → 该节点拉不到 Docker Hub。请改为目标云可拉的仓库后重跑:"
echo " 腾讯云 TCR : ccr.ccs.tencentyun.com/<ns>/lokistack:latest"
echo " 阿里云 ACR : registry.cn-hangzhou.aliyuncs.com/<ns>/lokistack:latest"
echo " 华为云 SWR : swr.cn-north-4.myhuaweicloud.com/<ns>/lokistack:latest"
echo " 自建 Harbor: harbor.your.domain/library/lokistack:latest"
;;
CreateContainerConfigError)
warn "容器配置缺失(通常是 Secret 不存在)"
echo " → 检查: kubectl -n ${NS} get secret lokistack-secrets"
;;
"")
PVC_PHASE=$(kubectl -n "${NS}" get pvc lokistack-data -o jsonpath='{.status.phase}' 2>/dev/null || echo unknown)
if [ "${PVC_PHASE}" != "Bound" ]; then
warn "PVC 未绑定(phase=${PVC_PHASE})"
echo " → 检查 StorageClass: kubectl -n ${NS} describe pvc lokistack-data"
echo " → ${CLOUD} 建议的 SC:"
case "${CLOUD}" in
腾讯云*) echo " cbs" ;;
AWS*) echo " gp3 / gp2" ;;
阿里云*) echo " alicloud-disk-essd" ;;
华为云*) echo " csi-disk" ;;
*) echo " local-path(需装 local-path-provisioner)" ;;
esac
fi
;;
esac
echo ""
kubectl -n "${NS}" describe po -l app=lokistack 2>/dev/null | tail -15 | sed 's/^/ /'
die "部署未完成,请按上面提示处理"
fi
ok "Deployment 已就绪"
# ============================================================================
# 6) 输出访问信息
# ============================================================================
POD=$(kubectl -n "${NS}" get po -l app=lokistack -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "-")
POD_IP=$(kubectl -n "${NS}" get po -l app=lokistack -o jsonpath='{.items[0].status.podIP}' 2>/dev/null || echo "-")
NODE=$(kubectl -n "${NS}" get po -l app=lokistack -o jsonpath='{.items[0].spec.nodeName}' 2>/dev/null || echo "-")
echo ""
echo "════════════════════════════════════════════════════════"
ok "lokistack 部署完成"
echo "════════════════════════════════════════════════════════"
echo " 平台 : ${CLOUD}"
echo " Namespace : ${NS}"
echo " 存储类 : ${RESOLVED_SC}"
echo " Pod : ${POD} (node=${NODE} ip=${POD_IP})"
echo " Service : lokistack.${NS}.svc.cluster.local:8080"
[ "${WITH_NODEPORT}" = "1" ] && echo " NodePort : <任意节点IP>:30080"
echo ""
echo " 初始账号 : admin / admin123 ← 首次登录请立即改密"
echo ""
echo " 本地访问 : kubectl -n ${NS} port-forward svc/lokistack 8080:8080"
echo " 浏览器打开 http://localhost:8080"
echo ""
echo " 配 Ingress(可选,先跑 WITH_NODEPORT=1 bash install.sh):"
case "${CLOUD}" in
腾讯云*) echo " kubectl apply -f ${DIR}/examples/ingress-tke.yaml # qcloud class" ;;
阿里云*) echo " kubectl apply -f ${DIR}/examples/ingress.yaml # alb class" ;;
*) echo " 参照 ${DIR}/examples/ 改 ingressClassName 与注解" ;;
esac
echo "════════════════════════════════════════════════════════"